diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 154736769fe..c7339cdb3a9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1268,6 +1268,9 @@ embed.go @grafana/grafana-as-code /.github/workflows/pr-e2e-tests.yml @grafana/grafana-developer-enablement-squad /.github/workflows/skye-add-to-project.yml @grafana/grafana-frontend-platform /.github/workflows/frontend-perf-tests.yaml @grafana/grafana-frontend-platform +/.github/workflows/release-npm.yml @grafana/grafana-frontend-platform +/.github/workflows/scripts/determine-npm-tag.sh @grafana/grafana-frontend-platform +/.github/workflows/scripts/validate-commit-in-head.sh @grafana/grafana-frontend-platform /.github/zizmor.yml @grafana/grafana-developer-enablement-squad /.github/license_finder.yaml @bergquist /.github/actionlint.yaml @grafana/grafana-developer-enablement-squad diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index f03315fd456..4ee871e88b5 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -271,43 +271,18 @@ jobs: docker manifest push "grafana/grafana-dev:${VERSION}" docker manifest push "grafana/grafana-dev:${VERSION}-ubuntu" - publish-npm: + publish-npm-canaries: if: github.ref_name == 'main' + name: Publish NPM canaries + uses: ./.github/workflows/release-npm.yml permissions: contents: read id-token: write - # NPM Trusted Publishing does not yet support self-hosted runners - runs-on: github-hosted-ubuntu-x64-small needs: - setup - build - env: - PACKAGES_VERSION: ${{ needs.setup.outputs.version }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - persist-credentials: false - - # Setting nodejs up just for npm publish. Not restoring the cache for all our dependencies - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - - # Trusted Publishing is only available in npm v11.5.1 and later - - name: Update npm - run: npm install -g npm@^11.5.1 - - - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 - with: - name: artifacts-linux-amd64 - path: dist - - - name: Copy packages - run: mv dist/"${PACKAGES_VERSION}"/npm-packages npm-artifacts - - - name: Publish to NPM - env: - NPM_TOKEN: "oidc" - run: ./scripts/publish-npm-packages.sh --dist-tag 'canary' --registry 'https://registry.npmjs.org/' + with: + grafana_commit: ${{ needs.setup.outputs.grafana-commit }} + version: ${{ needs.setup.outputs.version }} + build_id: ${{ github.run_id }} + version_type: "canary" diff --git a/.github/workflows/release-npm.yml b/.github/workflows/release-npm.yml new file mode 100644 index 00000000000..284e7558f03 --- /dev/null +++ b/.github/workflows/release-npm.yml @@ -0,0 +1,135 @@ +name: Publish NPM packages +on: + workflow_call: + inputs: + grafana_commit: + description: 'Grafana commit SHA to build against' + required: true + type: string + version: + description: 'Version to publish as' + required: true + type: string + build_id: + description: 'Run ID from the original release-build workflow' + required: true + type: string + version_type: + description: 'Version type (canary, nightly, stable)' + required: true + type: string + + workflow_dispatch: + inputs: + grafana_commit: + description: 'Grafana commit SHA to build against' + required: true + version: + description: 'Version to publish as' + required: true + build_id: + description: 'Run ID from the original release-build workflow' + required: true + version_type: + description: 'Version type (canary, nightly, stable)' + required: true + +permissions: {} + +jobs: + # If called with version_type 'canary' or 'stable', build + publish to NPM + # If called with version_type 'nightly', just tag the given version with nightly tag. It was already published by the canary build. + + publish: + name: Publish NPM packages + runs-on: github-hosted-ubuntu-x64-small + if: inputs.version_type == 'canary' || inputs.version_type == 'stable' + permissions: + contents: read + id-token: write + steps: + - name: Info + env: + GITHUB_REF: ${{ github.ref }} + run: | + echo "GRAFANA_COMMIT: $GRAFANA_COMMIT" + echo "github.ref: $GITHUB_REF" + + - name: Checkout workflow ref + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Verify commit is in workflow HEAD + env: + GIT_COMMIT: ${{ github.event.inputs.grafana_commit }} + run: ./github/workflows/scripts/validate-commit-in-head.sh + shell: bash + + - name: Map version type to NPM tag + id: npm-tag + env: + VERSION_TYPE: ${{ github.event.inputs.version_type }} + run: | + TAG=$(./github/workflows/scripts/determine-npm-tag.sh) + echo "NPM_TAG=$TAG" >> "$GITHUB_OUTPUT" + shell: bash + + - name: Checkout build commit + uses: actions/checkout@v4 + with: + persist-credentials: false + ref: ${{ github.event.inputs.grafana_commit }} + + - name: Setup Node + uses: ./github/actions/setup-node + + - name: Install dependencies + run: yarn install --immutable + + - name: Typecheck packages + run: yarn run packages:typecheck + + - name: Version, build, and pack packages + run: | + yarn run packages:build + yarn lerna version "$PACKAGES_VERSION" \ + --exact \ + --no-git-tag-version \ + --no-push \ + --force-publish \ + --yes + yarn run packages:pack + + - name: Debug packed files + run: tree -a ./npm-artifacts + + - name: Validate packages + run: ./scripts/validate-npm-packages.sh + + - name: Debug OIDC Claims + uses: github/actions-oidc-debugger@018a1dc4f8e47adca924d55e4bb0ddce917af32d + with: + audience: '${{ github.server_url }}/${{ github.repository_owner }}' + + - name: Publish packages + env: + NPM_TAG: ${{ steps.npm-tag.outputs.NPM_TAG }} + run: ./scripts/publish-npm-packages.sh --dist-tag "$NPM_TAG" --registry 'https://registry.npmjs.org/' + + # TODO: finish this step + tag-nightly: + name: Tag nightly release + runs-on: github-hosted-ubuntu-x64-small + needs: publish + if: inputs.version_type == 'nightly' + + steps: + - name: Checkout workflow ref + uses: actions/checkout@v4 + with: + persist-credentials: false + + # TODO: tag the given release with nightly + + diff --git a/.github/workflows/scripts/determine-npm-tag.sh b/.github/workflows/scripts/determine-npm-tag.sh new file mode 100755 index 00000000000..b603eb11211 --- /dev/null +++ b/.github/workflows/scripts/determine-npm-tag.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +set -euo pipefail + +fail() { echo "Error: $*" >&2; exit 1; } + +# Ensure required variables are set +if [[ -z "${REFERENCE_PKG}" || -z "${VERSION_TYPE}" || -z "${VERSION}" ]]; then + fail "Missing required environment variables: REFERENCE_PKG, VERSION_TYPE, VERSION" +fi + +semver_cmp () { + IFS='.' read -r -a arr_a <<< "$1" + IFS='.' read -r -a arr_b <<< "$2" + + for i in 0 1 2; do + local aa=${arr_a[i]:-0} + local bb=${arr_b[i]:-0} + # shellcheck disable=SC2004 + if (( 10#$aa > 10#$bb )); then echo gt; return 0; fi + if (( 10#$aa < 10#$bb )); then echo lt; return 0; fi + done + + echo "eq" +} + + +STABLE_REGEX='^([0-9]+)\.([0-9]+)\.([0-9]+)$' # x.y.z +PRE_REGEX='^([0-9]+)\.([0-9]+)\.([0-9]+)-([0-9]+)$' # x.y.z-123456 + +# Validate that the VERSION matches VERSION_TYPE +# - stable must be x.y.z +# - nightly/canary must be x.y.z-123456 +case "$VERSION_TYPE" in + stable) + [[ $VERSION =~ $STABLE_REGEX ]] || fail "For 'stable', version must match x.y.z" ;; + nightly|canary) + [[ $VERSION =~ $PRE_REGEX ]] || fail "For '$VERSION_TYPE', version must match x.y.z-123456" ;; + *) + fail "Unknown version_type '$VERSION_TYPE'" ;; +esac + +# Extract major, minor from VERSION +IFS=.- read -r major minor patch _ <<< "$VERSION" + +# Determine NPM tag +case "$VERSION_TYPE" in + canary) TAG="canary" ;; + nightly) TAG="nightly" ;; + stable) + # Use npm dist-tag "latest" as the reference + LATEST="$(npm view --silent "$REFERENCE_PKG" dist-tags.latest 2>/dev/null || true)" + echo "Latest for $REFERENCE_PKG is ${LATEST:-}" >&2 + + if [[ -z ${LATEST:-} ]]; then + TAG="latest" # first ever publish + else + case "$(semver_cmp "$VERSION" "$LATEST")" in + gt) TAG="latest" ;; # newer than reference -> latest + lt|eq) TAG="v${major}.${minor}-latest" ;; # older or equal -> vX.Y-latest + esac + fi + ;; +esac + +echo "Resolved NPM_TAG=$TAG (VERSION=$VERSION, current latest=${LATEST:-none})" 1>&2 # stderr +printf '%s' "$TAG" diff --git a/.github/workflows/scripts/validate-commit-in-head.sh b/.github/workflows/scripts/validate-commit-in-head.sh new file mode 100755 index 00000000000..370b2fbd4af --- /dev/null +++ b/.github/workflows/scripts/validate-commit-in-head.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ -z "${GIT_COMMIT:-}" ]]; then + echo "Error: Environment variable GIT_COMMIT is required" + exit 1 +fi + +if git merge-base --is-ancestor "$GIT_COMMIT" HEAD; then + echo "Commit $GIT_COMMIT is contained in HEAD" +else + echo "Error: Commit $GIT_COMMIT is not contained in HEAD" + exit 1 +fi diff --git a/scripts/publish-npm-packages.sh b/scripts/publish-npm-packages.sh index 80a3aed32cd..eaa2e595824 100755 --- a/scripts/publish-npm-packages.sh +++ b/scripts/publish-npm-packages.sh @@ -5,9 +5,11 @@ dist_tag="canary" registry="http://localhost:4873" -if [ -z "$NPM_TOKEN" ]; then - echo "The NPM_TOKEN environment variable does not exist." - exit 1 +# Require either ACTIONS_ID_TOKEN_REQUEST_URL or NPM_TOKEN to be set +if [ -z "$ACTIONS_ID_TOKEN_REQUEST_URL" ] && [ -z "$NPM_TOKEN" ]; then + echo "ERROR: Either ACTIONS_ID_TOKEN_REQUEST_URL or NPM_TOKEN environment variable must be set." + echo "If running in Github Actions, ensure that 'id-token: write' permission is granted." + exit 1 fi # Parse command line arguments @@ -33,7 +35,8 @@ done echo "Starting to release $dist_tag version with NPM version $(npm --version) to registry $registry" -if [[ "$NPM_TOKEN" != "oidc" ]]; then +if [ -n "$NPM_TOKEN" ]; then + echo "Configured NPM_TOKEN in ~/.npmrc" registry_without_protocol=${registry#*:} echo "$registry_without_protocol/:_authToken=${NPM_TOKEN}" >> ~/.npmrc fi