



























@@ -2,7 +2,7 @@ import fsSync from "node:fs";
22import fs from "node:fs/promises";
33import path from "node:path";
44import { afterEach, describe, expect, it, vi } from "vitest";
5-import { runCommandWithTimeout } from "../process/exec.js";
5+import { runCommandWithTimeout, type CommandOptions } from "../process/exec.js";
66import { createSuiteTempRootTracker } from "../test-helpers/temp-dir.js";
77import { installPackageDir } from "./install-package-dir.js";
88@@ -60,6 +60,38 @@ function createFsError(code: string, message = code): NodeJS.ErrnoException {
6060return Object.assign(new Error(message), { code });
6161}
626263+async function expectMissingPath(filePath: string): Promise<void> {
64+try {
65+await fs.stat(filePath);
66+} catch (error) {
67+expect((error as NodeJS.ErrnoException).code).toBe("ENOENT");
68+return;
69+}
70+throw new Error(`Expected missing path: ${filePath}`);
71+}
72+73+function expectRunCommandCallForArgv(
74+expectedArgv: string[],
75+predicate?: (options: CommandOptions) => boolean,
76+): CommandOptions {
77+const calls = vi.mocked(runCommandWithTimeout).mock.calls as [
78+string[],
79+number | CommandOptions,
80+][];
81+for (const [argv, optionsOrTimeout] of calls.toReversed()) {
82+if (
83+JSON.stringify(argv) !== JSON.stringify(expectedArgv) ||
84+typeof optionsOrTimeout === "number"
85+) {
86+continue;
87+}
88+if (!predicate || predicate(optionsOrTimeout)) {
89+return optionsOrTimeout;
90+}
91+}
92+throw new Error(`Expected runCommandWithTimeout call: ${expectedArgv.join(" ")}`);
93+}
94+6395async function rebindInstallBasePath(params: {
6496installBaseDir: string;
6597preservedDir: string;
@@ -295,11 +327,7 @@ describe("installPackageDir", () => {
295327},
296328});
297329298-await expect(
299-fs.stat(path.join(outsideInstallRoot, "demo", "marker.txt")),
300-).rejects.toMatchObject({
301-code: "ENOENT",
302-});
330+await expectMissingPath(path.join(outsideInstallRoot, "demo", "marker.txt"));
303331expect(warnings).toContain(
304332"Install base directory changed during install; aborting staged publish.",
305333);
@@ -345,11 +373,7 @@ describe("installPackageDir", () => {
345373expect(warnings).toContain(
346374"Install base directory changed before backup cleanup; leaving backup in place.",
347375);
348-await expect(
349-fs.stat(path.join(outsideInstallRoot, "demo", "marker.txt")),
350-).rejects.toMatchObject({
351-code: "ENOENT",
352-});
376+await expectMissingPath(path.join(outsideInstallRoot, "demo", "marker.txt"));
353377const backupRoot = path.join(preservedInstallRoot, ".openclaw-install-backups");
354378await expect(fs.readdir(backupRoot)).resolves.toHaveLength(1);
355379});
@@ -392,12 +416,14 @@ describe("installPackageDir", () => {
392416});
393417394418expect(result).toEqual({ ok: true });
395-expect(vi.mocked(runCommandWithTimeout)).toHaveBeenCalledWith(
396-["npm", "install", "--omit=dev", "--loglevel=error", "--ignore-scripts"],
397-expect.objectContaining({
398-cwd: expect.stringContaining(".openclaw-install-stage-"),
399-}),
400-);
419+const installOptions = expectRunCommandCallForArgv([
420+"npm",
421+"install",
422+"--omit=dev",
423+"--loglevel=error",
424+"--ignore-scripts",
425+]);
426+expect(installOptions.cwd).toContain(".openclaw-install-stage-");
401427});
402428403429it("hides the staged project .npmrc while npm install runs and restores it afterward", async () => {
@@ -425,9 +451,7 @@ describe("installPackageDir", () => {
425451if (cwd === undefined) {
426452throw new Error("expected package install cwd");
427453}
428-await expect(fs.stat(path.join(cwd, ".npmrc"))).rejects.toMatchObject({
429-code: "ENOENT",
430-});
454+await expectMissingPath(path.join(cwd, ".npmrc"));
431455await expect(
432456listMatchingEntries(cwd, ".openclaw-install-hidden-npmrc-"),
433457).resolves.toHaveLength(1);
@@ -502,28 +526,19 @@ describe("installPackageDir", () => {
502526});
503527504528expect(result).toEqual({ ok: true });
505-expect(vi.mocked(runCommandWithTimeout)).toHaveBeenCalledWith(
529+const installOptions = expectRunCommandCallForArgv(
506530["npm", "install", "--omit=dev", "--loglevel=error", "--ignore-scripts"],
507-expect.objectContaining({
508-env: expect.objectContaining({
509-npm_config_global: "false",
510-npm_config_location: "project",
511-npm_config_package_lock: "false",
512-npm_config_save: "false",
513-}),
514-}),
515-);
516-expect(vi.mocked(runCommandWithTimeout)).toHaveBeenCalledWith(
517-expect.any(Array),
518-expect.objectContaining({
519-env: expect.not.objectContaining({
520-NPM_CONFIG_GLOBAL: expect.any(String),
521-NPM_CONFIG_LOCATION: expect.any(String),
522-NPM_CONFIG_PREFIX: expect.any(String),
523-npm_config_prefix: expect.any(String),
524-}),
525-}),
531+(options) => options.env?.npm_config_global === "false",
526532);
533+const env = installOptions.env ?? {};
534+expect(env.npm_config_global).toBe("false");
535+expect(env.npm_config_location).toBe("project");
536+expect(env.npm_config_package_lock).toBe("false");
537+expect(env.npm_config_save).toBe("false");
538+expect("NPM_CONFIG_GLOBAL" in env).toBe(false);
539+expect("NPM_CONFIG_LOCATION" in env).toBe(false);
540+expect("NPM_CONFIG_PREFIX" in env).toBe(false);
541+expect("npm_config_prefix" in env).toBe(false);
527542});
528543529544it("surfaces npm stderr when dependency install fails", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。