@kungfu-tech/buildchain 4.1.3-alpha.9 → 4.1.4-alpha.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/AGENTS.md +11 -13
- package/CONTRIBUTING.md +32 -9
- package/README.md +56 -140
- package/actions/release/promotion/ref/README.md +1 -1
- package/architecture/action-taxonomy.json +1 -1
- package/architecture/agent-change-map.md +32 -45
- package/architecture/build-orchestration.json +1 -1
- package/architecture/capability-state-machine-manifest.json +5 -5
- package/architecture/ci-lane-change-budget.json +92 -92
- package/architecture/code-layout.json +2 -12
- package/architecture/core-mechanisms.json +38 -39
- package/architecture/cross-platform-adopter-qualification.json +1 -1
- package/architecture/dev-delivery-orchestration.json +2 -2
- package/architecture/floating-consumer-policy.json +3 -3
- package/architecture/internal-capabilities.json +27 -28
- package/architecture/maintainability-debt.json +60 -94
- package/architecture/maintainability-policy.json +7 -13
- package/architecture/minimal-consumer-migration.json +43 -5014
- package/architecture/platform-stage-checkpoints.json +1 -1
- package/architecture/release-tail-contract.json +2 -2
- package/architecture/release-topology.json +33 -25
- package/architecture/stage-capsule-qualification.json +1 -1
- package/architecture/universal-workflow-bootstrap.json +60 -60
- package/architecture/universal-workflow-capability-policy.json +3 -3
- package/architecture/universal-workflow-fault-campaign.json +7 -7
- package/architecture/workflow-taxonomy.json +59 -60
- package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
- package/dist/site/buildchain-contract.json +138 -66
- package/dist/site/buildchain-site.json +257 -331
- package/dist/site/capability-registry.json +4 -4
- package/dist/site/cli-registry.json +6 -142
- package/dist/site/controller-registry.json +19 -19
- package/dist/site/kfd-claims.json +481 -529
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +22 -22
- package/dist/site/node-api-registry.json +82 -339
- package/dist/site/page-registry.json +229 -304
- package/dist/site/public-surface-audit.json +544 -754
- package/dist/site/publication-authority-registry.json +316 -316
- package/dist/site/publication-registry.json +1 -1
- package/dist/site/site-manifest.json +23 -23
- package/dist/site/workflow-registry.json +1071 -1027
- package/docs/MAP.md +28 -20
- package/docs/auditable-demo.md +17 -49
- package/docs/bootstrap-recovery.md +46 -37
- package/docs/cli-reference.md +2 -46
- package/docs/cli.md +97 -186
- package/docs/code-organization.md +7 -5
- package/docs/dev-alpha-candidate-patrol.md +19 -42
- package/docs/dev-delivery-orchestration.md +15 -11
- package/docs/dev-delivery-qualification-landing-adr.md +10 -9
- package/docs/dev-delivery-warrant.md +17 -16
- package/docs/dev-qualification-patrol.md +25 -20
- package/docs/getting-started.md +80 -139
- package/docs/github-artifact-attestation.md +31 -81
- package/docs/install.md +66 -30
- package/docs/lifecycle-protocol.md +19 -57
- package/docs/migration-inventory.md +17 -45
- package/docs/next-development-transition.md +13 -13
- package/docs/node-api-reference.md +122 -138
- package/docs/oci-publication.md +13 -10
- package/docs/publication-artifacts.md +128 -486
- package/docs/publication-authority.md +15 -45
- package/docs/publish-transaction.md +13 -11
- package/docs/release-candidate.md +126 -355
- package/docs/release-discussions.md +36 -67
- package/docs/release-flow.md +80 -339
- package/docs/release-governance.md +114 -962
- package/docs/release-promotion-request.md +45 -43
- package/docs/release-propagation.md +13 -27
- package/docs/release-tail-provider-plane.md +38 -65
- package/docs/reusable-build-surface.md +78 -196
- package/docs/runtime-train-validation.md +8 -6
- package/docs/stable-candidate-patrol.md +13 -66
- package/docs/v4-adopter-delivery.md +15 -15
- package/docs/v4-declarative-release-tail-migration.md +19 -17
- package/docs/v4-production-release.md +11 -6
- package/docs/v4-publication-rehearsal.md +12 -27
- package/docs/v4-stage-capsule.md +14 -20
- package/docs/v4-tail-reseal.md +34 -20
- package/docs/web-surface-deployments.md +26 -264
- package/docs/workflow-catalog.md +31 -31
- package/docs/workflow-path-migration.md +10 -4
- package/package.json +5 -4
- package/packages/core/adoption/buildchain-kfd-claims.js +7 -7
- package/packages/core/adoption/cli/output.mjs +1 -9
- package/packages/core/adoption/cli/product-gate.mjs +1 -15
- package/packages/core/adoption/cli/project.mjs +9 -10
- package/packages/core/adoption/cli/standards.mjs +1 -14
- package/packages/core/adoption/cli/support.mjs +0 -2
- package/packages/core/adoption/cli/surface-register.mjs +0 -2
- package/packages/core/adoption/commands/init-repo.mjs +77 -485
- package/packages/core/adoption/consumer-init.js +279 -0
- package/packages/core/adoption/qualification/admission.js +1 -1
- package/packages/core/build/cli/cache.mjs +0 -2
- package/packages/core/build/cli/facts.mjs +0 -3
- package/packages/core/build/cli/homebrew.mjs +0 -4
- package/packages/core/build/cli/lifecycle.mjs +0 -3
- package/packages/core/build/github-artifact-attestation.js +2 -2
- package/packages/core/build/platform-stage-checkpoints.js +1 -1
- package/packages/core/build/signing/dispatch.js +1 -1
- package/packages/core/consumer/buildchain-config.js +9 -0
- package/packages/core/consumer/contract/inspection.js +351 -9
- package/packages/core/consumer/contract/javascript-syntax.js +439 -0
- package/packages/core/consumer/contract/javascript-values.js +61 -0
- package/packages/core/consumer/contract/javascript.js +555 -0
- package/packages/core/consumer/contract/local-validation.js +84 -0
- package/packages/core/contracts/buildchain-contract.js +39 -8
- package/packages/core/contracts/cli/introspection.mjs +1 -9
- package/packages/core/contracts/public-surface-cli.js +0 -1
- package/packages/core/dev-delivery/dev-delivery-contract-surface.js +2 -2
- package/packages/core/governance/buildchain-publication-authority.js +24 -24
- package/packages/core/governance/cli/doctor.mjs +58 -42
- package/packages/core/governance/commands/architecture.mjs +6 -1
- package/packages/core/governance/commands/audit-publication-control-plane.mjs +1 -1
- package/packages/core/governance/commands/workflow-friction-report.mjs +1 -1
- package/packages/core/governance/github-governance-authority.js +2 -2
- package/packages/core/governance/publication/collection.js +1 -1
- package/packages/core/index.js +1 -3
- package/packages/core/observability/cli/candidate.mjs +0 -2
- package/packages/core/observability/cli/context.mjs +0 -5
- package/packages/core/observability/cli/diagnostics.mjs +0 -7
- package/packages/core/observability/cli/events.mjs +2 -10
- package/packages/core/paper/commands/paper-work-fleet-cli.mjs +1 -1
- package/packages/core/paper/commands/paper.mjs +33 -422
- package/packages/core/paper/consumer-status.js +72 -0
- package/packages/core/paper/operations/identity.js +0 -3
- package/packages/core/paper/operations/migration.js +460 -0
- package/packages/core/paper/operations/preflight-checks.js +8 -68
- package/packages/core/paper/operations/scaffold.js +16 -326
- package/packages/core/paper/operations/status.js +27 -53
- package/packages/core/paper/paper-agent-entry.js +36 -147
- package/packages/core/paper/paper-fleet.js +24 -39
- package/packages/core/paper/paper-repository.js +16 -0
- package/packages/core/paper/paper-runtime-channels.js +1 -1
- package/packages/core/paper/paper-scaffold-content.js +153 -162
- package/packages/core/paper/paper-work.js +3 -10
- package/packages/core/paper/paper.js +43 -5
- package/packages/core/providers/github/artifact-attestation.js +1 -1
- package/packages/core/providers/github/pipeline-context-artifacts.js +172 -0
- package/packages/core/providers/github/pipeline-release-evidence.js +17 -98
- package/packages/core/providers/github/pipeline-stable-baseline.js +86 -0
- package/packages/core/providers/github/pipeline-stable-source.js +22 -4
- package/packages/core/providers/github/release-evidence-assets.js +97 -0
- package/packages/core/publication/authority/control-plane.js +2 -2
- package/packages/core/publication/authority/seal.js +1 -1
- package/packages/core/publication/cli/dispatch.mjs +1 -12
- package/packages/core/publication/pipeline/actions.js +16 -9
- package/packages/core/publication/pipeline/apply.js +17 -9
- package/packages/core/publication/pipeline/context.js +29 -0
- package/packages/core/publication/pipeline/journal.js +13 -1
- package/packages/core/publication/pipeline/prepare.js +15 -6
- package/packages/core/publication/pipeline/recovery-import.js +1 -1
- package/packages/core/publication/pipeline/recovery-materials.js +63 -1
- package/packages/core/publication/pipeline/recovery-prepare.js +21 -9
- package/packages/core/publication/pipeline/stable-entry.js +12 -3
- package/packages/core/publication/pipeline/stable-products.js +21 -19
- package/packages/core/publication/pipeline/version-actions.js +15 -6
- package/packages/core/publication/publication-rehearsal-projection.js +0 -9
- package/packages/core/release/cli/dispatch.mjs +1 -4
- package/packages/core/release/cli/inspection-handlers.mjs +1 -33
- package/packages/core/release/cli/release-handlers.mjs +2 -19
- package/packages/core/release/cli/verification-handlers.mjs +4 -25
- package/packages/core/release/next-development-projection.js +13 -13
- package/packages/core/release/passport/locations.js +13 -8
- package/packages/core/release/release-passport.js +70 -5
- package/packages/core/web/cli/badges.mjs +0 -4
- package/packages/core/web/publication-admission.js +1 -1
- package/packages/core/web/publication-capability.js +1 -1
- package/packages/core/web/readme-badges.js +1 -1
- package/packages/core/workflow/cli/main.mjs +1 -4
- package/packages/core/workflow/cli/process.mjs +1 -1
- package/packages/core/workflow/commands/buildchain-cli-help.mjs +2 -8
- package/packages/core/workflow/engine/conformance.js +1 -1
- package/packages/core/workflow/pipeline/build.js +21 -1
- package/packages/core/workflow/pipeline/delivery-observation.js +6 -2
- package/packages/core/workflow/workflow-taxonomy.mjs +61 -17
- package/scripts/check-code-layout.mjs +2 -0
- package/scripts/check-core-mechanism-inventory.mjs +29 -13
- package/scripts/check-floating-consumer-policy-contract.mjs +3 -3
- package/scripts/check-inventory.mjs +27 -27
- package/scripts/check-maintainability.mjs +28 -5
- package/scripts/check-public-dogfood-contract.mjs +3 -3
- package/scripts/check-release-topology.mjs +1 -1
- package/scripts/check-self-consumer-contract.mjs +7 -7
- package/scripts/check-universal-workflow-bootstrap.mjs +1 -1
- package/scripts/generate-channel-promotion-workflow.mjs +1 -1
- package/scripts/generate-minimal-consumer-contract.mjs +21 -1
- package/scripts/generate-site-bundle.mjs +2 -1
- package/scripts/generate-tail-reseal-fixture.mjs +3 -3
- package/scripts/inventory/promotion.mjs +1 -1
- package/scripts/next-development-self-dogfood-harness.mjs +1 -1
- package/scripts/next-development-self-dogfood.mjs +4 -4
- package/scripts/site-capability-metadata.mjs +2 -6
- package/scripts/source-metric-lineage.mjs +62 -7
- package/scripts/verify-golden-path.mjs +31 -35
- package/templates/native-dev-delivery.yml +1 -1
- package/templates/universal-buildchain-bootstrap.yml +1 -1
- package/packages/core/paper/operations/bootstrap.js +0 -75
- package/packages/core/paper/operations/plans.js +0 -213
- package/packages/core/paper/operations/provisioning.js +0 -113
- package/packages/core/paper/operations/scaffold-config.js +0 -77
- package/packages/core/paper/operations/scaffold-workflows.js +0 -116
- package/packages/core/paper/operations/workspace.js +0 -60
- package/packages/core/paper/paper-npm-bootstrap.js +0 -492
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { inspectJavaScriptModuleClosure } from "./javascript.js";
|
|
1
4
|
import { consumerWorkflows } from "./entries.js";
|
|
2
5
|
import { compileConsumerPlan, CONFIG_PATH } from "./plan.js";
|
|
6
|
+
import { checkWorkflowTaxonomy } from "../../workflow/workflow-taxonomy.mjs";
|
|
3
7
|
|
|
4
8
|
const INTERNAL_WIRING = [
|
|
5
9
|
/\b(?:request-json|native-roots-json|used-nonces-json|warrant-result|source-proof|integration-proof)\b/iu,
|
|
6
10
|
/\bbuildchain\s+(?:dev\s+(?:warrant|proof)|release|promote|recover|publish)\b/iu,
|
|
7
|
-
/\b(?:npm\s+publish|gh\s+release\s+(?:create|upload)|gh\s+run\s+download)\b/iu,
|
|
11
|
+
/\b(?:(?:npm|pnpm|yarn)\s+publish|gh\s+release\s+(?:create|upload)|gh\s+run\s+download)\b/iu,
|
|
8
12
|
/actions\/download-artifact@/u,
|
|
9
13
|
/\.buildchain\/runtime\/|packages\/core\/(?:publication|release|dev-delivery)\//u,
|
|
10
14
|
/\b(?:fencingToken|candidateRoot|sourceProofRoot|usedNonces|generationRoot|compareAndSwap)\b/u,
|
|
@@ -12,7 +16,9 @@ const INTERNAL_WIRING = [
|
|
|
12
16
|
];
|
|
13
17
|
|
|
14
18
|
// The supplied file map must be the complete tracked consumer tree, including
|
|
15
|
-
// package scripts and
|
|
19
|
+
// package scripts and invoked sources. Product data is not consumer wiring.
|
|
20
|
+
// Unknown product code stays diagnostic. controlIssues is usable only with the
|
|
21
|
+
// runtime's read-only product jobs; it does not certify a resolved source graph.
|
|
16
22
|
export function inspectConsumerContract(
|
|
17
23
|
files,
|
|
18
24
|
{ channel = "v4", configPath = CONFIG_PATH } = {},
|
|
@@ -28,18 +34,354 @@ export function inspectConsumerContract(
|
|
|
28
34
|
for (const [file, expected] of Object.entries(workflows))
|
|
29
35
|
if (files[file] !== expected)
|
|
30
36
|
issues.push(`${file}: must match the generated thin caller`);
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
if (
|
|
38
|
+
Object.keys(files).some(
|
|
39
|
+
(file) =>
|
|
40
|
+
/^\.github\/workflows\/.*\.ya?ml$/iu.test(file) &&
|
|
41
|
+
!Object.hasOwn(workflows, file),
|
|
35
42
|
)
|
|
36
|
-
|
|
37
|
-
|
|
43
|
+
) {
|
|
44
|
+
try {
|
|
45
|
+
const inventory = checkWorkflowTaxonomy("", {
|
|
46
|
+
files,
|
|
47
|
+
integration: false,
|
|
48
|
+
documentation: false,
|
|
49
|
+
});
|
|
50
|
+
issues.push(
|
|
51
|
+
...inventory.errors.map(
|
|
52
|
+
(error) => `extra consumer workflow inventory: ${error}`,
|
|
53
|
+
),
|
|
54
|
+
);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
issues.push(`extra consumer workflow inventory: ${error.message}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
for (const file of [configPath, ...Object.keys(workflows)]) {
|
|
60
|
+
const source = files[file];
|
|
38
61
|
for (const pattern of INTERNAL_WIRING)
|
|
39
62
|
if (pattern.test(String(source)))
|
|
40
63
|
issues.push(
|
|
41
64
|
`${file}: consumer owns internal orchestration (${pattern.source})`,
|
|
42
65
|
);
|
|
43
66
|
}
|
|
44
|
-
|
|
67
|
+
const controlIssues = [...issues];
|
|
68
|
+
const commandClosures = (plan?.products || []).map((product) => ({
|
|
69
|
+
product: product.id,
|
|
70
|
+
...inspectConsumerCommandClosure({
|
|
71
|
+
files,
|
|
72
|
+
cwd: product.directory || ".",
|
|
73
|
+
commands: ["install", "build", "verify"].flatMap(
|
|
74
|
+
(stage) => product[stage] || [],
|
|
75
|
+
),
|
|
76
|
+
}),
|
|
77
|
+
}));
|
|
78
|
+
for (const closure of commandClosures)
|
|
79
|
+
for (const problem of closure.problems) {
|
|
80
|
+
const issue = `${problem.file || closure.product}: ${problem.code}: ${problem.message || "consumer owns internal orchestration"}`;
|
|
81
|
+
issues.push(issue);
|
|
82
|
+
if (problem.code === "internal-orchestration") controlIssues.push(issue);
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
ok: issues.length === 0,
|
|
86
|
+
issues,
|
|
87
|
+
controlIssues,
|
|
88
|
+
plan,
|
|
89
|
+
commandClosures,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// A bounded source graph, not an approval to execute tools. Every non-source
|
|
94
|
+
// executable remains a boundary until its actual implementation is resolved.
|
|
95
|
+
export function inspectConsumerCommandClosure({
|
|
96
|
+
files,
|
|
97
|
+
commands,
|
|
98
|
+
cwd = ".",
|
|
99
|
+
maxCommands = 256,
|
|
100
|
+
}) {
|
|
101
|
+
if (
|
|
102
|
+
!Array.isArray(commands) ||
|
|
103
|
+
!Number.isSafeInteger(maxCommands) ||
|
|
104
|
+
maxCommands < 1 ||
|
|
105
|
+
maxCommands > 10000
|
|
106
|
+
)
|
|
107
|
+
throw Error(
|
|
108
|
+
"Command traversal requires an array and a budget from 1 to 10000",
|
|
109
|
+
);
|
|
110
|
+
cwd = consumerPath(".", cwd);
|
|
111
|
+
const graph = { edges: [], sources: {}, problems: [], boundaries: [] };
|
|
112
|
+
const pending = commands.map((script) => ({
|
|
113
|
+
kind: "shell",
|
|
114
|
+
script,
|
|
115
|
+
cwd,
|
|
116
|
+
file: "<product>",
|
|
117
|
+
ancestry: [],
|
|
118
|
+
}));
|
|
119
|
+
const seen = new Set();
|
|
120
|
+
while (pending.length && seen.size < maxCommands) {
|
|
121
|
+
const request = pending.shift();
|
|
122
|
+
const key = JSON.stringify([
|
|
123
|
+
request.kind,
|
|
124
|
+
request.script,
|
|
125
|
+
request.executable,
|
|
126
|
+
request.argv,
|
|
127
|
+
request.cwd,
|
|
128
|
+
]);
|
|
129
|
+
if (request.ancestry.includes(key)) {
|
|
130
|
+
graph.problems.push({ file: request.file, code: "command-cycle" });
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (seen.has(key)) continue;
|
|
134
|
+
seen.add(key);
|
|
135
|
+
const enqueue = (entry) =>
|
|
136
|
+
pending.push({ ...entry, ancestry: [...request.ancestry, key] });
|
|
137
|
+
try {
|
|
138
|
+
expandConsumerCommand(files, request, graph, enqueue);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
graph.problems.push({
|
|
141
|
+
file: request.file,
|
|
142
|
+
code: "unresolved-command",
|
|
143
|
+
message: error.message,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (pending.length)
|
|
148
|
+
graph.problems.push({
|
|
149
|
+
code: "command-budget",
|
|
150
|
+
message: "Command graph exceeded its bounded expansion",
|
|
151
|
+
});
|
|
152
|
+
const sourceResolved = graph.problems.length === 0;
|
|
153
|
+
return {
|
|
154
|
+
...graph,
|
|
155
|
+
sourceResolved,
|
|
156
|
+
commandEdgesResolved: sourceResolved && graph.boundaries.length === 0,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function escapedShellCharacter(source, index, quote) {
|
|
161
|
+
if (index >= source.length) throw Error("Incomplete shell escape");
|
|
162
|
+
const char = source[index];
|
|
163
|
+
if (char === "\n") return "";
|
|
164
|
+
return quote === '"' && !/[\\$`"]/.test(char) ? `\\${char}` : char;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function commandWords(source) {
|
|
168
|
+
if (typeof source !== "string" || source.length > 1024 * 1024)
|
|
169
|
+
throw Error("Expected bounded shell source");
|
|
170
|
+
const commands = [];
|
|
171
|
+
let words = [],
|
|
172
|
+
word = "",
|
|
173
|
+
quote = "",
|
|
174
|
+
active = false;
|
|
175
|
+
const flushWord = () => {
|
|
176
|
+
if (active) words.push(word);
|
|
177
|
+
word = "";
|
|
178
|
+
active = false;
|
|
179
|
+
};
|
|
180
|
+
const flushCommand = () => {
|
|
181
|
+
flushWord();
|
|
182
|
+
if (words.length) commands.push(words);
|
|
183
|
+
words = [];
|
|
184
|
+
};
|
|
185
|
+
for (let index = 0; index < source.length; index++) {
|
|
186
|
+
const char = source[index];
|
|
187
|
+
if (char === "\\" && quote !== "'") {
|
|
188
|
+
const value = escapedShellCharacter(source, ++index, quote);
|
|
189
|
+
word += value;
|
|
190
|
+
active ||= value.length > 0;
|
|
191
|
+
} else if (quote) {
|
|
192
|
+
if (char === quote) quote = "";
|
|
193
|
+
else {
|
|
194
|
+
if (quote === '"' && /[$`]/u.test(char))
|
|
195
|
+
throw Error("Shell expansion requires resolved execution context");
|
|
196
|
+
word += char;
|
|
197
|
+
}
|
|
198
|
+
} else if (char === '"' || char === "'") {
|
|
199
|
+
quote = char;
|
|
200
|
+
active = true;
|
|
201
|
+
} else if (/[$`(){}<>*?\[\]]/u.test(char))
|
|
202
|
+
throw Error(
|
|
203
|
+
"Shell expansion or control syntax requires explicit resolution",
|
|
204
|
+
);
|
|
205
|
+
else if (char === "#" && !active) {
|
|
206
|
+
while (index < source.length && source[index] !== "\n") index++;
|
|
207
|
+
flushCommand();
|
|
208
|
+
} else if (char === ";" || char === "\n") flushCommand();
|
|
209
|
+
else if (char === "&" || char === "|") {
|
|
210
|
+
if (source[index + 1] !== char)
|
|
211
|
+
throw Error(
|
|
212
|
+
"Pipelines and background execution require explicit resolution",
|
|
213
|
+
);
|
|
214
|
+
flushCommand();
|
|
215
|
+
index++;
|
|
216
|
+
} else if (/\s/u.test(char)) flushWord();
|
|
217
|
+
else {
|
|
218
|
+
word += char;
|
|
219
|
+
active = true;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (quote) throw Error("Unclosed shell quote");
|
|
223
|
+
flushCommand();
|
|
224
|
+
return commands;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function consumerPath(cwd, file) {
|
|
228
|
+
if (
|
|
229
|
+
typeof file !== "string" ||
|
|
230
|
+
file.includes("\\") ||
|
|
231
|
+
path.posix.isAbsolute(file)
|
|
232
|
+
)
|
|
233
|
+
throw Error(
|
|
234
|
+
"Executable paths must resolve inside the supplied consumer tree",
|
|
235
|
+
);
|
|
236
|
+
const resolved = path.posix.normalize(path.posix.join(cwd, file));
|
|
237
|
+
if (
|
|
238
|
+
path.posix.isAbsolute(resolved) ||
|
|
239
|
+
resolved === ".." ||
|
|
240
|
+
resolved.startsWith("../")
|
|
241
|
+
)
|
|
242
|
+
throw Error("Executable path escapes consumer tree");
|
|
243
|
+
return resolved;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function sourceBytes(files, file, graph) {
|
|
247
|
+
if (!Object.hasOwn(files, file) || typeof files[file] !== "string")
|
|
248
|
+
throw Error(`Executable source is absent: ${file}`);
|
|
249
|
+
graph.sources[file] = createHash("sha256").update(files[file]).digest("hex");
|
|
250
|
+
return files[file];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function expandPackageCommand(files, request, graph, enqueue) {
|
|
254
|
+
const file = consumerPath(request.cwd, "package.json");
|
|
255
|
+
const manifest = JSON.parse(sourceBytes(files, file, graph));
|
|
256
|
+
const [operation, name, ...args] = request.argv;
|
|
257
|
+
let keys = [`pre${name}`, name, `post${name}`];
|
|
258
|
+
if (operation !== "run") {
|
|
259
|
+
graph.boundaries.push({ ...request, code: "package-manager-operation" });
|
|
260
|
+
if (
|
|
261
|
+
request.executable !== "npm" ||
|
|
262
|
+
request.argv.length !== 1 ||
|
|
263
|
+
!["install", "ci"].includes(operation)
|
|
264
|
+
)
|
|
265
|
+
return;
|
|
266
|
+
keys =
|
|
267
|
+
"preinstall install postinstall prepublish preprepare prepare postprepare dependencies".split(
|
|
268
|
+
" ",
|
|
269
|
+
);
|
|
270
|
+
} else {
|
|
271
|
+
if (!name || args.length)
|
|
272
|
+
throw Error("Package script arguments require explicit resolution");
|
|
273
|
+
if (typeof manifest.scripts?.[name] !== "string")
|
|
274
|
+
throw Error(`Package script is absent: ${name}`);
|
|
275
|
+
}
|
|
276
|
+
// Include lifecycle hooks conservatively even for a manager that disables them.
|
|
277
|
+
for (const key of keys) {
|
|
278
|
+
if (!Object.hasOwn(manifest.scripts || {}, key)) continue;
|
|
279
|
+
const script = manifest.scripts[key];
|
|
280
|
+
if (typeof script !== "string")
|
|
281
|
+
throw Error(`Package script must be text: ${key}`);
|
|
282
|
+
enqueue({ kind: "shell", script, cwd: request.cwd, file });
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function expandJavaScriptCommand(files, request, graph, enqueue) {
|
|
287
|
+
if (request.argv.length !== 1 || request.argv[0].startsWith("-"))
|
|
288
|
+
throw Error(
|
|
289
|
+
"Node options, test runners and script arguments require explicit resolution",
|
|
290
|
+
);
|
|
291
|
+
const file = consumerPath(request.cwd, request.argv[0]);
|
|
292
|
+
sourceBytes(files, file, graph);
|
|
293
|
+
const report = inspectJavaScriptModuleClosure({
|
|
294
|
+
files,
|
|
295
|
+
entry: file,
|
|
296
|
+
cwd: request.cwd,
|
|
297
|
+
});
|
|
298
|
+
Object.assign(graph.sources, report.sources);
|
|
299
|
+
graph.problems.push(...report.problems);
|
|
300
|
+
for (const edge of report.edges) {
|
|
301
|
+
if (["process", "shell"].includes(edge.kind)) enqueue(edge);
|
|
302
|
+
else {
|
|
303
|
+
graph.edges.push(edge);
|
|
304
|
+
if (edge.kind === "network") graph.boundaries.push(edge);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function expandConsumerCommand(files, request, graph, enqueue) {
|
|
310
|
+
if (request.kind === "shell") {
|
|
311
|
+
const cwd = request.cwd;
|
|
312
|
+
for (const [executable, ...argv] of commandWords(request.script)) {
|
|
313
|
+
if (executable === "cd")
|
|
314
|
+
throw Error(
|
|
315
|
+
"Shell directory changes require conditional execution resolution",
|
|
316
|
+
);
|
|
317
|
+
enqueue({ kind: "process", executable, argv, cwd, file: request.file });
|
|
318
|
+
}
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
const { executable, argv, cwd, file } = request;
|
|
322
|
+
graph.edges.push({ kind: "process", executable, argv, cwd, file });
|
|
323
|
+
// Tool operands are data; inspect command positions and protocol switches.
|
|
324
|
+
const invocation = [
|
|
325
|
+
executable,
|
|
326
|
+
...argv.filter(
|
|
327
|
+
(arg, index) =>
|
|
328
|
+
arg.startsWith("--") ||
|
|
329
|
+
(executable === "node" && index === 0) ||
|
|
330
|
+
(/^(?:npm|pnpm|yarn|gh|buildchain)$/u.test(executable) && index < 3),
|
|
331
|
+
),
|
|
332
|
+
].join(" ");
|
|
333
|
+
if (INTERNAL_WIRING.some((pattern) => pattern.test(invocation))) {
|
|
334
|
+
graph.problems.push({
|
|
335
|
+
file,
|
|
336
|
+
code: "internal-orchestration",
|
|
337
|
+
executable,
|
|
338
|
+
argv,
|
|
339
|
+
});
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
if (
|
|
343
|
+
executable === "corepack" &&
|
|
344
|
+
/^(?:npm|pnpm|yarn)(?:@[\d.]+)?$/u.test(argv[0] || "")
|
|
345
|
+
) {
|
|
346
|
+
enqueue({
|
|
347
|
+
...request,
|
|
348
|
+
executable: argv[0].split("@")[0],
|
|
349
|
+
argv: argv.slice(1),
|
|
350
|
+
});
|
|
351
|
+
} else if (["npm", "pnpm", "yarn"].includes(executable))
|
|
352
|
+
expandPackageCommand(files, request, graph, enqueue);
|
|
353
|
+
else if (executable === "node")
|
|
354
|
+
expandJavaScriptCommand(files, request, graph, enqueue);
|
|
355
|
+
else if (["sh", "bash"].includes(executable)) {
|
|
356
|
+
if (argv.length === 2 && ["-c", "-lc"].includes(argv[0]))
|
|
357
|
+
enqueue({ kind: "shell", script: argv[1], cwd, file });
|
|
358
|
+
else if (argv.length === 1 && !argv[0].startsWith("-")) {
|
|
359
|
+
const target = consumerPath(cwd, argv[0]);
|
|
360
|
+
enqueue({
|
|
361
|
+
kind: "shell",
|
|
362
|
+
script: sourceBytes(files, target, graph),
|
|
363
|
+
cwd,
|
|
364
|
+
file: target,
|
|
365
|
+
});
|
|
366
|
+
} else
|
|
367
|
+
throw Error(
|
|
368
|
+
"Shell options and script arguments require explicit resolution",
|
|
369
|
+
);
|
|
370
|
+
} else if (executable.startsWith("./") || executable.startsWith("../")) {
|
|
371
|
+
const target = consumerPath(cwd, executable);
|
|
372
|
+
const source = sourceBytes(files, target, graph);
|
|
373
|
+
if (/^#!\/usr\/bin\/env node(?:\r?\n|$)/u.test(source))
|
|
374
|
+
enqueue({ ...request, executable: "node", argv: [executable, ...argv] });
|
|
375
|
+
else if (/^#!\/(?:usr\/bin\/env |bin\/)(?:ba)?sh(?:\r?\n|$)/u.test(source))
|
|
376
|
+
enqueue({ ...request, executable: "bash", argv: [executable, ...argv] });
|
|
377
|
+
else throw Error(`Executable interpreter is unresolved: ${target}`);
|
|
378
|
+
} else
|
|
379
|
+
graph.boundaries.push({
|
|
380
|
+
kind: "process",
|
|
381
|
+
executable,
|
|
382
|
+
argv,
|
|
383
|
+
cwd,
|
|
384
|
+
file,
|
|
385
|
+
code: "external-executable",
|
|
386
|
+
});
|
|
45
387
|
}
|