diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 10d44fb..438adb6 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -55,6 +55,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + # Release tags must be visible so the DefaultCLIVersion release-lag + # guard runs instead of skipping on a tag-less shallow checkout. + fetch-tags: true - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 with: diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml index 6e3a668..2813d6a 100644 --- a/.github/workflows/validate.yaml +++ b/.github/workflows/validate.yaml @@ -40,6 +40,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + # Release tags must be visible so the DefaultCLIVersion release-lag + # guard runs instead of skipping on a tag-less shallow checkout. + fetch-tags: true - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 4797e79..101cca9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,17 @@ A `Migration` section is added to any release that bumps `schema_version`. ## [Unreleased] +### Fixed + +- **config:** Validate `run_policy`, `on_failure`, and `retries` on the + `validate` block the same way as builds and deploys: `run_policy` and + `on_failure` must be one of their documented values and `retries` must stay + between 0 and 3, instead of out-of-range settings being silently accepted +- **config:** Bump the default `cli_version` pin for generated workflows to + v0.16.1, and guard the default against lagging the latest stable release +- **hotfix:** Converge the missing tag and release when the hotfix finalize + job reruns after a partial failure, instead of skipping release creation + ## [0.16.1] - 2026-07-15 ### Security diff --git a/README.md b/README.md index 31c41ca..51c7238 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ ci: config: schema_version: 1 trunk_branch: main - cli_version: v0.16.0 + cli_version: v0.16.1 environments: [dev, staging, prod] builds: - name: app @@ -105,7 +105,7 @@ ci: config: schema_version: 1 trunk_branch: main - cli_version: v0.16.0 + cli_version: v0.16.1 environments: [dev, staging, prod] builds: - name: api diff --git a/docs/src/content/docs/guides/adopt.md b/docs/src/content/docs/guides/adopt.md index 920aa3a..8113cf1 100644 --- a/docs/src/content/docs/guides/adopt.md +++ b/docs/src/content/docs/guides/adopt.md @@ -37,7 +37,7 @@ ci: config: schema_version: 1 trunk_branch: main - cli_version: v0.16.0 + cli_version: v0.16.1 environments: [dev, staging, prod] diff --git a/docs/src/content/docs/guides/components.md b/docs/src/content/docs/guides/components.md index 474bf71..2c8c2c1 100644 --- a/docs/src/content/docs/guides/components.md +++ b/docs/src/content/docs/guides/components.md @@ -59,7 +59,7 @@ ci: config: schema_version: 1 trunk_branch: main - cli_version: v0.16.0 + cli_version: v0.16.1 environments: [dev, staging, prod] components: diff --git a/docs/src/content/docs/reference/cli.md b/docs/src/content/docs/reference/cli.md index e0f5229..3efeb81 100644 --- a/docs/src/content/docs/reference/cli.md +++ b/docs/src/content/docs/reference/cli.md @@ -60,7 +60,7 @@ cascade version Output: ```text -cascade v0.16.0 +cascade v0.16.1 commit: abc123d built: 2026-01-15T10:30:00Z ``` diff --git a/docs/src/content/docs/reference/manifest.md b/docs/src/content/docs/reference/manifest.md index bcaecf0..4e6029e 100644 --- a/docs/src/content/docs/reference/manifest.md +++ b/docs/src/content/docs/reference/manifest.md @@ -99,7 +99,7 @@ ci: - name: prod gha_environment: production environment_url: "https://app.example.com" - cli_version: v0.16.0 + cli_version: v0.16.1 ``` Each entry is one of two forms: @@ -195,7 +195,7 @@ These fields pin the cascade CLI and third-party actions the generated workflows |-------|----------| | `latest` | Most recent stable release (default). | | `beta` | Newest prerelease build. | -| `vX.Y.Z` | A specific version (for example `v0.16.0`). Pin for reproducibility. | +| `vX.Y.Z` | A specific version (for example `v0.16.1`). Pin for reproducibility. | ### cli_version_sha diff --git a/docs/src/content/docs/start/getting-started.md b/docs/src/content/docs/start/getting-started.md index cc9ca37..e363200 100644 --- a/docs/src/content/docs/start/getting-started.md +++ b/docs/src/content/docs/start/getting-started.md @@ -22,7 +22,7 @@ go install github.com/stablekernel/cascade/cmd/cascade@latest for the newest tag): ```bash -go install github.com/stablekernel/cascade/cmd/cascade@v0.16.0 +go install github.com/stablekernel/cascade/cmd/cascade@v0.16.1 ``` Verify the install: @@ -34,7 +34,7 @@ cascade version You rarely need to invoke Cascade manually in CI: generated workflows install it for you via the `setup-cli` composite action, pinned to whatever `cli_version` you set in your manifest. If you need to call it directly in a workflow of your own: ```yaml -- uses: stablekernel/cascade/.github/actions/setup-cli@v0.16.0 +- uses: stablekernel/cascade/.github/actions/setup-cli@v0.16.1 with: token: ${{ secrets.GITHUB_TOKEN }} ``` @@ -70,7 +70,7 @@ ci: schema_version: 1 trunk_branch: main environments: [dev, test, prod] - cli_version: v0.16.0 + cli_version: v0.16.1 validate: workflow: .github/workflows/validate.yaml @@ -113,7 +113,7 @@ ci: config: schema_version: 1 trunk_branch: main - cli_version: v0.16.0 + cli_version: v0.16.1 builds: - name: cli workflow: .github/workflows/build-cli.yaml diff --git a/internal/config/default_cli_version_test.go b/internal/config/default_cli_version_test.go index 18ae2ab..7e51b7f 100644 --- a/internal/config/default_cli_version_test.go +++ b/internal/config/default_cli_version_test.go @@ -30,9 +30,9 @@ func TestDefaultCLIVersion_IsStableReleaseTag(t *testing.T) { // // The expectation is derived from the repository's own stable tags, so there // is no network dependency: each release must bump DefaultCLIVersion in the -// same body of work that cuts the tag (see CONTRIBUTING.md). Shallow CI -// checkouts without tags skip; any full clone (every local inner loop and -// fetch-depth: 0 workflow) enforces the match. +// same body of work that cuts the tag (see CONTRIBUTING.md). Checkouts +// without tags skip; local full clones and the CI test jobs (whose checkout +// steps set fetch-tags: true) enforce the match. func TestDefaultCLIVersion_MatchesLatestReleaseTag(t *testing.T) { out, err := exec.Command("git", "tag", "--list", "v*").Output() if err != nil { diff --git a/internal/config/docs_version_sync_test.go b/internal/config/docs_version_sync_test.go new file mode 100644 index 0000000..d735173 --- /dev/null +++ b/internal/config/docs_version_sync_test.go @@ -0,0 +1,79 @@ +package config + +import ( + "io/fs" + "os" + "path/filepath" + "regexp" + "strings" + "testing" +) + +// docsVersionPinPattern matches the concrete version pins that docs and the +// README present as current: `cli_version:` manifest examples, `go install +// .../cmd/cascade@vX.Y.Z`, tag-pinned `setup-cli@vX.Y.Z` uses lines, and the +// sample `cascade vX.Y.Z` version output. Deliberately outside the guard: +// sha-pinned `setup-cli@ # vX.Y.Z` examples (the sha/tag pairing is +// internally consistent whatever release it names) and generic prose such as +// "for example `vX.Y.Z`". +var docsVersionPinPattern = regexp.MustCompile( + `(?:cli_version:\s*|cascade/cmd/cascade@|setup-cli@|cascade )(v\d+\.\d+\.\d+)`) + +// TestDefaultCLIVersion_MatchesDocsExamples keeps the README and docs-site +// examples in lockstep with DefaultCLIVersion, so a release bump cannot leave +// the quickstart installing or pinning the previous CLI. The examples went +// stale on the v0.16.0 -> v0.16.1 bump; this turns that staleness into a +// failing gate instead of a per-release manual refresh. +func TestDefaultCLIVersion_MatchesDocsExamples(t *testing.T) { + wd, err := os.Getwd() + if err != nil { + t.Fatalf("getwd: %v", err) + } + root := filepath.Clean(filepath.Join(wd, "..", "..")) + + files := []string{filepath.Join(root, "README.md")} + docsRoot := filepath.Join(root, "docs", "src", "content", "docs") + err = filepath.WalkDir(docsRoot, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + if ext := filepath.Ext(path); ext == ".md" || ext == ".mdx" { + files = append(files, path) + } + return nil + }) + if err != nil { + t.Fatalf("walk docs content: %v", err) + } + + pins := 0 + for _, path := range files { + data, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read %s: %v", path, err) + } + rel, err := filepath.Rel(root, path) + if err != nil { + rel = path + } + for i, line := range strings.Split(string(data), "\n") { + for _, m := range docsVersionPinPattern.FindAllStringSubmatch(line, -1) { + pins++ + if m[1] != DefaultCLIVersion { + t.Errorf("%s:%d pins %q but DefaultCLIVersion is %q; refresh the example (line: %s)", + rel, i+1, m[1], DefaultCLIVersion, strings.TrimSpace(line)) + } + } + } + } + + // The guard only works while the docs actually contain pinned examples. + // If a docs restructure drops below this floor, re-point the pattern at + // the new example surface rather than deleting the check. + if pins < 5 { + t.Fatalf("found only %d version-pinned examples across README.md and docs/; expected at least 5, so the pattern no longer matches the docs surface", pins) + } +}