mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 12:14:46 +00:00
fix(release): verify npm tarball before publish
This commit is contained in:
107
scripts/openclaw-npm-prepublish-verify.ts
Normal file
107
scripts/openclaw-npm-prepublish-verify.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env -S node --import tsx
|
||||
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { mkdtempSync, readFileSync, realpathSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { formatErrorMessage } from "../src/infra/errors.ts";
|
||||
import { runInstalledWorkspaceBootstrapSmoke } from "./lib/workspace-bootstrap-smoke.mjs";
|
||||
import {
|
||||
collectInstalledPackageErrors,
|
||||
normalizeInstalledBinaryVersion,
|
||||
resolveInstalledBinaryPath,
|
||||
} from "./openclaw-npm-postpublish-verify.ts";
|
||||
import { resolveNpmCommandInvocation } from "./openclaw-npm-release-check.ts";
|
||||
|
||||
type InstalledPackageJson = {
|
||||
version?: string;
|
||||
};
|
||||
|
||||
function npmExec(args: string[], cwd: string): string {
|
||||
const invocation = resolveNpmCommandInvocation({
|
||||
npmExecPath: process.env.npm_execpath,
|
||||
nodeExecPath: process.execPath,
|
||||
platform: process.platform,
|
||||
});
|
||||
|
||||
return execFileSync(invocation.command, [...invocation.args, ...args], {
|
||||
cwd,
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
}).trim();
|
||||
}
|
||||
|
||||
function main(): void {
|
||||
const tarballPath = process.argv[2]?.trim();
|
||||
const expectedVersion = process.argv[3]?.trim();
|
||||
if (!tarballPath) {
|
||||
throw new Error(
|
||||
"Usage: node --import tsx scripts/openclaw-npm-prepublish-verify.ts <tarball.tgz> [expected-version]",
|
||||
);
|
||||
}
|
||||
|
||||
const workingDir = mkdtempSync(join(tmpdir(), "openclaw-prepublish-"));
|
||||
const prefixDir = join(workingDir, "prefix");
|
||||
try {
|
||||
npmExec(
|
||||
[
|
||||
"install",
|
||||
"-g",
|
||||
"--prefix",
|
||||
prefixDir,
|
||||
realpathSync(tarballPath),
|
||||
"--no-fund",
|
||||
"--no-audit",
|
||||
],
|
||||
workingDir,
|
||||
);
|
||||
const globalRoot = npmExec(["root", "-g", "--prefix", prefixDir], workingDir);
|
||||
const packageRoot = join(globalRoot, "openclaw");
|
||||
const pkg = JSON.parse(
|
||||
readFileSync(join(packageRoot, "package.json"), "utf8"),
|
||||
) as InstalledPackageJson;
|
||||
const resolvedExpectedVersion = expectedVersion || pkg.version?.trim() || "";
|
||||
const errors = collectInstalledPackageErrors({
|
||||
expectedVersion: resolvedExpectedVersion,
|
||||
installedVersion: pkg.version?.trim() ?? "",
|
||||
packageRoot,
|
||||
});
|
||||
const installedBinaryVersion = execFileSync(
|
||||
resolveInstalledBinaryPath(prefixDir),
|
||||
["--version"],
|
||||
{
|
||||
cwd: workingDir,
|
||||
encoding: "utf8",
|
||||
shell: process.platform === "win32",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
},
|
||||
).trim();
|
||||
if (normalizeInstalledBinaryVersion(installedBinaryVersion) !== resolvedExpectedVersion) {
|
||||
errors.push(
|
||||
`installed openclaw binary version mismatch: expected ${resolvedExpectedVersion}, found ${installedBinaryVersion || "<missing>"}.`,
|
||||
);
|
||||
}
|
||||
if (errors.length === 0) {
|
||||
runInstalledWorkspaceBootstrapSmoke({ packageRoot });
|
||||
}
|
||||
if (errors.length > 0) {
|
||||
throw new Error(`prepared tarball install failed:\n- ${errors.join("\n- ")}`);
|
||||
}
|
||||
console.log(
|
||||
`openclaw-npm-prepublish-verify: prepared tarball install OK (${resolvedExpectedVersion}).`,
|
||||
);
|
||||
} finally {
|
||||
rmSync(workingDir, { force: true, recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
const entrypoint = process.argv[1] ? pathToFileURL(process.argv[1]).href : null;
|
||||
if (entrypoint !== null && import.meta.url === entrypoint) {
|
||||
try {
|
||||
main();
|
||||
} catch (error) {
|
||||
console.error(`openclaw-npm-prepublish-verify: ${formatErrorMessage(error)}`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,10 @@ import {
|
||||
runInstalledWorkspaceBootstrapSmoke,
|
||||
WORKSPACE_TEMPLATE_PACK_PATHS,
|
||||
} from "./lib/workspace-bootstrap-smoke.mjs";
|
||||
import {
|
||||
collectInstalledPackageErrors,
|
||||
normalizeInstalledBinaryVersion,
|
||||
} from "./openclaw-npm-postpublish-verify.ts";
|
||||
import { listStaticExtensionAssetOutputs } from "./runtime-postbuild.mjs";
|
||||
import { sparkleBuildFloorsFromShortVersion, type SparkleBuildFloors } from "./sparkle-build.ts";
|
||||
import { buildCmdExeCommandLine } from "./windows-cmd-helpers.mjs";
|
||||
@@ -330,6 +334,58 @@ function runPackedBundledPluginPostinstall(packageRoot: string): void {
|
||||
});
|
||||
}
|
||||
|
||||
export function collectPackedInstalledPackageVerificationErrors(params: {
|
||||
expectedVersion: string;
|
||||
installedBinaryVersion?: string;
|
||||
packageRoot: string;
|
||||
}): string[] {
|
||||
const packageJson = JSON.parse(
|
||||
readFileSync(join(params.packageRoot, "package.json"), "utf8"),
|
||||
) as { version?: string };
|
||||
const errors = collectInstalledPackageErrors({
|
||||
expectedVersion: params.expectedVersion,
|
||||
installedVersion: packageJson.version?.trim() ?? "",
|
||||
packageRoot: params.packageRoot,
|
||||
});
|
||||
if (
|
||||
params.installedBinaryVersion !== undefined &&
|
||||
normalizeInstalledBinaryVersion(params.installedBinaryVersion) !== params.expectedVersion
|
||||
) {
|
||||
errors.push(
|
||||
`installed openclaw binary version mismatch: expected ${params.expectedVersion}, found ${params.installedBinaryVersion || "<missing>"}.`,
|
||||
);
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
function verifyPackedInstalledPackage(params: {
|
||||
expectedVersion: string;
|
||||
packageRoot: string;
|
||||
prefixDir: string;
|
||||
tmpRoot: string;
|
||||
}): void {
|
||||
const installedBinaryVersion = execFileSync(
|
||||
resolveInstalledBinaryPath(params.prefixDir),
|
||||
["--version"],
|
||||
{
|
||||
cwd: params.tmpRoot,
|
||||
encoding: "utf8",
|
||||
shell: process.platform === "win32",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
},
|
||||
).trim();
|
||||
const errors = collectPackedInstalledPackageVerificationErrors({
|
||||
expectedVersion: params.expectedVersion,
|
||||
installedBinaryVersion,
|
||||
packageRoot: params.packageRoot,
|
||||
});
|
||||
if (errors.length > 0) {
|
||||
throw new Error(
|
||||
`release-check: packed installed package verification failed:\n- ${errors.join("\n- ")}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function writePackedBundledPluginActivationConfig(homeDir: string): void {
|
||||
const configPath = join(homeDir, ".openclaw", "openclaw.json");
|
||||
mkdirSync(join(homeDir, ".openclaw"), { recursive: true });
|
||||
@@ -464,6 +520,14 @@ function runPackedCliSmoke(params: {
|
||||
function runPackedBundledChannelEntrySmoke(): void {
|
||||
const tmpRoot = mkdtempSync(join(tmpdir(), "openclaw-release-pack-smoke-"));
|
||||
try {
|
||||
const expectedVersion = (
|
||||
JSON.parse(readFileSync(resolve("package.json"), "utf8")) as {
|
||||
version?: string;
|
||||
}
|
||||
).version;
|
||||
if (!expectedVersion) {
|
||||
throw new Error("release-check: root package.json is missing version.");
|
||||
}
|
||||
const packDir = join(tmpRoot, "pack");
|
||||
mkdirSync(packDir);
|
||||
|
||||
@@ -473,6 +537,12 @@ function runPackedBundledChannelEntrySmoke(): void {
|
||||
installPackedTarball(prefixDir, tarballPath, tmpRoot);
|
||||
|
||||
const packageRoot = join(resolveGlobalRoot(prefixDir, tmpRoot), "openclaw");
|
||||
verifyPackedInstalledPackage({
|
||||
expectedVersion,
|
||||
packageRoot,
|
||||
prefixDir,
|
||||
tmpRoot,
|
||||
});
|
||||
const homeDir = join(tmpRoot, "home");
|
||||
const stateDir = join(tmpRoot, "state");
|
||||
mkdirSync(homeDir, { recursive: true });
|
||||
|
||||
Reference in New Issue
Block a user