feat: BE release channel pipeline + wheels.json migration + Linux packages#2545
feat: BE release channel pipeline + wheels.json migration + Linux packages#2545
Conversation
…kages Sets up the bleeding-edge release channel infrastructure ahead of GA: Release channel classification - New ReleaseChannel service classifies version strings into stable / bleeding-edge / release-candidate / development. - `wheels --version` now reports the channel suffix. - 13 specs cover both new (-snapshot.N) and legacy (-SNAPSHOT+N) formats. SemVer separator fix - Snapshot versions now use '.' (pre-release identifier) instead of '+' (build metadata, ignored in precedence per SemVer §10). - Fixes an ordering bug that would have made adjacent-run snapshots sort equal in brew/scoop version comparators. box.json -> wheels.json - Slim, framework-native manifest replacing the CommandBox/ForgeBox-shaped box.json. Drops type, slug, packageDirectory, dependencies-as-ForgeBox-slugs. - 4 readers (FrameworkInstaller, Module.runUpgradeCheck, Global.$buildReleaseZip, McpServer project-marker check) updated to prefer wheels.json with box.json fallback so legacy projects keep working. - 3 new specs cover wheels.json-preferred, both-exist precedence, and pre-rename brew bottle fallback. Snapshot publishing pipeline (publish-snapshot.yml) - Fires on every push to develop. Builds artifacts, publishes a pre-release to wheels-dev/wheels-snapshots, fans out via repository_dispatch to the brew tap so wheels-be.rb auto-bumps within minutes. Auto-bump after GA (bump-develop-version.yml) - Fires on release publication on main. Opens an auto-bump PR against develop setting wheels.json's version to next-patch. Solves the "predict the next version" friction — snapshots baseline on next-patch but the actual GA scope (patch / minor / major) is decided at tag-cut time. Linux packages (.deb / .rpm) - nfpm configs for stable + bleeding-edge channels. - build-linux-packages.sh stages content + runs nfpm. - Wired into release.yml (stable) and publish-snapshot.yml (BE). - Shipped as GitHub Release attachments for Phase 1; apt.wheels.dev / yum.wheels.dev on CF Pages documented as Phase 2. Distribution drafts - tools/distribution-drafts/ holds reference copies of files belonging in other repos: brew formula + auto-bump workflow, scoop manifests + bucket README, snapshots-repo README + cleanup, Linux packages docs. Operational docs - .github/RELEASE_PLAYBOOK.md — daily flow, GA cut, rollback, RC, common failures. Validated locally: 518 pass / 5 unrelated pre-existing fails on the CLI suite; 3429 pass / 0 fail on the framework core suite.
The first cut of this PR added a parallel publish-snapshot.yml workflow which duplicated the build/publish work already done by snapshot.yml + release.yml. Worse, the existing tap auto-update.yml currently bumps Formula/wheels.rb on every snapshot dispatch — so the channel separation goal was being defeated under the existing flow. This commit consolidates: Source repo changes: - Delete duplicate publish-snapshot.yml — its work moves into release.yml. - Modify release.yml's snapshot release step to publish to wheels-dev/wheels-snapshots (channel-correct) via new WHEELS_SNAPSHOTS_TOKEN workflow_call secret. Stable releases continue publishing to wheels-dev/wheels. - Modify release.yml's downstream-dispatch step to derive a `channel` field from the version string (snapshot → bleeding-edge, rc → release-candidate, bare SemVer → stable) and include it in the dispatch payload so the tap can route correctly. - Update snapshot.yml to pass WHEELS_SNAPSHOTS_TOKEN through the workflow_call secrets, and to read box.json -> wheels.json. - Update validate_package() in release.yml + release-candidate.yml to be manifest-format-agnostic (prefer wheels.json, fall back to box.json) so the wheels-core validation step keeps working with the slim manifest. - Update generate-changelog.yml + version-bump.yml + bump-develop-version.yml to read wheels.json (with box.json fallback for the changelog generator). Tap-side drafts (apply when ready): - New bleeding-edge-update.yml workflow handles the BE channel, sourcing artifacts from wheels-dev/wheels-snapshots and writing Formula/wheels-be.rb. - auto-update-channel-patch.md documents the small early-exit gate to add to the existing auto-update.yml so it skips non-stable channels (handed off to the new BE workflow). After Tuesday GA, the two tap workflows can be refactored into one channel-aware workflow as cleanup. For now the split is deliberate: it keeps the proven stable flow untouched while validating the BE flow in isolation. Validated locally: CLI suite 518p/5f (same as before; 5 fails are pre-existing non-gating).
1cb33b3 to
f37bf00
Compare
There was a problem hiding this comment.
Wheels Bot — Reviewer A
TL;DR: PR #2545 ships the bleeding-edge channel pipeline, wheels.json rename, and Linux package tooling — all well-motivated and tested at the CFML layer. One correctness bug in the new Linux package build script will silently produce artifacts in the wrong directory, causing the release upload to miss them. Two stale doc references and a harmless dead-code block round out the findings. Requesting changes on the correctness issue.
Correctness
build-linux-packages.sh — packages land in the wrong directory and won't be uploaded
The script does cd "${BUILD_DIR}" (which is ${repo_root}/.linux-pkg-build) before computing NFPM_OUT:
# tools/distribution-drafts/linux-packages/build-linux-packages.sh (lines 2168–2190)
cd "${BUILD_DIR}"
mkdir -p "${OUT_DIR}"
NFPM_OUT="$(pwd)/${OUT_DIR}" # ← $(pwd) is now BUILD_DIR, not repo rootWhen called from release.yml with OUT_DIR: artifacts/wheels/${{ env.WHEELS_VERSION }} (a relative path), NFPM_OUT resolves to:
${GITHUB_WORKSPACE}/.linux-pkg-build/artifacts/wheels/<ver>/
But the upload step globs:
# .github/workflows/release.yml (both stable and snapshot upload blocks)
artifacts/wheels/${{ env.WHEELS_VERSION }}/wheels_*_amd64.deb
artifacts/wheels/${{ env.WHEELS_VERSION }}/wheels-*.x86_64.rpmwhich GitHub Actions resolves against $GITHUB_WORKSPACE, i.e.:
${GITHUB_WORKSPACE}/artifacts/wheels/<ver>/
The nfpm pkg step exits 0 and prints "── Done ──" with a listing of the newly-created files, so CI looks green. The upload step either errors or silently skips the packages depending on the ncipollo/release-action version's glob-no-match behavior. Either way, the .deb/.rpm files are absent from the release.
Fix: capture the repo root before cd, or resolve OUT_DIR to an absolute path before entering BUILD_DIR:
REPO_ROOT="$(pwd)"
BUILD_DIR="${REPO_ROOT}/.linux-pkg-build"
...
cd "${BUILD_DIR}"
mkdir -p "${REPO_ROOT}/${OUT_DIR}"
NFPM_OUT="${REPO_ROOT}/${OUT_DIR}"Docs
.github/RELEASE_PLAYBOOK.md — stale box.json reference
# line 43
3. **Verify `box.json` version** matches what you want to release.box.json was deleted in this same PR. Should read wheels.json.
tools/distribution-drafts/snapshots-repo/README.md — references a removed workflow
# line ~8
see [`publish-snapshot.yml`](https://github.com/wheels-dev/wheels/blob/develop/.github/workflows/publish-snapshot.yml)The PR description explicitly states publish-snapshot.yml was consolidated into snapshot.yml + release.yml. The link is broken (that file no longer exists on develop). Should reference snapshot.yml.
Minor
build-linux-packages.sh — dead cat >> that silently fails
# lines ~2159–2165
cat >> "${NFPM_CONFIG}" <<NFPM_EXTRA_EOF || true
# Channel/version stamps — appended by build-linux-packages.sh
NFPM_EXTRA_EOFAfter cd "${BUILD_DIR}", NFPM_CONFIG (a path relative to the repo root) no longer resolves. The || true swallows the error. The surrounding comment acknowledges this is a no-op ("we won't actually mutate the YAML here"). These four lines should be removed; they add confusion without effect.
tools/distribution-drafts/snapshots-repo/cleanup-old-snapshots.yml — counter scoping bug
DELETED=0
KEPT=0
...
echo "${BATCH}" | jq -c '.[]' | while read -r release; do
DELETED=$((DELETED + 1)) # ← runs in a subshell; not visible to parent
KEPT=$((KEPT + 1))
done
echo "Cleanup complete. Deleted: ${DELETED}, kept: ${KEPT}" # always "0, 0"Bash pipelines run in a subshell; DELETED and KEPT increments don't escape. The cleanups happen correctly but the summary is always 0, 0. Fix: use a process-substitution redirect so the loop runs in the current shell:
while read -r release; do
...
done < <(echo "${BATCH}" | jq -c '.[]')
Wheels Bot — Reviewer B (round 1)A correctly identified the nfpm output-directory bug and two minor issues. One finding has a wrong stated reason (but the conclusion holds), and A missed several stale documentation references that were introduced by this same PR. Not converging — A should surface the additional misses before address-review picks this up. Note: the PR was merged before this review cycle completed. The issues below are still worth a follow-up PR. SycophancyNone detected. A requested changes rather than approving, and the correctness finding is substantiated. False positives
A claims the # line ~2160 — cat >> runs HERE (before cd)
cat >> "${NFPM_CONFIG}" <<NFPM_EXTRA_EOF || true
# Channel/version stamps — appended by build-linux-packages.sh
NFPM_EXTRA_EOF
# line ~2168 — cd happens AFTER
cd "${BUILD_DIR}"The Missed issuesRELEASE_PLAYBOOK.md — two additional stale A flagged the one at line 3 of the pre-flight checklist (
Both are in the same newly-added Five additional stale A caught one reference in
The PR description explicitly states Verdict alignmentA's ConvergenceNot aligned. A's review covers the critical nfpm bug and two minor issues, but misses two additional stale |
There was a problem hiding this comment.
Wheels Bot — Reviewer A response (round 1)
B's critique is well-founded across the board. I'm conceding the wrong reasoning on the cat >> finding, and all seven additional stale doc references B surfaced. None of B's points warrant pushback — the diff evidence is clear. My verdict stays CHANGES_REQUESTED, but the remediation scope is materially wider than my initial review described: address-review should now have a complete list.
Conceded points
-
cat >>reasoning was wrong. The diff showscat >> "${NFPM_CONFIG}"at diff line 2159 andcd "${BUILD_DIR}"at diff line 2168. The heredoc runs while the working directory is still the repo root, soNFPM_CONFIGresolves correctly. The|| trueis redundant, not hiding a failure. My stated reason — "aftercd,NFPM_CONFIGno longer resolves" — was incorrect. The conclusion (confusing dead code that should be removed) still holds, but for the right reason: the code is a no-op comment append that serves no purpose, not a silently swallowed error. -
Two additional stale
box.jsonreferences inRELEASE_PLAYBOOK.md. Confirmed at diff lines 59 and 149:- Line 59: Release-day section says
bump-develop-version.yml"opens a PR against develop bumpingbox.jsonto next-patch". The workflow (also introduced in this PR) bumpswheels.json, notbox.json. - Line 149: "Common failure modes" table row says
`box.json` has `-SNAPSHOT` suffix on main. The updatedrelease.ymlreads fromwheels.jsonand its error message nameswheels.json. A user following the table would look at the wrong file.
- Line 59: Release-day section says
-
Five additional stale
publish-snapshot.ymlreferences across distribution-drafts READMEs. All confirmed in the diff:tools/distribution-drafts/README.md(diff line 1370): "matching CI workflow changes (publish-snapshot.yml, etc.) in this repo"tools/distribution-drafts/homebrew/README (diff line 1772): "thepublish-snapshot.ymlworkflow fires them"tools/distribution-drafts/linux-packages/README.md(diff line 1918): "wired intorelease.ymlandpublish-snapshot.ymlas a parallel artifact stream"tools/distribution-drafts/scoop/README.md(diff line 2383): "repository_dispatchfrompublish-snapshot.yml→ tap workflow opens PR"tools/distribution-drafts/snapshots-repo/README.md(diff line 2625): Lifecycle Step 2 — "publish-snapshot.ymlbuilds artifacts, opens a release here" — second occurrence in the same file I partially reviewed
Combined with the one I flagged in my initial review (the link reference at diff line 2591), there are six stale references to the removed workflow, not one.
Defended points
None. B's critique is accurate on every point.
Updated verdict
Still CHANGES_REQUESTED. The nfpm output-directory bug is the blocking correctness issue. The doc findings (three stale box.json references, six stale publish-snapshot.yml references) are non-blocking but should ship in the same fix pass since two of the snapshots-repo README entries are already live in the external wheels-dev/wheels-snapshots repo. Address-review has a complete change list.
…cts (#2546) * ci: gate ForgeBox publish to main; restore box.json for stable artifacts The first develop snapshot run after PR #2545 failed at the "Publish All Packages to ForgeBox" step, which cascaded to skip every downstream step including the new "Create Snapshot Pre-Release" that publishes to wheels-dev/wheels-snapshots. Two issues: 1. publish-to-forgebox.sh reads box.json directly (CommandBox's `forgebox publish` reads slug/version/type natively — we can't make it polyglot without forking CommandBox itself). After PR #2545 dropped box.json from tools/build/core/, the wheels-core artifact only carried wheels.json, and ForgeBox publish hit "ERROR: box.json not found". 2. Develop snapshots have no business going to ForgeBox anymore — that's what wheels-dev/wheels-snapshots is for. The snapshot pipeline shouldn't even attempt the ForgeBox path. Two complementary fixes: - Restore tools/build/core/box.json (CommandBox-shaped template) and have prepare-core.sh emit BOTH wheels.json (new world) and box.json (legacy ForgeBox compat). Once ForgeBox publishing is fully retired post-4.0, the box.json template can be deleted alongside publish-to-forgebox.sh. - Gate "Install CommandBox" + "Publish All Packages to ForgeBox" steps to `if: github.ref == 'refs/heads/main'` so develop snapshots skip them cleanly. The downstream Build / Upload / Create Snapshot Pre-Release steps then run normally and publish artifacts to wheels-dev/wheels-snapshots. Tuesday's v4.0.0 GA tag on main is unaffected — all the gated steps run on main as before, with the restored box.json template providing the manifest ForgeBox publish needs. * fix: address Reviewer A feedback on PR #2546 Two issues called out: - tools/build/core/box.json lines 13 and 35: stale `blob/master/` URLs in the changelog + LICENSE references. The repo's default branch is `main` since the v3.0 rebrand. When restoring this file from git history in the initial commit on this branch, I pulled back the old URLs verbatim. ForgeBox renders these as bare anchor links and redirect-from-renamed- branch is not guaranteed across all rendering paths. - .github/workflows/release.yml line 169: validator comment claimed "wheels-core now uses wheels.json" — but after this PR the wheels-core artifact ships BOTH manifests. Updated the comment to match what the code does, so a future maintainer doing the ForgeBox retirement cleanup isn't misled. tools/build/base/box.json line 47 has the same `blob/master/` issue but predates this PR; flagging as a separate follow-up rather than expanding scope here.
Documents the new bleeding-edge channel (wheels-be) alongside stable (wheels). Covers: - Comparison table: package name, source repo, cadence, version shape, channel-aware --version output - Install steps for each channel on macOS / Windows / Linux - Switching channels (uninstall one, install the other — they're mutually exclusive via brew's conflicts_with) - "Already-scaffolded apps are unaffected" reassurance - Decision guide: when to pick stable vs bleeding-edge - "How channels work under the hood" optional section explaining the snapshot version format, where snapshots live (wheels-snapshots repo), and the auto-update propagation chain Also adds a "Choose a channel" section to installing.mdx pointing at the new page so first-time installers see the option before they brew install. Doesn't disrupt the happy path: stable remains the default and the install steps below the new section are unchanged. Sidebar order: release-channels.mdx is order 5, after the tutorial happy path (installing → first-15-minutes → tutorial), so users new to Wheels don't get distracted by channel discussion before they've seen the framework run. Doubles as a soft-launch validation: this PR's merge to develop will fire the snapshot pipeline that just went live (PR #2545, #2547, #2548 + homebrew-wheels#174). If the dispatch chain works end-to-end, this commit's snapshot will land at wheels-dev/wheels-snapshots and the brew tap's bleeding-edge-update.yml will auto-bump wheels-be.rb to the new version within ~5 minutes.
Summary
Sets up the bleeding-edge release channel infrastructure ahead of v4.0.0 GA. Three independent things bundled into one PR because they share enough plumbing (release.yml, publish workflow, manifest reads) that splitting would require coordinated merges:
wheels-dev/wheels-snapshots, brew tap auto-bumps, usersbrew install wheels-befor bleeding-edge.box.json→wheels.jsonrename — drops the CommandBox/ForgeBox-shaped manifest in favor of a slim Wheels-native one. Dual-read fallback keeps legacy projects working.Related issues
Refs #2131 (GA tag plan for Wheels 4.0.0) — close after Tuesday's
v4.0.0tag push.Refs #2383 (LuCLI Windows launcher leads to invalid setup) — close after Scoop bucket goes live and
scoop install wheelsis verified.What's in the diff
Release channel classification
cli/lucli/services/ReleaseChannel.cfc— pure-function helper. Maps version strings tostable/bleeding-edge/release-candidate/development. Recognizes both new (-snapshot.N) and legacy (-SNAPSHOT+N) snapshot formats.cli/lucli/tests/specs/services/ReleaseChannelSpec.cfc— 13 specs covering all classifications + edge cases.cli/lucli/Module.cfc—wheels --versionnow reports the channel suffix:wheels 4.0.1-snapshot.1742 (bleeding-edge).SemVer separator fix
release.yml+release-candidate.yml— snapshot versions use.(pre-release identifier) instead of+(build metadata, ignored in SemVer §10 precedence). Without this fix, adjacent-run snapshots would sort equal in brew/scoop comparators, silently breakingbrew upgrade wheels-be.box.json→wheels.jsonvendor/wheels/, andtools/build/core/. Slim schema: dropstype,slug,packageDirectory,dependencies-as-ForgeBox-slugs.wheels.json, fall back tobox.json):FrameworkInstaller.cfc::rewriteVersionPlaceholder(thewheels newscaffold path)Module.cfc::runUpgradeCheck(thewheels upgradecommand)Global.cfc::\$buildReleaseZip(runtime release zip helper)McpServer.cfcproject-marker checkprepare-core.sh,generate-changelog.sh,release.yml,release-candidate.yml) reads fromwheels.json.FrameworkInstallerSpec.cfccover wheels.json-preferred, both-exist precedence, pre-rename brew bottle fallback.Snapshot publishing (integrates with existing snapshot.yml + release.yml)
After review, the original
publish-snapshot.ymlwas found to duplicate work already done bysnapshot.yml(which fires on develop push, runs the fast-test gate, and callsrelease.ymlviaworkflow_call). The duplicate workflow is removed; instead,release.ymlis now channel-aware:wheels-dev/wheels-snapshotsusingWHEELS_SNAPSHOTS_TOKEN(secret ✅ already configured).wheels-dev/wheels(current behavior).channelfield (stable/bleeding-edge/release-candidate) derived from the version string, so the tap can route to the right formula.snapshot.ymlis updated to pass the new secret through and to readwheels.json.Auto-bump after GA (new)
.github/workflows/bump-develop-version.yml— fires on release publication on main. Opens an auto-bump PR against develop settingwheels.json's version to next-patch. Solves the "predict the next version" friction: snapshots baseline on next-patch but the actual GA scope (patch / minor / major) is decided at tag-cut time. Snapshot version strings sort strictly lower than any plausible next release.Linux packages
tools/distribution-drafts/linux-packages/nfpm-wheels.yaml+nfpm-wheels-be.yaml— nfpm configs for both channels.tools/distribution-drafts/linux-packages/build-linux-packages.sh— stages content + runs nfpm.release.yml(stable) andpublish-snapshot.yml(BE) as a parallel artifact stream..deb/.rpmship as GitHub Release attachments.apt.wheels.dev/yum.wheels.devon CF Pages — documented intools/distribution-drafts/linux-packages/README.md.Distribution drafts (reference copies for other repos)
tools/distribution-drafts/homebrew/—wheels-be.rbformula +wheels-be-bump.ymlauto-bump workflow →wheels-dev/homebrew-wheelstools/distribution-drafts/scoop/—wheels.json+wheels-be.jsonmanifests → newwheels-dev/scoop-wheelstools/distribution-drafts/snapshots-repo/— README + cleanup workflow →wheels-dev/wheels-snapshots(✅ already pushed there)Operational docs
.github/RELEASE_PLAYBOOK.md— daily flow, GA cut, rollback, RC, common failures.Validation
Run locally on a fresh server rooted in this worktree:
Manual follow-ups still needed (not blocking this PR)
wheels-dev/homebrew-wheels:tools/distribution-drafts/homebrew/wheels-be.rbtoFormula/wheels-be.rbtools/distribution-drafts/homebrew/bleeding-edge-update.ymlto.github/workflows/bleeding-edge-update.ymltools/distribution-drafts/homebrew/auto-update-channel-patch.mdto existingauto-update.yml(channel filter so it skips non-stable dispatches)wheels-dev/scoop-wheelsrepo, push manifest filesmicrosoft/winget-pkgswheels-dev/apt-wheels-dev+wheels-dev/yum-wheels-devCF Pages reposwheels-base-template,wheels-starter-app,publish-to-forgebox.sh, legacycli/src/Test plan
publish-snapshot.ymlfires on the merge commit and creates a release inwheels-dev/wheels-snapshotsrepository_dispatchreacheswheels-dev/homebrew-wheels(will no-op until brew tap setup is done — that's expected)bump-develop-version.ymlfires and opens the next-patch bump PR