@kungfu-tech/buildchain 3.0.3-alpha.0 → 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/AGENTS.md +1 -1
- package/CONTRIBUTING.md +1 -1
- package/README.md +32 -1
- package/actions/promote-buildchain-ref/README.md +90 -19
- package/actions/report-buildchain-issue/README.md +19 -1
- package/actions/run-lifecycle/README.md +5 -0
- package/actions/validate-config/README.md +19 -1
- package/bin/buildchain.mjs +41 -241
- package/bin/internal/command-registry.mjs +82 -0
- package/bin/internal/trust-release-cli.mjs +1 -0
- package/contracts/auditable-demo-media-profiles-v1.json +116 -1
- package/contracts/buildchain-v2-residuals-v1.json +0 -45
- package/contracts/evidence/auditable-demo-responsive-web-delivery-v1.json +143 -0
- package/contracts/evidence/auditable-demo-web-delivery-v1.json +7 -3
- package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/complete-transcript.txt +2 -0
- package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/public-projection.json +16 -0
- package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/scene.json +12 -0
- package/dist/site/buildchain-contract.json +161 -42
- package/dist/site/buildchain-site.json +180 -84
- package/dist/site/capability-registry.json +6 -6
- package/dist/site/cli-registry.json +932 -98
- package/dist/site/controller-registry.json +97 -5
- package/dist/site/kfd-claims.json +316 -23
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +20 -20
- package/dist/site/node-api-registry.json +371 -56
- package/dist/site/page-registry.json +155 -59
- package/dist/site/public-surface-audit.json +286 -24
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +3 -0
- package/dist/site/site-manifest.json +24 -24
- package/dist/site/workflow-registry.json +570 -79
- package/docs/MAP.md +22 -4
- package/docs/auditable-demo.md +35 -12
- package/docs/aws-us-elastic-runner-burst-plane.md +381 -0
- package/docs/cli.md +143 -12
- package/docs/consumer-issue-reporting.md +19 -1
- package/docs/github-governance-authority.md +2 -2
- package/docs/lifecycle-protocol.md +20 -2
- package/docs/migration-inventory.md +24 -5
- package/docs/ownership.md +19 -1
- package/docs/publication-artifacts.md +185 -18
- package/docs/publication-authority.md +4 -4
- package/docs/publish-transaction.md +78 -31
- package/docs/release-candidate.md +24 -0
- package/docs/release-flow.md +50 -32
- package/docs/release-governance.md +31 -31
- package/docs/release-passport.md +4 -0
- package/docs/release-propagation.md +57 -12
- package/docs/reusable-build-surface.md +95 -27
- package/docs/runtime-train-validation.md +20 -2
- package/docs/stable-candidate-patrol.md +6 -6
- package/docs/toolkit-observability.md +22 -0
- package/docs/versioning.md +6 -4
- package/docs/web-surface-deployments.md +62 -0
- package/package.json +7 -5
- package/packages/core/README.md +61 -4
- package/packages/core/buildchain-contract.js +8 -4
- package/packages/core/cache-evidence.js +1 -0
- package/packages/core/diagnostics.js +105 -30
- package/packages/core/github-governance-authority.js +10 -25
- package/packages/core/index.js +40 -0
- 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 +3180 -0
- package/packages/core/public-surface-audit.js +26 -75
- package/packages/core/public-surface-cli.js +117 -0
- package/packages/core/publication-artifact.js +1 -6
- package/packages/core/publication-authority.js +1 -0
- package/packages/core/publication-reproducibility.js +947 -0
- package/packages/core/publication-sealed-bundle.js +190 -0
- package/packages/core/publish-transaction.js +147 -17
- package/packages/core/release-candidate.js +234 -0
- package/packages/core/release-passport.js +483 -201
- package/packages/core/release-propagation.js +175 -1
- package/scripts/auditable-demo.mjs +119 -5
- package/scripts/aws-codebuild-toolchain.mjs +285 -0
- package/scripts/aws-macos-jit-core.mjs +378 -0
- package/scripts/aws-macos-jit.mjs +123 -0
- package/scripts/aws-runner-burst-core.mjs +248 -0
- package/scripts/aws-runner-burst.mjs +79 -0
- package/scripts/aws-windows-jit-core.mjs +374 -0
- package/scripts/aws-windows-jit.mjs +121 -0
- package/scripts/build-contract-core.mjs +69 -6
- package/scripts/build-standalone-binary.mjs +23 -0
- package/scripts/buildchain-cli-help.mjs +256 -0
- package/scripts/buildchain-contract-lock.mjs +1 -1
- package/scripts/check-internal-architecture.mjs +69 -2
- package/scripts/check-inventory.mjs +69 -2
- package/scripts/check-javascript-syntax.mjs +31 -0
- package/scripts/check-maintainability.mjs +371 -0
- package/scripts/compiler-cache-evidence.mjs +221 -0
- package/scripts/dev-alpha-candidate-patrol.mjs +268 -43
- package/scripts/finalize-native-artifact-signing-result.mjs +75 -4
- package/scripts/generate-channel-promotion-workflow.mjs +3 -12
- package/scripts/generate-release-candidate-passport.mjs +4 -0
- package/scripts/generate-site-bundle.mjs +68 -188
- package/scripts/import-artifact-signing-results.mjs +64 -5
- package/scripts/inspect-artifact-signing-requests.mjs +7 -0
- package/scripts/maintainability-metrics.mjs +339 -0
- package/scripts/npm-publish-transaction.mjs +57 -6
- package/scripts/paper-work-fleet-cli.mjs +563 -0
- package/scripts/paper.mjs +631 -0
- package/scripts/promotion-channel-router.mjs +16 -4
- package/scripts/publication-reproducibility.mjs +62 -0
- package/scripts/release-propagation.mjs +39 -0
- package/scripts/resolve-build-contract.mjs +6 -0
- package/scripts/route-offline-runners.mjs +236 -0
- package/scripts/run-lifecycle-core.mjs +62 -1
- package/scripts/runtime-ref-core.mjs +23 -2
- package/scripts/seal-artifact-signing-requests.mjs +13 -6
- package/scripts/site-capability-metadata.mjs +202 -0
- package/scripts/stable-candidate-qualification.mjs +42 -0
- package/scripts/web-surface-core.mjs +272 -86
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { planPaperMigration, writePaperMigration } from "./paper.js";
|
|
4
|
+
import {
|
|
5
|
+
PAPER_PATHS,
|
|
6
|
+
PAPER_WORK_BRANCH_PATTERN,
|
|
7
|
+
gitValue,
|
|
8
|
+
paperWorkSource,
|
|
9
|
+
readJson,
|
|
10
|
+
rootedPlan,
|
|
11
|
+
sha256Text,
|
|
12
|
+
stableJson,
|
|
13
|
+
workCheck,
|
|
14
|
+
} from "./paper-repository.js";
|
|
15
|
+
|
|
16
|
+
export const PAPER_FLEET_AUDIT_CONTRACT = "kungfu-buildchain-paper-fleet-audit";
|
|
17
|
+
export const PAPER_FLEET_UPDATE_PLAN_CONTRACT =
|
|
18
|
+
"kungfu-buildchain-paper-fleet-update-plan";
|
|
19
|
+
|
|
20
|
+
export function discoverPaperFleet(root = process.cwd()) {
|
|
21
|
+
const resolvedRoot = path.resolve(root);
|
|
22
|
+
if (
|
|
23
|
+
!fs.existsSync(resolvedRoot) ||
|
|
24
|
+
!fs.statSync(resolvedRoot).isDirectory()
|
|
25
|
+
) {
|
|
26
|
+
throw new Error("paper fleet root must be an existing directory");
|
|
27
|
+
}
|
|
28
|
+
return fs
|
|
29
|
+
.readdirSync(resolvedRoot, { withFileTypes: true })
|
|
30
|
+
.filter(
|
|
31
|
+
(entry) =>
|
|
32
|
+
entry.isDirectory() &&
|
|
33
|
+
entry.name.startsWith("paper-") &&
|
|
34
|
+
fs.existsSync(path.join(resolvedRoot, entry.name, ".git")),
|
|
35
|
+
)
|
|
36
|
+
.map((entry) => path.join(resolvedRoot, entry.name))
|
|
37
|
+
.sort();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function plannedManagedSurfaces(options) {
|
|
41
|
+
try {
|
|
42
|
+
const plan = planPaperMigration(options);
|
|
43
|
+
return { changes: plan.changes, error: "" };
|
|
44
|
+
} catch (error) {
|
|
45
|
+
return { changes: [], error: error.message };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function paperFleetEntry({
|
|
50
|
+
cwd,
|
|
51
|
+
buildchainRoot,
|
|
52
|
+
buildchainVersion,
|
|
53
|
+
buildchainSha,
|
|
54
|
+
}) {
|
|
55
|
+
const source = paperWorkSource(cwd);
|
|
56
|
+
const packageJson = readJson(path.resolve(cwd, "package.json")).value || {};
|
|
57
|
+
const pinPath = path.resolve(cwd, PAPER_PATHS.versionPin);
|
|
58
|
+
const lockPath = path.resolve(cwd, "pnpm-lock.yaml");
|
|
59
|
+
const expected = plannedManagedSurfaces({
|
|
60
|
+
cwd,
|
|
61
|
+
buildchainRoot,
|
|
62
|
+
buildchainVersion,
|
|
63
|
+
buildchainSha,
|
|
64
|
+
});
|
|
65
|
+
const managed = expected.changes.map((entry) => ({
|
|
66
|
+
path: entry.path,
|
|
67
|
+
status:
|
|
68
|
+
entry.action === "unchanged"
|
|
69
|
+
? "current"
|
|
70
|
+
: entry.action === "create"
|
|
71
|
+
? "missing"
|
|
72
|
+
: "drifted",
|
|
73
|
+
expectedSha256: entry.sha256,
|
|
74
|
+
currentSha256: entry.currentSha256,
|
|
75
|
+
}));
|
|
76
|
+
const dependency =
|
|
77
|
+
packageJson.devDependencies?.["@kungfu-tech/buildchain"] || "";
|
|
78
|
+
const lockText = fs.existsSync(lockPath)
|
|
79
|
+
? fs.readFileSync(lockPath, "utf8")
|
|
80
|
+
: "";
|
|
81
|
+
const checks = [
|
|
82
|
+
workCheck(
|
|
83
|
+
"repository.canonical-origin",
|
|
84
|
+
source.canonical,
|
|
85
|
+
"Repository identity resolves to the canonical kungfu-systems origin.",
|
|
86
|
+
"git remote -v",
|
|
87
|
+
),
|
|
88
|
+
workCheck(
|
|
89
|
+
"package.buildchain-v3",
|
|
90
|
+
dependency === buildchainVersion && /^3\./.test(dependency),
|
|
91
|
+
"package.json pins the exact Buildchain v3 runtime.",
|
|
92
|
+
"buildchain paper migrate --write --json",
|
|
93
|
+
),
|
|
94
|
+
workCheck(
|
|
95
|
+
"package.pnpm-lock",
|
|
96
|
+
Boolean(lockText) &&
|
|
97
|
+
lockText.includes("@kungfu-tech/buildchain") &&
|
|
98
|
+
lockText.includes(buildchainVersion),
|
|
99
|
+
"pnpm-lock.yaml binds the Buildchain dependency.",
|
|
100
|
+
"pnpm install --lockfile-only",
|
|
101
|
+
),
|
|
102
|
+
workCheck(
|
|
103
|
+
"runtime.version-pin",
|
|
104
|
+
fs.existsSync(pinPath) &&
|
|
105
|
+
fs.readFileSync(pinPath, "utf8").trim() === buildchainVersion,
|
|
106
|
+
"The repository version pin equals the exact Buildchain runtime.",
|
|
107
|
+
"buildchain paper migrate --write --json",
|
|
108
|
+
),
|
|
109
|
+
workCheck(
|
|
110
|
+
"managed-surfaces.current",
|
|
111
|
+
!expected.error && managed.every((entry) => entry.status === "current"),
|
|
112
|
+
"Every Buildchain-owned paper control surface matches v3.",
|
|
113
|
+
"buildchain paper migrate --write --json",
|
|
114
|
+
),
|
|
115
|
+
];
|
|
116
|
+
return {
|
|
117
|
+
name: path.basename(cwd),
|
|
118
|
+
cwd,
|
|
119
|
+
repository: source.repository,
|
|
120
|
+
branch: source.branch,
|
|
121
|
+
head: source.head,
|
|
122
|
+
clean: source.clean,
|
|
123
|
+
package: packageJson.name || "",
|
|
124
|
+
buildchainDependency: dependency,
|
|
125
|
+
expectedError: expected.error,
|
|
126
|
+
managed,
|
|
127
|
+
checks,
|
|
128
|
+
ok: checks.every((entry) => entry.status === "pass"),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function collectPaperFleetAudit({
|
|
133
|
+
root = process.cwd(),
|
|
134
|
+
repositories = [],
|
|
135
|
+
buildchainRoot = process.cwd(),
|
|
136
|
+
buildchainVersion = "",
|
|
137
|
+
buildchainSha = "",
|
|
138
|
+
governance = {},
|
|
139
|
+
} = {}) {
|
|
140
|
+
const resolvedRoot = path.resolve(root);
|
|
141
|
+
const paths = (
|
|
142
|
+
repositories.length > 0
|
|
143
|
+
? repositories.map((entry) => path.resolve(entry))
|
|
144
|
+
: discoverPaperFleet(resolvedRoot)
|
|
145
|
+
).sort();
|
|
146
|
+
const entries = paths.map((cwd) =>
|
|
147
|
+
paperFleetEntry({ cwd, buildchainRoot, buildchainVersion, buildchainSha }),
|
|
148
|
+
);
|
|
149
|
+
const governanceEntries = entries.map((entry) => ({
|
|
150
|
+
repository: entry.repository,
|
|
151
|
+
...(governance[entry.repository] || { status: "unobserved" }),
|
|
152
|
+
}));
|
|
153
|
+
const payload = {
|
|
154
|
+
schemaVersion: 1,
|
|
155
|
+
contract: PAPER_FLEET_AUDIT_CONTRACT,
|
|
156
|
+
ok:
|
|
157
|
+
entries.length > 0 &&
|
|
158
|
+
entries.every((entry) => entry.ok) &&
|
|
159
|
+
governanceEntries.every((entry) =>
|
|
160
|
+
["pass", "unobserved"].includes(entry.status),
|
|
161
|
+
),
|
|
162
|
+
root: resolvedRoot,
|
|
163
|
+
runtime: {
|
|
164
|
+
version: buildchainVersion,
|
|
165
|
+
sha: buildchainSha || gitValue(buildchainRoot, ["rev-parse", "HEAD"]),
|
|
166
|
+
},
|
|
167
|
+
summary: {
|
|
168
|
+
repositories: entries.length,
|
|
169
|
+
current: entries.filter((entry) => entry.ok).length,
|
|
170
|
+
drifted: entries.filter((entry) => !entry.ok).length,
|
|
171
|
+
governanceObserved: governanceEntries.filter(
|
|
172
|
+
(entry) => entry.status !== "unobserved",
|
|
173
|
+
).length,
|
|
174
|
+
},
|
|
175
|
+
repositories: entries,
|
|
176
|
+
governance: governanceEntries,
|
|
177
|
+
};
|
|
178
|
+
return { ...payload, auditRoot: sha256Text(stableJson(payload)) };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function planPaperFleetUpdate(options = {}) {
|
|
182
|
+
const audit = collectPaperFleetAudit(options);
|
|
183
|
+
const plans = audit.repositories.map((entry) => {
|
|
184
|
+
if (!PAPER_WORK_BRANCH_PATTERN.test(entry.branch)) {
|
|
185
|
+
return {
|
|
186
|
+
contract: "kungfu-buildchain-paper-migration",
|
|
187
|
+
ok: false,
|
|
188
|
+
cwd: entry.cwd,
|
|
189
|
+
dryRun: true,
|
|
190
|
+
error:
|
|
191
|
+
"paper fleet update requires an allowed non-protected work branch",
|
|
192
|
+
changes: [],
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
return planPaperMigration({
|
|
197
|
+
cwd: entry.cwd,
|
|
198
|
+
buildchainRoot: options.buildchainRoot,
|
|
199
|
+
buildchainVersion: options.buildchainVersion,
|
|
200
|
+
buildchainSha: options.buildchainSha,
|
|
201
|
+
});
|
|
202
|
+
} catch (error) {
|
|
203
|
+
return {
|
|
204
|
+
contract: "kungfu-buildchain-paper-migration",
|
|
205
|
+
ok: false,
|
|
206
|
+
cwd: entry.cwd,
|
|
207
|
+
dryRun: true,
|
|
208
|
+
error: error.message,
|
|
209
|
+
changes: [],
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
const ok = plans.length > 0 && plans.every((entry) => entry.ok);
|
|
214
|
+
return rootedPlan({
|
|
215
|
+
schemaVersion: 1,
|
|
216
|
+
contract: PAPER_FLEET_UPDATE_PLAN_CONTRACT,
|
|
217
|
+
ok,
|
|
218
|
+
root: audit.root,
|
|
219
|
+
dryRun: true,
|
|
220
|
+
auditRoot: audit.auditRoot,
|
|
221
|
+
runtime: audit.runtime,
|
|
222
|
+
plans,
|
|
223
|
+
nextActions: ok
|
|
224
|
+
? [
|
|
225
|
+
{
|
|
226
|
+
id: "write-fleet-update",
|
|
227
|
+
command: "buildchain paper fleet update --write --json",
|
|
228
|
+
description:
|
|
229
|
+
"Apply only reviewed Buildchain-owned control surfaces, then refresh each pnpm lockfile.",
|
|
230
|
+
},
|
|
231
|
+
]
|
|
232
|
+
: [
|
|
233
|
+
{
|
|
234
|
+
id: "repair-blocked-repositories",
|
|
235
|
+
command: "git status --short",
|
|
236
|
+
description:
|
|
237
|
+
"Fleet update requires every target repository to be clean and rooted exactly.",
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function writePaperFleetUpdate(plan) {
|
|
244
|
+
if (!plan || plan.contract !== PAPER_FLEET_UPDATE_PLAN_CONTRACT || !plan.ok) {
|
|
245
|
+
return {
|
|
246
|
+
...plan,
|
|
247
|
+
ok: false,
|
|
248
|
+
dryRun: false,
|
|
249
|
+
results: [],
|
|
250
|
+
errorCode: "paper-fleet-update-blocked",
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
const results = plan.plans.map((entry) => writePaperMigration(entry));
|
|
254
|
+
return {
|
|
255
|
+
...plan,
|
|
256
|
+
ok: results.every((entry) => entry.ok),
|
|
257
|
+
dryRun: false,
|
|
258
|
+
results,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { loadBuildchainConfig } from "./buildchain-config.js";
|
|
6
|
+
|
|
7
|
+
export const PAPER_PATHS = Object.freeze({
|
|
8
|
+
config: ".buildchain/buildchain.toml",
|
|
9
|
+
versionPin: ".buildchain-version",
|
|
10
|
+
contractLock: ".buildchain/contract-lock.json",
|
|
11
|
+
buildWorkflow: ".github/workflows/build.yml",
|
|
12
|
+
releaseWorkflow: ".github/workflows/paper-release.yml",
|
|
13
|
+
reproducibilityReceipt:
|
|
14
|
+
".buildchain/publication/reproducibility-receipt.json",
|
|
15
|
+
sealedBundle: ".buildchain/admitted/sealed-bundle.json",
|
|
16
|
+
admission: ".buildchain/admitted/publication-admission.json",
|
|
17
|
+
capability: ".buildchain/admitted/publication-capability.json",
|
|
18
|
+
npmBootstrap: ".buildchain/paper/npm-bootstrap.json",
|
|
19
|
+
npmTrust: ".buildchain/paper/npm-trust.json",
|
|
20
|
+
provisioningAuthority: ".buildchain/paper/provisioning-authority.json",
|
|
21
|
+
visibility: ".buildchain/paper/visibility.json",
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export const PAPER_WORK_BRANCH_PATTERN =
|
|
25
|
+
/^(?:feature|fix|docs|chore|ci|refactor)\/[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?(?:\/[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?)*$/;
|
|
26
|
+
|
|
27
|
+
export function stableJson(value) {
|
|
28
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
29
|
+
if (value && typeof value === "object") {
|
|
30
|
+
return `{${Object.keys(value)
|
|
31
|
+
.sort()
|
|
32
|
+
.map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`)
|
|
33
|
+
.join(",")}}`;
|
|
34
|
+
}
|
|
35
|
+
return JSON.stringify(value);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function sha256Text(value) {
|
|
39
|
+
return `sha256:${crypto.createHash("sha256").update(String(value)).digest("hex")}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function readJson(filePath) {
|
|
43
|
+
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
|
|
44
|
+
return { exists: false, value: undefined, error: "" };
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
return {
|
|
48
|
+
exists: true,
|
|
49
|
+
value: JSON.parse(fs.readFileSync(filePath, "utf8")),
|
|
50
|
+
error: "",
|
|
51
|
+
};
|
|
52
|
+
} catch (error) {
|
|
53
|
+
return { exists: true, value: undefined, error: error.message };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function normalizeRepository(value) {
|
|
58
|
+
const normalized = String(value || "")
|
|
59
|
+
.trim()
|
|
60
|
+
.replace(/^git\+/, "")
|
|
61
|
+
.replace(/^git@github\.com:/, "")
|
|
62
|
+
.replace(/^ssh:\/\/git@github\.com\//, "")
|
|
63
|
+
.replace(/^https?:\/\/github\.com\//, "")
|
|
64
|
+
.replace(/\.git$/, "")
|
|
65
|
+
.replace(/^\/+|\/+$/g, "");
|
|
66
|
+
return /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(normalized)
|
|
67
|
+
? normalized
|
|
68
|
+
: "";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function commandResult(
|
|
72
|
+
command,
|
|
73
|
+
args,
|
|
74
|
+
{ cwd, env = process.env, timeout = 15000 } = {},
|
|
75
|
+
) {
|
|
76
|
+
const result = spawnSync(command, args, {
|
|
77
|
+
cwd,
|
|
78
|
+
env,
|
|
79
|
+
encoding: "utf8",
|
|
80
|
+
timeout,
|
|
81
|
+
maxBuffer: 2 * 1024 * 1024,
|
|
82
|
+
});
|
|
83
|
+
return {
|
|
84
|
+
ok: result.status === 0,
|
|
85
|
+
status: result.status ?? 1,
|
|
86
|
+
stdout: String(result.stdout || "").trim(),
|
|
87
|
+
stderr: String(result.stderr || "").trim(),
|
|
88
|
+
error: result.error?.message || "",
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function gitResult(cwd, args) {
|
|
93
|
+
return commandResult("git", args, { cwd });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function gitValue(cwd, args) {
|
|
97
|
+
const result = gitResult(cwd, args);
|
|
98
|
+
return result.ok ? result.stdout : "";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function paperConfig(cwd) {
|
|
102
|
+
const loaded = loadBuildchainConfig(cwd);
|
|
103
|
+
if (!loaded)
|
|
104
|
+
return { loaded: undefined, error: `${PAPER_PATHS.config} is missing` };
|
|
105
|
+
if (loaded.config.project?.type !== "publication-artifact") {
|
|
106
|
+
return { loaded, error: 'project.type must be "publication-artifact"' };
|
|
107
|
+
}
|
|
108
|
+
if (!loaded.config.publication)
|
|
109
|
+
return { loaded, error: "[publication] is missing" };
|
|
110
|
+
return { loaded, error: "" };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function parsePaperVersion(version) {
|
|
114
|
+
const normalized = String(version || "")
|
|
115
|
+
.trim()
|
|
116
|
+
.replace(/^v/, "");
|
|
117
|
+
const match = normalized.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/);
|
|
118
|
+
if (!match)
|
|
119
|
+
throw new Error("publication.version must be semver before planning Alpha");
|
|
120
|
+
return {
|
|
121
|
+
version: normalized,
|
|
122
|
+
major: Number(match[1]),
|
|
123
|
+
minor: Number(match[2]),
|
|
124
|
+
patch: Number(match[3]),
|
|
125
|
+
prerelease: match[4] || "",
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function resolvePaperRepository(cwd = process.cwd()) {
|
|
130
|
+
const sourcePackage = readJson(path.resolve(cwd, "package.json")).value;
|
|
131
|
+
const configured =
|
|
132
|
+
typeof sourcePackage?.repository === "string"
|
|
133
|
+
? sourcePackage.repository
|
|
134
|
+
: sourcePackage?.repository?.url;
|
|
135
|
+
return (
|
|
136
|
+
normalizeRepository(configured) ||
|
|
137
|
+
normalizeRepository(gitValue(cwd, ["config", "--get", "remote.origin.url"]))
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function paperDevelopmentRef(cwd) {
|
|
142
|
+
const configResult = paperConfig(cwd);
|
|
143
|
+
if (configResult.error) throw new Error(configResult.error);
|
|
144
|
+
const parsed = parsePaperVersion(
|
|
145
|
+
configResult.loaded.config.publication.version,
|
|
146
|
+
);
|
|
147
|
+
return `dev/v${parsed.major}/v${parsed.major}.${parsed.minor}`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function remoteBranchObservation(cwd, branch) {
|
|
151
|
+
const result = gitResult(cwd, [
|
|
152
|
+
"ls-remote",
|
|
153
|
+
"--heads",
|
|
154
|
+
"origin",
|
|
155
|
+
`refs/heads/${branch}`,
|
|
156
|
+
]);
|
|
157
|
+
const sha = result.stdout.split(/\s+/)[0] || "";
|
|
158
|
+
return {
|
|
159
|
+
observed: result.ok,
|
|
160
|
+
ok: result.ok && /^[0-9a-f]{40}$/i.test(sha),
|
|
161
|
+
sha: /^[0-9a-f]{40}$/i.test(sha) ? sha : "",
|
|
162
|
+
error: result.error || result.stderr,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function paperWorkSource(cwd) {
|
|
167
|
+
const repository = resolvePaperRepository(cwd);
|
|
168
|
+
const remotes = gitValue(cwd, ["remote"]).split(/\s+/).filter(Boolean).sort();
|
|
169
|
+
const originUrl = gitValue(cwd, ["config", "--get", "remote.origin.url"]);
|
|
170
|
+
const originRepository = normalizeRepository(originUrl);
|
|
171
|
+
return {
|
|
172
|
+
repository,
|
|
173
|
+
remotes,
|
|
174
|
+
originUrl,
|
|
175
|
+
originRepository,
|
|
176
|
+
canonical:
|
|
177
|
+
remotes.length === 1 &&
|
|
178
|
+
remotes[0] === "origin" &&
|
|
179
|
+
Boolean(repository) &&
|
|
180
|
+
repository === originRepository &&
|
|
181
|
+
repository.startsWith("kungfu-systems/"),
|
|
182
|
+
branch: gitValue(cwd, ["branch", "--show-current"]),
|
|
183
|
+
head: gitValue(cwd, ["rev-parse", "HEAD"]),
|
|
184
|
+
clean: gitResult(cwd, ["status", "--porcelain"]).stdout === "",
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function rootedPlan(payload) {
|
|
189
|
+
return { ...payload, planRoot: sha256Text(stableJson(payload)) };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function workCheck(id, ok, message, correctiveCommand = "") {
|
|
193
|
+
return {
|
|
194
|
+
id,
|
|
195
|
+
status: ok ? "pass" : "fail",
|
|
196
|
+
message,
|
|
197
|
+
correctiveCommand: ok ? "" : correctiveCommand,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function normalizedWorkBranch(topic, explicitBranch = "") {
|
|
202
|
+
const candidate = explicitBranch
|
|
203
|
+
? String(explicitBranch).trim()
|
|
204
|
+
: `feature/${String(topic || "")
|
|
205
|
+
.trim()
|
|
206
|
+
.toLowerCase()
|
|
207
|
+
.replace(/[^a-z0-9._/-]+/g, "-")
|
|
208
|
+
.replace(/^-+|-+$/g, "")}`;
|
|
209
|
+
return PAPER_WORK_BRANCH_PATTERN.test(candidate) ? candidate : "";
|
|
210
|
+
}
|