docs: refocus gran around local workspace #277
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: CI | |
| on: | |
| push: | |
| pull_request: | |
| workflow_dispatch: | |
| inputs: | |
| publish: | |
| description: Force a publish candidate review for the selected ref | |
| type: boolean | |
| default: false | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| jobs: | |
| verify: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: voidzero-dev/setup-vp@v1 | |
| with: | |
| node-version: 24 | |
| cache: true | |
| - name: Install | |
| run: vp install --frozen-lockfile | |
| - name: Install Docs Dependencies | |
| run: npm ci --prefix docs | |
| - name: Check Web Bundle | |
| run: npm run web:check | |
| - name: Check | |
| run: vp check | |
| - name: SDK Check | |
| run: npm run sdk:check | |
| - name: Test | |
| run: vp test | |
| - name: SDK Test | |
| run: npm run sdk:test | |
| - name: Coverage | |
| run: npm run coverage | |
| - name: Standalone Smoke Build | |
| run: npm run standalone:smoke | |
| - name: Pack | |
| run: vp pack | |
| - name: SDK Pack | |
| run: npm run sdk:build | |
| - name: Build Docs | |
| run: npm run docs:check | |
| - name: Install Playwright Browser | |
| run: npx playwright install --with-deps chromium | |
| - name: Browser E2E | |
| run: npm run browser:e2e | |
| - name: Pack Dry Run | |
| run: npm pack --dry-run | |
| - name: SDK Pack Dry Run | |
| run: npm run sdk:pack:dry-run | |
| prepare-publish: | |
| if: | | |
| (github.event_name == 'push' && | |
| github.ref == 'refs/heads/main' && | |
| startsWith(github.event.head_commit.message, 'chore: release v')) || | |
| (github.event_name == 'workflow_dispatch' && inputs.publish) | |
| needs: verify | |
| runs-on: ubuntu-latest | |
| outputs: | |
| package_name: ${{ steps.package.outputs.package_name }} | |
| package_version: ${{ steps.package.outputs.package_version }} | |
| publish_matrix: ${{ steps.npm.outputs.publish_matrix }} | |
| should_publish: ${{ steps.npm.outputs.should_publish }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - id: package | |
| name: Read package metadata | |
| run: | | |
| node <<'EOF' | |
| const fs = require('node:fs'); | |
| const rootPkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); | |
| const sdkPkg = JSON.parse(fs.readFileSync('packages/sdk/package.json', 'utf8')); | |
| const packages = [ | |
| { name: rootPkg.name, version: rootPkg.version, workspace: '.', publishName: rootPkg.name }, | |
| { name: sdkPkg.name, version: sdkPkg.version, workspace: sdkPkg.name, publishName: sdkPkg.name }, | |
| ]; | |
| fs.appendFileSync(process.env.GITHUB_OUTPUT, `package_name=${rootPkg.name}\n`); | |
| fs.appendFileSync(process.env.GITHUB_OUTPUT, `package_version=${rootPkg.version}\n`); | |
| fs.appendFileSync(process.env.GITHUB_OUTPUT, `packages=${JSON.stringify(packages)}\n`); | |
| EOF | |
| - id: npm | |
| name: Check whether package versions are already published | |
| env: | |
| PACKAGES_JSON: ${{ steps.package.outputs.packages }} | |
| run: | | |
| node <<'EOF' | |
| const { execSync } = require('node:child_process'); | |
| const fs = require('node:fs'); | |
| const packages = JSON.parse(process.env.PACKAGES_JSON); | |
| const publishable = []; | |
| for (const pkg of packages) { | |
| try { | |
| execSync(`npm view "${pkg.name}@${pkg.version}" version`, { stdio: 'ignore' }); | |
| console.log(`Version ${pkg.name}@${pkg.version} is already on npm.`); | |
| } catch { | |
| console.log(`Version ${pkg.name}@${pkg.version} is unpublished and ready for review.`); | |
| publishable.push(pkg); | |
| } | |
| } | |
| fs.appendFileSync( | |
| process.env.GITHUB_OUTPUT, | |
| `should_publish=${publishable.length > 0 ? 'true' : 'false'}\n`, | |
| ); | |
| fs.appendFileSync( | |
| process.env.GITHUB_OUTPUT, | |
| `publish_matrix=${JSON.stringify(publishable)}\n`, | |
| ); | |
| EOF | |
| publish: | |
| if: needs.prepare-publish.outputs.should_publish == 'true' | |
| needs: | |
| - verify | |
| - prepare-publish | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| id-token: write | |
| concurrency: | |
| group: npm-publish-production | |
| cancel-in-progress: false | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: ${{ fromJson(needs.prepare-publish.outputs.publish_matrix) }} | |
| environment: | |
| name: production | |
| url: https://www.npmjs.com/package/${{ needs.prepare-publish.outputs.package_name }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: voidzero-dev/setup-vp@v1 | |
| with: | |
| node-version: 24 | |
| cache: true | |
| - name: Install | |
| run: vp install --frozen-lockfile | |
| - name: Publish | |
| env: | |
| PACKAGE_NAME: ${{ matrix.name }} | |
| PACKAGE_VERSION: ${{ matrix.version }} | |
| PACKAGE_WORKSPACE: ${{ matrix.workspace }} | |
| run: | | |
| echo "Publishing ${PACKAGE_NAME}@${PACKAGE_VERSION}" | |
| if [ "${PACKAGE_WORKSPACE}" = "." ]; then | |
| npm publish --provenance --access public | |
| else | |
| npm publish --provenance --access public --workspace "${PACKAGE_WORKSPACE}" | |
| fi | |
| standalone-assets: | |
| if: needs.prepare-publish.outputs.should_publish == 'true' | |
| needs: | |
| - verify | |
| - prepare-publish | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - runner: ubuntu-latest | |
| target: linux-x64 | |
| - runner: macos-14 | |
| target: darwin-arm64 | |
| - runner: windows-latest | |
| target: win32-x64 | |
| runs-on: ${{ matrix.runner }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: voidzero-dev/setup-vp@v1 | |
| with: | |
| node-version: 24 | |
| cache: true | |
| - name: Install | |
| run: vp install --frozen-lockfile | |
| - name: Build standalone archive | |
| run: npm run standalone:build -- --target ${{ matrix.target }} | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: standalone-${{ matrix.target }} | |
| path: | | |
| dist/release-assets/*.tar.gz | |
| dist/release-assets/*.zip | |
| github-release: | |
| if: needs.prepare-publish.outputs.should_publish == 'true' | |
| needs: | |
| - verify | |
| - prepare-publish | |
| - publish | |
| - standalone-assets | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/download-artifact@v5 | |
| with: | |
| merge-multiple: true | |
| path: release-assets | |
| pattern: standalone-* | |
| - name: Build asset checksums | |
| run: | | |
| cd release-assets | |
| sha256sum * > SHA256SUMS.txt | |
| - name: Create GitHub Release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| PACKAGE_NAME: ${{ needs.prepare-publish.outputs.package_name }} | |
| PACKAGE_VERSION: ${{ needs.prepare-publish.outputs.package_version }} | |
| run: | | |
| tag="v${PACKAGE_VERSION}" | |
| title="${PACKAGE_NAME} v${PACKAGE_VERSION}" | |
| mapfile -t assets < <(find release-assets -maxdepth 1 -type f | sort) | |
| notes_file="$(mktemp)" | |
| node scripts/release-notes.mjs > "${notes_file}" | |
| if gh release view "${tag}" >/dev/null 2>&1; then | |
| echo "GitHub Release ${tag} already exists; refreshing notes and assets." | |
| gh release edit "${tag}" \ | |
| --latest \ | |
| --notes-file "${notes_file}" \ | |
| --title "${title}" | |
| else | |
| gh release create "${tag}" \ | |
| --latest \ | |
| --notes-file "${notes_file}" \ | |
| --title "${title}" | |
| fi | |
| gh release upload "${tag}" "${assets[@]}" --clobber | |
| auto-approve-publish: | |
| if: needs.prepare-publish.outputs.should_publish == 'true' | |
| needs: | |
| - verify | |
| - prepare-publish | |
| runs-on: ubuntu-latest | |
| steps: | |
| - id: approver | |
| name: Check auto-approver configuration | |
| env: | |
| GH_RELEASE_APPROVER_TOKEN: ${{ secrets.GH_RELEASE_APPROVER_TOKEN }} | |
| run: | | |
| if [ -n "${GH_RELEASE_APPROVER_TOKEN}" ]; then | |
| echo "configured=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "configured=false" >> "$GITHUB_OUTPUT" | |
| echo "No GH_RELEASE_APPROVER_TOKEN secret configured; leaving the publish gate manual." | |
| fi | |
| - uses: actions/github-script@v8 | |
| if: steps.approver.outputs.configured == 'true' | |
| env: | |
| TARGET_ENVIRONMENT: production | |
| with: | |
| github-token: ${{ secrets.GH_RELEASE_APPROVER_TOKEN }} | |
| script: | | |
| const runId = context.runId; | |
| const environmentName = process.env.TARGET_ENVIRONMENT; | |
| for (let attempt = 1; attempt <= 60; attempt += 1) { | |
| const { data: pending } = await github.request( | |
| "GET /repos/{owner}/{repo}/actions/runs/{run_id}/pending_deployments", | |
| { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| run_id: runId, | |
| }, | |
| ); | |
| const deployment = pending.find( | |
| (candidate) => candidate.environment?.name === environmentName, | |
| ); | |
| if (deployment) { | |
| await github.request( | |
| "POST /repos/{owner}/{repo}/actions/runs/{run_id}/pending_deployments", | |
| { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| run_id: runId, | |
| environment_ids: [deployment.environment.id], | |
| state: "approved", | |
| comment: `Automatically approved ${environmentName} publish candidate`, | |
| }, | |
| ); | |
| core.info( | |
| `Approved pending deployment for ${environmentName} on run ${runId}.`, | |
| ); | |
| return; | |
| } | |
| core.info( | |
| `No pending deployment for ${environmentName} yet (attempt ${attempt}/60). Waiting 5 seconds...`, | |
| ); | |
| await new Promise((resolve) => setTimeout(resolve, 5_000)); | |
| } | |
| core.setFailed( | |
| `Timed out waiting for a pending ${environmentName} deployment on run ${runId}.`, | |
| ); |