cpflow 6.0.0.rc.0 → 6.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.agents/agent-workflow.yml +16 -38
- data/.agents/bin/README.md +7 -8
- data/.agents/legacy-workflow-policy.yml +11 -0
- data/.agents/shaka.md +20 -0
- data/.agents/trusted-actions.yml +12 -0
- data/.coderabbit.yaml +1 -0
- data/.github/actions/cpflow-build-docker-image/action.yml +153 -22
- data/.github/actions/cpflow-delete-control-plane-app/delete-app.sh +3 -0
- data/.github/actions/cpflow-preview-promotion/action.yml +82 -0
- data/.github/actions/cpflow-preview-promotion/preview-promotion.sh +90 -0
- data/.github/workflows/cpflow-cleanup-stale-review-apps.yml +3 -10
- data/.github/workflows/cpflow-delete-review-app.yml +4 -11
- data/.github/workflows/cpflow-deploy-review-app.yml +19 -14
- data/.github/workflows/cpflow-deploy-staging.yml +17 -12
- data/.github/workflows/cpflow-promote-staging-to-production.yml +132 -13
- data/.github/workflows/rspec.yml +24 -0
- data/AGENTS.md +18 -3
- data/CHANGELOG.md +26 -3
- data/CONTRIBUTING.md +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +5 -2
- data/docs/ai-github-flow-prompt.md +104 -8
- data/docs/ci-automation.md +258 -60
- data/docs/commands.md +32 -16
- data/docs/postgres-review-apps.md +90 -0
- data/docs/postgres.md +233 -375
- data/docs/secrets-and-env-values.md +42 -0
- data/docs/sidebars.ts +1 -0
- data/lib/command/ai_github_flow_prompt.rb +10 -13
- data/lib/command/ai_github_flow_prompt.txt +103 -0
- data/lib/command/apply_template.rb +36 -4
- data/lib/command/base.rb +56 -3
- data/lib/command/delete.rb +174 -4
- data/lib/command/deploy_image.rb +6 -0
- data/lib/command/generate.rb +247 -3
- data/lib/command/generate_github_actions.rb +15 -17
- data/lib/command/setup_app.rb +92 -10
- data/lib/command/update_github_actions.rb +107 -43
- data/lib/core/config.rb +73 -0
- data/lib/core/controlplane.rb +22 -2
- data/lib/core/controlplane_api.rb +28 -2
- data/lib/core/controlplane_api_direct.rb +17 -0
- data/lib/core/disposable_postgres_helpers.rb +167 -0
- data/lib/core/doctor_service.rb +7 -0
- data/lib/core/github_flow_readiness/checks.rb +2 -1
- data/lib/core/github_flow_readiness_service.rb +4 -0
- data/lib/core/repo_introspection.rb +112 -2
- data/lib/cpflow/version.rb +1 -1
- data/lib/generator_templates/.dockerignore +16 -0
- data/lib/generator_templates/Dockerfile +9 -5
- data/lib/generator_templates/entrypoint.sh +2 -0
- data/lib/generator_templates/templates/postgres.yml +9 -0
- data/lib/generator_templates_sqlite/controlplane.yml +2 -1
- data/lib/generator_templates_sqlite/release_script.sh +1 -1
- data/lib/generator_templates_sqlite/templates/rails.yml +2 -1
- data/lib/github_flow_templates/.github/cpflow-help.md +39 -12
- data/lib/github_flow_templates/.github/workflows/cpflow-deploy-review-app.yml +1 -0
- data/lib/github_flow_templates/.github/workflows/cpflow-deploy-staging.yml +1 -0
- data/lib/github_flow_templates/.github/workflows/cpflow-promote-staging-to-production.yml +132 -13
- data/lib/github_flow_templates/bin/pin-cpflow-github-ref +37 -13
- data/lib/github_flow_templates/bin/test-cpflow-github-flow +88 -47
- metadata +12 -3
data/docs/ci-automation.md
CHANGED
|
@@ -23,7 +23,7 @@ End-to-end rollout in one view:
|
|
|
23
23
|
|
|
24
24
|
1. `cpflow github-flow-readiness` — exits non-zero if the repo is not ready to deploy.
|
|
25
25
|
2. `cpflow generate` — creates `.controlplane/` if missing.
|
|
26
|
-
3. `cpflow generate-github-actions` — adds `cpflow-*` workflow wrappers
|
|
26
|
+
3. `cpflow generate-github-actions` — adds `cpflow-*` workflow wrappers. Review-app, staging, cleanup, and helper workflows call upstream reusable workflows; production promotion is a normal caller-repo job so it can own the protected production Environment.
|
|
27
27
|
4. Configure the [GitHub Actions secrets and variables](#github-actions-secrets-and-variables) the workflows expect.
|
|
28
28
|
5. Push the branch, then comment `+review-app-deploy` on a PR to spin up a review environment.
|
|
29
29
|
|
|
@@ -69,7 +69,6 @@ The second command writes namespaced files so they can coexist with an app's exi
|
|
|
69
69
|
- `.github/workflows/cpflow-deploy-staging.yml`
|
|
70
70
|
- `.github/workflows/cpflow-promote-staging-to-production.yml`
|
|
71
71
|
- `.github/workflows/cpflow-cleanup-stale-review-apps.yml`
|
|
72
|
-
- `.github/actions/cpflow-*` (the local composite actions used by those workflows)
|
|
73
72
|
- `bin/pin-cpflow-github-ref`
|
|
74
73
|
- `bin/test-cpflow-github-flow`
|
|
75
74
|
|
|
@@ -77,7 +76,7 @@ The second command writes namespaced files so they can coexist with an app's exi
|
|
|
77
76
|
Docker base Ruby version from `.ruby-version`, `.tool-versions`, or the app's
|
|
78
77
|
`Gemfile`, preserves repo-defined frontend precompile hooks such as Shakapacker
|
|
79
78
|
`precompile_hook` commands or React on Rails auto bundle generation, and
|
|
80
|
-
switches to persistent SQLite
|
|
79
|
+
switches to persistent SQLite data and Active Storage templates when
|
|
81
80
|
`config/database.yml` shows SQLite in production.
|
|
82
81
|
|
|
83
82
|
`cpflow github-flow-readiness` checks public RubyGems and npm registry metadata
|
|
@@ -230,14 +229,74 @@ because they could change the target org name after normalization.
|
|
|
230
229
|
|
|
231
230
|
Production promotion copies the exact image currently deployed on the selected
|
|
232
231
|
staging workload. If that staging image is digest-pinned, the digest is used for
|
|
233
|
-
the source copy while the production tag is derived from the tag portion.
|
|
234
|
-
|
|
235
|
-
|
|
232
|
+
the source copy while the production tag is derived from the tag portion. Staging tags must
|
|
233
|
+
include a full lowercase 40-character Git commit SHA suffix (`_<commit>`),
|
|
234
|
+
which is preserved in the new production tag. A staging image without that
|
|
235
|
+
provenance stops promotion before approval. The copy step uses
|
|
236
236
|
`docker buildx imagetools create --prefer-index=false --tag` with isolated
|
|
237
237
|
Docker credentials, which preserves multi-architecture manifests, preserves
|
|
238
238
|
single-platform manifest format when supported, and avoids pulling image layers
|
|
239
239
|
onto the GitHub Actions runner.
|
|
240
240
|
|
|
241
|
+
When you start production promotion, the **preview-promotion** job finishes
|
|
242
|
+
before GitHub requests approval for the protected production Environment. Open
|
|
243
|
+
the run summary’s **Commits ready for production** section before approving. It
|
|
244
|
+
compares staging’s deployed primary-workload image with the last recorded
|
|
245
|
+
successful `production-*` GitHub release. It links the full comparison, lists up
|
|
246
|
+
to 100 commit subjects, and flags diverged history or staging behind production.
|
|
247
|
+
Matching SHAs report no commits to promote. Release discovery checks the latest
|
|
248
|
+
100 releases; missing production history or GitHub API failures produce an
|
|
249
|
+
explicit availability notice. The preview uses the existing staging token and
|
|
250
|
+
GitHub `contents: read` permission; it has no production token.
|
|
251
|
+
|
|
252
|
+
The preview selects the primary workload from the staging app’s
|
|
253
|
+
`app_workloads` configuration. `PRODUCTION_APP_NAME` and
|
|
254
|
+
`CPLN_ORG_PRODUCTION` can remain production Environment variables. When staging
|
|
255
|
+
and production have different primary-workload defaults, set `PRIMARY_WORKLOAD`
|
|
256
|
+
as a repository variable shared by both jobs; the protected job rejects a
|
|
257
|
+
workload-selection mismatch. A Control Plane read failure or invalid staging
|
|
258
|
+
SHA fails the preview and prevents approval from starting.
|
|
259
|
+
|
|
260
|
+
After approval, the protected job checks that staging still uses the captured
|
|
261
|
+
image and, when a release baseline was available, that live production matches
|
|
262
|
+
that release’s commit. A mismatch stops before copying or deploying an image;
|
|
263
|
+
rerun after staging changes, or reconcile the production release record after an
|
|
264
|
+
out-of-band production deployment. If production history was unavailable, the
|
|
265
|
+
summary does not claim to know the production-to-staging diff.
|
|
266
|
+
|
|
267
|
+
Existing repositories adopt this with
|
|
268
|
+
`cpflow update-github-actions --workflows cpflow-promote-staging-to-production.yml`.
|
|
269
|
+
Review and reapply downstream workflow customizations before committing the
|
|
270
|
+
replacement. The bare update command preserves existing top-level workflows.
|
|
271
|
+
|
|
272
|
+
### Recover a missing or stale production release record
|
|
273
|
+
|
|
274
|
+
A successful production deployment and its GitHub release are separate jobs.
|
|
275
|
+
If deployment succeeded but **create-github-release** failed, open that original
|
|
276
|
+
Actions run and choose **Re-run failed jobs**. This retries release publication
|
|
277
|
+
at the original promoted SHA; do not rerun the whole promotion merely to repair
|
|
278
|
+
the record. Confirm the resulting `production-*` release targets the live
|
|
279
|
+
production image’s SHA, then start a fresh promotion preview.
|
|
280
|
+
|
|
281
|
+
After an out-of-band production deploy or rollback, an app maintainer should
|
|
282
|
+
first verify the healthy live primary-workload image and its full commit suffix
|
|
283
|
+
with `cpln workload get <primary-workload> --gvc <production-app> --org
|
|
284
|
+
<production-org> -o json`. Use that SHA to publish a fresh release record; do not
|
|
285
|
+
move an existing release tag. For example, after replacing `<verified-live-sha>`:
|
|
286
|
+
|
|
287
|
+
```sh
|
|
288
|
+
gh release create "production-$(date -u +%Y-%m-%d-%H%M%S)-0" \
|
|
289
|
+
--target <verified-live-sha> \
|
|
290
|
+
--title "Production state reconciliation" \
|
|
291
|
+
--notes "Records the verified live production commit after a manual deployment or rollback."
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Run this in the app repository with the maintainer’s normal GitHub credentials.
|
|
295
|
+
Verify the new tag’s commit and the live image agree, then rerun promotion to
|
|
296
|
+
produce a fresh preview. If the live image has no commit suffix, establish its
|
|
297
|
+
provenance and use a traceable image before recording a baseline; guessing a
|
|
298
|
+
branch head would make the comparison misleading.
|
|
299
|
+
|
|
241
300
|
Before copying the image, production promotion compares the environment variable
|
|
242
301
|
names exposed by staging and production at both the GVC level and each configured
|
|
243
302
|
app workload's container level. Variables present in staging are treated as
|
|
@@ -425,9 +484,11 @@ Recommended org layout:
|
|
|
425
484
|
review/staging CI operations; use a dedicated review-app org only if you also customize the generated
|
|
426
485
|
workflow/configuration to target that org separately
|
|
427
486
|
|
|
428
|
-
Optional repository
|
|
487
|
+
Optional repository secrets for Docker builds:
|
|
429
488
|
|
|
430
489
|
- `DOCKER_BUILD_SSH_KEY`: private SSH key used when the Dockerfile needs `RUN --mount=type=ssh` to fetch private GitHub dependencies during image build
|
|
490
|
+
- `DOCKER_BUILD_SECRETS`: staging build credentials as newline-delimited `id=value` entries supplied through BuildKit secret mounts. Values must be single-line; spaces and additional `=` characters are preserved. IDs may contain letters, digits, underscores, and hyphens, and must start with a letter or digit. Empty values and duplicate IDs are rejected. Leave this secret unset when the build does not need credentials.
|
|
491
|
+
- `REVIEW_APP_DOCKER_BUILD_SECRETS`: separate credentials for review-app builds, using the same format. The generated review-app caller uses only this secret; it does not fall back to `DOCKER_BUILD_SECRETS`. Leave it unset to supply no BuildKit secrets to review apps.
|
|
431
492
|
|
|
432
493
|
Optional repository variables for private dependency builds:
|
|
433
494
|
|
|
@@ -475,12 +536,11 @@ The generated flow uses these defaults:
|
|
|
475
536
|
makes the newest intent fail closed instead of falling back to an older operation. This permission gate applies to every
|
|
476
537
|
`+review-app-deploy` comment, whether or not a review app already exists. Later pushes to a base-repository branch PR
|
|
477
538
|
redeploy automatically without another approval because the auto-push path (`pull_request` event) does not use the
|
|
478
|
-
comment permission gate. The reusable deploy workflow loads
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
code run with staging/review credentials;
|
|
539
|
+
comment permission gate. The reusable deploy workflow loads its composite actions from `shakacode/control-plane-flow`
|
|
540
|
+
at the commit the wrapper pins, never from the pull request's revision; see
|
|
541
|
+
[Where the actions run from](#where-the-actions-run-from). Preserve the same-repository caller guard because a
|
|
542
|
+
same-repository pull request can still change the wrapper and the application code that run with staging/review
|
|
543
|
+
credentials;
|
|
484
544
|
- fork pull requests cannot deploy via the generated `pull_request` path because the caller workflow's job-level `if:`
|
|
485
545
|
condition explicitly skips fork-originated runs. For `issue_comment` events, the caller `if:` restricts invocation to
|
|
486
546
|
the exact command shape; the reusable workflow resolves repository permission before its deploy job runs, then its
|
|
@@ -516,11 +576,10 @@ generated staging/review org and make that token disposable and unable to access
|
|
|
516
576
|
|
|
517
577
|
The PR-close teardown workflow runs trusted base-branch workflow code with repository secret access so it can delete fork
|
|
518
578
|
PR review apps. The generated `cpflow-delete-control-plane-app` composite action script refuses to call `cpflow delete`
|
|
519
|
-
on any app whose name does not match the review-app prefix. This shell-level guard is effective because the
|
|
520
|
-
|
|
521
|
-
the commit GitHub recorded for the triggering event (`GITHUB_SHA`, the base-branch tip under
|
|
522
|
-
|
|
523
|
-
paths rather than PR or fork code. If you customize this
|
|
579
|
+
on any app whose name does not match the review-app prefix. This shell-level guard is effective because the delete
|
|
580
|
+
workflow loads that action from the wrapper-pinned `.cpflow` commit. Its separate app checkout has no `ref:` override,
|
|
581
|
+
which resolves to the commit GitHub recorded for the triggering event (`GITHUB_SHA`, the base-branch tip under
|
|
582
|
+
`pull_request_target`), so teardown reads base-branch configuration rather than PR or fork code. If you customize this
|
|
524
583
|
workflow, never check out PR or fork code in the same job as the delete step; doing so could let a PR replace the guard
|
|
525
584
|
script itself and would also make `hooks.pre_deletion` come from the PR's `controlplane.yml`. This is still not a token
|
|
526
585
|
policy, so use a scoped staging service account limited to review/staging operations. A configured
|
|
@@ -570,6 +629,30 @@ The generated `cpflow-build-docker-image` action supports this without hardcodin
|
|
|
570
629
|
- set `DOCKER_BUILD_SSH_KEY` if the Docker build needs SSH access to GitHub
|
|
571
630
|
- optionally set `DOCKER_BUILD_SSH_KNOWN_HOSTS` when the SSH build host is not GitHub.com or you need custom host entries
|
|
572
631
|
- set `DOCKER_BUILD_EXTRA_ARGS` when you need extra `docker build` flags
|
|
632
|
+
- set `DOCKER_BUILD_SECRETS` for staging builds that need credentials such as a private package token or a source-map upload token
|
|
633
|
+
- set `REVIEW_APP_DOCKER_BUILD_SECRETS` separately if review-app builds need credentials
|
|
634
|
+
|
|
635
|
+
For example, store the following as the **value** of the `DOCKER_BUILD_SECRETS` GitHub Actions secret:
|
|
636
|
+
|
|
637
|
+
```text
|
|
638
|
+
sentry_auth_token=<source-map upload token>
|
|
639
|
+
npm_token=<private package token>
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
The generated staging caller forwards `DOCKER_BUILD_SECRETS`. The review-app caller forwards `REVIEW_APP_DOCKER_BUILD_SECRETS`, the only build-secret parameter the reusable review-app workflow accepts, so review builds never receive the staging secret. The build action writes each value to a private temporary file, passes only its path through `--secret=id=...,src=...`, and removes the files on success or failure. Values are not passed as build arguments or persisted through `GITHUB_ENV`.
|
|
643
|
+
|
|
644
|
+
A Dockerfile can consume a token only for the command that needs it:
|
|
645
|
+
|
|
646
|
+
```dockerfile
|
|
647
|
+
# syntax=docker/dockerfile:1
|
|
648
|
+
ARG SENTRY_UPLOAD=false
|
|
649
|
+
RUN --mount=type=secret,id=sentry_auth_token,env=SENTRY_AUTH_TOKEN \
|
|
650
|
+
SENTRY_UPLOAD=${SENTRY_UPLOAD} npm run build
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
If the application's build requires an explicit upload opt-in, pass `docker_build_extra_args: --build-arg=SENTRY_UPLOAD=true` in the caller workflow's `with` block, or add that token to `DOCKER_BUILD_EXTRA_ARGS`. The optional workflow input is appended after the repository variable, retaining existing build arguments. Token availability alone need not enable uploads. BuildKit secret values do not invalidate build caches; force the relevant build step to run again when rotating a credential requires repeating an upload.
|
|
654
|
+
|
|
655
|
+
Every secret supplied to a review build is accessible to the PR-controlled Dockerfile, which can read and transmit it. Automatic rebuilds of an existing review app also receive these secrets; approval to create the app does not approve later commits. Configure only credentials whose access can be entrusted to anyone able to change the deployed branch, scoped to the minimum review-build permissions. Separate secret names reduce accidental sharing but do not prevent the Dockerfile from accessing supplied credentials. If a credential cannot be entrusted to branch authors, omit it from review builds or use a customized approval gate that approves each deployed commit. Apply the review-app security guidance above.
|
|
573
656
|
|
|
574
657
|
For example, a repo that installs private dependencies from GitHub during Docker build can set:
|
|
575
658
|
|
|
@@ -708,18 +791,36 @@ wrapper-level `if:` guard shown in that file, for example
|
|
|
708
791
|
## Upstream Workflows And Actions
|
|
709
792
|
|
|
710
793
|
Most generated workflows are intentionally small wrappers. The deployment
|
|
711
|
-
logic and comment formatting live in upstream reusable workflows
|
|
712
|
-
composite actions live in this repository
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
794
|
+
logic and comment formatting live in upstream reusable workflows, and the
|
|
795
|
+
composite actions they call live in this repository. Each job checks out
|
|
796
|
+
`shakacode/control-plane-flow` at the pinned ref into `.cpflow`, then runs the
|
|
797
|
+
actions as `./.cpflow/.github/actions/cpflow-*` and builds the `cpflow` runtime
|
|
798
|
+
from the same checkout. A downstream repository carries the wrappers and one
|
|
799
|
+
pinned ref, with no copies of the actions. Production promotion is expanded
|
|
800
|
+
into the caller repository so it can own `environment: production`; it pins the
|
|
801
|
+
same source in its own `.cpflow` checkout.
|
|
718
802
|
|
|
719
803
|
- `cpflow-setup-environment`: installs Ruby, the Control Plane CLI, and `cpflow`, then logs into the target org. By default it builds `cpflow` from the checked-out upstream `control-plane-flow` ref; set the `CPFLOW_VERSION` repository variable only when you want to force a published RubyGems release.
|
|
720
804
|
- `cpflow-build-docker-image`: builds and pushes the app image with the desired commit SHA
|
|
721
805
|
- `cpflow-delete-control-plane-app`: safely deletes temporary apps and refuses to touch names outside the configured review-app prefix
|
|
722
806
|
|
|
807
|
+
### Where the actions run from
|
|
808
|
+
|
|
809
|
+
The actions that receive `CPLN_TOKEN_STAGING` come from
|
|
810
|
+
`shakacode/control-plane-flow` at the commit the calling wrapper pins. For a
|
|
811
|
+
reusable workflow that is `job.workflow_sha`; for production promotion it is the
|
|
812
|
+
`ref:` of the `Checkout control-plane-flow actions` step.
|
|
813
|
+
|
|
814
|
+
A same-repository pull request can change the wrapper, including its pinned
|
|
815
|
+
ref, and the application code and `.controlplane/` configuration that the
|
|
816
|
+
deployment builds. It cannot change the action code without changing that pin,
|
|
817
|
+
so reviewing the pin reviews the actions. The review-app jobs check out caller
|
|
818
|
+
files only under `app/`, and never at the workspace root.
|
|
819
|
+
|
|
820
|
+
cpflow 6.0.0 instead ran copies of the actions from the caller repository at
|
|
821
|
+
the triggering revision. See
|
|
822
|
+
[Removing cpflow 6.0.0 action copies](#removing-cpflow-600-action-copies).
|
|
823
|
+
|
|
723
824
|
## Version Pins: GitHub Ref vs RubyGems
|
|
724
825
|
|
|
725
826
|
Generated `cpflow-*` workflow files pin `shakacode/control-plane-flow` from
|
|
@@ -741,22 +842,22 @@ that input outside the production promotion setup step, regenerate with a newer
|
|
|
741
842
|
|
|
742
843
|
There are two coordinated inputs, and they protect different things:
|
|
743
844
|
|
|
744
|
-
- The GitHub ref locks the reusable workflow
|
|
745
|
-
checkout that GitHub runs.
|
|
746
|
-
- The installed Ruby gem supplies the generated
|
|
747
|
-
|
|
748
|
-
|
|
845
|
+
- The GitHub ref locks the reusable workflow, its composite actions, and the
|
|
846
|
+
`.cpflow` runtime source checkout that GitHub runs.
|
|
847
|
+
- The installed Ruby gem supplies the generated wrappers. `CPFLOW_VERSION`,
|
|
848
|
+
when configured, separately locks the published `cpflow` runtime installed by
|
|
849
|
+
the setup action.
|
|
749
850
|
|
|
750
851
|
That means a downstream app cannot rely on the gem alone for GitHub Actions
|
|
751
852
|
behavior. The safe stable path is still gem-driven for generation, but
|
|
752
|
-
developers must commit the generated wrappers
|
|
753
|
-
|
|
853
|
+
developers must commit the generated wrappers from the gem that matches the
|
|
854
|
+
upstream release tag:
|
|
754
855
|
|
|
755
856
|
1. Publish a `cpflow` gem.
|
|
756
857
|
2. Install or bundle that released gem in the downstream project.
|
|
757
858
|
3. Run `cpflow generate-github-actions`.
|
|
758
|
-
4. Commit the generated wrappers
|
|
759
|
-
|
|
859
|
+
4. Commit the generated wrappers; they should point to the matching upstream
|
|
860
|
+
release tag such as `v5.0.0`.
|
|
760
861
|
|
|
761
862
|
That release tag should point to the same source that produced the RubyGems
|
|
762
863
|
release. Downstream production automation should use release tags, not `main` or
|
|
@@ -765,8 +866,8 @@ feature-branch refs.
|
|
|
765
866
|
Generated cross-repository reusable-workflow calls deliberately use an exact
|
|
766
867
|
release tag such as `v5.0.0`. This is the intentional downstream exception to
|
|
767
868
|
the repository's full-SHA external-action policy: it keeps the installed gem,
|
|
768
|
-
generated wrappers,
|
|
769
|
-
|
|
869
|
+
generated wrappers, and upstream workflow on one reviewed release update
|
|
870
|
+
contract. It does not extend to external actions inside
|
|
770
871
|
those workflows, which remain pinned to full commit SHAs with readable release
|
|
771
872
|
comments. Unreleased downstream validation temporarily replaces the exact tag
|
|
772
873
|
with the upstream PR's full commit SHA as described below; moving version
|
|
@@ -776,14 +877,14 @@ aliases and branches remain forbidden.
|
|
|
776
877
|
|
|
777
878
|
Whenever a downstream repo updates the `cpflow` gem, update all checked-in
|
|
778
879
|
GitHub Actions files in the same PR. The gem version does not make GitHub load
|
|
779
|
-
new reusable workflow YAML
|
|
780
|
-
|
|
880
|
+
new reusable workflow YAML by itself; GitHub uses the workflow ref committed in
|
|
881
|
+
the repository.
|
|
781
882
|
|
|
782
883
|
For a new repository that does not have generated wrappers yet, run
|
|
783
884
|
`cpflow generate-github-actions` first. Use the update command below for later
|
|
784
885
|
gem upgrades.
|
|
785
886
|
|
|
786
|
-
Use the installed gem to refresh the generated
|
|
887
|
+
Use the installed gem to refresh the generated files:
|
|
787
888
|
|
|
788
889
|
```sh
|
|
789
890
|
cpflow update-github-actions
|
|
@@ -797,11 +898,111 @@ bundle exec cpflow update-github-actions
|
|
|
797
898
|
bin/test-cpflow-github-flow bundle exec cpflow
|
|
798
899
|
```
|
|
799
900
|
|
|
800
|
-
`cpflow update-github-actions`
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
901
|
+
`cpflow update-github-actions` refreshes the generated helpers.
|
|
902
|
+
It preserves every top-level workflow by default, including refs, triggers,
|
|
903
|
+
permissions, and downstream deployment ownership. It does not create staging
|
|
904
|
+
or production deployment workflows when another CI system owns those deploys.
|
|
905
|
+
Select the wrappers you intend to replace explicitly; see
|
|
906
|
+
`cpflow update-github-actions --help` for the current interface:
|
|
907
|
+
|
|
908
|
+
```sh
|
|
909
|
+
bundle exec cpflow update-github-actions --workflows \
|
|
910
|
+
cpflow-deploy-review-app.yml cpflow-delete-review-app.yml \
|
|
911
|
+
cpflow-cleanup-stale-review-apps.yml cpflow-help-command.yml \
|
|
912
|
+
cpflow-review-app-help.yml
|
|
913
|
+
```
|
|
914
|
+
|
|
915
|
+
Each selected wrapper is replaced in full with the installed gem's template
|
|
916
|
+
and release ref. Review the diff and reapply downstream changes before
|
|
917
|
+
committing. Unselected wrappers keep their refs: preservation is not evidence
|
|
918
|
+
that old wrappers are compatible with the new release. Migrate wrapper
|
|
919
|
+
contracts and source pins together, then run the validator in the same PR.
|
|
920
|
+
For a selected staging wrapper, a single existing push branch or the default
|
|
921
|
+
main/master pair can be inferred. Missing, malformed, or ambiguous branch
|
|
922
|
+
configuration requires an explicit `--staging-branch BRANCH`; that option also
|
|
923
|
+
requires selecting `cpflow-deploy-staging.yml`.
|
|
924
|
+
|
|
925
|
+
### Removing cpflow 6.0.0 action copies
|
|
926
|
+
|
|
927
|
+
cpflow 6.0.0 generated `.github/actions/cpflow-*` and ran those copies. Later
|
|
928
|
+
releases load the actions from the pinned ref, so the copies are unused once
|
|
929
|
+
every workflow has moved off 6.0.0:
|
|
930
|
+
|
|
931
|
+
1. Update the gem, then replace the wrappers and the production promotion
|
|
932
|
+
workflow with `cpflow update-github-actions --workflows FILE...`. Reapply
|
|
933
|
+
downstream customizations.
|
|
934
|
+
2. Confirm that no workflow is still pinned to cpflow 6.0.0 and that none has a
|
|
935
|
+
`uses: ./.github/actions/cpflow-*` line. In a workflow you maintain by hand,
|
|
936
|
+
change each such line to `uses: ./.cpflow/.github/actions/cpflow-*`.
|
|
937
|
+
3. Delete the copies with `git rm -r .github/actions/cpflow-*`, delete any
|
|
938
|
+
downstream check that compares them with the gem, and run
|
|
939
|
+
`bin/test-cpflow-github-flow`.
|
|
940
|
+
|
|
941
|
+
`cpflow update-github-actions` lists the copies it finds and never deletes
|
|
942
|
+
them. A wrapper pinned to 6.0.0 runs them without naming them, and a full-SHA
|
|
943
|
+
pin does not say which release it is, so the command cannot prove a copy is
|
|
944
|
+
unused. Until step 3 the copies are harmless: a wrapper pinned to a later
|
|
945
|
+
release never reads them. The validator fails when a workflow names a copy
|
|
946
|
+
that is not checked in.
|
|
947
|
+
|
|
948
|
+
### Preserving downstream validation
|
|
949
|
+
|
|
950
|
+
The generated `bin/test-cpflow-github-flow` runs baseline checks followed by
|
|
951
|
+
an optional executable `bin/test-cpflow-github-flow-custom`. The extension runs
|
|
952
|
+
from the repository root with the cpflow command argv forwarded unchanged
|
|
953
|
+
(for example, `bundle`, `exec`, `cpflow`), and its nonzero exit fails validation.
|
|
954
|
+
The generator never creates or overwrites the custom extension. Baseline
|
|
955
|
+
production checks apply when the generated production workflow exists;
|
|
956
|
+
downstream-owned production workflows belong in the custom checks.
|
|
957
|
+
|
|
958
|
+
An existing validator that differs from the installed template blocks the
|
|
959
|
+
entire update before any writes. This includes unmodified validators from
|
|
960
|
+
older releases: the updater cannot safely distinguish them from custom code.
|
|
961
|
+
For a legacy installation such as HiChee's 5.3-to-6.0 RC migration:
|
|
962
|
+
|
|
963
|
+
1. Save the old validator in version control and compare it with its original
|
|
964
|
+
release. Extract the downstream checks into `bin/test-cpflow-github-flow-custom`
|
|
965
|
+
and make it executable. If it is entirely downstream-specific, rename it
|
|
966
|
+
directly. Remove any call back to the generated validator to avoid recursion.
|
|
967
|
+
2. Remove or rename the old `bin/test-cpflow-github-flow` after preserving those
|
|
968
|
+
checks, then run the update command with the selected wrappers. No override
|
|
969
|
+
flag bypasses this check. Do not use `generate-github-actions --force` as a
|
|
970
|
+
migration shortcut: it intentionally replaces the full generated flow.
|
|
971
|
+
3. Run the new validator. Confirm that the custom checks execute and that a
|
|
972
|
+
failing custom check still fails the command. Repeating the updater with the
|
|
973
|
+
same selected workflows should produce no further diff.
|
|
974
|
+
|
|
975
|
+
### Migrating trusted action allowlists
|
|
976
|
+
|
|
977
|
+
Repositories using a closed `trusted_actions` policy must review these exact
|
|
978
|
+
repositories used by the complete generated output:
|
|
979
|
+
|
|
980
|
+
```yaml
|
|
981
|
+
trusted_actions:
|
|
982
|
+
- actions/checkout
|
|
983
|
+
- docker/setup-buildx-action
|
|
984
|
+
- ruby/setup-ruby
|
|
985
|
+
- shakacode/control-plane-flow
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
Merge the needed entries into your existing policy rather than replacing it.
|
|
989
|
+
`ruby/setup-ruby` is used by cpflow's setup action, including in
|
|
990
|
+
review-app-only installations. `actions/checkout` and `docker/setup-buildx-action`
|
|
991
|
+
are used directly by the generated production wrapper;
|
|
992
|
+
`shakacode/control-plane-flow` supplies the reusable wrappers. Other existing
|
|
993
|
+
downstream workflows may need additional entries. Upstream reusable workflows
|
|
994
|
+
also use `actions/github-script`; reviewing that upstream implementation remains
|
|
995
|
+
part of trusting `shakacode/control-plane-flow`.
|
|
996
|
+
|
|
997
|
+
Allowlisting does not waive immutable pin requirements. For a policy requiring
|
|
998
|
+
full SHAs, resolve and independently verify the chosen upstream release tag or
|
|
999
|
+
test commit, then run `bin/pin-cpflow-github-ref --version <reviewed-version-tag>
|
|
1000
|
+
<full-sha>`. The helper writes that version/tag beside all cpflow SHA pins and
|
|
1001
|
+
updates obsolete same-line comments. The label is supplied by the caller; the
|
|
1002
|
+
helper does not contact GitHub or certify tag-to-SHA identity. For an unreleased
|
|
1003
|
+
test commit, use its source version tag as context and record the exact commit
|
|
1004
|
+
in the PR. Review the generated diff and run your repository's action security
|
|
1005
|
+
scanner after updating its allowlist.
|
|
805
1006
|
|
|
806
1007
|
When keeping `cpflow` in an app Gemfile, leave a comment next to the gem entry
|
|
807
1008
|
so future dependency bumps include the wrapper update:
|
|
@@ -835,8 +1036,7 @@ action commit, so a moving branch named like `v5.0.0` cannot be used with
|
|
|
835
1036
|
runners that cannot reach GitHub should leave `CPFLOW_VERSION` unset and build
|
|
836
1037
|
`cpflow` from the checked-out ref instead. When testing an unreleased upstream
|
|
837
1038
|
commit SHA, leave `CPFLOW_VERSION` unset so the workflow builds `cpflow` from the
|
|
838
|
-
same source that supplies the reusable workflow
|
|
839
|
-
actions from that checkout before testing as described below.
|
|
1039
|
+
same source that supplies the reusable workflow and its actions.
|
|
840
1040
|
|
|
841
1041
|
## Testing Unreleased Upstream Changes Downstream
|
|
842
1042
|
|
|
@@ -849,14 +1049,13 @@ releasing it. Use an immutable commit SHA from the upstream PR branch:
|
|
|
849
1049
|
|
|
850
1050
|
```sh
|
|
851
1051
|
ruby /path/to/control-plane-flow/bin/cpflow update-github-actions
|
|
852
|
-
bin/pin-cpflow-github-ref <upstream-pr-sha>
|
|
1052
|
+
bin/pin-cpflow-github-ref --version <reviewed-version-tag> <upstream-pr-sha>
|
|
853
1053
|
```
|
|
854
1054
|
|
|
855
|
-
The
|
|
856
|
-
`.github/actions/cpflow-*`. The pin helper then updates every generated
|
|
1055
|
+
The pin helper updates every generated
|
|
857
1056
|
reusable-workflow `uses:` ref plus the production workflow's pinned
|
|
858
1057
|
`control-plane-flow` checkout and setup validation ref. It accepts release
|
|
859
|
-
tags and full commit SHAs
|
|
1058
|
+
tags and full commit SHAs (with a reviewed `--version` label), rejects branch names such as `main` or
|
|
860
1059
|
`feature/foo`, and requires `--allow-moving-ref` for short-lived local
|
|
861
1060
|
experiments that should not be committed.
|
|
862
1061
|
|
|
@@ -881,19 +1080,18 @@ releasing it. Use an immutable commit SHA from the upstream PR branch:
|
|
|
881
1080
|
matches the repository's installation state.
|
|
882
1081
|
|
|
883
1082
|
For an existing installation, where `cpflow-deploy-review-app.yml` is already
|
|
884
|
-
present on the default branch, dispatch the workflow definition
|
|
885
|
-
|
|
886
|
-
base-repository PR:
|
|
1083
|
+
present on the default branch, dispatch the workflow definition from the
|
|
1084
|
+
unmerged downstream test branch against a suitable open base-repository PR:
|
|
887
1085
|
|
|
888
1086
|
```sh
|
|
889
1087
|
gh workflow run cpflow-deploy-review-app.yml --ref <downstream-test-branch> -f pr_number=<pr-number>
|
|
890
1088
|
```
|
|
891
1089
|
|
|
892
|
-
The `--ref` selects the downstream wrapper
|
|
1090
|
+
The `--ref` selects the downstream wrapper commit under test;
|
|
893
1091
|
`pr_number` selects the application source to deploy. Do not use a
|
|
894
1092
|
`+review-app-deploy` comment for this pre-merge validation: `issue_comment`
|
|
895
1093
|
always loads the workflow definition from the default branch, so it cannot
|
|
896
|
-
validate unmerged wrappers
|
|
1094
|
+
validate unmerged wrappers.
|
|
897
1095
|
|
|
898
1096
|
For a first installation, GitHub will not dispatch a workflow that does not
|
|
899
1097
|
yet exist on the default branch, even when `--ref` names the installation
|
|
@@ -914,9 +1112,9 @@ releasing it. Use an immutable commit SHA from the upstream PR branch:
|
|
|
914
1112
|
`bin/pin-cpflow-github-ref vX.Y.Z` only for a ref-only update when the
|
|
915
1113
|
generated templates are already current.
|
|
916
1114
|
|
|
917
|
-
For an existing installation, this tests the real reusable workflows,
|
|
918
|
-
|
|
919
|
-
|
|
1115
|
+
For an existing installation, this tests the real reusable workflows, their
|
|
1116
|
+
composite actions, and the source-built `cpflow` gem against one immutable
|
|
1117
|
+
upstream commit. A first installation gets the same remote
|
|
920
1118
|
proof immediately after its locally validated files reach the default branch.
|
|
921
1119
|
Both paths avoid testing against a moving upstream branch.
|
|
922
1120
|
|
|
@@ -961,7 +1159,7 @@ In practice, porting the flow into a demo app usually follows five phases.
|
|
|
961
1159
|
|
|
962
1160
|
4. Update `.controlplane/controlplane.yml` with staging, review, and production entries.
|
|
963
1161
|
5. Confirm that the generated Dockerfile picked a Ruby base image compatible with the app's declared Ruby requirement.
|
|
964
|
-
6. For SQLite-backed apps, confirm that the generated scaffold
|
|
1162
|
+
6. For SQLite-backed apps, confirm that the generated scaffold redirects every production database file into persistent `/app/data` (or keeps it under persistent `/app/storage`) without hiding image migrations under `/app/db`, mounts Active Storage at `/app/storage`, and adds a release script that runs `rails db:prepare`.
|
|
965
1163
|
|
|
966
1164
|
**Adapt for the app's runtime:**
|
|
967
1165
|
|
|
@@ -1005,7 +1203,7 @@ Expand that prompt with app-specific requirements before editing files:
|
|
|
1005
1203
|
- make sure the generated Dockerfile uses a Ruby base image compatible with the app's declared Ruby requirement
|
|
1006
1204
|
- preserve repo-defined frontend precompile hooks, such as Shakapacker `precompile_hook` commands or React on Rails `config.auto_load_bundle = true`
|
|
1007
1205
|
- keep Node available in the final image if Rails or SSR depends on ExecJS, Yarn, or `pnpm` after the main `npm install` layer
|
|
1008
|
-
- if `config/database.yml` shows SQLite in production, confirm that `cpflow generate`
|
|
1206
|
+
- if `config/database.yml` shows SQLite in production, confirm that `cpflow generate` redirected every production database path into persistent `/app/data` or `/app/storage`, emitted both volumes without mounting over `/app/db`, and added a `rails db:prepare` release script; otherwise keep the default Postgres workload
|
|
1009
1207
|
- 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
|
|
1010
1208
|
- add extra `app_workloads` and template files for any runtime sidecars, workers, or renderer processes
|
|
1011
1209
|
- make sure any sidecar process exposed to sibling workloads binds to `0.0.0.0` instead of container-local `localhost`
|
data/docs/commands.md
CHANGED
|
@@ -30,6 +30,7 @@ cpflow ai-github-flow-prompt
|
|
|
30
30
|
- Picks templates from the `.controlplane/templates` directory
|
|
31
31
|
- Templates are ordinary Control Plane templates but with variable preprocessing
|
|
32
32
|
- Use `--preserve-existing-runtime` to retain each workload container's configured app image, even when the workload is unready, and skip existing secret resources entirely while applying other template changes
|
|
33
|
+
- Use `--exclude-secret-template NAME` and `--exclude-policy-template NAME` to exclude exact named templates without changing workload image handling
|
|
33
34
|
- Missing or invalid workload images use only an unambiguous app image from ready workloads; refresh fails before applying templates when no safe fallback exists
|
|
34
35
|
|
|
35
36
|
**Preprocessed template variables:**
|
|
@@ -135,6 +136,7 @@ cpflow copy-image-from-upstream -a $APP_NAME --upstream-token $UPSTREAM_TOKEN --
|
|
|
135
136
|
- Deletes the whole app (GVC with all workloads, all volumesets and all images) or a specific workload
|
|
136
137
|
- Also unbinds the app from the secrets policy and any configured `shared_secret_grants` policies, as long as both the identity and each policy exist (and are bound)
|
|
137
138
|
- For the app-specific secrets policy, removes every permission held by the app identity; for `shared_secret_grants`, removes only `reveal`
|
|
139
|
+
- Removes a marked per-app dictionary and exact-target unbound policy for generated review credentials, including after the opt-in is removed from a dynamically matched review-app entry
|
|
138
140
|
- Will ask for explicit user confirmation
|
|
139
141
|
- Runs a pre-deletion hook before the app is deleted if `hooks.pre_deletion` is specified in the `.controlplane/controlplane.yml` file
|
|
140
142
|
- If the hook exits with a non-zero code, the command will stop executing and also exit with a non-zero code
|
|
@@ -217,7 +219,7 @@ Creates base Control Plane config and template files for a Rails project:
|
|
|
217
219
|
- infers the app prefix from the current directory and wires staging, review, and production entries
|
|
218
220
|
- infers the Docker base Ruby version from `.ruby-version`, `.tool-versions`, or the app's `Gemfile`
|
|
219
221
|
- preserves repo-defined asset precompile hooks, including React on Rails auto bundle generation
|
|
220
|
-
- detects SQLite in `config/database.yml` and generates persistent `
|
|
222
|
+
- detects SQLite in `config/database.yml` and generates persistent `/app/data` and `/app/storage` volume templates without hiding image migrations under `/app/db`
|
|
221
223
|
|
|
222
224
|
```sh
|
|
223
225
|
# Creates .controlplane directory with Control Plane config and starter templates
|
|
@@ -232,8 +234,8 @@ Creates GitHub Actions templates for a Heroku Flow style Control Plane pipeline:
|
|
|
232
234
|
- manual promotion from staging to production
|
|
233
235
|
- nightly cleanup and PR help workflows
|
|
234
236
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
+
The workflows load cpflow's composite actions from this gem's repository at
|
|
238
|
+
the ref each workflow pins, so no `.github/actions/cpflow-*` copies are generated.
|
|
237
239
|
|
|
238
240
|
Pass `--staging-branch BRANCH` when staging should auto-deploy from a branch
|
|
239
241
|
other than `main` or `master`; the generator will bake that branch into the
|
|
@@ -243,7 +245,7 @@ Pass `--force` to overwrite existing generated files. Prefer
|
|
|
243
245
|
repo.
|
|
244
246
|
|
|
245
247
|
```sh
|
|
246
|
-
# Creates workflow wrappers
|
|
248
|
+
# Creates workflow wrappers and validation helpers
|
|
247
249
|
cpflow generate-github-actions
|
|
248
250
|
|
|
249
251
|
# Creates the flow with staging deploys triggered from develop
|
|
@@ -558,13 +560,14 @@ cpflow run -a $APP_NAME --entrypoint /app/alternative-entrypoint.sh -- rails db:
|
|
|
558
560
|
- Configures app to have org-level secrets with default name `"{APP_PREFIX}-secrets"`
|
|
559
561
|
using org-level policy with default name `"{APP_PREFIX}-secrets-policy"` (names can be customized, see docs)
|
|
560
562
|
- Creates identity for secrets if it does not exist
|
|
563
|
+
- For dynamically named review apps with `generated_review_secret_keys`, checks an existing policy before writing credentials, creates a per-app dictionary, skips its secret template during initial setup, and fills missing disposable keys without printing or rotating values
|
|
561
564
|
- Binds the app identity to any configured `shared_secret_grants` policies as part of the secrets setup flow; skipped when `--skip-secrets-setup` or `--skip-secret-access-binding` is provided, or `skip_secrets_setup` is set
|
|
562
565
|
- Use `--skip-secrets-setup` to prevent the automatic setup of secrets,
|
|
563
566
|
or set it through `skip_secrets_setup` in the `.controlplane/controlplane.yml` file
|
|
564
567
|
- Runs a post-creation hook after the app is created if `hooks.post_creation` is specified in the `.controlplane/controlplane.yml` file
|
|
565
568
|
- If the hook exits with a non-zero code, the command will stop executing and also exit with a non-zero code
|
|
566
569
|
- Use `--skip-post-creation-hook` to skip the hook if specified in `controlplane.yml`
|
|
567
|
-
- Use `--refresh-templates` to apply configured templates noninteractively to an existing app while preserving each workload's configured app image even when workloads are unready or use mixed image versions, skipping existing secret
|
|
570
|
+
- Use `--refresh-templates` to apply configured templates noninteractively to an existing app while preserving each workload's configured app image even when workloads are unready or use mixed image versions, skipping existing secret templates (but filling missing opt-in generated review keys), repairing secrets access bindings, and skipping the post-creation hook
|
|
568
571
|
|
|
569
572
|
```sh
|
|
570
573
|
cpflow setup-app -a $APP_NAME
|
|
@@ -588,25 +591,38 @@ cpflow terraform import
|
|
|
588
591
|
|
|
589
592
|
### `update-github-actions`
|
|
590
593
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
594
|
+
Refreshes helper files from the installed gem. Workflows load cpflow's
|
|
595
|
+
composite actions from the pinned cpflow ref, so the command reports any
|
|
596
|
+
`.github/actions/cpflow-*` copies left by cpflow 6.0.0 for you to delete
|
|
597
|
+
once no workflow is pinned to 6.0.0. It never deletes them itself.
|
|
598
|
+
All top-level workflows are preserved by default, including their refs,
|
|
599
|
+
triggers, permissions, and deployment ownership. Use --workflows FILE...
|
|
600
|
+
to explicitly add or replace named generated workflows. Replacement resets
|
|
601
|
+
each selected workflow to its template and matching v6.0.1 ref;
|
|
602
|
+
review and reapply any downstream customizations in the same PR.
|
|
596
603
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
604
|
+
Selecting cpflow-deploy-staging.yml preserves a single existing push branch
|
|
605
|
+
or the default main/master pair. Missing or ambiguous branches require
|
|
606
|
+
--staging-branch BRANCH. This option requires selecting the staging workflow.
|
|
607
|
+
|
|
608
|
+
A differing existing bin/test-cpflow-github-flow blocks all writes. Move
|
|
609
|
+
downstream checks to executable bin/test-cpflow-github-flow-custom and
|
|
610
|
+
remove or rename the old validator before updating. The generated validator
|
|
611
|
+
invokes that extension after its baseline checks; the extension is never
|
|
612
|
+
generated or overwritten. See docs/ci-automation.md for migration guidance.
|
|
600
613
|
|
|
601
614
|
```sh
|
|
602
|
-
#
|
|
615
|
+
# Refresh helpers while preserving downstream workflows
|
|
603
616
|
cpflow update-github-actions
|
|
604
617
|
|
|
605
618
|
# When running cpflow through Bundler
|
|
606
619
|
bundle exec cpflow update-github-actions
|
|
607
620
|
|
|
608
|
-
#
|
|
609
|
-
cpflow update-github-actions --
|
|
621
|
+
# Explicitly replace the review-app wrapper
|
|
622
|
+
cpflow update-github-actions --workflows cpflow-deploy-review-app.yml
|
|
623
|
+
|
|
624
|
+
# Explicitly add or replace staging deployment from develop
|
|
625
|
+
cpflow update-github-actions --workflows cpflow-deploy-staging.yml --staging-branch develop
|
|
610
626
|
```
|
|
611
627
|
|
|
612
628
|
### `version`
|