cpflow 5.3.0 → 6.0.0.rc.0

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/.agents/agent-workflow.yml +13 -0
  3. data/.agents/bin/README.md +12 -1
  4. data/.agents/bin/docs +1 -1
  5. data/.agents/bin/lint +1 -1
  6. data/.agents/bin/setup +1 -1
  7. data/.agents/bin/test +1 -1
  8. data/.agents/bin/validate +2 -1
  9. data/.coderabbit.yaml +13 -0
  10. data/.github/actions/cpflow-delete-control-plane-app/action.yml +2 -1
  11. data/.github/actions/cpflow-setup-environment/action.yml +8 -6
  12. data/.github/dependabot.yml +10 -0
  13. data/.github/workflows/check_cpln_links.yml +6 -1
  14. data/.github/workflows/claude-code-review.yml +5 -2
  15. data/.github/workflows/claude.yml +6 -4
  16. data/.github/workflows/coderabbit-lifecycle-review.yml +29 -0
  17. data/.github/workflows/command_docs.yml +7 -2
  18. data/.github/workflows/cpflow-cleanup-stale-review-apps.yml +12 -5
  19. data/.github/workflows/cpflow-delete-review-app.yml +18 -11
  20. data/.github/workflows/cpflow-deploy-review-app.yml +25 -16
  21. data/.github/workflows/cpflow-deploy-staging.yml +16 -10
  22. data/.github/workflows/cpflow-help-command.yml +3 -3
  23. data/.github/workflows/cpflow-promote-staging-to-production.yml +7 -7
  24. data/.github/workflows/cpflow-review-app-help.yml +1 -1
  25. data/.github/workflows/rspec-shared.yml +34 -26
  26. data/.github/workflows/rspec-specific.yml +10 -2
  27. data/.github/workflows/rspec.yml +71 -4
  28. data/.github/workflows/rubocop.yml +9 -2
  29. data/.github/workflows/trigger-docs-site.yml +2 -0
  30. data/.rubocop.yml +6 -1
  31. data/CHANGELOG.md +24 -1
  32. data/CONTRIBUTING.md +39 -2
  33. data/Gemfile.lock +1 -1
  34. data/README.md +6 -1
  35. data/cpflow.gemspec +3 -15
  36. data/docs/ai-github-flow-prompt.md +2 -2
  37. data/docs/ci-automation.md +112 -60
  38. data/docs/commands.md +18 -8
  39. data/docs/rds-private-networking.md +12 -12
  40. data/docs/releasing.md +15 -4
  41. data/examples/controlplane.yml +5 -0
  42. data/lib/command/ai_github_flow_prompt.rb +1 -1
  43. data/lib/command/cleanup_stale_apps.rb +28 -4
  44. data/lib/command/copy_image_from_upstream.rb +3 -0
  45. data/lib/command/deploy_image.rb +38 -1
  46. data/lib/command/generate_github_actions.rb +36 -12
  47. data/lib/command/run.rb +224 -34
  48. data/lib/command/update_github_actions.rb +7 -6
  49. data/lib/core/controlplane.rb +145 -78
  50. data/lib/core/shell.rb +35 -10
  51. data/lib/core/timed_command.rb +111 -0
  52. data/lib/cpflow/version.rb +1 -1
  53. data/lib/cpflow.rb +1 -1
  54. data/lib/github_flow_templates/.github/workflows/cpflow-promote-staging-to-production.yml +7 -7
  55. data/lib/github_flow_templates/bin/test-cpflow-github-flow +63 -3
  56. data/lib/patches/hash.rb +2 -2
  57. data/rakelib/create_release.rake +14 -14
  58. data/script/check_shell_scripts +66 -0
  59. metadata +11 -18
@@ -16,10 +16,70 @@ echo "==> parse generated GitHub Actions YAML"
16
16
  ruby <<'RUBY'
17
17
  require "yaml"
18
18
 
19
- Dir[".github/actions/**/action.yml", ".github/workflows/*.yml"].sort.each do |path|
20
- YAML.load_file(path, aliases: true)
19
+ documents = Dir[
20
+ ".github/actions/**/action.yml",
21
+ ".github/actions/**/action.yaml",
22
+ ".github/workflows/*.yml",
23
+ ".github/workflows/*.yaml"
24
+ ].sort.to_h do |path|
25
+ [path, YAML.load_file(path, aliases: true)]
26
+ end
27
+
28
+ documents.each_key do |path|
21
29
  puts "parsed #{path}"
22
30
  end
31
+
32
+ generated_local_action_pattern = %r{\A\./(?<directory>\.github/actions/cpflow-[a-z0-9]+(?:-[a-z0-9]+)*)\z}
33
+ local_action_references = []
34
+ invalid_local_action_references = []
35
+ visited_container_ids = {}
36
+ walk = lambda do |node, source_path|
37
+ if node.is_a?(Hash) || node.is_a?(Array)
38
+ next if visited_container_ids.key?(node.object_id)
39
+
40
+ visited_container_ids[node.object_id] = true
41
+ end
42
+
43
+ case node
44
+ when Hash
45
+ uses = node["uses"]
46
+ if uses.is_a?(String) && uses.start_with?("./.github/actions/cpflow-")
47
+ match = uses.match(generated_local_action_pattern)
48
+ if match
49
+ local_action_references << [source_path, match[:directory]]
50
+ else
51
+ invalid_local_action_references <<
52
+ "#{source_path} has invalid generated local action reference #{uses}"
53
+ end
54
+ end
55
+ node.each_value { |value| walk.call(value, source_path) }
56
+ when Array
57
+ node.each { |value| walk.call(value, source_path) }
58
+ end
59
+ end
60
+ documents.each { |path, document| walk.call(document, path) }
61
+
62
+ abort invalid_local_action_references.uniq.join("\n") unless invalid_local_action_references.empty?
63
+
64
+ missing_actions = local_action_references.uniq.filter_map do |source_path, action_directory|
65
+ directory_is_local = begin
66
+ File.lstat(action_directory).directory?
67
+ rescue Errno::ENOENT
68
+ false
69
+ end
70
+ descriptor_is_local = %w[action.yml action.yaml].any? do |filename|
71
+ begin
72
+ File.lstat(File.join(action_directory, filename)).file?
73
+ rescue Errno::ENOENT
74
+ false
75
+ end
76
+ end
77
+
78
+ "#{source_path} references missing local action #{action_directory}" unless directory_is_local && descriptor_is_local
79
+ end
80
+
81
+ abort missing_actions.join("\n") unless missing_actions.empty?
82
+ puts "all referenced local actions have checked-in descriptors"
23
83
  RUBY
24
84
 
25
85
  echo "==> check composite action input descriptions"
@@ -45,7 +105,7 @@ require "yaml"
45
105
  CONTROL_PLANE_FLOW_WORKFLOW = %r{\Ashakacode/control-plane-flow/\.github/workflows/[^@\s]+@([^\s]+)\z}
46
106
  PROMOTE_WORKFLOW = %r{\Ashakacode/control-plane-flow/\.github/workflows/cpflow-promote-staging-to-production\.yml@([^\s]+)\z}
47
107
  EXPECTED_PROMOTE_WORKFLOW_REF_FORMAT = "shakacode/control-plane-flow/.github/workflows/cpflow-promote-staging-to-production.yml@vX.Y.Z"
48
- EXPECTED_CPFLOW_CHECKOUT_ACTION = "actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd"
108
+ EXPECTED_CPFLOW_CHECKOUT_ACTION = "actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1"
49
109
  EXPECTED_CPFLOW_CHECKOUT_REPOSITORY = "shakacode/control-plane-flow"
50
110
 
51
111
  refs = Hash.new { |hash, key| hash[key] = [] }
data/lib/patches/hash.rb CHANGED
@@ -27,8 +27,8 @@ class Hash
27
27
  private
28
28
 
29
29
  # Copied from Rails
30
- def deep_transform_keys(&block)
31
- deep_transform_keys_in_object(self, &block)
30
+ def deep_transform_keys(&)
31
+ deep_transform_keys_in_object(self, &)
32
32
  end
33
33
 
34
34
  # Copied from Rails
@@ -22,8 +22,9 @@ desc("Releases the cpflow Ruby gem.
22
22
  3. Enter the RubyGems OTP when prompted.
23
23
 
24
24
  With no version argument, the task reads the latest versioned CHANGELOG.md
25
- header and uses it when it is newer than the current gem version. Otherwise,
26
- it falls back to a patch bump.
25
+ header and uses it when it is newer than or equal to the current gem version.
26
+ It falls back to a patch bump only when the current version is stable and the
27
+ changelog does not name the current or a newer version.
27
28
 
28
29
  1st argument: Version (optional). Supported values:
29
30
  patch, minor, major, 4.2.0, or 4.2.0.rc.1
@@ -205,10 +206,15 @@ module Release
205
206
 
206
207
  version = parse_gem_version_components(current_gem_version)
207
208
 
209
+ if version[:prerelease_type] && version_input.to_s.strip.casecmp?("patch")
210
+ abort <<~ERROR
211
+ Automatic patch bumps are not allowed from prerelease version #{current_gem_version}.
212
+ Pass an explicit version instead so a retry cannot accidentally promote a prerelease to stable.
213
+ ERROR
214
+ end
215
+
208
216
  case version_input.to_s.strip.downcase
209
217
  when "patch"
210
- return "#{version[:major]}.#{version[:minor]}.#{version[:patch]}" if version[:prerelease_type]
211
-
212
218
  "#{version[:major]}.#{version[:minor]}.#{version[:patch] + 1}"
213
219
  when "minor"
214
220
  "#{version[:major]}.#{version[:minor] + 1}.0"
@@ -269,10 +275,8 @@ module Release
269
275
  return changelog_version
270
276
  end
271
277
 
272
- if changelog_version &&
273
- Gem::Version.new(changelog_version) == Gem::Version.new(current_version) &&
274
- !version_tagged?(gem_root, changelog_version)
275
- puts "Found untagged CHANGELOG.md version: #{changelog_version} (current: #{current_version})"
278
+ if changelog_version && Gem::Version.new(changelog_version) == Gem::Version.new(current_version)
279
+ puts "Found current CHANGELOG.md version: #{changelog_version}"
276
280
  return changelog_version
277
281
  end
278
282
 
@@ -308,10 +312,6 @@ module Release
308
312
  tags_output.lines.map(&:strip).filter_map { |tag| parse_release_tag_to_gem_version(tag) }.uniq
309
313
  end
310
314
 
311
- def version_tagged?(gem_root, version)
312
- tagged_release_gem_versions(gem_root, fetch_tags: true).include?(version)
313
- end
314
-
315
315
  def version_bump_type(previous_stable_gem_version:, target_gem_version:)
316
316
  previous = parse_gem_version_components(previous_stable_gem_version)
317
317
  target = parse_gem_version_components(target_gem_version)
@@ -479,8 +479,8 @@ module Release
479
479
  match[:repo]
480
480
  end
481
481
 
482
- def capture_gh_output(*args)
483
- Open3.capture2e("gh", *args)
482
+ def capture_gh_output(*)
483
+ Open3.capture2e("gh", *)
484
484
  rescue Errno::ENOENT
485
485
  abort "GitHub CLI (`gh`) is not installed. Install it from https://cli.github.com/ and retry."
486
486
  end
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ repo_root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
5
+ cd "$repo_root"
6
+
7
+ if ! command -v shellcheck >/dev/null 2>&1; then
8
+ echo "ShellCheck is required; install it and ensure shellcheck is on PATH." >&2
9
+ exit 127
10
+ fi
11
+
12
+ # Add, rename, or remove extensionless shell entrypoints here. See .agents/bin/README.md.
13
+ extensionless_shell_files=(
14
+ .agents/bin/docs
15
+ .agents/bin/lint
16
+ .agents/bin/setup
17
+ .agents/bin/test
18
+ .agents/bin/validate
19
+ lib/github_flow_templates/bin/test-cpflow-github-flow
20
+ script/check_command_docs
21
+ script/check_cpln_links
22
+ script/check_shell_scripts
23
+ script/precommit/check_command_docs
24
+ script/precommit/check_cpln_links
25
+ script/precommit/check_trailing_newlines
26
+ script/precommit/get_changed_files
27
+ script/precommit/ruby_autofix
28
+ script/precommit/ruby_lint
29
+ spec/dummy/bin/dev
30
+ )
31
+
32
+ # A file preserves NUL-separated names while letting us check Git's exit status.
33
+ inventory_file=$(mktemp)
34
+ trap 'rm -f -- "$inventory_file"' EXIT
35
+ if ! git ls-files -z -- '*.sh' '*.bash' "${extensionless_shell_files[@]}" >"$inventory_file"; then
36
+ echo "Unable to read tracked shell inventory." >&2
37
+ exit 1
38
+ fi
39
+
40
+ shell_files=()
41
+ while IFS= read -r -d '' file; do
42
+ if [ ! -r "$file" ]; then
43
+ echo "Tracked shell script is not readable: $file" >&2
44
+ exit 1
45
+ fi
46
+ shell_files+=("$file")
47
+ done < "$inventory_file"
48
+
49
+ if [ "${#shell_files[@]}" -eq 0 ]; then
50
+ echo "No tracked shell scripts found." >&2
51
+ exit 1
52
+ fi
53
+
54
+ for file in "${extensionless_shell_files[@]}"; do
55
+ if [ ! -f "$file" ] || [ ! -r "$file" ]; then
56
+ echo "Declared shell entrypoint is not a readable file: $file" >&2
57
+ exit 1
58
+ fi
59
+ done
60
+
61
+ if ! git ls-files --error-unmatch -- "${extensionless_shell_files[@]}" >/dev/null; then
62
+ echo "Declared extensionless shell scripts must be tracked; update the inventory for renamed or removed files." >&2
63
+ exit 1
64
+ fi
65
+
66
+ shellcheck -- "${shell_files[@]}"
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.3.0
4
+ version: 6.0.0.rc.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Gordon
@@ -87,6 +87,7 @@ files:
87
87
  - ".agents/workflows/ai-rollout-e2e-test.md"
88
88
  - ".claude/commands/address-review.md"
89
89
  - ".claude/commands/update-changelog.md"
90
+ - ".coderabbit.yaml"
90
91
  - ".github/actions/cpflow-build-docker-image/action.yml"
91
92
  - ".github/actions/cpflow-delete-control-plane-app/action.yml"
92
93
  - ".github/actions/cpflow-delete-control-plane-app/delete-app.sh"
@@ -95,10 +96,12 @@ files:
95
96
  - ".github/actions/cpflow-setup-environment/action.yml"
96
97
  - ".github/actions/cpflow-validate-config/action.yml"
97
98
  - ".github/actions/cpflow-wait-for-health/action.yml"
99
+ - ".github/dependabot.yml"
98
100
  - ".github/pull_request_template.md"
99
101
  - ".github/workflows/check_cpln_links.yml"
100
102
  - ".github/workflows/claude-code-review.yml"
101
103
  - ".github/workflows/claude.yml"
104
+ - ".github/workflows/coderabbit-lifecycle-review.yml"
102
105
  - ".github/workflows/command_docs.yml"
103
106
  - ".github/workflows/cpflow-cleanup-stale-review-apps.yml"
104
107
  - ".github/workflows/cpflow-delete-review-app.yml"
@@ -247,6 +250,7 @@ files:
247
250
  - lib/core/terraform_config/workload/main.tf
248
251
  - lib/core/terraform_config/workload/required_providers.tf
249
252
  - lib/core/terraform_config/workload/variables.tf
253
+ - lib/core/timed_command.rb
250
254
  - lib/cpflow.rb
251
255
  - lib/cpflow/version.rb
252
256
  - lib/deprecated_commands.json
@@ -281,6 +285,7 @@ files:
281
285
  - script/add_command
282
286
  - script/check_command_docs
283
287
  - script/check_cpln_links
288
+ - script/check_shell_scripts
284
289
  - script/precommit/check_command_docs
285
290
  - script/precommit/check_cpln_links
286
291
  - script/precommit/check_trailing_newlines
@@ -304,21 +309,9 @@ licenses:
304
309
  metadata:
305
310
  rubygems_mfa_required: 'true'
306
311
  post_install_message: |
307
- cpflow 5.3.0 installed.
308
-
309
- If this repository already uses generated cpflow GitHub Actions, update the
310
- checked-in wrappers so GitHub loads the matching control-plane-flow release tag:
311
-
312
- cpflow update-github-actions
313
- bin/test-cpflow-github-flow
314
-
315
- If you run cpflow through Bundler:
316
-
317
- bundle exec cpflow update-github-actions
318
- bin/test-cpflow-github-flow bundle exec cpflow
319
-
320
- New repository? Run `cpflow generate-github-actions` first to create the
321
- wrappers (and the `bin/test-cpflow-github-flow` script referenced above).
312
+ cpflow 6.0.0.rc.0 installed.
313
+ Generated GitHub Actions users: run `cpflow update-github-actions` after upgrading.
314
+ Docs: https://github.com/shakacode/control-plane-flow/blob/main/docs/ci-automation.md#updating-generated-github-actions-after-gem-updates
322
315
  rdoc_options: []
323
316
  require_paths:
324
317
  - lib
@@ -326,14 +319,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
326
319
  requirements:
327
320
  - - ">="
328
321
  - !ruby/object:Gem::Version
329
- version: 3.0.0
322
+ version: '3.2'
330
323
  required_rubygems_version: !ruby/object:Gem::Requirement
331
324
  requirements:
332
325
  - - ">="
333
326
  - !ruby/object:Gem::Version
334
327
  version: '0'
335
328
  requirements: []
336
- rubygems_version: 4.0.8
329
+ rubygems_version: 4.0.18
337
330
  specification_version: 4
338
331
  summary: Control Plane Flow
339
332
  test_files: []