Skip to content

feat: ✨ add release-promote composite action - #77

Merged
tomgrv merged 13 commits into
developfrom
claude/multi-repo-consistency-release-7se0jy
Sep 3, 2026
Merged

feat: ✨ add release-promote composite action#77
tomgrv merged 13 commits into
developfrom
claude/multi-repo-consistency-release-7se0jy

Conversation

@tomgrv

@tomgrv tomgrv commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Summary

Part of a coordinated consistency pass across tomgrv/devcontainer-features, tomgrv/actions, tomgrv/scripts, and perspikapps/vps. Companion PRs: tomgrv/scripts#3 (merged, released as v0.3.0), tomgrv/devcontainer-features#143, perspikapps/vps#10.

  • Adds release-promote/ — a composite action (matching every other action in this repo: action.yml + run.sh) consolidating the git beta && git prod logic every repo in this family had copy-pasted and let drift (different setup-node pins, an unpinned gitutils install from a moving default branch). It pulls git-release-beta/git-release-prod from tomgrv/scripts (pinned via a required scripts-ref input, now v0.3.0) instead of installing the whole gitutils devcontainer feature just for two scripts, reuses config-bot for the git identity instead of hand-rolling it, and adds a dry-run input for safe verification without pushing to main.
  • Adds setup-scripts/ — the idempotent zz_use bootstrap release-promote needed, extracted into its own composite action with optional scripts (space-separated tool names) and scripts-ref (shared pin) inputs. Also takes an optional scripts-branch input (default main) for which branch to bootstrap zz_use from.
  • Adds setup-gitversion/ — a composite action installing the GitVersion toolchain (docker-wrapped GitVersion CLI plus gv/bump-tag/bump-changelog/bump-version) that git-release-beta/git-release-prod call directly. Fetches these directly from the gitversion devcontainer feature's source (mirroring install-tools.sh's own logic) rather than running that feature's own installer — verified live and locally that the installer deploys unrelated dev-environment stub files into the checked-out repo and doesn't reliably land its bin/ scripts on PATH outside a real devcontainer.
  • Adds setup-gitflow/ — installs the git-flow extension if missing and runs git flow init against the checkout. git-release-beta/git-release-prod call git flow release start/git flow <flow> finish directly, and neither the binary nor its local git config exist on a bare CI runner. Mirrors install-gitflow.sh/configure-gitflow.sh, including their root-vs-sudo install branching (GitHub-hosted runners are non-root with passwordless sudo).
  • release-promote composes all three (setup-scripts, setup-gitversion, setup-gitflow) alongside config-bot, invoking each sibling's run.sh directly via github.action_path rather than nested uses: — verified live that neither a fixed @main ref (404s on a sibling that hasn't reached main yet) nor a relative ./name (resolves against the calling repo's checkout, not this one) works for every caller/ref.
  • Architecture note: release-promote started as a workflow_call reusable workflow, then was restructured into a composite action per explicit direction, since a composite action composes naturally as one step in each repo's own release-main job rather than taking over the whole job. setup-scripts/setup-gitversion/setup-gitflow were then split out of it, for the same compositional reason and because each is independently reusable.
  • This repo's own release-main.yml now calls release-promote too (dogfooding) as uses: ./release-promote, pinned to tomgrv/scripts@v0.3.0.
  • Adds docs/release-process.md: the canonical release flow plus the tag/branch-protection bypass checklist every repo in the family needs applied by hand (no tool here has API access to repo rulesets, so this stays a manual, documented step rather than something a workflow silently bypasses).

Live-verified

All three of setup-gitversion/setup-gitflow's bugs (missing toolchain install, wrong install mechanism, missing sudo) and the sibling-action uses: resolution bug were caught by actually running tomgrv/scripts' release-main workflow against this branch — dry-run rehearsals first, then culminating in a real release: tomgrv/scripts is now tagged v0.3.0 on main (run 33797784005).

Cross-repo dependency

References tomgrv/scripts v0.3.0 (released, tag pinned). release-promote's public interface (scripts-ref, dry-run, github-token) is unchanged by the setup-scripts/setup-gitversion/setup-gitflow extractions, so no other repo's release-main.yml needs updating for that part — only the scripts-ref/uses: pins themselves, once this repo tags a release.

Test plan

  • release-promote/run.bats — 6 tests
  • setup-scripts/run.bats — 6 tests
  • setup-gitversion/run.bats — 4 tests
  • setup-gitflow/run.bats — 6 tests
  • action.yml, workflow YAML, and JSON files all parse cleanly
  • npx prettier --check clean on all YAML/JSON/MD (.sh/.bats aren't in this repo's prettier glob — confirmed against the pre-existing config-bot/run.bats, which hits the same formatter error, so this isn't a regression)
  • Live end-to-end: tomgrv/scripts' real release succeeded through this exact branch's release-promote
  • Once merged, tag a release and update every consumer's uses: ref to pin the tag instead of this branch

🤖 Generated with Claude Code

https://claude.ai/code/session_01Dau8uW2rrctkyAxngdR1ix

Consolidates the release-main.yml logic every tomgrv/perspikapps repo
duplicated (and had let drift -- different setup-node pins, an unpinned
gitutils install from a moving default branch) into one reusable
workflow other repos call via workflow_call. Pulls git-release-beta/
git-release-prod from tomgrv/scripts (pinned via scripts_ref) instead of
installing the whole gitutils devcontainer feature just to get two
scripts, and adds a dry_run input for safe verification.

This repo's own release-main.yml now calls it too, and docs/release-
process.md documents the canonical flow plus the tag/branch-protection
bypass checklist every repo needs applied by hand (no tool has API
access to repo rulesets, so this stays a manual step).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dau8uW2rrctkyAxngdR1ix
@github-actions github-actions Bot changed the title feat: add shared release-promote reusable workflow feat: ✨ add shared release-promote reusable workflow Sep 3, 2026
release-promote was a workflow_call reusable workflow; convert it to a
tomgrv/actions composite action (release-promote/action.yml + run.sh),
matching every other action in this repo, so it composes as a normal
step in a release-main job instead of taking over the whole job via
workflow_call.

Reuses config-bot for git identity (was hand-rolled before) rather than
duplicating it. Same behavior otherwise: pulls git-release-beta/
git-release-prod from tomgrv/scripts (pinned via scripts-ref), dry-run
input, clear error pointing at the tag/branch-protection bypass
checklist on a protected-ref push failure.

This repo's own release-main.yml now calls it as `uses: ./release-promote`
within its job instead of delegating the whole job via workflow_call.
Adds release-promote/run.bats (6 tests, all passing) and a README
following the repo's action-doc convention.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dau8uW2rrctkyAxngdR1ix
@tomgrv tomgrv changed the title feat: ✨ add shared release-promote reusable workflow feat: add release-promote composite action Sep 3, 2026
Pulls the idempotent zz_use bootstrap out of release-promote into its
own reusable composite action, setup-scripts, with optional `scripts`
(space-separated tool names) and `scripts-ref` (shared pin) inputs --
generic enough for any future action that needs specific tomgrv/scripts
tools on PATH, not just release-promote.

release-promote now composes it (`uses: tomgrv/actions/setup-scripts`)
for installing git-release-beta/git-release-prod, the same way
check-dockerfile already composes setup-reviewdog. Its own run.sh is
now just the beta/prod-running logic; the bootstrap test coverage moved
to setup-scripts/run.bats (6 tests) and release-promote/run.bats was
trimmed to the 6 tests that are actually its own concern. All 12 pass.

release-promote's public interface (scripts-ref, dry-run, github-token
inputs) is unchanged, so no other repo's release-main.yml needs to
change for this.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dau8uW2rrctkyAxngdR1ix
@github-actions github-actions Bot changed the title feat: add release-promote composite action feat: ✨ add release-promote composite action Sep 3, 2026
Comment thread setup-scripts/run.sh Outdated
# that ships it, or a re-run in the same job) is reused as-is -- only
# fetch/install when it's genuinely missing.
if ! command -v zz_use > /dev/null 2>&1; then
curl -fsSL "${ZZ_SCRIPTS_SETUP_URL:-https://raw.githubusercontent.com/tomgrv/scripts/main/setup.sh}" -o /tmp/zz_setup.sh

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allow branch specification via optional parameter. Default is main.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Done. Added scripts-branch input to action.yml (defaults to main), updated run.sh to use the environment variable, and documented it in README.md. This allows callers to specify which branch of tomgrv/scripts to fetch setup.sh from, useful for testing with development branches during integration.

Tests still pass (6/6 ✓). New commit pushed: c7e62d8


Generated by Claude Code

claude and others added 2 commits September 3, 2026 10:19
…input parameter

Add optional scripts-branch input to setup-scripts composite action, defaulting
to 'main'. This allows callers to specify which branch of tomgrv/scripts to
fetch setup.sh from, enabling easier testing with development branches during
integration testing.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
git-release-beta/git-release-prod (from tomgrv/scripts) call gv/bump-tag/
bump-changelog/bump-version directly to compute the release version and
bump the changelog/tag. Neither tomgrv/scripts nor release-promote's
existing setup-scripts step installs this toolchain (a docker-wrapped
GitVersion CLI plus those bin scripts, from the gitversion devcontainer
feature) -- the old per-repo release-main.yml got it for free as a side
effect of installing the whole gitutils feature, and that installation
step was dropped when release-promote replaced it. Every live release
would fail at the first `gv -showvariable` call without this.

release-promote now composes setup-gitversion alongside setup-scripts and
config-bot, mirroring the same composition pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dau8uW2rrctkyAxngdR1ix
tomgrv added a commit to tomgrv/scripts that referenced this pull request Sep 3, 2026
…iner-features (#3)

## Summary

Part of a coordinated consistency pass across
`tomgrv/devcontainer-features`, `tomgrv/actions`, `tomgrv/scripts`, and
`perspikapps/vps`. Companion PRs: tomgrv/actions#77,
tomgrv/devcontainer-features#143, perspikapps/vps#10.

- Migrates the ~32 `git-*` scripts formerly under
`devcontainer-features/src/gitutils/bin/` into individual workspace
packages here (`/{run.sh,package.json,README.md,test.bats}`), mirroring
the exact migration `common-utils` already went through. One source of
truth for the script implementations now, fetched via `zz_use` instead
of duplicated per consumer.
- Switches this repo's own `release-main.yml` to call `tomgrv/actions`'
new `release-promote` composite action as a step, dogfooding the same
release path every consumer repo now uses.
- Adds a "Git utilities" table to the README documenting the new
packages.
- **Adds a `.gitversion` config file at repo root.**
`git-release-beta`/`git-release-prod` call `gv -showvariable
MajorMinorPatch`, which hardcodes `-config ".gitversion"` — this repo
never had that file (no devcontainer here to deploy it via the
`gitversion` feature's configure step). Content matches
`tomgrv/actions/.gitversion`, the same bump-message rules used across
the rest of the family.

## Cross-repo dependency

`tomgrv/actions`' `release-promote` action (this PR's release-main.yml
step depends on it, including its new `setup-gitversion` sub-step — see
tomgrv/actions#77) and `devcontainer-features`' `gitutils` feature (now
depends on the packages added here) are being updated in sibling PRs on
the same branch name. Until those merge and tag, the workflow points at
`tomgrv/actions@claude/multi-repo-consistency-release-7se0jy` — marked
with a `TODO` to pin to a released tag afterward. This repo's own
`release-main.yml` has a chicken-and-egg problem releasing itself (it
needs its own `git-release-beta`/`git-release-prod` before any tag of
this repo exists) — the operational plan for the actual release run pins
`scripts-ref` to the merge commit SHA for that one bootstrap run, then
updates it to the real tag afterward.

## Test plan

- [x] `npx bats --recursive .` — all 68 tests pass (32 new + 36
existing)
- [x] Each new script's `run.sh` verified `sh -n` clean via its
`test.bats`
- [x] `npx prettier --check` clean on all new/changed files
- [ ] Bump the workflow's `uses:` ref to a released tag once this merges
and tags

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01Dau8uW2rrctkyAxngdR1ix

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: bot <bot@example.com>
bot and others added 8 commits September 3, 2026 15:49
config-bot/setup-scripts/setup-gitversion were pinned to @main, which
404s for any sibling action that hasn't reached main yet -- exactly the
case for setup-scripts and setup-gitversion right now, both new in this
same PR. Verified live: tomgrv/scripts' release-main dry-run failed with
"Can't find 'action.yml' ... for action 'tomgrv/actions/setup-scripts@main'".

Relative `./` references resolve against the same ref release-promote
itself was invoked at (branch or tag), the same mechanism this repo's own
release-main.yml already relies on for `uses: ./release-promote`. This
also means release-promote never needs updating when a sibling action's
ref changes -- it always tracks whichever ref/tag called it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dau8uW2rrctkyAxngdR1ix
…ses:

The previous ./config-bot/./setup-scripts/./setup-gitversion fix was
itself wrong: a relative `uses:` path in a composite action resolves
against the *calling* repository's checkout (GITHUB_WORKSPACE), not this
action's own. Verified live: tomgrv/scripts' release-main dry-run failed
with "Can't find 'action.yml' ... under
'/home/runner/work/scripts/scripts/config-bot' -- Did you forget to run
actions/checkout".

Neither a fixed `@main` (404s until a sibling reaches main -- true right
now for setup-scripts/setup-gitversion, and for anything a future first
release introduces) nor `./<name>` (wrong repo for any external caller)
can express "whichever ref release-promote itself was invoked at". Since
`uses:` doesn't support expressions, route around it: invoke each
sibling's run.sh directly via `${{ github.action_path }}/../<name>/run.sh`
with the same env vars its own action.yml would set. action_path always
resolves inside this action's own checkout, and GitHub fetches the whole
repo (not just the one subpath) to run any of its actions, so the
siblings are right there alongside it at the same commit -- this works
for every caller/ref, including this repo's own first release.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dau8uW2rrctkyAxngdR1ix
Verified live: release-promote's direct-invocation step (sh -c
".../setup-gitversion/run.sh") failed with "Permission denied" / exit
126 -- the file was written without the executable bit every other
action's run.sh already carries.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dau8uW2rrctkyAxngdR1ix
Verified live: the feature-install call failed with "Unknown command:
gitversion" -- this repo's own CLAUDE.md documents the current CLI as
`npx tomgrv/devcontainer-features -- add <feature>`, not a bare feature
name (the old release-main.yml this pattern was copied from predates
that CLI's add subcommand).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dau8uW2rrctkyAxngdR1ix
…re installer

Verified live and locally: `devcontainer-features -- add gitversion`
deploys that feature's dev-environment stub files (.gitattributes,
package.json merges, VS Code tasks, skills) straight into the checked-out
repo -- unwanted noise for a CI release job -- and its bin/ scripts never
actually land on PATH outside a real devcontainer postCreate context
(confirmed: install ran, target dir had no bin/ subdir, "gv not on PATH
after install").

Rewritten to build the docker-gitversion wrapper and fetch gv/bump-tag/
bump-changelog/bump-version directly from the gitversion feature's source
via curl, mirroring install-tools.sh's own logic without going through
the feature installer at all. No repo pollution, and it actually ends up
on PATH.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dau8uW2rrctkyAxngdR1ix
Verified live: the first real release attempt for tomgrv/scripts failed
with "git: 'flow' is not a git command" -- git-release-beta/git-release-prod
call `git flow release start`/`git flow <flow> finish` directly, but
neither the git-flow binary nor its local git config
(gitflow.branch.*/gitflow.prefix.*, normally set once by a developer's own
devcontainer setup) exists on a bare CI checkout. The old workflow got
both for free as a side effect of installing the whole gitutils feature
(its own install-gitflow.sh/configure-gitflow.sh); nothing replaced that
when release-promote stopped installing gitutils wholesale.

Adds setup-gitflow, mirroring install-gitflow.sh's package-manager
install fallback chain and configure-gitflow.sh's `git flow init` call
(without their zz_log/zz_context dependencies, and without the "create
master/develop as an orphan branch" logic meant for a fresh repo scaffold
-- a CI checkout of an existing repo already has real ones).
release-promote composes it alongside setup-scripts/setup-gitversion.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dau8uW2rrctkyAxngdR1ix
Verified live: the install failed with "E: Unable to lock directory
/var/lib/apt/lists/" -- GitHub-hosted runners run as a non-root user with
passwordless sudo, but the install commands were being run directly,
matching neither this repo's own conventions nor install-gitflow.sh (the
script this was based on), which already handles the root-vs-sudo split
this dropped.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dau8uW2rrctkyAxngdR1ix
… tag

tomgrv/scripts just released v0.3.0 (which includes git-release-beta/
git-release-prod), so this repo's own release-main.yml no longer needs
to point scripts-ref at the working branch.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dau8uW2rrctkyAxngdR1ix
@tomgrv
tomgrv marked this pull request as ready for review September 3, 2026 19:43
@tomgrv
tomgrv merged commit 61dfb85 into develop Sep 3, 2026
6 checks passed
@tomgrv
tomgrv deleted the claude/multi-repo-consistency-release-7se0jy branch September 3, 2026 19:43
tomgrv added a commit to tomgrv/devcontainer-features that referenced this pull request Sep 3, 2026
…rv/scripts, remove pecl (#143)

## Summary

Part of a coordinated consistency pass across this repo,
`tomgrv/actions` (#77, merged, released as v2.22.0), `tomgrv/scripts`
(#3, merged, released as v0.3.0), and `perspikapps/vps` (#10).

- **gitutils migration**: `src/gitutils/bin/`'s ~32 `git-*` scripts now
live in `tomgrv/scripts` as individual packages (tomgrv/scripts#3) — one
source of truth, mirroring the exact move `common-utils` already made.
`install.sh` now bootstraps `zz_use` and pulls them in; `gitutils` keeps
only the config/alias wiring and git-flow install/configure lifecycle.
- **Shared release workflow**:
`src/gitutils/stubs/.github/workflows/release-main.yml` (the actual
template every consumer repo's copy is stub-deployed from) now checks
out the repo and calls `tomgrv/actions`' new `release-promote` composite
action (tomgrv/actions#77), pinned to `v2.22.0`, pulling
`git-release-beta`/`git-release-prod` from `tomgrv/scripts@v0.3.0`. This
repo's own root `.github/workflows/release-main.yml` is redeployed from
that same stub, since this repo dogfoods its own features.
- **Removes `pecl` entirely** (rather than bringing it to v8 parity) —
it was already flagged in `docs/reviews/2026-08-repo-review.md` §2.5 as
the one feature diverging from the standard `install-feature` pattern.
`CLAUDE.md`'s "minimal reference" pointer moves to `src/scripting/`. All
other references cleaned up; the historical audit doc's findings are
left as-is with a dated addendum note rather than rewritten.
- **Stale internal `dependsOn` pins**: 7 features' own `dependsOn`
blocks still pinned `common-utils:7`/`gitutils:7`/`gitversion:7` even
though those targets have been on major 8 for a while — bumped all to
`:8`.
- **Stale README self-install snippets**: every feature's own README
Quick Start snippet (plus the two in root `README.md`) still showed its
pre-v8 self-install version — bumped to `:8` (left `scripting:1` alone,
since that one's genuinely major v1).
- **VS Code tasks**: the stub's "🎈 Beta"/"🚀 Prod" local-CLI tasks are
dropped now that release is fully automated via GitHub's "Run workflow"
button.

## Fixed during review

- `validate-pr-title` failed on the original PR title (`feat(scope)!:
...`) — `devmoji`'s autocorrection inserts two emoji back-to-back for
that breaking-change position, and commitlint's parser chokes on the
result. Dropped the `!` from the title; the breaking-change signal is
still carried by the commit's `BREAKING CHANGE:` footer.
- `release-promote` was originally a `workflow_call` reusable workflow;
restructured into a composite action per explicit direction, so it
composes as a normal step instead of taking over the whole job. It went
through several more live-verified fixes upstream (sibling-action
resolution, a missing GitVersion toolchain, a missing git-flow install)
before `tomgrv/scripts`' and `tomgrv/actions`' own real releases
succeeded — see tomgrv/actions#77 for the full history.

## Cross-repo dependency

`install.sh`'s `zz_use` calls and the release-main stub's
`scripts-ref`/`uses:` refs are now pinned to the released tags
(`tomgrv/scripts@v0.3.0`, `tomgrv/actions/release-promote@v2.22.0`) —
both sibling PRs have merged and tagged.

## Test plan

- [x] `bats src/gateway/tests src/common-utils/tests` — all 24 pass
(unaffected by this change, run as a regression check)
- [x] Every touched `devcontainer-feature.json` validated as parseable
JSON
- [x] `install.sh` syntax-checked (`sh -n`)
- [x] Workflow YAML and `tasks.json` files parse cleanly
- [x] `npx prettier --check` clean on all changed files
- [x] `scripts-ref`/`uses:` pinned to released tags

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01Dau8uW2rrctkyAxngdR1ix

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: bot <bot@example.com>
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.

2 participants