fix(release): tolerate npm propagation after publish

This commit is contained in:
Vincent Koc
2026-06-16 09:51:42 +08:00
parent 3c65127827
commit e71cf0ffcb
5 changed files with 106 additions and 9 deletions

View File

@@ -39,6 +39,7 @@ export type NpmViewFields = {
version?: string;
distTagVersion?: string;
integrity?: string;
tarball?: string;
};
type WorkflowRunSummary = {
@@ -52,6 +53,10 @@ const DEFAULT_REPO = "openclaw/openclaw";
const DEFAULT_CLAWHUB_REGISTRY = "https://clawhub.ai";
const CLAWHUB_REQUEST_TIMEOUT_MS = 20_000;
const CLAWHUB_RESPONSE_BODY_MAX_BYTES = 1024 * 1024;
// Trusted publish can finish before npm registry metadata converges. Keep the
// verifier on the same release train instead of forcing a republish/correction.
const NPM_VIEW_ATTEMPTS = 30;
const NPM_VIEW_RETRY_MAX_DELAY_MS = 10_000;
function isRecord(value: unknown): value is JsonRecord {
return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -83,6 +88,35 @@ function runCommandInherited(command: string, args: string[]): void {
});
}
export async function runNpmViewWithRetry(
args: string[],
options: {
attempts?: number;
delay?: (delayMs: number) => Promise<void>;
run?: (args: string[]) => string;
} = {},
): Promise<string> {
const attempts = options.attempts ?? NPM_VIEW_ATTEMPTS;
const delay =
options.delay ??
((delayMs: number) => new Promise((resolveDelay) => setTimeout(resolveDelay, delayMs)));
const run = options.run ?? ((npmArgs: string[]) => runCommand("npm", npmArgs));
let lastError: unknown;
for (let attempt = 1; attempt <= attempts; attempt += 1) {
try {
return run([...args, "--prefer-online"]);
} catch (error) {
lastError = error;
}
if (attempt < attempts) {
await delay(Math.min(attempt * 1000, NPM_VIEW_RETRY_MAX_DELAY_MS));
}
}
throw lastError;
}
function parseJson(raw: string, label: string): unknown {
try {
return JSON.parse(raw) as unknown;
@@ -99,6 +133,7 @@ export function parseNpmViewFields(raw: string, distTag: string): NpmViewFields
version: readString(parsed[0]),
distTagVersion: readString(parsed[1]),
integrity: readString(parsed[2]),
tarball: readString(parsed[3]),
};
}
if (!isRecord(parsed)) {
@@ -110,6 +145,7 @@ export function parseNpmViewFields(raw: string, distTag: string): NpmViewFields
version: readString(parsed.version),
distTagVersion: readString(parsed[`dist-tags.${distTag}`]) ?? readString(distTags?.[distTag]),
integrity: readString(parsed["dist.integrity"]) ?? readString(dist?.integrity),
tarball: readString(parsed["dist.tarball"]) ?? readString(dist?.tarball),
};
}
@@ -269,13 +305,18 @@ async function fetchStatusWithRetry(url: string, method: "GET" | "HEAD"): Promis
return response.status;
}
function verifyNpmPackage(packageName: string, version: string, distTag: string): NpmViewFields {
const raw = runCommand("npm", [
async function verifyNpmPackage(
packageName: string,
version: string,
distTag: string,
): Promise<NpmViewFields> {
const raw = await runNpmViewWithRetry([
"view",
`${packageName}@${version}`,
"version",
`dist-tags.${distTag}`,
"dist.integrity",
"dist.tarball",
"--json",
]);
const fields = parseNpmViewFields(raw, distTag);
@@ -292,6 +333,9 @@ function verifyNpmPackage(packageName: string, version: string, distTag: string)
if (fields.integrity === undefined) {
throw new Error(`${packageName}: npm dist.integrity missing for ${version}.`);
}
if (fields.tarball === undefined) {
throw new Error(`${packageName}: npm dist.tarball missing for ${version}.`);
}
return fields;
}
@@ -500,7 +544,7 @@ export async function verifyBetaRelease(
lines.push(`GitHub release OK: ${releaseUrl}`);
}
const openclawNpm = verifyNpmPackage("openclaw", args.version, args.distTag);
const openclawNpm = await verifyNpmPackage("openclaw", args.version, args.distTag);
lines.push(`openclaw npm OK: ${args.version} (${args.distTag})`);
if (!args.skipPostpublish) {
@@ -522,7 +566,7 @@ export async function verifyBetaRelease(
packages: npmPlugins,
});
for (const plugin of npmPlugins) {
verifyNpmPackage(plugin.packageName, args.version, args.distTag);
await verifyNpmPackage(plugin.packageName, args.version, args.distTag);
}
lines.push(`plugin npm OK: ${npmPlugins.length}`);
@@ -644,6 +688,7 @@ export async function verifyBetaRelease(
npmDistTag: args.distTag,
pluginSelection: args.pluginSelection,
openclawNpmIntegrity: openclawNpm.integrity,
openclawNpmTarball: openclawNpm.tarball,
githubReleaseUrl: releaseUrl ?? null,
pluginNpmPackageCount: npmPlugins.length,
clawHubPackageCount: clawHubPlugins.length,