mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-27 09:21:35 +07:00
Release: fix npm release preflight under pnpm (#52985)
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { basename } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
type PackageJson = {
|
||||
@@ -40,7 +41,6 @@ const CORRECTION_TAG_REGEX = /^(?<base>\d{4}\.[1-9]\d?\.[1-9]\d?)-(?<correction>
|
||||
const EXPECTED_REPOSITORY_URL = "https://github.com/openclaw/openclaw";
|
||||
const MAX_CALVER_DISTANCE_DAYS = 2;
|
||||
const REQUIRED_PACKED_PATHS = ["dist/control-ui/index.html"];
|
||||
const NPM_COMMAND = process.platform === "win32" ? "npm.cmd" : "npm";
|
||||
|
||||
function normalizeRepoUrl(value: unknown): string {
|
||||
if (typeof value !== "string") {
|
||||
@@ -288,15 +288,31 @@ function loadPackageJson(): PackageJson {
|
||||
return JSON.parse(readFileSync("package.json", "utf8")) as PackageJson;
|
||||
}
|
||||
|
||||
function runNpmCommand(args: string[]): string {
|
||||
const npmExecPath = process.env.npm_execpath;
|
||||
if (typeof npmExecPath === "string" && npmExecPath.length > 0) {
|
||||
return execFileSync(process.execPath, [npmExecPath, ...args], {
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
function isNpmExecPath(value: string): boolean {
|
||||
return /^npm(?:-cli)?(?:\.(?:c?js|cmd|exe))?$/.test(basename(value).toLowerCase());
|
||||
}
|
||||
|
||||
export function resolveNpmCommandInvocation(
|
||||
params: {
|
||||
npmExecPath?: string;
|
||||
nodeExecPath?: string;
|
||||
platform?: NodeJS.Platform;
|
||||
} = {},
|
||||
): { command: string; args: string[] } {
|
||||
const npmExecPath = params.npmExecPath ?? process.env.npm_execpath;
|
||||
const nodeExecPath = params.nodeExecPath ?? process.execPath;
|
||||
const npmCommand = (params.platform ?? process.platform) === "win32" ? "npm.cmd" : "npm";
|
||||
|
||||
if (typeof npmExecPath === "string" && npmExecPath.length > 0 && isNpmExecPath(npmExecPath)) {
|
||||
return { command: nodeExecPath, args: [npmExecPath] };
|
||||
}
|
||||
return execFileSync(NPM_COMMAND, args, {
|
||||
|
||||
return { command: npmCommand, args: [] };
|
||||
}
|
||||
|
||||
function runNpmCommand(args: string[]): string {
|
||||
const invocation = resolveNpmCommandInvocation();
|
||||
return execFileSync(invocation.command, [...invocation.args, ...args], {
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
@@ -312,16 +328,16 @@ type NpmPackResult = {
|
||||
};
|
||||
|
||||
type ExecFailure = Error & {
|
||||
stderr?: string | Buffer;
|
||||
stdout?: string | Buffer;
|
||||
stderr?: string | Uint8Array;
|
||||
stdout?: string | Uint8Array;
|
||||
};
|
||||
|
||||
function toTrimmedUtf8(value: string | Buffer | undefined): string {
|
||||
function toTrimmedUtf8(value: string | Uint8Array | undefined): string {
|
||||
if (typeof value === "string") {
|
||||
return value.trim();
|
||||
}
|
||||
if (Buffer.isBuffer(value)) {
|
||||
return value.toString("utf8").trim();
|
||||
if (value instanceof Uint8Array) {
|
||||
return new TextDecoder().decode(value).trim();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@@ -343,6 +359,32 @@ function describeExecFailure(error: unknown): string {
|
||||
return details.join(" | ");
|
||||
}
|
||||
|
||||
export function parseNpmPackJsonOutput(stdout: string): NpmPackResult[] | null {
|
||||
const trimmed = stdout.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const candidates = [trimmed];
|
||||
const trailingArrayStart = trimmed.lastIndexOf("\n[");
|
||||
if (trailingArrayStart !== -1) {
|
||||
candidates.push(trimmed.slice(trailingArrayStart + 1).trim());
|
||||
}
|
||||
|
||||
for (const candidate of candidates) {
|
||||
try {
|
||||
const parsed = JSON.parse(candidate) as unknown;
|
||||
if (Array.isArray(parsed)) {
|
||||
return parsed as NpmPackResult[];
|
||||
}
|
||||
} catch {
|
||||
// Try the next candidate. npm lifecycle output can prepend non-JSON logs.
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function collectPackedTarballErrors(): string[] {
|
||||
const errors: string[] = [];
|
||||
let stdout = "";
|
||||
@@ -356,15 +398,11 @@ function collectPackedTarballErrors(): string[] {
|
||||
return errors;
|
||||
}
|
||||
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(stdout);
|
||||
} catch {
|
||||
const packResults = parseNpmPackJsonOutput(stdout);
|
||||
if (!packResults) {
|
||||
errors.push("Failed to parse JSON output from `npm pack --json --dry-run`.");
|
||||
return errors;
|
||||
}
|
||||
|
||||
const packResults = Array.isArray(parsed) ? (parsed as NpmPackResult[]) : [];
|
||||
const firstResult = packResults[0];
|
||||
if (!firstResult || !Array.isArray(firstResult.files)) {
|
||||
errors.push("`npm pack --json --dry-run` did not return a files list to validate.");
|
||||
|
||||
Reference in New Issue
Block a user