Release #13
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: Release | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.dry_run }} | |
| cancel-in-progress: true | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| dry_run: | |
| description: 'Run the release process without publishing to npm.' | |
| required: false | |
| default: false | |
| type: boolean | |
| jobs: | |
| guard: | |
| name: release-branch-guard | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Enforce release branch | |
| run: | | |
| RELEASE_BRANCH=dev \ | |
| RELEASE_DRY_RUN=${{ inputs.dry_run }} \ | |
| GITHUB_REF=${{ github.ref }} \ | |
| node ./scripts/verify-release-branch.js | |
| plan: | |
| name: plan | |
| runs-on: ubuntu-latest | |
| needs: guard | |
| outputs: | |
| prebuild_matrix: ${{ steps.prebuild_matrix.outputs.json }} | |
| compat_matrix: ${{ steps.compat_matrix.outputs.json }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24.14.0 | |
| - name: Resolve prebuild matrix | |
| id: prebuild_matrix | |
| run: echo "json=$(node ./scripts/workflow-matrix.js prebuild)" >> "$GITHUB_OUTPUT" | |
| - name: Resolve compatibility matrix | |
| id: compat_matrix | |
| run: echo "json=$(node ./scripts/workflow-matrix.js compat)" >> "$GITHUB_OUTPUT" | |
| test: | |
| name: test-${{ matrix.os }}-node-${{ matrix.node-version }} | |
| runs-on: ${{ matrix.os }} | |
| needs: guard | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, windows-latest, macos-15] | |
| node-version: [20.x, 22.x, 24.x] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ matrix.node-version }} | |
| cache: npm | |
| cache-dependency-path: package-lock.json | |
| - name: Install npm dependencies | |
| run: npm install --omit=optional --no-audit --no-fund | |
| - name: Run test suite | |
| run: npm test -- --runInBand | |
| root-package: | |
| name: package-root | |
| runs-on: ubuntu-latest | |
| needs: guard | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24.14.0 | |
| cache: npm | |
| cache-dependency-path: package-lock.json | |
| - name: Install npm dependencies | |
| run: npm install --omit=optional --no-audit --no-fund | |
| - name: Build TypeScript artifacts | |
| run: npm run build | |
| - name: Verify root package tarball | |
| run: npm run verify:pack | |
| - name: Create root package tarball | |
| run: node ./scripts/create-package-tarball.js --out-dir=artifacts | |
| - name: Upload root package artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: package-function-location | |
| path: artifacts/*.tgz | |
| if-no-files-found: error | |
| prebuild: | |
| name: prebuild-${{ matrix.packageName }} | |
| runs-on: ${{ matrix.runner }} | |
| needs: plan | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: ${{ fromJson(needs.plan.outputs.prebuild_matrix) }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ matrix.nodeVersion }} | |
| cache: npm | |
| cache-dependency-path: package-lock.json | |
| - name: Install npm dependencies | |
| run: npm install --omit=optional --no-audit --no-fund | |
| - name: Build platform prebuilds | |
| run: npm run build:prebuilds -- --package=${{ matrix.packageName }} | |
| - name: Verify platform prebuilds | |
| run: npm run verify:prebuild -- --package=${{ matrix.packageName }} | |
| - name: Verify platform package tarball | |
| run: node ./scripts/verify-pack-tarball.js --package-dir=${{ matrix.packageDir }} | |
| - name: Create platform package tarball | |
| run: node ./scripts/create-package-tarball.js --package-dir=${{ matrix.packageDir }} --out-dir=artifacts | |
| - name: Upload platform package artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: package-${{ matrix.packageName }} | |
| path: artifacts/*.tgz | |
| if-no-files-found: error | |
| compatibility: | |
| name: compat-${{ matrix.compatibilityLabel }}-node-${{ matrix.nodeVersion }} | |
| runs-on: ${{ matrix.runner }} | |
| needs: [plan, root-package, prebuild] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: ${{ fromJson(needs.plan.outputs.compat_matrix) }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ matrix.nodeVersion }} | |
| architecture: ${{ matrix.nodeArchitecture }} | |
| - name: Download root package artifact | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: package-function-location | |
| path: artifacts/root | |
| - name: Download platform package artifact | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: package-${{ matrix.packageName }} | |
| path: artifacts/platform | |
| - name: Install packed packages and run smoke test | |
| shell: bash | |
| run: | | |
| root_tarball="$(find artifacts/root -maxdepth 1 -name '*.tgz' -type f | head -n 1)" | |
| platform_tarball="$(find artifacts/platform -maxdepth 1 -name '*.tgz' -type f | head -n 1)" | |
| if [ -z "$root_tarball" ] || [ -z "$platform_tarball" ]; then | |
| echo "Required tarball artifacts were not downloaded." | |
| exit 1 | |
| fi | |
| node ./scripts/run-compat-smoke.js \ | |
| --root-tarball="$root_tarball" \ | |
| --platform-tarball="$platform_tarball" \ | |
| --expected-node-arch="${{ matrix.nodeArchitecture }}" \ | |
| --expected-host-arm64="${{ matrix.expectedHostArm64 }}" \ | |
| --expected-translated="${{ matrix.expectedTranslated }}" | |
| publish: | |
| name: publish | |
| runs-on: ubuntu-latest | |
| needs: compatibility | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24.14.0 | |
| - name: Download package artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| pattern: package-* | |
| path: artifacts | |
| - name: Verify package metadata alignment | |
| id: package_metadata | |
| run: | | |
| packages_json="$(node ./scripts/package-metadata.js)" | |
| root_name="$(node -p "require('./package.json').name")" | |
| root_version="$(node -p "require('./package.json').version")" | |
| publish_tag="$(node -p "require('./package.json').version.includes('-') ? 'next' : 'latest'")" | |
| printf '%s\n' "$packages_json" > "$RUNNER_TEMP/release-packages.json" | |
| echo "root_name=$root_name" >> "$GITHUB_OUTPUT" | |
| echo "root_version=$root_version" >> "$GITHUB_OUTPUT" | |
| echo "publish_tag=$publish_tag" >> "$GITHUB_OUTPUT" | |
| - name: Verify release versions are new | |
| if: ${{ !inputs.dry_run }} | |
| run: | | |
| node - "$RUNNER_TEMP/release-packages.json" <<'EOF' > "$RUNNER_TEMP/release-package-specs.txt" | |
| const fs = require('fs'); | |
| const packageSpecs = JSON.parse(fs.readFileSync(process.argv[2], 'utf8')); | |
| for (const entry of packageSpecs) { | |
| console.log(`${entry.name}@${entry.version}`); | |
| } | |
| EOF | |
| while IFS= read -r spec; do | |
| if npm view "$spec" version >/tmp/npm-view.out 2>/tmp/npm-view.err; then | |
| echo "$spec already exists on npm." | |
| echo "Publish would overwrite an existing release. Bump package versions before retrying." | |
| exit 1 | |
| fi | |
| if ! grep -q "E404" /tmp/npm-view.err; then | |
| echo "Failed to verify npm version availability for $spec." | |
| cat /tmp/npm-view.err | |
| exit 1 | |
| fi | |
| done < "$RUNNER_TEMP/release-package-specs.txt" | |
| - name: Skip version uniqueness check for dry run | |
| if: ${{ inputs.dry_run }} | |
| run: echo "Dry run enabled; skipping npm version uniqueness enforcement." | |
| - name: Build .npm auth config | |
| if: ${{ !inputs.dry_run }} | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| run: | | |
| if [ -z "$NODE_AUTH_TOKEN" ]; then | |
| echo "NPM_TOKEN is not set." | |
| exit 1 | |
| fi | |
| echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" > "$RUNNER_TEMP/npmrc" | |
| echo "NPM_CONFIG_USERCONFIG=$RUNNER_TEMP/npmrc" >> "$GITHUB_ENV" | |
| - name: Dry run publish | |
| if: ${{ inputs.dry_run }} | |
| shell: bash | |
| run: | | |
| root_tarball="$(find "artifacts/package-function-location" -maxdepth 1 -name '*.tgz' -type f | head -n 1)" | |
| mapfile -t platform_names < <(node - "$RUNNER_TEMP/release-packages.json" <<'EOF' | |
| const fs = require('fs'); | |
| const packages = JSON.parse(fs.readFileSync(process.argv[2], 'utf8')); | |
| for (const entry of packages.slice(1)) { | |
| console.log(entry.name); | |
| } | |
| EOF | |
| ) | |
| if [ -z "$root_tarball" ] || [ "${#platform_names[@]}" -eq 0 ]; then | |
| echo "Release tarball artifacts are incomplete." | |
| exit 1 | |
| fi | |
| for package_name in "${platform_names[@]}"; do | |
| tarball="$(find "artifacts/package-$package_name" -maxdepth 1 -name '*.tgz' -type f | head -n 1)" | |
| if [ -z "$tarball" ]; then | |
| echo "Missing tarball for $package_name." | |
| exit 1 | |
| fi | |
| npm publish "$tarball" --access public --dry-run --ignore-scripts --tag dry-run | |
| done | |
| npm publish "$root_tarball" --access public --dry-run --ignore-scripts --tag dry-run | |
| - name: Publish packages | |
| if: ${{ !inputs.dry_run }} | |
| shell: bash | |
| run: | | |
| root_tarball="$(find "artifacts/package-function-location" -maxdepth 1 -name '*.tgz' -type f | head -n 1)" | |
| mapfile -t platform_names < <(node - "$RUNNER_TEMP/release-packages.json" <<'EOF' | |
| const fs = require('fs'); | |
| const packages = JSON.parse(fs.readFileSync(process.argv[2], 'utf8')); | |
| for (const entry of packages.slice(1)) { | |
| console.log(entry.name); | |
| } | |
| EOF | |
| ) | |
| if [ -z "$root_tarball" ] || [ "${#platform_names[@]}" -eq 0 ]; then | |
| echo "Release tarball artifacts are incomplete." | |
| exit 1 | |
| fi | |
| for package_name in "${platform_names[@]}"; do | |
| tarball="$(find "artifacts/package-$package_name" -maxdepth 1 -name '*.tgz' -type f | head -n 1)" | |
| if [ -z "$tarball" ]; then | |
| echo "Missing tarball for $package_name." | |
| exit 1 | |
| fi | |
| npm publish "$tarball" --access public --tag "${{ steps.package_metadata.outputs.publish_tag }}" | |
| done | |
| npm publish "$root_tarball" --access public --tag "${{ steps.package_metadata.outputs.publish_tag }}" |