@kungfu-tech/buildchain 2.11.1 → 2.11.2-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.
@@ -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",
@@ -298,6 +301,10 @@ if (siteBundle.package?.version !== rootPackage.version) {
298
301
  if (siteManifest.package?.version !== rootPackage.version) {
299
302
  throw new Error("site-manifest.json package.version must match package.json version");
300
303
  }
304
+ const publicationRegistry = JSON.parse(fs.readFileSync(path.join(root, "dist/site/publication-registry.json"), "utf8"));
305
+ if (publicationRegistry.package?.version !== rootPackage.version) {
306
+ throw new Error("publication-registry.json package.version must match package.json version");
307
+ }
301
308
  if (siteBundle.source?.homepageTextSource !== "README.md") {
302
309
  throw new Error("buildchain-site.json source.homepageTextSource must be README.md");
303
310
  }
@@ -909,7 +916,7 @@ if (!badgeEndpointRegistry.badges?.some((entry) => entry.id === "buildchain-rele
909
916
  throw new Error("badge endpoint registry must include Buildchain Release Passport badge");
910
917
  }
911
918
 
912
- for (const siteFile of ["buildchain-site.json", "site-manifest.json", "badge-endpoint-registry.json", "page-registry.json", "capability-registry.json", "cli-registry.json", "manual-registry.json", "node-api-registry.json", "workflow-registry.json", "public-surface-audit.json", "release-model.json", "buildchain-contract.json"]) {
919
+ for (const siteFile of ["buildchain-site.json", "site-manifest.json", "badge-endpoint-registry.json", "publication-registry.json", "page-registry.json", "capability-registry.json", "cli-registry.json", "manual-registry.json", "node-api-registry.json", "workflow-registry.json", "public-surface-audit.json", "release-model.json", "buildchain-contract.json"]) {
913
920
  if (!fs.existsSync(path.join(root, "dist", "site", siteFile))) {
914
921
  throw new Error(`site bundle missing ${siteFile}`);
915
922
  }
@@ -24,6 +24,7 @@ import {
24
24
  import { createSurfaceTimestampPolicy } from "../packages/core/surface-manifest.js";
25
25
 
26
26
  const SITE_BUNDLE_CONTRACT = "kungfu-buildchain-site-bundle";
27
+ const PUBLICATION_RELEASE_REGISTRY_CONTRACT = "kungfu-buildchain-publication-release-registry";
27
28
  const README_PATH = "README.md";
28
29
  const root = path.resolve(import.meta.dirname, "..");
29
30
  const outputDir = path.join(root, "dist", "site");
@@ -430,6 +431,7 @@ function cliCommandMeta(id) {
430
431
  "publish-source": { group: "release-passport-trust", purpose: "Create, inspect, or verify publish-gate source-lock refs." },
431
432
  "publication-artifact": { group: "reusable-build", purpose: "Generate publication artifact manifests, passports, and source bundles for paper/report repositories." },
432
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." },
433
435
  "release-dry-run": { group: "governance-versioning", purpose: "Explain what a channel merge would publish before the PR is merged." },
434
436
  "release-line-open": { group: "governance-versioning", purpose: "Plan or write the initial version-state commit for a new minor release line." },
435
437
  "release-propagation": { group: "site-and-propagation", purpose: "Plan channel-preserving downstream release PRs and write exact upstream release locks." },
@@ -465,6 +467,7 @@ function nodeApiMeta(exportName) {
465
467
  "./diagnostics": { group: "observability-diagnostics", summary: "Native diagnostics collection, summarization, cache, compiler, and process-sampler APIs." },
466
468
  "./logging": { group: "observability-diagnostics", summary: "Buildchain JSONL logging, span, summary, and verification APIs." },
467
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." },
468
471
  "./artifact-passport": { group: "release-passport-trust", summary: "Artifact passport digest and evidence helper APIs." },
469
472
  "./release-passport": { group: "release-passport-trust", summary: "Release passport collection, verification, explanation, and evidence APIs." },
470
473
  "./release-candidate": { group: "reusable-build", summary: "PR-stage release-candidate artifact, passport, and promote-only resolver APIs." },
@@ -522,6 +525,7 @@ function buildCapabilityRegistry({ docs, pages, cliRegistry, manualRegistry, nod
522
525
  "cli-registry.json",
523
526
  "node-api-registry.json",
524
527
  "workflow-registry.json",
528
+ "publication-registry.json",
525
529
  "kfd-upstream-aggregate.json",
526
530
  "kfd-claims.json",
527
531
  ],
@@ -535,7 +539,7 @@ function buildCapabilityRegistry({ docs, pages, cliRegistry, manualRegistry, nod
535
539
 
536
540
  function workflowCapabilityGroup(entry) {
537
541
  if (["web-surface", "release-propagation"].includes(entry.id)) return capabilityGroup("site-and-propagation");
538
- 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");
539
543
  if (["buildchain-ref-promotion", "release-line-bootstrap"].includes(entry.id)) return capabilityGroup("release-passport-trust");
540
544
  if (entry.id.includes("patrol") || entry.id.includes("dev-pr-auto-merge")) return capabilityGroup("governance-versioning");
541
545
  if (entry.status === "repository-internal" || entry.status === "compatibility-fixture") return capabilityGroup("api-cli-reference");
@@ -574,6 +578,90 @@ function buildSitePages() {
574
578
  ].sort((a, b) => a.route.localeCompare(b.route));
575
579
  }
576
580
 
581
+ function createPublicationReleaseRegistry({ packageJson, timestampPolicy }) {
582
+ return {
583
+ schemaVersion: 1,
584
+ contract: PUBLICATION_RELEASE_REGISTRY_CONTRACT,
585
+ ...timestampPolicy,
586
+ package: {
587
+ name: packageJson.name,
588
+ version: packageJson.version,
589
+ versionSource: "package.json#version",
590
+ },
591
+ sourceKind: "package-site-bundle",
592
+ sourceBoundary: {
593
+ truthOwner: "@kungfu-tech/buildchain",
594
+ siteRole: "rendering, routing, archive preservation checks, and agent discovery",
595
+ rule: "Downstream papers sites render this registry; Buildchain owns publication route, latest, immutable artifact, passport, and source bundle facts.",
596
+ },
597
+ archivePolicy: {
598
+ contract: "kungfu-buildchain-publication-archive-policy",
599
+ mutableRouteKinds: [
600
+ "canonical-reader",
601
+ "latest",
602
+ "registry-index",
603
+ ],
604
+ immutableRouteKinds: [
605
+ "version-artifact",
606
+ "version-passport",
607
+ "version-source",
608
+ ],
609
+ deploymentBoundary: "append-only immutable version prefixes",
610
+ rule: "A site build may update latest and canonical reader pages, but it must not delete or overwrite files under a declared immutable version prefix.",
611
+ },
612
+ publications: [
613
+ {
614
+ id: "publication-archive-fixture",
615
+ title: "Publication Archive Fixture",
616
+ summary: "A paper-shaped Buildchain package fact proving that site rendering preserves immutable versioned PDF, source, and passport routes while latest pages can move.",
617
+ canonicalReader: {
618
+ kind: "canonical-reader",
619
+ url: "https://kungfu.tech/whitepaper/",
620
+ owner: "site-kungfu-tech",
621
+ },
622
+ latest: {
623
+ kind: "latest",
624
+ version: "0.1.0",
625
+ path: "/publication-archive-fixture/latest/",
626
+ },
627
+ immutablePrefixTemplate: "/publication-archive-fixture/v{version}/",
628
+ versions: [
629
+ {
630
+ version: "0.1.0",
631
+ releasedAt: "2026-07-09T00:00:00.000Z",
632
+ immutable: true,
633
+ immutablePath: "/publication-archive-fixture/v0.1.0/",
634
+ source: {
635
+ repository: "https://github.com/kungfu-systems/publication-archive-fixture",
636
+ tag: "v0.1.0",
637
+ commit: "0000000000000000000000000000000000000000",
638
+ bundle: {
639
+ path: "source.tar.gz",
640
+ sha256: "sha256:e2ca891dbf441f867ed135b21b3556ee5cdcd3ec80038f267a3ecff496c5a38b",
641
+ fixtureBodyBase64: "c2l0ZS1saWJrdW5nZnUtZGV2IHB1YmxpY2F0aW9uIGFyY2hpdmUgZml4dHVyZSBzb3VyY2UgYnVuZGxlCnZlcnNpb246IDAuMS4wCg==",
642
+ },
643
+ },
644
+ passport: {
645
+ path: "publication-artifact-passport.json",
646
+ sha256: "sha256:ca214e2e17c8d3c01565e507e57d5b440943c762199aa8d9de21995940538cea",
647
+ fixtureBodyBase64: "ewogICJjb250cmFjdCI6ICJrdW5nZnUtYnVpbGRjaGFpbi1wdWJsaWNhdGlvbi1hcnRpZmFjdC1wYXNzcG9ydCIsCiAgInB1YmxpY2F0aW9uIjogInB1YmxpY2F0aW9uLWFyY2hpdmUtZml4dHVyZSIsCiAgInZlcnNpb24iOiAiMC4xLjAiLAogICJzdGF0dXMiOiAiZml4dHVyZSIKfQo=",
648
+ },
649
+ artifacts: [
650
+ {
651
+ role: "pdf",
652
+ path: "main.pdf",
653
+ mediaType: "application/pdf",
654
+ sha256: "sha256:c1c2020cbdcf0cc339323d2276480a48d9b8d7da9be1d19ab58a0b3c0b7a4fbb",
655
+ fixtureBodyBase64: "JVBERi0xLjQKJSBzaXRlLWxpYmt1bmdmdS1kZXYgcHVibGljYXRpb24gYXJjaGl2ZSBmaXh0dXJlCjEgMCBvYmogPDwgL1R5cGUgL0NhdGFsb2cgL1BhZ2VzIDIgMCBSID4+IGVuZG9iagoyIDAgb2JqIDw8IC9UeXBlIC9QYWdlcyAvS2lkcyBbMyAwIFJdIC9Db3VudCAxID4+IGVuZG9iagozIDAgb2JqIDw8IC9UeXBlIC9QYWdlIC9QYXJlbnQgMiAwIFIgL01lZGlhQm94IFswIDAgMzAwIDE0NF0gL0NvbnRlbnRzIDQgMCBSID4+IGVuZG9iago0IDAgb2JqIDw8IC9MZW5ndGggNzIgPj4gc3RyZWFtCkJUIC9GMSAxMiBUZiAzNiAxMDAgVGQgKFB1YmxpY2F0aW9uIGFyY2hpdmUgZml4dHVyZSB2MC4xLjApIFRqIEVUCmVuZHN0cmVhbSBlbmRvYmoKeHJlZgowIDUKMDAwMDAwMDAwMCA2NTUzNSBmIAp0cmFpbGVyIDw8IC9Sb290IDEgMCBSIC9TaXplIDUgPj4Kc3RhcnR4cmVmCjM2MAolJUVPRgo=",
656
+ },
657
+ ],
658
+ },
659
+ ],
660
+ },
661
+ ],
662
+ };
663
+ }
664
+
577
665
  function buildSiteBundle() {
578
666
  const packageJson = readJson("package.json");
579
667
  const inventory = readJson("tests/buildchain-inventory.json");
@@ -729,6 +817,7 @@ function buildSiteBundle() {
729
817
  ["buildchain-ref-promotion", "release-governance"],
730
818
  ["release-line-bootstrap", "release-governance"],
731
819
  ["release-candidate-promote", "release-governance"],
820
+ ["paper-release", "reusable-build"],
732
821
  ["release-propagation", "release-propagation"],
733
822
  ["dev-pr-auto-merge", "dev-governance"],
734
823
  ["binary-distribution", "release-passport"],
@@ -843,6 +932,7 @@ function buildSiteBundle() {
843
932
  "buildchain-site.json",
844
933
  "site-manifest.json",
845
934
  "badge-endpoint-registry.json",
935
+ "publication-registry.json",
846
936
  "page-registry.json",
847
937
  "capability-registry.json",
848
938
  "cli-registry.json",
@@ -933,6 +1023,7 @@ function buildSiteBundle() {
933
1023
  "release-model.json",
934
1024
  "artifact-schemas.json",
935
1025
  "badge-endpoint-registry.json",
1026
+ "publication-registry.json",
936
1027
  "product-mechanism.json",
937
1028
  "release-provenance.json",
938
1029
  "kfd-upstream-aggregate.json",
@@ -960,12 +1051,14 @@ function buildSiteBundle() {
960
1051
  "kfd-upstream-aggregate.json",
961
1052
  "kfd-claims.json",
962
1053
  "badge-endpoint-registry.json",
1054
+ "publication-registry.json",
963
1055
  ],
964
1056
  instruction: "Use this bundle as the package-owned fact source for Buildchain pages. Do not infer current release mechanics from prose alone.",
965
1057
  };
966
1058
  const badgeEndpointRegistry = createReadmeBadgeEndpointRegistry({
967
1059
  kfdStandards: readPackageKfdStandards(),
968
1060
  });
1061
+ const publicationRegistry = createPublicationReleaseRegistry({ packageJson, timestampPolicy });
969
1062
 
970
1063
  const homepageSections = [
971
1064
  homepageSection({
@@ -1094,6 +1187,7 @@ function buildSiteBundle() {
1094
1187
  "KFD claim registry",
1095
1188
  "KFD upstream aggregate registry",
1096
1189
  "release-passport evidence vocabulary",
1190
+ "publication archive registry and immutable papers surface facts",
1097
1191
  ],
1098
1192
  ownedBySite: [
1099
1193
  "HTML structure",
@@ -1121,6 +1215,7 @@ function buildSiteBundle() {
1121
1215
  "release-model.json": releaseModel,
1122
1216
  "artifact-schemas.json": artifactSchemas,
1123
1217
  "badge-endpoint-registry.json": badgeEndpointRegistry,
1218
+ "publication-registry.json": publicationRegistry,
1124
1219
  "buildchain-contract.json": createBuildchainContractWorld({ root }),
1125
1220
  "kfd-upstream-aggregate.json": collectKfdUpstreamFacts({ cwd: root, includeOwn: false }),
1126
1221
  "kfd-claims.json": createBuildchainKfdClaimRegistry({ root }),
@@ -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
+ }