Files
rea/scripts/ci/verified-package.mjs
gensokyo-dev 5d6b8a0ab6 fix(release): isolate npm publication from dependency execution (#6681)
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.
2026-10-23 12:27:24 +00:00

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);
}