cpflow 4.1.1 → 5.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.
- checksums.yaml +4 -4
- data/.claude/commands/update-changelog.md +367 -0
- data/.github/workflows/claude-code-review.yml +44 -0
- data/.github/workflows/claude.yml +55 -0
- data/.gitignore +2 -0
- data/.overcommit.yml +43 -3
- data/.rubocop.yml +3 -3
- data/CHANGELOG.md +39 -3
- data/CONTRIBUTING.md +6 -0
- data/Gemfile +8 -7
- data/Gemfile.lock +93 -73
- data/README.md +53 -22
- data/cpflow.gemspec +5 -5
- data/docs/ai-github-flow-prompt.md +61 -0
- data/docs/ci-automation.md +335 -0
- data/docs/commands.md +70 -5
- data/docs/releasing.md +153 -0
- data/lib/command/ai_github_flow_prompt.rb +47 -0
- data/lib/command/base.rb +14 -0
- data/lib/command/cleanup_images.rb +1 -1
- data/lib/command/cleanup_stale_apps.rb +1 -1
- data/lib/command/copy_image_from_upstream.rb +14 -3
- data/lib/command/exists.rb +13 -2
- data/lib/command/generate.rb +153 -4
- data/lib/command/generate_github_actions.rb +170 -0
- data/lib/command/generator_helpers.rb +31 -0
- data/lib/command/github_flow_readiness.rb +37 -0
- data/lib/command/ps_wait.rb +5 -1
- data/lib/command/run.rb +4 -21
- data/lib/command/terraform/generate.rb +1 -0
- data/lib/command/version.rb +1 -0
- data/lib/constants/exit_code.rb +1 -0
- data/lib/core/config.rb +1 -1
- data/lib/core/controlplane.rb +13 -10
- data/lib/core/controlplane_api_direct.rb +25 -3
- data/lib/core/github_flow_readiness/checks.rb +143 -0
- data/lib/core/github_flow_readiness_service.rb +453 -0
- data/lib/core/repo_introspection.rb +118 -0
- data/lib/core/terraform_config/dsl.rb +1 -1
- data/lib/core/terraform_config/local_variable.rb +1 -1
- data/lib/cpflow/version.rb +1 -1
- data/lib/cpflow.rb +66 -3
- data/lib/generator_templates/Dockerfile +59 -3
- data/lib/generator_templates/controlplane.yml +27 -39
- data/lib/generator_templates/entrypoint.sh +1 -1
- data/lib/generator_templates/release_script.sh +23 -0
- data/lib/generator_templates/templates/app.yml +5 -8
- data/lib/generator_templates/templates/rails.yml +2 -11
- data/lib/generator_templates_sqlite/controlplane.yml +46 -0
- data/lib/generator_templates_sqlite/release_script.sh +25 -0
- data/lib/generator_templates_sqlite/templates/app.yml +15 -0
- data/lib/generator_templates_sqlite/templates/db.yml +6 -0
- data/lib/generator_templates_sqlite/templates/rails.yml +32 -0
- data/lib/generator_templates_sqlite/templates/storage.yml +6 -0
- data/lib/github_flow_templates/.github/actions/cpflow-build-docker-image/action.yml +131 -0
- data/lib/github_flow_templates/.github/actions/cpflow-delete-control-plane-app/action.yml +24 -0
- data/lib/github_flow_templates/.github/actions/cpflow-delete-control-plane-app/delete-app.sh +50 -0
- data/lib/github_flow_templates/.github/actions/cpflow-detect-release-phase/action.yml +62 -0
- data/lib/github_flow_templates/.github/actions/cpflow-setup-environment/action.yml +98 -0
- data/lib/github_flow_templates/.github/actions/cpflow-validate-config/action.yml +85 -0
- data/lib/github_flow_templates/.github/actions/cpflow-wait-for-health/action.yml +92 -0
- data/lib/github_flow_templates/.github/cpflow-help.md +47 -0
- data/lib/github_flow_templates/.github/workflows/cpflow-cleanup-stale-review-apps.yml +56 -0
- data/lib/github_flow_templates/.github/workflows/cpflow-delete-review-app.yml +142 -0
- data/lib/github_flow_templates/.github/workflows/cpflow-deploy-review-app.yml +445 -0
- data/lib/github_flow_templates/.github/workflows/cpflow-deploy-staging.yml +140 -0
- data/lib/github_flow_templates/.github/workflows/cpflow-help-command.yml +53 -0
- data/lib/github_flow_templates/.github/workflows/cpflow-promote-staging-to-production.yml +490 -0
- data/lib/github_flow_templates/.github/workflows/cpflow-review-app-help.yml +46 -0
- data/rakelib/create_release.rake +662 -37
- data/script/check_command_docs +4 -2
- data/script/check_cpln_links +25 -11
- data/script/precommit/check_command_docs +22 -0
- data/script/precommit/check_cpln_links +21 -0
- data/script/precommit/check_trailing_newlines +68 -0
- data/script/precommit/get_changed_files +49 -0
- data/script/precommit/ruby_autofix +52 -0
- data/script/precommit/ruby_lint +33 -0
- metadata +56 -15
- /data/docs/{migrating.md → migrating-heroku-to-control-plane.md} +0 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# GitHub Actions Flow for Review Apps, Staging, and Production
|
|
2
|
+
|
|
3
|
+
This document describes the reusable GitHub Actions scaffolding generated by `cpflow generate-github-actions`.
|
|
4
|
+
|
|
5
|
+
The goal is to bring the Heroku Flow model into any `cpflow` project:
|
|
6
|
+
|
|
7
|
+
1. Comment `/deploy-review-app` on a pull request to create or update a review app.
|
|
8
|
+
2. Push more commits to the PR to auto-redeploy that review app.
|
|
9
|
+
3. Push to the staging branch to auto-deploy staging.
|
|
10
|
+
4. Promote the already-built staging artifact to production from the Actions tab.
|
|
11
|
+
5. Let a nightly workflow clean up stale review apps.
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
End-to-end rollout in one view:
|
|
16
|
+
|
|
17
|
+
1. `cpflow github-flow-readiness` — exits non-zero if the repo is not ready to deploy.
|
|
18
|
+
2. `cpflow generate` — creates `.controlplane/` if missing.
|
|
19
|
+
3. `cpflow generate-github-actions` — adds the `cpflow-*` composite actions and workflows.
|
|
20
|
+
4. Configure the GitHub [repository secrets and variables](#required-github-repository-settings) the workflows expect.
|
|
21
|
+
5. Push the branch, then comment `/deploy-review-app` on a PR to spin up a review environment.
|
|
22
|
+
|
|
23
|
+
See [Bootstrap a Project](#bootstrap-a-project) for command details, [Repo Readiness Checklist](#repo-readiness-checklist) for what "ready" means, and [AI Playbook](#ai-playbook) to run the rollout through an agent.
|
|
24
|
+
|
|
25
|
+
## Bootstrap a Project
|
|
26
|
+
|
|
27
|
+
Run these commands from the project root:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
# Check the repo for common rollout blockers before generating files
|
|
31
|
+
cpflow github-flow-readiness
|
|
32
|
+
|
|
33
|
+
# Print the current AI rollout prompt for this repo, if you want to hand it to an agent
|
|
34
|
+
cpflow ai-github-flow-prompt
|
|
35
|
+
|
|
36
|
+
# Create .controlplane/ if it does not exist yet
|
|
37
|
+
cpflow generate
|
|
38
|
+
|
|
39
|
+
# Add reusable GitHub Actions for the Control Plane flow
|
|
40
|
+
cpflow generate-github-actions
|
|
41
|
+
|
|
42
|
+
# Or, run this instead when staging should trigger from a branch other than main/master:
|
|
43
|
+
cpflow generate-github-actions --staging-branch develop
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
These local bootstrap commands do not require `cpln` to be installed yet. Install and
|
|
47
|
+
log into the Control Plane CLI before any command that talks to the real platform.
|
|
48
|
+
`cpflow github-flow-readiness` is the fastest gate: it exits non-zero when the repo is
|
|
49
|
+
missing a production Dockerfile, missing Rails runtime files, pinned to a legacy
|
|
50
|
+
Ruby or Bundler toolchain, or depends on exact-pinned gem or npm versions that do
|
|
51
|
+
not appear to exist in the public registries.
|
|
52
|
+
|
|
53
|
+
The second command writes namespaced files so they can coexist with an app's existing CI:
|
|
54
|
+
|
|
55
|
+
- `.github/actions/cpflow-build-docker-image/action.yml`
|
|
56
|
+
- `.github/actions/cpflow-delete-control-plane-app/action.yml`
|
|
57
|
+
- `.github/actions/cpflow-delete-control-plane-app/delete-app.sh`
|
|
58
|
+
- `.github/actions/cpflow-setup-environment/action.yml`
|
|
59
|
+
- `.github/workflows/cpflow-review-app-help.yml`
|
|
60
|
+
- `.github/workflows/cpflow-help-command.yml`
|
|
61
|
+
- `.github/workflows/cpflow-deploy-review-app.yml`
|
|
62
|
+
- `.github/workflows/cpflow-delete-review-app.yml`
|
|
63
|
+
- `.github/workflows/cpflow-deploy-staging.yml`
|
|
64
|
+
- `.github/workflows/cpflow-promote-staging-to-production.yml`
|
|
65
|
+
- `.github/workflows/cpflow-cleanup-stale-review-apps.yml`
|
|
66
|
+
|
|
67
|
+
`cpflow generate` also infers the app prefix from the repo directory, infers the
|
|
68
|
+
Docker base Ruby version from `.ruby-version`, `.tool-versions`, or the app's
|
|
69
|
+
`Gemfile`, preserves repo-defined frontend precompile hooks such as Shakapacker
|
|
70
|
+
`precompile_hook` commands or React on Rails auto bundle generation, and
|
|
71
|
+
switches to persistent SQLite `db` and `storage` templates when
|
|
72
|
+
`config/database.yml` shows SQLite in production.
|
|
73
|
+
|
|
74
|
+
`cpflow github-flow-readiness` checks public RubyGems and npm registry metadata
|
|
75
|
+
for exact-pinned direct dependencies. In air-gapped or egress-restricted
|
|
76
|
+
environments those checks may report `unknown` instead of failing hard; confirm
|
|
77
|
+
the deployment runner has the package access your app needs before rollout.
|
|
78
|
+
|
|
79
|
+
## Repo Readiness Checklist
|
|
80
|
+
|
|
81
|
+
Before generating this flow, confirm that the target repository is already a
|
|
82
|
+
deployable application rather than a partial sample:
|
|
83
|
+
|
|
84
|
+
- the repo can be cloned and installed from scratch with published gem and npm
|
|
85
|
+
package versions
|
|
86
|
+
- the repo does not depend on unpublished or inaccessible package versions unless
|
|
87
|
+
the deployment flow also provisions the credentials needed to fetch them
|
|
88
|
+
- the repo is not just a historical generator snapshot pinned to an obsolete
|
|
89
|
+
Ruby or Bundler toolchain with no validated production build path
|
|
90
|
+
- the app has its real runtime scaffold checked in, for example a complete Rails
|
|
91
|
+
app with the boot files needed to run `bin/rails` and `bin/dev`
|
|
92
|
+
- the repo root maps to one deployable app; multi-app monorepos need a separate
|
|
93
|
+
rollout decision before using this one-app-per-repo flow
|
|
94
|
+
- the production Dockerfile can build the app's assets and any SSR or renderer
|
|
95
|
+
bundles that production needs
|
|
96
|
+
- any repo-defined frontend codegen or precompile hooks are preserved before
|
|
97
|
+
`rails assets:precompile`
|
|
98
|
+
- the runtime workloads, release command, and required secrets are known well
|
|
99
|
+
enough to model in `.controlplane/`
|
|
100
|
+
|
|
101
|
+
If any of those fail, stop and fix the application first. Do not merge
|
|
102
|
+
`cpflow-*` workflows into a repository that is not yet runnable from a clean
|
|
103
|
+
clone, because the result will be a misleading "deployment flow" for an app that
|
|
104
|
+
still cannot build or boot.
|
|
105
|
+
|
|
106
|
+
## Required `.controlplane/controlplane.yml` Structure
|
|
107
|
+
|
|
108
|
+
The generated workflows assume that `.controlplane/controlplane.yml` defines:
|
|
109
|
+
|
|
110
|
+
- one staging app
|
|
111
|
+
- one review-app prefix with `match_if_app_name_starts_with: true`
|
|
112
|
+
- one production app with `upstream` pointing to staging
|
|
113
|
+
|
|
114
|
+
Typical shape:
|
|
115
|
+
|
|
116
|
+
```yaml
|
|
117
|
+
aliases:
|
|
118
|
+
common: &common
|
|
119
|
+
cpln_org: my-org-staging
|
|
120
|
+
default_location: aws-us-east-2
|
|
121
|
+
setup_app_templates:
|
|
122
|
+
- app
|
|
123
|
+
- postgres
|
|
124
|
+
- redis
|
|
125
|
+
- rails
|
|
126
|
+
app_workloads:
|
|
127
|
+
- rails
|
|
128
|
+
additional_workloads:
|
|
129
|
+
- postgres
|
|
130
|
+
- redis
|
|
131
|
+
|
|
132
|
+
apps:
|
|
133
|
+
my-app-staging:
|
|
134
|
+
<<: *common
|
|
135
|
+
|
|
136
|
+
my-app-review:
|
|
137
|
+
<<: *common
|
|
138
|
+
match_if_app_name_starts_with: true
|
|
139
|
+
hooks:
|
|
140
|
+
post_creation: bundle exec rails db:prepare
|
|
141
|
+
pre_deletion: bundle exec rails db:drop
|
|
142
|
+
|
|
143
|
+
my-app-production:
|
|
144
|
+
<<: *common
|
|
145
|
+
allow_org_override_by_env: false
|
|
146
|
+
allow_app_override_by_env: false
|
|
147
|
+
cpln_org: my-org-production
|
|
148
|
+
upstream: my-app-staging
|
|
149
|
+
release_script: release_script.sh
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Important points:
|
|
153
|
+
|
|
154
|
+
- `REVIEW_APP_PREFIX` in GitHub Actions must match the review config key prefix, for example `my-app-review`.
|
|
155
|
+
- `match_if_app_name_starts_with: true` is what allows a single config entry to back `my-app-review-123`, `my-app-review-456`, and cleanup commands like `cpflow cleanup-stale-apps -a my-app-review`.
|
|
156
|
+
- `upstream: my-app-staging` is what lets the production promotion workflow copy the exact staging artifact.
|
|
157
|
+
- If your main web workload is not named `rails`, set the optional `PRIMARY_WORKLOAD` repository variable described below.
|
|
158
|
+
|
|
159
|
+
## Required GitHub Repository Settings
|
|
160
|
+
|
|
161
|
+
Configure these repository secrets:
|
|
162
|
+
|
|
163
|
+
- `CPLN_TOKEN_STAGING`: token for the staging Control Plane org
|
|
164
|
+
- `CPLN_TOKEN_PRODUCTION`: token for the production Control Plane org
|
|
165
|
+
|
|
166
|
+
Configure these repository variables:
|
|
167
|
+
|
|
168
|
+
- `CPLN_ORG_STAGING`: staging org name, for example `company-staging`
|
|
169
|
+
- `CPLN_ORG_PRODUCTION`: production org name, for example `company-production`
|
|
170
|
+
- `STAGING_APP_NAME`: staging GVC name, for example `my-app-staging`
|
|
171
|
+
- `PRODUCTION_APP_NAME`: production GVC name, for example `my-app-production`
|
|
172
|
+
- `REVIEW_APP_PREFIX`: review-app prefix, for example `my-app-review`
|
|
173
|
+
- `STAGING_APP_BRANCH`: optional branch that auto-deploys staging. If you use a custom branch, either pass it to `cpflow generate-github-actions --staging-branch BRANCH` during generation or edit `cpflow-deploy-staging.yml` so its `on.push.branches` list includes the same branch.
|
|
174
|
+
- `PRIMARY_WORKLOAD`: optional workload name used to discover the public endpoint and do production health checks; defaults to `rails`
|
|
175
|
+
- `DOCKER_BUILD_EXTRA_ARGS`: optional newline-delimited single `docker build` tokens passed through to `cpflow build-image`, for example `--build-arg=FOO=bar` or `--secret=id=npmrc,src=.npmrc`
|
|
176
|
+
- `DOCKER_BUILD_SSH_KNOWN_HOSTS`: optional multi-line `known_hosts` content used with `DOCKER_BUILD_SSH_KEY` when the build needs SSH access to hosts other than GitHub.com
|
|
177
|
+
|
|
178
|
+
Recommended org layout:
|
|
179
|
+
|
|
180
|
+
- keep review apps and staging in a staging org that developers can access
|
|
181
|
+
- keep production in a separate org with tighter access controls
|
|
182
|
+
|
|
183
|
+
Optional repository secret for private dependency builds:
|
|
184
|
+
|
|
185
|
+
- `DOCKER_BUILD_SSH_KEY`: private SSH key used when the Dockerfile needs `RUN --mount=type=ssh` to fetch private GitHub dependencies during image build
|
|
186
|
+
|
|
187
|
+
## Docker Builds with Private Dependencies
|
|
188
|
+
|
|
189
|
+
Some apps need extra Docker build configuration before the generated workflows are turnkey. Common examples are:
|
|
190
|
+
|
|
191
|
+
- `pnpm`, `npm`, `yarn`, or Bundler dependencies pulled from private GitHub repositories
|
|
192
|
+
- Dockerfiles that already use `RUN --mount=type=ssh`
|
|
193
|
+
- builds that need extra `--build-arg`, `--secret`, or related `docker build` flags
|
|
194
|
+
|
|
195
|
+
The generated `cpflow-build-docker-image` action supports this without hardcoding app-specific logic:
|
|
196
|
+
|
|
197
|
+
- set `DOCKER_BUILD_SSH_KEY` if the Docker build needs SSH access to GitHub
|
|
198
|
+
- optionally set `DOCKER_BUILD_SSH_KNOWN_HOSTS` when the SSH build host is not GitHub.com or you need custom host entries
|
|
199
|
+
- set `DOCKER_BUILD_EXTRA_ARGS` when you need extra `docker build` flags
|
|
200
|
+
|
|
201
|
+
For example, a repo that installs private dependencies from GitHub during Docker build can set:
|
|
202
|
+
|
|
203
|
+
```text
|
|
204
|
+
DOCKER_BUILD_SSH_KEY=<private deploy key secret>
|
|
205
|
+
DOCKER_BUILD_SSH_KNOWN_HOSTS=git.example.com ssh-ed25519 AAAA...
|
|
206
|
+
DOCKER_BUILD_EXTRA_ARGS=--build-arg=BUNDLE_WITHOUT=development:test
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
The action will start an SSH agent, add the key, write `known_hosts`, and pass `--ssh=default` to `cpflow build-image`. When `DOCKER_BUILD_SSH_KNOWN_HOSTS` is unset, the generated action uses pinned GitHub.com host keys by default. If your Dockerfile relies on `RUN --mount=type=ssh`, validate the build locally with `cpflow build-image -a <app> --ssh=default` before relying on CI.
|
|
210
|
+
|
|
211
|
+
## Generated Workflow Behavior
|
|
212
|
+
|
|
213
|
+
`cpflow-review-app-help.yml`
|
|
214
|
+
|
|
215
|
+
- Posts a quick reference when a pull request opens, including on fork-based PRs.
|
|
216
|
+
|
|
217
|
+
`cpflow-help-command.yml`
|
|
218
|
+
|
|
219
|
+
- Replies to `/help` on a pull request with the commands and required repo settings.
|
|
220
|
+
|
|
221
|
+
`cpflow-deploy-review-app.yml`
|
|
222
|
+
|
|
223
|
+
- Creates a review app when someone comments `/deploy-review-app`.
|
|
224
|
+
- Redeploys an existing review app automatically on later PR pushes.
|
|
225
|
+
- Creates a GitHub deployment and comments with the review URL and logs.
|
|
226
|
+
- Leaves PR pushes alone until the first review app is explicitly requested, which keeps demo-app costs down.
|
|
227
|
+
- Accepts `/deploy-review-app` only from trusted commenters (`OWNER`, `MEMBER`, or `COLLABORATOR`).
|
|
228
|
+
- Skips fork-based PR deploys because the workflow builds Docker images with repository secrets.
|
|
229
|
+
|
|
230
|
+
`cpflow-delete-review-app.yml`
|
|
231
|
+
|
|
232
|
+
- Deletes the review app on `/delete-review-app`.
|
|
233
|
+
- Also deletes it automatically when the pull request closes.
|
|
234
|
+
- Accepts `/delete-review-app` only from trusted commenters (`OWNER`, `MEMBER`, or `COLLABORATOR`).
|
|
235
|
+
|
|
236
|
+
`cpflow-deploy-staging.yml`
|
|
237
|
+
|
|
238
|
+
- Builds and deploys the staging app on pushes to the generated staging branch filter.
|
|
239
|
+
- Falls back to `main` or `master` when `STAGING_APP_BRANCH` is unset and no custom branch was generated.
|
|
240
|
+
- Custom staging branches must be present in the workflow's `on.push.branches` filter; repository variables alone cannot trigger a branch GitHub Actions is not listening to.
|
|
241
|
+
- Fails fast when required staging repo settings are missing instead of surfacing opaque `cpflow` errors.
|
|
242
|
+
|
|
243
|
+
`cpflow-promote-staging-to-production.yml`
|
|
244
|
+
|
|
245
|
+
- Manually promotes the staging artifact to production with a confirmation input.
|
|
246
|
+
- Verifies that production has the env var names staging expects.
|
|
247
|
+
- Runs a health check against `PRIMARY_WORKLOAD`.
|
|
248
|
+
- Attempts a rollback of every configured application workload if the new production image does not come up healthy.
|
|
249
|
+
- Creates a GitHub release after a successful promotion.
|
|
250
|
+
|
|
251
|
+
`cpflow-cleanup-stale-review-apps.yml`
|
|
252
|
+
|
|
253
|
+
- Runs nightly and on demand.
|
|
254
|
+
- Deletes stale review apps using `cpflow cleanup-stale-apps`.
|
|
255
|
+
|
|
256
|
+
## Composite Actions
|
|
257
|
+
|
|
258
|
+
The generated workflows share these local composite actions:
|
|
259
|
+
|
|
260
|
+
- `cpflow-setup-environment`: installs Ruby, the Control Plane CLI, and the `cpflow` gem, then logs into the target org
|
|
261
|
+
- `cpflow-build-docker-image`: builds and pushes the app image with the desired commit SHA
|
|
262
|
+
- `cpflow-delete-control-plane-app`: safely deletes temporary apps and refuses to touch names outside the configured review-app prefix
|
|
263
|
+
|
|
264
|
+
## Applying This to React on Rails Demo Apps
|
|
265
|
+
|
|
266
|
+
This flow is a good fit for the React on Rails demo apps because they already follow the same basic assumptions:
|
|
267
|
+
|
|
268
|
+
- the deployable app is a Rails project
|
|
269
|
+
- the primary web workload is usually `rails`
|
|
270
|
+
- review environments should be temporary and opt-in
|
|
271
|
+
- staging should auto-follow a single branch
|
|
272
|
+
- production should promote the already-tested staging image
|
|
273
|
+
|
|
274
|
+
In practice, porting the flow into a demo app usually follows five phases.
|
|
275
|
+
|
|
276
|
+
**Before generating:**
|
|
277
|
+
|
|
278
|
+
1. Confirm the repo passes the readiness checklist above.
|
|
279
|
+
2. Generate `.controlplane/` if the app does not have it yet.
|
|
280
|
+
3. Generate the `cpflow-*` GitHub Actions files.
|
|
281
|
+
|
|
282
|
+
**Verify the generated scaffold:**
|
|
283
|
+
|
|
284
|
+
4. Update `.controlplane/controlplane.yml` with staging, review, and production entries.
|
|
285
|
+
5. Confirm that the generated Dockerfile picked a Ruby base image compatible with the app's declared Ruby requirement.
|
|
286
|
+
6. For SQLite-backed apps, confirm that the generated scaffold switched to persistent `db` and `storage` volumes, mounted them into the main workload, and added a release script that runs `rails db:prepare`.
|
|
287
|
+
|
|
288
|
+
**Adapt for the app's runtime:**
|
|
289
|
+
|
|
290
|
+
7. Keep Node available in the final app image whenever Rails asset compilation or SSR depends on ExecJS or frontend package managers at build or runtime.
|
|
291
|
+
8. Preserve repo-defined frontend precompile hooks, such as Shakapacker `precompile_hook` commands or React on Rails `config.auto_load_bundle = true`, before `rails assets:precompile`.
|
|
292
|
+
9. Add any additional app workloads the app needs at runtime, for example `sidekiq`, a Node renderer, or any other process type that should deploy the same application image.
|
|
293
|
+
10. Adjust `PRIMARY_WORKLOAD` only if the public workload is not named `rails`.
|
|
294
|
+
|
|
295
|
+
**Wire up GitHub secrets, variables, and private builds:**
|
|
296
|
+
|
|
297
|
+
11. Make sure the repo variables and secrets line up with the configured app names.
|
|
298
|
+
12. If the Dockerfile pulls private dependencies over SSH, configure `DOCKER_BUILD_SSH_KEY`, add `DOCKER_BUILD_SSH_KNOWN_HOSTS` when the host is not GitHub.com, and validate that the image can build with `RUN --mount=type=ssh`.
|
|
299
|
+
|
|
300
|
+
**Validate and push:**
|
|
301
|
+
|
|
302
|
+
13. Validate the real production Docker build before relying on the workflows, especially if asset compilation or SSR requires Node, extra system packages, multiple processes, extra Docker build flags, or persistent writable paths.
|
|
303
|
+
14. Expect review app deploys to run only for branches in the base repository; fork PRs still get help comments, but deploys are skipped because the workflow uses repository secrets.
|
|
304
|
+
|
|
305
|
+
## AI Playbook
|
|
306
|
+
|
|
307
|
+
If you want an AI agent to apply this flow to another project, start with
|
|
308
|
+
`cpflow github-flow-readiness`, then use the standalone
|
|
309
|
+
[AI rollout prompt](./ai-github-flow-prompt.md). It captures the exact wording,
|
|
310
|
+
hard stop conditions, and definition of done for this workflow. You can also
|
|
311
|
+
run `cpflow ai-github-flow-prompt` from inside the target repo to print the
|
|
312
|
+
current prompt with that repo's default app prefix already filled in.
|
|
313
|
+
|
|
314
|
+
Short version:
|
|
315
|
+
|
|
316
|
+
```text
|
|
317
|
+
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, with published package versions and a production Dockerfile that can really build the app. Stop and report blockers for unpublished packages, inaccessible private dependencies, legacy toolchains, or missing production build paths instead of generating workflows blindly. Then run `cpflow generate` if `.controlplane/` is missing, run `cpflow generate-github-actions`, adapt the generated scaffold to the real workloads, document the required GitHub secrets and variables, validate the real build path locally, push the branch, and check the GitHub Actions results.
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Expand that prompt with app-specific requirements before editing files:
|
|
321
|
+
|
|
322
|
+
- verify the repo is a real deployable app, not a partial code sample or a demo pinned to unpublished package versions
|
|
323
|
+
- stop and report a scope decision when the repo is a monorepo or contains multiple deployable apps without an already-decided single flow target
|
|
324
|
+
- inspect the production Dockerfile and make sure it can build the app's assets in CI
|
|
325
|
+
- make sure the generated Dockerfile uses a Ruby base image compatible with the app's declared Ruby requirement
|
|
326
|
+
- preserve repo-defined frontend precompile hooks, such as Shakapacker `precompile_hook` commands or React on Rails `config.auto_load_bundle = true`
|
|
327
|
+
- keep Node available in the final image if Rails or SSR depends on ExecJS, Yarn, or `pnpm` after the main `npm install` layer
|
|
328
|
+
- if `config/database.yml` shows SQLite in production, confirm that `cpflow generate` emitted persistent `db` and `storage` volumes plus a `rails db:prepare` release script; otherwise keep the default Postgres workload
|
|
329
|
+
- inspect the production Dockerfile and package sources for private GitHub dependencies, and wire `DOCKER_BUILD_SSH_KEY` plus `DOCKER_BUILD_SSH_KNOWN_HOSTS` when the build uses `RUN --mount=type=ssh` against non-GitHub hosts
|
|
330
|
+
- add extra `app_workloads` and template files for any runtime sidecars, workers, or renderer processes
|
|
331
|
+
- make sure any sidecar process exposed to sibling workloads binds to `0.0.0.0` instead of container-local `localhost`
|
|
332
|
+
- make sure sidecar caches or bundle directories live in writable paths for the runtime user, such as `tmp/`, instead of root-owned image paths
|
|
333
|
+
- keep workflow files generic and put app names, org names, branch names, and Docker build knobs in repository `vars` and `secrets`
|
|
334
|
+
|
|
335
|
+
When the agent applies this to a project, it should avoid hardcoding app names or org names into the workflow files. Those belong in repository `vars` and `secrets`.
|
data/docs/commands.md
CHANGED
|
@@ -11,6 +11,18 @@ This `-a` option is used in most of the commands and will pick all other app con
|
|
|
11
11
|
|
|
12
12
|
## Commands
|
|
13
13
|
|
|
14
|
+
### `ai-github-flow-prompt`
|
|
15
|
+
|
|
16
|
+
Prints a copy-paste prompt for an AI agent to roll out the reusable Control Plane GitHub Flow:
|
|
17
|
+
- verifies the repo is deployable from a clean clone before generating files
|
|
18
|
+
- scaffolds `.controlplane/` and `cpflow-*` GitHub Actions files when the repo qualifies
|
|
19
|
+
- stops on external blockers or product decisions instead of forcing a broken rollout
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
# Prints the recommended AI rollout prompt for the current repo
|
|
23
|
+
cpflow ai-github-flow-prompt
|
|
24
|
+
```
|
|
25
|
+
|
|
14
26
|
### `apply-template`
|
|
15
27
|
|
|
16
28
|
- Applies application-specific configs from templates (e.g., for every review-app)
|
|
@@ -93,13 +105,16 @@ cpflow config -a $APP_NAME
|
|
|
93
105
|
|
|
94
106
|
- Copies an image (by default the latest) from a source org to the current org
|
|
95
107
|
- The source app must be specified either through the `CPLN_UPSTREAM` env var or `upstream` in the `.controlplane/controlplane.yml` file
|
|
96
|
-
-
|
|
108
|
+
- The token for the source org must be provided through `--upstream-token`/`-t` or the `CPLN_UPSTREAM_TOKEN` env var
|
|
97
109
|
- A `cpln` profile will be temporarily created to pull the image from the source org
|
|
98
110
|
|
|
99
111
|
```sh
|
|
100
112
|
# Copies the latest image from the source org to the current org.
|
|
101
113
|
cpflow copy-image-from-upstream -a $APP_NAME --upstream-token $UPSTREAM_TOKEN
|
|
102
114
|
|
|
115
|
+
# Equivalent call using an env var (avoids exposing the token via the OS process table).
|
|
116
|
+
CPLN_UPSTREAM_TOKEN=$UPSTREAM_TOKEN cpflow copy-image-from-upstream -a $APP_NAME
|
|
117
|
+
|
|
103
118
|
# Copies a specific image from the source org to the current org.
|
|
104
119
|
cpflow copy-image-from-upstream -a $APP_NAME --upstream-token $UPSTREAM_TOKEN --image appimage:123
|
|
105
120
|
```
|
|
@@ -158,20 +173,66 @@ cpflow env -a $APP_NAME
|
|
|
158
173
|
### `exists`
|
|
159
174
|
|
|
160
175
|
- Shell-checks if an application (GVC) exists, useful in scripts, e.g.:
|
|
176
|
+
- Exits 0 when the app exists, 3 when it does not exist, and 64 for other errors.
|
|
161
177
|
|
|
162
178
|
```sh
|
|
163
|
-
|
|
179
|
+
cpflow exists -a "$APP_NAME"
|
|
180
|
+
status=$?
|
|
181
|
+
if [ "$status" -eq 0 ]; then
|
|
182
|
+
echo "exists"
|
|
183
|
+
elif [ "$status" -eq 3 ]; then
|
|
184
|
+
echo "not found"
|
|
185
|
+
else
|
|
186
|
+
echo "error: cpflow exists exited $status"
|
|
187
|
+
fi
|
|
164
188
|
```
|
|
165
189
|
|
|
166
190
|
### `generate`
|
|
167
191
|
|
|
168
|
-
Creates base Control Plane config and template files
|
|
192
|
+
Creates base Control Plane config and template files for a Rails project:
|
|
193
|
+
- infers the app prefix from the current directory and wires staging, review, and production entries
|
|
194
|
+
- infers the Docker base Ruby version from `.ruby-version`, `.tool-versions`, or the app's `Gemfile`
|
|
195
|
+
- preserves repo-defined asset precompile hooks, including React on Rails auto bundle generation
|
|
196
|
+
- detects SQLite in `config/database.yml` and generates persistent `db` and `storage` volume templates instead of the default Postgres workload
|
|
169
197
|
|
|
170
198
|
```sh
|
|
171
|
-
# Creates .controlplane directory with Control Plane config and
|
|
199
|
+
# Creates .controlplane directory with Control Plane config and starter templates
|
|
172
200
|
cpflow generate
|
|
173
201
|
```
|
|
174
202
|
|
|
203
|
+
### `generate-github-actions`
|
|
204
|
+
|
|
205
|
+
Creates GitHub Actions templates for a Heroku Flow style Control Plane pipeline:
|
|
206
|
+
- on-demand review apps for pull requests
|
|
207
|
+
- automatic staging deploys from your main branch
|
|
208
|
+
- manual promotion from staging to production
|
|
209
|
+
- nightly cleanup and PR help workflows
|
|
210
|
+
|
|
211
|
+
Pass `--staging-branch BRANCH` when staging should auto-deploy from a branch
|
|
212
|
+
other than `main` or `master`; the generator will bake that branch into the
|
|
213
|
+
GitHub Actions push trigger and use it as the default STAGING_APP_BRANCH.
|
|
214
|
+
|
|
215
|
+
```sh
|
|
216
|
+
# Creates .github/actions and .github/workflows files for the Control Plane flow
|
|
217
|
+
cpflow generate-github-actions
|
|
218
|
+
|
|
219
|
+
# Creates the flow with staging deploys triggered from develop
|
|
220
|
+
cpflow generate-github-actions --staging-branch develop
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### `github-flow-readiness`
|
|
224
|
+
|
|
225
|
+
Checks the current repository for common rollout blockers before adding the Control Plane GitHub flow:
|
|
226
|
+
- Rails runtime scaffold present
|
|
227
|
+
- modern Ruby and Bundler toolchain
|
|
228
|
+
- installable exact-pinned direct gem and npm package versions
|
|
229
|
+
- production Dockerfile presence and SQLite production hints
|
|
230
|
+
|
|
231
|
+
```sh
|
|
232
|
+
# Checks the current repo for common rollout blockers
|
|
233
|
+
cpflow github-flow-readiness
|
|
234
|
+
```
|
|
235
|
+
|
|
175
236
|
### `info`
|
|
176
237
|
|
|
177
238
|
- Displays the diff between defined/available apps/workloads (apps equal GVCs)
|
|
@@ -354,13 +415,17 @@ cpflow ps:stop -a $APP_NAME -w $WORKLOAD_NAME -r $REPLICA_NAME
|
|
|
354
415
|
### `ps:wait`
|
|
355
416
|
|
|
356
417
|
- Waits for workloads in app to be ready after re-deployment
|
|
418
|
+
- Use Unix timeout command to set a maximum wait time (e.g., `timeout 300 cpflow ps:wait ...`)
|
|
357
419
|
|
|
358
420
|
```sh
|
|
359
421
|
# Waits for all workloads in app.
|
|
360
422
|
cpflow ps:wait -a $APP_NAME
|
|
361
423
|
|
|
362
424
|
# Waits for a specific workload in app.
|
|
363
|
-
cpflow ps:
|
|
425
|
+
cpflow ps:wait -a $APP_NAME -w $WORKLOAD_NAME
|
|
426
|
+
|
|
427
|
+
# Waits for all workloads with a 5-minute timeout.
|
|
428
|
+
timeout 300 cpflow ps:wait -a $APP_NAME
|
|
364
429
|
```
|
|
365
430
|
|
|
366
431
|
### `run`
|
data/docs/releasing.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Releasing the Gem
|
|
2
|
+
|
|
3
|
+
This project follows a changelog-first Ruby gem release process, modeled after
|
|
4
|
+
the React on Rails release flow but without any npm publishing steps.
|
|
5
|
+
|
|
6
|
+
## Release Process
|
|
7
|
+
|
|
8
|
+
### 1. Update the Changelog
|
|
9
|
+
|
|
10
|
+
Always update `CHANGELOG.md` before running the release task.
|
|
11
|
+
|
|
12
|
+
1. Ensure all desired changes are merged to `main`.
|
|
13
|
+
2. Run `/update-changelog release` to find merged PRs, add entries under the
|
|
14
|
+
right headings, compute the next version, stamp the version header, update
|
|
15
|
+
compare links, and open a changelog PR. Use `/update-changelog rc`,
|
|
16
|
+
`/update-changelog beta`, or an explicit version like
|
|
17
|
+
`/update-changelog 4.2.0.rc.1` when preparing a prerelease or fixed target
|
|
18
|
+
version.
|
|
19
|
+
3. Review the generated changelog PR and verify the version number matches the
|
|
20
|
+
intended release level:
|
|
21
|
+
- Breaking changes: major
|
|
22
|
+
- Added features or enhancements: minor
|
|
23
|
+
- Fixes, improvements, deprecations, removals, or security updates: patch
|
|
24
|
+
4. Merge the changelog PR before releasing.
|
|
25
|
+
|
|
26
|
+
If updating the changelog manually, move the relevant `Unreleased` entries into
|
|
27
|
+
a versioned header:
|
|
28
|
+
|
|
29
|
+
```markdown
|
|
30
|
+
## [4.2.0] - 2026-05-05
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Then update the compare links at the bottom of `CHANGELOG.md`, including the
|
|
34
|
+
`Unreleased` link and the new version link, and merge those changelog changes
|
|
35
|
+
before releasing.
|
|
36
|
+
|
|
37
|
+
The release task reads the latest versioned `CHANGELOG.md` header and can create
|
|
38
|
+
the GitHub release from that section automatically.
|
|
39
|
+
|
|
40
|
+
### 2. Run the Release Task
|
|
41
|
+
|
|
42
|
+
The recommended command has no arguments:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
bundle exec rake release
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
With no arguments, `rake release`:
|
|
49
|
+
|
|
50
|
+
1. Reads the first versioned `CHANGELOG.md` header, such as `## [4.2.0]`.
|
|
51
|
+
2. Uses that version when it is newer than the current gem version.
|
|
52
|
+
3. Uses the current version if the changelog version matches the gem version
|
|
53
|
+
but has not been tagged yet.
|
|
54
|
+
4. Falls back to a patch bump if no new changelog version is found.
|
|
55
|
+
|
|
56
|
+
Other supported forms:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
bundle exec rake "release[patch]"
|
|
60
|
+
bundle exec rake "release[minor]"
|
|
61
|
+
bundle exec rake "release[major]"
|
|
62
|
+
bundle exec rake "release[4.2.0]"
|
|
63
|
+
bundle exec rake "release[4.2.0.rc.1]"
|
|
64
|
+
bundle exec rake "release[4.2.0,true]"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Use RubyGems version format for prereleases: `4.2.0.rc.1`, not
|
|
68
|
+
`4.2.0-rc.1`.
|
|
69
|
+
|
|
70
|
+
Full argument list:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
bundle exec rake "release[version,dry_run,override_version_policy]"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Environment variables:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
VERBOSE=1
|
|
80
|
+
RUBYGEMS_OTP=<code>
|
|
81
|
+
RELEASE_VERSION_POLICY_OVERRIDE=true
|
|
82
|
+
GEM_RELEASE_MAX_RETRIES=<n>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 3. What the Task Does
|
|
86
|
+
|
|
87
|
+
`bundle exec rake release` performs the gem-only release:
|
|
88
|
+
|
|
89
|
+
1. Requires a clean working tree.
|
|
90
|
+
2. Verifies `gem-release` is available through Bundler.
|
|
91
|
+
3. For real releases, verifies GitHub CLI authentication and write access.
|
|
92
|
+
4. Pulls the latest changes with `git pull --rebase`.
|
|
93
|
+
5. Resolves the target version from the changelog or explicit argument.
|
|
94
|
+
6. Requires stable releases to run from `main`; prereleases may run from another
|
|
95
|
+
branch.
|
|
96
|
+
7. Validates the target version is newer than the latest tag and is consistent
|
|
97
|
+
with the changelog section when the section indicates a bump level.
|
|
98
|
+
8. Bumps `lib/cpflow/version.rb` and updates `Gemfile.lock`.
|
|
99
|
+
9. Commits the version bump, tags `vX.Y.Z`, and pushes the commit and tags.
|
|
100
|
+
10. Publishes the `cpflow` gem to RubyGems.org.
|
|
101
|
+
11. Creates or updates the GitHub release from the matching changelog section.
|
|
102
|
+
|
|
103
|
+
The older `bundle exec rake "create_release[4.2.0,false]"` task name remains as
|
|
104
|
+
a compatibility alias, but new releases should use `bundle exec rake release`.
|
|
105
|
+
|
|
106
|
+
### 4. Sync GitHub Release Notes Manually
|
|
107
|
+
|
|
108
|
+
If the GitHub release was not created automatically, update and commit the
|
|
109
|
+
matching changelog section, then run:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
bundle exec rake "sync_github_release[4.2.0]"
|
|
113
|
+
bundle exec rake "sync_github_release[4.2.0,true]"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`sync_github_release` reads notes from `CHANGELOG.md` and creates or updates the
|
|
117
|
+
GitHub release for the corresponding `vX.Y.Z` tag.
|
|
118
|
+
|
|
119
|
+
## Pre-Release Checklist
|
|
120
|
+
|
|
121
|
+
Before running the release:
|
|
122
|
+
|
|
123
|
+
1. `git checkout main`
|
|
124
|
+
2. `git pull --rebase`
|
|
125
|
+
3. `bundle install`
|
|
126
|
+
4. `gh auth status`
|
|
127
|
+
5. Confirm RubyGems credentials can publish `cpflow`.
|
|
128
|
+
6. Confirm `CHANGELOG.md` has a committed section for the target version.
|
|
129
|
+
7. Run a dry run:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
bundle exec rake "release[4.2.0,true]"
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## If a Release Fails
|
|
136
|
+
|
|
137
|
+
Check what was published before retrying:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
gem list cpflow -r -a
|
|
141
|
+
gh release view v4.2.0
|
|
142
|
+
git tag -l v4.2.0
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
If the gem was published but GitHub release creation failed, fix GitHub CLI
|
|
146
|
+
authentication or permissions and run:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
bundle exec rake "sync_github_release[4.2.0]"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
If the tag was pushed but the gem was not published, delete or correct the tag
|
|
153
|
+
and version commit intentionally before trying again.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../core/repo_introspection"
|
|
4
|
+
|
|
5
|
+
module Command
|
|
6
|
+
class AiGithubFlowPrompt < Base
|
|
7
|
+
NAME = "ai-github-flow-prompt"
|
|
8
|
+
DESCRIPTION = "Prints the recommended AI prompt for adding the Control Plane GitHub Flow to a repo"
|
|
9
|
+
LONG_DESCRIPTION = <<~DESC
|
|
10
|
+
Prints a copy-paste prompt for an AI agent to roll out the reusable Control Plane GitHub Flow:
|
|
11
|
+
- verifies the repo is deployable from a clean clone before generating files
|
|
12
|
+
- scaffolds `.controlplane/` and `cpflow-*` GitHub Actions files when the repo qualifies
|
|
13
|
+
- stops on external blockers or product decisions instead of forcing a broken rollout
|
|
14
|
+
DESC
|
|
15
|
+
EXAMPLES = <<~EX
|
|
16
|
+
```sh
|
|
17
|
+
# Prints the recommended AI rollout prompt for the current repo
|
|
18
|
+
cpflow ai-github-flow-prompt
|
|
19
|
+
```
|
|
20
|
+
EX
|
|
21
|
+
WITH_INFO_HEADER = false
|
|
22
|
+
VALIDATIONS = [].freeze
|
|
23
|
+
REQUIRES_STARTUP_CHECKS = false
|
|
24
|
+
|
|
25
|
+
def call
|
|
26
|
+
puts prompt
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def prompt
|
|
32
|
+
<<~PROMPT
|
|
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
|
+
|
|
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 `/deploy-review-app`, 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.
|
|
36
|
+
|
|
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
|
+
|
|
39
|
+
Run the real local validations you can: Docker build if feasible, repo tests or smoke checks, YAML validation, and any CI-equivalent build steps. Push the branch and check the GitHub Actions results. Only stop early for a real external blocker or a product decision that changes scope.
|
|
40
|
+
PROMPT
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def inferred_app_prefix
|
|
44
|
+
RepoIntrospection.inferred_app_prefix(Dir.pwd)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|