diff --git a/internal/generate/drift_check.go b/internal/generate/drift_check.go index 9cfc037..4a3dd90 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 6eb4871..e643715 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 20c1555..9696a27 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 8175331..6cb8aa2 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 f3f68f4..12e7dac 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 3d26ce1..27e5311 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 775f4d7..6ed2127 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 b82da0c..d3c9688 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 0313ce3..577c046 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 62739f4..ea77978 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 0000000..9cbddf5 --- /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 daf4911..58d9837 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")