lobster: parse windows cmd shim paths with rooted tokens (#20833)

This commit is contained in:
Vincent Koc
2026-02-19 02:34:08 -08:00
committed by GitHub
parent 942ed89277
commit 1faa7a87a0
2 changed files with 52 additions and 3 deletions

View File

@@ -304,6 +304,38 @@ describe("lobster plugin tool", () => {
expect(options).not.toHaveProperty("shell");
});
it("runs Windows cmd shims with rooted dp0 tokens through Node", async () => {
setProcessPlatform("win32");
const shimScriptPath = path.join(tempDir, "shim-dist", "lobster-cli.cjs");
const shimPath = path.join(tempDir, "shim", "lobster.cmd");
await fs.mkdir(path.dirname(shimScriptPath), { recursive: true });
await fs.mkdir(path.dirname(shimPath), { recursive: true });
await fs.writeFile(shimScriptPath, "module.exports = {};\n", "utf8");
await fs.writeFile(
shimPath,
`@echo off\r\n"%dp0%\\..\\shim-dist\\lobster-cli.cjs" %*\r\n`,
"utf8",
);
spawnState.queue.push({
stdout: JSON.stringify({
ok: true,
status: "ok",
output: [{ hello: "rooted" }],
requiresApproval: null,
}),
});
const tool = createLobsterTool(fakeApi({ pluginConfig: { lobsterPath: shimPath } }));
await tool.execute("call-win-rooted-shim", {
action: "run",
pipeline: "noop",
});
const [command, argv] = spawnState.spawn.mock.calls[0] ?? [];
expect(command).toBe(process.execPath);
expect(argv).toEqual([shimScriptPath, "run", "--mode", "tool", "noop"]);
});
it("ignores node.exe shim entries and resolves the actual lobster script", async () => {
setProcessPlatform("win32");
const shimDir = path.join(tempDir, "shim-with-node");

View File

@@ -121,13 +121,30 @@ function resolveLobsterScriptFromCmdShim(wrapperPath: string): string | null {
try {
const content = fs.readFileSync(wrapperPath, "utf8");
const candidates: string[] = [];
const matches = content.matchAll(/"%~?dp0%\\([^"\r\n]+)"/gi);
for (const match of matches) {
const extractRelativeFromToken = (token: string): string | null => {
const match = token.match(/%~?dp0%\s*[\\/]*(.*)$/i);
if (!match) {
return null;
}
const relative = match[1];
if (!relative) {
return null;
}
return relative;
};
const matches = content.matchAll(/"([^"\r\n]*)"/g);
for (const match of matches) {
const token = match[1] ?? "";
const relative = extractRelativeFromToken(token);
if (!relative) {
continue;
}
const normalizedRelative = relative.replace(/[\\/]+/g, path.sep);
const normalizedRelative = relative
.trim()
.replace(/[\\/]+/g, path.sep)
.replace(/^[\\/]+/, "");
const candidate = path.resolve(path.dirname(wrapperPath), normalizedRelative);
if (isFilePath(candidate)) {
candidates.push(candidate);