Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,37 @@ A `Migration` section is added to any release that bumps `schema_version`.
per page, so the lookup scanned only the newest 30 of the repository's tags and
resolved an empty version once the repository grew past 30 tags.

- **config:** `trunk_branch` is now enforced as required, matching the manifest
schema, which has always listed it as required. Omitting it passed `lint` with
no diagnostics and then generated an orchestrate workflow whose push trigger
read `branches: []`, an allow-list matching no branch. The workflow was
accepted by GitHub and reported green while never firing on a trunk push, so a
pipeline could be adopted, committed, and appear healthy without ever running.
Manifests that omit it now fail `lint` and `generate-workflow` with a message
naming the field. No manifest that works today is affected: every manifest that
sets `trunk_branch` generates byte-identical output, and one that omits it
could not run at all. There is no default: inferring `main` for a repository
whose trunk is named otherwise would rebuild the same dead workflow silently.

- **config:** `publish.workflow` and `external[].deploys` are now enforced as
required, matching the schema. Both were accepted when absent and both then
vanished from generated output, so a manifest could declare a publish callback
that never published, or an external repository that coordinated nothing.

- **generate:** Generation of the orchestrate workflow now fails rather than
emitting an empty trigger filter. A filter that is present but empty matches
nothing and silently disables the trigger it guards, and GitHub accepts the
workflow and reports it green forever. The check covers every `branches`,
`paths`, and `tags` filter (and their `-ignore` forms) in the emitted
workflow, in both YAML list styles, so an emission site that forgets a length
guard fails the build instead of shipping a workflow that never runs.

- **docs:** Every manifest example across the documentation now sets
`trunk_branch`, and the reference table no longer lists it as both required and
defaulted. Copying a documented example produced a pipeline that never ran. The
documentation examples are now validated against the published schema by the
same test that has always guarded the README examples.

- **generate:** A manifest-level `concurrency.group` is now namespaced per
workflow instead of being emitted bare onto every cascade workflow. A GitHub
concurrency group is matched repository-wide rather than per workflow, and a
Expand Down
3 changes: 3 additions & 0 deletions docs/src/content/docs/guides/action-pins.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Generated workflows are build output, so their action pins are build output too.
# .github/manifest.yaml
ci:
config:
trunk_branch: main
pin_mode: sha
```

Expand All @@ -34,6 +35,7 @@ Under `sha`, the pin resolves to a 40-character commit SHA and the human-readabl
```yaml
ci:
config:
trunk_branch: main
pin_mode: sha
action_pins:
actions/checkout: "0123456789abcdef0123456789abcdef01234567"
Expand Down Expand Up @@ -63,6 +65,7 @@ Rather than run `reconcile` by hand, enable the reconcile companion so an extern
```yaml
ci:
config:
trunk_branch: main
reconcile:
enabled: true
source: dependabot
Expand Down
4 changes: 4 additions & 0 deletions docs/src/content/docs/guides/companions.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The generated workflows are build output. If someone hand-edits `.github/workflo
# .github/manifest.yaml
ci:
config:
trunk_branch: main
drift_check:
enabled: true
comment: true
Expand All @@ -27,6 +28,7 @@ The preview companion renders what the pipeline would do for a pull request with
```yaml
ci:
config:
trunk_branch: main
pr_preview:
enabled: true
comment: true
Expand All @@ -41,6 +43,7 @@ The validate check runs manifest validation as its own pull-request check, so a
```yaml
ci:
config:
trunk_branch: main
validate_check:
enabled: true
```
Expand All @@ -54,6 +57,7 @@ When the repository uses GitHub's merge queue, the merge-queue companion adds a
```yaml
ci:
config:
trunk_branch: main
merge_queue:
enabled: true
```
Expand Down
1 change: 1 addition & 0 deletions docs/src/content/docs/guides/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ the shared code it depends on:
```yaml
ci:
config:
trunk_branch: main
environments: [dev, prod]
shared_paths:
- libs/common/** # every component depends on this
Expand Down
3 changes: 3 additions & 0 deletions docs/src/content/docs/reference/callbacks.md
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,8 @@ Pass custom inputs via `inputs` and `env_inputs` in the manifest:
```yaml
ci:
config:
trunk_branch: main
environments: [prod]
builds:
- name: app
workflow: .github/workflows/build-app.yaml
Expand Down Expand Up @@ -441,6 +443,7 @@ Outputs from one callback are passed to dependents:
```yaml
ci:
config:
trunk_branch: main
builds:
- name: app
workflow: .github/workflows/build-app.yaml
Expand Down
1 change: 1 addition & 0 deletions docs/src/content/docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -939,6 +939,7 @@ block the output keeps the historical `rc.N` shape shown above. A manifest with
```yaml
ci:
config:
trunk_branch: main
tag_grammar:
prerelease_token: pre
prerelease_separator: ""
Expand Down
24 changes: 23 additions & 1 deletion docs/src/content/docs/reference/manifest.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ The two fields that define the pipeline shape.

| Field | Status | Type | Required | Default | Description |
|-------|--------|------|----------|---------|-------------|
| `trunk_branch` | emitted | string | Yes | `main` | The trunk branch releases flow from. |
| `trunk_branch` | emitted | string | Yes | - | The trunk branch the orchestrate workflow runs on. |
| `environments` | emitted | list of strings or objects | No | - | The promotion ladder. Each entry is a bare name or an object carrying that environment's name, optional role, and inline settings. Omit for a no-environment library or CLI project. |

```yaml
Expand Down Expand Up @@ -153,6 +153,7 @@ produces cascade's historical grammar exactly: `vX.Y.Z` releases, `-rc.N` pre-re
```yaml
ci:
config:
trunk_branch: main
tag_grammar:
prefix: v
prerelease_token: rc
Expand Down Expand Up @@ -242,6 +243,7 @@ The `sha` values come from a single committed pin table (`internal/generate/acti
```yaml
ci:
config:
trunk_branch: main
pin_mode: sha
action_pins:
actions/checkout: 0123456789abcdef0123456789abcdef01234567
Expand All @@ -265,6 +267,7 @@ Reference secrets by bare name; cascade wraps a bare name in a `${{ secrets.* }}
```yaml
ci:
config:
trunk_branch: main
release_token: RELEASE_PAT
state_token: STATE_PAT
```
Expand All @@ -278,6 +281,7 @@ A GitHub App avoids storing a long-lived PAT. Point `release_token_app` and `sta
```yaml
ci:
config:
trunk_branch: main
release_token_app:
app_id: CASCADE_APP_ID
private_key: CASCADE_APP_PRIVATE_KEY
Expand All @@ -295,6 +299,7 @@ Optional git identity and signing configuration for state commits.
```yaml
ci:
config:
trunk_branch: main
git:
mode: custom
user_name: deploy-bot
Expand Down Expand Up @@ -322,6 +327,7 @@ Optional pre-build validation callback.
```yaml
ci:
config:
trunk_branch: main
validate:
workflow: .github/workflows/validate.yaml
supports_dry_run: false
Expand Down Expand Up @@ -354,6 +360,8 @@ Builds produce artifacts (container images, binaries, and the like). `builds` is
```yaml
ci:
config:
trunk_branch: main
environments: [prod]
builds:
- name: app
workflow: .github/workflows/build-app.yaml
Expand Down Expand Up @@ -457,6 +465,7 @@ Deploys target environments. `deploys` is a list and shares most fields with `bu
```yaml
ci:
config:
trunk_branch: main
deploys:
- name: infra
workflow: .github/workflows/deploy-infra.yaml
Expand Down Expand Up @@ -533,6 +542,7 @@ The publish callback runs once per build when a release is published, at the poi
```yaml
ci:
config:
trunk_branch: main
publish:
workflow: .github/workflows/publish.yaml
```
Expand All @@ -554,6 +564,7 @@ ci:
```yaml
ci:
config:
trunk_branch: main
external:
- repo: org/cdk-infra
ref: main
Expand Down Expand Up @@ -585,6 +596,7 @@ For satellite repos that report deployments back to a primary.
```yaml
ci:
config:
trunk_branch: main
notify:
repo: org/my-backend
workflow: external-update.yaml
Expand All @@ -608,6 +620,7 @@ The primary validates the dispatched `deploy_name` and `environment` against its
```yaml
ci:
config:
trunk_branch: main
release_build:
disabled: false
workflow: .github/workflows/release-assets.yaml
Expand Down Expand Up @@ -655,6 +668,7 @@ leave the gate on.
```yaml
ci:
config:
trunk_branch: main
allow_breaking_changes: true
```

Expand All @@ -670,6 +684,7 @@ native GitHub Environment support and deployment URLs.
```yaml
ci:
config:
trunk_branch: main
environments:
- name: production
role: release
Expand Down Expand Up @@ -704,6 +719,7 @@ Top-level concurrency block emitted onto the orchestrate, promote, hotfix, rollb
```yaml
ci:
config:
trunk_branch: main
concurrency:
group: cascade-${{ github.ref }}
cancel_in_progress: false
Expand Down Expand Up @@ -771,6 +787,7 @@ change orchestrate's cancellation behavior.
```yaml
ci:
config:
trunk_branch: main
job_timeout_minutes: 30
```

Expand All @@ -785,6 +802,7 @@ Non-push trigger types wired onto the generated workflows.
```yaml
ci:
config:
trunk_branch: main
extra_triggers:
schedule:
- cron: "0 7 * * *"
Expand All @@ -808,6 +826,7 @@ Opts the rollback workflow into a `repository_dispatch` trigger, driving the rol
```yaml
ci:
config:
trunk_branch: main
rollback:
repository_dispatch:
types: [rollback-request]
Expand All @@ -828,6 +847,7 @@ Each of these emits an additional workflow only when its block is present. Omit
```yaml
ci:
config:
trunk_branch: main
pr_preview:
enabled: true
comment: true
Expand Down Expand Up @@ -899,6 +919,7 @@ components](/cascade/guides/components/) for the operator walkthrough.
```yaml
ci:
config:
trunk_branch: main
environments: [dev, staging, prod]
components:
api:
Expand Down Expand Up @@ -1005,6 +1026,7 @@ major of exactly the components that declare it, and leaves the rest untouched.
```yaml
ci:
config:
trunk_branch: main
environments: [dev, prod]
shared_paths:
- libs/common/** # every component depends on this
Expand Down
1 change: 1 addition & 0 deletions docs/src/content/docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ Third-party action pinning is shipped today, not a roadmap item. Two mechanisms
```yaml
ci:
config:
trunk_branch: main
pin_mode: sha
action_pins:
actions/checkout: a1b2c3d4e5f6...
Expand Down
1 change: 1 addition & 0 deletions internal/config/artifact_upload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ func TestArtifactUpload_ParsesOnBuildAndDeploy(t *testing.T) {
y := `ci:
config:
schema_version: 1
trunk_branch: main
environments: [dev]
builds:
- name: app
Expand Down
14 changes: 14 additions & 0 deletions internal/config/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,12 +410,26 @@ func Validate(cfg *TrunkConfig) []string {
}
}

// The publish callback is a reusable-workflow reference, and the promote
// generator emits the publish step only when the path is non-empty. An
// empty one is accepted by YAML and then silently drops the step, so a
// manifest would declare publish and never publish. Required, per schema.
if cfg.Publish != nil && cfg.Publish.Workflow == "" {
errors = append(errors, "publish.workflow is required when a publish block is present")
}

// Validate external repos (for primary repos)
externalDeployNames := make(map[string]bool)
for i, ext := range cfg.External {
if ext.Repo == "" {
errors = append(errors, fmt.Sprintf("external[%d].repo is required", i))
}
// An external entry exists to coordinate deploys in another repo. With
// none it emits no jobs and coordinates nothing, so the entry is inert
// rather than wrong, and nothing surfaces that. Required, per schema.
if len(ext.Deploys) == 0 {
errors = append(errors, fmt.Sprintf("external[%d].deploys is required and must not be empty", i))
}

for j, d := range ext.Deploys {
if d.Name == "" {
Expand Down
Loading