From 8aedfd67a0aa77a0ec1fe3d6a5eeedb85916c03a Mon Sep 17 00:00:00 2001 From: Sung-Kyu Yoo Date: Sun, 6 Sep 2026 11:41:36 +0900 Subject: [PATCH] docs: the release procedure never mentioned the Homebrew tap it publishes The release process lived at docs/release.md and described the tag pipeline end to end except for what it hands to a second repository: .goreleaser.yaml's brews section publishes Formula/devcloud.rb to skyoo2003/homebrew-tap using a token minted from a GitHub App installed only there. A maintainer reading the doc to debug a failed release had nothing to check. Two smaller drifts alongside it. The doc said a tag produces "versioned *-alpine container images" where the config pushes four tags per release (vX.Y.Z, vX.Y, vX, latest), and it walked through changie batch and changie merge by hand while the Makefile has had `make changelog VERSION=` for both. The running example was v0.3.0, three releases behind v1.0.0. Moved to RELEASE.md at the repo root, next to the other governance files, and added to the archives files list in .goreleaser.yaml: docs/README.md links it, and a root file left off that list is absent from the tarball even though the docs tree that points at it ships. --- .github/pull_request_template.md | 2 +- .goreleaser.yaml | 1 + CONTRIBUTING.md | 2 +- docs/release.md => RELEASE.md | 89 +++++++++++-------- .../Documentation-20260906-220000.yaml | 5 ++ docs/README.md | 2 +- docs/compatibility-policy.md | 2 +- 7 files changed, 63 insertions(+), 40 deletions(-) rename docs/release.md => RELEASE.md (59%) create mode 100644 changes/unreleased/Documentation-20260906-220000.yaml diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 3ad1e55e..9ba673ee 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -26,4 +26,4 @@ Fixes # - [ ] Added/updated tests - [ ] Lint/format passes (`golangci-lint run`) - [ ] Updated documentation (if applicable) -- [ ] Added a Changie changelog fragment for user-facing changes (`changie new`, see [docs/release.md](../docs/release.md)) — or N/A (docs/tests/chore only) +- [ ] Added a Changie changelog fragment for user-facing changes (`changie new`, see [RELEASE.md](../RELEASE.md)) — or N/A (docs/tests/chore only) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 15ec391e..a2426248 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -46,6 +46,7 @@ archives: # points into source and CI config, which a binary archive has no business # carrying. - CONTRIBUTING.md + - RELEASE.md - CODE_OF_CONDUCT.md - GOVERNANCE.md - SECURITY.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ecc5e11b..934a16e4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,7 +28,7 @@ Thanks for your interest in contributing! This file is a short pointer — the f Conventional prefixes are preferred: `feat:`, `fix:`, `chore:`, `docs:`, `test:`, `refactor:`. -Release notes are **not** generated from commits — they come from [Changie](https://changie.dev) fragments. Add one for any user-facing change with `changie new` (see [docs/release.md](docs/release.md)). +Release notes are **not** generated from commits — they come from [Changie](https://changie.dev) fragments. Add one for any user-facing change with `changie new` (see [RELEASE.md](RELEASE.md)). ## License of Contributions diff --git a/docs/release.md b/RELEASE.md similarity index 59% rename from docs/release.md rename to RELEASE.md index c87ecb31..74257170 100644 --- a/docs/release.md +++ b/RELEASE.md @@ -12,8 +12,8 @@ Versions follow [Semantic Versioning](https://semver.org): `vMAJOR.MINOR.PATCH`. | Pipeline | Trigger | What it produces | Workflow | |----------|---------|------------------|----------| -| **Release** | pushing a `v*` tag (or manual dispatch) | GitHub Release with binaries, checksums, and versioned `*-alpine` container images | [`.github/workflows/release.yml`](../.github/workflows/release.yml) | -| **CD** | every successful CI run on a push | rolling multi-arch `latest` / branch container images to GHCR | [`.github/workflows/cd.yml`](../.github/workflows/cd.yml) | +| **Release** | pushing a `v*` tag (or manual dispatch) | GitHub Release with binaries and checksums, versioned `*-alpine` container images, a Homebrew formula | [`.github/workflows/release.yml`](.github/workflows/release.yml) | +| **CD** | every successful CI run on a push | rolling multi-arch `latest` / branch container images to GHCR | [`.github/workflows/cd.yml`](.github/workflows/cd.yml) | CD keeps `ghcr.io/skyoo2003/devcloud:latest` current with `main`. The Release pipeline is what produces an actual tagged, downloadable release. This document covers the Release pipeline. @@ -30,7 +30,7 @@ You'll be prompted for a **kind** (`Added`, `Changed`, `Deprecated`, `Removed`, `Security`, `Documentation`), a one-line **body**, and the **issue number**. This writes a small YAML file under `changes/unreleased/`. Commit it alongside your code change. -Config lives in [`.changie.yaml`](../.changie.yaml). +Config lives in [`.changie.yaml`](.changie.yaml). Prefer `changie new` over writing the YAML by hand: it enforces the issue number, and a fragment without one renders as a dead link in the release notes. @@ -59,48 +59,45 @@ The rest are only caught here. - [ ] **Deprecation review.** If this release *removes* anything previously deprecated — a config key, an env var, an admin route — confirm it shipped for at least one release with a warning first. The precedent is the `dashboard` → `admin` rename in - [`internal/config/config.go`](../internal/config/config.go): the old key kept working, + [`internal/config/config.go`](internal/config/config.go): the old key kept working, emitted a warning, and only then became removable. Removing without that overlap is a major-version change. The full procedure, and the surfaces it applies to, is - [compatibility-policy.md](compatibility-policy.md). + [docs/compatibility-policy.md](docs/compatibility-policy.md). - [ ] **Compatibility review.** If this release changes anything on the guaranteed list in - [compatibility-policy.md](compatibility-policy.md), it is a major bump — or it is a bug. - Additive change (a new config key, a new response field, a new service) is a minor bump. + [docs/compatibility-policy.md](docs/compatibility-policy.md), it is a major bump — or it + is a bug. Additive change (a new config key, a new response field, a new service) is a + minor bump. ## Cutting a release 1. **Make sure `main` is green** and holds all changes you want in the release. -2. **Batch the unreleased fragments** into a version file. Pick the next version per SemVer: +2. **Batch the unreleased fragments** into a version file, then merge them into the changelog. + Pick the next version per SemVer: ```sh - changie batch v0.3.0 + make changelog VERSION=v1.1.0 ``` - This consumes everything in `changes/unreleased/` and writes `changes/v0.3.0.md`. + That is `changie batch v1.1.0 && changie merge`, which consumes everything in + `changes/unreleased/`, writes `changes/v1.1.0.md`, and regenerates + [`CHANGELOG.md`](CHANGELOG.md) from all version files. Run the two commands directly if you + want to inspect the batched file before it reaches the changelog. -3. **Merge into the changelog:** - - ```sh - changie merge - ``` - - This regenerates [`CHANGELOG.md`](../CHANGELOG.md) from all version files. - -4. **Commit** the generated files: +3. **Commit** the generated files: ```sh git add changes/ CHANGELOG.md - git commit -m "chore(release): v0.3.0" + git commit -m "chore(release): v1.1.0" git push origin main ``` -5. **Tag and push.** The tag name **must** match the batched version — the Release workflow +4. **Tag and push.** The tag name **must** match the batched version — the Release workflow fails if `changes/.md` does not exist. ```sh - git tag v0.3.0 - git push origin v0.3.0 + git tag v1.1.0 + git push origin v1.1.0 ``` Pushing the tag triggers the Release workflow. It will: @@ -109,35 +106,55 @@ Pushing the tag triggers the Release workflow. It will: follows. Moving the tag mid-run therefore cannot make the guardrails vouch for one commit while GoReleaser publishes another, - re-run the guardrails against that commit and stop before publishing anything if any of them - fails: the Go test suite on amd64 and arm64, the boto3 compatibility suite, and the codegen - drift check. CI is not relied on here — it races the tag, and `compat.yml` does not trigger - on tags at all, -- verify `changes/v0.3.0.md` exists (guard against tagging without release notes), contains - only what `changie batch` renders, and that no entry in it is missing its issue number, + fails: the Go test suite on amd64 and arm64, the boto3 compatibility suite (run against a + binary GoReleaser built, not `go build`), and the codegen drift check. CI is not relied on + here — it races the tag, and `compat.yml` does not trigger on tags at all, +- verify `changes/v1.1.0.md` exists (guard against tagging without release notes), carries + exactly one version heading and that it names *this* tag (a file copied from an earlier + release is rejected), contains only what `changie batch` renders, and that every entry ends + in a valid issue link, - run GoReleaser, which builds binaries for **darwin/linux/windows × amd64/arm64**, packages them as `tar.gz` (`zip` on Windows) with the `docs/` tree and the top-level files it and `README.md` link to, and generates a SHA-256 `CHECKSUMS` file, -- build and push `*-alpine` container images to `ghcr.io/skyoo2003/devcloud`, -- publish a **GitHub Release** whose notes come from `changes/v0.3.0.md` +- build and push container images to `ghcr.io/skyoo2003/devcloud`, tagged + `v1.1.0-alpine`, `v1.1-alpine`, `v1-alpine`, and `latest-alpine`, +- publish `Formula/devcloud.rb` to the Homebrew tap (see below), +- publish a **GitHub Release** whose notes come from `changes/v1.1.0.md` (`--release-notes`, `mode: replace`). -GoReleaser config: [`.goreleaser.yaml`](../.goreleaser.yaml). +GoReleaser config: [`.goreleaser.yaml`](.goreleaser.yaml). + +## Homebrew tap + +GoReleaser's `brews` section publishes `Formula/devcloud.rb` to a separate tap repository — +`homebrew-tap` under the same owner, named by `HOMEBREW_TAP_OWNER` / `HOMEBREW_TAP_REPO` in +[`.github/workflows/release.yml`](.github/workflows/release.yml). + +The job's own `GITHUB_TOKEN` cannot write to another repository, so the workflow mints a +short-lived token from a GitHub App installed **only** on the tap repo, using the +`TAP_APP_ID` and `TAP_APP_PRIVATE_KEY` secrets. If a release fails at the formula step, check +that the App is still installed on the tap and that neither secret has expired. + +The formula's `test` block only asserts the `-h` usage text: `devcloud` is a long-running +server with no subcommands, so actually starting it would hang the test. + +Token minting is skipped on a dry run, where GoReleaser publishes nothing. ## Dry run To validate the build without publishing, run the workflow manually from the Actions tab -(**Release → Run workflow**) with a tag and `dry_run: true`. This runs GoReleaser in -`--snapshot` mode: it builds artifacts and uploads them to the run, but publishes nothing to -GHCR or GitHub Releases. +(**Release → Run workflow**) with a tag and `dry_run: true` (the default for manual dispatch). +This runs GoReleaser in `--snapshot` mode: it builds artifacts and uploads them to the run, but +publishes nothing to GHCR, the Homebrew tap, or GitHub Releases. ## Requirements recap -- The tag (`v0.3.0`) and the fragment file (`changes/v0.3.0.md`) must match exactly. +- The tag (`v1.1.0`) and the fragment file (`changes/v1.1.0.md`) must match exactly. - `changie batch` + `changie merge` must be committed **before** the tag is pushed. - The tagged commit must pass the Go test suite; the workflow will not publish otherwise. - Every entry in the batched notes needs an issue number. - No manual GitHub Release editing — release notes are owned by Changie fragments. Docs ship inside the release archive, so they are versioned by tag: the `docs/` tree in -`devcloud_v0.3.0_linux_amd64.tar.gz` describes exactly the binary beside it. There is no +`devcloud_v1.1.0_linux_amd64.tar.gz` describes exactly the binary beside it. There is no separate docs site to version. diff --git a/changes/unreleased/Documentation-20260906-220000.yaml b/changes/unreleased/Documentation-20260906-220000.yaml new file mode 100644 index 00000000..69004e42 --- /dev/null +++ b/changes/unreleased/Documentation-20260906-220000.yaml @@ -0,0 +1,5 @@ +kind: Documentation +body: 'The release procedure moved from `docs/release.md` to `RELEASE.md` at the repo root, and three things it never stated are now in it: GoReleaser publishes a Homebrew formula to a separate tap repository through a GitHub App token scoped to that repo alone, a tagged build pushes four `*-alpine` image tags rather than the one the doc implied, and `make changelog VERSION=` is the batch and merge pair. The archive files list carries `RELEASE.md`, so the docs index link to it resolves inside a release tarball and not only on GitHub' +time: 2026-09-06T22:00:00.000000+09:00 +custom: + Issue: "148" diff --git a/docs/README.md b/docs/README.md index de0c574e..0e100f13 100644 --- a/docs/README.md +++ b/docs/README.md @@ -37,7 +37,7 @@ Located under [`services/`](services/): ## Contributing - **[Contributing Guide](contributing.md)** — dev setup, testing, codegen, adding new services -- **[Releasing](release.md)** — Changie + GoReleaser release process, versioning, dry runs +- **[Releasing](../RELEASE.md)** — Changie + GoReleaser release process, versioning, Homebrew tap, dry runs - Root-level pointers: [CONTRIBUTING.md](../CONTRIBUTING.md), [CODE_OF_CONDUCT.md](../CODE_OF_CONDUCT.md), [SECURITY.md](../SECURITY.md), [SUPPORT.md](../SUPPORT.md) ## Meta diff --git a/docs/compatibility-policy.md b/docs/compatibility-policy.md index 066fc6ac..31abe861 100644 --- a/docs/compatibility-policy.md +++ b/docs/compatibility-policy.md @@ -160,7 +160,7 @@ Silence is not deprecation. A removed key that YAML would otherwise drop without kept in the parser purely to warn — that is why `auth` still produces a message telling you SigV4 is not enforced rather than being ignored. -The pre-flight checklist in [release.md](release.md#pre-flight-checklist) makes this a step in +The pre-flight checklist in [RELEASE.md](../RELEASE.md#pre-flight-checklist) makes this a step in cutting a release, not a thing to remember. ## Reporting a break