Publish Stable Release #18
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Publish Stable Release | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| release_sha: | |
| description: Full commit SHA to publish | |
| required: true | |
| type: string | |
| concurrency: | |
| group: publish-stable-release-${{ inputs.release_sha }} | |
| cancel-in-progress: false | |
| env: | |
| HUSKY: "0" | |
| NPM_CONFIG_PROVENANCE: "true" | |
| jobs: | |
| validate-release-sha: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| outputs: | |
| release_sha: ${{ steps.release.outputs.release_sha }} | |
| steps: | |
| - name: Validate release SHA | |
| id: release | |
| env: | |
| RELEASE_SHA: ${{ inputs.release_sha }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "$RELEASE_SHA" ]; then | |
| echo "Stable releases must provide the commit SHA to publish in release_sha." >&2 | |
| exit 1 | |
| fi | |
| if ! printf '%s\n' "$RELEASE_SHA" | grep -Eq '^[0-9a-fA-F]{40}$'; then | |
| echo "release_sha must be the full 40-character commit SHA." >&2 | |
| exit 1 | |
| fi | |
| release_sha="$(printf '%s\n' "$RELEASE_SHA" | tr '[:upper:]' '[:lower:]')" | |
| echo "release_sha=$release_sha" >> "$GITHUB_OUTPUT" | |
| print-changelog-links: | |
| needs: | |
| - validate-release-sha | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Print changelog links | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| GH_REPO: ${{ github.repository }} | |
| RELEASE_SHA: ${{ needs.validate-release-sha.outputs.release_sha }} | |
| GITHUB_SERVER_URL: ${{ github.server_url }} | |
| run: | | |
| set -euo pipefail | |
| latest_release_tag="$(gh api "repos/$GH_REPO/releases/latest" --jq '.tag_name')" | |
| encoded_latest_release_tag="$(jq -rn --arg tag "$latest_release_tag" '$tag|@uri')" | |
| diff_url="$GITHUB_SERVER_URL/$GH_REPO/compare/$encoded_latest_release_tag...$RELEASE_SHA" | |
| { | |
| echo "Release SHA: $RELEASE_SHA" | |
| echo "Diff to latest release: $diff_url" | |
| } | tee -a "$GITHUB_STEP_SUMMARY" | |
| publish-stable: | |
| needs: | |
| - validate-release-sha | |
| - print-changelog-links | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: write | |
| issues: write | |
| id-token: write | |
| pull-requests: read | |
| environment: npm-publish | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ needs.validate-release-sha.outputs.release_sha }} | |
| - name: Validate release checkout | |
| env: | |
| RELEASE_SHA: ${{ needs.validate-release-sha.outputs.release_sha }} | |
| run: | | |
| set -euo pipefail | |
| checked_out_sha="$(git rev-parse HEAD | tr '[:upper:]' '[:lower:]')" | |
| if [ "$checked_out_sha" != "$RELEASE_SHA" ]; then | |
| echo "Checked out $checked_out_sha, expected $RELEASE_SHA." >&2 | |
| exit 1 | |
| fi | |
| - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version-file: .tool-versions | |
| cache: pnpm | |
| registry-url: https://registry.npmjs.org | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Validate package metadata | |
| run: node scripts/release/validate-publishable-packages.mjs | |
| - name: Detect stable publish work | |
| id: detect | |
| run: node scripts/release/release-manifest.mjs --mode stable --output .release-manifest.json | |
| - name: Build packages | |
| if: steps.detect.outputs.needs_publish == 'true' | |
| run: pnpm run build | |
| - name: Publish stable packages to npm | |
| if: steps.detect.outputs.needs_publish == 'true' | |
| env: | |
| NODE_AUTH_TOKEN: "" | |
| NPM_TOKEN: "" | |
| run: node scripts/release/publish-release-manifest.mjs --manifest .release-manifest.json | |
| - name: Push Changesets release tags | |
| if: steps.detect.outputs.has_work == 'true' | |
| run: | | |
| set -euo pipefail | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| target_commit="$(jq -r '.commit // empty' .release-manifest.json)" | |
| mapfile -t tags < <( | |
| jq -r '.packages[]? | .tag // "\(.name)@\(.version)"' .release-manifest.json | |
| ) | |
| if [ "${#tags[@]}" -eq 0 ]; then | |
| echo "No release tags to push." | |
| exit 0 | |
| fi | |
| if [ -z "$target_commit" ]; then | |
| echo "Release manifest is missing commit." >&2 | |
| exit 1 | |
| fi | |
| to_push=() | |
| for tag in "${tags[@]}"; do | |
| remote_ref="$(git ls-remote --tags origin "refs/tags/$tag" | head -n 1 || true)" | |
| if [ -n "$remote_ref" ]; then | |
| remote_target="$(printf '%s\n' "$remote_ref" | awk '{print $1}')" | |
| if [ "$remote_target" != "$target_commit" ]; then | |
| echo "Remote tag $tag already exists on $remote_target, expected $target_commit." >&2 | |
| exit 1 | |
| fi | |
| continue | |
| fi | |
| if git rev-parse -q --verify "refs/tags/$tag" >/dev/null 2>&1; then | |
| local_target="$(git rev-list -n 1 "$tag")" | |
| if [ "$local_target" != "$target_commit" ]; then | |
| echo "Local tag $tag already exists on $local_target, expected $target_commit." >&2 | |
| exit 1 | |
| fi | |
| else | |
| git tag "$tag" "$target_commit" | |
| fi | |
| to_push+=("refs/tags/$tag") | |
| done | |
| if [ "${#to_push[@]}" -eq 0 ]; then | |
| echo "All release tags already exist on origin." | |
| exit 0 | |
| fi | |
| git push origin "${to_push[@]}" | |
| - name: Create GitHub Releases | |
| if: steps.detect.outputs.has_work == 'true' | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: node scripts/release/create-github-releases.mjs --manifest .release-manifest.json | |
| - name: Comment on issues closed by released PRs | |
| if: steps.detect.outputs.has_work == 'true' | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: node scripts/release/comment-release-issues.mjs | |
| - name: Post stable release to Slack | |
| if: steps.detect.outputs.has_work == 'true' | |
| uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 # v3.0.1 | |
| with: | |
| method: chat.postMessage | |
| token: ${{ secrets.SLACK_BOT_TOKEN }} | |
| payload: | | |
| channel: C0ABHT0SWA2 | |
| text: "✅ Packages published" | |
| blocks: | |
| - type: "header" | |
| text: | |
| type: "plain_text" | |
| text: "✅ Packages published" | |
| - type: "section" | |
| text: | |
| type: "mrkdwn" | |
| text: "*Release SHA:* `${{ needs.validate-release-sha.outputs.release_sha }}`\n\n*Packages:*\n${{ steps.detect.outputs.markdown }}\n\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Run>" | |
| notify-failure: | |
| needs: | |
| - validate-release-sha | |
| - print-changelog-links | |
| - publish-stable | |
| if: | | |
| always() && | |
| ( | |
| needs.validate-release-sha.result == 'failure' || | |
| needs.print-changelog-links.result == 'failure' || | |
| needs.publish-stable.result == 'failure' | |
| ) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Post to Slack on failure | |
| uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 # v3.0.1 | |
| with: | |
| method: chat.postMessage | |
| token: ${{ secrets.SLACK_BOT_TOKEN }} | |
| payload: | | |
| channel: C0ABHT0SWA2 | |
| text: "🚨 Stable release failed" | |
| blocks: | |
| - type: "header" | |
| text: | |
| type: "plain_text" | |
| text: "🚨 Stable release failed" | |
| - type: "section" | |
| text: | |
| type: "mrkdwn" | |
| text: "*Workflow:* `Publish Stable Release`\n*Release SHA:* `${{ inputs.release_sha }}`\n\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Run>" |