mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-28 01:42:59 +00:00
fix(plugins): harden Windows npm package staging
This commit is contained in:
@@ -116,6 +116,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Install/Windows: run Git hook setup through a Node prepare helper so native Windows installs no longer print POSIX shell errors.
|
||||
- Checks/Windows: chunk and serialize extension oxlint shards on native Windows so changed gates avoid Go-backed linter memory spikes.
|
||||
- Release/Windows: run installed `openclaw.cmd` verification through explicit `cmd.exe` wrapping so npm prepublish/postpublish checks avoid Node shell-argv warnings.
|
||||
- Plugins/Windows: run plugin npm package staging through the shared npm runner so native Windows release checks avoid bare `npm` lookup and `.cmd` shell-argv handling.
|
||||
- Checks/Windows: route full `pnpm check` stage commands through the managed child runner so Windows avoids Node shell-argv deprecation warnings there too.
|
||||
- Agents/fs: allow workspace-only host write/edit tools to write through in-workspace symlink directory parents while preserving outside-workspace symlink rejection. Fixes #84696. Thanks @garbagenetwork.
|
||||
- Checks/Windows: run managed child commands through explicit `cmd.exe` wrapping instead of Node shell mode with argv, avoiding Node 24 subprocess deprecation warnings during changed checks.
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
listPluginNpmRuntimeBuildOutputs,
|
||||
resolvePluginNpmRuntimeBuildPlan,
|
||||
} from "./plugin-npm-runtime-build.mjs";
|
||||
import { resolveNpmRunner } from "../npm-runner.mjs";
|
||||
|
||||
const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA_PATH =
|
||||
"src/config/bundled-channel-config-metadata.generated.ts";
|
||||
@@ -136,28 +137,26 @@ function listConfiguredBundledDependencyNames(packageJson) {
|
||||
return [];
|
||||
}
|
||||
|
||||
function npmInvocation() {
|
||||
if (process.platform !== "win32") {
|
||||
return { args: [], command: "npm" };
|
||||
}
|
||||
const npmCliPath = path.join(
|
||||
path.dirname(process.execPath),
|
||||
"node_modules",
|
||||
"npm",
|
||||
"bin",
|
||||
"npm-cli.js",
|
||||
);
|
||||
if (fs.existsSync(npmCliPath)) {
|
||||
return { args: [npmCliPath], command: process.execPath };
|
||||
}
|
||||
return { args: [], command: "npm.cmd", shell: true };
|
||||
export function resolvePluginNpmCommand(args, params = {}) {
|
||||
return resolveNpmRunner({
|
||||
comSpec: params.comSpec,
|
||||
env: params.env,
|
||||
execPath: params.execPath,
|
||||
existsSync: params.existsSync,
|
||||
npmArgs: args,
|
||||
platform: params.platform,
|
||||
});
|
||||
}
|
||||
|
||||
function spawnNpmSync(args, options) {
|
||||
const invocation = npmInvocation();
|
||||
return spawnSync(invocation.command, [...invocation.args, ...args], {
|
||||
function spawnNpmSync(args, options = {}) {
|
||||
const invocation = resolvePluginNpmCommand(args, { env: options.env ?? process.env });
|
||||
return spawnSync(invocation.command, invocation.args, {
|
||||
...options,
|
||||
...(invocation.shell ? { shell: invocation.shell } : {}),
|
||||
...(invocation.env ? { env: invocation.env } : {}),
|
||||
...(invocation.shell !== undefined ? { shell: invocation.shell } : {}),
|
||||
...(invocation.windowsVerbatimArguments !== undefined
|
||||
? { windowsVerbatimArguments: invocation.windowsVerbatimArguments }
|
||||
: {}),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { dirname, join, win32 } from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
resolveAugmentedPluginNpmPackageJson,
|
||||
resolveAugmentedPluginNpmManifest,
|
||||
resolvePluginNpmCommand,
|
||||
withAugmentedPluginNpmManifestForPackage,
|
||||
} from "../scripts/lib/plugin-npm-package-manifest.mjs";
|
||||
import { cleanupTempDirs, makeTempRepoRoot, writeJsonFile } from "./helpers/temp-repo.js";
|
||||
@@ -51,10 +52,16 @@ function writeFileText(filePath: string, text: string): void {
|
||||
}
|
||||
|
||||
function listNpmPackDryRunFiles(packageDir: string): string[] {
|
||||
const result = spawnSync("npm", ["pack", "--dry-run", "--json", "--ignore-scripts"], {
|
||||
const invocation = resolvePluginNpmCommand(["pack", "--dry-run", "--json", "--ignore-scripts"]);
|
||||
const result = spawnSync(invocation.command, invocation.args, {
|
||||
cwd: packageDir,
|
||||
encoding: "utf8",
|
||||
...(invocation.env ? { env: invocation.env } : {}),
|
||||
...(invocation.shell !== undefined ? { shell: invocation.shell } : {}),
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
...(invocation.windowsVerbatimArguments !== undefined
|
||||
? { windowsVerbatimArguments: invocation.windowsVerbatimArguments }
|
||||
: {}),
|
||||
});
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
@@ -132,6 +139,41 @@ function writeOptionalPlatformDependencyPackage(packageDir: string): string {
|
||||
}
|
||||
|
||||
describe("plugin npm package manifest staging", () => {
|
||||
it("wraps Windows npm.cmd staging through cmd.exe without shell mode", () => {
|
||||
const nodeDir = "C:\\Program Files\\nodejs";
|
||||
const npmCmdPath = win32.resolve(nodeDir, "npm.cmd");
|
||||
|
||||
expect(
|
||||
resolvePluginNpmCommand(["install", "--package-lock-only"], {
|
||||
comSpec: "C:\\Windows\\System32\\cmd.exe",
|
||||
env: { PATH: "C:\\bin" },
|
||||
execPath: win32.join(nodeDir, "node.exe"),
|
||||
existsSync: (candidate: string) => candidate === npmCmdPath,
|
||||
platform: "win32",
|
||||
}),
|
||||
).toEqual({
|
||||
command: "C:\\Windows\\System32\\cmd.exe",
|
||||
args: [
|
||||
"/d",
|
||||
"/s",
|
||||
"/c",
|
||||
'""C:\\Program Files\\nodejs\\npm.cmd" install --package-lock-only"',
|
||||
],
|
||||
shell: false,
|
||||
windowsVerbatimArguments: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects bare npm fallback on Windows plugin package staging", () => {
|
||||
expect(() =>
|
||||
resolvePluginNpmCommand(["install"], {
|
||||
execPath: "C:\\nodejs\\node.exe",
|
||||
existsSync: () => false,
|
||||
platform: "win32",
|
||||
}),
|
||||
).toThrow("OpenClaw refuses to shell out to bare npm on Windows");
|
||||
});
|
||||
|
||||
it("overlays generated channel configs while packing and restores source manifest", () => {
|
||||
const repoDir = makeTempRepoRoot(tempDirs, "openclaw-plugin-npm-package-manifest-");
|
||||
const packageDir = join(repoDir, "extensions", "twitch");
|
||||
|
||||
Reference in New Issue
Block a user