Tests: type package contract npm pack helper

This commit is contained in:
Gustavo Madeira Santana 2026-03-29 23:10:47 -04:00
parent c52fac836c
commit e86a2183df
No known key found for this signature in database
1 changed files with 88 additions and 10 deletions

View File

@ -1,11 +1,10 @@
import { execFileSync } from "node:child_process";
import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync } from "node:fs";
import { execFileSync, spawnSync } from "node:child_process";
import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync } from "node:fs";
import { createRequire } from "node:module";
import os from "node:os";
import { dirname, join, relative, resolve } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { describe, expect, it } from "vitest";
import { resolveNpmRunner } from "../../scripts/stage-bundled-plugin-runtime-deps.mjs";
import { pluginSdkEntrypoints } from "./entrypoints.js";
const ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..");
@ -16,6 +15,7 @@ const PUBLIC_CONTRACT_REFERENCE_FILES = [
] as const;
const PLUGIN_SDK_SUBPATH_PATTERN = /openclaw\/plugin-sdk\/([a-z0-9][a-z0-9-]*)\b/g;
const NPM_PACK_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
const WINDOWS_UNSAFE_CMD_CHARS_RE = /[&|<>^%\r\n]/;
function collectPluginSdkPackageExports(): string[] {
const packageJson = JSON.parse(readFileSync(resolve(REPO_ROOT, "package.json"), "utf8")) as {
@ -95,23 +95,101 @@ function createRootPackageRequire() {
return createRequire(pathToFileURL(resolve(REPO_ROOT, "package.json")).href);
}
function isNpmExecPath(value: string): boolean {
return /^npm(?:-cli)?(?:\.(?:c?js|cmd|exe))?$/.test(
value.split(/[\\/]/).at(-1)?.toLowerCase() ?? "",
);
}
function escapeForCmdExe(arg: string): string {
if (WINDOWS_UNSAFE_CMD_CHARS_RE.test(arg)) {
throw new Error(`unsafe Windows cmd.exe argument detected: ${JSON.stringify(arg)}`);
}
if (!arg.includes(" ") && !arg.includes('"')) {
return arg;
}
return `"${arg.replace(/"/g, '""')}"`;
}
function buildCmdExeCommandLine(command: string, args: string[]): string {
return [escapeForCmdExe(command), ...args.map(escapeForCmdExe)].join(" ");
}
type NpmCommandInvocation = {
command: string;
args: string[];
env?: NodeJS.ProcessEnv;
windowsVerbatimArguments?: boolean;
};
function resolveNpmCommandInvocation(npmArgs: string[]): NpmCommandInvocation {
const npmExecPath = process.env.npm_execpath;
if (typeof npmExecPath === "string" && npmExecPath.length > 0 && isNpmExecPath(npmExecPath)) {
return { command: process.execPath, args: [npmExecPath, ...npmArgs] };
}
if (process.platform !== "win32") {
return { command: "npm", args: npmArgs };
}
const nodeDir = dirname(process.execPath);
const npmCliCandidates = [
resolve(nodeDir, "../lib/node_modules/npm/bin/npm-cli.js"),
resolve(nodeDir, "node_modules/npm/bin/npm-cli.js"),
];
const npmCliPath = npmCliCandidates.find((candidate) => existsSync(candidate));
if (npmCliPath) {
return { command: process.execPath, args: [npmCliPath, ...npmArgs] };
}
const npmExePath = resolve(nodeDir, "npm.exe");
if (existsSync(npmExePath)) {
return { command: npmExePath, args: npmArgs };
}
const npmCmdPath = resolve(nodeDir, "npm.cmd");
if (existsSync(npmCmdPath)) {
return {
command: process.env.ComSpec ?? "cmd.exe",
args: ["/d", "/s", "/c", buildCmdExeCommandLine(npmCmdPath, npmArgs)],
windowsVerbatimArguments: true,
};
}
return {
command: process.env.ComSpec ?? "cmd.exe",
args: ["/d", "/s", "/c", buildCmdExeCommandLine("npm.cmd", npmArgs)],
windowsVerbatimArguments: true,
};
}
function packOpenClawToTempDir(packDir: string): string {
const npmRunner = resolveNpmRunner({
npmArgs: ["pack", "--ignore-scripts", "--json", "--pack-destination", packDir],
});
const raw = execFileSync(npmRunner.command, npmRunner.args, {
const invocation = resolveNpmCommandInvocation([
"pack",
"--ignore-scripts",
"--json",
"--pack-destination",
packDir,
]);
const result = spawnSync(invocation.command, invocation.args, {
cwd: REPO_ROOT,
encoding: "utf8",
env: {
...process.env,
...npmRunner.env,
...invocation.env,
COREPACK_ENABLE_DOWNLOAD_PROMPT: "0",
},
maxBuffer: NPM_PACK_MAX_BUFFER_BYTES,
shell: npmRunner.shell,
stdio: ["ignore", "pipe", "pipe"],
windowsVerbatimArguments: npmRunner.windowsVerbatimArguments,
windowsVerbatimArguments: invocation.windowsVerbatimArguments,
});
if (result.error) {
throw result.error;
}
if (result.status !== 0) {
throw new Error((result.stderr || result.stdout || "npm pack failed").trim());
}
const raw = result.stdout;
const parsed = JSON.parse(raw) as Array<{ filename?: string }>;
const filename = parsed[0]?.filename?.trim();
if (!filename) {