From 0618c4b1c99c0e0ede77c640cc6bad34e6915793 Mon Sep 17 00:00:00 2001 From: Joshua Temple Date: Tue, 21 Jul 2026 12:35:01 -0400 Subject: [PATCH] refactor(generate): route all setup-cli emissions through one emitter Collapse the 15 scattered sites that print the setup-cli composite-action step into a single writeSetupCLIStep helper. Each generator keeps its own getCLIRef/GetCLIVersion resolution and passes the resolved values plus the token, input ordering, and optional step-level if guard. Generated output is byte-identical. Signed-off-by: Joshua Temple --- internal/generate/drift_check.go | 10 +++--- internal/generate/external.go | 11 +++--- internal/generate/generator.go | 26 +++++++------- internal/generate/hotfix.go | 10 +++--- internal/generate/merge_queue.go | 10 +++--- internal/generate/pr_preview.go | 10 +++--- internal/generate/promote.go | 33 +++++++++-------- internal/generate/reconcile_companion.go | 22 ++++++------ internal/generate/release.go | 22 ++++++------ internal/generate/rollback.go | 11 +++--- internal/generate/setup_cli.go | 46 ++++++++++++++++++++++++ internal/generate/validate_check.go | 10 +++--- 12 files changed, 137 insertions(+), 84 deletions(-) create mode 100644 internal/generate/setup_cli.go diff --git a/internal/generate/drift_check.go b/internal/generate/drift_check.go index 9cfc0376..4a3dd904 100644 --- a/internal/generate/drift_check.go +++ b/internal/generate/drift_check.go @@ -137,13 +137,13 @@ func (g *DriftCheckGenerator) writeCheckJob(sb *strings.Builder) { writeActionStep(sb, g.config, " ", actionCheckout) sb.WriteString("\n") - sb.WriteString(" - name: Setup CLI\n") - fmt.Fprintf(sb, " uses: stablekernel/cascade/.github/actions/setup-cli@%s\n", g.getCLIRef()) - sb.WriteString(" with:\n") - fmt.Fprintf(sb, " version: %s\n", g.config.GetCLIVersion()) // github.token is the built-in Actions token, sufficient to authenticate // gh release download against the public stablekernel/cascade repository. - sb.WriteString(" token: ${{ github.token }}\n") + writeSetupCLIStep(sb, setupCLIStep{ + ref: g.getCLIRef(), + version: g.config.GetCLIVersion(), + token: "${{ github.token }}", + }) sb.WriteString("\n") // Run verify, capturing stdout/stderr and the exit code without failing the diff --git a/internal/generate/external.go b/internal/generate/external.go index 6eb48717..e643715b 100644 --- a/internal/generate/external.go +++ b/internal/generate/external.go @@ -230,11 +230,12 @@ func (g *ExternalUpdateGenerator) writeJob(sb *strings.Builder) { sb.WriteString("\n") // Setup CLI - sb.WriteString(" - name: Setup CLI\n") - fmt.Fprintf(sb, " uses: stablekernel/cascade/.github/actions/setup-cli@%s\n", g.getCLIRef()) - sb.WriteString(" with:\n") - fmt.Fprintf(sb, " token: %s\n", g.getReleaseTokenRef()) - fmt.Fprintf(sb, " version: %s\n", g.config.GetCLIVersion()) + writeSetupCLIStep(sb, setupCLIStep{ + ref: g.getCLIRef(), + version: g.config.GetCLIVersion(), + token: g.getReleaseTokenRef(), + tokenBeforeVersion: true, + }) sb.WriteString("\n") // Configure git identity so the verb's commit/push of the manifest state diff --git a/internal/generate/generator.go b/internal/generate/generator.go index 20c15558..9696a274 100644 --- a/internal/generate/generator.go +++ b/internal/generate/generator.go @@ -1069,11 +1069,12 @@ func (g *Generator) writeSetupJob(sb *strings.Builder) { sb.WriteString(" fetch-depth: 0\n") // Setup CLI - sb.WriteString(" - name: Setup CLI\n") - fmt.Fprintf(sb, " uses: stablekernel/cascade/.github/actions/setup-cli@%s\n", g.getCLIRef()) - sb.WriteString(" with:\n") - fmt.Fprintf(sb, " token: %s\n", g.getReleaseTokenRef()) - fmt.Fprintf(sb, " version: %s\n", g.config.GetCLIVersion()) + writeSetupCLIStep(sb, setupCLIStep{ + ref: g.getCLIRef(), + version: g.config.GetCLIVersion(), + token: g.getReleaseTokenRef(), + tokenBeforeVersion: true, + }) // Single CLI call does all setup work sb.WriteString(" - name: Run Setup\n") @@ -2300,14 +2301,13 @@ func (g *Generator) writeFinalizeCLIBootstrap(sb *strings.Builder) { // writeFinalizePinnedCLI emits the pinned Setup CLI step. When cond is non-empty // it is emitted as the step's if: guard. func (g *Generator) writeFinalizePinnedCLI(sb *strings.Builder, cond string) { - sb.WriteString(" - name: Setup CLI\n") - if cond != "" { - fmt.Fprintf(sb, " if: %s\n", cond) - } - fmt.Fprintf(sb, " uses: stablekernel/cascade/.github/actions/setup-cli@%s\n", g.getCLIRef()) - sb.WriteString(" with:\n") - fmt.Fprintf(sb, " token: %s\n", g.getReleaseTokenRef()) - fmt.Fprintf(sb, " version: %s\n", g.config.GetCLIVersion()) + writeSetupCLIStep(sb, setupCLIStep{ + ref: g.getCLIRef(), + version: g.config.GetCLIVersion(), + token: g.getReleaseTokenRef(), + tokenBeforeVersion: true, + ifExpr: cond, + }) } // writeChangelogStep emits the built-in changelog generation as a step inside diff --git a/internal/generate/hotfix.go b/internal/generate/hotfix.go index 81753318..6cb8aa24 100644 --- a/internal/generate/hotfix.go +++ b/internal/generate/hotfix.go @@ -917,13 +917,13 @@ func (g *HotfixGenerator) writeFinalizeJob(sb *strings.Builder) { // writeSetupCLI emits the setup-cli step, mirroring the merge-queue generator. func (g *HotfixGenerator) writeSetupCLI(sb *strings.Builder) { - sb.WriteString(" - name: Setup CLI\n") - fmt.Fprintf(sb, " uses: stablekernel/cascade/.github/actions/setup-cli@%s\n", g.getCLIRef()) - sb.WriteString(" with:\n") - fmt.Fprintf(sb, " version: %s\n", g.config.GetCLIVersion()) // github.token is the built-in Actions token, sufficient to authenticate // gh release download against the public stablekernel/cascade repository. - sb.WriteString(" token: ${{ github.token }}\n") + writeSetupCLIStep(sb, setupCLIStep{ + ref: g.getCLIRef(), + version: g.config.GetCLIVersion(), + token: "${{ github.token }}", + }) } // writeFetchEnvBranches emits a step that fetches the env/* branches and tags so diff --git a/internal/generate/merge_queue.go b/internal/generate/merge_queue.go index f3f68f4d..12e7dace 100644 --- a/internal/generate/merge_queue.go +++ b/internal/generate/merge_queue.go @@ -102,13 +102,13 @@ func (g *MergeQueueGenerator) writeJob(sb *strings.Builder) { sb.WriteString(" with:\n") sb.WriteString(" fetch-depth: 0\n") - sb.WriteString(" - name: Setup CLI\n") - fmt.Fprintf(sb, " uses: stablekernel/cascade/.github/actions/setup-cli@%s\n", g.getCLIRef()) - sb.WriteString(" with:\n") - fmt.Fprintf(sb, " version: %s\n", g.config.GetCLIVersion()) // github.token is the built-in Actions token, sufficient to authenticate // gh release download against the public stablekernel/cascade repository. - sb.WriteString(" token: ${{ github.token }}\n") + writeSetupCLIStep(sb, setupCLIStep{ + ref: g.getCLIRef(), + version: g.config.GetCLIVersion(), + token: "${{ github.token }}", + }) // Validity gate: lint --json reports validity in its JSON output, so gate // on the parsed result. diff --git a/internal/generate/pr_preview.go b/internal/generate/pr_preview.go index 3d26ce15..27e5311f 100644 --- a/internal/generate/pr_preview.go +++ b/internal/generate/pr_preview.go @@ -114,14 +114,14 @@ func (g *PRPreviewGenerator) writeJob(sb *strings.Builder) { sb.WriteString("\n") // Setup CLI. - sb.WriteString(" - name: Setup CLI\n") - fmt.Fprintf(sb, " uses: stablekernel/cascade/.github/actions/setup-cli@%s\n", g.getCLIRef()) - sb.WriteString(" with:\n") - fmt.Fprintf(sb, " version: %s\n", g.config.GetCLIVersion()) // github.token is the built-in Actions token. It is sufficient to // authenticate gh release download against the public stablekernel/cascade // repository and requires no adopter configuration. - sb.WriteString(" token: ${{ github.token }}\n") + writeSetupCLIStep(sb, setupCLIStep{ + ref: g.getCLIRef(), + version: g.config.GetCLIVersion(), + token: "${{ github.token }}", + }) sb.WriteString("\n") g.writeValidateStep(sb) diff --git a/internal/generate/promote.go b/internal/generate/promote.go index 775f4d76..6ed21275 100644 --- a/internal/generate/promote.go +++ b/internal/generate/promote.go @@ -665,11 +665,12 @@ func (g *PromoteGenerator) writePreflightJob(sb *strings.Builder) { sb.WriteString(" fetch-depth: 0\n") // Setup CLI - sb.WriteString(" - name: Setup CLI\n") - fmt.Fprintf(sb, " uses: stablekernel/cascade/.github/actions/setup-cli@%s\n", g.getCLIRef()) - sb.WriteString(" with:\n") - fmt.Fprintf(sb, " token: %s\n", g.getReleaseTokenRef()) - fmt.Fprintf(sb, " version: %s\n", g.config.GetCLIVersion()) + writeSetupCLIStep(sb, setupCLIStep{ + ref: g.getCLIRef(), + version: g.config.GetCLIVersion(), + token: g.getReleaseTokenRef(), + tokenBeforeVersion: true, + }) // Single CLI call does everything sb.WriteString(" - name: Run Preflight\n") @@ -717,11 +718,12 @@ func (g *PromoteGenerator) writePromoteJob(sb *strings.Builder) { sb.WriteString(" steps:\n") writeMintSteps(sb, g.config, " ", seamRelease) writeActionStep(sb, g.config, " ", actionCheckout) - sb.WriteString(" - name: Setup CLI\n") - fmt.Fprintf(sb, " uses: stablekernel/cascade/.github/actions/setup-cli@%s\n", g.getCLIRef()) - sb.WriteString(" with:\n") - fmt.Fprintf(sb, " token: %s\n", g.getReleaseTokenRef()) - fmt.Fprintf(sb, " version: %s\n", g.config.GetCLIVersion()) + writeSetupCLIStep(sb, setupCLIStep{ + ref: g.getCLIRef(), + version: g.config.GetCLIVersion(), + token: g.getReleaseTokenRef(), + tokenBeforeVersion: true, + }) sb.WriteString(" - name: Validate Promotion\n") // The mode input is untrusted workflow_dispatch data. GitHub expands ${{ ... }} // into the run: script before the shell runs it, so a mode value carrying shell @@ -1109,11 +1111,12 @@ func (g *PromoteGenerator) writeFinalizeJob(sb *strings.Builder) { writeActionStep(sb, g.config, " ", actionCheckout) sb.WriteString(" with:\n") sb.WriteString(" fetch-depth: 0\n") - sb.WriteString(" - name: Setup CLI\n") - fmt.Fprintf(sb, " uses: stablekernel/cascade/.github/actions/setup-cli@%s\n", g.getCLIRef()) - sb.WriteString(" with:\n") - fmt.Fprintf(sb, " token: %s\n", g.getReleaseTokenRef()) - fmt.Fprintf(sb, " version: %s\n", g.config.GetCLIVersion()) + writeSetupCLIStep(sb, setupCLIStep{ + ref: g.getCLIRef(), + version: g.config.GetCLIVersion(), + token: g.getReleaseTokenRef(), + tokenBeforeVersion: true, + }) // Generate changelog sb.WriteString(" - name: Generate Changelog\n") diff --git a/internal/generate/reconcile_companion.go b/internal/generate/reconcile_companion.go index b82da0cd..d3c96881 100644 --- a/internal/generate/reconcile_companion.go +++ b/internal/generate/reconcile_companion.go @@ -144,13 +144,13 @@ func (g *ReconcileGenerator) writeCheckJob(sb *strings.Builder) { sb.WriteString(" fetch-depth: 0\n") sb.WriteString("\n") - sb.WriteString(" - name: Setup CLI\n") - fmt.Fprintf(sb, " uses: stablekernel/cascade/.github/actions/setup-cli@%s\n", g.getCLIRef()) - sb.WriteString(" with:\n") - fmt.Fprintf(sb, " version: %s\n", g.config.GetCLIVersion()) // github.token is the built-in Actions token, sufficient to authenticate // gh release download against the public stablekernel/cascade repository. - sb.WriteString(" token: ${{ github.token }}\n") + writeSetupCLIStep(sb, setupCLIStep{ + ref: g.getCLIRef(), + version: g.config.GetCLIVersion(), + token: "${{ github.token }}", + }) sb.WriteString("\n") g.writeChangedFilesStep(sb) @@ -412,12 +412,12 @@ func (g *ReconcileGenerator) writeCompanionCheckoutStep(sb *strings.Builder) { // writeCompanionSetupCLIStep obtains a PINNED release binary, never a `go // run` off the repository's own (possibly malicious) source tree. func (g *ReconcileGenerator) writeCompanionSetupCLIStep(sb *strings.Builder) { - sb.WriteString(" - name: Setup CLI\n") - sb.WriteString(" if: steps.resolve.outputs.relevant == 'true'\n") - fmt.Fprintf(sb, " uses: stablekernel/cascade/.github/actions/setup-cli@%s\n", g.getCLIRef()) - sb.WriteString(" with:\n") - fmt.Fprintf(sb, " version: %s\n", g.config.GetCLIVersion()) - sb.WriteString(" token: ${{ github.token }}\n") + writeSetupCLIStep(sb, setupCLIStep{ + ref: g.getCLIRef(), + version: g.config.GetCLIVersion(), + token: "${{ github.token }}", + ifExpr: "steps.resolve.outputs.relevant == 'true'", + }) sb.WriteString("\n") } diff --git a/internal/generate/release.go b/internal/generate/release.go index 0313ce39..577c046b 100644 --- a/internal/generate/release.go +++ b/internal/generate/release.go @@ -231,11 +231,12 @@ func (g *ReleaseGenerator) writePreflightJob(sb *strings.Builder) { sb.WriteString(" echo \"::notice::Semver tag: $SEMVER_TAG (from $SOURCE_VERSION)\"\n") // Check for breaking changes - sb.WriteString(" - name: Setup CLI\n") - fmt.Fprintf(sb, " uses: stablekernel/cascade/.github/actions/setup-cli@%s\n", g.getCLIRef()) - sb.WriteString(" with:\n") - fmt.Fprintf(sb, " token: %s\n", g.getReleaseTokenRef()) - fmt.Fprintf(sb, " version: %s\n", g.config.GetCLIVersion()) + writeSetupCLIStep(sb, setupCLIStep{ + ref: g.getCLIRef(), + version: g.config.GetCLIVersion(), + token: g.getReleaseTokenRef(), + tokenBeforeVersion: true, + }) sb.WriteString(" - name: Check Breaking Changes\n") sb.WriteString(" id: check\n") sb.WriteString(" env:\n") @@ -314,11 +315,12 @@ func (g *ReleaseGenerator) writeReleaseJob(sb *strings.Builder) { sb.WriteString(" fetch-depth: 0\n") // Setup CLI - sb.WriteString(" - name: Setup CLI\n") - fmt.Fprintf(sb, " uses: stablekernel/cascade/.github/actions/setup-cli@%s\n", g.getCLIRef()) - sb.WriteString(" with:\n") - fmt.Fprintf(sb, " token: %s\n", g.getReleaseTokenRef()) - fmt.Fprintf(sb, " version: %s\n", g.config.GetCLIVersion()) + writeSetupCLIStep(sb, setupCLIStep{ + ref: g.getCLIRef(), + version: g.config.GetCLIVersion(), + token: g.getReleaseTokenRef(), + tokenBeforeVersion: true, + }) // Generate changelog sb.WriteString(" - name: Generate Changelog\n") diff --git a/internal/generate/rollback.go b/internal/generate/rollback.go index 62739f4c..ea77978c 100644 --- a/internal/generate/rollback.go +++ b/internal/generate/rollback.go @@ -305,11 +305,12 @@ func (g *RollbackGenerator) writeSetupCLI(sb *strings.Builder) { writeActionStep(sb, g.config, " ", actionCheckout) sb.WriteString(" with:\n") sb.WriteString(" fetch-depth: 0\n") - sb.WriteString(" - name: Setup CLI\n") - fmt.Fprintf(sb, " uses: stablekernel/cascade/.github/actions/setup-cli@%s\n", g.getCLIRef()) - sb.WriteString(" with:\n") - fmt.Fprintf(sb, " token: %s\n", g.getReleaseTokenRef()) - fmt.Fprintf(sb, " version: %s\n", g.config.GetCLIVersion()) + writeSetupCLIStep(sb, setupCLIStep{ + ref: g.getCLIRef(), + version: g.config.GetCLIVersion(), + token: g.getReleaseTokenRef(), + tokenBeforeVersion: true, + }) } // writePreflightJob emits the read-only target-resolution job. It exposes the diff --git a/internal/generate/setup_cli.go b/internal/generate/setup_cli.go new file mode 100644 index 00000000..9cbddf5a --- /dev/null +++ b/internal/generate/setup_cli.go @@ -0,0 +1,46 @@ +package generate + +import ( + "fmt" + "strings" +) + +// setupCLIStep describes a single emission of the "Setup CLI" step that installs +// the cascade binary through the setup-cli composite action. Every generator +// routes its setup-cli emission through writeSetupCLIStep so the action +// reference and its inputs have exactly one source. +type setupCLIStep struct { + // ref is the git ref spliced after "setup-cli@". Callers pass the result of + // their own getCLIRef helper so the ref-resolution logic stays unchanged. + ref string + // version is the value of the action's "version" input, typically the result + // of TrunkConfig.GetCLIVersion. + version string + // token is the value of the action's "token" input. + token string + // tokenBeforeVersion emits the token input above the version input, matching + // the release-path ordering. When false, version is emitted first. + tokenBeforeVersion bool + // ifExpr, when non-empty, adds a step-level "if:" guard above the uses line. + ifExpr string +} + +// writeSetupCLIStep emits the "Setup CLI" step that installs the cascade binary +// via the setup-cli composite action. It is the single canonical emitter for the +// action reference (`uses: ...setup-cli@`) and the `version` input, so any +// future change to how the CLI is installed has one place to branch. +func writeSetupCLIStep(sb *strings.Builder, step setupCLIStep) { + sb.WriteString(" - name: Setup CLI\n") + if step.ifExpr != "" { + fmt.Fprintf(sb, " if: %s\n", step.ifExpr) + } + fmt.Fprintf(sb, " uses: stablekernel/cascade/.github/actions/setup-cli@%s\n", step.ref) + sb.WriteString(" with:\n") + if step.tokenBeforeVersion { + fmt.Fprintf(sb, " token: %s\n", step.token) + fmt.Fprintf(sb, " version: %s\n", step.version) + return + } + fmt.Fprintf(sb, " version: %s\n", step.version) + fmt.Fprintf(sb, " token: %s\n", step.token) +} diff --git a/internal/generate/validate_check.go b/internal/generate/validate_check.go index daf49111..58d9837d 100644 --- a/internal/generate/validate_check.go +++ b/internal/generate/validate_check.go @@ -95,13 +95,13 @@ func (g *ValidateCheckGenerator) writeJob(sb *strings.Builder) { sb.WriteString(" steps:\n") writeActionStep(sb, g.config, " ", actionCheckout) - sb.WriteString(" - name: Setup CLI\n") - fmt.Fprintf(sb, " uses: stablekernel/cascade/.github/actions/setup-cli@%s\n", g.getCLIRef()) - sb.WriteString(" with:\n") - fmt.Fprintf(sb, " version: %s\n", g.config.GetCLIVersion()) // github.token is the built-in Actions token, sufficient to authenticate // gh release download against the public stablekernel/cascade repository. - sb.WriteString(" token: ${{ github.token }}\n") + writeSetupCLIStep(sb, setupCLIStep{ + ref: g.getCLIRef(), + version: g.config.GetCLIVersion(), + token: "${{ github.token }}", + }) sb.WriteString(" - name: Validate Manifest\n") sb.WriteString(" run: |\n")