Skip to content

feat(release): semantic-release on main + compiled binaries + install docs - #55

Merged
2000game merged 2 commits into
mainfrom
feat/automated-releases-9
Jul 9, 2026
Merged

2000game merged 2 commits into
mainfrom
feat/automated-releases-9

Conversation

@2000game

@2000game 2000game commented Jul 9, 2026

Copy link
Copy Markdown
Member

Summary

Closes #9.

Builds on the tag-triggered release flow from #44 (.github/workflows/release.yml) — this PR replaces the tag-push trigger with the remaining scope from #9:

  1. Automated versioning + changelog on merge to main via semantic-release (.releaserc.json: @semantic-release/commit-analyzer + @semantic-release/release-notes-generator + @semantic-release/github, all pinned via npx in CI rather than added as devDependencies). No manual tag push, no npm publish (private repo — GitHub Releases only). The GitHub Release's auto-generated notes double as the changelog; @semantic-release/github's comment/label side effects are disabled so the workflow only needs contents: write.
  2. Standalone dependency-free binaries via bun build --compile, cross-compiled from a single Linux runner for bun-darwin-arm64, bun-darwin-x64, and bun-linux-x64 (all pure-JS deps: commander, jiti, openapi-fetch, picocolors; Keychain access goes through the security CLI, not a native module).
  3. CI smoke-tests each binary on its native OS/arch (macos-14 for arm64, macos-13 for x64, ubuntu-latest for linux-x64) before the release job runs at all — see .github/scripts/smoke-test-binary.sh. Each binary runs ct --help and ct plan --config tests/fixtures/sample.config.ts with a bogus CT_HOST and no stored token. That's the deepest command that exercises jiti's runtime TS transpilation of a .config.ts (the actual risk of bun build --compile) without touching the network: the fixture must load and evaluate successfully, and the command must fail specifically at the authedSession() step ("Not logged in" on a clean runner, or the host-mismatch "Refusing to send the stored login token" if a credential happens to be present) — not at config load. A config-load failure is treated as a real bug and fails the job.
  4. README one-liner install — curl the platform binary from .../releases/latest/download/ct-<target>, chmod +x, move onto PATH; npm tarball (ct-cli.tgz) kept as a Node-based fallback.

Release pipeline shape

build (CI gate + compile 3 binaries + npm-pack tarball + INSTALL.md, uploaded as a workflow artifact) → smoke-darwin-arm64 / smoke-darwin-x64 / smoke-linux-x64 (each downloads the artifact, runs the smoke script against its own binary) → release (needs all three smoke jobs; downloads the artifact, runs npx semantic-release, which — only if there's a releasable feat:/fix:/breaking-change commit since the last release — creates the tag, the GitHub Release, and attaches the tarball + binaries as assets).

semantic-release publishes via the GitHub API using the default GITHUB_TOKEN, so the tag/release it creates does not re-trigger this push: branches: [main] workflow (GitHub doesn't fire push events for activity performed with the default token) — no loop.

Verified locally

  • npm run lint, npm run typecheck, npm test (369 passed / 4 skipped), npm run build all clean, on top of latest main (rebased in after two other PRs landed mid-task).
  • Workflow YAML parses cleanly (python3 -m yaml) and every embedded run: block (including the INSTALL.md heredoc) passes bash -n.
  • .releaserc.json is valid JSON.
  • The smoke-test script's logic is validated end-to-end against the real CLI running via tsx (not a compiled bun binary — bun isn't installed on this machine, and I did not install global tooling per the task constraints):
    • Positive case: ct --help succeeds, then ct plan against the fixture config with CT_HOST set to a bogus host correctly fails at the auth layer (on this dev machine that's the host-mismatch branch, since a real credential for a different host is in the Keychain) — script exits 0.
    • Negative case: pointing at a nonexistent config file produces a "Config file not found" error, and the script correctly does not treat that as a pass (confirms the pass/fail logic actually discriminates config-load failures from auth failures, rather than just checking for "any error").
  • actionlint and shellcheck are not installed on this machine and were not installed for this task (per the task's tooling constraint); YAML/bash were validated manually as above instead.

Not verified (CI-only)

  • That bun build --compile actually succeeds and produces a working binary — bun is not installed locally. This is exactly what the build + three smoke-* jobs exist to prove; if bundling misbehaves (e.g. jiti's dynamic import() doesn't survive compilation), the smoke jobs will fail loudly and no release will be cut.
  • That semantic-release actually cuts a real tag/release end-to-end with these exact plugin versions/config against this repo's GitHub API permissions.
  • Cross-compilation of bun-darwin-* targets from a Linux host (should work per Bun's documented cross-compile support, but unverified here).

Test plan

  • Merge to main, confirm the Release workflow runs build → all three smoke-* jobs → release, and that a feat:/fix: commit produces a new GitHub Release with a version bump, generated notes, and 4 attached assets (ct-cli.tgz, ct-darwin-arm64, ct-darwin-x64, ct-linux-x64, plus INSTALL.md).
  • Download ct-darwin-arm64 (or -x64) from the release on a clean Mac with no Node installed and confirm ct --help runs.
  • Confirm a non-releasable commit (e.g. chore:) on main runs the pipeline but does not publish a release.

2000game added 2 commits July 9, 2026 13:25
…trix (#9)

Replace the tag-triggered release flow (#44) with per-merge releases: every
push to main runs the CI gate, cross-compiles ct as standalone bun binaries
(darwin-arm64, darwin-x64, linux-x64), smoke-tests each one on its native
OS/arch (--help plus a real ct plan against a fixture .config.ts, so jiti's
runtime TS transpilation inside the compiled binary is actually exercised —
not just --help), and only then runs semantic-release (pinned via npx, no
new devDependencies) to compute the version, generate the changelog as
GitHub Release notes, create the tag, and publish the release with the
tarball + binaries attached. No manual tag push.

Release stays private/GitHub-only: no npm publish plugin. Comment/label
side effects on @semantic-release/github are disabled to keep the
GITHUB_TOKEN permission footprint at contents:write only.
…#9)

Point Install at the release binaries (no Node required) with the npm
tarball as a fallback, and describe the new push-to-main release flow that
replaces the old v* tag trigger.
@2000game
2000game merged commit 148a5f4 into main Jul 9, 2026
1 check passed
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.

feat: automated releases — install ct from the GitHub Releases page

1 participant