diff --git a/e2e/harness/runner.go b/e2e/harness/runner.go index 7a379b68..8b46455f 100644 --- a/e2e/harness/runner.go +++ b/e2e/harness/runner.go @@ -376,24 +376,43 @@ func (r *Runner) executePromote(ctx context.Context, promote *PromoteStep, confi // pair into the "-to-" form. Source defaults to the first // env (typically dev) since the workflow generator only emits dev-rooted // cascade options. - mode := promote.Mode - if mode == "cascade" { - source := "dev" - if len(config.Environments) > 0 { - source = config.Environments[0] + var inputs map[string]string + if len(config.Environments) == 1 { + // Single-environment repos generate a Release workflow (see + // command.go IsSingleEnvironment → NewReleaseGenerator) written to + // promote.yaml. That workflow's dispatch input is release_action + // (create-draft|prerelease|release), not the multi-env promote's + // mode. A "promote to release" step on a single-env repo means + // publish the final release, so dispatch release_action: release. + // Sending mode here was silently ignored, the workflow fell back to + // its create-draft default, and the run "succeeded" without ever + // publishing v0.1.0, wiping prod state, or cleaning up the RC tags. + inputs = map[string]string{ + "release_action": "release", + } + if promote.AllowBreaking { + inputs["allow_breaking_changes"] = "true" + } + } else { + mode := promote.Mode + if mode == "cascade" { + source := "dev" + if len(config.Environments) > 0 { + source = config.Environments[0] + } + mode = fmt.Sprintf("%s-to-%s", source, promote.Target) + } + inputs = map[string]string{ + "mode": mode, + } + if promote.AllowBreaking { + // Workflow input is named allow_breaking_changes (see internal/generate + // promote.go); the workflow forwards it to the CLI's --allow-breaking + // flag. The harness's previous "allow_breaking" key was silently + // ignored, leaving the breaking-change gate active even when the + // scenario asked for it to be bypassed. + inputs["allow_breaking_changes"] = "true" } - mode = fmt.Sprintf("%s-to-%s", source, promote.Target) - } - inputs := map[string]string{ - "mode": mode, - } - if promote.AllowBreaking { - // Workflow input is named allow_breaking_changes (see internal/generate - // promote.go); the workflow forwards it to the CLI's --allow-breaking - // flag. The harness's previous "allow_breaking" key was silently - // ignored, leaving the breaking-change gate active even when the - // scenario asked for it to be bypassed. - inputs["allow_breaking_changes"] = "true" } // Determine the branch ref @@ -521,6 +540,14 @@ func (r *Runner) syncStateFromGitea(ctx context.Context, config Config) error { SHA string `yaml:"sha"` } `yaml:"deploys"` } `yaml:"state"` + // LatestRelease is the single-environment Release workflow's published + // pointer (ci.latest_release). Single-env repos publish via the Release + // workflow's finalize step, which writes latest_release.{version,sha} + // rather than a state[release] env (see internal/generate/release.go). + LatestRelease struct { + SHA string `yaml:"sha"` + Version string `yaml:"version"` + } `yaml:"latest_release"` } if err := yaml.Unmarshal([]byte(manifestContent), &manifest); err != nil { return fmt.Errorf("failed to parse manifest: %w", err) @@ -550,6 +577,16 @@ func (r *Runner) syncStateFromGitea(ctx context.Context, config Config) error { } } + // Surface the single-env Release workflow's latest_release pointer under the + // synthetic "release" state key so scenarios can assert it the same way they + // assert any other environment's state. Multi-env repos leave latest_release + // empty (they wipe and repopulate state[release] directly), so this only + // records when the Release workflow has actually published. + if lr := ciData.LatestRelease; lr.Version != "" || lr.SHA != "" { + r.ctx.RecordState("release", lr.SHA, lr.Version) + r.t.Logf(" Synced state[release] (from latest_release) = %s @ %s", truncateSHA(lr.SHA), lr.Version) + } + return nil } diff --git a/e2e/harness/scenario.go b/e2e/harness/scenario.go index 0c4d15ec..f4d64543 100644 --- a/e2e/harness/scenario.go +++ b/e2e/harness/scenario.go @@ -33,6 +33,12 @@ type Config struct { Builds []BuildConfig `yaml:"builds"` Deploys []DeployConfig `yaml:"deploys"` Publish *PublishConfig `yaml:"publish,omitempty"` + // DispatchInputs carries operator-facing workflow_dispatch inputs through to + // the generated manifest untouched. A generic map (rather than a typed + // struct) is used so the harness stays decoupled from the generator's + // DispatchInput shape while preserving every key (type, options, default, + // description, required) across the marshal round-trip. + DispatchInputs map[string]map[string]any `yaml:"dispatch_inputs,omitempty"` } // PublishConfig defines a publish callback invoked after a release is published diff --git a/e2e/scenarios/09-inline-run-callback.yaml b/e2e/scenarios/09-inline-run-callback.yaml index 8d43f867..6672a121 100644 --- a/e2e/scenarios/09-inline-run-callback.yaml +++ b/e2e/scenarios/09-inline-run-callback.yaml @@ -36,7 +36,7 @@ steps: - path: ".github/workflows/orchestrate.yaml" contains: - "build-app:" - - "uses: build.yaml" + - "uses: ./build.yaml" - "build-smoke:" - "shell: bash" - 'echo "smoke check"' diff --git a/e2e/scenarios/09-single-env-repo.yaml b/e2e/scenarios/09-single-env-repo.yaml index 9f296c76..4e455f1c 100644 --- a/e2e/scenarios/09-single-env-repo.yaml +++ b/e2e/scenarios/09-single-env-repo.yaml @@ -96,11 +96,16 @@ steps: mode: default expect: state: + # Single-env repos publish via the Release workflow, which writes the + # ci.latest_release pointer (surfaced here as state[release]) rather than + # a state[release] env. It does not wipe the trunk-tracking env, so prod + # legitimately still holds the latest RC it was orchestrated to. release: sha: commit2 version: "v0.1.0" prod: - wiped: true + sha: commit2 + version: "v0.1.0-rc.1" releases: - tag: "v0.1.0" prerelease: false diff --git a/e2e/scenarios/10-inline-job-attributes.yaml b/e2e/scenarios/10-inline-job-attributes.yaml index e097ca07..0651c089 100644 --- a/e2e/scenarios/10-inline-job-attributes.yaml +++ b/e2e/scenarios/10-inline-job-attributes.yaml @@ -53,4 +53,4 @@ steps: - "group: sign-${{ github.ref }}" - "cancel-in-progress: true" - "build-app:" - - "uses: build.yaml" + - "uses: ./build.yaml"