skillhub/cli/RELEASE.md
Cheney 7847e0aa33 fix(cli): address PR #441 review findings from @dongmucat
- Bind release tag to origin/main: PR body, end-of-run hint, and
  cli/RELEASE.md now use `git tag $TAG origin/main` so the tag is
  always placed on the merged commit, regardless of local branch state
- Reject prerelease tags in version computation: if the latest cli-v*
  tag contains non-X.Y.Z characters (e.g., -rc.1), exit with a clear
  message instead of crashing in node parsing
- Add pr-scripts.yml workflow: runs publish-cli-test.sh on scripts/**
  changes so the release script regression suite gates PRs
- Add Test 11 covering prerelease tag rejection
2026-05-19 14:57:16 +08:00

6.1 KiB

CLI Release Guide

Overview

CLI releases use a PR-based flow. Running make publish-cli on a clean main branch:

  1. Runs local build-and-test (lint, typecheck, test, build)
  2. Computes the next version from the latest cli-v* tag
  3. Creates a release/cli-vX.Y.Z branch with the version bump committed
  4. Pushes the branch and opens a PR to main

After the PR is merged, you manually tag and push — the tag triggers release-cli.yml which builds, publishes to npm, and creates a GitHub Release.

Prerequisites

Repository Secrets

Configure in GitHub repository → Settings → Secrets and variables → Actions:

Repository Variables (optional)

  • NPM_REGISTRY: npm registry URL (default: https://registry.npmjs.org)

Local Environment

  • node and bun installed
  • gh CLI installed and authenticated (gh auth login)
  • git with push access to the repository
  • On the main branch with a clean working tree

Package Configuration

In cli/package.json:

{
  "name": "@astron-team/skillhub",
  "publishConfig": {
    "access": "public"
  }
}

Release Process

Step 1: Run the publish script

From the repository root, on a clean main branch:

make publish-cli         # patch: 0.1.5 -> 0.1.6
make publish-cli-minor   # minor: 0.1.5 -> 0.2.0
make publish-cli-major   # major: 0.1.5 -> 1.0.0

scripts/publish-cli.sh performs the following steps:

  1. Verify gh CLI is installed and authenticated
  2. Verify the working tree is clean and on main
  3. git pull --ff-only from origin/main
  4. Run full local build-and-test (lint, typecheck, test, build)
  5. Compute the next version from the latest cli-v* tag
  6. Verify the tag and release branch don't already exist
  7. After interactive confirmation: create release branch, commit version bump, push, and open PR

Step 2: Merge the PR

Review and merge the PR on GitHub as usual.

Step 3: Tag and push

After the PR is merged:

git fetch origin main
git tag cli-vX.Y.Z origin/main   # replace with the actual version
git push origin cli-vX.Y.Z

This ensures the tag is always placed on the merge commit on origin/main, regardless of your local branch state.

Pushing the tag triggers CI which builds, publishes to npm, and creates a GitHub Release.

CI Workflow

release-cli.yml contains three jobs:

  1. build-and-test

    • Extract version from tag name (cli-v0.1.60.1.6) and write it into cli/package.json
    • Install deps, lint, typecheck, test, build
    • Verify the built CLI's runtime version matches the tag
  2. publish-npm

    • Skip if the target version already exists on the registry
    • Configure ~/.npmrc and run npm publish --access public
  3. create-release

    • Package dist/ + README + LICENSE as tar.gz and zip
    • Generate SHA256 checksums
    • Create a GitHub Release and upload artifacts

Verify Release

Release Audit Trail

GitHub Actions automatically records on each workflow run page:

  • Triggering user (the developer who pushed the tag, i.e. github.actor)
  • Trigger event (push tag or workflow_dispatch)
  • Tag name and commit SHA

The team can review the full audit trail in the Actions tab without any extra configuration.

Manual Trigger

From the Actions UI:

  1. Actions → Release CLI → "Run workflow"
  2. Enter an existing tag name matching cli-vX.Y.Z
  3. Optionally enable skip npm publish

Error Recovery

The script uses a cleanup state machine. If it fails at different stages:

  • Before push: release branch is deleted locally, you're returned to main
  • After push, before PR: the script prints recovery instructions (open PR manually or delete the remote branch)
  • After PR opened: success — no cleanup needed

If you need to manually clean up a failed release:

git checkout main
git branch -D release/cli-vX.Y.Z           # delete local branch
git push origin --delete release/cli-vX.Y.Z # delete remote branch (if pushed)

Troubleshooting

releases must be cut from 'main'

Switch back to main, pull the latest, and retry.

git working tree is not clean

Commit or stash local changes first.

tag cli-vX.Y.Z already exists

The previous release didn't clean up, or someone else released the same version. Check git tag --list 'cli-v*' and remote tags, then retry with a higher version.

branch release/cli-vX.Y.Z already exists

A previous release attempt left a stale branch. Delete it locally and/or on origin, then retry.

npm Publish Fails

  • 403 with 2FA message: NPM_TOKEN is not an Automation Token, or bypass 2FA is not enabled — regenerate with the correct type
  • 403 Forbidden: Package scope doesn't match token permissions — confirm publish rights for the @astron-team org
  • E404: The registry doesn't host this scope — check NPM_REGISTRY

Build / Test Fails

Reproduce locally:

make lint-cli && make typecheck-cli && make test-cli && make build-cli

Confirm the Bun version matches packageManager in cli/package.json.

Version Mismatch (runtime ≠ tag)

CI runs node dist/index.js version and requires the output to match the tag. If the CLI's version command implementation changes, update the verification logic in release-cli.yml accordingly.

Tag Naming Convention

  • CLI releases: cli-v* (e.g., cli-v0.1.6)
  • Repository releases: v* (e.g., v0.3.0)

The two tag namespaces are independent, allowing CLI and server to version separately.