@kungfu-tech/buildchain 2.11.14-alpha.1 → 2.11.14-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/dist/site/buildchain-contract.json +25 -17
- package/dist/site/buildchain-site.json +18 -18
- package/dist/site/kfd-claims.json +5 -4
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +4 -4
- package/dist/site/node-api-registry.json +3 -3
- package/dist/site/page-registry.json +10 -10
- package/dist/site/public-surface-audit.json +4 -3
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +8 -8
- package/dist/site/workflow-registry.json +2 -1
- package/docs/lifecycle-protocol.md +4 -1
- package/docs/publication-artifacts.md +9 -0
- package/docs/release-governance.md +9 -3
- package/docs/reusable-build-surface.md +6 -2
- package/docs/web-surface-deployments.md +15 -1
- package/package.json +1 -1
- package/packages/core/buildchain-contract.js +9 -1
- package/packages/core/publication-artifact.js +56 -8
- package/packages/core/publication-package.js +5 -0
- package/scripts/locked-source-checkout.mjs +43 -9
- package/scripts/publication-artifact.mjs +20 -2
- package/scripts/publication-registry-hydrate.mjs +216 -0
- package/scripts/run-lifecycle-core.mjs +7 -2
- package/scripts/web-surface-core.mjs +28 -0
|
@@ -412,6 +412,7 @@
|
|
|
412
412
|
"buildchain-ref",
|
|
413
413
|
"buildchain-repository",
|
|
414
414
|
"checkout-cache-fallback",
|
|
415
|
+
"checkout-cache-fetch-attempts",
|
|
415
416
|
"checkout-cache-mirror-url-template",
|
|
416
417
|
"checkout-cache-mode",
|
|
417
418
|
"checkout-cache-reference-repository-template",
|
|
@@ -442,7 +443,7 @@
|
|
|
442
443
|
"verify-command",
|
|
443
444
|
"working-directory"
|
|
444
445
|
],
|
|
445
|
-
"inputCount":
|
|
446
|
+
"inputCount": 49
|
|
446
447
|
},
|
|
447
448
|
{
|
|
448
449
|
"id": ".bump-minor-version",
|
|
@@ -2538,8 +2539,8 @@
|
|
|
2538
2539
|
"workflowRegistryPath": "dist/site/workflow-registry.json",
|
|
2539
2540
|
"pageRegistryPath": "dist/site/page-registry.json",
|
|
2540
2541
|
"cliRegistryDigest": "3c0729f5351eb82fe26625fa5aabf30903f45f68af2431e02860f85ada468ac9",
|
|
2541
|
-
"workflowRegistryDigest": "
|
|
2542
|
-
"pageRegistryDigest": "
|
|
2542
|
+
"workflowRegistryDigest": "7052aa984ab49647ce2c9f96669caa32f92da57d8b2e506ac172089bedd793cf",
|
|
2543
|
+
"pageRegistryDigest": "784424e0ecb730449947fc0a3aba0bfcff77c34d097d63d71a9d60d5db1383df"
|
|
2543
2544
|
},
|
|
2544
2545
|
"comparison": {
|
|
2545
2546
|
"missingCliRegistry": [],
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"contract": "kungfu-buildchain-publication-release-registry",
|
|
4
|
-
"generatedAt": "2026-07-
|
|
5
|
-
"publishedAt": "2026-07-
|
|
4
|
+
"generatedAt": "2026-07-10T13:03:42.866Z",
|
|
5
|
+
"publishedAt": "2026-07-10T13:03:42.866Z",
|
|
6
6
|
"reproducible": true,
|
|
7
7
|
"timestampPolicy": "ci-injected",
|
|
8
8
|
"deterministicInputs": [
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"declared Buildchain surface manifest contract"
|
|
20
20
|
],
|
|
21
21
|
"sourceDateEpoch": "0",
|
|
22
|
-
"sourceRevision": "
|
|
22
|
+
"sourceRevision": "d0be2b26d092ef5fa411d30888ea0ee3fa044c85",
|
|
23
23
|
"timestampPolicyDetails": {
|
|
24
24
|
"contract": "kungfu-buildchain-surface-timestamp-policy",
|
|
25
25
|
"timestampFields": [
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"package": {
|
|
34
34
|
"name": "@kungfu-tech/buildchain",
|
|
35
|
-
"version": "2.11.14-alpha.
|
|
35
|
+
"version": "2.11.14-alpha.2",
|
|
36
36
|
"versionSource": "package.json#version"
|
|
37
37
|
},
|
|
38
38
|
"sourceKind": "package-site-bundle",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"contract": "kungfu-buildchain-site-manifest",
|
|
4
|
-
"generatedAt": "2026-07-
|
|
5
|
-
"publishedAt": "2026-07-
|
|
4
|
+
"generatedAt": "2026-07-10T13:03:42.866Z",
|
|
5
|
+
"publishedAt": "2026-07-10T13:03:42.866Z",
|
|
6
6
|
"reproducible": true,
|
|
7
7
|
"timestampPolicy": "ci-injected",
|
|
8
8
|
"deterministicInputs": [
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"declared Buildchain surface manifest contract"
|
|
20
20
|
],
|
|
21
21
|
"sourceDateEpoch": "0",
|
|
22
|
-
"sourceRevision": "
|
|
22
|
+
"sourceRevision": "d0be2b26d092ef5fa411d30888ea0ee3fa044c85",
|
|
23
23
|
"timestampPolicyDetails": {
|
|
24
24
|
"contract": "kungfu-buildchain-surface-timestamp-policy",
|
|
25
25
|
"timestampFields": [
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"package": {
|
|
39
39
|
"name": "@kungfu-tech/buildchain",
|
|
40
|
-
"version": "2.11.14-alpha.
|
|
40
|
+
"version": "2.11.14-alpha.2",
|
|
41
41
|
"versionSource": "package.json#version"
|
|
42
42
|
},
|
|
43
43
|
"entrypoint": "buildchain-site.json",
|
|
@@ -197,7 +197,7 @@
|
|
|
197
197
|
"path": "docs/lifecycle-protocol.md",
|
|
198
198
|
"plane": "use",
|
|
199
199
|
"exists": true,
|
|
200
|
-
"digest": "sha256:
|
|
200
|
+
"digest": "sha256:60c39693878258d275e9d47983b306faed35d6416297c14570429de73284604c"
|
|
201
201
|
},
|
|
202
202
|
{
|
|
203
203
|
"id": "reusable-build-surface",
|
|
@@ -205,7 +205,7 @@
|
|
|
205
205
|
"path": "docs/reusable-build-surface.md",
|
|
206
206
|
"plane": "use",
|
|
207
207
|
"exists": true,
|
|
208
|
-
"digest": "sha256:
|
|
208
|
+
"digest": "sha256:4f3699841c5a189d199db045d526b11f76ad0dff1464d6a63ca98ceadf1bb215"
|
|
209
209
|
},
|
|
210
210
|
{
|
|
211
211
|
"id": "publish-transaction",
|
|
@@ -221,7 +221,7 @@
|
|
|
221
221
|
"path": "docs/release-governance.md",
|
|
222
222
|
"plane": "why",
|
|
223
223
|
"exists": true,
|
|
224
|
-
"digest": "sha256:
|
|
224
|
+
"digest": "sha256:59858b6e02e42377b8a1f3d6d33ba50c5ffba476f7e1443fd61e03919046eff2"
|
|
225
225
|
},
|
|
226
226
|
{
|
|
227
227
|
"id": "release-flow",
|
|
@@ -253,7 +253,7 @@
|
|
|
253
253
|
"path": "docs/web-surface-deployments.md",
|
|
254
254
|
"plane": "use",
|
|
255
255
|
"exists": true,
|
|
256
|
-
"digest": "sha256:
|
|
256
|
+
"digest": "sha256:f1e92ba87417dc9aac4f8cf080565f7f2c114b5007bb68b90f33692c73fdad88"
|
|
257
257
|
}
|
|
258
258
|
],
|
|
259
259
|
"facts": [
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"buildchain-ref",
|
|
27
27
|
"buildchain-repository",
|
|
28
28
|
"checkout-cache-fallback",
|
|
29
|
+
"checkout-cache-fetch-attempts",
|
|
29
30
|
"checkout-cache-mirror-url-template",
|
|
30
31
|
"checkout-cache-mode",
|
|
31
32
|
"checkout-cache-reference-repository-template",
|
|
@@ -56,7 +57,7 @@
|
|
|
56
57
|
"verify-command",
|
|
57
58
|
"working-directory"
|
|
58
59
|
],
|
|
59
|
-
"inputCount":
|
|
60
|
+
"inputCount": 49,
|
|
60
61
|
"surface": "reusable-workflow",
|
|
61
62
|
"capabilityGroup": "api-cli-reference",
|
|
62
63
|
"status": "active"
|
|
@@ -293,7 +293,10 @@ checked-out release material. Any code or undeclared file change still fails the
|
|
|
293
293
|
release tree gate.
|
|
294
294
|
|
|
295
295
|
The action input `verification-command` remains supported. When it is provided,
|
|
296
|
-
it overrides `lifecycle.verify` for that invocation.
|
|
296
|
+
it overrides `lifecycle.verify` for that invocation. The override inherits
|
|
297
|
+
`[lifecycle.env]` and `lifecycle.verify.env`; when the configured stage declares
|
|
298
|
+
`shell`, it also inherits that shell. If the stage has no explicit shell,
|
|
299
|
+
Buildchain preserves the platform-default shell for compatibility.
|
|
297
300
|
|
|
298
301
|
## Migration Preflight
|
|
299
302
|
|
|
@@ -150,6 +150,15 @@ The build-only workflow:
|
|
|
150
150
|
|
|
151
151
|
It does not publish npm packages, deploy web pages, or create GitHub Releases.
|
|
152
152
|
|
|
153
|
+
The paper release preset additionally hydrates every prior published package
|
|
154
|
+
registry from the npm registry before generating the current manifest. npm
|
|
155
|
+
package integrity authenticates each downloaded source; Buildchain verifies the
|
|
156
|
+
registry self-digest, merges immutable records, and fails if a cumulative
|
|
157
|
+
registry drops an accepted version or changes immutable route/artifact facts.
|
|
158
|
+
The synthesized package therefore carries complete history even on a clean
|
|
159
|
+
runner. Its cumulative registry and file SHA-256 values are bound into the paper
|
|
160
|
+
release build summary and release passport evidence.
|
|
161
|
+
|
|
153
162
|
## Paper Release Preset
|
|
154
163
|
|
|
155
164
|
Paper repositories that publish a versioned npm package should use the
|
|
@@ -318,8 +318,14 @@ After each merge, the next PR is re-evaluated before it can move the protected
|
|
|
318
318
|
dev branch. This prevents one merge from silently making the next candidate
|
|
319
319
|
stale or conflicting.
|
|
320
320
|
|
|
321
|
-
The required check
|
|
322
|
-
|
|
321
|
+
The canonical consumer required check context is `check / check`, matching the
|
|
322
|
+
reusable workflow call plus its `check` job. Buildchain's own `Verify` workflow
|
|
323
|
+
emits the repository-local context `check`, so Buildchain self-promotion,
|
|
324
|
+
release-line bootstrap, and dogfood patrol wrappers pass `check` explicitly.
|
|
325
|
+
Release governance preserves the exact context emitted by the repository and
|
|
326
|
+
records branch-protection policy before/after facts; it does not rewrite one
|
|
327
|
+
context shape into the other. Consumer repositories can keep their context
|
|
328
|
+
stable while changing the actual verification command declaratively in
|
|
323
329
|
`buildchain.toml`:
|
|
324
330
|
|
|
325
331
|
```toml
|
|
@@ -359,7 +365,7 @@ jobs:
|
|
|
359
365
|
statuses: read
|
|
360
366
|
with:
|
|
361
367
|
target-branch: dev/v2/v2.6
|
|
362
|
-
required-status-checks: check
|
|
368
|
+
required-status-checks: check / check
|
|
363
369
|
ready-label: ready
|
|
364
370
|
block-labels: blocked,do-not-merge
|
|
365
371
|
max-merges: 1
|
|
@@ -233,6 +233,7 @@ jobs:
|
|
|
233
233
|
checkout-cache-reference-repository-template: ${{ vars.BUILDCHAIN_CHECKOUT_CACHE_REFERENCE_REPOSITORY_TEMPLATE }}
|
|
234
234
|
checkout-cache-fallback: github
|
|
235
235
|
checkout-cache-timeout-seconds: 60
|
|
236
|
+
checkout-cache-fetch-attempts: 3
|
|
236
237
|
```
|
|
237
238
|
|
|
238
239
|
`checkout-cache-mode` accepts:
|
|
@@ -255,7 +256,10 @@ Do not read cache URLs or reference paths from PR-controlled files such as
|
|
|
255
256
|
`.buildchain/buildchain.toml`. These values are trusted workflow inputs or repo/org
|
|
256
257
|
variables. Buildchain does not pass GitHub credentials to cache mirrors or
|
|
257
258
|
reference repositories. If it must fall back to GitHub, the workflow token is
|
|
258
|
-
used only for the GitHub fetch path.
|
|
259
|
+
used only for the GitHub fetch path. Retryable timeout and transient network
|
|
260
|
+
failures use the bounded `checkout-cache-fetch-attempts` budget; permanent
|
|
261
|
+
failures stop immediately. Diagnostics record the configured budget and actual
|
|
262
|
+
GitHub fetch attempts before exact HEAD/tree verification.
|
|
259
263
|
|
|
260
264
|
Each platform diagnostics artifact includes `source-checkout.json` and embeds a
|
|
261
265
|
compact `sourceCheckout` summary in `diagnostics.json`: mode, transport,
|
|
@@ -592,7 +596,7 @@ jobs:
|
|
|
592
596
|
runner-preset: github-hosted
|
|
593
597
|
trusted-publishing: true
|
|
594
598
|
github-release: true
|
|
595
|
-
required-status-check: check
|
|
599
|
+
required-status-check: check / check
|
|
596
600
|
required-artifact-count: 3
|
|
597
601
|
publish-dist-tag: alpha
|
|
598
602
|
publish-package-set-order: platforms-first-main-last
|
|
@@ -351,6 +351,16 @@ The CLI emits GitHub outputs when `GITHUB_OUTPUT` is present:
|
|
|
351
351
|
- `web-surface-artifact-hash`
|
|
352
352
|
- `web-surface-manifest-json`
|
|
353
353
|
|
|
354
|
+
The reusable workflow resolves that same effective channel before running the
|
|
355
|
+
caller build or verify command. Both steps receive
|
|
356
|
+
`BUILDCHAIN_WEB_SURFACE_CHANNEL` (`preview`, `staging`, or `production`) and
|
|
357
|
+
`BUILDCHAIN_PREVIEW_ALIAS` for previews. Compatibility aliases
|
|
358
|
+
`BUILDCHAIN_SURFACE_CHANNEL` and `BUILDCHAIN_WEB_SURFACE_ALIAS` are also
|
|
359
|
+
provided. Callers should consume these variables instead of reconstructing the
|
|
360
|
+
release-intent state machine from raw GitHub events.
|
|
361
|
+
An unapproved manual canary resolves to `staging`; a trusted manual dispatch
|
|
362
|
+
with `production-approved=true` resolves to `production` through the same path.
|
|
363
|
+
|
|
354
364
|
## Explicit Apply
|
|
355
365
|
|
|
356
366
|
`deploy-apply` and `cleanup-apply` are explicit execution modes for the
|
|
@@ -452,7 +462,11 @@ HTTP check is visible from the consumer run.
|
|
|
452
462
|
|
|
453
463
|
It can also execute a previously saved deploy plan. In that mode Buildchain
|
|
454
464
|
recomputes the local artifact hash before running AWS commands and fails closed
|
|
455
|
-
if the artifact no longer matches the saved plan
|
|
465
|
+
if the artifact no longer matches the saved plan. Before any adapter operation,
|
|
466
|
+
it also checks channel-aware JSON manifests in the artifact: top-level
|
|
467
|
+
`canonicalHost` and declared `pages[].host` facts must belong to the plan's
|
|
468
|
+
surface hosts. A production plan therefore rejects staging/preview host facts
|
|
469
|
+
before AWS apply:
|
|
456
470
|
|
|
457
471
|
```bash
|
|
458
472
|
node scripts/web-surface.mjs \
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kungfu-tech/buildchain",
|
|
3
|
-
"version": "2.11.14-alpha.
|
|
3
|
+
"version": "2.11.14-alpha.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Buildchain Release Passport, release governance, CLI toolkit, and site facts.",
|
|
6
6
|
"repository": "https://github.com/kungfu-systems/buildchain",
|
|
@@ -96,6 +96,10 @@ export function createBuildchainContractWorld({ root = process.cwd(), packageJso
|
|
|
96
96
|
"platforms-json",
|
|
97
97
|
"release-candidate",
|
|
98
98
|
"artifact-transfer-mode",
|
|
99
|
+
"checkout-cache-mode",
|
|
100
|
+
"checkout-cache-fallback",
|
|
101
|
+
"checkout-cache-timeout-seconds",
|
|
102
|
+
"checkout-cache-fetch-attempts",
|
|
99
103
|
"buildchain-contract-lock-path",
|
|
100
104
|
"buildchain-contract-drift-issue-mode",
|
|
101
105
|
],
|
|
@@ -104,6 +108,7 @@ export function createBuildchainContractWorld({ root = process.cwd(), packageJso
|
|
|
104
108
|
"publish source locks are verified before heavy build jobs",
|
|
105
109
|
"release-candidate builds do not publish registry artifacts",
|
|
106
110
|
"contract drift is checked before heavy build jobs for stable floating refs",
|
|
111
|
+
"retryable GitHub fallback fetches use a bounded attempt budget before exact source SHA and tree verification",
|
|
107
112
|
],
|
|
108
113
|
}),
|
|
109
114
|
surface(root, {
|
|
@@ -115,7 +120,7 @@ export function createBuildchainContractWorld({ root = process.cwd(), packageJso
|
|
|
115
120
|
requiredOutputs: ["promoted-sha", "built-source-sha", "release-candidate-artifact"],
|
|
116
121
|
breakingDefaults: {
|
|
117
122
|
promoteOnlyReleaseCandidate: true,
|
|
118
|
-
requiredStatusCheck: "check",
|
|
123
|
+
requiredStatusCheck: "check / check",
|
|
119
124
|
},
|
|
120
125
|
optionalInputs: [
|
|
121
126
|
"buildchain-ref",
|
|
@@ -177,6 +182,8 @@ export function createBuildchainContractWorld({ root = process.cwd(), packageJso
|
|
|
177
182
|
"contract drift is checked before caller build, web-surface render, deploy planning, and apply side effects",
|
|
178
183
|
"compatible contract drift continues and produces a consumer issue or summary",
|
|
179
184
|
"breaking contract drift fails closed before rendering, deployment, or release publication",
|
|
185
|
+
"effective channel and preview alias are injected before caller build and verify commands",
|
|
186
|
+
"channel-aware artifact host facts are checked against the deploy plan before adapter side effects",
|
|
180
187
|
],
|
|
181
188
|
}),
|
|
182
189
|
surface(root, {
|
|
@@ -189,6 +196,7 @@ export function createBuildchainContractWorld({ root = process.cwd(), packageJso
|
|
|
189
196
|
breakingDefaults: {
|
|
190
197
|
requireGovernance: false,
|
|
191
198
|
releasePassport: true,
|
|
199
|
+
requiredStatusCheck: "check / check",
|
|
192
200
|
},
|
|
193
201
|
optionalInputs: [
|
|
194
202
|
"publish-transaction",
|
|
@@ -477,7 +477,54 @@ function publicationArchiveRecord({ collected, manifestPath, passportPath, regis
|
|
|
477
477
|
};
|
|
478
478
|
}
|
|
479
479
|
|
|
480
|
-
function
|
|
480
|
+
function immutablePublicationRecord(record = {}) {
|
|
481
|
+
const { publishedAt: _publishedAt, latestObservedAt: _latestObservedAt, recordSha256: _recordSha256, ...immutable } = record;
|
|
482
|
+
return immutable;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function readVerifiedPublicationRegistry(registryPath) {
|
|
486
|
+
const registry = JSON.parse(fs.readFileSync(registryPath, "utf8"));
|
|
487
|
+
if (registry.contract !== PUBLICATION_ARTIFACT_REGISTRY_CONTRACT) {
|
|
488
|
+
throw new Error(`publication archive registry has unsupported contract: ${registry.contract || "(missing)"}`);
|
|
489
|
+
}
|
|
490
|
+
const expectedDigest = sha256Json({ ...registry, registrySha256: undefined });
|
|
491
|
+
if (registry.registrySha256 !== expectedDigest) {
|
|
492
|
+
throw new Error(`publication archive registry digest mismatch for ${registryPath}: expected ${expectedDigest}, got ${registry.registrySha256 || "(missing)"}`);
|
|
493
|
+
}
|
|
494
|
+
if (!Array.isArray(registry.versions)) throw new Error(`publication archive registry versions must be an array: ${registryPath}`);
|
|
495
|
+
return registry;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function mergePublicationRegistryVersions({ registries = [], publicationId }) {
|
|
499
|
+
const versions = new Map();
|
|
500
|
+
for (const registry of registries) {
|
|
501
|
+
if (registry.publication?.id !== publicationId) {
|
|
502
|
+
throw new Error(`publication archive registry id mismatch: expected ${publicationId}, got ${registry.publication?.id || "(missing)"}`);
|
|
503
|
+
}
|
|
504
|
+
if (registry.historyPolicy === "cumulative-authenticated-hydration") {
|
|
505
|
+
const declaredVersions = new Set(registry.versions.map((record) => String(record?.version || "")));
|
|
506
|
+
const missingVersions = [...versions.keys()].filter((version) => !declaredVersions.has(version));
|
|
507
|
+
if (missingVersions.length > 0) throw new Error(`cumulative publication archive registry dropped accepted versions: ${missingVersions.join(", ")}`);
|
|
508
|
+
}
|
|
509
|
+
for (const record of registry.versions) {
|
|
510
|
+
const version = String(record?.version || "").trim();
|
|
511
|
+
if (!version) throw new Error("publication archive registry record is missing version");
|
|
512
|
+
const existing = versions.get(version);
|
|
513
|
+
if (!existing) {
|
|
514
|
+
versions.set(version, record);
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
if (existing.immutableDigest !== record.immutableDigest || stableJson(immutablePublicationRecord(existing)) !== stableJson(immutablePublicationRecord(record))) {
|
|
518
|
+
throw new Error(`publication archive version ${version} changed across accepted registries: ${existing.immutableDigest || "(missing)"} != ${record.immutableDigest || "(missing)"}`);
|
|
519
|
+
}
|
|
520
|
+
const latestObservedAt = [existing.latestObservedAt, record.latestObservedAt].filter(Boolean).sort().at(-1);
|
|
521
|
+
versions.set(version, latestObservedAt ? { ...existing, latestObservedAt } : existing);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return [...versions.values()];
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function updatePublicationArchiveRegistry({ cwd, collected, manifestPath, passportPath, registryOutput, registryInputs = [], generatedAt }) {
|
|
481
528
|
const archive = collected.manifest.publication.archive;
|
|
482
529
|
if (!archive) {
|
|
483
530
|
return undefined;
|
|
@@ -503,17 +550,16 @@ function updatePublicationArchiveRegistry({ cwd, collected, manifestPath, passpo
|
|
|
503
550
|
siteConsumers: collected.manifest.publication.siteConsumers,
|
|
504
551
|
},
|
|
505
552
|
appendOnly: true,
|
|
553
|
+
historyPolicy: "cumulative-authenticated-hydration",
|
|
506
554
|
sameVersionRepublish: "fail-on-digest-change",
|
|
507
555
|
immutablePathPolicy: archive.immutablePathPolicy,
|
|
508
556
|
versions: [],
|
|
509
557
|
};
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
}
|
|
516
|
-
const versions = Array.isArray(registry.versions) ? registry.versions : [];
|
|
558
|
+
const inputPaths = [...registryInputs.map((entry) => path.resolve(cwd, entry)), ...(fs.existsSync(registryPath) ? [registryPath] : [])];
|
|
559
|
+
const versions = mergePublicationRegistryVersions({
|
|
560
|
+
registries: inputPaths.map(readVerifiedPublicationRegistry),
|
|
561
|
+
publicationId: archive.id,
|
|
562
|
+
});
|
|
517
563
|
const existingIndex = versions.findIndex((entry) => entry.version === record.version);
|
|
518
564
|
if (existingIndex !== -1) {
|
|
519
565
|
const existing = versions[existingIndex];
|
|
@@ -559,6 +605,7 @@ export function writePublicationArtifact({
|
|
|
559
605
|
output = "",
|
|
560
606
|
passportOutput = "",
|
|
561
607
|
registryOutput = "",
|
|
608
|
+
registryInputs = [],
|
|
562
609
|
sourceSha = "",
|
|
563
610
|
sourceBundle = true,
|
|
564
611
|
sourceBundlePath = "",
|
|
@@ -586,6 +633,7 @@ export function writePublicationArtifact({
|
|
|
586
633
|
manifestPath: path.resolve(cwd, manifestOutput),
|
|
587
634
|
passportPath: path.resolve(cwd, passportPath),
|
|
588
635
|
registryOutput,
|
|
636
|
+
registryInputs,
|
|
589
637
|
generatedAt,
|
|
590
638
|
});
|
|
591
639
|
return {
|
|
@@ -106,6 +106,7 @@ export function collectPublicationPackageFacts({
|
|
|
106
106
|
manifestPath: publication.manifestPath,
|
|
107
107
|
passportPath: ".buildchain/publication/publication-artifact-passport.json",
|
|
108
108
|
registryPath: publication.archive?.registryPath || "",
|
|
109
|
+
registryHydrationPath: publication.archive ? ".buildchain/publication/registry-hydration.json" : "",
|
|
109
110
|
sourceBundlePath: publication.sourceBundlePath,
|
|
110
111
|
artifactPaths: [publication.primaryArtifact, ...publication.artifactPaths].filter(Boolean),
|
|
111
112
|
metadataPaths: publication.metadataPaths,
|
|
@@ -131,6 +132,7 @@ export function preparePublicationNpmPackage({
|
|
|
131
132
|
facts.publication.manifestPath,
|
|
132
133
|
facts.publication.passportPath,
|
|
133
134
|
facts.publication.registryPath,
|
|
135
|
+
facts.publication.registryHydrationPath,
|
|
134
136
|
facts.publication.sourceBundlePath,
|
|
135
137
|
...facts.publication.artifactPaths,
|
|
136
138
|
...facts.publication.metadataPaths,
|
|
@@ -197,6 +199,9 @@ export function preparePublicationNpmPackage({
|
|
|
197
199
|
publicationManifest: facts.publication.manifestPath,
|
|
198
200
|
publicationPassport: facts.publication.passportPath,
|
|
199
201
|
publicationRegistry: facts.publication.registryPath || undefined,
|
|
202
|
+
publicationRegistryHydration: files.some((file) => file.path === facts.publication.registryHydrationPath)
|
|
203
|
+
? facts.publication.registryHydrationPath
|
|
204
|
+
: undefined,
|
|
200
205
|
primaryArtifact: facts.publication.primaryArtifact,
|
|
201
206
|
sourceBundle: facts.publication.sourceBundlePath,
|
|
202
207
|
siteConsumers: facts.publication.siteConsumers,
|
|
@@ -213,6 +213,31 @@ function fetchSha({ targetPath, remoteName, remoteUrl, sha, fetchRef, timeoutMs,
|
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
+
export function runBoundedFetch({ attempts = 1, fetch, onAttempt = () => {}, onRetry = () => {}, shouldRetry = () => true }) {
|
|
217
|
+
const limit = Math.max(1, Math.floor(Number(attempts) || 1));
|
|
218
|
+
let lastError;
|
|
219
|
+
for (let attempt = 1; attempt <= limit; attempt += 1) {
|
|
220
|
+
onAttempt({ attempt, limit });
|
|
221
|
+
try {
|
|
222
|
+
return { value: fetch({ attempt, limit }), attempts: attempt };
|
|
223
|
+
} catch (error) {
|
|
224
|
+
lastError = error;
|
|
225
|
+
if (attempt >= limit || !shouldRetry(error)) {
|
|
226
|
+
error.fetchAttempts = attempt;
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
onRetry({ attempt, limit, error });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
throw lastError;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function retryableGitFetchError(error) {
|
|
236
|
+
const code = String(error?.code || "").toUpperCase();
|
|
237
|
+
if (["ETIMEDOUT", "ECONNRESET", "ECONNREFUSED", "EAI_AGAIN", "ENETUNREACH", "EPIPE"].includes(code)) return true;
|
|
238
|
+
return /timed?\s*out|timeout|connection (?:reset|refused)|remote end hung up|early eof|rpc failed|http (?:429|5\d\d)|temporary failure|network is unreachable/i.test(String(error?.message || error || ""));
|
|
239
|
+
}
|
|
240
|
+
|
|
216
241
|
function githubRemoteUrl({ repository, serverUrl = "https://github.com" }) {
|
|
217
242
|
const base = String(serverUrl || "https://github.com").replace(/\/+$/, "");
|
|
218
243
|
return `${base}/${repository}.git`;
|
|
@@ -252,6 +277,7 @@ export function lockedSourceCheckout({
|
|
|
252
277
|
referenceRepositoryTemplate = readEnv("BUILDCHAIN_CHECKOUT_CACHE_REFERENCE_REPOSITORY_TEMPLATE"),
|
|
253
278
|
fallback = readEnv("BUILDCHAIN_CHECKOUT_CACHE_FALLBACK", "github"),
|
|
254
279
|
timeoutSeconds = Number(readEnv("BUILDCHAIN_CHECKOUT_CACHE_TIMEOUT_SECONDS", "60") || 60),
|
|
280
|
+
fetchAttempts = Number(readEnv("BUILDCHAIN_CHECKOUT_CACHE_FETCH_ATTEMPTS", "3") || 3),
|
|
255
281
|
diagnosticsPath = readEnv("BUILDCHAIN_SOURCE_CHECKOUT_DIAGNOSTICS_PATH", ".buildchain/diagnostics/source-checkout.json"),
|
|
256
282
|
githubToken = readEnv("GITHUB_TOKEN"),
|
|
257
283
|
githubServerUrl = readEnv("GITHUB_SERVER_URL", "https://github.com"),
|
|
@@ -265,6 +291,7 @@ export function lockedSourceCheckout({
|
|
|
265
291
|
const treeSha = sourceTreeSha ? assertSha(sourceTreeSha, "sourceTreeSha") : "";
|
|
266
292
|
const repoParts = splitRepository(repository);
|
|
267
293
|
const timeoutMs = Math.max(1, Number(timeoutSeconds || 60)) * 1000;
|
|
294
|
+
const normalizedFetchAttempts = Math.max(1, Math.floor(Number(fetchAttempts) || 3));
|
|
268
295
|
const targetPath = ensureCheckoutTarget(checkoutPath, workspace);
|
|
269
296
|
git(["init"], { cwd: targetPath, timeoutMs });
|
|
270
297
|
const renderedMirrorUrl = renderTemplate(mirrorUrlTemplate, { ...repoParts, sha });
|
|
@@ -285,6 +312,7 @@ export function lockedSourceCheckout({
|
|
|
285
312
|
mode: normalizedMode,
|
|
286
313
|
fallback: normalizedFallback,
|
|
287
314
|
timeoutSeconds: Math.max(1, Number(timeoutSeconds || 60)),
|
|
315
|
+
fetchAttempts: normalizedFetchAttempts,
|
|
288
316
|
mirror: sanitizeIdentity(renderedMirrorUrl),
|
|
289
317
|
referenceRepository: sanitizeIdentity(renderedReferenceRepository),
|
|
290
318
|
},
|
|
@@ -294,6 +322,7 @@ export function lockedSourceCheckout({
|
|
|
294
322
|
transport: "github",
|
|
295
323
|
fallbackUsed: normalizedMode === "off",
|
|
296
324
|
fallbackReason: normalizedMode === "off" ? "cache disabled" : "",
|
|
325
|
+
githubFetchAttempts: 0,
|
|
297
326
|
},
|
|
298
327
|
verification: {
|
|
299
328
|
head: "",
|
|
@@ -352,15 +381,20 @@ export function lockedSourceCheckout({
|
|
|
352
381
|
if (!evidence.cache.fallbackReason && checkoutError) {
|
|
353
382
|
evidence.cache.fallbackReason = checkoutError.message;
|
|
354
383
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
384
|
+
try {
|
|
385
|
+
const fetchResult = runBoundedFetch({
|
|
386
|
+
attempts: normalizedFetchAttempts,
|
|
387
|
+
fetch: () => fetchSha({ targetPath, remoteName: "origin", remoteUrl, sha, fetchRef, timeoutMs, env: githubAuthEnv(githubToken) }),
|
|
388
|
+
onAttempt: ({ attempt }) => { evidence.cache.githubFetchAttempts = attempt; },
|
|
389
|
+
onRetry: ({ attempt, limit, error }) => console.log(`buildchain: GitHub source fetch failed, retry ${attempt + 1}/${limit}: ${error.message}`),
|
|
390
|
+
shouldRetry: retryableGitFetchError,
|
|
391
|
+
});
|
|
392
|
+
evidence.cache.githubFetchAttempts = fetchResult.attempts;
|
|
393
|
+
} catch (error) {
|
|
394
|
+
evidence.durationMs = Date.now() - startedAt;
|
|
395
|
+
writeEvidence(path.resolve(workspace, diagnosticsPath), evidence);
|
|
396
|
+
throw error;
|
|
397
|
+
}
|
|
364
398
|
checkoutFetchedCommit(targetPath, sha, timeoutMs);
|
|
365
399
|
}
|
|
366
400
|
evidence.verification = verifyCheckout({ targetPath, sourceSha: sha, sourceTreeSha: treeSha });
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { pathToFileURL } from "node:url";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
3
5
|
import { writePublicationArtifact } from "../packages/core/publication-artifact.js";
|
|
4
6
|
|
|
5
7
|
function readFlag(args, name, fallback = "") {
|
|
@@ -11,16 +13,32 @@ function readBooleanFlag(args, name) {
|
|
|
11
13
|
return args.includes(`--${name}`);
|
|
12
14
|
}
|
|
13
15
|
|
|
16
|
+
function readRegistryInputs(args, cwd) {
|
|
17
|
+
const inputs = [];
|
|
18
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
19
|
+
if (args[index] === "--registry-input" && args[index + 1]) inputs.push(args[index + 1]);
|
|
20
|
+
}
|
|
21
|
+
const inputDir = readFlag(args, "registry-input-dir", "");
|
|
22
|
+
if (inputDir) {
|
|
23
|
+
const resolved = path.resolve(cwd, inputDir);
|
|
24
|
+
if (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory()) throw new Error(`publication registry input directory is missing: ${inputDir}`);
|
|
25
|
+
inputs.push(...fs.readdirSync(resolved).filter((entry) => entry.endsWith(".json")).sort().map((entry) => path.join(resolved, entry)));
|
|
26
|
+
}
|
|
27
|
+
return inputs;
|
|
28
|
+
}
|
|
29
|
+
|
|
14
30
|
export function runPublicationArtifactCli(args = process.argv.slice(2)) {
|
|
15
31
|
const [mode = "manifest"] = args;
|
|
16
32
|
if (!["manifest", "collect"].includes(mode)) {
|
|
17
|
-
throw new Error("usage: buildchain publication-artifact manifest [--cwd <dir>] [--source-sha <sha>] [--output <file>] [--passport-output <file>] [--registry-output <file>] [--source-bundle <file>] [--no-source-bundle] [--json]");
|
|
33
|
+
throw new Error("usage: buildchain publication-artifact manifest [--cwd <dir>] [--source-sha <sha>] [--output <file>] [--passport-output <file>] [--registry-output <file>] [--registry-input <file>] [--registry-input-dir <dir>] [--source-bundle <file>] [--no-source-bundle] [--json]");
|
|
18
34
|
}
|
|
35
|
+
const cwd = readFlag(args, "cwd", process.cwd());
|
|
19
36
|
const result = writePublicationArtifact({
|
|
20
|
-
cwd
|
|
37
|
+
cwd,
|
|
21
38
|
output: readFlag(args, "output", ""),
|
|
22
39
|
passportOutput: readFlag(args, "passport-output", ""),
|
|
23
40
|
registryOutput: readFlag(args, "registry-output", ""),
|
|
41
|
+
registryInputs: readRegistryInputs(args, cwd),
|
|
24
42
|
sourceSha: readFlag(args, "source-sha", ""),
|
|
25
43
|
sourceBundlePath: readFlag(args, "source-bundle", ""),
|
|
26
44
|
sourceBundle: !readBooleanFlag(args, "no-source-bundle"),
|