Skip to content

Preview Release

Preview Release #226

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`);
}