











@@ -1,6 +1,7 @@
1-import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
1+import { execFileSync } from "node:child_process";
2+import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
23import { tmpdir } from "node:os";
3-import { join } from "node:path";
4+import { delimiter, join } from "node:path";
45import { describe, expect, it } from "vitest";
56import { WORKSPACE_TEMPLATE_PACK_PATHS } from "../scripts/lib/workspace-bootstrap-smoke.mjs";
67import {
@@ -284,34 +285,122 @@ describe("resolveNpmCommandInvocation", () => {
284285expect(
285286resolveNpmCommandInvocation({
286287npmExecPath: "/usr/local/lib/node_modules/npm/bin/npm-cli.js",
288+npmArgs: ["view", "openclaw", "version"],
287289nodeExecPath: "/usr/local/bin/node",
288290platform: "linux",
289291}),
290292).toEqual({
291293command: "/usr/local/bin/node",
292-args: ["/usr/local/lib/node_modules/npm/bin/npm-cli.js"],
294+args: ["/usr/local/lib/node_modules/npm/bin/npm-cli.js", "view", "openclaw", "version"],
293295});
294296});
295297296298it("falls back to the npm command when npm_execpath points to pnpm", () => {
297299expect(
298300resolveNpmCommandInvocation({
301+npmArgs: ["pack"],
299302npmExecPath: "/home/test/.cache/node/corepack/v1/pnpm/10.23.0/bin/pnpm.cjs",
300303nodeExecPath: "/usr/local/bin/node",
301304platform: "linux",
302305}),
303306).toEqual({
304307command: "npm",
305-args: [],
308+args: ["pack"],
306309});
307310});
308311309-it("uses the platform npm command when npm_execpath is missing", () => {
310-expect(resolveNpmCommandInvocation({ npmExecPath: "", platform: "win32" })).toEqual({
311-command: "npm.cmd",
312-args: [],
312+it("wraps the Windows npm command when npm_execpath is missing", () => {
313+expect(
314+resolveNpmCommandInvocation({
315+comSpec: "C:\\Windows\\System32\\cmd.exe",
316+npmArgs: ["view", "openclaw@beta", "version"],
317+npmExecPath: "",
318+platform: "win32",
319+}),
320+).toEqual({
321+command: "C:\\Windows\\System32\\cmd.exe",
322+args: ["/d", "/s", "/c", "npm.cmd view openclaw@beta version"],
323+windowsVerbatimArguments: true,
324+});
325+});
326+327+it("wraps Windows npm_execpath command shims", () => {
328+expect(
329+resolveNpmCommandInvocation({
330+comSpec: "C:\\Windows\\System32\\cmd.exe",
331+npmArgs: ["install", "-g", "C:\\tmp\\openclaw package.tgz"],
332+npmExecPath: "C:\\Program Files\\nodejs\\npm.cmd",
333+nodeExecPath: "C:\\Program Files\\nodejs\\node.exe",
334+platform: "win32",
335+}),
336+).toEqual({
337+command: "C:\\Windows\\System32\\cmd.exe",
338+args: [
339+"/d",
340+"/s",
341+"/c",
342+'"C:\\Program Files\\nodejs\\npm.cmd" install -g "C:\\tmp\\openclaw package.tgz"',
343+],
344+windowsVerbatimArguments: true,
345+});
346+});
347+348+it("runs Windows npm_execpath executables directly", () => {
349+expect(
350+resolveNpmCommandInvocation({
351+npmArgs: ["--version"],
352+npmExecPath: "C:\\Program Files\\nodejs\\npm.exe",
353+platform: "win32",
354+}),
355+).toEqual({
356+command: "C:\\Program Files\\nodejs\\npm.exe",
357+args: ["--version"],
313358});
314359});
360+361+if (process.platform === "win32") {
362+it("executes fallback npm.cmd through cmd.exe on Windows", () => {
363+const dir = mkdtempSync(join(tmpdir(), "openclaw-fake-npm-cmd-"));
364+try {
365+const outputPath = join(dir, "args.json");
366+writeFileSync(
367+join(dir, "fake-npm.js"),
368+[
369+"const fs = require('node:fs');",
370+"fs.writeFileSync(process.env.OPENCLAW_FAKE_NPM_OUT, JSON.stringify(process.argv.slice(2)));",
371+].join("\n"),
372+);
373+writeFileSync(
374+join(dir, "npm.cmd"),
375+`@echo off\r\n"${process.execPath}" "%~dp0fake-npm.js" %*\r\n`,
376+);
377+378+const invocation = resolveNpmCommandInvocation({
379+comSpec: process.env.ComSpec ?? "cmd.exe",
380+npmArgs: ["view", "openclaw@beta", "version"],
381+npmExecPath: "",
382+platform: "win32",
383+});
384+execFileSync(invocation.command, invocation.args, {
385+cwd: dir,
386+env: {
387+ ...process.env,
388+OPENCLAW_FAKE_NPM_OUT: outputPath,
389+PATH: `${dir}${delimiter}${process.env.PATH ?? ""}`,
390+},
391+windowsVerbatimArguments: invocation.windowsVerbatimArguments,
392+});
393+394+expect(JSON.parse(readFileSync(outputPath, "utf8"))).toEqual([
395+"view",
396+"openclaw@beta",
397+"version",
398+]);
399+} finally {
400+rmSync(dir, { recursive: true, force: true });
401+}
402+});
403+}
315404});
316405317406describe("parseNpmPackJsonOutput", () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。