Keep dependency execution in read-only release jobs and validate the original qualified archive with Node built-ins before publication. Refresh registry state on every publisher attempt; retain exact bytes/channel verification, v1 read-only recovery, and installed CLI/MCP checks before MCP publication. Static/docs checks, artifact/registry/recovery tests and real package/rehearsal verification pass. All four hosted package platforms pass. Portable CI retains the independently reproduced main PTX failure in #6675 and fail-fast missing coverage; use the owner-authorized existing PR-only exception, with limitations documented in the PR. Refs #6480.
118 lines
3.5 KiB
JavaScript
118 lines
3.5 KiB
JavaScript
import { copyFile, writeFile } from "node:fs/promises";
|
|
import { join, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { valid } from "semver";
|
|
import { releaseNpmTag } from "../release-npm-tag.mjs";
|
|
import {
|
|
MANIFEST_NAME,
|
|
TARBALL_NAME,
|
|
createVerifiedPackageEnvelope,
|
|
parsePositiveInteger,
|
|
sha256File,
|
|
validateVerifiedPackageEnvelope,
|
|
workflowPackageIdentity,
|
|
} from "./qualified-package-envelope.mjs";
|
|
|
|
/** Qualification owns the exact SemVer and its parsed publication channel. */
|
|
export function createVerifiedPackageMetadata({
|
|
sourceSha,
|
|
runId,
|
|
runAttempt,
|
|
platform,
|
|
architecture,
|
|
packageVersion,
|
|
nodeVersion,
|
|
}) {
|
|
if (!/^[a-f0-9]{40}$/u.test(sourceSha))
|
|
throw new Error("Source SHA must be a full commit SHA");
|
|
return {
|
|
sourceSha,
|
|
runId: parsePositiveInteger(runId, "GITHUB_RUN_ID"),
|
|
runAttempt: parsePositiveInteger(runAttempt, "GITHUB_RUN_ATTEMPT"),
|
|
platform,
|
|
architecture,
|
|
packageVersion,
|
|
nodeVersion,
|
|
releaseTag: releaseNpmTag(packageVersion),
|
|
};
|
|
}
|
|
|
|
function validateReleaseIntent(manifest) {
|
|
if (valid(manifest.packageVersion) !== manifest.packageVersion)
|
|
throw new Error("Verified package manifest has an invalid package version");
|
|
if (
|
|
manifest.schemaVersion === 2 &&
|
|
releaseNpmTag(manifest.packageVersion) !== manifest.releaseTag
|
|
)
|
|
throw new Error("Verified package release tag mismatch");
|
|
}
|
|
|
|
function createVerifiedPackageManifest(metadata) {
|
|
const manifest = createVerifiedPackageEnvelope(metadata);
|
|
validateReleaseIntent(manifest);
|
|
return manifest;
|
|
}
|
|
|
|
/** Write only after qualification and its cleanup have completed. */
|
|
export async function writeVerifiedPackageManifest(directory, metadata) {
|
|
const manifest = createVerifiedPackageManifest(metadata);
|
|
await writeFile(
|
|
join(directory, MANIFEST_NAME),
|
|
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
"utf8",
|
|
);
|
|
return manifest;
|
|
}
|
|
|
|
/** Retain the exact archive without unpacking or rebuilding it. */
|
|
export async function retainVerifiedPackage({
|
|
sourceTarball,
|
|
directory,
|
|
metadata,
|
|
}) {
|
|
const tarball = join(directory, TARBALL_NAME);
|
|
await copyFile(sourceTarball, tarball);
|
|
return {
|
|
directory,
|
|
metadata: createVerifiedPackageManifest({
|
|
...metadata,
|
|
sha256: await sha256File(tarball),
|
|
}),
|
|
};
|
|
}
|
|
|
|
/** Read-only consumers additionally validate SemVer and its qualified channel. */
|
|
export async function validateVerifiedPackage(options) {
|
|
const { tarball, manifest } = await validateVerifiedPackageEnvelope(options);
|
|
validateReleaseIntent(manifest);
|
|
return tarball;
|
|
}
|
|
|
|
async function validateFromCli(directory) {
|
|
const expected = await workflowPackageIdentity();
|
|
const tarball = await validateVerifiedPackage({ directory, ...expected });
|
|
if (process.env.GITHUB_OUTPUT === undefined)
|
|
throw new Error(
|
|
"GITHUB_OUTPUT is required to publish the validated tarball path",
|
|
);
|
|
await writeFile(
|
|
process.env.GITHUB_OUTPUT,
|
|
`tarball=${tarball}\nversion=${expected.packageVersion}\n`,
|
|
{ encoding: "utf8", flag: "a" },
|
|
);
|
|
process.stdout.write(`${tarball}\n`);
|
|
}
|
|
|
|
if (
|
|
process.argv[1] !== undefined &&
|
|
resolve(process.argv[1]) === fileURLToPath(import.meta.url)
|
|
) {
|
|
const [command, directory, ...extra] = process.argv.slice(2);
|
|
if (command !== "validate" || directory === undefined || extra.length > 0) {
|
|
process.stderr.write(
|
|
"Usage: node scripts/ci/verified-package.mjs validate <artifact-directory>\n",
|
|
);
|
|
process.exitCode = 2;
|
|
} else await validateFromCli(directory);
|
|
}
|