@sdd-method/maintain-cli 0.2.0 → 0.4.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.
- package/NOTICE +1 -1
- package/dist/lib/briefing/render.js +1 -2
- package/dist/lib/briefing/variables.js +1 -2
- package/dist/lib/catalogue/aggregate.js +38 -39
- package/dist/lib/catalogue/application-archetype/expectations.js +1 -1
- package/dist/lib/catalogue/archetype/wireframe.js +1 -1
- package/dist/lib/catalogue/build.js +17 -23
- package/dist/lib/catalogue/builders/application-conformance-coverage.js +1 -1
- package/dist/lib/catalogue/builders/c4-components.js +4 -5
- package/dist/lib/catalogue/builders/c4-containers.js +4 -4
- package/dist/lib/catalogue/builders/c4-external-systems.js +1 -1
- package/dist/lib/catalogue/builders/c4-l1-candidates.js +2 -2
- package/dist/lib/catalogue/builders/capabilities.js +2 -2
- package/dist/lib/catalogue/builders/capability-edges.js +4 -4
- package/dist/lib/catalogue/builders/capability-spec-walker.js +3 -3
- package/dist/lib/catalogue/builders/container-dependency.js +19 -20
- package/dist/lib/catalogue/builders/contracts.js +5 -6
- package/dist/lib/catalogue/builders/data-store-mapping.js +4 -4
- package/dist/lib/catalogue/builders/edge-label-merge.js +2 -2
- package/dist/lib/catalogue/builders/entities-manifest.js +1 -1
- package/dist/lib/catalogue/builders/entities-mermaid.js +3 -3
- package/dist/lib/catalogue/builders/entity-slug.js +11 -12
- package/dist/lib/catalogue/builders/event-catalog-walker.js +3 -3
- package/dist/lib/catalogue/builders/evidence-class.js +2 -2
- package/dist/lib/catalogue/builders/external-system-exemption.js +1 -1
- package/dist/lib/catalogue/builders/feature-contract.js +1 -3
- package/dist/lib/catalogue/builders/feature-grounding.js +1 -2
- package/dist/lib/catalogue/builders/feature-groups.js +0 -1
- package/dist/lib/catalogue/builders/feature-subscription-channels.js +2 -3
- package/dist/lib/catalogue/builders/feature-tree-walker.js +1 -1
- package/dist/lib/catalogue/builders/features.js +3 -4
- package/dist/lib/catalogue/builders/glossary-xref.js +1 -1
- package/dist/lib/catalogue/builders/persona-c4system.js +7 -7
- package/dist/lib/catalogue/builders/persona-container.js +14 -14
- package/dist/lib/catalogue/builders/personas.js +1 -2
- package/dist/lib/catalogue/builders/product-platform-dependencies.js +2 -2
- package/dist/lib/catalogue/builders/quality-scores.js +1 -1
- package/dist/lib/catalogue/builders/repositories.js +2 -2
- package/dist/lib/catalogue/builders/screen-tree.js +2 -2
- package/dist/lib/catalogue/builders/screens.js +3 -4
- package/dist/lib/catalogue/builders/system-external-dependency.js +7 -7
- package/dist/lib/catalogue/canonical-schema.yaml +23 -23
- package/dist/lib/catalogue/contract-path-resolution.js +3 -3
- package/dist/lib/catalogue/coverage-predicates.js +4 -4
- package/dist/lib/catalogue/coverage-run.js +1 -1
- package/dist/lib/catalogue/dedupe-keyed-tables.js +0 -0
- package/dist/lib/catalogue/ext-id.js +4 -4
- package/dist/lib/catalogue/feature-grounding-aliases-loader.js +1 -1
- package/dist/lib/catalogue/finding-identity.js +3 -3
- package/dist/lib/catalogue/freshness.js +1 -1
- package/dist/lib/catalogue/manifest-loader.js +2 -2
- package/dist/lib/catalogue/scaffold-delta.js +1 -1
- package/dist/lib/catalogue/schema-compat.js +1 -2
- package/dist/lib/catalogue/scores-coverage.js +1 -1
- package/dist/lib/catalogue/screen-validation-loader.js +1 -1
- package/dist/lib/catalogue/service-contract-aliases-loader.js +1 -1
- package/dist/lib/catalogue/staleness-verdicts.js +2 -2
- package/dist/lib/catalogue/traceability-loader.js +2 -3
- package/dist/lib/ci-binding/index.js +147 -35
- package/dist/lib/ci-binding/module.js +74 -2
- package/dist/lib/hooks-seed/entries.js +43 -21
- package/dist/lib/hooks-seed/index.js +1 -1
- package/dist/lib/init/ci-workflow.js +26 -2
- package/dist/lib/lock-file.js +11 -1
- package/dist/lib/pack/builder.js +56 -4
- package/dist/lib/pack/index.js +1 -0
- package/dist/lib/pack/publish-transform.js +1 -1
- package/dist/lib/pack/reference-closure.js +3 -3
- package/dist/lib/package-version.js +1 -1
- package/dist/lib/snapshot/manifest.js +1 -1
- package/dist/lib/sync/adr-transitions.js +1 -1
- package/dist/lib/sync/diff.js +1 -1
- package/dist/lib/sync/index.js +34 -5
- package/dist/lib/sync/settings-merge.js +1 -1
- package/dist/lib/validate/index.js +1 -1
- package/dist/lib/validate/plan.js +7 -4
- package/dist/lib/validate/yaml-parse.js +1 -1
- package/dist/maintain-program.js +1 -1
- package/dist/maintain-version.json +2 -2
- package/dist/verbs/pack-build.js +22 -0
- package/dist/verbs/sync.js +18 -1
- package/dist/verbs/validate.js +6 -1
- package/dist/verify-program.js +1 -1
- package/dist/verify-version.json +2 -2
- package/package.json +2 -6
|
@@ -8,14 +8,21 @@
|
|
|
8
8
|
* wrote is pristine and re-renders with no override, and only an edited copy needs --allow-overwrite.
|
|
9
9
|
* The binding is remembered in the lock too (`ci_binding:`, absent meaning github), so an estate
|
|
10
10
|
* chooses it once.
|
|
11
|
+
*
|
|
12
|
+
* `local` is the binding with no host: the module is written and nothing calls it but people, for an
|
|
13
|
+
* estate that validates on its own machines only (the module is run with `dagger call` there). Sync
|
|
14
|
+
* removes the wrapper this tool wrote for the previous binding when its block still hashes to the
|
|
15
|
+
* lock's digest; an edited one is drift, and a file without markers is the adopter's and stays.
|
|
11
16
|
*/
|
|
12
|
-
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
17
|
+
import { access, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
13
18
|
import { createHash } from "node:crypto";
|
|
14
19
|
import { dirname, join } from "node:path";
|
|
15
20
|
import { BLOCK_BEGIN, BLOCK_END, WORKFLOW_REL_PATH, renderMethodManagedWorkflowBlock, renderMethodValidationWorkflow, } from "../init/ci-workflow.js";
|
|
16
21
|
import { detectBlockDrift, writeMarkedBlockFile } from "../sync/yaml-block-merge.js";
|
|
17
|
-
import { DAGGER_VERSION, MODULE_CALL, MODULE_DIR, moduleSha256, renderModuleFiles } from "./module.js";
|
|
18
|
-
|
|
22
|
+
import { DAGGER_VERSION, MODULE_CALL, MODULE_DIR, REBUILD_FLAG, REBUILD_SCRIPT, moduleSha256, renderModuleFiles } from "./module.js";
|
|
23
|
+
/** The bindings that render a wrapper for a CI host. */
|
|
24
|
+
export const HOST_BINDINGS = ["github", "gitlab", "jenkins"];
|
|
25
|
+
export const CI_BINDINGS = [...HOST_BINDINGS, "local"];
|
|
19
26
|
/** M-ADR 0219 §8.2 (ruled 2026-10-16): what every SDD has before it chooses. */
|
|
20
27
|
export const DEFAULT_CI_BINDING = "github";
|
|
21
28
|
/**
|
|
@@ -31,9 +38,24 @@ export function parseCiRunner(s) {
|
|
|
31
38
|
export function isCiBinding(s) {
|
|
32
39
|
return CI_BINDINGS.includes(s);
|
|
33
40
|
}
|
|
41
|
+
export function isHostBinding(s) {
|
|
42
|
+
return HOST_BINDINGS.includes(s);
|
|
43
|
+
}
|
|
34
44
|
const HASH = { begin: BLOCK_BEGIN, end: BLOCK_END };
|
|
35
45
|
// Groovy has no '#' comments.
|
|
36
46
|
const SLASH = { begin: "// method-managed: begin", end: "// method-managed: end" };
|
|
47
|
+
// How each host's adopter switches the catalogue rebuild on (M-ADR 0220 §4.4): the variable the method names.
|
|
48
|
+
const REBUILD_SETUP = {
|
|
49
|
+
"#": [
|
|
50
|
+
"set the masked (not protected) CI variable",
|
|
51
|
+
"SDD_CATALOGUE_PUSH_TOKEN to a project or group access token with write_repository.",
|
|
52
|
+
],
|
|
53
|
+
"//": [
|
|
54
|
+
"set the job's environment variable",
|
|
55
|
+
"SDD_CATALOGUE_PUSH_CREDENTIAL to the ID of a username-and-password credential (a token",
|
|
56
|
+
"with write) for the repository's https remote.",
|
|
57
|
+
],
|
|
58
|
+
};
|
|
37
59
|
function header(comment, profile) {
|
|
38
60
|
return [
|
|
39
61
|
`${comment} Method Validation per M-ADR 0129, run by the method's Dagger module (M-ADR 0219).`,
|
|
@@ -47,6 +69,11 @@ function header(comment, profile) {
|
|
|
47
69
|
comment,
|
|
48
70
|
`${comment} Run it locally:`,
|
|
49
71
|
`${comment} ${MODULE_CALL}`,
|
|
72
|
+
comment,
|
|
73
|
+
`${comment} Catalogue rebuild (M-ADR 0220 §4.4), off until you configure it:`,
|
|
74
|
+
...REBUILD_SETUP[comment].map((l) => `${comment} ${l}`),
|
|
75
|
+
`${comment} The job then rebuilds the catalogues and pushes them to the branch it is`,
|
|
76
|
+
`${comment} building (never a fork's), and the freshness step gates.`,
|
|
50
77
|
];
|
|
51
78
|
}
|
|
52
79
|
// GitLab: a Docker-in-Docker service, the shape GitLab documents for Dagger. The runner must allow
|
|
@@ -71,10 +98,16 @@ function gitlabBlock(profile, runner) {
|
|
|
71
98
|
' - if: $CI_PIPELINE_SOURCE == "merge_request_event"',
|
|
72
99
|
" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH",
|
|
73
100
|
" before_script:",
|
|
74
|
-
" - apk add --no-cache curl",
|
|
101
|
+
" - apk add --no-cache curl git",
|
|
75
102
|
` - curl -fsSL https://dl.dagger.io/dagger/install.sh | BIN_DIR=/usr/local/bin DAGGER_VERSION=${DAGGER_VERSION} sh`,
|
|
76
103
|
" script:",
|
|
77
|
-
|
|
104
|
+
" - |",
|
|
105
|
+
' if [ -n "${SDD_CATALOGUE_PUSH_TOKEN:-}" ] && [ "${CI_MERGE_REQUEST_SOURCE_PROJECT_ID:-$CI_PROJECT_ID}" = "$CI_PROJECT_ID" ]; then',
|
|
106
|
+
' SDD_REBUILD_BRANCH="${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME:-$CI_COMMIT_BRANCH}" \\',
|
|
107
|
+
' SDD_REBUILD_PUSH_URL="${CI_SERVER_PROTOCOL}://oauth2:${SDD_CATALOGUE_PUSH_TOKEN}@${CI_SERVER_URL#*://}/${CI_PROJECT_PATH}.git" \\',
|
|
108
|
+
` sh ${REBUILD_SCRIPT}`,
|
|
109
|
+
" fi",
|
|
110
|
+
` - ${MODULE_CALL} ${REBUILD_FLAG}`,
|
|
78
111
|
HASH.end,
|
|
79
112
|
].join("\n");
|
|
80
113
|
}
|
|
@@ -92,7 +125,18 @@ function jenkinsBlock(profile, runner) {
|
|
|
92
125
|
" stage('Method Validation') {",
|
|
93
126
|
" steps {",
|
|
94
127
|
` sh 'curl -fsSL https://dl.dagger.io/dagger/install.sh | BIN_DIR="$WORKSPACE@tmp/dagger" DAGGER_VERSION=${DAGGER_VERSION} sh'`,
|
|
95
|
-
|
|
128
|
+
" script {",
|
|
129
|
+
" if (env.SDD_CATALOGUE_PUSH_CREDENTIAL && !env.CHANGE_FORK) {",
|
|
130
|
+
" withCredentials([usernamePassword(credentialsId: env.SDD_CATALOGUE_PUSH_CREDENTIAL, usernameVariable: 'SDD_PUSH_USER', passwordVariable: 'SDD_PUSH_TOKEN')]) {",
|
|
131
|
+
" sh '''",
|
|
132
|
+
' export SDD_REBUILD_BRANCH="${CHANGE_BRANCH:-${BRANCH_NAME:-${GIT_BRANCH#origin/}}}"',
|
|
133
|
+
' export SDD_REBUILD_PUSH_URL="$(git config --get remote.origin.url | sed "s#^https://#https://${SDD_PUSH_USER}:${SDD_PUSH_TOKEN}@#")"',
|
|
134
|
+
` PATH="$WORKSPACE@tmp/dagger:$PATH" sh ${REBUILD_SCRIPT}`,
|
|
135
|
+
" '''",
|
|
136
|
+
" }",
|
|
137
|
+
" }",
|
|
138
|
+
" }",
|
|
139
|
+
` sh 'PATH="$WORKSPACE@tmp/dagger:$PATH" ${MODULE_CALL} ${REBUILD_FLAG}'`,
|
|
96
140
|
" }",
|
|
97
141
|
" }",
|
|
98
142
|
" }",
|
|
@@ -134,8 +178,41 @@ export function blockSha256(text, markers) {
|
|
|
134
178
|
export async function syncCiBinding(o) {
|
|
135
179
|
const failures = [];
|
|
136
180
|
const log = [];
|
|
181
|
+
const was = o.prevLock?.ciBinding;
|
|
182
|
+
const sameBinding = (was ?? DEFAULT_CI_BINDING) === o.binding;
|
|
183
|
+
if (o.binding === "local") {
|
|
184
|
+
// No host. A switch removes the wrapper this tool wrote for the previous binding, but only when its
|
|
185
|
+
// block still hashes to the lock's digest: an edited block is drift, a file without markers is the
|
|
186
|
+
// adopter's and stays.
|
|
187
|
+
if (o.runner !== undefined && o.runner.length > 0) {
|
|
188
|
+
failures.push("--ci-runner names where a CI host runs the job; the local binding has no host. Drop --ci-runner (or choose a host binding).");
|
|
189
|
+
}
|
|
190
|
+
let removal = null;
|
|
191
|
+
const previous = sameBinding ? null : was === undefined ? DEFAULT_CI_BINDING : isHostBinding(was) ? was : null;
|
|
192
|
+
if (previous !== null) {
|
|
193
|
+
const prevHost = HOST_WRAPPERS[previous];
|
|
194
|
+
const abs = join(o.targetRepo, prevHost.relPath);
|
|
195
|
+
const text = await tryRead(abs);
|
|
196
|
+
const sha = blockSha256(text, prevHost.markers);
|
|
197
|
+
if (text !== null && sha === null) {
|
|
198
|
+
log.push(`${prevHost.relPath} has no method-managed markers, so it is the adopter's: left in place.`);
|
|
199
|
+
}
|
|
200
|
+
else if (text !== null && (sha === o.prevLock?.ciWorkflowBlockSha256 || o.allowDrift)) {
|
|
201
|
+
removal = { abs, rel: prevHost.relPath, text, host: prevHost };
|
|
202
|
+
}
|
|
203
|
+
else if (text !== null) {
|
|
204
|
+
failures.push(`${prevHost.relPath}: the method-managed block was edited since the last sync (it no longer matches the lock's digest), so switching to the local binding will not remove it. Re-run with --allow-overwrite to remove the block; adopter content outside the markers is kept.`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const mod = await checkModule(o, failures, log);
|
|
208
|
+
if (o.checkOnly || failures.length > 0)
|
|
209
|
+
return { failures, log, wrapperSha: null, moduleSha: mod.shaOnDisk };
|
|
210
|
+
await writeModule(o, mod, log, "; the local binding calls it from no CI host");
|
|
211
|
+
if (removal !== null)
|
|
212
|
+
await removeWrapper(removal, o.profile, log);
|
|
213
|
+
return { failures, log, wrapperSha: null, moduleSha: mod.shaRendered };
|
|
214
|
+
}
|
|
137
215
|
const host = HOST_WRAPPERS[o.binding];
|
|
138
|
-
const sameBinding = (o.prevLock?.ciBinding ?? DEFAULT_CI_BINDING) === o.binding;
|
|
139
216
|
// 1. The host wrapper.
|
|
140
217
|
const wrapperAbs = join(o.targetRepo, host.relPath);
|
|
141
218
|
const existing = await tryRead(wrapperAbs);
|
|
@@ -153,49 +230,84 @@ export async function syncCiBinding(o) {
|
|
|
153
230
|
else if (markerless && !o.allowOverwrite) {
|
|
154
231
|
failures.push(`${host.relPath} exists without method-managed markers — refusing to overwrite adopter content. Re-run with --allow-overwrite to replace the file.`);
|
|
155
232
|
}
|
|
156
|
-
// 2. The module
|
|
233
|
+
// 2. The module.
|
|
234
|
+
const mod = await checkModule(o, failures, log);
|
|
235
|
+
if (o.checkOnly || failures.length > 0) {
|
|
236
|
+
return { failures, log, wrapperSha: null, moduleSha: mod.shaOnDisk };
|
|
237
|
+
}
|
|
238
|
+
const written = await writeMarkedBlockFile(wrapperAbs, existing === null ? host.file(o.profile, o.runner) : block, host.markers, o.allowOverwrite);
|
|
239
|
+
if (written.changed)
|
|
240
|
+
log.push(`${existing === null ? "Wrote" : "Refreshed"} ${host.relPath}.`);
|
|
241
|
+
await writeModule(o, mod, log, "");
|
|
242
|
+
// A binding switch between hosts leaves the previous host's file alone: it is the adopter's to delete
|
|
243
|
+
// once the new host runs, and deleting it here would remove a job some branch protection may still
|
|
244
|
+
// require. (From local there is no previous host file.)
|
|
245
|
+
if (!sameBinding && (was === undefined || isHostBinding(was))) {
|
|
246
|
+
const prevHost = HOST_WRAPPERS[was ?? DEFAULT_CI_BINDING];
|
|
247
|
+
if ((await tryRead(join(o.targetRepo, prevHost.relPath))) !== null) {
|
|
248
|
+
log.push(`CI binding is now ${o.binding}; ${prevHost.relPath} (the previous binding) is left in place. Delete it once ${host.relPath} runs.`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
failures,
|
|
253
|
+
log,
|
|
254
|
+
wrapperSha: blockSha256(await tryRead(wrapperAbs), host.markers),
|
|
255
|
+
moduleSha: mod.shaRendered,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* The module: every file it renders, compared as one set. Pristine when the files on disk hash to the
|
|
260
|
+
* lock's digest. The render may list a file the last sync never wrote (the generator gained one): it is
|
|
261
|
+
* absent, not edited, so the digest over the files present decides. A file the adopter deleted still
|
|
262
|
+
* changes that digest, so it is still drift.
|
|
263
|
+
*/
|
|
264
|
+
async function checkModule(o, failures, log) {
|
|
157
265
|
const rendered = renderModuleFiles();
|
|
158
266
|
const onDisk = [];
|
|
159
267
|
for (const [rel] of rendered)
|
|
160
268
|
onDisk.push([rel, await tryRead(join(o.targetRepo, MODULE_DIR, rel))]);
|
|
161
269
|
const present = onDisk.some(([, c]) => c !== null);
|
|
162
270
|
const matches = rendered.every(([rel, c], i) => onDisk[i][0] === rel && onDisk[i][1] === c);
|
|
163
|
-
const
|
|
271
|
+
const lockSha = o.prevLock?.ciModuleSha256;
|
|
272
|
+
const modulePristine = lockSha !== undefined &&
|
|
273
|
+
(moduleSha256(onDisk) === lockSha || moduleSha256(onDisk.filter(([, c]) => c !== null)) === lockSha);
|
|
164
274
|
if (present && !matches && modulePristine) {
|
|
165
275
|
log.push(`${MODULE_DIR}/: unmodified since the last sync (digest matches the lock); re-rendering it for this sdd-cli.`);
|
|
166
276
|
}
|
|
167
277
|
if (present && !matches && !modulePristine && !o.allowDrift) {
|
|
168
278
|
failures.push(`${MODULE_DIR}/: the method-managed module differs from what this sdd-cli renders. Either the generator changed since the last sync (expected after a CLI upgrade) or a file was hand-edited. Re-run with --allow-overwrite to re-render it.`);
|
|
169
279
|
}
|
|
170
|
-
|
|
171
|
-
|
|
280
|
+
return { rendered, present, matches, shaOnDisk: moduleSha256(onDisk), shaRendered: moduleSha256(rendered) };
|
|
281
|
+
}
|
|
282
|
+
async function writeModule(o, mod, log, note) {
|
|
283
|
+
if (mod.matches)
|
|
284
|
+
return;
|
|
285
|
+
for (const [rel, content] of mod.rendered) {
|
|
286
|
+
const abs = join(o.targetRepo, MODULE_DIR, rel);
|
|
287
|
+
await mkdir(dirname(abs), { recursive: true });
|
|
288
|
+
await writeFile(abs, content, "utf8");
|
|
172
289
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
290
|
+
log.push(`${mod.present ? "Refreshed" : "Wrote"} ${MODULE_DIR}/ (the Method Validation module, M-ADR 0219)${note}.`);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Remove a wrapper's method-managed block. The file goes with it when what remains is this tool's own
|
|
294
|
+
* template text (or nothing), since then no line of it is the adopter's; otherwise the rest is kept.
|
|
295
|
+
*/
|
|
296
|
+
async function removeWrapper(r, profile, log) {
|
|
297
|
+
const cut = (text) => {
|
|
298
|
+
const b = text.indexOf(r.host.markers.begin);
|
|
299
|
+
const e = text.indexOf(r.host.markers.end, b) + r.host.markers.end.length;
|
|
300
|
+
return (text.slice(0, b) + text.slice(e)).replace(/^\n+/, "");
|
|
301
|
+
};
|
|
302
|
+
const rest = cut(r.text);
|
|
303
|
+
if (rest.trim() === "" || rest.trim() === cut(r.host.file(profile)).trim()) {
|
|
304
|
+
await rm(r.abs);
|
|
305
|
+
log.push(`Removed ${r.rel}: the local binding has no CI host, and the file held only this tool's wrapper.`);
|
|
183
306
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const was = o.prevLock?.ciBinding;
|
|
188
|
-
const previous = HOST_WRAPPERS[was !== undefined && isCiBinding(was) ? was : DEFAULT_CI_BINDING];
|
|
189
|
-
if ((await tryRead(join(o.targetRepo, previous.relPath))) !== null) {
|
|
190
|
-
log.push(`CI binding is now ${o.binding}; ${previous.relPath} (the previous binding) is left in place. Delete it once ${host.relPath} runs.`);
|
|
191
|
-
}
|
|
307
|
+
else {
|
|
308
|
+
await writeFile(r.abs, rest, "utf8");
|
|
309
|
+
log.push(`Removed the method-managed block from ${r.rel}; the adopter content outside it is kept.`);
|
|
192
310
|
}
|
|
193
|
-
return {
|
|
194
|
-
failures,
|
|
195
|
-
log,
|
|
196
|
-
wrapperSha: blockSha256(await tryRead(wrapperAbs), host.markers),
|
|
197
|
-
moduleSha: moduleSha256(rendered),
|
|
198
|
-
};
|
|
199
311
|
}
|
|
200
312
|
async function tryRead(path) {
|
|
201
313
|
try {
|
|
@@ -22,6 +22,16 @@ export const MODULE_DIR = ".dagger/method-validation";
|
|
|
22
22
|
export const DAGGER_VERSION = "0.21.10";
|
|
23
23
|
/** The command every host wrapper runs. */
|
|
24
24
|
export const MODULE_CALL = `dagger call -m ${MODULE_DIR} method-validation --source .`;
|
|
25
|
+
/** The pipeline catalogue rebuild (M-ADR 0220 §4.4): a method-managed script in the module, run by every host wrapper. */
|
|
26
|
+
export const REBUILD_SCRIPT = `${MODULE_DIR}/catalogue-rebuild.sh`;
|
|
27
|
+
/**
|
|
28
|
+
* Left in the git directory by REBUILD_SCRIPT when the rebuild ran, whether or not it pushed. Every
|
|
29
|
+
* wrapper turns it into the module's --catalogue-rebuild, so the freshness step gates exactly where
|
|
30
|
+
* the rebuild ran and the wrapper holds no condition of its own.
|
|
31
|
+
*/
|
|
32
|
+
export const REBUILD_RAN_MARKER = "sdd-catalogue-rebuild-ran";
|
|
33
|
+
/** What every wrapper appends to MODULE_CALL: --catalogue-rebuild when, and only when, the rebuild ran. */
|
|
34
|
+
export const REBUILD_FLAG = `$(test -f "$(git rev-parse --git-dir)/${REBUILD_RAN_MARKER}" && echo --catalogue-rebuild)`;
|
|
25
35
|
const VERIFY_PIN = "@@VERIFY_CLI_VERSION@@";
|
|
26
36
|
// The module's SDK import, substituted at render: written literally, the persona builds' import-closure
|
|
27
37
|
// scan would read this text as an import of the CLI's own and refuse a dependency it does not declare.
|
|
@@ -39,7 +49,8 @@ const INDEX_TS = String.raw `/**
|
|
|
39
49
|
*
|
|
40
50
|
* The steps are the method's job plan, read at run time from "sdd-verify validate --list". Every
|
|
41
51
|
* step runs even after an earlier one fails, so one red step never hides the verdicts after it. The
|
|
42
|
-
* job fails only when a non-advisory step fails; the catalogue freshness step is advisory
|
|
52
|
+
* job fails only when a non-advisory step fails; the catalogue freshness step is advisory unless the
|
|
53
|
+
* pipeline rebuilt the catalogues on this run (catalogue-rebuild.sh, M-ADR 0220 §4.4).
|
|
43
54
|
*/
|
|
44
55
|
import { dag, Container, Directory, File, object, func, ReturnType } from ${DAGGER_SDK}
|
|
45
56
|
|
|
@@ -102,12 +113,14 @@ export class MethodValidation {
|
|
|
102
113
|
maintainVersion: string = "${MAINTAIN_PIN}",
|
|
103
114
|
verifyTarball?: File,
|
|
104
115
|
maintainTarball?: File,
|
|
116
|
+
catalogueRebuild: boolean = false,
|
|
105
117
|
): Promise<string> {
|
|
106
118
|
const base = this.toolchain(verifyVersion, maintainVersion, verifyTarball, maintainTarball)
|
|
107
119
|
.withMountedDirectory("/src", source)
|
|
108
120
|
.withWorkdir("/src")
|
|
109
121
|
const versions = (await base.withExec(["sh", "-c", "sdd-verify --version; sdd-maintain --version"]).stdout()).split("\n")
|
|
110
|
-
const
|
|
122
|
+
const listArgs = ["sdd-verify", "validate", "--list"]
|
|
123
|
+
const plan = JSON.parse(await base.withExec(catalogueRebuild ? [...listArgs, "--catalogue-rebuild"] : listArgs).stdout()) as {
|
|
111
124
|
profile: string
|
|
112
125
|
steps: PlanStep[]
|
|
113
126
|
}
|
|
@@ -130,8 +143,66 @@ export class MethodValidation {
|
|
|
130
143
|
if (failed > 0) throw new Error(report)
|
|
131
144
|
return report
|
|
132
145
|
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Rebuild the committed catalogues from the specifications (M-ADR 0220 §4.4) and return the whole
|
|
149
|
+
* new catalogues directory: every file sdd-maintain catalogue build produces, plus every file
|
|
150
|
+
* already there except a .csv the build no longer produces, the freshness check's own rule. The
|
|
151
|
+
* host wrapper's catalogue-rebuild.sh exports it over catalogues/ and pushes only if it changed.
|
|
152
|
+
*/
|
|
153
|
+
@func()
|
|
154
|
+
catalogueRebuild(source: Directory, maintainVersion: string = "${MAINTAIN_PIN}", maintainTarball?: File): Directory {
|
|
155
|
+
return this.toolchain(undefined, maintainVersion, undefined, maintainTarball)
|
|
156
|
+
.withMountedDirectory("/src", source)
|
|
157
|
+
.withWorkdir("/src")
|
|
158
|
+
.withExec(["sdd-maintain", "catalogue", "build", "--sdd-path", ".", "--out", "/rebuilt"])
|
|
159
|
+
.withExec([
|
|
160
|
+
"sh",
|
|
161
|
+
"-c",
|
|
162
|
+
"mkdir -p /next && if [ -d catalogues ]; then cp -a catalogues/. /next/; fi && " +
|
|
163
|
+
"for f in /next/*.csv; do [ -e \"$f\" ] || continue; [ -e \"/rebuilt/$(basename \"$f\")\" ] || rm \"$f\"; done && " +
|
|
164
|
+
"cp -a /rebuilt/. /next/",
|
|
165
|
+
])
|
|
166
|
+
.directory("/next")
|
|
167
|
+
}
|
|
133
168
|
}
|
|
134
169
|
`;
|
|
170
|
+
// Run by every host wrapper, and only where the adopter has provided the push credential. Plain
|
|
171
|
+
// strings, not a template: the shell's own \${...} must reach the file as written.
|
|
172
|
+
const REBUILD_SH = [
|
|
173
|
+
"#!/bin/sh",
|
|
174
|
+
"# Pipeline catalogue rebuild (M-ADR 0220 §4.4). Written by sdd-cli sync and method-managed, like the",
|
|
175
|
+
"# rest of this module. The host wrapper runs it only where the adopter has provided the push",
|
|
176
|
+
"# credential, and sets:",
|
|
177
|
+
"# SDD_REBUILD_BRANCH the branch this pipeline builds, which the rebuild is pushed to",
|
|
178
|
+
"# SDD_REBUILD_PUSH_URL the repository's URL, carrying the credential",
|
|
179
|
+
"#",
|
|
180
|
+
"# A rebuild that changes nothing pushes nothing, so the push cannot loop: the pipeline it starts",
|
|
181
|
+
"# rebuilds the same catalogues, finds no change, and runs Method Validation.",
|
|
182
|
+
"set -eu",
|
|
183
|
+
': "${SDD_REBUILD_BRANCH:?the host wrapper sets it}" "${SDD_REBUILD_PUSH_URL:?the host wrapper sets it}"',
|
|
184
|
+
"",
|
|
185
|
+
"# A merge the pipeline made (a merged-results or merge-with-target build) is not the branch's head,",
|
|
186
|
+
"# and pushing it would put that merge on the branch. The branch's own pipeline rebuilds instead.",
|
|
187
|
+
'if [ "$(git rev-list --parents -n 1 HEAD | wc -w)" -gt 2 ]; then',
|
|
188
|
+
' echo "Catalogue rebuild: skipped, HEAD is a merge commit this pipeline made; the branch pipeline rebuilds."',
|
|
189
|
+
" exit 0",
|
|
190
|
+
"fi",
|
|
191
|
+
"",
|
|
192
|
+
"dagger call -m " + MODULE_DIR + " catalogue-rebuild --source . export --path catalogues --wipe >/dev/null",
|
|
193
|
+
'touch "$(git rev-parse --git-dir)/' + REBUILD_RAN_MARKER + '"',
|
|
194
|
+
"git add -A catalogues",
|
|
195
|
+
"if git diff --cached --quiet; then",
|
|
196
|
+
' echo "Catalogue rebuild: the committed catalogues already match the specifications; nothing to push."',
|
|
197
|
+
" exit 0",
|
|
198
|
+
"fi",
|
|
199
|
+
"git diff --cached --stat",
|
|
200
|
+
'git -c user.name="sdd-method catalogue rebuild" -c user.email="catalogue-rebuild@sdd-method.invalid" \\',
|
|
201
|
+
' commit -q -m "Rebuild catalogues from the specifications (Method Validation, M-ADR 0220 §4.4)"',
|
|
202
|
+
'git push -q "$SDD_REBUILD_PUSH_URL" "HEAD:refs/heads/$SDD_REBUILD_BRANCH"',
|
|
203
|
+
'echo "Catalogue rebuild: pushed $(git rev-parse --short HEAD) to $SDD_REBUILD_BRANCH; Method Validation runs on it there too."',
|
|
204
|
+
"",
|
|
205
|
+
].join("\n");
|
|
135
206
|
const DAGGER_JSON = `{
|
|
136
207
|
"name": "method-validation",
|
|
137
208
|
"engineVersion": "v${DAGGER_VERSION}",
|
|
@@ -183,6 +254,7 @@ const GITIGNORE = `/sdk
|
|
|
183
254
|
export function renderModuleFiles(pins = { verify: getVerifyCliVersion(), maintain: getMaintainCliVersion() }) {
|
|
184
255
|
return [
|
|
185
256
|
[".gitignore", GITIGNORE],
|
|
257
|
+
["catalogue-rebuild.sh", REBUILD_SH],
|
|
186
258
|
["dagger.json", DAGGER_JSON],
|
|
187
259
|
["package.json", PACKAGE_JSON],
|
|
188
260
|
["src/index.ts", INDEX_TS.split(VERIFY_PIN).join(pins.verify).split(MAINTAIN_PIN).join(pins.maintain).split(DAGGER_SDK).join('"@dagger.io' + '/dagger"')],
|
|
@@ -24,40 +24,62 @@ export function buildMethodGuardPreToolUse() {
|
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
const SESSION_START_VALIDATE_ARGS = ' validate --target-repo "${CLAUDE_PROJECT_DIR:-$(pwd)}" --validator adr-structure 2>&1 | head -20';
|
|
27
|
-
|
|
27
|
+
/** The verify persona's npm package, unless the lock names a rescoped one (sync --cli-package). */
|
|
28
|
+
export const DEFAULT_VERIFY_CLI_PACKAGE = "@sdd-method/verify-cli";
|
|
29
|
+
/** One npm package name, scoped or not, the shape `npm pack` and `npx` accept. */
|
|
30
|
+
export const NPM_PACKAGE_NAME = /^(@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/;
|
|
31
|
+
export const CLI_SOURCES = ["registry", "installed"];
|
|
32
|
+
export const DEFAULT_CLI_SOURCE = { source: "registry" };
|
|
33
|
+
function registryLauncher(version, pkg) {
|
|
34
|
+
return `npx --yes ${pkg}@${version}${SESSION_START_VALIDATE_ARGS}`;
|
|
35
|
+
}
|
|
36
|
+
function installedLauncher(version) {
|
|
37
|
+
return (`v=$(sdd-verify --version 2>/dev/null); [ "$v" = "${version}" ] || ` +
|
|
38
|
+
`echo "[method] sdd-verify \${v:-is not installed}; this SDD pins verify-cli ${version}: ` +
|
|
39
|
+
`pull your CLI release repository and run ./install.sh ${version}"; ` +
|
|
40
|
+
`if [ -n "$v" ]; then sdd-verify${SESSION_START_VALIDATE_ARGS}; fi`);
|
|
41
|
+
}
|
|
28
42
|
/**
|
|
29
43
|
* The session-start check runs the VERIFY persona's CLI (M-ADR 0218, 0220), not sdd-cli, which is
|
|
30
|
-
* the administrator's: validating is routine author work. It runs
|
|
31
|
-
*
|
|
32
|
-
*
|
|
44
|
+
* the administrator's: validating is routine author work. It runs at the exact verify-cli version
|
|
45
|
+
* this sdd-cli releases with, the version the managed CI workflow installs, so a local pass comes
|
|
46
|
+
* from the same verifier CI uses.
|
|
33
47
|
*/
|
|
34
|
-
export function sessionStartValidateCommand(verifyVersion = getVerifyCliVersion()) {
|
|
35
|
-
|
|
48
|
+
export function sessionStartValidateCommand(verifyVersion = getVerifyCliVersion(), cli = DEFAULT_CLI_SOURCE) {
|
|
49
|
+
const body = cli.source === "installed"
|
|
50
|
+
? installedLauncher(verifyVersion)
|
|
51
|
+
: registryLauncher(verifyVersion, cli.package ?? DEFAULT_VERIFY_CLI_PACKAGE);
|
|
52
|
+
return withSentinel(body);
|
|
36
53
|
}
|
|
54
|
+
const REGISTRY_FORM = /^npx --yes ((?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*)@(\S+) validate /;
|
|
55
|
+
const INSTALLED_FORM = /^v=\$\(sdd-verify --version 2>\/dev\/null\); \[ "\$v" = "([^"]+)" \]/;
|
|
37
56
|
/**
|
|
38
|
-
* Whether a session-start command is one this tool rendered, at any version
|
|
39
|
-
* wrote until the hook moved to verify-cli,
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* the adopter owns.
|
|
57
|
+
* Whether a session-start command is one this tool rendered, at any exact version and from either
|
|
58
|
+
* source: the sdd-cli form it wrote until the hook moved to verify-cli, the npx form under any
|
|
59
|
+
* package name, or the installed form. Sync replaces its own earlier render without counting it as
|
|
60
|
+
* drift, so a release that moves the pin, or a switch of source, needs no --allow-overwrite (the
|
|
61
|
+
* flag that also rewrites keys the adopter owns). The candidate is re-rendered from the version it
|
|
62
|
+
* names and must match exactly, so anything edited under the sentinel is still drift.
|
|
43
63
|
*/
|
|
44
64
|
export function isOwnSessionStartRender(command) {
|
|
45
65
|
const body = command.replace(METHOD_MANAGED_SENTINEL, "").trimEnd();
|
|
46
|
-
if (
|
|
47
|
-
return false;
|
|
48
|
-
const launcher = body.slice(0, body.length - SESSION_START_VALIDATE_ARGS.length);
|
|
49
|
-
if (launcher === "sdd-cli")
|
|
66
|
+
if (body === `sdd-cli${SESSION_START_VALIDATE_ARGS}`)
|
|
50
67
|
return true;
|
|
51
|
-
const npx =
|
|
52
|
-
|
|
68
|
+
const npx = REGISTRY_FORM.exec(body);
|
|
69
|
+
if (npx && EXACT_VERSION.test(npx[2]))
|
|
70
|
+
return body === registryLauncher(npx[2], npx[1]);
|
|
71
|
+
const installed = INSTALLED_FORM.exec(body);
|
|
72
|
+
if (installed && EXACT_VERSION.test(installed[1]))
|
|
73
|
+
return body === installedLauncher(installed[1]);
|
|
74
|
+
return false;
|
|
53
75
|
}
|
|
54
|
-
export function buildSessionStartValidate() {
|
|
76
|
+
export function buildSessionStartValidate(cli = DEFAULT_CLI_SOURCE) {
|
|
55
77
|
return {
|
|
56
78
|
matcher: "startup",
|
|
57
79
|
hooks: [
|
|
58
80
|
{
|
|
59
81
|
type: "command",
|
|
60
|
-
command: sessionStartValidateCommand(),
|
|
82
|
+
command: sessionStartValidateCommand(getVerifyCliVersion(), cli),
|
|
61
83
|
},
|
|
62
84
|
],
|
|
63
85
|
};
|
|
@@ -66,10 +88,10 @@ export function buildSessionStartValidate() {
|
|
|
66
88
|
* Full method-managed hooks block. When extending, add a new event
|
|
67
89
|
* name to the returned object and a corresponding builder above.
|
|
68
90
|
*/
|
|
69
|
-
export function buildMethodManagedHooks() {
|
|
91
|
+
export function buildMethodManagedHooks(cli = DEFAULT_CLI_SOURCE) {
|
|
70
92
|
const hooks = {
|
|
71
93
|
PreToolUse: [buildMethodGuardPreToolUse()],
|
|
72
|
-
SessionStart: [buildSessionStartValidate()],
|
|
94
|
+
SessionStart: [buildSessionStartValidate(cli)],
|
|
73
95
|
};
|
|
74
96
|
return hooks;
|
|
75
97
|
}
|
|
@@ -9,6 +9,6 @@
|
|
|
9
9
|
* when the repo runs the claude-code harness (the default).
|
|
10
10
|
*/
|
|
11
11
|
export { METHOD_MANAGED_SENTINEL, isMethodManagedMatcher } from "./types.js";
|
|
12
|
-
export { buildMethodManagedHooks, isOwnSessionStartRender, sessionStartValidateCommand, } from "./entries.js";
|
|
12
|
+
export { buildMethodManagedHooks, CLI_SOURCES, DEFAULT_CLI_SOURCE, DEFAULT_VERIFY_CLI_PACKAGE, isOwnSessionStartRender, NPM_PACKAGE_NAME, sessionStartValidateCommand, } from "./entries.js";
|
|
13
13
|
export { detectHooksDrift, normaliseOwnHookRenders, upsertMethodManagedHooks, } from "./merge.js";
|
|
14
14
|
//# sourceMappingURL=index.js.map
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* block-marker pair so the sync path can preserve adopter-authored
|
|
9
9
|
* content outside the block.
|
|
10
10
|
*/
|
|
11
|
-
import { DAGGER_VERSION, MODULE_CALL, MODULE_DIR } from "../ci-binding/module.js";
|
|
11
|
+
import { DAGGER_VERSION, MODULE_CALL, MODULE_DIR, REBUILD_FLAG, REBUILD_SCRIPT } from "../ci-binding/module.js";
|
|
12
12
|
export const WORKFLOW_REL_PATH = ".github/workflows/method-validation.yml";
|
|
13
13
|
export const BLOCK_BEGIN = "# method-managed: begin";
|
|
14
14
|
export const BLOCK_END = "# method-managed: end";
|
|
@@ -56,6 +56,13 @@ function buildMethodManagedBlock(profile, runner) {
|
|
|
56
56
|
"# released with the sdd-cli that last synced this repository. The catalogue",
|
|
57
57
|
"# freshness step (@sdd-method/maintain-cli, pinned the same way) is advisory.",
|
|
58
58
|
"#",
|
|
59
|
+
"# Catalogue rebuild (M-ADR 0220 §4.4), off until you configure it: set the",
|
|
60
|
+
"# repository variable SDD_CATALOGUE_APP_ID and the secret",
|
|
61
|
+
"# SDD_CATALOGUE_APP_PRIVATE_KEY for a GitHub App that can write contents, and",
|
|
62
|
+
"# the job rebuilds the catalogues and pushes them to the branch it is building",
|
|
63
|
+
"# (a pull request's own branch, never a fork's); the freshness step then gates.",
|
|
64
|
+
"# An App token, because pushes made with the default token start no workflow.",
|
|
65
|
+
"#",
|
|
59
66
|
"# Run it locally (needs a container engine):",
|
|
60
67
|
`# ${MODULE_CALL}`,
|
|
61
68
|
"",
|
|
@@ -74,11 +81,28 @@ function buildMethodManagedBlock(profile, runner) {
|
|
|
74
81
|
" name: Method Validation",
|
|
75
82
|
` runs-on: ${runsOn(runner)}`,
|
|
76
83
|
" steps:",
|
|
84
|
+
" - name: Catalogue rebuild token",
|
|
85
|
+
" id: catalogue-token",
|
|
86
|
+
" if: vars.SDD_CATALOGUE_APP_ID != '' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)",
|
|
87
|
+
" uses: actions/create-github-app-token@v1",
|
|
88
|
+
" with:",
|
|
89
|
+
" app-id: ${{ vars.SDD_CATALOGUE_APP_ID }}",
|
|
90
|
+
" private-key: ${{ secrets.SDD_CATALOGUE_APP_PRIVATE_KEY }}",
|
|
77
91
|
" - uses: actions/checkout@v4",
|
|
92
|
+
" with:",
|
|
93
|
+
// The branch itself when rebuilding, so the rebuild lands on it; otherwise the event's own ref.
|
|
94
|
+
" ref: ${{ steps.catalogue-token.outputs.token != '' && (github.head_ref || github.ref_name) || '' }}",
|
|
95
|
+
" persist-credentials: false",
|
|
78
96
|
" - name: Install Dagger",
|
|
79
97
|
` run: curl -fsSL https://dl.dagger.io/dagger/install.sh | BIN_DIR="$RUNNER_TEMP/dagger" DAGGER_VERSION=${DAGGER_VERSION} sh && echo "$RUNNER_TEMP/dagger" >> "$GITHUB_PATH"`,
|
|
98
|
+
" - name: Rebuild catalogues",
|
|
99
|
+
" if: steps.catalogue-token.outputs.token != ''",
|
|
100
|
+
" env:",
|
|
101
|
+
" SDD_REBUILD_TOKEN: ${{ steps.catalogue-token.outputs.token }}",
|
|
102
|
+
" SDD_REBUILD_BRANCH: ${{ github.head_ref || github.ref_name }}",
|
|
103
|
+
` run: SDD_REBUILD_PUSH_URL="https://x-access-token:$SDD_REBUILD_TOKEN@\${GITHUB_SERVER_URL#https://}/$GITHUB_REPOSITORY.git" sh ${REBUILD_SCRIPT}`,
|
|
80
104
|
" - name: Method Validation",
|
|
81
|
-
` run: ${MODULE_CALL}`,
|
|
105
|
+
` run: ${MODULE_CALL} ${REBUILD_FLAG}`,
|
|
82
106
|
BLOCK_END,
|
|
83
107
|
].join("\n");
|
|
84
108
|
}
|
package/dist/lib/lock-file.js
CHANGED
|
@@ -66,6 +66,8 @@ export function serialiseLockFile(lock) {
|
|
|
66
66
|
...(lock.ciRunner !== undefined ? [`ci_runner: "${lock.ciRunner}"`] : []),
|
|
67
67
|
...(lock.ciWorkflowBlockSha256 !== undefined ? [`ci_workflow_block_sha256: "${lock.ciWorkflowBlockSha256}"`] : []),
|
|
68
68
|
...(lock.ciModuleSha256 !== undefined ? [`ci_module_sha256: "${lock.ciModuleSha256}"`] : []),
|
|
69
|
+
...(lock.cliSource !== undefined ? [`cli_source: ${lock.cliSource}`] : []),
|
|
70
|
+
...(lock.cliPackage !== undefined ? [`cli_package: "${lock.cliPackage}"`] : []),
|
|
69
71
|
"bundles:",
|
|
70
72
|
];
|
|
71
73
|
for (const b of lock.bundles) {
|
|
@@ -117,6 +119,8 @@ function parseVersioned(raw) {
|
|
|
117
119
|
let ciBinding;
|
|
118
120
|
let ciRunner;
|
|
119
121
|
let ciModuleSha256;
|
|
122
|
+
let cliSource;
|
|
123
|
+
let cliPackage;
|
|
120
124
|
const bundles = [];
|
|
121
125
|
let inBundles = false;
|
|
122
126
|
let inPaths = false;
|
|
@@ -186,6 +190,10 @@ function parseVersioned(raw) {
|
|
|
186
190
|
ciRunner = stripQuotes(value);
|
|
187
191
|
else if (key === "ci_module_sha256")
|
|
188
192
|
ciModuleSha256 = stripQuotes(value);
|
|
193
|
+
else if (key === "cli_source")
|
|
194
|
+
cliSource = stripQuotes(value);
|
|
195
|
+
else if (key === "cli_package")
|
|
196
|
+
cliPackage = stripQuotes(value);
|
|
189
197
|
}
|
|
190
198
|
continue;
|
|
191
199
|
}
|
|
@@ -207,7 +215,7 @@ function parseVersioned(raw) {
|
|
|
207
215
|
// a YAML reader parses 113 of 325 entries in one real lock as MAPPINGS
|
|
208
216
|
// rather than strings — silently, with no error. Quoting on write is the
|
|
209
217
|
// fix; reading both forms ships first so no lock can be written that an
|
|
210
|
-
// older reader cannot parse.
|
|
218
|
+
// older reader cannot parse.
|
|
211
219
|
const entry = stripQuotes(line.slice(" - ".length));
|
|
212
220
|
const withDigest = DIGEST_ENTRY_RE.exec(entry);
|
|
213
221
|
if (withDigest) {
|
|
@@ -313,6 +321,8 @@ function parseVersioned(raw) {
|
|
|
313
321
|
...(ciRunner !== undefined ? { ciRunner } : {}),
|
|
314
322
|
...(ciWorkflowBlockSha256 !== undefined ? { ciWorkflowBlockSha256 } : {}),
|
|
315
323
|
...(ciModuleSha256 !== undefined ? { ciModuleSha256 } : {}),
|
|
324
|
+
...(cliSource !== undefined ? { cliSource } : {}),
|
|
325
|
+
...(cliPackage !== undefined ? { cliPackage } : {}),
|
|
316
326
|
};
|
|
317
327
|
}
|
|
318
328
|
function migrateV1(raw, legacyBundleName) {
|