149 lines
5.6 KiB
TypeScript
149 lines
5.6 KiB
TypeScript
import fs from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
import { resolveWindowsLobsterSpawn } from "./windows-spawn.js";
|
|
|
|
function setProcessPlatform(platform: NodeJS.Platform) {
|
|
Object.defineProperty(process, "platform", {
|
|
value: platform,
|
|
configurable: true,
|
|
});
|
|
}
|
|
|
|
describe("resolveWindowsLobsterSpawn", () => {
|
|
let tempDir = "";
|
|
const originalPlatform = Object.getOwnPropertyDescriptor(process, "platform");
|
|
const originalPath = process.env.PATH;
|
|
const originalPathAlt = process.env.Path;
|
|
const originalPathExt = process.env.PATHEXT;
|
|
const originalPathExtAlt = process.env.Pathext;
|
|
|
|
beforeEach(async () => {
|
|
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-lobster-win-spawn-"));
|
|
setProcessPlatform("win32");
|
|
});
|
|
|
|
afterEach(async () => {
|
|
if (originalPlatform) {
|
|
Object.defineProperty(process, "platform", originalPlatform);
|
|
}
|
|
if (originalPath === undefined) {
|
|
delete process.env.PATH;
|
|
} else {
|
|
process.env.PATH = originalPath;
|
|
}
|
|
if (originalPathAlt === undefined) {
|
|
delete process.env.Path;
|
|
} else {
|
|
process.env.Path = originalPathAlt;
|
|
}
|
|
if (originalPathExt === undefined) {
|
|
delete process.env.PATHEXT;
|
|
} else {
|
|
process.env.PATHEXT = originalPathExt;
|
|
}
|
|
if (originalPathExtAlt === undefined) {
|
|
delete process.env.Pathext;
|
|
} else {
|
|
process.env.Pathext = originalPathExtAlt;
|
|
}
|
|
if (tempDir) {
|
|
await fs.rm(tempDir, { recursive: true, force: true });
|
|
tempDir = "";
|
|
}
|
|
});
|
|
|
|
it("unwraps cmd shim with %dp0% token", async () => {
|
|
const scriptPath = path.join(tempDir, "shim-dist", "lobster-cli.cjs");
|
|
const shimPath = path.join(tempDir, "shim", "lobster.cmd");
|
|
await fs.mkdir(path.dirname(scriptPath), { recursive: true });
|
|
await fs.mkdir(path.dirname(shimPath), { recursive: true });
|
|
await fs.writeFile(scriptPath, "module.exports = {};\n", "utf8");
|
|
await fs.writeFile(
|
|
shimPath,
|
|
`@echo off\r\n"%dp0%\\..\\shim-dist\\lobster-cli.cjs" %*\r\n`,
|
|
"utf8",
|
|
);
|
|
|
|
const target = resolveWindowsLobsterSpawn(shimPath, ["run", "noop"], process.env);
|
|
expect(target.command).toBe(process.execPath);
|
|
expect(target.argv).toEqual([scriptPath, "run", "noop"]);
|
|
expect(target.windowsHide).toBe(true);
|
|
});
|
|
|
|
it("unwraps cmd shim with %~dp0% token", async () => {
|
|
const scriptPath = path.join(tempDir, "shim-dist", "lobster-cli.cjs");
|
|
const shimPath = path.join(tempDir, "shim", "lobster.cmd");
|
|
await fs.mkdir(path.dirname(scriptPath), { recursive: true });
|
|
await fs.mkdir(path.dirname(shimPath), { recursive: true });
|
|
await fs.writeFile(scriptPath, "module.exports = {};\n", "utf8");
|
|
await fs.writeFile(
|
|
shimPath,
|
|
`@echo off\r\n"%~dp0%\\..\\shim-dist\\lobster-cli.cjs" %*\r\n`,
|
|
"utf8",
|
|
);
|
|
|
|
const target = resolveWindowsLobsterSpawn(shimPath, ["run", "noop"], process.env);
|
|
expect(target.command).toBe(process.execPath);
|
|
expect(target.argv).toEqual([scriptPath, "run", "noop"]);
|
|
expect(target.windowsHide).toBe(true);
|
|
});
|
|
|
|
it("ignores node.exe shim entries and picks lobster script", async () => {
|
|
const shimDir = path.join(tempDir, "shim-with-node");
|
|
const scriptPath = path.join(tempDir, "shim-dist-node", "lobster-cli.cjs");
|
|
const shimPath = path.join(shimDir, "lobster.cmd");
|
|
await fs.mkdir(path.dirname(scriptPath), { recursive: true });
|
|
await fs.mkdir(shimDir, { recursive: true });
|
|
await fs.writeFile(path.join(shimDir, "node.exe"), "", "utf8");
|
|
await fs.writeFile(scriptPath, "module.exports = {};\n", "utf8");
|
|
await fs.writeFile(
|
|
shimPath,
|
|
`@echo off\r\n"%~dp0%\\node.exe" "%~dp0%\\..\\shim-dist-node\\lobster-cli.cjs" %*\r\n`,
|
|
"utf8",
|
|
);
|
|
|
|
const target = resolveWindowsLobsterSpawn(shimPath, ["run", "noop"], process.env);
|
|
expect(target.command).toBe(process.execPath);
|
|
expect(target.argv).toEqual([scriptPath, "run", "noop"]);
|
|
expect(target.windowsHide).toBe(true);
|
|
});
|
|
|
|
it("resolves lobster.cmd from PATH and unwraps npm layout shim", async () => {
|
|
const binDir = path.join(tempDir, "node_modules", ".bin");
|
|
const packageDir = path.join(tempDir, "node_modules", "lobster");
|
|
const scriptPath = path.join(packageDir, "dist", "cli.js");
|
|
const shimPath = path.join(binDir, "lobster.cmd");
|
|
await fs.mkdir(path.dirname(scriptPath), { recursive: true });
|
|
await fs.mkdir(binDir, { recursive: true });
|
|
await fs.writeFile(shimPath, "@echo off\r\n", "utf8");
|
|
await fs.writeFile(
|
|
path.join(packageDir, "package.json"),
|
|
JSON.stringify({ name: "lobster", version: "0.0.0", bin: { lobster: "dist/cli.js" } }),
|
|
"utf8",
|
|
);
|
|
await fs.writeFile(scriptPath, "module.exports = {};\n", "utf8");
|
|
|
|
const env = {
|
|
...process.env,
|
|
PATH: `${binDir};${process.env.PATH ?? ""}`,
|
|
PATHEXT: ".CMD;.EXE",
|
|
};
|
|
const target = resolveWindowsLobsterSpawn("lobster", ["run", "noop"], env);
|
|
expect(target.command).toBe(process.execPath);
|
|
expect(target.argv).toEqual([scriptPath, "run", "noop"]);
|
|
expect(target.windowsHide).toBe(true);
|
|
});
|
|
|
|
it("fails fast when wrapper cannot be resolved without shell execution", async () => {
|
|
const badShimPath = path.join(tempDir, "bad-shim", "lobster.cmd");
|
|
await fs.mkdir(path.dirname(badShimPath), { recursive: true });
|
|
await fs.writeFile(badShimPath, "@echo off\r\nREM no entrypoint\r\n", "utf8");
|
|
|
|
expect(() => resolveWindowsLobsterSpawn(badShimPath, ["run", "noop"], process.env)).toThrow(
|
|
/without shell execution/,
|
|
);
|
|
});
|
|
});
|