@catladder/cli 0.0.0-next-ddc387fe → 0.0.0-next-67f89e03

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.
@@ -2299,9 +2299,10 @@ const WORKFLOWS_FOLDER = ".github/workflows";
2299
2299
  */
2300
2300
  const GENERATED_FILE_PREFIX = "catladder-";
2301
2301
  // NOTE on the emoji prefixes: github sorts the Actions sidebar by raw
2302
- // code point, so the đŸ› ī¸ (U+1F6E0) prefix makes the pipeline workflows
2303
- // sort BELOW the triggerable action workflows (â†Šī¸ âšī¸ â–ļī¸ 🚀, all lower
2304
- // code points) — the things a human clicks come first.
2302
+ // code point, so the prefix decides the order. đŸ› ī¸ (U+1F6E0) is the
2303
+ // highest, which keeps the pipeline workflows below the triggerable
2304
+ // action workflows (â†Šī¸ â–ļī¸ 🚀 🛑) — the things a human clicks come
2305
+ // first, and 🛑 (U+1F6D1) puts the stop actions after â–ļī¸ deploy.
2305
2306
  const TRIGGER_WORKFLOWS = {
2306
2307
  mr: {
2307
2308
  name: "đŸ› ī¸ catladder review",
@@ -2514,10 +2515,30 @@ class GithubBackend {
2514
2515
  }
2515
2516
  if (Object.keys(reviewStopJobs).length > 0) {
2516
2517
  workflows[`${GENERATED_FILE_PREFIX}review-stop.yml`] = {
2517
- name: "đŸ› ī¸ catladder review stop",
2518
- on: { pull_request: { types: ["closed"] } },
2518
+ name: "🛑 catladder stop review app",
2519
+ on: {
2520
+ pull_request: { types: ["closed"] },
2521
+ // tearing a review app down while its pull request is still
2522
+ // open (gitlab offers this as a manual job in the MR
2523
+ // pipeline); the automatic teardown on close stays the
2524
+ // normal path
2525
+ workflow_dispatch: {
2526
+ inputs: {
2527
+ pr: {
2528
+ description: "number of the pull request whose review app to stop",
2529
+ required: true,
2530
+ type: "string",
2531
+ },
2532
+ },
2533
+ },
2534
+ },
2519
2535
  ...workflowPermissions(images),
2520
- env: workflowEnv,
2536
+ env: {
2537
+ ...workflowEnv,
2538
+ // a dispatched run has no pull_request event to take the
2539
+ // number from — fall back to the input
2540
+ CL_PR_NUMBER: "${{ github.event.number || inputs.pr }}",
2541
+ },
2521
2542
  jobs: { ...(0, ensureImageJobs_1.makeEnsureImageGithubJobs)(images), ...reviewStopJobs },
2522
2543
  };
2523
2544
  }
@@ -2604,7 +2625,7 @@ const isReviewStopJob = (context, job) => {
2604
2625
  const isManualTaskJob = (job) => job.stage === "stop" || job.stage === "rollback";
2605
2626
  const MANUAL_KIND_WORKFLOW_NAMES = {
2606
2627
  deploy: "â–ļī¸ catladder deploy",
2607
- stop: "âšī¸ catladder stop",
2628
+ stop: "🛑 catladder stop",
2608
2629
  rollback: "â†Šī¸ catladder rollback",
2609
2630
  };
2610
2631
  const manualKind = (job) => job.stage === "stop"
@@ -8292,9 +8292,10 @@ const WORKFLOWS_FOLDER = ".github/workflows";
8292
8292
  */
8293
8293
  const GENERATED_FILE_PREFIX = "catladder-";
8294
8294
  // NOTE on the emoji prefixes: github sorts the Actions sidebar by raw
8295
- // code point, so the đŸ› ī¸ (U+1F6E0) prefix makes the pipeline workflows
8296
- // sort BELOW the triggerable action workflows (â†Šī¸ âšī¸ â–ļī¸ 🚀, all lower
8297
- // code points) — the things a human clicks come first.
8295
+ // code point, so the prefix decides the order. đŸ› ī¸ (U+1F6E0) is the
8296
+ // highest, which keeps the pipeline workflows below the triggerable
8297
+ // action workflows (â†Šī¸ â–ļī¸ 🚀 🛑) — the things a human clicks come
8298
+ // first, and 🛑 (U+1F6D1) puts the stop actions after â–ļī¸ deploy.
8298
8299
  const TRIGGER_WORKFLOWS = {
8299
8300
  mr: {
8300
8301
  name: "đŸ› ī¸ catladder review",
@@ -8507,10 +8508,30 @@ class GithubBackend {
8507
8508
  }
8508
8509
  if (Object.keys(reviewStopJobs).length > 0) {
8509
8510
  workflows[`${GENERATED_FILE_PREFIX}review-stop.yml`] = {
8510
- name: "đŸ› ī¸ catladder review stop",
8511
- on: { pull_request: { types: ["closed"] } },
8511
+ name: "🛑 catladder stop review app",
8512
+ on: {
8513
+ pull_request: { types: ["closed"] },
8514
+ // tearing a review app down while its pull request is still
8515
+ // open (gitlab offers this as a manual job in the MR
8516
+ // pipeline); the automatic teardown on close stays the
8517
+ // normal path
8518
+ workflow_dispatch: {
8519
+ inputs: {
8520
+ pr: {
8521
+ description: "number of the pull request whose review app to stop",
8522
+ required: true,
8523
+ type: "string",
8524
+ },
8525
+ },
8526
+ },
8527
+ },
8512
8528
  ...workflowPermissions(images),
8513
- env: workflowEnv,
8529
+ env: {
8530
+ ...workflowEnv,
8531
+ // a dispatched run has no pull_request event to take the
8532
+ // number from — fall back to the input
8533
+ CL_PR_NUMBER: "${{ github.event.number || inputs.pr }}",
8534
+ },
8514
8535
  jobs: { ...(0, ensureImageJobs_1.makeEnsureImageGithubJobs)(images), ...reviewStopJobs },
8515
8536
  };
8516
8537
  }
@@ -8597,7 +8618,7 @@ const isReviewStopJob = (context, job) => {
8597
8618
  const isManualTaskJob = (job) => job.stage === "stop" || job.stage === "rollback";
8598
8619
  const MANUAL_KIND_WORKFLOW_NAMES = {
8599
8620
  deploy: "â–ļī¸ catladder deploy",
8600
- stop: "âšī¸ catladder stop",
8621
+ stop: "🛑 catladder stop",
8601
8622
  rollback: "â†Šī¸ catladder rollback",
8602
8623
  };
8603
8624
  const manualKind = (job) => job.stage === "stop"
@@ -117,3 +117,7 @@ escape hatches to shrink over time, not accumulate.
117
117
  - `catladder-releases` — releasing (semantic-release / changesets)
118
118
  - `catladder-pipelines` — how the generated pipelines work, debugging CI
119
119
  - `catladder-cli` — non-interactive CLI usage and command reference
120
+ - `catladder-migrate-ci-backend` — moving a project between GitLab CI
121
+ and GitHub Actions
122
+ - `catladder-migrate-release-method` — switching between
123
+ semantic-release and changesets
@@ -0,0 +1,249 @@
1
+ ---
2
+ name: catladder-migrate-ci-backend
3
+ description: Guiding a catladder project from one CI backend to the other — GitLab CI → GitHub Actions or GitHub Actions → GitLab CI — using the `pipelines` config to run both in parallel first, then cutting over. Covers the blockers to check up front (unsupported features, secrets vault, release tokens, container registry) and the manual cleanup a cut-over needs. Use when moving a project between GitLab and GitHub, enabling a second CI backend, or decommissioning one. Triggers on "migrate to github", "move to github actions", "switch from gitlab to github", "migrate to gitlab", "enable github pipeline", "run both pipelines", "gitRemote", "secrets-sync-github".
4
+ ---
5
+
6
+ # Migrating between GitLab CI and GitHub Actions
7
+
8
+ catladder generates pipelines for both CI systems from the same
9
+ `catladder.ts`, and **both can be enabled at once**. That is what makes
10
+ this migration safe: run the new backend in parallel with the old one on
11
+ the same commits, compare, and only then cut over.
12
+
13
+ ```ts
14
+ pipelines: {
15
+ gitlab: true,
16
+ github: { gitRemote: "github" },
17
+ }
18
+ ```
19
+
20
+ (`pipelineType: "gitlab"` is the deprecated single-backend form —
21
+ replace it with `pipelines` as the first step.)
22
+
23
+ Never do this as one big-bang commit. Work through the steps below and
24
+ **stop at each decision point that is the user's to make** — creating
25
+ repositories, pushing to a new host, and moving secrets are all
26
+ outward-facing actions that need their explicit go-ahead.
27
+
28
+ ## Step 0 — scope it with the user
29
+
30
+ Ask up front (once, with your reading of the repo attached):
31
+
32
+ - **Direction and end state**: is the old backend going away, or do both
33
+ stay live permanently (some projects keep GitLab for releases and
34
+ GitHub for PR checks)?
35
+ - **Repository**: does the target repo already exist, or must it be
36
+ created and pushed?
37
+ - **Which backend owns releases** during the parallel phase — see step 6.
38
+ - **Where secret values live afterwards** — see step 5. This is the one
39
+ that blocks decommissioning, so raise it early rather than at the end.
40
+
41
+ ## Step 1 — check the blockers before changing anything
42
+
43
+ Read the project's `catladder.ts` and report what will *not* carry over.
44
+ Going **GitLab → GitHub**:
45
+
46
+ - **`deploy: { type: "pages" }`** — not supported on the GitHub backend.
47
+ Those components simply generate no deploy jobs there (a warning, not
48
+ a generation error), so the site silently stops being published.
49
+ - **`agents`** (the Claude agent integration) — GitLab-only; agent jobs
50
+ are skipped on GitHub.
51
+ - **Default branch must be `main`** — the generated GitHub main-branch
52
+ workflow triggers on `push: branches: [main]`. A project on `master`
53
+ or `develop` gets a workflow that never runs.
54
+ - **`hooks.transformYamlBeforeWrite` / `transformFileBeforeWrite`** —
55
+ hooks run for the GitHub files too. Any hook that keys on
56
+ `.gitlab-ci.yml` or on GitLab's YAML shape needs revisiting; a hook
57
+ that blindly rewrites `data.include` will corrupt workflow files.
58
+ - **`customJobs`** do carry over (they are platform-neutral job specs),
59
+ but anything GitLab-shaped *inside* them — raw `rules:`, `only:`,
60
+ GitLab-specific job keys — does not.
61
+ - **`secrets.vault: "gitlab"`** (the default) requires a GitLab project.
62
+ See step 5.
63
+
64
+ Going **GitHub → GitLab** the feature list is easier (GitLab supports
65
+ everything the GitHub backend does), but still check the hooks, and note
66
+ that GitLab needs a real access token for releases (step 6).
67
+
68
+ Report this list to the user before touching the config. If a blocker
69
+ has no first-class solution, that is worth an issue on catladder —
70
+ https://git.panter.ch/catladder/catladder/-/issues.
71
+
72
+ ## Step 2 — get the repository onto the target host
73
+
74
+ For a new GitHub repo: create it and push `main` plus **all tags**
75
+ (the release job derives the version from the nearest `v*` tag — a
76
+ tagless mirror re-releases `1.0.0`).
77
+
78
+ Add it as a **second remote** rather than replacing `origin`, so the old
79
+ backend keeps working during the parallel phase:
80
+
81
+ ```bash
82
+ git remote add github git@github.com:<owner>/<repo>.git
83
+ git push github main --tags
84
+ ```
85
+
86
+ Ask before creating the repository or pushing — this publishes the code
87
+ to a new host.
88
+
89
+ ## Step 3 — enable both backends
90
+
91
+ ```ts
92
+ pipelines: {
93
+ gitlab: true,
94
+ github: { gitRemote: "github" },
95
+ }
96
+ ```
97
+
98
+ `gitRemote` tells catladder which git remote points at that CI system's
99
+ repository (default `origin`). Then regenerate and commit:
100
+
101
+ ```bash
102
+ yarn catenv
103
+ ```
104
+
105
+ The two file sets live side by side and never collide:
106
+
107
+ | backend | generated files |
108
+ |---|---|
109
+ | gitlab | `.gitlab-ci.yml`, `.catladder-generated/gitlab/` |
110
+ | github | `.github/workflows/catladder-*.yml`, `.catladder-generated/github/scripts/` |
111
+
112
+ Shared (generated once for both): `.catladder-generated/images/` and
113
+ `.catladder-generated/catci/`.
114
+
115
+ If the hosted runners of the new backend are smaller than the old ones,
116
+ tune them per backend instead of globally:
117
+
118
+ ```ts
119
+ github: { gitRemote: "github", runnerVariables: { TURBO_CONCURRENCY: "2" } },
120
+ ```
121
+
122
+ ## Step 4 — mirror the secrets to the new backend
123
+
124
+ GitHub Actions cannot read the vault at runtime, so the values must be
125
+ pushed into GitHub:
126
+
127
+ ```bash
128
+ gh auth login
129
+ yarn catladder project secrets-sync-github
130
+ ```
131
+
132
+ This creates one **GitHub environment per catladder env** and writes
133
+ each secret/variable into the environment(s) whose generated jobs
134
+ actually reference it. (Environments are not cosmetic: GitHub caps a
135
+ repository at 100 secrets, which a multi-env project blows through; the
136
+ cap applies per environment.) Re-run it after every secret change for as
137
+ long as both backends are live.
138
+
139
+ Then verify:
140
+
141
+ ```bash
142
+ yarn catladder project doctor
143
+ ```
144
+
145
+ It compares the secret/variable *names* the generated workflows
146
+ reference against what is actually set, per environment, and prints the
147
+ command that heals each gap. Values cannot be compared — GitHub secrets
148
+ are write-only by design.
149
+
150
+ ## Step 5 — the secrets vault is what actually blocks decommissioning
151
+
152
+ `secrets.vault` defaults to `"gitlab"`: the GitLab project variables
153
+ double as the **readable source of truth**, and the CI backends only
154
+ ever get mirrored copies. GitHub secrets can never serve as that source.
155
+
156
+ So if GitLab is going away, move the vault **before** deleting anything:
157
+
158
+ ```ts
159
+ secrets: {
160
+ vault: { type: "bitwarden", collection: "catladder" },
161
+ }
162
+ ```
163
+
164
+ A safe order:
165
+
166
+ 1. `yarn catladder project secrets-pull <env>: --out /tmp/secrets.yml`
167
+ against the old vault (unset values show as `🚨 FILL ME`)
168
+ 2. switch `secrets.vault` in `catladder.ts`, `yarn catenv`
169
+ 3. `yarn catladder project secrets-push <env>: --file /tmp/secrets.yml`
170
+ 4. `yarn catladder project doctor`, then delete the temp file
171
+ 5. only now decommission the GitLab project
172
+
173
+ Never commit the intermediate file, and see the `catladder-secrets`
174
+ skill for the exact command surface.
175
+
176
+ ## Step 6 — releases and tokens
177
+
178
+ - **GitLab** needs a `GL_TOKEN` project access token (named
179
+ `semantic-release`) to push the release commit and tag. It is created
180
+ and rotated by `yarn catladder project setup`.
181
+ - **GitHub** needs nothing extra — the release jobs use the built-in
182
+ `github.token`. Tags pushed by that token do not retrigger workflows,
183
+ so the release job dispatches the tagged-release workflow explicitly;
184
+ that is built in and needs no configuration.
185
+ - **Only one backend should own releases at a time.** Both backends
186
+ generate release jobs, and with `releases: { when: "auto" }` both would
187
+ try to tag the same commit. During the parallel phase, agree with the
188
+ user which backend releases; the other one's release job must stay
189
+ unclicked (`when: "manual"`) or the parallel phase must be short.
190
+
191
+ See the `catladder-releases` skill for how the release jobs differ
192
+ (GitLab manual job vs. GitHub dispatch workflow).
193
+
194
+ ## Step 7 — registry and the first run
195
+
196
+ The container registry changes with the backend: `$CI_REGISTRY_IMAGE` on
197
+ GitLab, `ghcr.io/<owner>/<repo>` on GitHub. Consequences to state
198
+ explicitly:
199
+
200
+ - The first pipeline on the new backend **rebuilds every job image and
201
+ every app image** — it will be slow, and that is expected, not a bug.
202
+ - The deploy target must be able to **pull from the new registry**.
203
+ catladder does not manage image-pull credentials on the cluster or on
204
+ Cloud Run, so check GHCR package visibility / the pull secret with
205
+ whoever owns the target *before* pointing a real environment at it.
206
+
207
+ ## Step 8 — run both, compare, then cut over
208
+
209
+ Let both pipelines run on the same commits until the new backend is
210
+ green for all three triggers: a merge/pull request, the main branch, and
211
+ a tagged release. Only then flip:
212
+
213
+ ```ts
214
+ pipelines: { github: true }
215
+ ```
216
+
217
+ ```bash
218
+ yarn catenv
219
+ ```
220
+
221
+ âš ī¸ **catladder only cleans up backends that are still enabled** — the
222
+ disabled backend's files stay behind and must be deleted by hand:
223
+
224
+ - gitlab: `.gitlab-ci.yml` and `.catladder-generated/gitlab/`
225
+ - github: `.github/workflows/catladder-*.yml` and
226
+ `.catladder-generated/github/scripts/`
227
+
228
+ Then, if the new host becomes `origin`, update the remotes and drop the
229
+ now-wrong `gitRemote`, regenerate once more, and finish with
230
+ `yarn catladder project doctor`.
231
+
232
+ ## After the cut-over
233
+
234
+ - Tell the team where the pipeline lives now and how manual actions
235
+ work there (on GitHub, manual jobs are dispatch workflows in the
236
+ Actions sidebar — `🚀 catladder create release`, `â–ļī¸ catladder deploy`,
237
+ `âšī¸ catladder stop`, `â†Šī¸ catladder rollback`).
238
+ - Revoke tokens that are no longer needed (`GL_TOKEN`).
239
+ - Keep the old host readable until the vault migration is verified in
240
+ practice — a rotated secret is much cheaper to recover than a deleted
241
+ one.
242
+
243
+ ## Related skills
244
+
245
+ - `catladder-config` — `catladder.ts` structure, `pipelines`, regeneration
246
+ - `catladder-secrets` — vault, `secrets-sync-github`, secret commands
247
+ - `catladder-releases` — release jobs and how they differ per backend
248
+ - `catladder-pipelines` — triggers, generated file layout, debugging CI
249
+ - `catladder-cli` — `project doctor`, `project setup`, non-interactive usage
@@ -0,0 +1,233 @@
1
+ ---
2
+ name: catladder-migrate-release-method
3
+ description: Migrating a catladder project from one release method to another — semantic-release (conventional commits) ↔ changesets (`.changeset/*.md` files) — including backfilling changesets for everything merged since the last release tag so the changelog and the version bump stay correct. Use when switching `releases.method`, adopting changesets in a project that has been releasing with semantic-release, or going back. Triggers on "switch to changesets", "migrate to changesets", "adopt changesets", "use changesets instead", "back to semantic-release", "change the release method", "backfill changesets".
4
+ ---
5
+
6
+ # Migrating the release method
7
+
8
+ catladder projects release either with **semantic-release** (version
9
+ derived from conventional commit messages) or with **changesets**
10
+ (version derived from `.changeset/*.md` files that developers write
11
+ deliberately). The method is one field in `catladder.ts`:
12
+
13
+ ```ts
14
+ releases: {
15
+ when: "manual",
16
+ method: "changesets", // "semantic-release" (default) | "changesets"
17
+ }
18
+ ```
19
+
20
+ Flipping that field is **not** the whole migration. Read
21
+ `catladder-releases` first if you are unsure how either method works.
22
+
23
+ ## Why a config flip is not enough
24
+
25
+ The two methods disagree about where "what changed since the last
26
+ release" lives:
27
+
28
+ | | semantic-release | changesets |
29
+ |---|---|---|
30
+ | source of the bump | commit messages since the last `v*` tag | pending `.changeset/*.md` |
31
+ | changelog text | generated from those commits | the changeset summaries |
32
+
33
+ Everything up to and including the **last `v*` tag** is already written
34
+ into `CHANGELOG.md` — that part is settled and must not be touched.
35
+ But everything merged **after** that tag is exactly what semantic-release
36
+ would have released next, and after the switch the release job can no
37
+ longer see it. Without a backfill:
38
+
39
+ - those changes silently disappear from the changelog, and
40
+ - the next version is likely too small (a `feat:` merged since the tag
41
+ releases as a patch, or nothing releases at all).
42
+
43
+ So the migration is: **backfill changesets for the commits since the
44
+ last release tag, then flip the config, in one merge request.**
45
+
46
+ ## Step 1 — establish the range
47
+
48
+ Work on a branch (never directly on the main branch).
49
+
50
+ ```bash
51
+ git fetch --tags
52
+ git describe --tags --abbrev=0 --match "v[0-9]*"
53
+ ```
54
+
55
+ That is the same tag the release job uses. Then list the candidates:
56
+
57
+ ```bash
58
+ git log <tag>..HEAD --no-merges --pretty=format:"%h %s"
59
+ ```
60
+
61
+ Cases:
62
+
63
+ - **No `v*` tag at all** — the project has never released. There is
64
+ nothing to backfill; the first changesets release will be `1.0.0`
65
+ regardless of the bump levels. Skip to step 5.
66
+ - **The tag is the current HEAD** — nothing merged since the release,
67
+ nothing to backfill. Skip to step 5.
68
+
69
+ ## Step 2 — drop what is not user-facing
70
+
71
+ From that list, remove:
72
+
73
+ - `chore(release): X.Y.Z` commits (semantic-release's own commits)
74
+ - pure `chore:` / `ci:` / `build:` / `test:` / internal-refactor commits
75
+ - documentation-only changes, unless the docs *are* the product
76
+
77
+ Keep everything a user of this app would notice. When a commit message
78
+ is too vague to judge, look at the diff (`git show <sha> --stat`) before
79
+ deciding — do not guess from the subject line alone.
80
+
81
+ ## Step 3 — group the remaining commits
82
+
83
+ A changeset is **one user-facing change**, not one commit. Several
84
+ commits that built the same feature belong in one changeset; one commit
85
+ that shipped two unrelated things belongs in two.
86
+
87
+ **Decide yourself** when the remaining set is small (roughly ≤ 5
88
+ commits) and the grouping is obvious — then show the user the resulting
89
+ changesets and let them correct.
90
+
91
+ **Ask the user first** when there are many commits, or when the grouping
92
+ is genuinely ambiguous. Ask once, with a concrete proposal attached
93
+ rather than an open question — for example:
94
+
95
+ - the proposed grouping as a list ("these 4 commits → one changeset
96
+ *Export endpoint*"), asking whether to merge or split any of them
97
+ - the commits you could not classify: user-facing or internal?
98
+ - any change they consider **breaking** — that alone decides whether the
99
+ next release is a major
100
+
101
+ Do not ask about things you can answer from the diff.
102
+
103
+ ## Step 4 — pick the bump level per changeset
104
+
105
+ - **major** — breaking change (`feat!:`/`fix!:`, or a `BREAKING CHANGE:`
106
+ footer, or anything that forces consumers to adapt)
107
+ - **minor** — a new feature (`feat:`)
108
+ - **patch** — fixes, performance, reverts, and anything else user-facing
109
+
110
+ Only the **highest** pending bump decides the next version, but write
111
+ the honest level on each changeset anyway: the changelog groups by it.
112
+ Note that `0.x` versions get no special treatment — a minor bump on
113
+ `v0.3.1` produces `0.4.0`.
114
+
115
+ ## Step 5 — write the changeset files
116
+
117
+ One file per changeset, `.changeset/<kebab-case-slug>.md`:
118
+
119
+ ```md
120
+ ---
121
+ "<appName>": minor
122
+ ---
123
+
124
+ One or two sentences describing the change from the user's perspective.
125
+ ```
126
+
127
+ - The package name in the frontmatter is **ignored** (catladder versions
128
+ from git tags, not from package.json) — use the project's `appName`
129
+ for readability.
130
+ - Write the summary for someone reading the changelog, not the commit
131
+ log: what changed and why it matters, not `refactor foo helper`.
132
+ - Reference the MR/PR or issue when it helps the reader.
133
+
134
+ Also add `.changeset/README.md` so the next developer knows what the
135
+ folder is:
136
+
137
+ ````md
138
+ # Changesets
139
+
140
+ This repo releases with catladder's `changesets` release method: every
141
+ user-facing change merges together with a changeset file in this
142
+ directory. The release job consumes all pending changesets — the highest
143
+ bump (applied to the last `v*` tag) becomes the new version, the
144
+ summaries become the changelog entries.
145
+
146
+ Write one by hand (`.changeset/<descriptive-name>.md`):
147
+
148
+ ```md
149
+ ---
150
+ "<appName>": minor
151
+ ---
152
+
153
+ One or two sentences describing the change from the user's perspective.
154
+ ```
155
+
156
+ Bump levels: `major` (breaking), `minor` (feature), `patch` (fix).
157
+ The package name in the frontmatter is ignored — only the bump counts.
158
+ `yarn changeset` also works if you prefer the interactive prompt.
159
+
160
+ No pending changesets on the main branch means the release job is a
161
+ no-op.
162
+ ````
163
+
164
+ ## Step 6 — flip the config and regenerate
165
+
166
+ ```ts
167
+ releases: {
168
+ when: "manual", // unchanged — `when` is independent of `method`
169
+ method: "changesets",
170
+ }
171
+ ```
172
+
173
+ ```bash
174
+ yarn catenv
175
+ ```
176
+
177
+ Commit the regenerated files (`.catladder-generated/`, `.gitlab-ci.yml`,
178
+ `.github/workflows/catladder-*`) together with the config change and the
179
+ changesets — see the `catladder-config` skill.
180
+
181
+ ## Step 7 — verify before merging
182
+
183
+ - State the expected outcome explicitly: *last tag `vX.Y.Z` + highest
184
+ pending bump = next release `vA.B.C`*. If that is not what the user
185
+ expects, the bump levels are wrong.
186
+ - The merge request now runs the **`đŸĻ‹ changeset check`** job. It should
187
+ report the pending changesets and that same version. A yellow check
188
+ only means "this MR adds no changeset" — for the migration MR itself
189
+ that is not the case, so expect it green.
190
+ - Do **not** hand-write `CHANGELOG.md`. The release job prepends the new
191
+ section; anything you write by hand there gets duplicated.
192
+ - Leave any `.releaserc` / `release.config.js` alone if the project has
193
+ one — it becomes inert, and deleting it is a separate cleanup.
194
+
195
+ ## Step 8 — hand over to the team
196
+
197
+ After merging, tell the user what changes for everyone:
198
+
199
+ - every user-facing MR now needs a `.changeset/*.md` file; the
200
+ `đŸĻ‹ changeset check` job warns (without blocking) when one is missing
201
+ - commit messages no longer decide the version — conventional commits
202
+ are still nice, but purely cosmetic now
203
+ - if something was merged without a changeset and must ship anyway, the
204
+ **force release** escape hatch releases a patch (GitLab:
205
+ `âš ī¸ force create release`, GitHub: the *force* checkbox of the
206
+ `🚀 catladder create release` workflow)
207
+
208
+ ## The other direction: changesets → semantic-release
209
+
210
+ Same shape, mirrored, with one loss to flag up front:
211
+
212
+ 1. **Pending changesets are not converted.** Their summaries would
213
+ simply vanish from the changelog. Either cut one last changesets
214
+ release first (draining `.changeset/`), or fold each summary into the
215
+ changelog by hand before removing the files. Tell the user which one
216
+ you are doing.
217
+ 2. Commits since the last tag must carry conventional messages for
218
+ semantic-release to see them — history cannot be rewritten after the
219
+ fact, so accept that the first semantic-release version may
220
+ under-report. Say so rather than silently releasing a patch.
221
+ 3. Set `method: "semantic-release"` (or drop the field — it is the
222
+ default), run `yarn catenv`, commit the regenerated files.
223
+ 4. Delete `.changeset/` including its `README.md`.
224
+ 5. From then on the version comes from commit messages again, and the
225
+ `đŸĻ‹ changeset check` job disappears from MR pipelines.
226
+
227
+ ## Related skills
228
+
229
+ - `catladder-releases` — how both methods work, the release jobs and the
230
+ security-audit gate
231
+ - `catladder-config` — catladder.ts structure and regeneration
232
+ - `catladder-migrate-ci-backend` — migrating between GitLab CI and
233
+ GitHub Actions
@@ -25,10 +25,13 @@ Generated layout:
25
25
  "generated by catladder" header; others are unmanaged). Manual
26
26
  actions are per-kind `workflow_dispatch` workflows, visually grouped
27
27
  by their emoji prefix in the Actions sidebar: `🚀 catladder create
28
- release`, `â–ļī¸ catladder deploy` (manual-gated deploys), `âšī¸ catladder
28
+ release`, `â–ļī¸ catladder deploy` (manual-gated deploys), `🛑 catladder
29
29
  stop`, `â†Šī¸ catladder rollback` — when all of a kind's tasks target one
30
30
  env the name carries it (e.g. `â–ļī¸ catladder deploy prod`); otherwise
31
- pick the task from the workflow's dropdown (gitlab runs these as manual jobs inside the pipeline
31
+ pick the task from the workflow's dropdown. Review apps are stopped
32
+ automatically when their pull request closes (`🛑 catladder stop
33
+ review app`); the same workflow can be dispatched with a PR number to
34
+ tear one down while the pull request is still open (gitlab runs these as manual jobs inside the pipeline
32
35
  instead).
33
36
 
34
37
  Stages: setup → test → build → deploy → verify (post-deploy checks),
@@ -93,3 +96,5 @@ Useful CLI commands (see the `catladder-cli` skill for invocation):
93
96
  - `catladder-releases` — the `taggedRelease` flow and security-audit gate
94
97
  - `catladder-secrets` — env vars and secret management
95
98
  - `catladder-cli` — non-interactive CLI usage and command reference
99
+ - `catladder-migrate-ci-backend` — moving between GitLab CI and GitHub
100
+ Actions (running both in parallel, then cutting over)
@@ -127,3 +127,6 @@ the `security` commands in the `catladder-cli` reference.
127
127
  - `catladder-config` — catladder.ts structure and regeneration
128
128
  - `catladder-pipelines` — the `taggedRelease` trigger and stage/prod deploys
129
129
  - `catladder-cli` — `project ci` and `security` commands
130
+ - `catladder-migrate-release-method` — switching an existing project
131
+ between semantic-release and changesets (incl. backfilling changesets
132
+ for everything merged since the last release tag)
@@ -107,3 +107,5 @@ suspecting anything else.
107
107
  - `catladder-deploys` — runtime deploy config
108
108
  - `catladder-pipelines` — how the generated pipelines work
109
109
  - `catladder-cli` — non-interactive CLI usage and command reference
110
+ - `catladder-migrate-ci-backend` — moving between CI backends (the vault
111
+ is what blocks decommissioning the old one)