Skip to content

feat(boxel-cli): auto-publish unstable per merge, repurpose manual workflow as stable promoter#4804

Open
FadhlanR wants to merge 5 commits into
mainfrom
cs-11112-auto-publish-boxel-cli-unstable-per-merge-and-unblock
Open

feat(boxel-cli): auto-publish unstable per merge, repurpose manual workflow as stable promoter#4804
FadhlanR wants to merge 5 commits into
mainfrom
cs-11112-auto-publish-boxel-cli-unstable-per-merge-and-unblock

Conversation

@FadhlanR
Copy link
Copy Markdown
Contributor

@FadhlanR FadhlanR commented May 13, 2026

Summary

Closes CS-11112.

PR-time enforcement for boxel-cli shrinks to "write a conventional PR title." Every merge to main touching packages/boxel-cli/** auto-publishes @cardstack/boxel-cli@<v>-unstable.<n> to npm under dist-tag unstable (Ember canary pattern). Stable releases stay deliberate via the repurposed manual workflow, which promotes the latest unstable to latest.

What changes

  • PR-time: .github/workflows/boxel-cli-pr-title.yml (new) validates the PR title via amannn/action-semantic-pull-request, path-scoped to packages/boxel-cli/**. Four old CI gates in ci-lint.yaml (lines 128-224) are removed — no more local regen, no more hand-bumping plugin.json.
  • Merge-time: .github/workflows/boxel-cli-on-main.yml (new) regenerates plugin/skills/, fetches the merged PR's title via gh api repos/.../commits/<sha>/pulls, classifies the bump, applies per-surface version updates, commits back with [skip ci], tags, and publishes to npm. Concurrency-serialized to prevent prerelease-counter collisions.
  • Stable promotion: .github/workflows/manual-boxel-cli-publish.yml repurposed — strips -unstable.N, publishes under latest, tags, creates a non-prerelease GitHub Release. Input is now a confirm: 'promote' typed string.
  • Logic: packages/boxel-cli/scripts/compute-release.ts (new) is the pure decision engine. Maps PR-title prefix → bump level, checks per-surface (src/ vs plugin/), handles prerelease base escalation (e.g. 0.1.5-unstable.X + breaking change → 1.0.0-unstable.<n>). 29 vitest cases.
  • Docs: plugin/README.md Versioning + Releasing rewritten for the new flow, including the BOXEL_SKILLS_VERSION fix(skills): caveat. AGENTS.md gains a "boxel-cli commit prefixes" subsection.
  • Seed: package.json bumped 0.1.40.1.5-unstable.0 so the first auto-publish after this merges has a clean starting state.

Test plan

  • pnpm test:unit covers compute-release logic — 29 / 29 passing.
  • pnpm lint clean.
  • Dry-run: PR_TITLE='feat: test' pnpm exec ts-node --transpileOnly scripts/compute-release.ts emits valid JSON.
  • Post-merge (will happen automatically): this PR's own merge fires boxel-cli-on-main.yml. Expected behavior:
    • PR title feat(boxel-cli): … → minor bump.
    • npm surface touched (scripts/compute-release.ts, tests/scripts/, package.json) → 0.1.5-unstable.0 escalates to 0.2.0-unstable.<n>, publishes to npm under unstable.
    • plugin surface touched (plugin/README.md) → plugin.json 0.1.40.2.0.
  • Follow-up validation: open a tiny fix: PR touching src/ once this lands; verify it publishes 0.2.0-unstable.<n+1>.
  • Follow-up validation: trigger manual-boxel-cli-publish.yml with confirm: promote; verify it strips the suffix and publishes latest.

Notes for reviewer

  • The PR-title check is not marked as a required status check on main. Required path-filtered checks would leave non-boxel-cli PRs pending forever — discussed in the plan doc.
  • Loop safety is belt + suspenders: bot commit ends [skip ci] AND the workflow job guards if: github.actor != 'github-actions[bot]'.
  • Concurrency group boxel-cli-release is shared with the manual workflow so a stable promotion can't race an auto unstable publish.

FadhlanR and others added 5 commits May 13, 2026 11:25
…rkflow as stable promoter

Closes CS-11112.

PR-time enforcement shrinks to "conventional PR title" — no more local
regen, no more hand-bumping plugin.json, no more four-gate CI block.
On every merge to main that touches packages/boxel-cli/**, a new on-main
workflow regenerates plugin/skills/, computes per-surface bumps from the
merged PR title (fetched via gh api), commits back to main, and publishes
@cardstack/boxel-cli@<v>-unstable.<n> under npm dist-tag `unstable`.
Stable releases stay deliberate — the renamed manual workflow promotes
the latest unstable to `latest` rather than cutting fresh.

- .github/workflows/boxel-cli-pr-title.yml — new; amannn/action-semantic-pull-request, path-scoped to boxel-cli
- .github/workflows/boxel-cli-on-main.yml — new; regen + compute-release + bump + tag + publish, concurrency-serialized
- .github/workflows/manual-boxel-cli-publish.yml — repurposed: strip -unstable.N, publish under latest
- .github/workflows/ci-lint.yaml — drop the four boxel-cli verification gates (lines 128-224); plain pnpm run lint stays
- packages/boxel-cli/scripts/compute-release.ts — new; pure function + I/O wrapper. Handles prerelease base escalation.
- packages/boxel-cli/tests/scripts/compute-release.test.ts — 29 vitest cases
- packages/boxel-cli/plugin/README.md — rewrite Versioning + Releasing for the new flow
- AGENTS.md — add "boxel-cli commit prefixes" subsection
- packages/boxel-cli/package.json — seed 0.1.4 → 0.1.5-unstable.0
- docs/cs-11112-auto-publish-boxel-cli-unstable-plan.md — implementation plan doc
The seed bump to 0.1.5-unstable.0 (and subsequent auto-unstable versions
like 0.2.0-unstable.7) broke the strict /^\d+\.\d+\.\d+$/ regex. Accept
optional semver prerelease tail.
Move the `merge_group:` caveat out of the `on:` block (indent 2, which
matched neither neighbor) into the file-header comment block at column 0,
where the rest of the workflow's design rationale lives.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extract the prefix → bump-level map to release-prefixes.json so the
pre-merge PR title check (amannn/action-semantic-pull-request) and the
post-merge classifier (compute-release.ts → classifyBumpFromTitle) read
from one file. Previously the prefix list was duplicated, and a new prefix
added in one place would silently drift from the other.

- New: packages/boxel-cli/scripts/release-prefixes.json
- compute-release.ts: import the JSON instead of inlining BUMP_BY_PREFIX
- tsconfig.json: enable resolveJsonModule for the TS import
- boxel-cli-pr-title.yml: sparse-checkout the JSON, extract keys via
  `node -e`, feed them to amannn's `types:` input

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@FadhlanR FadhlanR marked this pull request as ready for review May 15, 2026 09:23
@FadhlanR FadhlanR requested review from a team, backspace and richardhjtan May 15, 2026 09:23
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 63d5c176de

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: main
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Pin release job to triggering SHA

The checkout step hard-codes ref: main, so queued or re-run jobs can execute against a newer branch tip than the push event they are supposed to release. In practice, if two packages/boxel-cli/** merges happen close together, the earlier run can classify and version the later commit (or include prior bot bump commits), which leads to incorrect bump decisions and duplicate/shifted unstable versions. Check out ${{ github.sha }} (the triggering commit) to keep release computation deterministic per event.

Useful? React with 👍 / 👎.

Comment on lines +137 to +145
if: steps.pr.outputs.skip != 'true'
env:
NEXT_NPM: ${{ steps.release.outputs.nextNpm }}
NEXT_PLUGIN: ${{ steps.release.outputs.nextPlugin }}
run: |
set -euo pipefail
git add packages/boxel-cli/package.json \
packages/boxel-cli/plugin/.claude-plugin/plugin.json \
packages/boxel-cli/plugin/skills
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Block plugin skill commits when bump resolves to none

This commit/push step runs even when pluginBump is none, and it always stages packages/boxel-cli/plugin/skills. If a PR title maps to a no-release prefix (for example chore:) but regenerated skill content changes, the workflow will commit those skill updates without bumping plugin.json version, so marketplace clients keyed by version will not pick up the new content. Gate this step on bump outputs or fail when generated plugin artifacts changed without a version bump.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant