Repository navigation
ci: cut releases from a workflow that runs release.sh #336
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,167 @@ | ||
| # Cut Release — run scripts/release.sh from CI. | ||
| # | ||
| # Three steps, one workflow: | ||
| # | ||
| # 1. Dispatch with an explicit version: | ||
| # gh workflow run cut-release.yml -f version=0.40.0 | ||
| # The `prepare` job runs `scripts/release.sh prepare <version>`: skill | ||
| # check, release/<version> branch, cargo-release bump (no publish, no | ||
| # tag), changelog validation, pushes the branch and opens the release PR. | ||
| # The job then enables auto-merge (squash) on that PR. | ||
| # 2. The release PR merges on its own once CI and the review gate pass. | ||
| # 3. The merge fires this workflow again (pull_request closed, head | ||
| # release/*). The `publish` job runs `scripts/release.sh finish` on main: | ||
| # skill check with --require-version, annotated v<version> tag, push. | ||
| # The tag push triggers release.yml (cargo-dist), which builds, publishes | ||
| # the GitHub Release and the Homebrew formula. | ||
| # | ||
| # Both jobs act as the hotdata-automation GitHub App rather than GITHUB_TOKEN: | ||
| # a PR opened or a tag pushed with GITHUB_TOKEN triggers no other workflows, | ||
| # so CI would never run on the release PR and release.yml would never see the | ||
| # tag. | ||
| # | ||
| # The skill-drift check inside `prepare` is not bypassed here (no | ||
| # SKIP_SKILL_DRIFT). If the job fails on it, update skills/ on main first and | ||
| # dispatch again. | ||
|
|
||
| name: Cut Release | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| inputs: | ||
| version: | ||
| description: 'Version to release, e.g. 0.40.0' | ||
| required: true | ||
| type: string | ||
| pull_request: | ||
| types: [closed] | ||
| branches: [main] | ||
|
|
||
| permissions: {} | ||
|
|
||
| concurrency: | ||
| group: cut-release | ||
| cancel-in-progress: false | ||
|
|
||
| jobs: | ||
| prepare: | ||
| name: Open release PR | ||
| if: github.event_name == 'workflow_dispatch' | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Generate GitHub App token | ||
| id: app-token | ||
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 | ||
| with: | ||
| app-id: ${{ secrets.HOTDATA_AUTOMATION_APP_ID }} | ||
| private-key: ${{ secrets.HOTDATA_AUTOMATION_PRIVATE_KEY }} | ||
| owner: hotdata-dev | ||
|
|
||
| # Full history and tags: check-skills.sh diffs against the last v* tag, | ||
| # and cargo-release pushes the release branch with the app token. | ||
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | ||
| with: | ||
| token: ${{ steps.app-token.outputs.token }} | ||
| persist-credentials: true | ||
| fetch-depth: 0 | ||
| fetch-tags: true | ||
|
|
||
| - name: Install Rust | ||
| uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | ||
|
|
||
| - name: Cache cargo | ||
| uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 | ||
| with: | ||
| path: | | ||
| ~/.cargo/registry | ||
| ~/.cargo/git | ||
| target | ||
| key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} | ||
| restore-keys: | | ||
| ${{ runner.os }}-cargo- | ||
|
|
||
| # cargo-release does the bump; git-cliff is its pre-release-hook | ||
| # (Cargo.toml [package.metadata.release]) and writes CHANGELOG.md. | ||
| - name: Install cargo-release and git-cliff | ||
| uses: taiki-e/install-action@9afc45b9c8ff2049b2e4e34df3db97754711d5c1 # cargo-llvm-cov tag, same pin as ci.yml | ||
| with: | ||
| tool: cargo-release,git-cliff | ||
|
|
||
| - name: Configure git identity | ||
| env: | ||
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | ||
| APP_SLUG: ${{ steps.app-token.outputs.app-slug }} | ||
| run: | | ||
| slug="${APP_SLUG}[bot]" | ||
| id="$(gh api "/users/${slug}" --jq .id)" | ||
| git config user.name "$slug" | ||
| git config user.email "${id}+${slug}@users.noreply.github.com" | ||
|
|
||
| - name: Prepare release | ||
| env: | ||
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | ||
| VERSION: ${{ inputs.version }} | ||
| run: scripts/release.sh prepare "$VERSION" | ||
|
|
||
| - name: Enable auto-merge on the release PR | ||
| env: | ||
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | ||
| run: | | ||
| branch="$(git rev-parse --abbrev-ref HEAD)" | ||
| number="$(gh pr list --head "$branch" --state open --json number --jq '.[0].number')" | ||
| [[ -n "$number" ]] || { echo "::error::no open PR for $branch"; exit 1; } | ||
| gh pr merge "$number" --squash --auto | ||
|
|
||
| publish: | ||
| name: Tag release | ||
| if: >- | ||
| github.event_name == 'pull_request' | ||
| && github.event.pull_request.merged == true | ||
| && startsWith(github.event.pull_request.head.ref, 'release/') | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Generate GitHub App token | ||
| id: app-token | ||
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 | ||
| with: | ||
| app-id: ${{ secrets.HOTDATA_AUTOMATION_APP_ID }} | ||
| private-key: ${{ secrets.HOTDATA_AUTOMATION_PRIVATE_KEY }} | ||
| owner: hotdata-dev | ||
|
|
||
| # finish expects to be on main and pushes the tag with the app token. | ||
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | ||
| with: | ||
| ref: main | ||
| token: ${{ steps.app-token.outputs.token }} | ||
| persist-credentials: true | ||
| fetch-depth: 0 | ||
| fetch-tags: true | ||
|
|
||
| - name: Install Rust | ||
| uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | ||
|
|
||
| - name: Cache cargo | ||
| uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 | ||
| with: | ||
| path: | | ||
| ~/.cargo/registry | ||
| ~/.cargo/git | ||
| target | ||
| key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} | ||
| restore-keys: | | ||
| ${{ runner.os }}-cargo- | ||
|
|
||
| - name: Configure git identity | ||
| env: | ||
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | ||
| APP_SLUG: ${{ steps.app-token.outputs.app-slug }} | ||
| run: | | ||
| slug="${APP_SLUG}[bot]" | ||
| id="$(gh api "/users/${slug}" --jq .id)" | ||
| git config user.name "$slug" | ||
| git config user.email "${id}+${slug}@users.noreply.github.com" | ||
|
|
||
| - name: Tag and push | ||
| env: | ||
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | ||
| run: scripts/release.sh finish | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,29 +1,38 @@ | ||
| # Releasing | ||
|
|
||
| Releases use a two-phase workflow wrapping [`cargo-release`](https://github.com/crate-ci/cargo-release). | ||
| Releases run in CI. The `Cut Release` workflow (`.github/workflows/cut-release.yml`) calls the same two-phase script, `scripts/release.sh`, that wraps [`cargo-release`](https://github.com/crate-ci/cargo-release). | ||
|
|
||
| **Phase 1 — prepare** | ||
| ## Normal path | ||
|
|
||
| ```sh | ||
| scripts/release.sh prepare <version> | ||
| gh workflow run cut-release.yml -f version=0.40.0 | ||
| ``` | ||
|
|
||
| Runs the skill check (below), then creates a `release/<version>` branch, bumps the version (including the `version:` line in every `SKILL.md`), updates `CHANGELOG.md`, pushes the branch, and opens a pull request. | ||
| 1. **prepare** — the workflow runs `scripts/release.sh prepare 0.40.0`: skill check (below), `release/0.40.0` branch, version bump (including the `version:` line in every `SKILL.md`), `CHANGELOG.md` update, push, and a pull request. The job then enables auto-merge on the PR. | ||
| 2. **merge** — the PR squash-merges on its own once CI and the review gate pass. | ||
| 3. **finish** — the merge triggers the workflow again, which runs `scripts/release.sh finish` on `main`: skill check with `--require-version`, annotated `v0.40.0` tag, push. The tag triggers the dist workflow (`release.yml`), which builds the binaries, publishes the GitHub Release, and updates the Homebrew formula. | ||
|
|
||
| **Phase 2 — finish** | ||
| The workflow acts as the `hotdata-automation` GitHub App. A PR opened or a tag pushed with the default `GITHUB_TOKEN` would trigger no other workflows. | ||
|
|
||
| If the `prepare` job fails on the skill check, the skills lag the code: update `skills/` on `main` first (see below), then dispatch again. CI does not set `SKIP_SKILL_DRIFT=1`. | ||
|
|
||
| ## Manual path | ||
|
|
||
| The script still works locally with the same two phases: | ||
|
|
||
| ```sh | ||
| scripts/release.sh finish | ||
| scripts/release.sh prepare <version> # branch, bump, changelog, PR | ||
| scripts/release.sh finish # on main after the PR merges: tag and push | ||
| ``` | ||
|
|
||
| Switches to `main`, pulls latest, runs the skill check with `--require-version`, tags the release, and triggers the dist workflow. | ||
| If you run `prepare` locally, the merge of its `release/<version>` PR still triggers the workflow's `finish` job, so do not run `finish` by hand as well. | ||
|
|
||
| ## Skill check | ||
|
|
||
| Both phases run `scripts/check-skills.sh`. The agent skills under `skills/` ship in every release (`skills.tar.gz`, `SKILL.md`), so the script refuses to release when they lag the code: | ||
|
|
||
| - **Drift** — if `src/` or `README.md` changed since the last `v*` tag and `skills/` did not, the run fails and lists the commits to review. Update the skills and commit, or set `SKIP_SKILL_DRIFT=1` when the release is verified to be skill-neutral. | ||
| - **Drift** — if `src/` or `README.md` changed since the last `v*` tag and `skills/` did not, the run fails and lists the commits to review. Update the skills and commit, or set `SKIP_SKILL_DRIFT=1` (local runs only) when the release is verified to be skill-neutral. | ||
| - **Coverage** — every subcommand the built binary exposes must appear as `hotdata <group> <sub>` somewhere in `skills/**/*.md`, and every long flag in its `--help` must be mentioned too. CI runs this check on every pull request (with the drift check skipped), so a new command or flag cannot merge undocumented. | ||
| - **Version** (`finish` only) — every `SKILL.md` frontmatter `version:` must equal the crate version. | ||
|
|
||
| The drift check is a reminder, not a judge of prose. Before `prepare`, read the commits since the last tag and update `skills/hotdata/SKILL.md` and the subskills to match the current `--help` output and behavior. | ||
| The drift check is a reminder, not a judge of prose. Before cutting a release, read the commits since the last tag and update `skills/hotdata/SKILL.md` and the subskills to match the current `--help` output and behavior. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Move this
concurrencyblock into theprepareandpublishjobs. (not blocking)At workflow level, every closed PR on
mainjoins thecut-releasegroup. GitHub keeps at most one pending run per group and cancels the older pending run. A release-PR merge run can wait behind another run in the group. An unrelated PR that closes during that wait cancels the release-PR run. Thev<version>tag is then not pushed, and nothing reports the failure. Job-level concurrency leaves skipped jobs outside the group.