@kungfu-tech/buildchain 3.0.3-alpha.1 → 3.0.3-alpha.2
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/README.md +9 -4
- package/bin/buildchain.mjs +2 -256
- package/dist/site/buildchain-contract.json +5 -5
- package/dist/site/buildchain-site.json +19 -14
- package/dist/site/capability-registry.json +2 -2
- package/dist/site/cli-registry.json +72 -0
- package/dist/site/kfd-claims.json +95 -11
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +1 -1
- package/dist/site/node-api-registry.json +7 -7
- package/dist/site/page-registry.json +13 -8
- package/dist/site/public-surface-audit.json +72 -14
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +5 -5
- package/docs/cli.md +31 -7
- package/docs/publication-artifacts.md +27 -0
- package/package.json +1 -1
- package/packages/core/README.md +3 -2
- package/packages/core/github-governance-authority.js +10 -25
- package/packages/core/index.js +1 -1
- package/packages/core/paper-fleet.js +260 -0
- package/packages/core/paper-repository.js +210 -0
- package/packages/core/paper-work.js +365 -0
- package/packages/core/paper.js +117 -163
- package/packages/core/public-surface-audit.js +26 -78
- package/packages/core/public-surface-cli.js +117 -0
- package/scripts/buildchain-cli-help.mjs +256 -0
- package/scripts/generate-site-bundle.mjs +1 -187
- package/scripts/paper-work-fleet-cli.mjs +563 -0
- package/scripts/paper.mjs +31 -45
- package/scripts/site-capability-metadata.mjs +202 -0
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
PAPER_FLEET_AUDIT_CONTRACT,
|
|
5
|
+
PAPER_FLEET_UPDATE_PLAN_CONTRACT,
|
|
6
|
+
PAPER_MIGRATION_CONTRACT,
|
|
7
|
+
PAPER_SCAFFOLD_CONTRACT,
|
|
8
|
+
PAPER_WORK_START_PLAN_CONTRACT,
|
|
9
|
+
PAPER_WORK_SUBMIT_PLAN_CONTRACT,
|
|
10
|
+
collectPaperFleetAudit,
|
|
11
|
+
collectPaperStatus,
|
|
12
|
+
createPaperWorkStartPlan,
|
|
13
|
+
createPaperWorkSubmitPlan,
|
|
14
|
+
discoverPaperFleet,
|
|
15
|
+
executePaperWorkStart,
|
|
16
|
+
executePaperWorkSubmitPush,
|
|
17
|
+
planPaperMigration,
|
|
18
|
+
planPaperScaffold,
|
|
19
|
+
planPaperFleetUpdate,
|
|
20
|
+
writePaperFleetUpdate,
|
|
21
|
+
writePaperMigration,
|
|
22
|
+
writePaperScaffold,
|
|
23
|
+
} from "../packages/core/paper.js";
|
|
24
|
+
import {
|
|
25
|
+
BUILDCHAIN_GITHUB_GOVERNANCE_AUTHORITY,
|
|
26
|
+
compileEffectiveGithubGovernancePolicy,
|
|
27
|
+
} from "../packages/core/github-governance-authority.js";
|
|
28
|
+
|
|
29
|
+
function commandResult(command, args, { cwd, timeout = 60000 } = {}) {
|
|
30
|
+
const result = spawnSync(command, args, {
|
|
31
|
+
cwd,
|
|
32
|
+
env: process.env,
|
|
33
|
+
encoding: "utf8",
|
|
34
|
+
timeout,
|
|
35
|
+
maxBuffer: 2 * 1024 * 1024,
|
|
36
|
+
});
|
|
37
|
+
return {
|
|
38
|
+
ok: result.status === 0,
|
|
39
|
+
stdout: String(result.stdout || "").trim(),
|
|
40
|
+
stderr: String(result.stderr || "").trim(),
|
|
41
|
+
error: result.error?.message || "",
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function readFlag(args, name, fallback = "") {
|
|
46
|
+
const index = args.indexOf(`--${name}`);
|
|
47
|
+
return index === -1 ? fallback : args[index + 1] || "";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function githubPullRequests({ cwd, repository, branch }) {
|
|
51
|
+
const query = commandResult(
|
|
52
|
+
"gh",
|
|
53
|
+
[
|
|
54
|
+
"pr",
|
|
55
|
+
"list",
|
|
56
|
+
"--repo",
|
|
57
|
+
repository,
|
|
58
|
+
"--head",
|
|
59
|
+
branch,
|
|
60
|
+
"--state",
|
|
61
|
+
"open",
|
|
62
|
+
"--json",
|
|
63
|
+
"number,url,headRefName,baseRefName",
|
|
64
|
+
],
|
|
65
|
+
{ cwd },
|
|
66
|
+
);
|
|
67
|
+
if (!query.ok) {
|
|
68
|
+
return {
|
|
69
|
+
ok: false,
|
|
70
|
+
rows: [],
|
|
71
|
+
errorCode: query.error ? "gh-unavailable" : "github-pr-query-failed",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const rows = JSON.parse(query.stdout || "[]");
|
|
76
|
+
return {
|
|
77
|
+
ok: Array.isArray(rows),
|
|
78
|
+
rows: Array.isArray(rows) ? rows : [],
|
|
79
|
+
errorCode: "",
|
|
80
|
+
};
|
|
81
|
+
} catch {
|
|
82
|
+
return { ok: false, rows: [], errorCode: "github-pr-response-invalid" };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function createPullRequest(plan, title, body) {
|
|
87
|
+
const prTitle =
|
|
88
|
+
title ||
|
|
89
|
+
`chore(paper): ${plan.source.branch.replace(/^[^/]+\//, "").replaceAll("-", " ")}`;
|
|
90
|
+
const prBody =
|
|
91
|
+
body ||
|
|
92
|
+
[
|
|
93
|
+
"## Summary",
|
|
94
|
+
"",
|
|
95
|
+
"Submit this Paper work branch through the protected Buildchain development path.",
|
|
96
|
+
"",
|
|
97
|
+
"## Safety",
|
|
98
|
+
"",
|
|
99
|
+
"- No direct push to a protected channel.",
|
|
100
|
+
"- No force push.",
|
|
101
|
+
"- Publication remains behind the accepted PR and release gates.",
|
|
102
|
+
].join("\n");
|
|
103
|
+
return commandResult(
|
|
104
|
+
"gh",
|
|
105
|
+
[
|
|
106
|
+
"pr",
|
|
107
|
+
"create",
|
|
108
|
+
"--repo",
|
|
109
|
+
plan.repository,
|
|
110
|
+
"--base",
|
|
111
|
+
plan.target.branch,
|
|
112
|
+
"--head",
|
|
113
|
+
plan.source.branch,
|
|
114
|
+
"--title",
|
|
115
|
+
prTitle,
|
|
116
|
+
"--body",
|
|
117
|
+
prBody,
|
|
118
|
+
],
|
|
119
|
+
{ cwd: plan.cwd },
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function executeWorkSubmit(plan, args) {
|
|
124
|
+
const pushed = executePaperWorkSubmitPush(plan);
|
|
125
|
+
if (!pushed.ok) return pushed;
|
|
126
|
+
if (plan.pullRequest?.url)
|
|
127
|
+
return { ...pushed, reused: true, pr: plan.pullRequest };
|
|
128
|
+
const created = createPullRequest(
|
|
129
|
+
plan,
|
|
130
|
+
readFlag(args, "title"),
|
|
131
|
+
readFlag(args, "body"),
|
|
132
|
+
);
|
|
133
|
+
if (!created.ok) {
|
|
134
|
+
return {
|
|
135
|
+
...pushed,
|
|
136
|
+
ok: false,
|
|
137
|
+
errorCode: created.error ? "gh-unavailable" : "github-pr-create-failed",
|
|
138
|
+
stderr: created.error || created.stderr,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const url =
|
|
142
|
+
created.stdout
|
|
143
|
+
.split(/\s+/)
|
|
144
|
+
.find((entry) => /^https:\/\/github\.com\//.test(entry)) || "";
|
|
145
|
+
return {
|
|
146
|
+
...pushed,
|
|
147
|
+
reused: false,
|
|
148
|
+
pr: {
|
|
149
|
+
url,
|
|
150
|
+
headRefName: plan.source.branch,
|
|
151
|
+
baseRefName: plan.target.branch,
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function compactRuleset(value) {
|
|
157
|
+
return {
|
|
158
|
+
id: value.id,
|
|
159
|
+
name: value.name || "",
|
|
160
|
+
enforcement: value.enforcement || "",
|
|
161
|
+
target: value.target || "",
|
|
162
|
+
bypass_actors: value.bypass_actors || [],
|
|
163
|
+
conditions: value.conditions || {},
|
|
164
|
+
rules: (value.rules || []).map((rule) => ({
|
|
165
|
+
type: rule.type || "",
|
|
166
|
+
parameters: rule.parameters || {},
|
|
167
|
+
})),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function fetchRulesets(cwd, repository) {
|
|
172
|
+
const listed = commandResult(
|
|
173
|
+
"gh",
|
|
174
|
+
["api", `repos/${repository}/rulesets?includes_parents=true`],
|
|
175
|
+
{ cwd },
|
|
176
|
+
);
|
|
177
|
+
if (!listed.ok) return { ok: false, rows: [] };
|
|
178
|
+
try {
|
|
179
|
+
const summaries = JSON.parse(listed.stdout || "[]");
|
|
180
|
+
const rows = summaries.flatMap((summary) => {
|
|
181
|
+
const detail = commandResult(
|
|
182
|
+
"gh",
|
|
183
|
+
["api", `repos/${repository}/rulesets/${summary.id}`],
|
|
184
|
+
{ cwd },
|
|
185
|
+
);
|
|
186
|
+
if (!detail.ok) return [];
|
|
187
|
+
try {
|
|
188
|
+
return [compactRuleset(JSON.parse(detail.stdout || "{}"))];
|
|
189
|
+
} catch {
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
return { ok: rows.length === summaries.length, rows };
|
|
194
|
+
} catch {
|
|
195
|
+
return { ok: false, rows: [] };
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function paperGovernanceTargetPolicies(repository) {
|
|
200
|
+
const name = String(repository || "").split("/")[1] || "";
|
|
201
|
+
return (
|
|
202
|
+
BUILDCHAIN_GITHUB_GOVERNANCE_AUTHORITY.repositoryAdmission
|
|
203
|
+
.publicAuthoritativeTargets[name] || []
|
|
204
|
+
).filter((entry) => /^(dev|alpha|release)\//.test(entry.targetRef));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function bindingKey(entry) {
|
|
208
|
+
return `${entry.context}:${entry.appId ?? ""}`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function sameBindings(observed, expected) {
|
|
212
|
+
const left = (observed || []).map(bindingKey).sort();
|
|
213
|
+
const right = (expected || []).map(bindingKey).sort();
|
|
214
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function bypassAllowed(observed, allowed) {
|
|
218
|
+
return (observed || []).every((actor) =>
|
|
219
|
+
(allowed || []).some(
|
|
220
|
+
(candidate) =>
|
|
221
|
+
candidate.actorType === actor.actorType &&
|
|
222
|
+
candidate.actorId === actor.actorId &&
|
|
223
|
+
candidate.bypassMode === actor.bypassMode,
|
|
224
|
+
),
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function evaluatePaperGithubGovernance({
|
|
229
|
+
repository,
|
|
230
|
+
actions = {},
|
|
231
|
+
rulesets = [],
|
|
232
|
+
protections = {},
|
|
233
|
+
} = {}) {
|
|
234
|
+
const targetPolicies = paperGovernanceTargetPolicies(repository);
|
|
235
|
+
const checks = [
|
|
236
|
+
{
|
|
237
|
+
id: "actions.default-workflow-permissions",
|
|
238
|
+
ok: actions.default_workflow_permissions === "read",
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
id: "actions.pull-request-approval-disabled",
|
|
242
|
+
ok: actions.can_approve_pull_request_reviews === false,
|
|
243
|
+
},
|
|
244
|
+
];
|
|
245
|
+
const targets = targetPolicies.map((expected) => {
|
|
246
|
+
const observation = protections[expected.targetRef] || {};
|
|
247
|
+
const effective = compileEffectiveGithubGovernancePolicy({
|
|
248
|
+
branch: expected.targetRef,
|
|
249
|
+
defaultBranch: "",
|
|
250
|
+
protectedBranch: observation.ok === true,
|
|
251
|
+
protection: observation.protection || null,
|
|
252
|
+
rulesets,
|
|
253
|
+
});
|
|
254
|
+
const targetChecks = [
|
|
255
|
+
["observed", observation.ok === true],
|
|
256
|
+
["native-pull-request", effective.nativePullRequestRequired === true],
|
|
257
|
+
["approving-review", Number(effective.requiredApprovals) >= 1],
|
|
258
|
+
["code-owner-review", effective.codeOwnerReviewRequired === true],
|
|
259
|
+
[
|
|
260
|
+
"fresh-review",
|
|
261
|
+
effective.dismissStaleReviews === true ||
|
|
262
|
+
effective.requireLastPushApproval === true,
|
|
263
|
+
],
|
|
264
|
+
["administrator-enforcement", effective.enforceAdmins === true],
|
|
265
|
+
["conversation-resolution", effective.conversationResolution === true],
|
|
266
|
+
[
|
|
267
|
+
"required-check-bindings",
|
|
268
|
+
sameBindings(
|
|
269
|
+
effective.requiredCheckBindings,
|
|
270
|
+
expected.requiredCheckBindings,
|
|
271
|
+
),
|
|
272
|
+
],
|
|
273
|
+
[
|
|
274
|
+
"strict-required-checks",
|
|
275
|
+
effective.strictRequiredChecks === expected.strictRequiredChecks,
|
|
276
|
+
],
|
|
277
|
+
["force-push-blocked", effective.allowForcePushes === false],
|
|
278
|
+
["deletion-blocked", effective.allowDeletions === false],
|
|
279
|
+
[
|
|
280
|
+
"bypass-policy",
|
|
281
|
+
bypassAllowed(effective.bypassActors, expected.allowedBypassActors),
|
|
282
|
+
],
|
|
283
|
+
].map(([id, ok]) => ({
|
|
284
|
+
id: `protection.${expected.targetRef}.${id}`,
|
|
285
|
+
ok,
|
|
286
|
+
}));
|
|
287
|
+
checks.push(...targetChecks);
|
|
288
|
+
return {
|
|
289
|
+
targetRef: expected.targetRef,
|
|
290
|
+
status: targetChecks.every((entry) => entry.ok) ? "pass" : "fail",
|
|
291
|
+
effectivePolicy: effective,
|
|
292
|
+
};
|
|
293
|
+
});
|
|
294
|
+
const normalizedChecks = checks.map((entry) => ({
|
|
295
|
+
...entry,
|
|
296
|
+
status: entry.ok ? "pass" : "fail",
|
|
297
|
+
}));
|
|
298
|
+
return {
|
|
299
|
+
status:
|
|
300
|
+
targetPolicies.length > 0 && normalizedChecks.every((entry) => entry.ok)
|
|
301
|
+
? "pass"
|
|
302
|
+
: "fail",
|
|
303
|
+
checks: normalizedChecks,
|
|
304
|
+
targets,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function fetchProtection(cwd, repository, targetRef) {
|
|
309
|
+
const result = commandResult(
|
|
310
|
+
"gh",
|
|
311
|
+
[
|
|
312
|
+
"api",
|
|
313
|
+
`repos/${repository}/branches/${encodeURIComponent(targetRef)}/protection`,
|
|
314
|
+
],
|
|
315
|
+
{ cwd },
|
|
316
|
+
);
|
|
317
|
+
if (!result.ok) return { ok: false, protection: null };
|
|
318
|
+
try {
|
|
319
|
+
return { ok: true, protection: JSON.parse(result.stdout || "{}") };
|
|
320
|
+
} catch {
|
|
321
|
+
return { ok: false, protection: null };
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function githubGovernance(cwd, repository) {
|
|
326
|
+
const actions = commandResult(
|
|
327
|
+
"gh",
|
|
328
|
+
["api", `repos/${repository}/actions/permissions/workflow`],
|
|
329
|
+
{ cwd },
|
|
330
|
+
);
|
|
331
|
+
const rulesets = fetchRulesets(cwd, repository);
|
|
332
|
+
const targets = paperGovernanceTargetPolicies(repository);
|
|
333
|
+
const protections = Object.fromEntries(
|
|
334
|
+
targets.map((entry) => [
|
|
335
|
+
entry.targetRef,
|
|
336
|
+
fetchProtection(cwd, repository, entry.targetRef),
|
|
337
|
+
]),
|
|
338
|
+
);
|
|
339
|
+
if (!actions.ok || !rulesets.ok) {
|
|
340
|
+
return {
|
|
341
|
+
status: "fail",
|
|
342
|
+
errorCode: actions.error
|
|
343
|
+
? "gh-unavailable"
|
|
344
|
+
: "github-governance-query-failed",
|
|
345
|
+
checks: [],
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
try {
|
|
349
|
+
const policy = JSON.parse(actions.stdout || "{}");
|
|
350
|
+
const evaluation = evaluatePaperGithubGovernance({
|
|
351
|
+
repository,
|
|
352
|
+
actions: policy,
|
|
353
|
+
rulesets: rulesets.rows,
|
|
354
|
+
protections,
|
|
355
|
+
});
|
|
356
|
+
return {
|
|
357
|
+
...evaluation,
|
|
358
|
+
errorCode: "",
|
|
359
|
+
actions: policy,
|
|
360
|
+
rulesets: rulesets.rows,
|
|
361
|
+
};
|
|
362
|
+
} catch {
|
|
363
|
+
return {
|
|
364
|
+
status: "fail",
|
|
365
|
+
errorCode: "github-governance-response-invalid",
|
|
366
|
+
checks: [],
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function runWorkStart({ args, cwd }) {
|
|
372
|
+
const topic = args[0]?.startsWith("--") ? "" : args[0] || "";
|
|
373
|
+
const plan = createPaperWorkStartPlan({
|
|
374
|
+
cwd,
|
|
375
|
+
topic,
|
|
376
|
+
branch: readFlag(args, "branch"),
|
|
377
|
+
});
|
|
378
|
+
return args.includes("--execute") ? executePaperWorkStart(plan) : plan;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function runScaffold(options) {
|
|
382
|
+
const plan = planPaperScaffold({
|
|
383
|
+
cwd: options.cwd,
|
|
384
|
+
buildchainRoot: options.buildchainRoot,
|
|
385
|
+
buildchainVersion: options.buildchainVersion,
|
|
386
|
+
buildchainRef: readFlag(
|
|
387
|
+
options.args,
|
|
388
|
+
"buildchain-ref",
|
|
389
|
+
options.buildchainRef,
|
|
390
|
+
),
|
|
391
|
+
buildchainSha: options.buildchainSha,
|
|
392
|
+
name: readFlag(options.args, "name", path.basename(options.cwd)),
|
|
393
|
+
title: readFlag(options.args, "title"),
|
|
394
|
+
packageName: readFlag(options.args, "package"),
|
|
395
|
+
repository: readFlag(options.args, "repository"),
|
|
396
|
+
version: readFlag(options.args, "version", "0.1.0-alpha.0"),
|
|
397
|
+
siteBaseUrl: readFlag(options.args, "site-base-url"),
|
|
398
|
+
});
|
|
399
|
+
return options.args.some((entry) => ["--write", "--execute"].includes(entry))
|
|
400
|
+
? writePaperScaffold(plan)
|
|
401
|
+
: plan;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function runMigration(options) {
|
|
405
|
+
const plan = planPaperMigration({
|
|
406
|
+
cwd: options.cwd,
|
|
407
|
+
buildchainRoot: options.buildchainRoot,
|
|
408
|
+
buildchainVersion: options.buildchainVersion,
|
|
409
|
+
buildchainSha: options.buildchainSha,
|
|
410
|
+
});
|
|
411
|
+
return options.args.some((entry) => ["--write", "--execute"].includes(entry))
|
|
412
|
+
? writePaperMigration(plan)
|
|
413
|
+
: plan;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function runWorkSubmit({ args, cwd }) {
|
|
417
|
+
const repository = collectPaperStatus({ cwd }).identity.repository;
|
|
418
|
+
const branch = commandResult("git", ["branch", "--show-current"], {
|
|
419
|
+
cwd,
|
|
420
|
+
}).stdout;
|
|
421
|
+
const observation =
|
|
422
|
+
repository && branch
|
|
423
|
+
? githubPullRequests({ cwd, repository, branch })
|
|
424
|
+
: { ok: false, rows: [], errorCode: "paper-repository-unresolved" };
|
|
425
|
+
const plan = createPaperWorkSubmitPlan({
|
|
426
|
+
cwd,
|
|
427
|
+
pullRequests: observation.rows,
|
|
428
|
+
pullRequestObservation: observation,
|
|
429
|
+
});
|
|
430
|
+
return args.includes("--execute") ? executeWorkSubmit(plan, args) : plan;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function runFleetAudit(options) {
|
|
434
|
+
const root = path.resolve(readFlag(options.args, "root", options.cwd));
|
|
435
|
+
const repositories = discoverPaperFleet(root);
|
|
436
|
+
const governance = {};
|
|
437
|
+
if (!options.args.includes("--offline")) {
|
|
438
|
+
for (const repositoryCwd of repositories) {
|
|
439
|
+
const repository = collectPaperStatus({ cwd: repositoryCwd }).identity
|
|
440
|
+
.repository;
|
|
441
|
+
if (repository)
|
|
442
|
+
governance[repository] = githubGovernance(repositoryCwd, repository);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return collectPaperFleetAudit({
|
|
446
|
+
...options,
|
|
447
|
+
root,
|
|
448
|
+
repositories,
|
|
449
|
+
governance,
|
|
450
|
+
args: undefined,
|
|
451
|
+
command: undefined,
|
|
452
|
+
subcommand: undefined,
|
|
453
|
+
cwd: undefined,
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function refreshFleetLocks(result) {
|
|
458
|
+
for (const entry of result.results || []) {
|
|
459
|
+
if (!entry.ok) continue;
|
|
460
|
+
const lock = commandResult("pnpm", ["install", "--lockfile-only"], {
|
|
461
|
+
cwd: entry.cwd,
|
|
462
|
+
});
|
|
463
|
+
if (lock.ok) continue;
|
|
464
|
+
return {
|
|
465
|
+
...result,
|
|
466
|
+
ok: false,
|
|
467
|
+
errorCode: "paper-fleet-lock-refresh-failed",
|
|
468
|
+
lockFailure: { cwd: entry.cwd, stderr: lock.error || lock.stderr },
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
return result;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function runFleetUpdate(options) {
|
|
475
|
+
const plan = planPaperFleetUpdate({
|
|
476
|
+
root: path.resolve(readFlag(options.args, "root", options.cwd)),
|
|
477
|
+
buildchainRoot: options.buildchainRoot,
|
|
478
|
+
buildchainVersion: options.buildchainVersion,
|
|
479
|
+
buildchainSha: options.buildchainSha,
|
|
480
|
+
});
|
|
481
|
+
return options.args.includes("--write")
|
|
482
|
+
? refreshFleetLocks(writePaperFleetUpdate(plan))
|
|
483
|
+
: plan;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export function runPaperWorkFleetCli(options) {
|
|
487
|
+
const route = `${options.command}:${options.subcommand}`;
|
|
488
|
+
if (options.command === "scaffold")
|
|
489
|
+
return { handled: true, result: runScaffold(options) };
|
|
490
|
+
if (options.command === "migrate")
|
|
491
|
+
return { handled: true, result: runMigration(options) };
|
|
492
|
+
if (options.command === "status") {
|
|
493
|
+
return { handled: true, result: collectPaperStatus({ cwd: options.cwd }) };
|
|
494
|
+
}
|
|
495
|
+
if (route === "work:start")
|
|
496
|
+
return { handled: true, result: runWorkStart(options) };
|
|
497
|
+
if (route === "work:submit")
|
|
498
|
+
return { handled: true, result: runWorkSubmit(options) };
|
|
499
|
+
if (route === "fleet:audit")
|
|
500
|
+
return { handled: true, result: runFleetAudit(options) };
|
|
501
|
+
if (route === "fleet:update")
|
|
502
|
+
return { handled: true, result: runFleetUpdate(options) };
|
|
503
|
+
return { handled: false, result: undefined };
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export function printPaperWorkFleetSummary(result, fallback = () => {}) {
|
|
507
|
+
if (
|
|
508
|
+
[PAPER_SCAFFOLD_CONTRACT, PAPER_MIGRATION_CONTRACT].includes(
|
|
509
|
+
result.contract,
|
|
510
|
+
)
|
|
511
|
+
)
|
|
512
|
+
return false;
|
|
513
|
+
if (result.contract === PAPER_WORK_START_PLAN_CONTRACT) {
|
|
514
|
+
process.stdout.write(
|
|
515
|
+
`paper work start: ${result.ok ? (result.dryRun ? "ready" : "created") : "blocked"}\n`,
|
|
516
|
+
);
|
|
517
|
+
process.stdout.write(
|
|
518
|
+
`${result.source.developmentRef} -> ${result.target.branch || "<invalid>"}\n`,
|
|
519
|
+
);
|
|
520
|
+
for (const check of result.checks)
|
|
521
|
+
process.stdout.write(
|
|
522
|
+
`- ${check.status}: ${check.id}: ${check.message}\n`,
|
|
523
|
+
);
|
|
524
|
+
return true;
|
|
525
|
+
}
|
|
526
|
+
if (result.contract === PAPER_WORK_SUBMIT_PLAN_CONTRACT) {
|
|
527
|
+
process.stdout.write(
|
|
528
|
+
`paper work submit: ${result.ok ? (result.dryRun ? "ready" : result.pr?.url || "submitted") : "blocked"}\n`,
|
|
529
|
+
);
|
|
530
|
+
process.stdout.write(
|
|
531
|
+
`${result.source.branch} -> ${result.target.branch}\n`,
|
|
532
|
+
);
|
|
533
|
+
for (const check of result.checks)
|
|
534
|
+
process.stdout.write(
|
|
535
|
+
`- ${check.status}: ${check.id}: ${check.message}\n`,
|
|
536
|
+
);
|
|
537
|
+
return true;
|
|
538
|
+
}
|
|
539
|
+
if (result.contract === PAPER_FLEET_AUDIT_CONTRACT) {
|
|
540
|
+
process.stdout.write(
|
|
541
|
+
`paper fleet audit: ${result.summary.current}/${result.summary.repositories} current\n`,
|
|
542
|
+
);
|
|
543
|
+
process.stdout.write(`audit root: ${result.auditRoot}\n`);
|
|
544
|
+
for (const entry of result.repositories)
|
|
545
|
+
process.stdout.write(
|
|
546
|
+
`- ${entry.ok ? "current" : "drifted"}: ${entry.name}\n`,
|
|
547
|
+
);
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
if (result.contract === PAPER_FLEET_UPDATE_PLAN_CONTRACT) {
|
|
551
|
+
process.stdout.write(
|
|
552
|
+
`paper fleet update: ${result.ok ? (result.dryRun ? "ready" : "written") : "blocked"}\n`,
|
|
553
|
+
);
|
|
554
|
+
process.stdout.write(`plan root: ${result.planRoot}\n`);
|
|
555
|
+
for (const entry of result.plans)
|
|
556
|
+
process.stdout.write(
|
|
557
|
+
`- ${entry.ok ? "ready" : "blocked"}: ${entry.cwd}\n`,
|
|
558
|
+
);
|
|
559
|
+
return true;
|
|
560
|
+
}
|
|
561
|
+
fallback(result);
|
|
562
|
+
return false;
|
|
563
|
+
}
|
package/scripts/paper.mjs
CHANGED
|
@@ -18,12 +18,12 @@ import {
|
|
|
18
18
|
createPaperBuildPlan,
|
|
19
19
|
createPaperResumePlan,
|
|
20
20
|
executePaperNpmBootstrap,
|
|
21
|
-
planPaperMigration,
|
|
22
|
-
planPaperScaffold,
|
|
23
|
-
writePaperMigration,
|
|
24
|
-
writePaperScaffold,
|
|
25
21
|
} from "../packages/core/paper.js";
|
|
26
22
|
import { verifyPublicationReproducibility } from "../packages/core/publication-reproducibility.js";
|
|
23
|
+
import {
|
|
24
|
+
printPaperWorkFleetSummary,
|
|
25
|
+
runPaperWorkFleetCli,
|
|
26
|
+
} from "./paper-work-fleet-cli.mjs";
|
|
27
27
|
|
|
28
28
|
function usage() {
|
|
29
29
|
return `Usage:
|
|
@@ -32,6 +32,12 @@ function usage() {
|
|
|
32
32
|
[--version <semver>] [--site-base-url <url>]
|
|
33
33
|
[--buildchain-ref <ref>] [--write] [--json]
|
|
34
34
|
buildchain paper migrate [--cwd <dir>] [--write] [--json]
|
|
35
|
+
buildchain paper work start <topic> [--cwd <dir>] [--branch <branch>]
|
|
36
|
+
[--execute] [--json]
|
|
37
|
+
buildchain paper work submit [--cwd <dir>] [--title <title>] [--body <body>]
|
|
38
|
+
[--execute] [--json]
|
|
39
|
+
buildchain paper fleet audit [--root <dir>] [--offline] [--json]
|
|
40
|
+
buildchain paper fleet update [--root <dir>] [--write] [--json]
|
|
35
41
|
buildchain paper preflight [--cwd <dir>] [--offline] [--json]
|
|
36
42
|
buildchain paper bootstrap npm [--cwd <dir>] [--package <name>]
|
|
37
43
|
[--repository <owner/repo>] [--workflow <filename>]
|
|
@@ -49,7 +55,10 @@ function usage() {
|
|
|
49
55
|
|
|
50
56
|
Safety:
|
|
51
57
|
scaffold is a no-overwrite dry-run unless --write is present. migrate only
|
|
52
|
-
rewrites
|
|
58
|
+
rewrites only Buildchain-owned authority, workflow, lock, version, and package
|
|
59
|
+
control surfaces. work start/submit refuse dirty, stale, forked, ambiguous,
|
|
60
|
+
protected, or non-fast-forward sources. fleet update requires isolated work
|
|
61
|
+
branches and is a dry-run unless --write is present.
|
|
53
62
|
npm bootstrap, Alpha PR creation, and resume dispatch never mutate externally
|
|
54
63
|
unless --execute is present. Real npm bootstrap additionally requires the
|
|
55
64
|
exact --confirm-public-package value.
|
|
@@ -87,10 +96,6 @@ function commandResult(command, args, { cwd, timeout = 60000 } = {}) {
|
|
|
87
96
|
};
|
|
88
97
|
}
|
|
89
98
|
|
|
90
|
-
function publicScaffoldPlan(plan) {
|
|
91
|
-
return plan;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
99
|
function humanSummary(result) {
|
|
95
100
|
if (result.contract === PAPER_SCAFFOLD_CONTRACT) {
|
|
96
101
|
process.stdout.write(
|
|
@@ -169,7 +174,7 @@ function humanSummary(result) {
|
|
|
169
174
|
}
|
|
170
175
|
return;
|
|
171
176
|
}
|
|
172
|
-
|
|
177
|
+
printPaperWorkFleetSummary(result, printJson);
|
|
173
178
|
}
|
|
174
179
|
|
|
175
180
|
function printResult(result, json) {
|
|
@@ -177,7 +182,8 @@ function printResult(result, json) {
|
|
|
177
182
|
else humanSummary(result);
|
|
178
183
|
}
|
|
179
184
|
|
|
180
|
-
function githubPrRows({ cwd, repository, sourceRef, targetRef }) {
|
|
185
|
+
function githubPrRows({ cwd, repository, sourceRef, targetRef = "" }) {
|
|
186
|
+
const targetArgs = targetRef ? ["--base", targetRef] : [];
|
|
181
187
|
const query = commandResult(
|
|
182
188
|
"gh",
|
|
183
189
|
[
|
|
@@ -187,8 +193,7 @@ function githubPrRows({ cwd, repository, sourceRef, targetRef }) {
|
|
|
187
193
|
repository,
|
|
188
194
|
"--head",
|
|
189
195
|
sourceRef,
|
|
190
|
-
|
|
191
|
-
targetRef,
|
|
196
|
+
...targetArgs,
|
|
192
197
|
"--state",
|
|
193
198
|
"open",
|
|
194
199
|
"--json",
|
|
@@ -461,36 +466,19 @@ export async function runPaperCli(
|
|
|
461
466
|
? [maybeSubcommand, ...rest]
|
|
462
467
|
: rest;
|
|
463
468
|
const cwd = path.resolve(readFlag(effectiveArgs, "cwd", process.cwd()));
|
|
469
|
+
const workFleet = runPaperWorkFleetCli({
|
|
470
|
+
command,
|
|
471
|
+
subcommand: maybeSubcommand,
|
|
472
|
+
args: effectiveArgs,
|
|
473
|
+
cwd,
|
|
474
|
+
buildchainRoot,
|
|
475
|
+
buildchainVersion,
|
|
476
|
+
buildchainRef,
|
|
477
|
+
buildchainSha,
|
|
478
|
+
});
|
|
464
479
|
let result;
|
|
465
|
-
if (
|
|
466
|
-
|
|
467
|
-
cwd,
|
|
468
|
-
buildchainRoot,
|
|
469
|
-
buildchainVersion,
|
|
470
|
-
buildchainRef: readFlag(effectiveArgs, "buildchain-ref", buildchainRef),
|
|
471
|
-
buildchainSha,
|
|
472
|
-
name: readFlag(effectiveArgs, "name", path.basename(cwd)),
|
|
473
|
-
title: readFlag(effectiveArgs, "title", ""),
|
|
474
|
-
packageName: readFlag(effectiveArgs, "package", ""),
|
|
475
|
-
repository: readFlag(effectiveArgs, "repository", ""),
|
|
476
|
-
version: readFlag(effectiveArgs, "version", "0.1.0-alpha.0"),
|
|
477
|
-
siteBaseUrl: readFlag(effectiveArgs, "site-base-url", ""),
|
|
478
|
-
});
|
|
479
|
-
result =
|
|
480
|
-
hasFlag(effectiveArgs, "write") || hasFlag(effectiveArgs, "execute")
|
|
481
|
-
? writePaperScaffold(plan)
|
|
482
|
-
: publicScaffoldPlan(plan);
|
|
483
|
-
} else if (command === "migrate") {
|
|
484
|
-
const plan = planPaperMigration({
|
|
485
|
-
cwd,
|
|
486
|
-
buildchainRoot,
|
|
487
|
-
buildchainVersion,
|
|
488
|
-
buildchainSha,
|
|
489
|
-
});
|
|
490
|
-
result =
|
|
491
|
-
hasFlag(effectiveArgs, "write") || hasFlag(effectiveArgs, "execute")
|
|
492
|
-
? writePaperMigration(plan)
|
|
493
|
-
: plan;
|
|
480
|
+
if (workFleet.handled) {
|
|
481
|
+
result = workFleet.result;
|
|
494
482
|
} else if (command === "preflight") {
|
|
495
483
|
result = collectPaperPreflight({
|
|
496
484
|
cwd,
|
|
@@ -505,8 +493,6 @@ export async function runPaperCli(
|
|
|
505
493
|
),
|
|
506
494
|
offline: hasFlag(effectiveArgs, "offline"),
|
|
507
495
|
});
|
|
508
|
-
} else if (command === "status") {
|
|
509
|
-
result = collectPaperStatus({ cwd });
|
|
510
496
|
} else if (command === "bootstrap" && maybeSubcommand === "npm") {
|
|
511
497
|
result = executePaperNpmBootstrap({
|
|
512
498
|
cwd,
|
|
@@ -591,7 +577,7 @@ export async function runPaperCli(
|
|
|
591
577
|
: plan;
|
|
592
578
|
} else {
|
|
593
579
|
throw new Error(
|
|
594
|
-
"usage: buildchain paper <scaffold|migrate|preflight|bootstrap npm|build|alpha|status|resume> ...",
|
|
580
|
+
"usage: buildchain paper <scaffold|migrate|work start|work submit|fleet audit|fleet update|preflight|bootstrap npm|build|alpha|status|resume> ...",
|
|
595
581
|
);
|
|
596
582
|
}
|
|
597
583
|
printResult(result, json);
|