a6682c6a07
Three R1-era assumptions retired now that the kernel is the release's headline rather than an optional speedup: 1. The kernel matrix drops continue-on-error (fail-fast stays false so every platform leg reports). A Rust toolchain failure now blocks the release instead of silently shipping wasm-only bundles under a Rust-engine banner. First real risk it guards: the vendored-grammar-C languages (kotlin/lua/scala/dart, incl. scala's 35MB parser.c) have never compiled on these runners — no release has run since the kernel merged. 2. The release-job gate expands from the two R1 suites to ALL __tests__/kernel-*.test.ts (14 files today: contract, grammar-source parity, and every language's walker byte-parity suite) — the glob keeps it current as languages land. 3. A missing linux-x64 prebuild at the gate is now a hard failure (the matrix guarantees it; absence means a wiring bug), and the artifact download step loses its best-effort flag for the same reason. No packaging changes needed: build-bundle.sh already stages lib/kernel/codegraph-kernel.node per target and pack-npm.sh repacks bundles verbatim into the per-platform npm packages. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
304 lines
15 KiB
YAML
304 lines
15 KiB
YAML
name: Release
|
|
|
|
# Manually triggered ("Run workflow"). On trigger it:
|
|
# 1. reads the version from package.json,
|
|
# 2. promotes `## [Unreleased]` content into `## [<version>]` in
|
|
# CHANGELOG.md (and commits + pushes that change back to main), so
|
|
# the published release notes are never sparse just because the
|
|
# maintainer didn't pre-stage the [<version>] block by hand,
|
|
# 3. builds a self-contained bundle for every platform (one runner — there's no
|
|
# native compilation, so cross-packaging is fine),
|
|
# 4. creates the GitHub Release (tag v<version>) with all archives, using the
|
|
# release notes from CHANGELOG.md,
|
|
# 5. publishes the npm thin-installer (shim + per-platform packages).
|
|
#
|
|
# Before triggering: bump package.json. CHANGELOG.md entries can live under
|
|
# `## [Unreleased]` — step 2 takes care of moving them.
|
|
#
|
|
# npm auth is OIDC trusted publishing (no NPM_TOKEN): every published package
|
|
# (@colbymchenry/codegraph + the per-platform bundles) has this repo +
|
|
# release.yml configured as its trusted publisher on npmjs.com. Adding a new
|
|
# platform package means configuring its trusted publisher there before the
|
|
# first release that includes it.
|
|
on:
|
|
workflow_dispatch: {}
|
|
|
|
permissions:
|
|
contents: write # create the GitHub Release + tag, push the CHANGELOG promote
|
|
id-token: write # OIDC token for npm --provenance and Sigstore signing
|
|
attestations: write # store the GitHub artifact attestations for the bundles
|
|
|
|
jobs:
|
|
# Native extraction-kernel prebuilds (docs/design/rust-kernel-migration-plan.md).
|
|
# As of 1.5.0 the kernel is the release's HEADLINE, not an optional extra, so
|
|
# this matrix is REQUIRED: a failed kernel build blocks the release instead of
|
|
# silently shipping wasm-only bundles under a Rust-engine banner. Per-file
|
|
# wasm fallback still exists at runtime for erroring files and unsupported
|
|
# platforms — but every published bundle must carry its .node. Note the
|
|
# vendored-grammar-C languages (kotlin/lua/scala/dart) compile parser.c via
|
|
# the cc crate, so each leg needs its platform C toolchain (runner images
|
|
# ship one). (Runner images ship rustup; build-kernel.sh
|
|
# adds each cross target itself.)
|
|
kernel:
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- runner: macos-14
|
|
targets: aarch64-apple-darwin x86_64-apple-darwin
|
|
- runner: ubuntu-22.04 # oldest glibc runner → widest compatibility
|
|
targets: x86_64-unknown-linux-gnu
|
|
- runner: ubuntu-22.04-arm
|
|
targets: aarch64-unknown-linux-gnu
|
|
- runner: windows-latest
|
|
targets: x86_64-pc-windows-msvc aarch64-pc-windows-msvc
|
|
runs-on: ${{ matrix.runner }}
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- name: Build kernel prebuilds
|
|
shell: bash
|
|
run: |
|
|
for t in ${{ matrix.targets }}; do
|
|
bash scripts/build-kernel.sh --target "$t"
|
|
done
|
|
ls -R codegraph-kernel/prebuilds
|
|
- uses: actions/upload-artifact@v4
|
|
with:
|
|
name: kernel-${{ matrix.runner }}
|
|
path: codegraph-kernel/prebuilds/
|
|
if-no-files-found: error
|
|
|
|
release:
|
|
runs-on: ubuntu-latest
|
|
needs: kernel
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
# Default checkout is detached at a SHA; we need an actual branch
|
|
# so the CHANGELOG-promote commit knows where to push.
|
|
ref: ${{ github.ref }}
|
|
# Authenticate as the maintainer (admin), not as github-actions[bot].
|
|
# The "Require PR approval for main branch" ruleset only lets the
|
|
# Admin repo role bypass — and GitHub blocks adding the GitHub
|
|
# Actions integration to bypass_actors on user-owned (non-org)
|
|
# repos with "Actor GitHub Actions integration must be part of
|
|
# the ruleset source or owner organization." So the auto-promote
|
|
# and auto-sync `git push origin HEAD:main` steps below both fail
|
|
# under the default GITHUB_TOKEN. Using a fine-grained PAT owned
|
|
# by the admin makes the push go through cleanly. Set the
|
|
# RELEASE_PAT secret with: contents:write on this repo, no other
|
|
# scopes. Rotate per your token policy; the workflow only runs
|
|
# on manual dispatch so the blast radius is small.
|
|
token: ${{ secrets.RELEASE_PAT }}
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 22
|
|
# No registry-url here: it writes an .npmrc that requires a
|
|
# NODE_AUTH_TOKEN env var to exist, and we publish via OIDC
|
|
# trusted publishing instead of a token.
|
|
|
|
- name: Upgrade npm for OIDC trusted publishing
|
|
# Trusted publishing needs npm >= 11.5; Node 22 bundles npm 10.
|
|
run: npm install -g npm@11 && npm --version
|
|
|
|
- name: Sync package-lock.json if version drifted
|
|
# When the maintainer bumps the version on package.json only — for
|
|
# example via a GitHub web-UI edit — `npm ci` would refuse to run
|
|
# with `EUSAGE: npm ci can only install packages when your
|
|
# package.json and package-lock.json … are in sync`. This step
|
|
# rewrites just the lock-file's version fields (top-level + the
|
|
# `packages.""` entry) to match package.json, then auto-commits
|
|
# and pushes the result so on-disk truth on `main` stays
|
|
# consistent. Idempotent: if the lock file already matches, no
|
|
# commit is made.
|
|
run: |
|
|
set -euo pipefail
|
|
PKG_V=$(node -p "require('./package.json').version")
|
|
LOCK_V=$(node -p "require('./package-lock.json').version")
|
|
if [ "$PKG_V" = "$LOCK_V" ]; then
|
|
echo "package-lock.json already at $PKG_V — nothing to sync."
|
|
exit 0
|
|
fi
|
|
echo "Lock-file version drift: lock=$LOCK_V, package=$PKG_V. Syncing."
|
|
# `--package-lock-only` rewrites only the lock file, doesn't
|
|
# touch node_modules or actually install anything. Cheap.
|
|
npm install --package-lock-only --ignore-scripts
|
|
# Sanity: lockfile should now report the package version.
|
|
NEW_LOCK_V=$(node -p "require('./package-lock.json').version")
|
|
if [ "$NEW_LOCK_V" != "$PKG_V" ]; then
|
|
echo "::error::lock-file still at $NEW_LOCK_V after sync attempt; expected $PKG_V"; exit 1
|
|
fi
|
|
if git diff --quiet -- package-lock.json; then
|
|
echo "lock file unchanged after sync? bailing"; exit 1
|
|
fi
|
|
git config user.name "github-actions[bot]"
|
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
git add package-lock.json
|
|
git commit -m "release: sync package-lock.json to ${PKG_V}" -m "[skip ci] Auto-generated by Release workflow."
|
|
git push origin "HEAD:${GITHUB_REF#refs/heads/}"
|
|
|
|
- run: npm ci
|
|
- name: Ensure zip/unzip
|
|
run: sudo apt-get update -qq && sudo apt-get install -y -qq zip unzip
|
|
|
|
- name: Resolve version
|
|
id: ver
|
|
run: echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Promote [Unreleased] → [<version>] in CHANGELOG.md
|
|
# Idempotent: a no-op if [Unreleased] is empty OR if the previous
|
|
# run already moved everything. Auto-commit + push the change back
|
|
# so the version block on main is the source of truth going
|
|
# forward (and so subsequent extract-release-notes.mjs calls
|
|
# surface the full content even if this run is re-triggered).
|
|
run: |
|
|
set -euo pipefail
|
|
V="${{ steps.ver.outputs.version }}"
|
|
before=$(git rev-parse HEAD)
|
|
node scripts/prepare-release.mjs "$V"
|
|
if git diff --quiet -- CHANGELOG.md; then
|
|
echo "CHANGELOG.md unchanged — nothing to commit."
|
|
else
|
|
git config user.name "github-actions[bot]"
|
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
git add CHANGELOG.md
|
|
git commit -m "docs(changelog): promote [Unreleased] into [${V}]" -m "[skip ci] Auto-generated by Release workflow."
|
|
# Push to the branch the workflow was triggered on (main).
|
|
git push origin "HEAD:${GITHUB_REF#refs/heads/}"
|
|
fi
|
|
|
|
- name: Download kernel prebuilds
|
|
# All legs are required (see the kernel job), so every target's
|
|
# <target>/codegraph-kernel.node must be present in release/kernel/.
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
pattern: kernel-*
|
|
merge-multiple: true
|
|
path: release/kernel/
|
|
|
|
- name: Kernel contract + full walker-parity gate
|
|
# Runs EVERY kernel suite (__tests__/kernel-*.test.ts — the wire
|
|
# contract, the grammar-source parity table checks, and all 20
|
|
# languages' walker byte-parity suites incl. torture/CRLF/defer pins)
|
|
# against the freshly built linux-x64 .node. The glob keeps this gate
|
|
# current as languages are added. CODEGRAPH_KERNEL_EXPECT=1 turns a
|
|
# missing binary into a FAILURE, and a missing prebuild fails outright
|
|
# — the matrix is required, so absence here means a wiring bug.
|
|
run: |
|
|
if [ -f release/kernel/linux-x64/codegraph-kernel.node ]; then
|
|
mkdir -p codegraph-kernel/prebuilds/linux-x64
|
|
cp release/kernel/linux-x64/codegraph-kernel.node codegraph-kernel/prebuilds/linux-x64/
|
|
CODEGRAPH_KERNEL_EXPECT=1 npx vitest run __tests__/kernel-*.test.ts
|
|
else
|
|
echo "::error::linux-x64 kernel prebuild missing despite required matrix"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Build all platform bundles
|
|
run: |
|
|
for t in darwin-arm64 darwin-x64 linux-x64 linux-arm64 win32-x64 win32-arm64; do
|
|
bash scripts/build-bundle.sh "$t"
|
|
done
|
|
ls -lh release
|
|
|
|
- name: Generate SHA256SUMS
|
|
# Published as a release asset; the npm launcher verifies downloaded
|
|
# bundles against it (basenames only, so its path.basename match works).
|
|
run: |
|
|
( cd release && sha256sum codegraph-* > SHA256SUMS )
|
|
cat release/SHA256SUMS
|
|
|
|
- name: Attest build provenance for release bundles
|
|
# Signed, publicly-verifiable proof that each bundle (and SHA256SUMS)
|
|
# was built by this workflow from this repo — SHA256SUMS alone only
|
|
# proves integrity, not origin, since it ships next to the bundles.
|
|
# Verify any downloaded artifact with:
|
|
# gh attestation verify <file> -R colbymchenry/codegraph
|
|
uses: actions/attest-build-provenance@v4
|
|
with:
|
|
subject-path: |
|
|
release/codegraph-*
|
|
release/SHA256SUMS
|
|
|
|
- name: Release notes from CHANGELOG.md
|
|
# The [<version>] block was guaranteed-populated by the
|
|
# "Promote" step above, so the [Unreleased] fallback should
|
|
# never be needed in practice. Kept for defense-in-depth.
|
|
run: |
|
|
V="${{ steps.ver.outputs.version }}"
|
|
node scripts/extract-release-notes.mjs "$V" > notes.md 2>/dev/null \
|
|
|| node scripts/extract-release-notes.mjs Unreleased > notes.md 2>/dev/null || true
|
|
if [ ! -s notes.md ]; then
|
|
echo "::error::No release notes in CHANGELOG.md for [$V] or [Unreleased]."
|
|
exit 1
|
|
fi
|
|
echo "----- release notes -----"; cat notes.md
|
|
|
|
- name: Create GitHub Release
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
TAG="v${{ steps.ver.outputs.version }}"
|
|
# Idempotent: create the release once, otherwise (re-run) refresh assets.
|
|
if gh release view "$TAG" >/dev/null 2>&1; then
|
|
gh release upload "$TAG" release/codegraph-* release/SHA256SUMS --clobber
|
|
else
|
|
gh release create "$TAG" release/codegraph-* release/SHA256SUMS --title "$TAG" --notes-file notes.md
|
|
fi
|
|
|
|
- name: Publish to npm
|
|
# Auth is OIDC trusted publishing (id-token: write above) — npm mints
|
|
# a short-lived credential from the workflow's identity; there is no
|
|
# NPM_TOKEN. Provenance is generated automatically on this path; the
|
|
# explicit --provenance keeps the intent visible and fails loudly if
|
|
# OIDC is ever unavailable.
|
|
run: |
|
|
V="${{ steps.ver.outputs.version }}"
|
|
bash scripts/pack-npm.sh "$V"
|
|
# Platform packages first, then the main shim (which depends on them).
|
|
# Skip any already on the registry so a re-run only fills in gaps.
|
|
for dir in release/npm/codegraph-* release/npm/main; do
|
|
name=$(node -p "require('./$dir/package.json').name")
|
|
if npm view "$name@$V" version >/dev/null 2>&1; then
|
|
echo "skip $name@$V (already published)"
|
|
else
|
|
echo "publishing $name@$V"
|
|
# --provenance: publish with an npm provenance attestation
|
|
# (needs the id-token: write permission above and the
|
|
# repository field pack-npm.sh writes into each package.json).
|
|
( cd "$dir" && npm publish --access public --provenance )
|
|
fi
|
|
done
|
|
|
|
- name: Verify every package is actually on the registry
|
|
run: |
|
|
V="${{ steps.ver.outputs.version }}"
|
|
# npm publish can print success without persisting; confirm against the
|
|
# registry (with retries for propagation) so green means really shipped.
|
|
for dir in release/npm/codegraph-* release/npm/main; do
|
|
name=$(node -p "require('./$dir/package.json').name")
|
|
ok=
|
|
for i in 1 2 3 4 5 6; do
|
|
if npm view "$name@$V" version >/dev/null 2>&1; then ok=1; break; fi
|
|
echo "waiting for $name@$V to appear ($i)…"; sleep 10
|
|
done
|
|
[ -n "$ok" ] || { echo "::error::$name@$V never appeared on the registry"; exit 1; }
|
|
echo "verified $name@$V"
|
|
done
|
|
|
|
- name: Sync packages to npmmirror
|
|
# npmmirror/cnpm mirror lazily and frequently never pull the per-platform
|
|
# optionalDependencies on their own, so `npm i` there fails with
|
|
# "no prebuilt bundle" (issue #303). Nudge a sync now so mirror users get
|
|
# the bundle without waiting. Best-effort — the launcher also self-heals
|
|
# from GitHub Releases — so a mirror hiccup never fails the release.
|
|
continue-on-error: true
|
|
run: |
|
|
for dir in release/npm/codegraph-* release/npm/main; do
|
|
name=$(node -p "require('./$dir/package.json').name")
|
|
enc=$(node -p "encodeURIComponent(require('./$dir/package.json').name)")
|
|
echo "sync $name"
|
|
curl -s -X PUT "https://registry.npmmirror.com/-/package/$enc/syncs" || true
|
|
echo
|
|
done
|