@kungfu-tech/buildchain 2.14.13 → 2.14.14-alpha.1
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 +4 -4
- package/dist/site/buildchain-site.json +13 -13
- package/dist/site/kfd-claims.json +2 -2
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +3 -3
- package/dist/site/page-registry.json +6 -6
- package/dist/site/public-surface-audit.json +1 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +7 -7
- package/docs/migration-inventory.md +1 -1
- package/docs/reusable-build-surface.md +1 -1
- package/docs/web-surface-deployments.md +6 -1
- package/package.json +1 -1
- package/scripts/web-surface-cloudfront-rewrite.mjs +100 -53
|
@@ -3690,7 +3690,7 @@
|
|
|
3690
3690
|
"pageRegistryPath": "dist/site/page-registry.json",
|
|
3691
3691
|
"cliRegistryDigest": "90c8650f870c350ca187c612c37f2459dcbfe265aa9b1d5b375ee5c4fb31fab6",
|
|
3692
3692
|
"workflowRegistryDigest": "02950eef2769b1bd2d899099ef23c7b9ae716b18d602e25aef169d788bc6ece0",
|
|
3693
|
-
"pageRegistryDigest": "
|
|
3693
|
+
"pageRegistryDigest": "2121b86c6c11ad27c69b5b00bda989c1203ddb3f7cb4a864df375a62e13fced6"
|
|
3694
3694
|
},
|
|
3695
3695
|
"comparison": {
|
|
3696
3696
|
"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-21T22:45:53.347Z",
|
|
5
|
+
"publishedAt": "2026-07-21T22:45:53.347Z",
|
|
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": "b72c725311d1e37adc98b25a4555bb61ca8023ca",
|
|
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.14.
|
|
35
|
+
"version": "2.14.14-alpha.1",
|
|
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-21T22:45:53.347Z",
|
|
5
|
+
"publishedAt": "2026-07-21T22:45:53.347Z",
|
|
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": "b72c725311d1e37adc98b25a4555bb61ca8023ca",
|
|
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.14.
|
|
40
|
+
"version": "2.14.14-alpha.1",
|
|
41
41
|
"versionSource": "package.json#version"
|
|
42
42
|
},
|
|
43
43
|
"entrypoint": "buildchain-site.json",
|
|
@@ -165,7 +165,7 @@
|
|
|
165
165
|
"path": "docs/migration-inventory.md",
|
|
166
166
|
"plane": "verify",
|
|
167
167
|
"exists": true,
|
|
168
|
-
"digest": "sha256:
|
|
168
|
+
"digest": "sha256:18f98635bb16a4e43cac2d46443b3e812c305f708f071929dc6a46a8cd5dccd5"
|
|
169
169
|
},
|
|
170
170
|
{
|
|
171
171
|
"id": "ownership",
|
|
@@ -221,7 +221,7 @@
|
|
|
221
221
|
"path": "docs/reusable-build-surface.md",
|
|
222
222
|
"plane": "use",
|
|
223
223
|
"exists": true,
|
|
224
|
-
"digest": "sha256:
|
|
224
|
+
"digest": "sha256:9c125f6d2b519c96718f45da62ac1a891ab618bcb331d3b14c4b88b5158fc4a9"
|
|
225
225
|
},
|
|
226
226
|
{
|
|
227
227
|
"id": "publish-transaction",
|
|
@@ -269,7 +269,7 @@
|
|
|
269
269
|
"path": "docs/web-surface-deployments.md",
|
|
270
270
|
"plane": "use",
|
|
271
271
|
"exists": true,
|
|
272
|
-
"digest": "sha256:
|
|
272
|
+
"digest": "sha256:efbfc97a947978e61c79dac9d67f87050b1bef2297617303340af8aa499703c9"
|
|
273
273
|
}
|
|
274
274
|
],
|
|
275
275
|
"facts": [
|
|
@@ -46,7 +46,7 @@ do not duplicate alpha and stable jobs or call
|
|
|
46
46
|
`.release-candidate-promote.yml` directly.
|
|
47
47
|
|
|
48
48
|
Buildchain also keeps the workflow-file layout transition declarative in
|
|
49
|
-
`.buildchain/promotion-shell-routing.json`. Stable `v2.14.
|
|
49
|
+
`.buildchain/promotion-shell-routing.json`. Stable `v2.14.13` contains the hidden
|
|
50
50
|
advanced workflow, so the stable lane is pinned to that implementation's
|
|
51
51
|
immutable release SHA and forwards the full internal identity surface. This is
|
|
52
52
|
provider-owned compatibility state; consumers still keep the same single public
|
|
@@ -754,7 +754,7 @@ promotion starts:
|
|
|
754
754
|
`vN`, and `buildchain-stable-contract-lock-path`.
|
|
755
755
|
|
|
756
756
|
The generated router also owns the stable-shell layout transition through
|
|
757
|
-
`.buildchain/promotion-shell-routing.json`. Stable `v2.14.
|
|
757
|
+
`.buildchain/promotion-shell-routing.json`. Stable `v2.14.13` contains the hidden
|
|
758
758
|
advanced workflow, so the stable lane calls that workflow at the exact immutable
|
|
759
759
|
SHA behind the released `v2` state and forwards the complete internal promotion
|
|
760
760
|
identity surface. The logical shell identity remains `vN`, and the router
|
|
@@ -456,7 +456,12 @@ compatibility evidence, but root correctness comes from the viewer-request
|
|
|
456
456
|
rewrite contract, not from extensionless S3 keys. If Buildchain-managed mode
|
|
457
457
|
finds a distribution with a different viewer-request function, apply fails
|
|
458
458
|
closed and records that conflict in the apply result instead of silently serving
|
|
459
|
-
403s.
|
|
459
|
+
403s. If CloudFront rejects `UpdateDistribution` because its optimistic-lock
|
|
460
|
+
ETag became stale, Buildchain re-reads the distribution and retries with the
|
|
461
|
+
new ETag at most twice. A concurrent update that already attached the intended
|
|
462
|
+
function is accepted as converged; a different viewer-request function or any
|
|
463
|
+
non-ETag AWS error still fails immediately. The reusable workflow uploads
|
|
464
|
+
`buildchain-web-surface-*-diagnostics`
|
|
460
465
|
artifacts containing the apply and health JSON so the failing AWS operation or
|
|
461
466
|
HTTP check is visible from the consumer run.
|
|
462
467
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kungfu-tech/buildchain",
|
|
3
|
-
"version": "2.14.
|
|
3
|
+
"version": "2.14.14-alpha.1",
|
|
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",
|
|
@@ -15,6 +15,9 @@ const FUNCTION_CODE = `function handler(event) {
|
|
|
15
15
|
}
|
|
16
16
|
`;
|
|
17
17
|
|
|
18
|
+
const DISTRIBUTION_UPDATE_MAX_ATTEMPTS = 3;
|
|
19
|
+
const DISTRIBUTION_UPDATE_RETRY_DELAY_MS = 250;
|
|
20
|
+
|
|
18
21
|
function readArg(name, fallback = "") {
|
|
19
22
|
const flag = `--${name}`;
|
|
20
23
|
const index = process.argv.indexOf(flag);
|
|
@@ -31,7 +34,7 @@ function runAws(args, { allowFailure = false } = {}) {
|
|
|
31
34
|
});
|
|
32
35
|
if (result.error) {
|
|
33
36
|
if (allowFailure) {
|
|
34
|
-
return { ok: false, stdout: "", stderr: String(result.error.message || result.error) };
|
|
37
|
+
return { ok: false, status: null, stdout: "", stderr: String(result.error.message || result.error) };
|
|
35
38
|
}
|
|
36
39
|
throw result.error;
|
|
37
40
|
}
|
|
@@ -39,11 +42,11 @@ function runAws(args, { allowFailure = false } = {}) {
|
|
|
39
42
|
const stderr = String(result.stderr || "");
|
|
40
43
|
if (result.status !== 0) {
|
|
41
44
|
if (allowFailure) {
|
|
42
|
-
return { ok: false, stdout, stderr };
|
|
45
|
+
return { ok: false, status: result.status, stdout, stderr };
|
|
43
46
|
}
|
|
44
47
|
throw new Error(`aws ${args.join(" ")} failed with exit code ${result.status}${stderr.trim() ? `: ${stderr.trim()}` : ""}`);
|
|
45
48
|
}
|
|
46
|
-
return { ok: true, stdout, stderr };
|
|
49
|
+
return { ok: true, status: result.status, stdout, stderr };
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
function runAwsJson(args, options = {}) {
|
|
@@ -58,6 +61,22 @@ function functionConfig(comment) {
|
|
|
58
61
|
return `Comment=${comment.replace(/,/g, " ")},Runtime=cloudfront-js-2.0`;
|
|
59
62
|
}
|
|
60
63
|
|
|
64
|
+
function awsFailureMessage(args, result) {
|
|
65
|
+
const status = result.status === null || result.status === undefined
|
|
66
|
+
? ""
|
|
67
|
+
: ` with exit code ${result.status}`;
|
|
68
|
+
const stderr = String(result.stderr || "").trim();
|
|
69
|
+
return `aws ${args.join(" ")} failed${status}${stderr ? `: ${stderr}` : ""}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isStaleDistributionEtag(result) {
|
|
73
|
+
return result.ok === false && /\bPreconditionFailed\b/.test(String(result.stderr || ""));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function sleepSync(milliseconds) {
|
|
77
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
|
|
78
|
+
}
|
|
79
|
+
|
|
61
80
|
function writeFunctionCode(tmpDir) {
|
|
62
81
|
const file = path.join(tmpDir, "directory-index-rewrite.js");
|
|
63
82
|
fs.writeFileSync(file, FUNCTION_CODE);
|
|
@@ -130,57 +149,85 @@ function publishFunction({ name, etag }) {
|
|
|
130
149
|
}
|
|
131
150
|
|
|
132
151
|
function attachFunction({ distributionId, functionArn }) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
152
|
+
let previousViewerRequestFunction;
|
|
153
|
+
for (let attempt = 1; attempt <= DISTRIBUTION_UPDATE_MAX_ATTEMPTS; attempt += 1) {
|
|
154
|
+
const current = runAwsJson(["cloudfront", "get-distribution-config", "--id", distributionId]);
|
|
155
|
+
const etag = current.data?.ETag || "";
|
|
156
|
+
const config = current.data?.DistributionConfig;
|
|
157
|
+
if (!etag || !config) {
|
|
158
|
+
throw new Error(`cloudfront get-distribution-config did not return ETag and DistributionConfig for ${distributionId}`);
|
|
159
|
+
}
|
|
160
|
+
const defaultBehavior = config.DefaultCacheBehavior || {};
|
|
161
|
+
const existing = defaultBehavior.FunctionAssociations || { Quantity: 0, Items: [] };
|
|
162
|
+
const items = Array.isArray(existing.Items) ? [...existing.Items] : [];
|
|
163
|
+
const currentViewer = items.find((item) => item.EventType === "viewer-request");
|
|
164
|
+
if (previousViewerRequestFunction === undefined) {
|
|
165
|
+
previousViewerRequestFunction = currentViewer?.FunctionARN || "";
|
|
166
|
+
}
|
|
167
|
+
if (currentViewer && currentViewer.FunctionARN && currentViewer.FunctionARN !== functionArn) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
`CloudFront distribution ${distributionId} already has a different viewer-request function ${currentViewer.FunctionARN}; ` +
|
|
170
|
+
"remove or migrate it before Buildchain can manage directory-index rewrites",
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
if (currentViewer?.FunctionARN === functionArn) {
|
|
174
|
+
return {
|
|
175
|
+
previousViewerRequestFunction,
|
|
176
|
+
nextViewerRequestFunction: functionArn,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const nextItems = [
|
|
181
|
+
...items.filter((item) => item.EventType !== "viewer-request"),
|
|
182
|
+
{
|
|
183
|
+
EventType: "viewer-request",
|
|
184
|
+
FunctionARN: functionArn,
|
|
185
|
+
},
|
|
186
|
+
];
|
|
187
|
+
config.DefaultCacheBehavior = {
|
|
188
|
+
...defaultBehavior,
|
|
189
|
+
FunctionAssociations: {
|
|
190
|
+
Quantity: nextItems.length,
|
|
191
|
+
Items: nextItems,
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "buildchain-cloudfront-distribution-"));
|
|
195
|
+
let args;
|
|
196
|
+
let updated;
|
|
197
|
+
try {
|
|
198
|
+
const configFile = path.join(tmpDir, "distribution-config.json");
|
|
199
|
+
fs.writeFileSync(configFile, `${JSON.stringify(config, null, 2)}\n`);
|
|
200
|
+
args = [
|
|
201
|
+
"cloudfront",
|
|
202
|
+
"update-distribution",
|
|
203
|
+
"--id",
|
|
204
|
+
distributionId,
|
|
205
|
+
"--if-match",
|
|
206
|
+
etag,
|
|
207
|
+
"--distribution-config",
|
|
208
|
+
`file://${configFile}`,
|
|
209
|
+
];
|
|
210
|
+
updated = runAws(args, { allowFailure: true });
|
|
211
|
+
} finally {
|
|
212
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
213
|
+
}
|
|
214
|
+
if (updated.ok) {
|
|
215
|
+
return {
|
|
216
|
+
previousViewerRequestFunction,
|
|
217
|
+
nextViewerRequestFunction: functionArn,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
if (!isStaleDistributionEtag(updated)) {
|
|
221
|
+
throw new Error(awsFailureMessage(args, updated));
|
|
222
|
+
}
|
|
223
|
+
if (attempt === DISTRIBUTION_UPDATE_MAX_ATTEMPTS) {
|
|
224
|
+
throw new Error(
|
|
225
|
+
`CloudFront update-distribution still failed after ${attempt} attempts: ${awsFailureMessage(args, updated)}`,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
sleepSync(DISTRIBUTION_UPDATE_RETRY_DELAY_MS * attempt);
|
|
179
229
|
}
|
|
180
|
-
|
|
181
|
-
previousViewerRequestFunction: currentViewer?.FunctionARN || "",
|
|
182
|
-
nextViewerRequestFunction: functionArn,
|
|
183
|
-
};
|
|
230
|
+
throw new Error(`CloudFront update-distribution retry loop ended unexpectedly for ${distributionId}`);
|
|
184
231
|
}
|
|
185
232
|
|
|
186
233
|
export function ensureCloudFrontDirectoryIndexRewrite({
|