@cleocode/cleo 2026.5.105 → 2026.5.107

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.
@@ -1,322 +0,0 @@
1
- # CLEO release pipeline — Rollback workflow.
2
- #
3
- # Generated from packages/cleo/templates/workflows/release-rollback.yml.tmpl by
4
- # `cleo init --workflows` (T9531). DO NOT EDIT the rendered file directly —
5
- # extend via `.workflow-overrides.yml` per SPEC-T9345 R-260.
6
- #
7
- # Implements SPEC-T9345-release-pipeline-v2.md §5.4 (R-250 — R-257, R-260 —
8
- # R-263). Triggers ONLY on `workflow_dispatch` with `version`, `mode`
9
- # (`metadata-only|full`), and `reason` inputs. For `mode=full`, opens a
10
- # revert PR against `main` (NEVER pushes directly to `main` per ADR-065),
11
- # then runs `npm deprecate` (best-effort) and finally records the rollback
12
- # in the provenance graph via `cleo release reconcile --rollback`.
13
- #
14
- # Placeholders (replaced at scaffold time — see workflows/README.md):
15
- # <NODE_VERSION> Node.js version (e.g. "22.x")
16
- # <PUBLISHERS> Space-separated publisher list (e.g. "npm cargo")
17
- # <NPM_PACKAGES> Space-separated npm package names to deprecate
18
- # (e.g. "@cleocode/cleo @cleocode/core")
19
- # <CARGO_CRATES> Space-separated cargo crate names to yank
20
- # (e.g. "cleo-core cleo-cli")
21
- # (Angle-brackets in this doc comment are intentional — they avoid being
22
- # substituted by the {{...}} regex pass. The placeholders below use {{...}}.)
23
-
24
- name: Release Rollback
25
-
26
- # R-250: workflow_dispatch only — rollback is an explicit operator action,
27
- # never an automatic response to push/release events.
28
- # R-251: required inputs `version`, `mode` (choice), `reason` (free text).
29
- on:
30
- workflow_dispatch:
31
- inputs:
32
- version:
33
- description: 'Version to roll back (e.g. v2026.6.0)'
34
- type: string
35
- required: true
36
- mode:
37
- description: 'Rollback mode — metadata-only deprecates packages; full also opens a revert PR'
38
- type: choice
39
- required: true
40
- options:
41
- - metadata-only
42
- - full
43
- reason:
44
- description: 'Human-readable reason recorded with the rollback'
45
- type: string
46
- required: true
47
-
48
- # R-252: workflow-level grants minimum read scope. The `revert` and
49
- # `deprecate` jobs escalate per-job. `pull-requests: write` and
50
- # `packages: write` MUST NOT be inherited workflow-wide so reconcile
51
- # CANNOT silently widen its blast radius.
52
- permissions:
53
- contents: read
54
-
55
- # R-257: serialize rollbacks against the same version. cancel-in-progress
56
- # is FALSE so an in-flight rollback completes rather than leaving the
57
- # revert PR / npm deprecate state half-applied.
58
- concurrency:
59
- group: rollback-${{ inputs.version }}
60
- cancel-in-progress: false
61
-
62
- # R-262: bash everywhere — no cross-platform shell drift.
63
- defaults:
64
- run:
65
- shell: bash
66
-
67
- jobs:
68
- # R-253 (1/4): validate the rollback request. Confirms the tag exists
69
- # and resolves the release commit SHA so the `revert` job has a fixed
70
- # target. A missing tag MUST fail fast — no downstream side effects.
71
- validate:
72
- name: Validate rollback request
73
- runs-on: ubuntu-latest
74
- timeout-minutes: 5
75
- outputs:
76
- release_sha: ${{ steps.find.outputs.release_sha }}
77
- steps:
78
- # R-263: third-party Actions pinned to major+minor.
79
- - name: Checkout
80
- uses: actions/checkout@v4.1
81
- with:
82
- fetch-depth: 0
83
- timeout-minutes: 5
84
-
85
- - name: Resolve release commit SHA
86
- id: find
87
- run: |
88
- set -euo pipefail
89
- VERSION="${{ inputs.version }}"
90
- if ! git rev-parse --verify "$VERSION" >/dev/null 2>&1; then
91
- echo "::error::Tag $VERSION does not exist — cannot roll back a release that was never tagged."
92
- exit 1
93
- fi
94
- # The release commit is the commit the tag points to (peeled).
95
- RELEASE_SHA=$(git rev-list -n 1 "$VERSION")
96
- if [ -z "$RELEASE_SHA" ]; then
97
- echo "::error::Could not resolve commit SHA for tag $VERSION"
98
- exit 1
99
- fi
100
- echo "release_sha=$RELEASE_SHA" >> "$GITHUB_OUTPUT"
101
- echo "Resolved $VERSION -> $RELEASE_SHA"
102
- timeout-minutes: 2
103
-
104
- # R-253 (2/4) + R-254: open a revert PR for `mode=full`. NEVER pushes
105
- # directly to `main` (ADR-065). The PR title MUST be
106
- # `Revert release <version>: <reason>` and MUST carry the `rollback`
107
- # label so branch-protection / triage automation can route it.
108
- revert:
109
- name: Open revert PR
110
- needs: validate
111
- if: inputs.mode == 'full'
112
- runs-on: ubuntu-latest
113
- timeout-minutes: 15
114
- # R-252: per-job escalation — `contents: write` for the revert
115
- # branch commit; `pull-requests: write` for `gh pr create`. NO
116
- # `packages: write` here — that belongs to the `deprecate` job only.
117
- permissions:
118
- contents: write
119
- pull-requests: write
120
- env:
121
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
122
- VERSION: ${{ inputs.version }}
123
- REASON: ${{ inputs.reason }}
124
- RELEASE_SHA: ${{ needs.validate.outputs.release_sha }}
125
- steps:
126
- - name: Checkout
127
- uses: actions/checkout@v4.1
128
- with:
129
- fetch-depth: 0
130
- token: ${{ secrets.GITHUB_TOKEN }}
131
- timeout-minutes: 5
132
-
133
- - name: Configure git identity
134
- run: |
135
- git config user.email "actions@github.com"
136
- git config user.name "GitHub Actions"
137
- timeout-minutes: 1
138
-
139
- # R-254: cut `revert/<version>` branch, run `git revert -n` against
140
- # the resolved release commit, commit with the canonical subject.
141
- # Branch is cut from the action's checked-out main HEAD so the
142
- # revert applies on top of current main.
143
- - name: Create revert branch and commit
144
- run: |
145
- set -euo pipefail
146
- BRANCH="revert/${VERSION}"
147
- git checkout -b "$BRANCH"
148
- # `-n` (no-commit) lets us craft the subject line ourselves.
149
- # `-m 1` selects the first parent when the release commit is a
150
- # merge (the bump-PR merge into main), which is the canonical
151
- # release-commit shape for ADR-065 PR-gated releases.
152
- if git rev-parse --verify "${RELEASE_SHA}^2" >/dev/null 2>&1; then
153
- git revert -n -m 1 "$RELEASE_SHA"
154
- else
155
- git revert -n "$RELEASE_SHA"
156
- fi
157
- git commit -m "Revert release ${VERSION}: ${REASON}"
158
- timeout-minutes: 5
159
-
160
- # R-254 (continued): push the revert branch — NEVER `git push origin
161
- # main`. Branch protection on main is the second line of defence,
162
- # but the workflow MUST NOT rely on it.
163
- - name: Push revert branch
164
- run: git push -u origin "revert/${VERSION}"
165
- timeout-minutes: 5
166
-
167
- # R-254 (continued): open the revert PR with the canonical title
168
- # and the `rollback` label. The PR body links back to this
169
- # workflow run so reviewers can audit the rollback context.
170
- - name: Open revert PR
171
- run: |
172
- set -euo pipefail
173
- gh pr create \
174
- --base main \
175
- --head "revert/${VERSION}" \
176
- --title "Revert release ${VERSION}: ${REASON}" \
177
- --label rollback \
178
- --body "Automated rollback PR for release **${VERSION}**.
179
-
180
- **Reason**: ${REASON}
181
-
182
- This PR reverts commit \`${RELEASE_SHA}\` (the merge commit the
183
- release tag points to). It was opened by the \`release-rollback.yml\`
184
- workflow — see the workflow run for full context:
185
- ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
186
-
187
- After this PR merges, the package registries have already been marked
188
- as deprecated by the \`deprecate\` job, and the provenance graph has
189
- been updated by the \`reconcile-rollback\` job."
190
- timeout-minutes: 5
191
-
192
- # R-253 (3/4) + R-255: deprecate published artifacts. Best-effort —
193
- # `continue-on-error: true` so a transient registry hiccup CANNOT
194
- # block the reconcile-rollback step. Runs for BOTH `metadata-only`
195
- # and `full` modes since deprecation is the core rollback signal.
196
- deprecate:
197
- name: Deprecate published artifacts
198
- needs: validate
199
- runs-on: ubuntu-latest
200
- timeout-minutes: 15
201
- continue-on-error: true
202
- # R-252: per-job `packages: write` for npm deprecate. NO
203
- # `contents: write` — this job MUST NOT touch git refs.
204
- permissions:
205
- contents: read
206
- packages: write
207
- env:
208
- VERSION: ${{ inputs.version }}
209
- REASON: ${{ inputs.reason }}
210
- # Hoisted into env so downstream `contains(env.PUBLISHERS, ...)`
211
- # is a dynamic expression (avoids actionlint constant-expression
212
- # warnings — same pattern as release-publish.yml.tmpl).
213
- PUBLISHERS: '{{PUBLISHERS}}'
214
- NPM_PACKAGES: '{{NPM_PACKAGES}}'
215
- CARGO_CRATES: '{{CARGO_CRATES}}'
216
- steps:
217
- - name: Set up Node.js
218
- uses: actions/setup-node@v4.0
219
- with:
220
- node-version: '{{NODE_VERSION}}'
221
- registry-url: 'https://registry.npmjs.org'
222
- timeout-minutes: 3
223
-
224
- # R-255: `npm deprecate <pkg>@<version> "Rolled back: <reason>"`
225
- # per published artifact. Failure is NON-FATAL — each iteration
226
- # is wrapped so one package's failure cannot stop the others.
227
- - name: Deprecate npm packages
228
- if: contains(env.PUBLISHERS, 'npm')
229
- run: |
230
- set -uo pipefail
231
- STRIPPED="${VERSION#v}"
232
- for pkg in ${NPM_PACKAGES}; do
233
- if npm deprecate "$pkg@$STRIPPED" "Rolled back: ${REASON}"; then
234
- echo "Deprecated $pkg@$STRIPPED"
235
- else
236
- echo "::warning::Failed to deprecate $pkg@$STRIPPED — continuing"
237
- fi
238
- done
239
- env:
240
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
241
- timeout-minutes: 10
242
-
243
- # R-255 (extension for cargo): `cargo yank` is the cargo analogue
244
- # of `npm deprecate`. Also best-effort — a missing crate or a
245
- # rate-limit response MUST NOT block reconcile.
246
- - name: Yank cargo crates
247
- if: contains(env.PUBLISHERS, 'cargo')
248
- run: |
249
- set -uo pipefail
250
- STRIPPED="${VERSION#v}"
251
- for crate in ${CARGO_CRATES}; do
252
- if cargo yank --version "$STRIPPED" "$crate"; then
253
- echo "Yanked $crate@$STRIPPED"
254
- else
255
- echo "::warning::Failed to yank $crate@$STRIPPED — continuing"
256
- fi
257
- done
258
- env:
259
- CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_TOKEN }}
260
- timeout-minutes: 10
261
-
262
- # R-253 (4/4) + R-256: record the rollback in the provenance graph via
263
- # `cleo release reconcile <version> --rollback --reason "<reason>"`.
264
- # This writes `releases.rolled_back_at` and inserts a
265
- # `release_changes` row with `change_type='revert'`. Runs after
266
- # `validate` regardless of `revert` / `deprecate` outcome — provenance
267
- # must record the operator's intent even if individual side-effects
268
- # failed.
269
- reconcile-rollback:
270
- name: Record rollback in provenance graph
271
- needs:
272
- - validate
273
- if: always() && needs.validate.result == 'success'
274
- runs-on: ubuntu-latest
275
- timeout-minutes: 10
276
- env:
277
- VERSION: ${{ inputs.version }}
278
- REASON: ${{ inputs.reason }}
279
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
280
- steps:
281
- - name: Checkout
282
- uses: actions/checkout@v4.1
283
- with:
284
- fetch-depth: 0
285
- timeout-minutes: 5
286
-
287
- - name: Set up Node.js
288
- uses: actions/setup-node@v4.0
289
- with:
290
- node-version: '{{NODE_VERSION}}'
291
- registry-url: 'https://registry.npmjs.org'
292
- timeout-minutes: 3
293
-
294
- - name: Install cleo
295
- run: npm install -g @cleocode/cleo
296
- timeout-minutes: 5
297
-
298
- # R-256: reconcile MUST write rollback marker into `releases` +
299
- # `release_changes`. The CLI is the canonical writer — workflow
300
- # YAML never touches the DB directly.
301
- - name: Record rollback
302
- run: cleo release reconcile "${VERSION}" --rollback --reason "${REASON}"
303
- timeout-minutes: 5
304
-
305
- # Recovery hint in the job summary so an operator who sees a
306
- # half-applied rollback can finish the job from a local checkout.
307
- - name: Emit recovery hint
308
- if: always()
309
- run: |
310
- {
311
- echo "## Release Rollback — ${VERSION}"
312
- echo ""
313
- echo "**Mode**: \`${{ inputs.mode }}\`"
314
- echo "**Reason**: ${REASON}"
315
- echo ""
316
- echo "**Recovery commands** (if anything went wrong):"
317
- echo ""
318
- echo "\`\`\`"
319
- echo "cleo release reconcile ${VERSION} --rollback --reason \"${REASON}\""
320
- echo "\`\`\`"
321
- } >> "$GITHUB_STEP_SUMMARY"
322
- timeout-minutes: 1