cpflow 5.0.0 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/update-changelog.md +88 -23
  3. data/.github/actions/cpflow-resolve-review-config/action.yml +137 -0
  4. data/.github/actions/cpflow-setup-environment/action.yml +118 -0
  5. data/.github/workflows/cpflow-cleanup-stale-review-apps.yml +26 -21
  6. data/.github/workflows/cpflow-delete-review-app.yml +21 -18
  7. data/.github/workflows/cpflow-deploy-review-app.yml +23 -19
  8. data/.github/workflows/cpflow-deploy-staging.yml +15 -11
  9. data/.github/workflows/cpflow-help-command.yml +0 -6
  10. data/.github/workflows/cpflow-promote-staging-to-production.yml +30 -5
  11. data/.github/workflows/cpflow-review-app-help.yml +1 -10
  12. data/CHANGELOG.md +23 -1
  13. data/Gemfile.lock +1 -1
  14. data/docs/ai-github-flow-prompt.md +1 -1
  15. data/docs/ci-automation.md +165 -29
  16. data/lib/command/ai_github_flow_prompt.rb +1 -1
  17. data/lib/cpflow/version.rb +1 -1
  18. data/lib/generator_templates/Dockerfile +1 -0
  19. data/lib/generator_templates/entrypoint.sh +42 -2
  20. data/lib/github_flow_templates/.github/cpflow-help.md +79 -83
  21. data/lib/github_flow_templates/.github/workflows/cpflow-cleanup-stale-review-apps.yml +4 -9
  22. data/lib/github_flow_templates/.github/workflows/cpflow-delete-review-app.yml +2 -9
  23. data/lib/github_flow_templates/.github/workflows/cpflow-deploy-review-app.yml +3 -9
  24. data/lib/github_flow_templates/.github/workflows/cpflow-deploy-staging.yml +3 -8
  25. data/lib/github_flow_templates/.github/workflows/cpflow-help-command.yml +0 -9
  26. data/lib/github_flow_templates/.github/workflows/cpflow-promote-staging-to-production.yml +10 -8
  27. data/lib/github_flow_templates/.github/workflows/cpflow-review-app-help.yml +4 -10
  28. data/lib/github_flow_templates/bin/pin-cpflow-github-ref +3 -1
  29. data/lib/github_flow_templates/bin/test-cpflow-github-flow +23 -8
  30. metadata +2 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1c17c05bff512134eb30334102939a75447792daaad19e4c77a04c5f9df60c32
4
- data.tar.gz: 382adbb86b322355500791f24aa2e2ce3937917d327fe64ffe7fd231d46ab612
3
+ metadata.gz: e1371f70bf636d92e0dacd78322ad6ef7d87e29fbec6c853e487a01205be1528
4
+ data.tar.gz: 9ad5116ccb84cc7e3757922c3c0580785caa1cfff96aed768c65d63c7ff01e93
5
5
  SHA512:
6
- metadata.gz: abd5e5788bfae7f2b42a85ec50abf557d2807d9f45544b78a95f50fc131d90d05918ca6d9e208b336c59ba628b0f5fe938b2d87aafc542e1549e173394ff96d3
7
- data.tar.gz: 0a3382ea577d7f563b7ae41d7977ae1a642c80cbf0487793a987faa1ed7634b40f1b38138a2202711f379f87ed6a8bafbd5a29755d8a11633f5ec811ab469d6e
6
+ metadata.gz: 6c96a1db55e94a5e22efa0640d51129566aa6afb16c8ea9887f5cf709a313ef3b1a690d5b64ba932b50cb1fb72efe85e7fa3371f8941b60242486d6fc1322566
7
+ data.tar.gz: 5a91cc36014d13c3b36ea3af7620758821d1fa0000bab12b1742d47fe0240015353c59b5dae6d981680b7746bcaa9154bd44f00a1fc240dce651cd57926a253b
@@ -166,13 +166,16 @@ This repo does **not** have an `update_changelog` rake task — the slash comman
166
166
  ```
167
167
 
168
168
  Note: this repo uses `...HEAD`, not `...main`. Match the existing convention.
169
- - Add a new compare link for the stamped version, comparing the previous tag to the new one:
169
+ - Add a new compare link for the stamped version:
170
170
 
171
171
  ```markdown
172
172
  [4.3.0]: https://github.com/shakacode/control-plane-flow/compare/v4.2.0...v4.3.0
173
173
  ```
174
174
 
175
- 3. **For `rc`/`beta` modes**: collapse prior prerelease sections of the same base version into a single section (see "For Prerelease Versions" below). Remove the orphaned compare links for the collapsed prerelease versions.
175
+ - **For stable releases**, skip prerelease tags compare from the previous **stable** tag. A stable release that coalesces prior RCs (e.g., `v5.0.0.rc.0`, `v5.0.0.rc.1`) still uses the last stable tag as the left side (e.g., `v4.2.0...v5.0.0`), not the latest RC tag.
176
+ - **For prereleases**, compare from the immediately previous tag (which may be a prior RC/beta or the last stable tag).
177
+
178
+ 3. **For `rc`/`beta` modes**: Insert the new RC/beta section above any prior prereleases — do NOT collapse them. Each RC/beta is a separately-tagged release that users install, and they need to see what changed between, say, `rc.0` and `rc.1`. See "For Prerelease Versions" below. Coalescing happens only at the stable release.
176
179
 
177
180
  The `rake release` task reads the first `## [VERSION]` header (skipping `Unreleased`) and uses it as the target version when newer than the current gem version. So once the changelog PR merges, `bundle exec rake release` (no args) will pick up the version automatically.
178
181
 
@@ -267,9 +270,11 @@ If the user passed `release`, `rc`, `beta`, or an explicit version string as an
267
270
  - Update `[Unreleased]` to compare from the new tag to `HEAD`
268
271
  - Add a new compare link for the stamped version
269
272
 
270
- 5. **For `rc`/`beta`**: collapse prior prerelease sections of the same base version (see below) and remove their orphaned compare links.
273
+ 5. **For `rc`/`beta`**: Do NOT collapse prior prerelease sections each RC/beta gets its own section so users can see what changed between prereleases. Just insert the new section above the prior ones and add a new compare link. See "For Prerelease Versions" below.
274
+
275
+ 6. **For stable `release` (or explicit stable version) when prior `rc`/`beta` sections exist for the same base version**: Do NOT just stamp `## [5.0.0]` above the prior RC sections. Instead, follow the "For Prerelease to Stable Version Release" process below — it replaces steps 3–4 here with the coalesce + curate flow (combine all RC sections into the new stable section, move any matching `[Unreleased]` entries in, drop prerelease-only noise, and use the previous **stable** tag in the compare link).
271
276
 
272
- 6. **Verify** the stamped header and diff links match the requested version. If anything looks off, fix it before continuing.
277
+ 7. **Verify** the stamped header and diff links match the requested version. If anything looks off, fix it before continuing.
273
278
 
274
279
  If no argument was passed, skip this step -- entries stay in `## [Unreleased]`.
275
280
 
@@ -310,32 +315,92 @@ When the user passes `rc` or `beta` as an argument:
310
315
 
311
316
  2. **Auto-compute the next prerelease version** using the process in "Auto-Computing the Next Version" above. Use RubyGems format (`5.0.0.rc.0`), not `5.0.0-rc.0`.
312
317
 
313
- 3. **Always collapse prior prereleases into the current prerelease** (this is the default behavior):
314
- - Combine all prior prerelease changelog entries into the new prerelease version section
315
- - Remove previous prerelease version sections (e.g., remove `## [5.0.0.rc.0]` when creating `## [5.0.0.rc.1]`)
316
- - When collapsing, **consolidate duplicate category headings** — if both the Unreleased section and a prior prerelease section have `### Fixed`, merge all entries under a single `### Fixed` heading
317
- - **Remove orphaned version diff links** at the bottom of the file for collapsed prerelease sections
318
- - Add any new user-visible changes from commits since the last prerelease
319
- - Update version diff links to point from the last stable version to the new prerelease
320
- - This keeps the changelog clean with a single prerelease section that accumulates all changes since the last stable release
318
+ 3. **Do NOT collapse prior prereleases.** Each RC/beta is a separately-tagged release that users install — they need to see what changed between, for example, `rc.0` and `rc.1` (especially when diagnosing a regression in a specific RC). Each successive `bundle exec rake release` reads only the top-most `## [VERSION]` section (the RC you just stamped — see the "Version Stamping" section above), so as long as each RC has its own section the corresponding GitHub release gets its own focused notes. Instead:
319
+
320
+ - Insert the new prerelease version section immediately after `## [Unreleased]`, **above** any prior prerelease sections (preserves newest-first ordering)
321
+ - Move any entries from `## [Unreleased]` that belong to this prerelease into the new section
322
+ - Leave prior prerelease sections (e.g., `## [5.0.0.rc.0]`) untouched — keep their entries and their compare links at the bottom of the file
323
+ - Add any new user-visible changes from commits since the last prerelease tag to the new section only
324
+ - Add a new compare link at the bottom comparing the previous prerelease tag (or the last stable tag if this is the first RC) to the new prerelease tag
325
+ - Update the `[Unreleased]` compare link to point from the new prerelease tag to `HEAD`
326
+
327
+ **Resulting structure** after stamping `5.0.0.rc.1` (with `5.0.0.rc.0` already shipped on top of stable `4.2.0`):
328
+
329
+ ```markdown
330
+ ## [Unreleased]
331
+
332
+ ## [5.0.0.rc.1] - 2026-05-25
333
+
334
+ ### Fixed
335
+
336
+ - **Fix regression introduced in rc.0**. [PR 320](https://github.com/shakacode/control-plane-flow/pull/320) by [Justin Gordon](https://github.com/justin808).
337
+
338
+ ## [5.0.0.rc.0] - 2026-05-10
339
+
340
+ ### Added
341
+
342
+ - **New feature**. [PR 315](https://github.com/shakacode/control-plane-flow/pull/315) by [Justin Gordon](https://github.com/justin808).
343
+
344
+ ## [4.2.0] - 2026-04-01
345
+
346
+ ...
347
+
348
+ [Unreleased]: https://github.com/shakacode/control-plane-flow/compare/v5.0.0.rc.1...HEAD
349
+ [5.0.0.rc.1]: https://github.com/shakacode/control-plane-flow/compare/v5.0.0.rc.0...v5.0.0.rc.1
350
+ [5.0.0.rc.0]: https://github.com/shakacode/control-plane-flow/compare/v4.2.0...v5.0.0.rc.0
351
+ [4.2.0]: https://github.com/shakacode/control-plane-flow/compare/v4.1.1...v4.2.0
352
+ ```
321
353
 
322
- **Note**: The new version header must be inserted **immediately after `## [Unreleased]`** (see Step 4). This ensures correct ordering of version headers.
354
+ Both RC sections remain intact with their own compare links until the stable release coalesces them.
355
+
356
+ **Coalescing happens only at the stable release** — see "For Prerelease to Stable Version Release" below.
357
+
358
+ **Note**: The new version header must be inserted **immediately after `## [Unreleased]`** (see Step 4). This ensures correct newest-first ordering of version headers.
323
359
 
324
360
  ### For Prerelease to Stable Version Release
325
361
 
326
- When releasing from prerelease to a stable version (e.g., `v5.0.0.rc.1` -> `v5.0.0`):
362
+ When releasing from prerelease to a stable version (e.g., `v5.0.0.rc.2` -> `v5.0.0`), this is where the accumulated prerelease sections get coalesced into one stable section. **Curate carefully** — users landing on the stable version don't care about intermediate prerelease state, and noise here makes the upgrade story harder to read.
363
+
364
+ #### Step 1: Coalesce all prerelease sections into one stable section
365
+
366
+ - Replace `## [5.0.0.rc.0]`, `## [5.0.0.rc.1]`, `## [5.0.0.beta.1]`, etc. with a single `## [5.0.0] - YYYY-MM-DD` section
367
+ - **Move any remaining entries from `## [Unreleased]` into the new stable section** — anything still under `[Unreleased]` at stable-release time is shipping in this stable version. Leave `## [Unreleased]` with only its header (no entries).
368
+ - Combine entries from all prerelease sections and the moved `[Unreleased]` entries, consolidating duplicate category headings (e.g., merge multiple `### Fixed` sections into one under the preferred order from "Category Organization")
369
+ - Remove the orphaned compare links at the bottom of the file for the coalesced prerelease versions
370
+ - Add the `[5.0.0]` compare link pointing from the previous stable tag (e.g., `v4.2.0`) to `v5.0.0` — **not** from the latest RC tag
371
+ - Update the `[Unreleased]` compare link to point from `v5.0.0` to `HEAD`
372
+
373
+ #### Step 2: Curate the entries — REMOVE these
374
+
375
+ 1. **Prerelease-only fixes** — bugs introduced during the prerelease cycle and fixed in a later RC. If the bug never shipped in a stable release, the fix is noise to stable users.
376
+ - Investigate when a bug was introduced: `git log --oneline v<last_stable>..v<rc_that_fixed_the_bug>` — if this commit range doesn't contain the bug's introduction (i.e., the introducing commit predates the RC cycle), the bug was already in the last stable release and the fix belongs in the stable section. If the introduction *is* in this range, the bug never shipped in stable, so drop the fix.
377
+ - Check the PR description for what was broken and when
378
+
379
+ 2. **Refinements to prerelease-only features** — if a new feature was introduced in `rc.0` and then iterated in `rc.1`/`rc.2`, keep only the final description and drop the iteration history
380
+
381
+ 3. **Internal/contributor-only tooling** — CI tweaks, build script changes, generator handling of prerelease version formats, local-dev tooling fixes. These don't belong in a user-facing changelog.
382
+
383
+ #### Step 3: Curate the entries — KEEP these
384
+
385
+ 1. **User-facing fixes for bugs that existed in the previous stable** — if `rc.2` fixes a bug that was in `4.2.0`, that fix matters to stable users upgrading
386
+
387
+ 2. **Compatibility fixes** — Ruby/Rails version support, dependency relaxations, etc.
388
+
389
+ 3. **All breaking changes** — API/CLI changes, removed methods, configuration changes, exit code changes, generator output changes. Even if a breaking change was introduced and refined across multiple prereleases, the final breaking change description belongs in stable.
390
+
391
+ 4. **Performance/security improvements affecting all users**
392
+
393
+ #### Step 4: Investigation process for each entry
394
+
395
+ For each entry from a prerelease section, ask:
327
396
 
328
- 1. **Remove all prerelease version labels** from the changelog:
329
- - Change `## [5.0.0.rc.0]`, `## [5.0.0.rc.1]`, etc. to a single `## [5.0.0]` section
330
- - Also handle beta versions: `## [5.0.0.beta.1]` etc.
331
- - Combine all prerelease entries into the stable release section
397
+ - Was this bug present in the last stable release? If no, drop.
398
+ - Was this feature introduced in an earlier prerelease and then superseded? If yes, keep only the final state.
399
+ - Does this matter to someone upgrading from the last stable to this stable? If no, drop.
332
400
 
333
- 2. **Consolidate duplicate entries**:
334
- - If bug fixes or changes were made to features introduced in earlier prereleases, keep only the final state
335
- - Remove redundant changelog entries for fixes to prerelease features
336
- - Keep the most recent/accurate description of each change
401
+ #### Step 5: Final read-through
337
402
 
338
- 3. **Update version diff links** at the bottom to point to the stable version (and remove the orphaned prerelease compare links)
403
+ Read the resulting stable section as if you're a user upgrading from the previous stable. Every entry should be something you'd want to know about. If an entry only makes sense to someone who tracked the RC cycle, drop it.
339
404
 
340
405
  ## Examples
341
406
 
@@ -0,0 +1,137 @@
1
+ name: Resolve Review App Config
2
+ description: Infers review app prefix and Control Plane org from controlplane.yml
3
+
4
+ inputs:
5
+ configured_cpln_org_staging:
6
+ description: Optional override for the staging Control Plane org.
7
+ required: false
8
+ default: ""
9
+ configured_review_app_prefix:
10
+ description: Optional override for the review app prefix.
11
+ required: false
12
+ default: ""
13
+ pr_number:
14
+ description: Pull request number used to build the review app name.
15
+ required: false
16
+ default: ""
17
+ working_directory:
18
+ description: Directory containing .controlplane/controlplane.yml.
19
+ required: false
20
+ default: "."
21
+
22
+ outputs:
23
+ review_app_prefix:
24
+ description: Resolved review app prefix.
25
+ value: ${{ steps.resolve.outputs.review_app_prefix }}
26
+ cpln_org:
27
+ description: Resolved Control Plane org.
28
+ value: ${{ steps.resolve.outputs.cpln_org }}
29
+ app_name:
30
+ description: Resolved review app name when pr_number is present; omitted when pr_number is empty.
31
+ value: ${{ steps.resolve.outputs.app_name }}
32
+
33
+ runs:
34
+ using: composite
35
+ steps:
36
+ - name: Resolve review app config
37
+ id: resolve
38
+ shell: bash
39
+ working-directory: ${{ inputs.working_directory }}
40
+ env:
41
+ CONFIGURED_CPLN_ORG_STAGING: ${{ inputs.configured_cpln_org_staging }}
42
+ CONFIGURED_REVIEW_APP_PREFIX: ${{ inputs.configured_review_app_prefix }}
43
+ PR_NUMBER: ${{ inputs.pr_number }}
44
+ run: |
45
+ set -euo pipefail
46
+
47
+ ruby <<'RUBY'
48
+ require "yaml"
49
+
50
+ def safe_load_yaml_file(path)
51
+ contents = File.read(path)
52
+
53
+ if YAML.method(:safe_load).parameters.any? { |type, name| type == :key && name == :aliases }
54
+ YAML.safe_load(contents, aliases: true)
55
+ else
56
+ YAML.safe_load(contents, [], [], true)
57
+ end
58
+ end
59
+
60
+ def validate_github_env_value!(name, value)
61
+ return if value.match?(/\A[A-Za-z0-9-]+\z/)
62
+
63
+ warn "::error::#{name} must contain only letters, numbers, and hyphens so it is a valid Control Plane name and can be safely written to GitHub environment files."
64
+ exit 1
65
+ end
66
+
67
+ begin
68
+ config = safe_load_yaml_file(".controlplane/controlplane.yml")
69
+ unless config.is_a?(Hash)
70
+ warn "::error::.controlplane/controlplane.yml must be a YAML mapping; got #{config.class}: #{config.inspect}"
71
+ exit 1
72
+ end
73
+
74
+ apps = config["apps"]
75
+ unless apps.is_a?(Hash)
76
+ warn "::error::.controlplane/controlplane.yml must define an apps mapping; got #{apps.class}: #{apps.inspect}"
77
+ exit 1
78
+ end
79
+
80
+ review_apps = apps.select do |_name, app_config|
81
+ app_config.is_a?(Hash) && app_config["match_if_app_name_starts_with"] == true
82
+ end
83
+
84
+ prefix = ENV.fetch("CONFIGURED_REVIEW_APP_PREFIX", "").strip
85
+ if prefix.empty?
86
+ if review_apps.length == 1
87
+ prefix = review_apps.keys.first
88
+ elsif review_apps.empty?
89
+ warn "::error::Set REVIEW_APP_PREFIX or define exactly one app with match_if_app_name_starts_with: true in .controlplane/controlplane.yml."
90
+ exit 1
91
+ else
92
+ warn "::error::Set REVIEW_APP_PREFIX because .controlplane/controlplane.yml defines multiple review app prefixes: #{review_apps.keys.sort.join(', ')}."
93
+ exit 1
94
+ end
95
+ end
96
+
97
+ app_config = apps[prefix]
98
+ unless app_config.is_a?(Hash) && app_config["match_if_app_name_starts_with"] == true
99
+ warn "::error::Review app prefix '#{prefix}' must match an app in .controlplane/controlplane.yml with match_if_app_name_starts_with: true."
100
+ exit 1
101
+ end
102
+
103
+ cpln_org = ENV.fetch("CONFIGURED_CPLN_ORG_STAGING", "").strip
104
+ cpln_org = app_config["cpln_org"].to_s.strip if cpln_org.empty?
105
+ if cpln_org.empty?
106
+ warn "::error::Set CPLN_ORG_STAGING or cpln_org for review app prefix '#{prefix}' in .controlplane/controlplane.yml."
107
+ exit 1
108
+ end
109
+
110
+ pr_number = ENV.fetch("PR_NUMBER", "").strip
111
+ unless pr_number.empty? || pr_number.match?(/\A[1-9][0-9]*\z/)
112
+ warn "::error::PR_NUMBER must be a positive integer; got: #{pr_number.inspect}"
113
+ exit 1
114
+ end
115
+
116
+ app_name = pr_number.empty? ? "" : "#{prefix}-#{pr_number}"
117
+
118
+ validate_github_env_value!("REVIEW_APP_PREFIX", prefix)
119
+ validate_github_env_value!("CPLN_ORG", cpln_org)
120
+ validate_github_env_value!("APP_NAME", app_name) unless app_name.empty?
121
+
122
+ File.open(ENV.fetch("GITHUB_ENV"), "a") do |file|
123
+ file.puts "REVIEW_APP_PREFIX=#{prefix}"
124
+ file.puts "CPLN_ORG=#{cpln_org}"
125
+ file.puts "APP_NAME=#{app_name}" unless app_name.empty?
126
+ end
127
+
128
+ File.open(ENV.fetch("GITHUB_OUTPUT"), "a") do |file|
129
+ file.puts "review_app_prefix=#{prefix}"
130
+ file.puts "cpln_org=#{cpln_org}"
131
+ file.puts "app_name=#{app_name}" unless app_name.empty?
132
+ end
133
+ rescue StandardError => e
134
+ warn "::error::Could not resolve review app config from .controlplane/controlplane.yml: #{e.class}: #{e.message}"
135
+ exit 1
136
+ end
137
+ RUBY
@@ -37,6 +37,13 @@ inputs:
37
37
  without publishing a release.
38
38
  required: false
39
39
  default: ""
40
+ control_plane_flow_ref:
41
+ description: >-
42
+ Full GitHub workflow ref for the control-plane-flow reusable workflow, for
43
+ example shakacode/control-plane-flow/.github/workflows/deploy.yml@refs/tags/v5.0.1.
44
+ When cpflow_version is set, this must point at the matching release tag.
45
+ required: false
46
+ default: ""
40
47
 
41
48
  runs:
42
49
  using: composite
@@ -83,6 +90,7 @@ runs:
83
90
  - name: Install Control Plane CLI and cpflow gem
84
91
  shell: bash
85
92
  env:
93
+ CONTROL_PLANE_FLOW_REF: ${{ inputs.control_plane_flow_ref }}
86
94
  CPLN_CLI_VERSION: ${{ inputs.cpln_cli_version }}
87
95
  CPFLOW_VERSION: ${{ inputs.cpflow_version }}
88
96
  CPFLOW_SOURCE_DIR: ${{ github.action_path }}/../../..
@@ -95,6 +103,116 @@ runs:
95
103
 
96
104
  CPLN_CLI_VERSION="${CPLN_CLI_VERSION:-${default_cpln_cli_version}}"
97
105
 
106
+ normalize_version() {
107
+ local version="${1#v}"
108
+ version="${version//-/.}"
109
+
110
+ if [[ "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(\.[0-9A-Za-z]+)*$ ]]; then
111
+ echo "${version}"
112
+ fi
113
+ # Empty output means an unrecognized format; callers check for that.
114
+ }
115
+
116
+ extract_ref_name() {
117
+ local ref="${1##*@}"
118
+ ref="${ref#refs/tags/}"
119
+ echo "${ref}"
120
+ }
121
+
122
+ normalize_release_ref() {
123
+ local ref
124
+ ref="$(extract_ref_name "$1")"
125
+
126
+ # Intentional: non-v refs return success with empty stdout; callers
127
+ # treat empty output as "not a release tag".
128
+ [[ "${ref}" == v* ]] || return 0
129
+ normalize_version "${ref}"
130
+ }
131
+
132
+ verify_release_ref_matches_checkout() {
133
+ local ref
134
+ local remote_url="https://github.com/shakacode/control-plane-flow.git"
135
+ local tag_refs
136
+ local tag_commit=""
137
+ local checkout_commit
138
+
139
+ ref="$(extract_ref_name "$1")"
140
+ [[ "${ref}" == v* ]] || return 1
141
+
142
+ git_ls_remote_tag() {
143
+ if command -v timeout >/dev/null 2>&1; then
144
+ timeout 20 git ls-remote --tags "${remote_url}" "refs/tags/${ref}" "refs/tags/${ref}^{}"
145
+ else
146
+ git ls-remote --tags "${remote_url}" "refs/tags/${ref}" "refs/tags/${ref}^{}"
147
+ fi
148
+ }
149
+
150
+ if ! tag_refs="$(git_ls_remote_tag 2>&1)"; then
151
+ echo "::error::Could not verify the control-plane-flow workflow ref against ${remote_url}. Runners that set CPFLOW_VERSION need outbound HTTPS access to GitHub for this tag check. Leave CPFLOW_VERSION unset when testing a commit SHA or branch. Details: ${tag_refs}"
152
+ exit 1
153
+ fi
154
+
155
+ if [[ -z "${tag_refs}" ]]; then
156
+ echo "::error::CPFLOW_VERSION can only be used with an existing control-plane-flow release tag. No remote tag found for workflow ref ${CONTROL_PLANE_FLOW_REF}."
157
+ exit 1
158
+ fi
159
+
160
+ while read -r sha tag_ref; do
161
+ if [[ "${tag_ref}" == "refs/tags/${ref}^{}" ]]; then
162
+ tag_commit="${sha}"
163
+ break
164
+ fi
165
+
166
+ if [[ "${tag_ref}" == "refs/tags/${ref}" ]]; then
167
+ tag_commit="${sha}"
168
+ fi
169
+ done <<< "${tag_refs}"
170
+
171
+ if [[ -z "${tag_commit}" ]]; then
172
+ echo "::error::Could not resolve the commit for release tag ${ref}."
173
+ exit 1
174
+ fi
175
+
176
+ checkout_commit="$(git -C "${CPFLOW_SOURCE_DIR}" rev-parse HEAD)"
177
+ if [[ "${checkout_commit}" != "${tag_commit}" ]]; then
178
+ echo "::error::control-plane-flow workflow ref ${CONTROL_PLANE_FLOW_REF} resolved to ${checkout_commit}, but release tag ${ref} points to ${tag_commit}. Use the real release tag ref, not a moving branch, or leave CPFLOW_VERSION unset."
179
+ exit 1
180
+ fi
181
+ }
182
+
183
+ is_rubygems_version() {
184
+ [[ "${1}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(\.[0-9A-Za-z]+)*$ ]]
185
+ }
186
+
187
+ validate_cpflow_version_pin() {
188
+ [[ -z "${CPFLOW_VERSION}" ]] && return 0
189
+
190
+ local actual_version
191
+ local expected_version
192
+
193
+ if ! is_rubygems_version "${CPFLOW_VERSION}"; then
194
+ echo "::error::CPFLOW_VERSION must be a RubyGems version usable by 'gem install cpflow -v', such as 5.0.0 or 5.0.0.rc.1. Do not include a leading v, and use dot-separated prereleases instead of dash-separated prereleases."
195
+ exit 1
196
+ fi
197
+
198
+ actual_version="$(normalize_version "${CPFLOW_VERSION}")"
199
+ expected_version="$(normalize_release_ref "${CONTROL_PLANE_FLOW_REF}")"
200
+
201
+ if [[ -z "${expected_version}" ]]; then
202
+ echo "::error::CPFLOW_VERSION can only be used when the control-plane-flow reusable workflow is pinned to a release tag like v${CPFLOW_VERSION}. Dot and dash prerelease tags are accepted, for example v5.0.0.rc.1 or v5.0.0-rc.1. Current workflow ref: ${CONTROL_PLANE_FLOW_REF:-<empty>}. Leave CPFLOW_VERSION unset when testing a commit SHA or branch so cpflow is built from the same source as the reusable workflow."
203
+ exit 1
204
+ fi
205
+
206
+ verify_release_ref_matches_checkout "${CONTROL_PLANE_FLOW_REF}"
207
+
208
+ if [[ "${actual_version}" != "${expected_version}" ]]; then
209
+ echo "::error::CPFLOW_VERSION must match the control-plane-flow reusable workflow tag. CPFLOW_VERSION=${CPFLOW_VERSION}, normalized CPFLOW_VERSION=${actual_version}, workflow ref=${CONTROL_PLANE_FLOW_REF}, expected CPFLOW_VERSION=${expected_version}."
210
+ exit 1
211
+ fi
212
+ }
213
+
214
+ validate_cpflow_version_pin
215
+
98
216
  npm_global_prefix="${HOME}/.npm-global"
99
217
  mkdir -p "${npm_global_prefix}"
100
218
  echo "${npm_global_prefix}/bin" >> "$GITHUB_PATH"
@@ -2,12 +2,10 @@ name: Cleanup Stale Review Apps
2
2
 
3
3
  on:
4
4
  workflow_call:
5
- inputs:
6
- control_plane_flow_ref:
7
- description: Git ref used to load shared cpflow composite actions.
8
- required: false
9
- type: string
10
- default: main
5
+ secrets:
6
+ CPLN_TOKEN_STAGING:
7
+ description: Control Plane token for the staging org that owns review apps.
8
+ required: true
11
9
 
12
10
  permissions:
13
11
  contents: read
@@ -28,8 +26,8 @@ jobs:
28
26
  - name: Checkout control-plane-flow actions
29
27
  uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
30
28
  with:
31
- repository: shakacode/control-plane-flow
32
- ref: ${{ inputs.control_plane_flow_ref }}
29
+ repository: ${{ job.workflow_repository }}
30
+ ref: ${{ job.workflow_sha }}
33
31
  path: .cpflow
34
32
  persist-credentials: false
35
33
 
@@ -37,33 +35,40 @@ jobs:
37
35
  uses: ./.cpflow/.github/actions/cpflow-validate-config
38
36
  env:
39
37
  CPLN_TOKEN_STAGING: ${{ secrets.CPLN_TOKEN_STAGING }}
40
- CPLN_ORG_STAGING: ${{ vars.CPLN_ORG_STAGING }}
41
- REVIEW_APP_PREFIX: ${{ vars.REVIEW_APP_PREFIX }}
42
38
  with:
43
39
  required: |
44
40
  secret:CPLN_TOKEN_STAGING
45
- variable:CPLN_ORG_STAGING
46
- variable:REVIEW_APP_PREFIX
41
+
42
+ - name: Checkout caller repository
43
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
44
+ with:
45
+ path: app
46
+ persist-credentials: false
47
+
48
+ - name: Resolve review app config
49
+ id: review-config
50
+ uses: ./.cpflow/.github/actions/cpflow-resolve-review-config
51
+ with:
52
+ working_directory: app
53
+ configured_cpln_org_staging: ${{ vars.CPLN_ORG_STAGING }}
54
+ configured_review_app_prefix: ${{ vars.REVIEW_APP_PREFIX }}
47
55
 
48
56
  - name: Setup environment
49
57
  uses: ./.cpflow/.github/actions/cpflow-setup-environment
50
58
  with:
51
59
  token: ${{ secrets.CPLN_TOKEN_STAGING }}
52
- org: ${{ vars.CPLN_ORG_STAGING }}
60
+ org: ${{ steps.review-config.outputs.cpln_org }}
53
61
  working_directory: .cpflow
54
62
  cpln_cli_version: ${{ vars.CPLN_CLI_VERSION }}
55
63
  cpflow_version: ${{ vars.CPFLOW_VERSION }}
56
-
57
- - name: Checkout caller repository
58
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
59
- with:
60
- persist-credentials: false
64
+ control_plane_flow_ref: ${{ job.workflow_ref }}
61
65
 
62
66
  - name: Remove stale review apps
67
+ working-directory: app
63
68
  env:
64
- REVIEW_APP_PREFIX: ${{ vars.REVIEW_APP_PREFIX }}
65
- CPLN_ORG_STAGING: ${{ vars.CPLN_ORG_STAGING }}
69
+ REVIEW_APP_PREFIX: ${{ steps.review-config.outputs.review_app_prefix }}
70
+ CPLN_ORG: ${{ steps.review-config.outputs.cpln_org }}
66
71
  shell: bash
67
72
  run: |
68
73
  set -euo pipefail
69
- cpflow cleanup-stale-apps -a "${REVIEW_APP_PREFIX}" --org "${CPLN_ORG_STAGING}" --yes
74
+ cpflow cleanup-stale-apps -a "${REVIEW_APP_PREFIX}" --org "${CPLN_ORG}" --yes
@@ -2,12 +2,10 @@ name: Delete Review App
2
2
 
3
3
  on:
4
4
  workflow_call:
5
- inputs:
6
- control_plane_flow_ref:
7
- description: Git ref used to load shared cpflow composite actions.
8
- required: false
9
- type: string
10
- default: main
5
+ secrets:
6
+ CPLN_TOKEN_STAGING:
7
+ description: Control Plane token for the staging org that owns review apps.
8
+ required: true
11
9
 
12
10
  permissions:
13
11
  contents: read
@@ -21,8 +19,6 @@ concurrency:
21
19
  cancel-in-progress: false
22
20
 
23
21
  env:
24
- APP_NAME: ${{ vars.REVIEW_APP_PREFIX }}-${{ github.event.pull_request.number || github.event.issue.number || github.event.inputs.pr_number }}
25
- CPLN_ORG: ${{ vars.CPLN_ORG_STAGING }}
26
22
  PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number || github.event.inputs.pr_number }}
27
23
 
28
24
  jobs:
@@ -62,8 +58,8 @@ jobs:
62
58
  - name: Checkout control-plane-flow actions
63
59
  uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
64
60
  with:
65
- repository: shakacode/control-plane-flow
66
- ref: ${{ inputs.control_plane_flow_ref }}
61
+ repository: ${{ job.workflow_repository }}
62
+ ref: ${{ job.workflow_sha }}
67
63
  path: .cpflow
68
64
  persist-credentials: false
69
65
 
@@ -72,13 +68,9 @@ jobs:
72
68
  uses: ./.cpflow/.github/actions/cpflow-validate-config
73
69
  env:
74
70
  CPLN_TOKEN_STAGING: ${{ secrets.CPLN_TOKEN_STAGING }}
75
- CPLN_ORG_STAGING: ${{ vars.CPLN_ORG_STAGING }}
76
- REVIEW_APP_PREFIX: ${{ vars.REVIEW_APP_PREFIX }}
77
71
  with:
78
72
  required: |
79
73
  secret:CPLN_TOKEN_STAGING
80
- variable:CPLN_ORG_STAGING
81
- variable:REVIEW_APP_PREFIX
82
74
  pull_request_friendly: "true"
83
75
 
84
76
  - name: Checkout repository
@@ -88,15 +80,26 @@ jobs:
88
80
  path: app
89
81
  persist-credentials: false
90
82
 
83
+ - name: Resolve review app config
84
+ if: steps.config.outputs.ready == 'true'
85
+ id: review-config
86
+ uses: ./.cpflow/.github/actions/cpflow-resolve-review-config
87
+ with:
88
+ working_directory: app
89
+ configured_cpln_org_staging: ${{ vars.CPLN_ORG_STAGING }}
90
+ configured_review_app_prefix: ${{ vars.REVIEW_APP_PREFIX }}
91
+ pr_number: ${{ env.PR_NUMBER }}
92
+
91
93
  - name: Setup environment
92
94
  if: steps.config.outputs.ready == 'true'
93
95
  uses: ./.cpflow/.github/actions/cpflow-setup-environment
94
96
  with:
95
97
  token: ${{ secrets.CPLN_TOKEN_STAGING }}
96
- org: ${{ vars.CPLN_ORG_STAGING }}
98
+ org: ${{ steps.review-config.outputs.cpln_org }}
97
99
  working_directory: app
98
100
  cpln_cli_version: ${{ vars.CPLN_CLI_VERSION }}
99
101
  cpflow_version: ${{ vars.CPFLOW_VERSION }}
102
+ control_plane_flow_ref: ${{ job.workflow_ref }}
100
103
 
101
104
  - name: Set workflow links
102
105
  if: steps.config.outputs.ready == 'true'
@@ -134,9 +137,9 @@ jobs:
134
137
  if: steps.config.outputs.ready == 'true'
135
138
  uses: ./.cpflow/.github/actions/cpflow-delete-control-plane-app
136
139
  with:
137
- app_name: ${{ env.APP_NAME }}
138
- cpln_org: ${{ vars.CPLN_ORG_STAGING }}
139
- review_app_prefix: ${{ vars.REVIEW_APP_PREFIX }}
140
+ app_name: ${{ steps.review-config.outputs.app_name }}
141
+ cpln_org: ${{ steps.review-config.outputs.cpln_org }}
142
+ review_app_prefix: ${{ steps.review-config.outputs.review_app_prefix }}
140
143
  working_directory: app
141
144
 
142
145
  # Finalizer still runs after delete failures, but only after config validation