diff --git a/src/standalone/runtime.spec.ts b/src/standalone/runtime.spec.ts new file mode 100644 index 00000000000..f59c65f6d2f --- /dev/null +++ b/src/standalone/runtime.spec.ts @@ -0,0 +1,77 @@ +import { expect } from "chai"; +import * as childProcess from "child_process"; +import * as sinon from "sinon"; + +import * as runtime from "../../standalone/runtime"; + +describe("standalone runtime", () => { + let sandbox: sinon.SinonSandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + delete (globalThis as any).appendToPath; + delete (globalThis as any).getSafeCrossPlatformPath; + sandbox.restore(); + }); + + describe("normalizeShellScriptArgs", () => { + it("strips npm's -- sentinel after -c", () => { + expect(runtime.normalizeShellScriptArgs(["-c", "--", "npm install"])).to.deep.equal([ + "npm install", + ]); + }); + + it("preserves a leading -- when it is not the -c sentinel", () => { + expect(runtime.normalizeShellScriptArgs(["--", "npm install"])).to.deep.equal([ + "--", + "npm install", + ]); + }); + + it("preserves arguments after the command string", () => { + expect( + runtime.normalizeShellScriptArgs(["-c", "--", "node ./script.js", "--flag", "value"]), + ).to.deep.equal(["node ./script.js", "--flag", "value"]); + }); + }); + + describe("Script_ShellJS", () => { + it("uses normalized shell args when invoking node commands", async () => { + const originalArgv = process.argv; + const fakeChild = { on: sandbox.stub().returnsThis() }; + const forkStub = sandbox.stub(childProcess, "fork").returns(fakeChild as any); + const spawnStub = sandbox.stub(childProcess, "spawn").returns(fakeChild as any); + + (globalThis as any).appendToPath = sandbox.stub(); + (globalThis as any).getSafeCrossPlatformPath = sandbox.stub().resolvesArg(1); + + process.argv = [ + process.execPath, + "shell.js", + "-c", + "--", + `${process.execPath} ./script.js --flag value`, + ]; + + try { + await runtime.Script_ShellJS(); + } finally { + process.argv = originalArgv; + } + + expect(forkStub).to.have.been.calledOnceWithExactly( + "./script.js", + ["--flag", "value"], + { + env: process.env, + cwd: process.cwd(), + stdio: "inherit", + }, + ); + expect(spawnStub).not.to.have.been.called; + }); + }); +}); diff --git a/standalone/firepit.js b/standalone/firepit.js index f7927e1869e..a68b25ad3a0 100644 --- a/standalone/firepit.js +++ b/standalone/firepit.js @@ -669,7 +669,7 @@ async function createRuntimeBinaries() { node "${FindTool("npm/bin/npm-cli")[0]}" ${npmArgs.join(" ")} %*`, /* Runtime scripts */ - "shell.js": `${appendToPath.toString()}\n${getSafeCrossPlatformPath.toString()}\n(${runtime.Script_ShellJS.toString()})()`, + "shell.js": `${appendToPath.toString()}\n${getSafeCrossPlatformPath.toString()}\nconst normalizeShellScriptArgs = ${runtime.normalizeShellScriptArgs.toString()};\n(${runtime.Script_ShellJS.toString()})()`, "node.js": `(${runtime.Script_NodeJS.toString()})()`, /* Config files */ diff --git a/standalone/runtime.js b/standalone/runtime.js index 36dd52aa9ab..38ded5fba21 100644 --- a/standalone/runtime.js +++ b/standalone/runtime.js @@ -57,6 +57,23 @@ exports.Script_NodeJS = function() { }); }; +function normalizeShellScriptArgs(args) { + args = [...args]; + + const index = args.indexOf("-c"); + if (index !== -1) { + args.splice(index, 1); + + if (args[index] === "--") { + args.splice(index, 1); + } + } + + return args; +} + +exports.normalizeShellScriptArgs = normalizeShellScriptArgs; + /* ------------------------------------- "sh" Command @@ -74,18 +91,13 @@ exports.Script_ShellJS = async function() { const path = require("path"); const child_process = require("child_process"); const isWin = process.platform === "win32"; - const args = process.argv.slice(2); + const args = normalizeShellScriptArgs(process.argv.slice(2)); appendToPath(isWin, [ __dirname, path.join(process.cwd(), "node_modules/.bin") ]); - let index; - if ((index = args.indexOf("-c")) !== -1) { - args.splice(index, 1); - } - args[0] = args[0].replace(process.execPath, "node"); let [cmdRuntime, cmdScript, ...otherArgs] = args[0].split(" ");