Fixes the silent-sparse-release-notes failure mode that surfaced on
v0.9.5: the Release workflow used to do a literal
`extract-release-notes.mjs <version>` lookup with an `[Unreleased]`
fallback. The fallback only triggered when the `[<version>]` block
DIDN'T exist at all — and in practice maintainers sometimes had a
sparse `[<version>]` block pre-populated (e.g. one early fix
documented before the rest of the work landed). The workflow then
extracted that sparse block, ignoring the much-larger `[Unreleased]`
section above it. Result: the published v0.9.5 release notes were
missing the shared MCP daemon, the per-file staleness banner, the
Objective-C indexing, AND the Mixed iOS/RN/Expo bridging.
The fix is a new `scripts/prepare-release.mjs` step that runs at the
start of the workflow:
Case A — `[<version>]` does not yet exist:
Rename `[Unreleased]` → `[<version>] - <today>`. Add a fresh
empty `[Unreleased]` above. The common path.
Case B — `[<version>]` exists AND `[Unreleased]` has content:
Merge `[Unreleased]`'s sub-sections (### Added / ### Fixed /
### Changed / ### Removed / ### Deprecated / ### Security) into
the corresponding sub-sections of `[<version>]`. Unmatched
sub-sections are appended. Then empty `[Unreleased]`.
Case C — `[Unreleased]` is empty:
No-op. Re-runs of the workflow are safe.
After the script runs, the workflow auto-commits + pushes the
CHANGELOG.md change back to main (with a `[skip ci]` tag in the
commit body) so future runs and human eyes both see the same
on-disk truth.
9 unit tests (`__tests__/prepare-release.test.ts`) cover all three
cases, idempotency, version-source precedence, and an
extract-release-notes.mjs integration check.
Workflow comment header rewritten to reflect the new flow.
Trigger reminder going forward: bump package.json. CHANGELOG entries
can live under `[Unreleased]` — the workflow takes care of moving
them.
937/939 existing tests pass (2 pre-existing skips); +9 new tests.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
155 lines
6.9 KiB
YAML
155 lines
6.9 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. Set the NPM_TOKEN secret.
|
|
on:
|
|
workflow_dispatch: {}
|
|
|
|
permissions:
|
|
contents: write # create the GitHub Release + tag, push the CHANGELOG promote
|
|
|
|
jobs:
|
|
release:
|
|
runs-on: ubuntu-latest
|
|
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 }}
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 22
|
|
registry-url: https://registry.npmjs.org
|
|
- 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: 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: 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
|
|
env:
|
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
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"
|
|
( cd "$dir" && npm publish --access public )
|
|
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
|