cpflow 5.0.2 → 5.0.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f6a3d49d8db4d5df28b82fa8cfb81af3470299d485169997c2904c683b1a56c6
4
- data.tar.gz: 48cacf78f5d61ca95405b46e152b26547536a3862a06d93433efaa7c52f7f85d
3
+ metadata.gz: 4ae89eb9d37d93ce9d6e22fa42d4210cce3183505a00ee91cee172e47cb9750b
4
+ data.tar.gz: ba697c7e72c80ae792feb52b47f42db38180c63af42ab7959650ce1b757d0201
5
5
  SHA512:
6
- metadata.gz: 9158740f20c83981417325473f6f9d3b27fee1e0967b31746d44f32d4d6e0dca2d14ab17dfd05d2f27a10a745b33e663cc775c562919fdaa2311e554cddfb2af
7
- data.tar.gz: c822be6375f28c6c01e13b397f5857fa4d3cd8463cc787cd6dc9be5a6f4967aa17a84af19f4e31c6b565e8325e19286de1c0fa398c22db4966a3f723e7adfb1a
6
+ metadata.gz: d8e9ed791d7c2401b598b2ca8aefd33b363d609baca9193b678c828eaf4ba286ce3b1177a9cbe4dee5d3c6bdaf03648d77d1d8ecc2c86b384bd123625cf38038
7
+ data.tar.gz: 972e3a9a8db47d3875363ab969efd14038181147b2e31ab3ad80de6d062b00d0674d4be76d53f7f470763f663b4a762568c174399c0d482c787fe4e8a1b6f79e
@@ -4,7 +4,17 @@ on:
4
4
  push:
5
5
  branches:
6
6
  - main
7
+ paths-ignore:
8
+ - '**.md'
9
+ - 'docs/**'
10
+ - 'LICENSE'
11
+ - 'COMM-LICENSE.txt'
7
12
  pull_request:
13
+ paths-ignore:
14
+ - '**.md'
15
+ - 'docs/**'
16
+ - 'LICENSE'
17
+ - 'COMM-LICENSE.txt'
8
18
  workflow_dispatch:
9
19
 
10
20
  jobs:
@@ -4,7 +4,17 @@ on:
4
4
  push:
5
5
  branches:
6
6
  - main
7
+ paths-ignore:
8
+ - '**.md'
9
+ - 'docs/**'
10
+ - 'LICENSE'
11
+ - 'COMM-LICENSE.txt'
7
12
  pull_request:
13
+ paths-ignore:
14
+ - '**.md'
15
+ - 'docs/**'
16
+ - 'LICENSE'
17
+ - 'COMM-LICENSE.txt'
8
18
 
9
19
  jobs:
10
20
  rubocop:
@@ -34,7 +34,7 @@ jobs:
34
34
  timeout-minutes: 5
35
35
  steps:
36
36
  - name: Generate GitHub App token
37
- uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0
37
+ uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
38
38
  id: app-token
39
39
  with:
40
40
  app-id: ${{ secrets.DOCS_DISPATCH_APP_ID }}
@@ -44,7 +44,7 @@ jobs:
44
44
 
45
45
  - name: Dispatch docs-updated event
46
46
  id: dispatch
47
- uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3.0.0
47
+ uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1
48
48
  with:
49
49
  token: ${{ steps.app-token.outputs.token }}
50
50
  repository: shakacode/controlplaneflow-com
data/CHANGELOG.md CHANGED
@@ -12,6 +12,16 @@ In addition to the standard keepachangelog.com categories, this project uses a l
12
12
 
13
13
  ## [Unreleased]
14
14
 
15
+ ## [5.0.3] - 2026-05-26
16
+
17
+ ### Changed
18
+
19
+ - **Changed `cpflow update-github-actions`, `cpflow generate-github-actions --force`, RubyGems install guidance, and release-task follow-up output so downstream repositories remember to update checked-in generated GitHub Actions wrappers when the `cpflow` gem version changes.** [PR 328](https://github.com/shakacode/control-plane-flow/pull/328) by [Justin Gordon](https://github.com/justin808).
20
+
21
+ ### Fixed
22
+
23
+ - **Fixed `cpflow update-github-actions` so reordered default staging branches remain defaults, empty or non-hash staging workflow YAML no longer crashes, and fresh repos fail with a clear `generate-github-actions` instruction instead of silently creating wrappers.** [PR 331](https://github.com/shakacode/control-plane-flow/pull/331) by [Justin Gordon](https://github.com/justin808). Clarified the RubyGems post-install message so first-time installers see the `generate-github-actions` path.
24
+
15
25
  ## [5.0.2] - 2026-05-25
16
26
 
17
27
  ### Fixed
@@ -382,7 +392,8 @@ Deprecated `cpl` gem. New gem is `cpflow`.
382
392
 
383
393
  First release.
384
394
 
385
- [Unreleased]: https://github.com/shakacode/control-plane-flow/compare/v5.0.2...HEAD
395
+ [Unreleased]: https://github.com/shakacode/control-plane-flow/compare/v5.0.3...HEAD
396
+ [5.0.3]: https://github.com/shakacode/control-plane-flow/compare/v5.0.2...v5.0.3
386
397
  [5.0.2]: https://github.com/shakacode/control-plane-flow/compare/v5.0.1...v5.0.2
387
398
  [5.0.1]: https://github.com/shakacode/control-plane-flow/compare/v5.0.0...v5.0.1
388
399
  [5.0.0]: https://github.com/shakacode/control-plane-flow/compare/v5.0.0.rc.3...v5.0.0
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cpflow (5.0.2)
4
+ cpflow (5.0.3)
5
5
  dotenv (~> 3.1)
6
6
  jwt (~> 3.1)
7
7
  psych (~> 5.2)
data/cpflow.gemspec CHANGED
@@ -12,6 +12,23 @@ Gem::Specification.new do |spec|
12
12
  spec.description = "CLI for providing Heroku-like platform-as-a-service on Control Plane"
13
13
  spec.homepage = "https://github.com/shakacode/control-plane-flow"
14
14
  spec.license = "MIT"
15
+ spec.post_install_message = <<~MESSAGE
16
+ cpflow #{Cpflow::VERSION} installed.
17
+
18
+ If this repository already uses generated cpflow GitHub Actions, update the
19
+ checked-in wrappers so GitHub loads the matching control-plane-flow release tag:
20
+
21
+ cpflow update-github-actions
22
+ bin/test-cpflow-github-flow
23
+
24
+ If you run cpflow through Bundler:
25
+
26
+ bundle exec cpflow update-github-actions
27
+ bin/test-cpflow-github-flow bundle exec cpflow
28
+
29
+ New repository? Run `cpflow generate-github-actions` first to create the
30
+ wrappers (and the `bin/test-cpflow-github-flow` script referenced above).
31
+ MESSAGE
15
32
 
16
33
  spec.required_ruby_version = ">= 3.0.0"
17
34
 
@@ -20,7 +20,7 @@ prompt tells the agent to stop on.
20
20
  ```text
21
21
  Set up Control Plane GitHub Flow for this repo. Start with `cpflow github-flow-readiness` and stop on any reported blockers. The repo must be deployable from a clean clone: published package versions, complete runtime scaffold, and a production Dockerfile that can build the app. If any package version is unpublished, inaccessible from CI, or requires credentials that are not already modeled in the repo or GitHub settings, stop and report the blocker instead of generating workflow files. If the repo is a legacy sample pinned to an obsolete Ruby or Bundler toolchain, if it does not even have a production Dockerfile yet, or if it is a monorepo without an already-decided single app boundary for this flow, stop and report that as a prerequisite instead of forcing the rollout.
22
22
 
23
- If `.controlplane/` is missing, run `cpflow generate`. Treat the generated app names as the repo-name default and rename them only if the project needs a different prefix. Then run `cpflow generate-github-actions` (or `cpflow generate-github-actions --staging-branch BRANCH` when staging should deploy from a branch other than `main`/`master`), keep review apps opt-in via `+review-app-deploy`, make sure any `STAGING_APP_BRANCH` repository variable is also present in the generated staging workflow's `on.push.branches` filter, and list the GitHub secrets and variables that must be configured. Do not hand-edit duplicated upstream refs into the generated wrappers: the only downstream Control Plane Flow pin should be the reusable workflow `uses: ...@vX.Y.Z` value generated from the installed `cpflow` gem version, and upstream workflows load their matching shared actions automatically. Keep the standard path simple: review apps require only `CPLN_TOKEN_STAGING` when the generated review app config can be inferred. Document the one-time Control Plane bootstrap command for persistent staging and production apps with `cpflow setup-app --skip-post-creation-hook`; for existing apps or later template updates, document `cpflow apply-template` and the need for the app identity to have `reveal` on the app secret policy. Do not imply the staging deploy or promotion workflows create those persistent GVCs. For production promotion, document a protected `production` GitHub Environment with required reviewers, prevent self-review, and `CPLN_TOKEN_PRODUCTION` stored as an environment secret, not as a repository or organization secret.
23
+ If `.controlplane/` is missing, run `cpflow generate`. Treat the generated app names as the repo-name default and rename them only if the project needs a different prefix. Then run `cpflow generate-github-actions` (or `cpflow generate-github-actions --staging-branch BRANCH` when staging should deploy from a branch other than `main`/`master`), keep review apps opt-in via `+review-app-deploy`, make sure any `STAGING_APP_BRANCH` repository variable is also present in the generated staging workflow's `on.push.branches` filter, and list the GitHub secrets and variables that must be configured. Do not hand-edit duplicated upstream refs into the generated wrappers: the only downstream Control Plane Flow pin should be the reusable workflow `uses: ...@vX.Y.Z` value generated from the installed `cpflow` gem version, and upstream workflows load their matching shared actions automatically. When bumping the `cpflow` gem in a downstream repo, run `cpflow update-github-actions` (or `bundle exec cpflow update-github-actions`) and validate with `bin/test-cpflow-github-flow` in the same PR so the checked-in wrappers move to the matching release tag. Keep the standard path simple: review apps require only `CPLN_TOKEN_STAGING` when the generated review app config can be inferred. Document the one-time Control Plane bootstrap command for persistent staging and production apps with `cpflow setup-app --skip-post-creation-hook`; for existing apps or later template updates, document `cpflow apply-template` and the need for the app identity to have `reveal` on the app secret policy. Do not imply the staging deploy or promotion workflows create those persistent GVCs. For production promotion, document a protected `production` GitHub Environment with required reviewers, prevent self-review, and `CPLN_TOKEN_PRODUCTION` stored as an environment secret, not as a repository or organization secret.
24
24
 
25
25
  Keep Node available in the final image if asset compilation or SSR depends on ExecJS, Yarn, `pnpm`, or npm after the main install layer. Make sure the generated Dockerfile uses a Ruby base image compatible with the app's declared Ruby requirement. Preserve repo-defined frontend build hooks: if `config/shakapacker.yml` defines a `precompile_hook`, or React on Rails enables `config.auto_load_bundle = true`, confirm the generated Dockerfile runs that codegen step before `rails assets:precompile`. If `config/database.yml` shows SQLite in production, confirm that the generated scaffold uses persistent `db` and `storage` volumes plus a release script that runs `rails db:prepare`; otherwise keep the default Postgres workload. If the public workload is not named `rails`, set `PRIMARY_WORKLOAD` or adjust the generated workflows. Inspect the Dockerfile and package sources for private GitHub dependencies or `RUN --mount=type=ssh`; if present, wire `DOCKER_BUILD_SSH_KEY`, optionally set `DOCKER_BUILD_SSH_KNOWN_HOSTS` for non-GitHub SSH hosts, and keep `DOCKER_BUILD_EXTRA_ARGS` to newline-delimited single tokens such as `--build-arg=FOO=bar`.
26
26
 
@@ -455,6 +455,43 @@ That release tag should point to the same source that produced the RubyGems
455
455
  release. Downstream production automation should use release tags, not `main` or
456
456
  feature-branch refs.
457
457
 
458
+ ## Updating Generated GitHub Actions After Gem Updates
459
+
460
+ Whenever a downstream repo updates the `cpflow` gem, update the checked-in
461
+ GitHub Actions wrappers in the same PR. The gem version does not make GitHub load
462
+ new reusable workflow YAML by itself; GitHub loads the `uses:` ref committed in
463
+ `.github/workflows/cpflow-*.yml`.
464
+
465
+ Use the installed gem to refresh the generated wrappers:
466
+
467
+ ```sh
468
+ cpflow update-github-actions
469
+ bin/test-cpflow-github-flow
470
+ ```
471
+
472
+ If the app runs `cpflow` through Bundler, use:
473
+
474
+ ```sh
475
+ bundle exec cpflow update-github-actions
476
+ bin/test-cpflow-github-flow bundle exec cpflow
477
+ ```
478
+
479
+ `cpflow update-github-actions` regenerates the generated wrapper and helper
480
+ files from the installed gem, pins the wrapper `uses:` refs to `v<gem-version>`,
481
+ and preserves a single custom staging branch from the existing generated staging
482
+ workflow. Pass `--staging-branch BRANCH` when changing or restoring a custom
483
+ staging branch explicitly.
484
+
485
+ When keeping `cpflow` in an app Gemfile, leave a comment next to the gem entry
486
+ so future dependency bumps include the wrapper update:
487
+
488
+ ```ruby
489
+ # After bumping cpflow, run:
490
+ # bundle exec cpflow update-github-actions
491
+ # bin/test-cpflow-github-flow bundle exec cpflow
492
+ gem "cpflow", "5.0.1"
493
+ ```
494
+
458
495
  `CPFLOW_VERSION` is a runtime override. If a downstream repository sets the
459
496
  `CPFLOW_VERSION` variable, the setup action runs `gem install cpflow -v
460
497
  <version>`. If it is unset, the setup action builds `cpflow` from the checked-out
data/docs/commands.md CHANGED
@@ -218,6 +218,9 @@ Creates GitHub Actions templates for a Heroku Flow style Control Plane pipeline:
218
218
  Pass `--staging-branch BRANCH` when staging should auto-deploy from a branch
219
219
  other than `main` or `master`; the generator will bake that branch into the
220
220
  GitHub Actions push trigger and use it as the default STAGING_APP_BRANCH.
221
+ Pass `--force` to overwrite existing generated files. Prefer
222
+ `cpflow update-github-actions` after bumping the cpflow gem in a downstream
223
+ repo.
221
224
 
222
225
  ```sh
223
226
  # Creates thin .github/workflows wrappers for the Control Plane flow
@@ -225,6 +228,9 @@ cpflow generate-github-actions
225
228
 
226
229
  # Creates the flow with staging deploys triggered from develop
227
230
  cpflow generate-github-actions --staging-branch develop
231
+
232
+ # Overwrites existing generated wrappers from the installed cpflow gem
233
+ cpflow generate-github-actions --force
228
234
  ```
229
235
 
230
236
  ### `github-flow-readiness`
@@ -533,6 +539,28 @@ cpflow terraform generate
533
539
  cpflow terraform import
534
540
  ```
535
541
 
542
+ ### `update-github-actions`
543
+
544
+ Regenerates the generated cpflow GitHub Actions wrappers and helper files
545
+ from the currently installed cpflow gem. Use this after updating the
546
+ cpflow gem so checked-in workflow wrappers move to the matching upstream
547
+ release tag, for example `v5.0.2`.
548
+
549
+ If the existing generated staging workflow uses a custom single staging
550
+ branch, the command preserves it. Pass `--staging-branch BRANCH` to set or
551
+ replace the generated staging branch explicitly.
552
+
553
+ ```sh
554
+ # After updating the cpflow gem, refresh generated GitHub Actions wrappers
555
+ cpflow update-github-actions
556
+
557
+ # When running cpflow through Bundler
558
+ bundle exec cpflow update-github-actions
559
+
560
+ # Preserve or set a custom staging branch
561
+ cpflow update-github-actions --staging-branch develop
562
+ ```
563
+
536
564
  ### `version`
537
565
 
538
566
  - Displays the current version of the CLI
data/docs/releasing.md CHANGED
@@ -99,6 +99,8 @@ GEM_RELEASE_MAX_RETRIES=<n>
99
99
  9. Commits the version bump, tags `vX.Y.Z`, and pushes the commit and tags.
100
100
  10. Publishes the `cpflow` gem to RubyGems.org.
101
101
  11. Creates or updates the GitHub release from the matching changelog section.
102
+ 12. Prints the downstream GitHub Actions follow-up command:
103
+ `cpflow update-github-actions`.
102
104
 
103
105
  The older `bundle exec rake "create_release[4.2.0,false]"` task name remains as
104
106
  a compatibility alias, but new releases should use `bundle exec rake release`.
@@ -32,7 +32,7 @@ module Command
32
32
  <<~PROMPT
33
33
  Set up Control Plane GitHub Flow for this repo. Start with `cpflow github-flow-readiness` and stop on any reported blockers. The repo must be deployable from a clean clone: published package versions, complete runtime scaffold, and a production Dockerfile that can build the app. If any package version is unpublished, inaccessible from CI, or requires credentials that are not already modeled in the repo or GitHub settings, stop and report the blocker instead of generating workflow files. If the repo is a legacy sample pinned to an obsolete Ruby or Bundler toolchain, if it does not even have a production Dockerfile yet, or if it is a monorepo without an already-decided single app boundary for this flow, stop and report that as a prerequisite instead of forcing the rollout.
34
34
 
35
- If `.controlplane/` is missing, run `cpflow generate`. Treat the generated app names as the repo-name default (`#{inferred_app_prefix}`) and rename them only if the project needs a different prefix. Then run `cpflow generate-github-actions` (or `cpflow generate-github-actions --staging-branch BRANCH` when staging should deploy from a branch other than `main`/`master`), keep review apps opt-in via `+review-app-deploy`, make sure any `STAGING_APP_BRANCH` repository variable is also present in the generated staging workflow's `on.push.branches` filter, and list the GitHub secrets and variables that must be configured. Do not hand-edit duplicated upstream refs into the generated wrappers: the only downstream Control Plane Flow pin should be the reusable workflow `uses: ...@vX.Y.Z` value generated from the installed `cpflow` gem version, and upstream workflows load their matching shared actions automatically. Keep the standard path simple: review apps require only `CPLN_TOKEN_STAGING` when the generated review app config can be inferred. Document the one-time Control Plane bootstrap command for persistent staging and production apps with `cpflow setup-app --skip-post-creation-hook`; for existing apps or later template updates, document `cpflow apply-template` and the need for the app identity to have `reveal` on the app secret policy. Do not imply the staging deploy or promotion workflows create those persistent GVCs. For production promotion, document a protected `production` GitHub Environment with required reviewers, prevent self-review, and `CPLN_TOKEN_PRODUCTION` stored as an environment secret, not as a repository or organization secret.
35
+ If `.controlplane/` is missing, run `cpflow generate`. Treat the generated app names as the repo-name default (`#{inferred_app_prefix}`) and rename them only if the project needs a different prefix. Then run `cpflow generate-github-actions` (or `cpflow generate-github-actions --staging-branch BRANCH` when staging should deploy from a branch other than `main`/`master`), keep review apps opt-in via `+review-app-deploy`, make sure any `STAGING_APP_BRANCH` repository variable is also present in the generated staging workflow's `on.push.branches` filter, and list the GitHub secrets and variables that must be configured. Do not hand-edit duplicated upstream refs into the generated wrappers: the only downstream Control Plane Flow pin should be the reusable workflow `uses: ...@vX.Y.Z` value generated from the installed `cpflow` gem version, and upstream workflows load their matching shared actions automatically. When bumping the `cpflow` gem in a downstream repo, run `cpflow update-github-actions` (or `bundle exec cpflow update-github-actions`) and validate with `bin/test-cpflow-github-flow` in the same PR so the checked-in wrappers move to the matching release tag. Keep the standard path simple: review apps require only `CPLN_TOKEN_STAGING` when the generated review app config can be inferred. Document the one-time Control Plane bootstrap command for persistent staging and production apps with `cpflow setup-app --skip-post-creation-hook`; for existing apps or later template updates, document `cpflow apply-template` and the need for the app identity to have `reveal` on the app secret policy. Do not imply the staging deploy or promotion workflows create those persistent GVCs. For production promotion, document a protected `production` GitHub Environment with required reviewers, prevent self-review, and `CPLN_TOKEN_PRODUCTION` stored as an environment secret, not as a repository or organization secret.
36
36
 
37
37
  Keep Node available in the final image if asset compilation or SSR depends on ExecJS, Yarn, `pnpm`, or npm after the main install layer. Make sure the generated Dockerfile uses a Ruby base image compatible with the app's declared Ruby requirement. Preserve repo-defined frontend build hooks: if `config/shakapacker.yml` defines a `precompile_hook`, or React on Rails enables `config.auto_load_bundle = true`, confirm the generated Dockerfile runs that codegen step before `rails assets:precompile`. If `config/database.yml` shows SQLite in production, confirm that the generated scaffold uses persistent `db` and `storage` volumes plus a release script that runs `rails db:prepare`; otherwise keep the default Postgres workload. If the public workload is not named `rails`, set `PRIMARY_WORKLOAD` or adjust the generated workflows. Inspect the Dockerfile and package sources for private GitHub dependencies or `RUN --mount=type=ssh`; if present, wire `DOCKER_BUILD_SSH_KEY`, optionally set `DOCKER_BUILD_SSH_KNOWN_HOSTS` for non-GitHub SSH hosts, and keep `DOCKER_BUILD_EXTRA_ARGS` to newline-delimited single tokens such as `--build-arg=FOO=bar`.
38
38
 
data/lib/command/base.rb CHANGED
@@ -330,6 +330,17 @@ module Command
330
330
  }
331
331
  end
332
332
 
333
+ def self.force_option(required: false)
334
+ {
335
+ name: :force,
336
+ params: {
337
+ desc: "Overwrite existing generated files",
338
+ type: :boolean,
339
+ required: required
340
+ }
341
+ }
342
+ end
343
+
333
344
  def self.logs_limit_option(required: false)
334
345
  {
335
346
  name: :limit,
@@ -4,6 +4,7 @@ require "json"
4
4
  require "pathname"
5
5
 
6
6
  require_relative "generator_helpers"
7
+ require_relative "staging_branch_validation"
7
8
 
8
9
  module Command
9
10
  class GithubActionsGenerator < Thor::Group
@@ -80,8 +81,10 @@ module Command
80
81
  end
81
82
 
82
83
  class GenerateGithubActions < Base
84
+ include StagingBranchValidation
85
+
83
86
  NAME = "generate-github-actions"
84
- OPTIONS = [staging_branch_option].freeze
87
+ OPTIONS = [staging_branch_option, force_option].freeze
85
88
  DESCRIPTION = "Creates GitHub Actions templates for review apps, staging deploys, and production promotion"
86
89
  LONG_DESCRIPTION = <<~DESC
87
90
  Creates GitHub Actions templates for a Heroku Flow style Control Plane pipeline:
@@ -93,6 +96,9 @@ module Command
93
96
  Pass `--staging-branch BRANCH` when staging should auto-deploy from a branch
94
97
  other than `main` or `master`; the generator will bake that branch into the
95
98
  GitHub Actions push trigger and use it as the default STAGING_APP_BRANCH.
99
+ Pass `--force` to overwrite existing generated files. Prefer
100
+ `cpflow update-github-actions` after bumping the cpflow gem in a downstream
101
+ repo.
96
102
  DESC
97
103
  EXAMPLES = <<~EX
98
104
  ```sh
@@ -101,6 +107,9 @@ module Command
101
107
 
102
108
  # Creates the flow with staging deploys triggered from develop
103
109
  cpflow generate-github-actions --staging-branch develop
110
+
111
+ # Overwrites existing generated wrappers from the installed cpflow gem
112
+ cpflow generate-github-actions --force
104
113
  ```
105
114
  EX
106
115
  WITH_INFO_HEADER = false
@@ -129,10 +138,11 @@ module Command
129
138
  self.class.ensure_template_root!
130
139
  branch = staging_branch
131
140
 
132
- if (existing = existing_files).any?
141
+ if (existing = existing_files).any? && !force?
133
142
  files = existing.map { |path| "- #{path}" }.join("\n")
134
143
  Shell.warn("The following files already exist:\n#{files}\n\n" \
135
- "Remove or rename them before running `cpflow #{NAME}` again.")
144
+ "Remove or rename them before running `cpflow #{NAME}` again, " \
145
+ "or run `cpflow update-github-actions` after updating the cpflow gem.")
136
146
  return
137
147
  end
138
148
 
@@ -145,38 +155,8 @@ module Command
145
155
  @existing_files ||= self.class.generated_files.select { |path| File.exist?(path) }
146
156
  end
147
157
 
148
- def staging_branch
149
- branch = config.options[:staging_branch].to_s.strip
150
- return nil if branch.empty?
151
-
152
- unless valid_staging_branch?(branch)
153
- Shell.abort(
154
- "Invalid --staging-branch value: #{branch.inspect}. " \
155
- "Use a valid git branch name containing only alphanumerics, dots, slashes, underscores, hyphens, and @."
156
- )
157
- end
158
-
159
- branch
160
- end
161
-
162
- def valid_staging_branch?(branch)
163
- return false unless branch.match?(%r{\A[a-zA-Z0-9._/@-]+\z})
164
-
165
- valid_git_branch_shape?(branch) && valid_git_branch_components?(branch)
166
- end
167
-
168
- def valid_git_branch_shape?(branch)
169
- return false if branch.start_with?("-", "/", ".")
170
- return false if branch.end_with?("/", ".")
171
- return false if branch.include?("@{")
172
-
173
- !branch.include?("..")
174
- end
175
-
176
- def valid_git_branch_components?(branch)
177
- branch.split("/").none? do |component|
178
- component.empty? || component.start_with?(".") || component.end_with?(".lock")
179
- end
158
+ def force?
159
+ config.options[:force]
180
160
  end
181
161
  end
182
162
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ module StagingBranchValidation
5
+ private
6
+
7
+ def staging_branch
8
+ branch = config.options[:staging_branch].to_s.strip
9
+ return nil if branch.empty?
10
+
11
+ unless valid_staging_branch?(branch)
12
+ Shell.abort(
13
+ "Invalid --staging-branch value: #{branch.inspect}. " \
14
+ "Use a valid git branch name containing only alphanumerics, dots, slashes, underscores, hyphens, and @."
15
+ )
16
+ end
17
+
18
+ branch
19
+ end
20
+
21
+ def valid_staging_branch?(branch)
22
+ return false unless branch.match?(%r{\A[a-zA-Z0-9._/@-]+\z})
23
+
24
+ valid_git_branch_shape?(branch) && valid_git_branch_components?(branch)
25
+ end
26
+
27
+ def valid_git_branch_shape?(branch)
28
+ return false if branch.start_with?("-", "/", ".")
29
+ return false if branch.end_with?("/", ".")
30
+ return false if branch.include?("@{")
31
+
32
+ !branch.include?("..")
33
+ end
34
+
35
+ def valid_git_branch_components?(branch)
36
+ branch.split("/").none? do |component|
37
+ component.empty? || component.start_with?(".") || component.end_with?(".lock")
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ require_relative "staging_branch_validation"
6
+
7
+ module Command
8
+ class UpdateGithubActions < Base
9
+ include StagingBranchValidation
10
+
11
+ NAME = "update-github-actions"
12
+ OPTIONS = [staging_branch_option].freeze
13
+ DESCRIPTION = "Regenerates generated GitHub Actions wrappers for the installed cpflow version"
14
+ LONG_DESCRIPTION = <<~DESC.freeze
15
+ Regenerates the generated cpflow GitHub Actions wrappers and helper files
16
+ from the currently installed cpflow gem. Use this after updating the
17
+ cpflow gem so checked-in workflow wrappers move to the matching upstream
18
+ release tag, for example `v#{Cpflow::VERSION}`.
19
+
20
+ If the existing generated staging workflow uses a custom single staging
21
+ branch, the command preserves it. Pass `--staging-branch BRANCH` to set or
22
+ replace the generated staging branch explicitly.
23
+ DESC
24
+ EXAMPLES = <<~EX
25
+ ```sh
26
+ # After updating the cpflow gem, refresh generated GitHub Actions wrappers
27
+ cpflow update-github-actions
28
+
29
+ # When running cpflow through Bundler
30
+ bundle exec cpflow update-github-actions
31
+
32
+ # Preserve or set a custom staging branch
33
+ cpflow update-github-actions --staging-branch develop
34
+ ```
35
+ EX
36
+ WITH_INFO_HEADER = false
37
+ VALIDATIONS = [].freeze
38
+ REQUIRES_STARTUP_CHECKS = false
39
+
40
+ DEFAULT_STAGING_BRANCHES = %w[main master].freeze
41
+ STAGING_WORKFLOW_PATH = Pathname.new(".github/workflows/cpflow-deploy-staging.yml")
42
+
43
+ def call
44
+ GenerateGithubActions.ensure_template_root!
45
+ abort_if_no_generated_files!
46
+
47
+ branch = staging_branch || inferred_staging_branch
48
+ GithubActionsGenerator.start([branch].compact)
49
+
50
+ print_post_update_message
51
+ end
52
+
53
+ private
54
+
55
+ def abort_if_no_generated_files!
56
+ return if GenerateGithubActions.generated_files.any? { |path| File.exist?(path) }
57
+
58
+ Shell.abort(
59
+ "No generated cpflow GitHub Actions files found in this repository. " \
60
+ "Run `cpflow generate-github-actions` first to create the wrappers, " \
61
+ "then use `cpflow update-github-actions` after future gem upgrades."
62
+ )
63
+ end
64
+
65
+ def print_post_update_message
66
+ Shell.info("")
67
+ Shell.info("Updated cpflow GitHub Actions wrappers for cpflow #{Cpflow::VERSION}.")
68
+ Shell.info("Next: review the diff and run `bin/test-cpflow-github-flow`.")
69
+ Shell.info("If you run cpflow through Bundler, use `bin/test-cpflow-github-flow bundle exec cpflow`.")
70
+ end
71
+
72
+ def inferred_staging_branch
73
+ branches = existing_staging_branches
74
+ return if branches.empty? || branches.sort == DEFAULT_STAGING_BRANCHES.sort
75
+ return branches.first if branches.length == 1
76
+
77
+ Shell.warn(
78
+ "Existing staging workflow has multiple custom push branches: #{branches.join(', ')}. " \
79
+ "Run with --staging-branch BRANCH if only one branch should auto-deploy staging."
80
+ )
81
+
82
+ nil
83
+ end
84
+
85
+ def existing_staging_branches
86
+ Array(parsed_staging_workflow_on.dig("push", "branches")).map(&:to_s)
87
+ end
88
+
89
+ def parsed_staging_workflow_on
90
+ return {} unless STAGING_WORKFLOW_PATH.file?
91
+
92
+ workflow = YAML.load_file(STAGING_WORKFLOW_PATH, aliases: true)
93
+ return {} unless workflow.is_a?(Hash)
94
+
95
+ workflow_on = workflow["on"] || workflow[true]
96
+ workflow_on.is_a?(Hash) ? workflow_on : {}
97
+ rescue Psych::SyntaxError => e
98
+ warn_unparseable_staging_workflow(e)
99
+ {}
100
+ end
101
+
102
+ def warn_unparseable_staging_workflow(error)
103
+ Shell.warn(
104
+ "Could not parse #{STAGING_WORKFLOW_PATH}: #{error.message}. " \
105
+ "Run with --staging-branch BRANCH if staging should use a custom branch."
106
+ )
107
+ end
108
+ end
109
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cpflow
4
- VERSION = "5.0.2"
4
+ VERSION = "5.0.3"
5
5
  MIN_CPLN_VERSION = "3.1.0"
6
6
  end
@@ -88,6 +88,21 @@ checked-out upstream source. If you set `CPFLOW_VERSION`, it must match the
88
88
  release tag, for example `CPFLOW_VERSION=5.0.1` with a wrapper pinned to
89
89
  `uses: ...@v5.0.1`.
90
90
 
91
+ After updating the `cpflow` gem in this repo, update the generated wrappers in
92
+ the same PR:
93
+
94
+ ```sh
95
+ cpflow update-github-actions
96
+ bin/test-cpflow-github-flow
97
+ ```
98
+
99
+ If `cpflow` is bundled by the app, use:
100
+
101
+ ```sh
102
+ bundle exec cpflow update-github-actions
103
+ bin/test-cpflow-github-flow bundle exec cpflow
104
+ ```
105
+
91
106
  Do not leave downstream apps pinned to a moving branch such as `main`. For a
92
107
  short-lived test of an unreleased upstream PR, pin to a full 40-character commit
93
108
  SHA and leave `CPFLOW_VERSION` unset:
@@ -101,6 +101,8 @@ task :release, %i[version dry_run override_version_policy] do |_t, args|
101
101
  puts "RELEASE COMPLETE"
102
102
  puts "Published cpflow #{released_gem_version} to RubyGems.org."
103
103
  end
104
+
105
+ Release.print_github_actions_update_reminder(released_gem_version)
104
106
  end
105
107
 
106
108
  desc("Compatibility alias for the old release task. Prefer `bundle exec rake release`.")
@@ -452,6 +454,21 @@ module Release
452
454
  abort "gem-release is required. Run `bundle install`.\n\n#{output}" unless status.success?
453
455
  end
454
456
 
457
+ def print_github_actions_update_reminder(version)
458
+ puts ""
459
+ puts "GitHub Actions follow-up:"
460
+ puts " Downstream repos using generated cpflow GitHub Actions must update their checked-in wrappers."
461
+ puts " Run this in each downstream repo after installing cpflow #{version}:"
462
+ puts ""
463
+ puts " cpflow update-github-actions"
464
+ puts " bin/test-cpflow-github-flow"
465
+ puts ""
466
+ puts " If cpflow is bundled in the app:"
467
+ puts ""
468
+ puts " bundle exec cpflow update-github-actions"
469
+ puts " bin/test-cpflow-github-flow bundle exec cpflow"
470
+ end
471
+
455
472
  def github_repo_slug(gem_root)
456
473
  origin_url, status = Open3.capture2e("git", "-C", gem_root, "remote", "get-url", "origin")
457
474
  abort "Unable to determine git origin URL.\n\n#{origin_url}" unless status.success?
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cpflow
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.2
4
+ version: 5.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Gordon
@@ -177,10 +177,12 @@ files:
177
177
  - lib/command/ps_wait.rb
178
178
  - lib/command/run.rb
179
179
  - lib/command/setup_app.rb
180
+ - lib/command/staging_branch_validation.rb
180
181
  - lib/command/terraform/base.rb
181
182
  - lib/command/terraform/generate.rb
182
183
  - lib/command/terraform/import.rb
183
184
  - lib/command/test.rb
185
+ - lib/command/update_github_actions.rb
184
186
  - lib/command/version.rb
185
187
  - lib/constants/exit_code.rb
186
188
  - lib/core/config.rb
@@ -268,6 +270,22 @@ licenses:
268
270
  - MIT
269
271
  metadata:
270
272
  rubygems_mfa_required: 'true'
273
+ post_install_message: |
274
+ cpflow 5.0.3 installed.
275
+
276
+ If this repository already uses generated cpflow GitHub Actions, update the
277
+ checked-in wrappers so GitHub loads the matching control-plane-flow release tag:
278
+
279
+ cpflow update-github-actions
280
+ bin/test-cpflow-github-flow
281
+
282
+ If you run cpflow through Bundler:
283
+
284
+ bundle exec cpflow update-github-actions
285
+ bin/test-cpflow-github-flow bundle exec cpflow
286
+
287
+ New repository? Run `cpflow generate-github-actions` first to create the
288
+ wrappers (and the `bin/test-cpflow-github-flow` script referenced above).
271
289
  rdoc_options: []
272
290
  require_paths:
273
291
  - lib