@kungfu-tech/buildchain 3.0.6-alpha.3 → 3.0.6-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.
- package/contracts/release-candidate-recovery-v1.schema.json +3 -2
- package/dist/site/buildchain-contract.json +6 -6
- package/dist/site/buildchain-site.json +9 -12
- package/dist/site/kfd-claims.json +2 -2
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +2 -2
- package/dist/site/node-api-registry.json +37 -7
- package/dist/site/page-registry.json +4 -4
- package/dist/site/public-surface-audit.json +1 -1
- package/dist/site/publication-registry.json +3 -6
- package/dist/site/site-manifest.json +5 -8
- package/docs/node-api-reference.md +8 -7
- package/docs/release-candidate.md +9 -0
- package/package.json +1 -1
- package/packages/core/release-candidate-recovery.js +78 -13
- package/scripts/check-inventory.mjs +3 -1
- package/scripts/publication-candidate-sealer.mjs +104 -0
- package/scripts/release-candidate-resolver.mjs +28 -0
- package/scripts/resume-from-candidate-run.mjs +49 -20
|
@@ -148,7 +148,11 @@ function candidateArtifactNames({ passport, selected, artifacts, artifactPattern
|
|
|
148
148
|
if (!reference.artifact) throw new Error(`Passport controller receipt ${reference.controllerId} has no artifact identity`);
|
|
149
149
|
names.add(reference.artifact);
|
|
150
150
|
}
|
|
151
|
-
for (const platform of passport.platformMatrix || [])
|
|
151
|
+
for (const platform of passport.platformMatrix || []) {
|
|
152
|
+
names.add(platform.artifactName);
|
|
153
|
+
const diagnosticsArtifactName = `${selected.prefix}-diagnostics-${platform.platformId}-${selected.sourceSha}`;
|
|
154
|
+
if (artifacts.some((artifact) => artifact.name === diagnosticsArtifactName)) names.add(diagnosticsArtifactName);
|
|
155
|
+
}
|
|
152
156
|
const manifestPattern = new RegExp(`^${selected.prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}-manifest-.+-${selected.sourceSha}$`);
|
|
153
157
|
for (const artifact of artifacts) {
|
|
154
158
|
if (manifestPattern.test(String(artifact.name || ""))) names.add(artifact.name);
|
|
@@ -162,18 +166,42 @@ function candidateArtifactNames({ passport, selected, artifacts, artifactPattern
|
|
|
162
166
|
|
|
163
167
|
function normalizePlatformManifests(downloads, passport) {
|
|
164
168
|
const manifests = [];
|
|
169
|
+
const evidenceByArtifact = new Map();
|
|
170
|
+
function addEvidence(artifactName, files) {
|
|
171
|
+
if (!artifactName) return;
|
|
172
|
+
const evidenceFiles = evidenceByArtifact.get(artifactName) || new Map();
|
|
173
|
+
for (const file of files) {
|
|
174
|
+
const existing = evidenceFiles.get(file.path);
|
|
175
|
+
if (existing && (existing.size !== file.size || existing.sha256 !== file.sha256)) {
|
|
176
|
+
throw new Error(`platform evidence disagrees for ${artifactName}/${file.path}`);
|
|
177
|
+
}
|
|
178
|
+
evidenceFiles.set(file.path, file);
|
|
179
|
+
}
|
|
180
|
+
evidenceByArtifact.set(artifactName, evidenceFiles);
|
|
181
|
+
}
|
|
165
182
|
for (const download of downloads) {
|
|
166
|
-
if (
|
|
167
|
-
for (const file of download.files.filter((entry) => path.basename(entry.path) === "manifest.json")) {
|
|
183
|
+
if (String(download.artifact.name).includes("-manifest-")) for (const file of download.files.filter((entry) => path.basename(entry.path) === "manifest.json")) {
|
|
168
184
|
const manifest = JSON.parse(fs.readFileSync(file.absolutePath, "utf8"));
|
|
169
185
|
if (!manifest.artifactName) {
|
|
170
186
|
const platformId = String(manifest.platform?.id || manifest.platformId || "");
|
|
171
187
|
manifest.artifactName = (passport.platformMatrix || []).find((entry) => entry.platformId === platformId)?.artifactName || "";
|
|
172
188
|
}
|
|
173
189
|
manifests.push(manifest);
|
|
190
|
+
addEvidence(manifest.artifactName, download.record.files);
|
|
191
|
+
}
|
|
192
|
+
if (String(download.artifact.name).includes("-diagnostics-")) {
|
|
193
|
+
const diagnosticsFiles = download.files.filter((entry) => path.basename(entry.path) === "diagnostics.json");
|
|
194
|
+
if (diagnosticsFiles.length === 1) {
|
|
195
|
+
const diagnostics = JSON.parse(fs.readFileSync(diagnosticsFiles[0].absolutePath, "utf8"));
|
|
196
|
+
addEvidence(String(diagnostics.links?.artifactName || ""), download.record.files);
|
|
197
|
+
}
|
|
174
198
|
}
|
|
175
199
|
}
|
|
176
|
-
|
|
200
|
+
const evidence = [...evidenceByArtifact].map(([artifactName, files]) => ({
|
|
201
|
+
artifactName,
|
|
202
|
+
files: [...files.values()].sort((left, right) => left.path.localeCompare(right.path)),
|
|
203
|
+
}));
|
|
204
|
+
return { manifests, evidence };
|
|
177
205
|
}
|
|
178
206
|
|
|
179
207
|
function normalizeControllerReceipts(downloads, passport) {
|
|
@@ -258,9 +286,6 @@ async function recoverCandidateEvidence({
|
|
|
258
286
|
for (const artifact of [selected.passport, selected.summary]) initialDownloads.push(await downloadArtifact({ artifact, repoInfo, apiUrl, token, archiveDir, bundleRoot, fetchImpl }));
|
|
259
287
|
const passport = readOnlyJson(initialDownloads[0].files.filter((file) => path.basename(file.path) === "release-candidate-passport.json"), "release-candidate-passport.json");
|
|
260
288
|
const buildSummary = readOnlyJson(initialDownloads[1].files.filter((file) => path.basename(file.path) === "build-summary.json"), "build-summary.json");
|
|
261
|
-
const candidateVersion = String(passport.target?.version || "").trim();
|
|
262
|
-
if (!candidateVersion) throw new Error("Release Candidate Passport has no exact target version");
|
|
263
|
-
const existingTransaction = await readExistingTransaction({ repoInfo, apiUrl, token, fetchImpl, version: candidateVersion });
|
|
264
289
|
const requiredNames = candidateArtifactNames({ passport, selected, artifacts, artifactPatterns });
|
|
265
290
|
const chosen = artifacts.filter((artifact) => requiredNames.has(artifact.name));
|
|
266
291
|
if (chosen.length !== requiredNames.size) {
|
|
@@ -272,7 +297,7 @@ async function recoverCandidateEvidence({
|
|
|
272
297
|
for (const artifact of chosen.filter((entry) => ![selected.passport.id, selected.summary.id].includes(entry.id))) downloads.push(await downloadArtifact({ artifact, repoInfo, apiUrl, token, archiveDir, bundleRoot, fetchImpl }));
|
|
273
298
|
return {
|
|
274
299
|
run, workflow, selected, resolvedOutput, bundleRoot, initialDownloads,
|
|
275
|
-
passport, buildSummary,
|
|
300
|
+
passport, buildSummary, chosen, downloads,
|
|
276
301
|
};
|
|
277
302
|
}
|
|
278
303
|
|
|
@@ -311,7 +336,7 @@ export async function resumeFromCandidateRun({
|
|
|
311
336
|
try {
|
|
312
337
|
const {
|
|
313
338
|
run, workflow, selected, resolvedOutput, bundleRoot, initialDownloads,
|
|
314
|
-
passport, buildSummary,
|
|
339
|
+
passport, buildSummary, chosen, downloads,
|
|
315
340
|
} = await recoverCandidateEvidence({
|
|
316
341
|
repoInfo, runId, artifactName, artifactPatterns, requiredArtifactCount,
|
|
317
342
|
outputDir, apiUrl, token, fetchImpl, archiveDir,
|
|
@@ -323,9 +348,20 @@ export async function resumeFromCandidateRun({
|
|
|
323
348
|
const targetRefState = await githubJson({ apiUrl, token, fetchImpl, path: `/repos/${repoInfo.owner}/${repoInfo.repo}/git/ref/heads/${targetRef.replace(/^refs\/heads\//, "")}` });
|
|
324
349
|
if (targetRefState.object?.sha !== targetSha) throw new Error(`target ref ${targetRef} no longer points at ${targetSha}`);
|
|
325
350
|
const compare = await githubJson({ apiUrl, token, fetchImpl, path: `/repos/${repoInfo.owner}/${repoInfo.repo}/compare/${pullRequest.merge_commit_sha}...${targetSha}` });
|
|
326
|
-
const
|
|
351
|
+
const platformManifestEvidence = normalizePlatformManifests(downloads, passport);
|
|
327
352
|
const controllerReceipts = normalizeControllerReceipts(downloads, passport);
|
|
328
353
|
const productPayloadManifests = normalizeProductPayloadManifests(downloads);
|
|
354
|
+
const sealed = createSealedBundle({
|
|
355
|
+
downloads,
|
|
356
|
+
bundleRoot,
|
|
357
|
+
repository: repoInfo.fullName,
|
|
358
|
+
passport,
|
|
359
|
+
runtimeSha,
|
|
360
|
+
publishPackageMain,
|
|
361
|
+
releasePatterns,
|
|
362
|
+
});
|
|
363
|
+
const candidateVersion = sealed.manifest.npm.version;
|
|
364
|
+
const existingTransaction = await readExistingTransaction({ repoInfo, apiUrl, token, fetchImpl, version: candidateVersion });
|
|
329
365
|
const recovery = verifyReleaseCandidateRecovery({
|
|
330
366
|
candidateRepository: repoInfo.fullName,
|
|
331
367
|
targetRepository,
|
|
@@ -369,21 +405,14 @@ export async function resumeFromCandidateRun({
|
|
|
369
405
|
passport,
|
|
370
406
|
buildSummary,
|
|
371
407
|
controllerReceipts,
|
|
372
|
-
platformManifests,
|
|
408
|
+
platformManifests: platformManifestEvidence.manifests,
|
|
409
|
+
platformManifestEvidence: platformManifestEvidence.evidence,
|
|
373
410
|
productPayloadManifests,
|
|
374
411
|
artifacts: downloads.map((download) => download.record),
|
|
412
|
+
publicationVersion: candidateVersion,
|
|
375
413
|
currentToolingSha: runtimeSha,
|
|
376
414
|
recoveryRunId,
|
|
377
415
|
});
|
|
378
|
-
const sealed = createSealedBundle({
|
|
379
|
-
downloads,
|
|
380
|
-
bundleRoot,
|
|
381
|
-
repository: repoInfo.fullName,
|
|
382
|
-
passport,
|
|
383
|
-
runtimeSha,
|
|
384
|
-
publishPackageMain,
|
|
385
|
-
releasePatterns,
|
|
386
|
-
});
|
|
387
416
|
const recoveryReceiptPath = path.join(resolvedOutput, "recovery-receipt.json");
|
|
388
417
|
const sealedManifestPath = path.join(resolvedOutput, "sealed-bundle.json");
|
|
389
418
|
const requiredArtifactsPath = path.join(resolvedOutput, "publish-required-artifacts.json");
|