@kungfu-tech/buildchain 3.0.4-alpha.4 → 3.0.4-alpha.5

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.
@@ -0,0 +1,174 @@
1
+ import { createNodeApiReference } from "./public-reference.mjs";
2
+
3
+ export function createSiteNodeApiRegistry({
4
+ root,
5
+ packageJson,
6
+ nodeApiMeta,
7
+ sha256File,
8
+ publicSurfaceLifecycle,
9
+ }) {
10
+ const reference = createNodeApiReference({ root, packageJson });
11
+ const referenceByExport = new Map(
12
+ reference.map((entry) => [entry.export, entry]),
13
+ );
14
+ const symbolsById = new Map();
15
+ for (const surface of reference) {
16
+ const meta = nodeApiMeta(surface.export);
17
+ for (const symbol of surface.symbols) {
18
+ const id = `${symbol.source.path}#${symbol.name}`;
19
+ if (!symbolsById.has(id)) {
20
+ symbolsById.set(id, {
21
+ id,
22
+ ...symbol,
23
+ maturity: meta.maturity,
24
+ audience: meta.audience,
25
+ });
26
+ }
27
+ }
28
+ }
29
+ return {
30
+ schemaVersion: 1,
31
+ contract: "kungfu-buildchain-node-api-registry",
32
+ package: packageJson.name,
33
+ moduleSystem: packageJson.type || "module",
34
+ symbols: [...symbolsById.values()].sort((left, right) =>
35
+ left.id.localeCompare(right.id),
36
+ ),
37
+ exports: Object.entries(packageJson.exports || {})
38
+ .filter(
39
+ ([specifier]) =>
40
+ !specifier.startsWith("./site/") && specifier !== "./package.json",
41
+ )
42
+ .map(([specifier, target]) => {
43
+ const meta = nodeApiMeta(specifier);
44
+ const surface = referenceByExport.get(specifier);
45
+ return {
46
+ specifier:
47
+ specifier === "."
48
+ ? packageJson.name
49
+ : `${packageJson.name}/${specifier.replace(/^\.\//, "")}`,
50
+ export: specifier,
51
+ target,
52
+ digest:
53
+ typeof target === "string"
54
+ ? sha256File(target.replace(/^\.\//, ""))
55
+ : "",
56
+ summary: meta.summary,
57
+ capabilityGroup: meta.capabilityGroup,
58
+ audience: meta.audience,
59
+ symbolCount: surface?.symbols.length || 0,
60
+ symbolIds: (surface?.symbols || []).map(
61
+ (symbol) => `${symbol.source.path}#${symbol.name}`,
62
+ ),
63
+ ...publicSurfaceLifecycle({
64
+ owner: "buildchain-core",
65
+ maturity: meta.maturity,
66
+ nonDuplicationRationale:
67
+ "Existing package subpath retained as the canonical API boundary for this capability.",
68
+ }),
69
+ };
70
+ }),
71
+ docs: [
72
+ ["cli-and-node-package", "docs/cli.md"],
73
+ ["node-api-reference", "docs/node-api-reference.md"],
74
+ ["build-facts", "docs/build-facts.md"],
75
+ ["kfd-support", "docs/kfd-support.md"],
76
+ ["readme-badges", "docs/readme-badges.md"],
77
+ ["homebrew", "docs/homebrew.md"],
78
+ ["site-bundle-contract", "docs/site-bundle-contract.md"],
79
+ ].map(([id, docPath]) => ({
80
+ id,
81
+ path: docPath,
82
+ digest: sha256File(docPath),
83
+ })),
84
+ guidance:
85
+ "These are the public Node import surfaces and symbols shipped by the npm package. Prefer them over internal file paths.",
86
+ };
87
+ }
88
+
89
+ function assertCliReferenceRegistry(cliRegistry) {
90
+ for (const command of cliRegistry.commands || []) {
91
+ for (const field of [
92
+ "paths",
93
+ "syntaxes",
94
+ "options",
95
+ "aliases",
96
+ "helpCommands",
97
+ ]) {
98
+ if (!Array.isArray(command[field])) {
99
+ throw new Error(
100
+ `cli-registry.json command missing generated ${field}: ${command.id || command.usage}`,
101
+ );
102
+ }
103
+ }
104
+ if (
105
+ command.paths.length === 0 ||
106
+ command.syntaxes.length === 0 ||
107
+ command.helpCommands.length === 0
108
+ ) {
109
+ throw new Error(
110
+ `cli-registry.json command has an empty generated reference: ${command.id || command.usage}`,
111
+ );
112
+ }
113
+ }
114
+ }
115
+
116
+ function assertNodeSymbol(symbol) {
117
+ const arraysComplete =
118
+ Array.isArray(symbol.parameters) &&
119
+ Array.isArray(symbol.errors) &&
120
+ symbol.errors.length > 0 &&
121
+ Array.isArray(symbol.sideEffects) &&
122
+ symbol.sideEffects.length > 0 &&
123
+ Array.isArray(symbol.audience);
124
+ const sourceComplete =
125
+ symbol.source?.path && Number.isInteger(symbol.source?.line);
126
+ if (
127
+ !symbol.id ||
128
+ !symbol.name ||
129
+ !symbol.kind ||
130
+ !symbol.signature ||
131
+ !symbol.returns ||
132
+ !symbol.maturity ||
133
+ !symbol.example ||
134
+ !arraysComplete ||
135
+ !sourceComplete
136
+ ) {
137
+ throw new Error(
138
+ `node-api-registry.json symbol is incomplete: ${symbol.id || "<unknown>"}`,
139
+ );
140
+ }
141
+ }
142
+
143
+ function assertNodeReferenceRegistry(nodeApiRegistry) {
144
+ const symbolsById = new Map(
145
+ (nodeApiRegistry.symbols || []).map((symbol) => [symbol.id, symbol]),
146
+ );
147
+ for (const symbol of symbolsById.values()) assertNodeSymbol(symbol);
148
+ for (const surface of nodeApiRegistry.exports || []) {
149
+ if (
150
+ !Number.isInteger(surface.symbolCount) ||
151
+ surface.symbolCount !== surface.symbolIds?.length ||
152
+ surface.symbolCount === 0
153
+ ) {
154
+ throw new Error(
155
+ `node-api-registry.json export has incomplete symbol closure: ${surface.export || surface.specifier}`,
156
+ );
157
+ }
158
+ for (const id of surface.symbolIds) {
159
+ if (!symbolsById.has(id)) {
160
+ throw new Error(
161
+ `node-api-registry.json export references an unknown symbol: ${surface.specifier}#${id}`,
162
+ );
163
+ }
164
+ }
165
+ }
166
+ }
167
+
168
+ export function assertPublicReferenceRegistry({
169
+ cliRegistry,
170
+ nodeApiRegistry,
171
+ }) {
172
+ assertCliReferenceRegistry(cliRegistry);
173
+ assertNodeReferenceRegistry(nodeApiRegistry);
174
+ }
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
9
+
10
+ function run(command, args, { cwd, json = false } = {}) {
11
+ const result = spawnSync(command, args, {
12
+ cwd,
13
+ encoding: "utf8",
14
+ env: {
15
+ ...process.env,
16
+ npm_config_audit: "false",
17
+ npm_config_fund: "false",
18
+ },
19
+ timeout: 10 * 60 * 1000,
20
+ });
21
+ if (result.error || result.status !== 0) {
22
+ throw new Error(
23
+ `${command} ${args.join(" ")} failed (${result.status}): ${result.stderr || result.stdout || result.error?.message}`,
24
+ );
25
+ }
26
+ return json ? JSON.parse(result.stdout) : result.stdout;
27
+ }
28
+
29
+ function assert(condition, message) {
30
+ if (!condition) throw new Error(message);
31
+ }
32
+
33
+ export function verifyGoldenPath() {
34
+ const temporary = fs.mkdtempSync(
35
+ path.join(os.tmpdir(), "buildchain-golden-path-"),
36
+ );
37
+ const packDir = path.join(temporary, "pack");
38
+ const consumer = path.join(temporary, "consumer");
39
+ fs.mkdirSync(packDir);
40
+ fs.mkdirSync(consumer);
41
+ try {
42
+ const packed = run(
43
+ "npm",
44
+ ["pack", "--json", "--pack-destination", packDir],
45
+ { cwd: root, json: true },
46
+ );
47
+ const tarball = path.join(packDir, packed[0].filename);
48
+ fs.writeFileSync(
49
+ path.join(consumer, "package.json"),
50
+ `${JSON.stringify(
51
+ {
52
+ name: "buildchain-golden-path-consumer",
53
+ version: "0.1.0",
54
+ private: true,
55
+ scripts: { build: 'node -e ""', check: 'node -e ""' },
56
+ },
57
+ null,
58
+ 2,
59
+ )}\n`,
60
+ );
61
+ run("npm", ["install", "--ignore-scripts", "--package-lock", tarball], {
62
+ cwd: consumer,
63
+ });
64
+ const buildchain = path.join(
65
+ consumer,
66
+ "node_modules",
67
+ ".bin",
68
+ process.platform === "win32" ? "buildchain.cmd" : "buildchain",
69
+ );
70
+ const version = run(buildchain, ["--version"], { cwd: consumer }).trim();
71
+ const initialized = run(
72
+ buildchain,
73
+ ["init", "--type", "package", "--package-manager", "npm"],
74
+ { cwd: consumer, json: true },
75
+ );
76
+ const validation = run(
77
+ buildchain,
78
+ [
79
+ "validate",
80
+ "--require-version-state",
81
+ "--require-lifecycle-stages",
82
+ "install,build,verify",
83
+ ],
84
+ { cwd: consumer, json: true },
85
+ );
86
+ const workflow = fs.readFileSync(
87
+ path.join(consumer, ".github", "workflows", "build.yml"),
88
+ "utf8",
89
+ );
90
+ const releaseDryRun = run(
91
+ buildchain,
92
+ ["release", "--dry-run", "--target-ref", "alpha/v3/v3.0", "--json"],
93
+ { cwd: consumer, json: true },
94
+ );
95
+ const passportPath = path.join(
96
+ consumer,
97
+ ".buildchain",
98
+ "golden-path",
99
+ "buildchain.release.json",
100
+ );
101
+ fs.mkdirSync(path.dirname(passportPath), { recursive: true });
102
+ run(
103
+ process.execPath,
104
+ [
105
+ "--input-type=module",
106
+ "--eval",
107
+ `import fs from "node:fs"; import { createReleasePassport } from "@kungfu-tech/buildchain"; const value=createReleasePassport({repository:"example/consumer",tag:"v0.1.0-alpha.0",sourceSha:"a".repeat(40),assets:[{name:"consumer.tgz",sha256:"b".repeat(64)}]}); fs.writeFileSync(${JSON.stringify(passportPath)}, JSON.stringify(value,null,2)+"\\n");`,
108
+ ],
109
+ { cwd: consumer },
110
+ );
111
+ const inspection = run(
112
+ buildchain,
113
+ ["inspect", "release", "--passport", passportPath, "--json"],
114
+ { cwd: consumer, json: true },
115
+ );
116
+
117
+ assert(
118
+ initialized.type === "package",
119
+ "Golden Path init did not retain the package project type",
120
+ );
121
+ assert(
122
+ validation.config?.path === ".buildchain/buildchain.toml",
123
+ "Golden Path validation did not read the generated config",
124
+ );
125
+ assert(
126
+ ["install", "build", "verify"].every((name) =>
127
+ (validation.lifecycleStages || []).some((stage) => stage.name === name),
128
+ ),
129
+ "Golden Path validation did not retain the required lifecycle stages",
130
+ );
131
+ assert(
132
+ /uses:\s+kungfu-systems\/buildchain\/.github\/workflows\/.build.yml@v3/.test(
133
+ workflow,
134
+ ),
135
+ "Golden Path workflow is not a thin v3 reusable-workflow caller",
136
+ );
137
+ assert(
138
+ /buildchain-ref:/.test(workflow),
139
+ "Golden Path workflow lacks the bounded runtime override input",
140
+ );
141
+ assert(
142
+ releaseDryRun && typeof releaseDryRun === "object",
143
+ "Golden Path release dry-run did not return JSON",
144
+ );
145
+ assert(
146
+ inspection && typeof inspection === "object",
147
+ "Golden Path Release Passport inspection did not return JSON",
148
+ );
149
+ return {
150
+ contract: "kungfu-buildchain-golden-path-verification/v1",
151
+ ok: true,
152
+ version,
153
+ projectType: initialized.type,
154
+ validationPath: validation.config?.path,
155
+ reusableWorkflow: ".github/workflows/build.yml",
156
+ releaseDryRun: true,
157
+ releasePassportInspection: true,
158
+ };
159
+ } finally {
160
+ fs.rmSync(temporary, { recursive: true, force: true });
161
+ }
162
+ }
163
+
164
+ try {
165
+ process.stdout.write(`${JSON.stringify(verifyGoldenPath(), null, 2)}\n`);
166
+ } catch (error) {
167
+ console.error(`buildchain Golden Path: ${error.message}`);
168
+ process.exitCode = 1;
169
+ }