Files
commandkit/docs/release.md

7.9 KiB

CommandKit release procedure

Scope and prerequisites

CommandKit publishes source-only Go module releases: an immutable annotated version tag for one committed candidate and its matching checked-in release note. This procedure does not publish a hosted release page, binaries, checksums, or other delivery assets, and it requires no delivery credentials.

Use branch main, remote origin, and the expected effective push endpoint https://gitea.maximumdirect.net/eric/CommandKit.git. The profile enforces that endpoint during publication. The maintainer needs the selected release toolchain, Git with push access to origin, and a POSIX shell.

This procedure follows the release policy; the release-note footer follows the OSS discoverability policy.

ReleaseKit v0.3.0 owns candidate validation, publication guards, annotated tag creation, tag publication, and outcome reporting. The repository profile selects only its default candidate suite. The launcher bootstraps the module-selected native executable into ignored .releasekit/ storage and invokes its publish command. The upstream CLI reference, profile reference, and publication guide own detailed command, profile, and status contracts.

Release toolchain

The selected release toolchain is Go 1.26.8. The consumer minimum is Go 1.26.0; validation uses a later patch within that supported Go series. The consumer guide explains the minimum and its shared tool-module requirement; the release policy governs changes to either selection.

Use this toolchain for candidate validation and the native ReleaseKit bootstrap, including CI jobs that perform those tasks. The launcher and ReleaseKit invoke go from PATH with GOTOOLCHAIN=local, so select that installation in the shell used for preparation and publication.

If it is not installed, use the standard Go installation method. Ensure Go's executable installation directory (GOBIN, or GOPATH/bin by default) is on PATH so the downloaded-version wrapper can be invoked:

GOWORK=off GOTOOLCHAIN=local go install golang.org/dl/go1.26.8@latest
go1.26.8 download
export PATH="$(go1.26.8 env GOROOT)/bin:$PATH"
hash -r
GOWORK=off GOTOOLCHAIN=local go version

The final command should identify Go 1.26.8. An existing Go 1.26.8 installation may be selected directly on PATH instead. Toolchain installation and dependency preparation precede the offline publication checks; they add no validation pass.

Prepare and publish a candidate

Choose a fresh, unused stable Semantic Version tag in the form vMAJOR.MINOR.PATCH, with no leading zeroes. Before v1.0.0, a minor release may change a documented contract if its note explains the impact and consumer action; a patch release must not intentionally break contracts within its minor line. Never reuse a version previously published through any release channel. The first version is selected during release preparation, not embedded in the tool or profile.

Start from main and prepare a note at docs/releases/<version>.md. Begin with a heading containing CommandKit and the exact version. Include a useful summary and any compatibility impact or upgrade instructions. End it with the license and attribution links at that version's immutable source tag, replacing VERSION with the selected tag:

[COPYRIGHT](https://gitea.maximumdirect.net/eric/CommandKit/src/tag/VERSION/COPYRIGHT) |
[LICENSE](https://gitea.maximumdirect.net/eric/CommandKit/src/tag/VERSION/LICENSE) |
[ATTRIBUTION](https://gitea.maximumdirect.net/eric/CommandKit/src/tag/VERSION/docs/oss/attribution.md)

Review these paths in the candidate; the future tag URLs do not need to resolve before publication.

Prepare dependencies separately from the offline candidate suite:

Run these commands in the same shell session so the version variable remains set:

commandkit_version=vX.Y.Z
GOWORK=off GOTOOLCHAIN=local go mod download

If dependency preparation changes go.mod or go.sum, review and commit those changes with the candidate. Commit the note and all candidate changes on main, then publish that commit to origin:

git push origin HEAD:refs/heads/main
./scripts/release "$commandkit_version"

The launcher builds the ReleaseKit version selected by the module in readonly mode. After preflight, publish runs the offline default Go suite once, checks the candidate and remote guards, creates an annotated tag from the committed note, and pushes only that tag object. The launcher does not push main; push the candidate first. check is available for optional diagnosis and is not a required step before publish. The automated publication workflow runs the final-candidate suite once; configured additional checks should avoid duplicating that suite. Maintainers may rerun diagnostics when useful. Successful source publication does not wait for CI, module availability, or consumer installation.

Completion and statuses

Publication is complete when the intended annotated tag is successfully pushed to the expected endpoint, or when an exact tag-object query confirms that push after an error. Status 0 reports confirmed publication. Status 1 reports a known failure or conflict; status 2 reports invalid input or profile; status 3 means the push outcome remains unresolved. See the upstream CLI status contract.

For a failed or ambiguous push, use the exact endpoint, version, and annotated tag object reported by the invocation. Confirm that Git still resolves the captured endpoint to itself before querying only that tag ref:

releasekit_endpoint='COPY THE CAPTURED ENDPOINT EXACTLY'
releasekit_version='vX.Y.Z'
releasekit_tag_object='COPY THE REPORTED TAG OBJECT ID'
releasekit_ref="refs/tags/$releasekit_version"

releasekit_resolved_endpoint=$(git ls-remote --get-url -- "$releasekit_endpoint") || {
    printf '%s\n' 'publication state is unresolved: endpoint resolution failed' >&2
    exit 1
}
if [ "$releasekit_resolved_endpoint" != "$releasekit_endpoint" ]; then
    printf '%s\n' 'publication state is unresolved: endpoint resolution changed' >&2
    exit 1
fi
releasekit_remote_tag=$(git ls-remote --refs -- "$releasekit_endpoint" "$releasekit_ref") || {
    printf '%s\n' 'publication state is unresolved: remote query failed' >&2
    exit 1
}
printf '%s\n' "$releasekit_remote_tag"

--refs returns the annotated tag object's ID, not its peeled commit. Compare that ID with releasekit_tag_object: an exact match confirms publication; a different object is a conflict; successful output with no matching ref proves absence at the captured endpoint. A failed query or changed URL resolution is unresolved, never evidence of absence. Do not query a different fetch URL or follow a rewritten endpoint to infer the captured destination's state.

ReleaseKit retains a local tag after a failed push, so the tag blocks an ordinary retry. After proving the tag absent at the captured endpoint, confirm that it was never published or used by another release channel before deliberately removing the unpublished local tag:

git tag -d "$releasekit_version"

Do not move, overwrite, recreate, or delete a published tag. If the exact tag object is present remotely, keep it. Resolve conflicts with a fresh version; corrections to published source require a new version. A transport failure alone does not require a new version.