@kungfu-tech/buildchain 2.11.2-alpha.1 → 2.11.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.
@@ -0,0 +1,216 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { loadBuildchainConfig } from "./buildchain-config.js";
5
+
6
+ export const PUBLICATION_NPM_PACKAGE_CONTRACT = "kungfu-buildchain-publication-npm-package";
7
+
8
+ function toPosix(value) {
9
+ return String(value || "").split(path.sep).join("/");
10
+ }
11
+
12
+ function sha256File(filePath) {
13
+ return crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
14
+ }
15
+
16
+ function readJson(filePath) {
17
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
18
+ }
19
+
20
+ function copyFilePreservingPath({ cwd, outputDir, relPath, copied }) {
21
+ const source = path.resolve(cwd, relPath);
22
+ if (!fs.existsSync(source) || !fs.statSync(source).isFile()) {
23
+ return undefined;
24
+ }
25
+ const normalized = toPosix(relPath);
26
+ if (copied.has(normalized)) {
27
+ return copied.get(normalized);
28
+ }
29
+ const target = path.join(outputDir, normalized);
30
+ fs.mkdirSync(path.dirname(target), { recursive: true });
31
+ fs.copyFileSync(source, target);
32
+ const fact = {
33
+ path: normalized,
34
+ bytes: fs.statSync(target).size,
35
+ sha256: sha256File(target),
36
+ };
37
+ copied.set(normalized, fact);
38
+ return fact;
39
+ }
40
+
41
+ function publicationConfig(cwd) {
42
+ const loaded = loadBuildchainConfig(cwd);
43
+ if (loaded.config.project?.type !== "publication-artifact") {
44
+ throw new Error('publication npm package requires project.type = "publication-artifact"');
45
+ }
46
+ if (!loaded.config.publication) {
47
+ throw new Error("publication npm package requires [publication]");
48
+ }
49
+ const publish = loaded.config.publish || {};
50
+ if (publish.kind && publish.kind !== "npm-paper-package") {
51
+ throw new Error('publication npm package requires publish.kind = "npm-paper-package"');
52
+ }
53
+ return { loaded, publication: loaded.config.publication, publish };
54
+ }
55
+
56
+ export function collectPublicationPackageFacts({
57
+ cwd = process.cwd(),
58
+ packageName = "",
59
+ outputDir = ".buildchain/publication/npm-package",
60
+ } = {}) {
61
+ const resolvedCwd = path.resolve(cwd);
62
+ const { loaded, publication, publish } = publicationConfig(resolvedCwd);
63
+ const name = packageName || publish.package || publish.mainPackage || "";
64
+ if (!name) {
65
+ throw new Error("publication npm package requires publish.package or --package-name");
66
+ }
67
+ const version = publication.version || "";
68
+ if (!version) {
69
+ throw new Error("publication npm package requires publication.version");
70
+ }
71
+ return {
72
+ schemaVersion: 1,
73
+ contract: PUBLICATION_NPM_PACKAGE_CONTRACT,
74
+ package: {
75
+ name,
76
+ version,
77
+ distTag: publish.distTag || (version.includes("-") ? "alpha" : "latest"),
78
+ auth: publish.auth || "trusted-publishing",
79
+ },
80
+ project: {
81
+ name: loaded.config.project?.name || path.basename(resolvedCwd),
82
+ type: loaded.config.project?.type || "",
83
+ },
84
+ publication: {
85
+ kind: publication.kind,
86
+ title: publication.title,
87
+ version,
88
+ primaryArtifact: publication.primaryArtifact,
89
+ manifestPath: publication.manifestPath,
90
+ passportPath: ".buildchain/publication/publication-artifact-passport.json",
91
+ registryPath: publication.archive?.registryPath || "",
92
+ sourceBundlePath: publication.sourceBundlePath,
93
+ artifactPaths: [publication.primaryArtifact, ...publication.artifactPaths].filter(Boolean),
94
+ metadataPaths: publication.metadataPaths,
95
+ siteConsumers: publication.siteConsumers,
96
+ },
97
+ outputDir: toPosix(outputDir),
98
+ };
99
+ }
100
+
101
+ export function preparePublicationNpmPackage({
102
+ cwd = process.cwd(),
103
+ outputDir = ".buildchain/publication/npm-package",
104
+ packageName = "",
105
+ } = {}) {
106
+ const resolvedCwd = path.resolve(cwd);
107
+ const facts = collectPublicationPackageFacts({ cwd: resolvedCwd, outputDir, packageName });
108
+ const resolvedOutputDir = path.resolve(resolvedCwd, outputDir);
109
+ fs.rmSync(resolvedOutputDir, { recursive: true, force: true });
110
+ fs.mkdirSync(resolvedOutputDir, { recursive: true });
111
+
112
+ const copied = new Map();
113
+ const declaredPaths = [
114
+ facts.publication.manifestPath,
115
+ facts.publication.passportPath,
116
+ facts.publication.registryPath,
117
+ facts.publication.sourceBundlePath,
118
+ ...facts.publication.artifactPaths,
119
+ ...facts.publication.metadataPaths,
120
+ ].filter(Boolean);
121
+ const files = declaredPaths
122
+ .map((relPath) => copyFilePreservingPath({
123
+ cwd: resolvedCwd,
124
+ outputDir: resolvedOutputDir,
125
+ relPath,
126
+ copied,
127
+ }))
128
+ .filter(Boolean);
129
+
130
+ const manifestFile = path.resolve(resolvedCwd, facts.publication.manifestPath);
131
+ const passportFile = path.resolve(resolvedCwd, facts.publication.passportPath);
132
+ if (!fs.existsSync(manifestFile)) {
133
+ throw new Error(`publication manifest is missing: ${facts.publication.manifestPath}`);
134
+ }
135
+ if (!fs.existsSync(passportFile)) {
136
+ throw new Error(`publication passport is missing: ${facts.publication.passportPath}`);
137
+ }
138
+ if (!files.some((file) => file.path === facts.publication.primaryArtifact)) {
139
+ throw new Error(`publication primary artifact is missing from npm package: ${facts.publication.primaryArtifact}`);
140
+ }
141
+
142
+ const sourcePackage = fs.existsSync(path.join(resolvedCwd, "package.json"))
143
+ ? readJson(path.join(resolvedCwd, "package.json"))
144
+ : {};
145
+ const packageJson = {
146
+ name: facts.package.name,
147
+ version: facts.package.version,
148
+ private: false,
149
+ description: `${facts.publication.title} publication artifact package.`,
150
+ license: sourcePackage.license || "UNLICENSED",
151
+ type: "module",
152
+ files: [
153
+ ".buildchain/publication/",
154
+ "buildchain-publication-package.json",
155
+ facts.publication.primaryArtifact,
156
+ ...facts.publication.artifactPaths,
157
+ ...facts.publication.metadataPaths,
158
+ ].filter(Boolean),
159
+ exports: {
160
+ "./package.json": "./package.json",
161
+ "./publication-artifact.json": `./${facts.publication.manifestPath}`,
162
+ "./publication-artifact-passport.json": `./${facts.publication.passportPath}`,
163
+ ...(facts.publication.registryPath
164
+ ? { "./publication-registry.json": `./${facts.publication.registryPath}` }
165
+ : {}),
166
+ },
167
+ publishConfig: {
168
+ access: "public",
169
+ registry: "https://registry.npmjs.org/",
170
+ },
171
+ buildchain: {
172
+ contract: PUBLICATION_NPM_PACKAGE_CONTRACT,
173
+ publicationManifest: facts.publication.manifestPath,
174
+ publicationPassport: facts.publication.passportPath,
175
+ publicationRegistry: facts.publication.registryPath || undefined,
176
+ primaryArtifact: facts.publication.primaryArtifact,
177
+ sourceBundle: facts.publication.sourceBundlePath,
178
+ siteConsumers: facts.publication.siteConsumers,
179
+ },
180
+ };
181
+ fs.writeFileSync(path.join(resolvedOutputDir, "package.json"), `${JSON.stringify(packageJson, null, 2)}\n`);
182
+ const readme = [
183
+ `# ${facts.publication.title}`,
184
+ "",
185
+ "This npm package was synthesized by Buildchain from a publication-artifact repository.",
186
+ "",
187
+ `- Publication manifest: \`${facts.publication.manifestPath}\``,
188
+ `- Publication passport: \`${facts.publication.passportPath}\``,
189
+ `- Primary artifact: \`${facts.publication.primaryArtifact}\``,
190
+ "",
191
+ ].join("\n");
192
+ fs.writeFileSync(path.join(resolvedOutputDir, "README.md"), readme);
193
+
194
+ const packageManifest = {
195
+ ...facts,
196
+ outputDir: toPosix(path.relative(resolvedCwd, resolvedOutputDir)),
197
+ files: [
198
+ {
199
+ path: "package.json",
200
+ bytes: fs.statSync(path.join(resolvedOutputDir, "package.json")).size,
201
+ sha256: sha256File(path.join(resolvedOutputDir, "package.json")),
202
+ },
203
+ {
204
+ path: "README.md",
205
+ bytes: fs.statSync(path.join(resolvedOutputDir, "README.md")).size,
206
+ sha256: sha256File(path.join(resolvedOutputDir, "README.md")),
207
+ },
208
+ ...files,
209
+ ].sort((left, right) => left.path.localeCompare(right.path)),
210
+ };
211
+ fs.writeFileSync(
212
+ path.join(resolvedOutputDir, "buildchain-publication-package.json"),
213
+ `${JSON.stringify(packageManifest, null, 2)}\n`,
214
+ );
215
+ return packageManifest;
216
+ }
@@ -20,6 +20,7 @@ const requiredPaths = [
20
20
  "bin/buildchain.mjs",
21
21
  "packages/core/homebrew.js",
22
22
  "packages/core/build-facts.js",
23
+ "packages/core/publication-package.js",
23
24
  "packages/core/release-line-bootstrap.js",
24
25
  "packages/core/public-surface-audit.js",
25
26
  "docs/MAP.md",
@@ -46,6 +47,7 @@ const requiredPaths = [
46
47
  "scripts/artifact-relay-s3.mjs",
47
48
  "scripts/npm-publish-dry-run.mjs",
48
49
  "scripts/npm-publish-transaction.mjs",
50
+ "scripts/publication-package.mjs",
49
51
  "scripts/release-candidate-resolver.mjs",
50
52
  "scripts/buildchain-patrol.mjs",
51
53
  "scripts/workflow-friction-report.mjs",
@@ -71,6 +73,7 @@ const requiredPaths = [
71
73
  ".github/workflows/release-candidate-promote.yml",
72
74
  ".github/workflows/release-propagation.yml",
73
75
  ".github/workflows/npm-publish.yml",
76
+ ".github/workflows/paper-release.yml",
74
77
  ".github/workflows/binary-distribution.yml",
75
78
  ".github/workflows/verify.yml",
76
79
  ".github/workflows/.build.yml",
@@ -431,6 +431,7 @@ function cliCommandMeta(id) {
431
431
  "publish-source": { group: "release-passport-trust", purpose: "Create, inspect, or verify publish-gate source-lock refs." },
432
432
  "publication-artifact": { group: "reusable-build", purpose: "Generate publication artifact manifests, passports, and source bundles for paper/report repositories." },
433
433
  "publication-artifact-manifest": { group: "reusable-build", purpose: "Write a site-consumable publication artifact manifest, publication passport, and source bundle." },
434
+ "publication-artifact-npm-package": { group: "reusable-build", purpose: "Synthesize the declared npm paper package from a publication artifact manifest, passport, registry, source bundle, and primary artifact." },
434
435
  "release-dry-run": { group: "governance-versioning", purpose: "Explain what a channel merge would publish before the PR is merged." },
435
436
  "release-line-open": { group: "governance-versioning", purpose: "Plan or write the initial version-state commit for a new minor release line." },
436
437
  "release-propagation": { group: "site-and-propagation", purpose: "Plan channel-preserving downstream release PRs and write exact upstream release locks." },
@@ -466,6 +467,7 @@ function nodeApiMeta(exportName) {
466
467
  "./diagnostics": { group: "observability-diagnostics", summary: "Native diagnostics collection, summarization, cache, compiler, and process-sampler APIs." },
467
468
  "./logging": { group: "observability-diagnostics", summary: "Buildchain JSONL logging, span, summary, and verification APIs." },
468
469
  "./publication-artifact": { group: "reusable-build", summary: "Publication artifact manifest, source bundle, and publication passport APIs." },
470
+ "./publication-package": { group: "reusable-build", summary: "Publication npm package synthesis APIs for Buildchain-managed paper release presets." },
469
471
  "./artifact-passport": { group: "release-passport-trust", summary: "Artifact passport digest and evidence helper APIs." },
470
472
  "./release-passport": { group: "release-passport-trust", summary: "Release passport collection, verification, explanation, and evidence APIs." },
471
473
  "./release-candidate": { group: "reusable-build", summary: "PR-stage release-candidate artifact, passport, and promote-only resolver APIs." },
@@ -537,7 +539,7 @@ function buildCapabilityRegistry({ docs, pages, cliRegistry, manualRegistry, nod
537
539
 
538
540
  function workflowCapabilityGroup(entry) {
539
541
  if (["web-surface", "release-propagation"].includes(entry.id)) return capabilityGroup("site-and-propagation");
540
- if (["build", "release-candidate-promote", "publication-artifact"].includes(entry.id)) return capabilityGroup("reusable-build");
542
+ if (["build", "release-candidate-promote", "publication-artifact", "paper-release"].includes(entry.id)) return capabilityGroup("reusable-build");
541
543
  if (["buildchain-ref-promotion", "release-line-bootstrap"].includes(entry.id)) return capabilityGroup("release-passport-trust");
542
544
  if (entry.id.includes("patrol") || entry.id.includes("dev-pr-auto-merge")) return capabilityGroup("governance-versioning");
543
545
  if (entry.status === "repository-internal" || entry.status === "compatibility-fixture") return capabilityGroup("api-cli-reference");
@@ -815,6 +817,7 @@ function buildSiteBundle() {
815
817
  ["buildchain-ref-promotion", "release-governance"],
816
818
  ["release-line-bootstrap", "release-governance"],
817
819
  ["release-candidate-promote", "release-governance"],
820
+ ["paper-release", "reusable-build"],
818
821
  ["release-propagation", "release-propagation"],
819
822
  ["dev-pr-auto-merge", "dev-governance"],
820
823
  ["binary-distribution", "release-passport"],
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ import { pathToFileURL } from "node:url";
3
+ import { preparePublicationNpmPackage } from "../packages/core/publication-package.js";
4
+
5
+ function readFlag(args, name, fallback = "") {
6
+ const index = args.indexOf(`--${name}`);
7
+ return index === -1 ? fallback : args[index + 1] || "";
8
+ }
9
+
10
+ function readBooleanFlag(args, name) {
11
+ return args.includes(`--${name}`);
12
+ }
13
+
14
+ export function runPublicationPackageCli(args = process.argv.slice(2)) {
15
+ const result = preparePublicationNpmPackage({
16
+ cwd: readFlag(args, "cwd", process.cwd()),
17
+ outputDir: readFlag(args, "output-dir", ".buildchain/publication/npm-package"),
18
+ packageName: readFlag(args, "package-name", ""),
19
+ });
20
+ if (readBooleanFlag(args, "json")) {
21
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
22
+ } else {
23
+ process.stdout.write(`publication-package-dir=${result.outputDir}\n`);
24
+ process.stdout.write(`publication-package-name=${result.package.name}\n`);
25
+ process.stdout.write(`publication-package-version=${result.package.version}\n`);
26
+ process.stdout.write(`publication-package-dist-tag=${result.package.distTag}\n`);
27
+ }
28
+ return result;
29
+ }
30
+
31
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
32
+ try {
33
+ runPublicationPackageCli();
34
+ } catch (error) {
35
+ console.error(`publication-package: ${error.message}`);
36
+ process.exitCode = 1;
37
+ }
38
+ }