From a29b6dcb3519f241b2238e0d011a54cb31cd8e4b Mon Sep 17 00:00:00 2001 From: meh Date: Sun, 13 Sep 2026 12:23:04 +0700 Subject: [PATCH 1/2] fix(release): publish only tested revisions --- .github/workflows/ci.yml | 8 +++--- .github/workflows/release.yml | 47 ++++++++++++++++++++++++++++------- AGENTS.md | 31 +++++++++++------------ tests/qa-harness/README.md | 3 ++- tests/qa-harness/run-all.sh | 8 +++--- 5 files changed, 64 insertions(+), 33 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 64e671c6..db85ca82 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v1 with: - bun-version: latest + bun-version: 1.3.14 - uses: dtolnay/rust-toolchain@stable @@ -56,12 +56,14 @@ jobs: # deleted from source; this is the last consumer to move off it. - name: Headless browser host id: qa - uses: pathscale/chuzz/.github/actions/headless-host@master + uses: pathscale/chuzz/.github/actions/headless-host@b0e4ce0d35fe96230c90d2a3ed55a64686abe370 with: token: ${{ secrets.SIBLING_REPOS_TOKEN }} + chuzz-ref: b0e4ce0d35fe96230c90d2a3ed55a64686abe370 + ps-qa-version: =0.7.3 - name: Install Dependencies - run: bun install + run: bun install --no-save # `qa` only, for now: `src` carries 20 pre-existing findings that are not # this change's to fix, and gating on them would block every PR. The diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ef5fb129..bc1306c2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,8 +1,8 @@ name: Release -# Fully automatic releases. Push to master, and if the commits since the last -# published version warrant a release, this bumps the version, publishes to npm -# and tags it. No version-bump PR, no manual step. +# Fully automatic releases. Successful CI on a master push starts this workflow; +# if the commits since the last published version warrant a release, it bumps +# the version, publishes to npm and tags it. No version-bump PR, no manual step. # # The version comes from conventional commits (scripts/next-version.ts): # type! / BREAKING CHANGE -> major, feat -> minor, fix|perf|revert -> patch. @@ -20,8 +20,9 @@ name: Release # Without it the publish step fails closed rather than publishing unauthenticated. on: - push: - branches: [master] + workflow_run: + workflows: [CI] + types: [completed] workflow_dispatch: permissions: @@ -29,6 +30,11 @@ permissions: jobs: decide: + if: >- + github.event_name == 'workflow_dispatch' || + (github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.event == 'push' && + github.event.workflow_run.head_branch == 'master') runs-on: ubicloud-standard-2 outputs: mode: ${{ steps.decide.outputs.mode }} @@ -37,10 +43,18 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 # next-version.ts reads commit history + ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || 'master' }} + + - name: Require the tested revision to remain master + if: github.event_name == 'workflow_run' + run: | + git fetch origin master + test "$(git rev-parse HEAD)" = "${{ github.event.workflow_run.head_sha }}" + test "$(git rev-parse origin/master)" = "${{ github.event.workflow_run.head_sha }}" - uses: oven-sh/setup-bun@v1 with: - bun-version: latest + bun-version: 1.3.14 - name: Decide whether to release, and as what version id: decide @@ -71,10 +85,20 @@ jobs: runs-on: ubicloud-standard-2 permissions: contents: write # push the bump commit + outputs: + release_sha: ${{ steps.release-sha.outputs.value }} env: VERSION: ${{ needs.decide.outputs.version }} steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || 'master' }} + + - name: Require the tested revision to remain master + run: | + git fetch origin master + test "$(git rev-parse HEAD)" = "$(git rev-parse origin/master)" # Mirrors ci.yml — the build resolves this plugin from a sibling directory. - name: Checkout rsbuild-plugin-ui-css-purge @@ -88,7 +112,7 @@ jobs: - uses: oven-sh/setup-bun@v1 with: - bun-version: latest + bun-version: 1.3.14 - uses: actions/setup-node@v4 with: @@ -108,7 +132,7 @@ jobs: git push origin HEAD:master - name: Install dependencies - run: bun install + run: bun install --no-save - name: Contract check run: bun run check @@ -145,6 +169,10 @@ jobs: mkdir -p release npm pack --pack-destination release + - name: Record the release revision + id: release-sha + run: echo "value=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + - uses: actions/upload-artifact@v4 with: name: npm-package @@ -185,7 +213,7 @@ jobs: # is not on npm. Tags had drifted badly before this — newest was v1.1.51 # while npm was on 1.2.11. tag: - needs: [decide, publish] + needs: [decide, prepare, publish] runs-on: ubicloud-standard-2 permissions: contents: write @@ -199,4 +227,5 @@ jobs: gh release create "v$VERSION" \ --repo "$GITHUB_REPOSITORY" \ --title "v$VERSION" \ + --target "${{ needs.prepare.outputs.release_sha }}" \ --generate-notes diff --git a/AGENTS.md b/AGENTS.md index ab1abc7b..ed518705 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,20 +17,18 @@ natively, and Claude Code loads it through the `@AGENTS.md` import in implementation files.** It is the frontend working agreement: SolidJS/`@pathscale/ui` conventions, and a context-efficient workflow. Reading it first keeps context small and avoids re-deriving patterns that already exist. -- **Releases are automatic — never run `npm publish` by hand.** Pushing to `master` - runs [`.github/workflows/release.yml`](.github/workflows/release.yml), which derives the - version from the conventional commits since the last release, bumps `package.json`, - publishes through npm Trusted Publishing (OIDC) and tags it. There is no `NPM_TOKEN` in - this repository, so a local publish cannot authenticate anyway, and a hand-written +- **Releases are automatic — never run `npm publish` by hand.** A successful CI run for + `master` starts [`.github/workflows/release.yml`](.github/workflows/release.yml) at the + exact revision CI tested. The workflow stops if `master` advances before release, + derives the version from conventional commits, bumps `package.json`, publishes through + npm Trusted Publishing (OIDC), and tags the published revision. There is no `NPM_TOKEN` + in this repository, so a local publish cannot authenticate anyway, and a hand-written version bump only fights the workflow. Publishing is irreversible and a version can never be reused, so if a release looks wrong, read the workflow run before touching anything. -- **After a merge, confirm the fix actually published.** The automatic release has one - hole and it is easy to walk into: if the release job for the *previous* merge is still - running when yours lands, the new tag can end up pointing at your commit while npm - never receives it. `bun run scripts/next-version.ts` then reports "no releasable - commits", because the range it inspects is already empty. The symptom is a merged fix - that a consumer cannot install. +- **After a merge, confirm the fix actually published.** The release is gated by CI and + fails closed when another merge advances `master`. Confirm the npm version and tag both + point at the expected release revision: So check, every time: @@ -40,20 +38,21 @@ natively, and Claude Code loads it through the `@AGENTS.md` import in git tag --points-at HEAD # a tag here with no npm release is the hole ``` - If master is ahead of npm, push a one-line `chore(release): ` bump to `master` - to trigger the workflow. That is the one case where touching the version by hand is - correct, and it is a repair, not the normal path. + If master is ahead of npm, inspect the CI and Release runs. Fix or re-run the failed + workflow rather than manufacturing another version bump. - **One open PR per repository. Add to it.** If a PR is already open here, push your commits onto that branch instead of opening a second one. Two open PRs against the same library mean two releases, two version bumps, and a consumer that has to wait for both — and whichever lands second can hit the tag hole above. -- **`bun` is the package manager** — its lockfile is authoritative. Don't introduce a second one by running npm/yarn/pnpm here. +- **`bun` is the package manager. Do not add or generate lockfiles.** This fleet + intentionally resolves from `package.json`; remove a generated lockfile before + reviewing the change. Don't run npm/yarn/pnpm here. - **Docs describe what is true now.** If you change behaviour, update the README and any affected doc in the same change. ## Build & run ```bash -bun install +bun install --no-save bun run dev bun run build bun run lint diff --git a/tests/qa-harness/README.md b/tests/qa-harness/README.md index be79b2e0..6573a61a 100644 --- a/tests/qa-harness/README.md +++ b/tests/qa-harness/README.md @@ -49,7 +49,8 @@ cargo build --release --manifest-path ../chuzz/Cargo.toml --bin chuzz-headless \ QA_HOST=../chuzz/target/release/chuzz-headless bash tests/qa-harness/run-all.sh ``` -`QA_PS_QA` does the same for a local ps-qa, version 0.7.1 or newer. The script +`QA_PS_QA` does the same for a local ps-qa. Release runs use the reviewed exact +version 0.7.3 so a driver update cannot silently change the result. The script refuses stale bundles unless `QA_ALLOW_STALE=1` is explicitly set. Linux rendered QA needs fontconfig development files and an installed font such as DejaVu; the shared CI host action installs both. Release verification uses `QA_PROFILE=full`. diff --git a/tests/qa-harness/run-all.sh b/tests/qa-harness/run-all.sh index f74dd400..bface499 100755 --- a/tests/qa-harness/run-all.sh +++ b/tests/qa-harness/run-all.sh @@ -66,15 +66,15 @@ fi # days stale, and all three passed the moment the current one ran. # # The driver and host use the shared control protocol from this release. -readonly PS_QA_FLOOR="0.7.1" +readonly PS_QA_VERSION="0.7.3" ps_qa_version="$("$PS_QA" --version 2>/dev/null | awk '{ print $2 }')" if [[ -z "$ps_qa_version" ]]; then echo "$PS_QA does not report a version; it is too old to sweep with" >&2 exit 1 fi -if [[ "$(printf '%s\n%s\n' "$PS_QA_FLOOR" "$ps_qa_version" | sort -V | head -1)" != "$PS_QA_FLOOR" ]]; then - echo "ps-qa $ps_qa_version is older than the $PS_QA_FLOOR this harness needs." >&2 - echo " cargo install ps-qa --version '^$PS_QA_FLOOR'" >&2 +if [[ "$ps_qa_version" != "$PS_QA_VERSION" ]]; then + echo "ps-qa $ps_qa_version is not the reviewed $PS_QA_VERSION this harness requires." >&2 + echo " cargo install ps-qa --version '=$PS_QA_VERSION'" >&2 echo " (or set QA_PS_QA to a build, which is what to do when changing the driver)" >&2 exit 1 fi From a3d937c4e3ac73894c3864c2a456910abc01db3d Mon Sep 17 00:00:00 2001 From: meh Date: Sun, 13 Sep 2026 12:59:55 +0700 Subject: [PATCH 2/2] fix(release): require a successful CI trigger --- .github/workflows/release.yml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bc1306c2..bb39f058 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,7 +23,6 @@ on: workflow_run: workflows: [CI] types: [completed] - workflow_dispatch: permissions: contents: read @@ -31,10 +30,9 @@ permissions: jobs: decide: if: >- - github.event_name == 'workflow_dispatch' || - (github.event.workflow_run.conclusion == 'success' && - github.event.workflow_run.event == 'push' && - github.event.workflow_run.head_branch == 'master') + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.event == 'push' && + github.event.workflow_run.head_branch == 'master' runs-on: ubicloud-standard-2 outputs: mode: ${{ steps.decide.outputs.mode }} @@ -43,10 +41,9 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 # next-version.ts reads commit history - ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || 'master' }} + ref: ${{ github.event.workflow_run.head_sha }} - name: Require the tested revision to remain master - if: github.event_name == 'workflow_run' run: | git fetch origin master test "$(git rev-parse HEAD)" = "${{ github.event.workflow_run.head_sha }}" @@ -93,7 +90,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || 'master' }} + ref: ${{ github.event.workflow_run.head_sha }} - name: Require the tested revision to remain master run: |