Preview Release #236
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: Preview Release | |
| # Triggered when the CI workflow completes. Runs with base-branch code and | |
| # write permissions, so it works for fork PRs (which get read-only tokens | |
| # on the pull_request event). | |
| 'on': | |
| workflow_run: | |
| workflows: ['CI'] | |
| types: [completed] | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| actions: read | |
| jobs: | |
| preview-release: | |
| name: Preview Release | |
| if: >- | |
| github.event.workflow_run.conclusion == 'success' && | |
| github.event.workflow_run.event == 'pull_request' | |
| runs-on: ubuntu-latest | |
| concurrency: | |
| group: preview-release-${{ github.event.workflow_run.pull_requests[0].number || github.event.workflow_run.id }} | |
| cancel-in-progress: true | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Setup node | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24 | |
| cache: yarn | |
| - name: Install dependencies | |
| run: yarn install --frozen-lockfile | |
| - name: Download PR metadata | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: pr-metadata | |
| path: . | |
| run-id: ${{ github.event.workflow_run.id }} | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Read PR metadata | |
| id: pr | |
| run: | | |
| set -euo pipefail | |
| pr_number=$(jq -r .pr_number pr-metadata.json) | |
| head_sha=$(jq -r .head_sha pr-metadata.json) | |
| # Defense-in-depth: validate before using in shell/scripts downstream. | |
| if ! [[ "$pr_number" =~ ^[0-9]+$ ]]; then | |
| echo "Invalid pr_number from pr-metadata.json: $pr_number" >&2 | |
| exit 1 | |
| fi | |
| if ! [[ "$head_sha" =~ ^[0-9a-f]{40}$ ]]; then | |
| echo "Invalid head_sha from pr-metadata.json: $head_sha" >&2 | |
| exit 1 | |
| fi | |
| echo "number=$pr_number" >> "$GITHUB_OUTPUT" | |
| echo "head_sha=$head_sha" >> "$GITHUB_OUTPUT" | |
| - name: Download all build artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts | |
| pattern: bindings-* | |
| run-id: ${{ github.event.workflow_run.id }} | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Generate packages | |
| run: node scripts/generate-packages.js | |
| - name: List packages | |
| run: ls -R ./npm | |
| shell: bash | |
| - name: Prepare preview release | |
| run: | | |
| node scripts/publish-preview.js ./npm/darwin-arm64 ./npm/darwin-x64 ./npm/linux-arm64-gnu ./npm/linux-arm64-musl ./npm/linux-x64-gnu ./npm/linux-x64-musl ./npm/win32-x64-msvc . | |
| env: | |
| PR_NUMBER: ${{ steps.pr.outputs.number }} | |
| COMMIT_SHA: ${{ steps.pr.outputs.head_sha }} | |
| GITHUB_REPOSITORY: ${{ github.repository }} | |
| - name: Read manifest | |
| id: manifest | |
| run: | | |
| echo "data=$(cat manifest.json | jq -c)" >> "$GITHUB_OUTPUT" | |
| - name: Delete prior preview releases for this PR | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| PR_NUMBER: ${{ steps.pr.outputs.number }} | |
| run: | | |
| set -euo pipefail | |
| # Capture API output and exit status explicitly. A pipe-into-loop | |
| # would mask a gh api failure (network blip, rate limit) as an | |
| # empty result — leaving stale previews while the publish step | |
| # still creates a new one. Warn-and-continue here because the | |
| # close-time workflow is the authoritative backstop. | |
| # Server-side prefix match; trailing dash disambiguates pr-1- from pr-12-. | |
| if ! api_refs=$(gh api "repos/${GH_REPO}/git/matching-refs/tags/pr-${PR_NUMBER}-" --jq '.[].ref'); then | |
| echo "WARN: failed to query prior preview tags via gh api; skipping in-PR cleanup (close-time workflow will handle it)" >&2 | |
| exit 0 | |
| fi | |
| if [ -z "$api_refs" ]; then | |
| echo "No prior previews to delete." | |
| exit 0 | |
| fi | |
| while IFS= read -r ref; do | |
| [ -z "$ref" ] && continue | |
| tag="${ref#refs/tags/}" | |
| echo "Deleting prior preview release ${tag}" | |
| gh release delete "${tag}" --yes --cleanup-tag --repo "${GH_REPO}" \ | |
| || echo "WARN: failed to delete ${tag} (continuing; close-time workflow is the backstop)" >&2 | |
| done <<< "$api_refs" | |
| - name: Create GitHub Release | |
| uses: actions/github-script@v7 | |
| env: | |
| MANIFEST: ${{ steps.manifest.outputs.data }} | |
| with: | |
| script: | | |
| const manifest = JSON.parse(process.env.MANIFEST); | |
| console.log(`Creating release: ${manifest.tagName}`); | |
| const release = await github.rest.repos.createRelease({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| tag_name: manifest.tagName, | |
| name: manifest.releaseName, | |
| body: manifest.releaseNotes, | |
| draft: false, | |
| prerelease: true, | |
| target_commitish: manifest.commitSha | |
| }); | |
| console.log(`✓ Created release: ${release.data.html_url}`); | |
| return release.data.id; | |
| - name: Upload Release Assets | |
| uses: actions/github-script@v7 | |
| env: | |
| MANIFEST: ${{ steps.manifest.outputs.data }} | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const manifest = JSON.parse(process.env.MANIFEST); | |
| const { data: release } = await github.rest.repos.getReleaseByTag({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| tag: manifest.tagName | |
| }); | |
| console.log(`Uploading assets to release ${manifest.tagName}...`); | |
| for (const pkg of manifest.packages) { | |
| console.log(` Uploading ${pkg.tarball}...`); | |
| await github.rest.repos.uploadReleaseAsset({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| release_id: release.id, | |
| name: pkg.tarball, | |
| data: await fs.promises.readFile(pkg.path) | |
| }); | |
| console.log(` ✓ Uploaded ${pkg.tarball}`); | |
| } | |
| console.log(` Uploading ${manifest.mainPackage.tarball}...`); | |
| await github.rest.repos.uploadReleaseAsset({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| release_id: release.id, | |
| name: manifest.mainPackage.tarball, | |
| data: await fs.promises.readFile(manifest.mainPackage.path) | |
| }); | |
| console.log(` ✓ Uploaded ${manifest.mainPackage.tarball}`); | |
| console.log(`\n✓ All assets uploaded successfully`); | |
| - name: Post or Update PR Comment | |
| uses: actions/github-script@v7 | |
| env: | |
| MANIFEST: ${{ steps.manifest.outputs.data }} | |
| PR_NUMBER: ${{ steps.pr.outputs.number }} | |
| with: | |
| script: | | |
| const manifest = JSON.parse(process.env.MANIFEST); | |
| const prNumber = parseInt(process.env.PR_NUMBER, 10); | |
| const marker = '<!-- domino-preview-release -->'; | |
| console.log(`Managing PR comment for PR #${prNumber}...`); | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber | |
| }); | |
| const existingComment = comments.find(comment => | |
| comment.body?.includes(marker) | |
| ); | |
| if (existingComment) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existingComment.id, | |
| body: manifest.commentBody | |
| }); | |
| console.log(`✓ Updated existing comment #${existingComment.id}`); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| body: manifest.commentBody | |
| }); | |
| console.log(`✓ Created new comment`); | |
| } |