diff --git a/packages/safe-chain/src/main.js b/packages/safe-chain/src/main.js index 38bb8ff..0e895b3 100644 --- a/packages/safe-chain/src/main.js +++ b/packages/safe-chain/src/main.js @@ -23,6 +23,7 @@ export async function main(args) { process.on("uncaughtException", (error) => { ui.writeError(`Safe-chain: Uncaught exception: ${error.message}`); ui.writeVerbose(`Stack trace: ${error.stack}`); + ui.writeBufferedLogsAndStopBuffering(); process.exit(1); }); @@ -31,6 +32,7 @@ export async function main(args) { if (reason instanceof Error) { ui.writeVerbose(`Stack trace: ${reason.stack}`); } + ui.writeBufferedLogsAndStopBuffering(); process.exit(1); }); @@ -89,6 +91,7 @@ export async function main(args) { return packageManagerResult.status; } catch (/** @type any */ error) { ui.writeError("Failed to check for malicious packages:", error.message); + ui.writeBufferedLogsAndStopBuffering(); // Returning the exit code back to the caller allows the promise // to be awaited in the bin files and return the correct exit code diff --git a/packages/safe-chain/src/packagemanager/pip/runPipCommand.js b/packages/safe-chain/src/packagemanager/pip/runPipCommand.js index e9f05c7..83bc03e 100644 --- a/packages/safe-chain/src/packagemanager/pip/runPipCommand.js +++ b/packages/safe-chain/src/packagemanager/pip/runPipCommand.js @@ -8,6 +8,7 @@ import fsSync from "node:fs"; import os from "node:os"; import path from "node:path"; import ini from "ini"; +import { spawn } from "child_process"; /** * Checks if this pip invocation should bypass safe-chain and spawn directly. @@ -16,7 +17,7 @@ import ini from "ini"; * @param {string[]} args - The arguments * @returns {boolean} */ -function shouldBypassSafeChain(command, args) { +export function shouldBypassSafeChain(command, args) { if (command === PYTHON_COMMAND || command === PYTHON3_COMMAND) { // Check if args start with -m pip if (args.length >= 2 && args[0] === "-m" && (args[1] === PIP_COMMAND || args[1] === PIP3_COMMAND)) { @@ -77,14 +78,16 @@ export async function runPip(command, args) { if (shouldBypassSafeChain(command, args)) { ui.writeVerbose(`Safe-chain: Bypassing safe-chain for non-pip invocation: ${command} ${args.join(" ")}`); // Spawn the ORIGINAL command with ORIGINAL args - const { spawn } = await import("child_process"); return new Promise((_resolve) => { const proc = spawn(command, args, { stdio: "inherit" }); proc.on("exit", (/** @type {number | null} */ code) => { + ui.writeVerbose(`${command} ${args.join(" ")} exited with status ${code}`); + ui.writeBufferedLogsAndStopBuffering(); process.exit(code ?? 0); }); proc.on("error", (/** @type {Error} */ err) => { ui.writeError(`Error executing command: ${err.message}`); + ui.writeBufferedLogsAndStopBuffering(); process.exit(1); }); }); diff --git a/packages/safe-chain/src/packagemanager/pip/runPipCommand.spec.js b/packages/safe-chain/src/packagemanager/pip/runPipCommand.spec.js index cf121f6..0707333 100644 --- a/packages/safe-chain/src/packagemanager/pip/runPipCommand.spec.js +++ b/packages/safe-chain/src/packagemanager/pip/runPipCommand.spec.js @@ -7,6 +7,7 @@ import ini from "ini"; describe("runPipCommand environment variable handling", () => { let runPip; + let shouldBypassSafeChain; let capturedArgs = null; let customEnv = null; let capturedConfigContent = null; // Capture config file content before cleanup @@ -56,6 +57,7 @@ describe("runPipCommand environment variable handling", () => { const mod = await import("./runPipCommand.js"); runPip = mod.runPip; + shouldBypassSafeChain = mod.shouldBypassSafeChain; }); afterEach(() => { @@ -66,14 +68,14 @@ describe("runPipCommand environment variable handling", () => { const res = await runPip("pip3", ["config", "set", "global.index-url", "https://test.pypi.org/simple"]); assert.strictEqual(res.status, 0); assert.ok(capturedArgs, "safeSpawn should have been called"); - + // PIP_CONFIG_FILE should NOT be set for config commands assert.strictEqual( capturedArgs.options.env.PIP_CONFIG_FILE, undefined, "PIP_CONFIG_FILE should NOT be set for pip config commands" ); - + // But CA environment variables should still be set assert.strictEqual( capturedArgs.options.env.REQUESTS_CA_BUNDLE, @@ -96,7 +98,7 @@ describe("runPipCommand environment variable handling", () => { const res = await runPip("pip3", ["config", "get", "global.index-url"]); assert.strictEqual(res.status, 0); assert.ok(capturedArgs, "safeSpawn should have been called"); - + assert.strictEqual( capturedArgs.options.env.PIP_CONFIG_FILE, undefined, @@ -108,7 +110,7 @@ describe("runPipCommand environment variable handling", () => { const res = await runPip("pip3", ["config", "list"]); assert.strictEqual(res.status, 0); assert.ok(capturedArgs, "safeSpawn should have been called"); - + assert.strictEqual( capturedArgs.options.env.PIP_CONFIG_FILE, undefined, @@ -120,13 +122,13 @@ describe("runPipCommand environment variable handling", () => { const res = await runPip("pip3", ["cache", "dir"]); assert.strictEqual(res.status, 0); assert.ok(capturedArgs, "safeSpawn should have been called"); - + assert.strictEqual( capturedArgs.options.env.PIP_CONFIG_FILE, undefined, "PIP_CONFIG_FILE should NOT be set for pip cache commands" ); - + // CA env vars should still be set assert.strictEqual( capturedArgs.options.env.SSL_CERT_FILE, @@ -139,7 +141,7 @@ describe("runPipCommand environment variable handling", () => { const res = await runPip("pip3", ["debug"]); assert.strictEqual(res.status, 0); assert.ok(capturedArgs, "safeSpawn should have been called"); - + assert.strictEqual( capturedArgs.options.env.PIP_CONFIG_FILE, undefined, @@ -151,7 +153,7 @@ describe("runPipCommand environment variable handling", () => { const res = await runPip("pip3", ["completion", "--bash"]); assert.strictEqual(res.status, 0); assert.ok(capturedArgs, "safeSpawn should have been called"); - + assert.strictEqual( capturedArgs.options.env.PIP_CONFIG_FILE, undefined, @@ -181,7 +183,7 @@ describe("runPipCommand environment variable handling", () => { assert.strictEqual(res.status, 0); assert.ok(capturedArgs, "safeSpawn should have been called"); - + // Check environment variables are set assert.strictEqual( capturedArgs.options.env.REQUESTS_CA_BUNDLE, @@ -218,7 +220,7 @@ describe("runPipCommand environment variable handling", () => { // For default PyPI, we still set env vars; pip CLI --cert takes precedence const res = await runPip("pip3", ["install", "requests"]); assert.strictEqual(res.status, 0); - + // Environment variables still set (pip CLI --cert takes precedence) assert.strictEqual( capturedArgs.options.env.REQUESTS_CA_BUNDLE, @@ -233,7 +235,7 @@ describe("runPipCommand environment variable handling", () => { it("should preserve HTTPS_PROXY from proxy merge", async () => { const res = await runPip("pip3", ["install", "requests"]); assert.strictEqual(res.status, 0); - + assert.strictEqual( capturedArgs.options.env.HTTPS_PROXY, "http://localhost:8080", @@ -380,7 +382,7 @@ describe("runPipCommand environment variable handling", () => { await fs.writeFile(cfgPath, initialIni, "utf-8"); customEnv = { PIP_CONFIG_FILE: cfgPath }; - + // Capture stdout/stderr let output = ""; const originalWrite = process.stdout.write; @@ -397,4 +399,21 @@ describe("runPipCommand environment variable handling", () => { assert.ok(output.includes("proxy found in PIP_CONFIG_FILE"), "Should warn about proxy overwrite in output"); customEnv = null; }); + + it("should bypass safe-chain for python correctly", async () => { + assert.strictEqual(shouldBypassSafeChain("python", []), true); + assert.strictEqual(shouldBypassSafeChain("python3", []), true); + + assert.strictEqual(shouldBypassSafeChain("python", ["--version"]), true); + assert.strictEqual(shouldBypassSafeChain("python3", ["--version"]), true); + + assert.strictEqual(shouldBypassSafeChain("python", ["-m", "http.server"]), true); + assert.strictEqual(shouldBypassSafeChain("python3", ["-m", "http.server"]), true); + + assert.strictEqual(shouldBypassSafeChain("python", ["-m", "pip"]), false); + assert.strictEqual(shouldBypassSafeChain("python3", ["-m", "pip"]), false); + assert.strictEqual(shouldBypassSafeChain("python", ["-m", "pip3"]), false); + assert.strictEqual(shouldBypassSafeChain("python3", ["-m", "pip3"]), false); + }); + }); diff --git a/test/e2e/DockerTestContainer.js b/test/e2e/DockerTestContainer.js index ec1af3c..95a467c 100644 --- a/test/e2e/DockerTestContainer.js +++ b/test/e2e/DockerTestContainer.js @@ -33,12 +33,16 @@ export class DockerTestContainer { ].join(" "); execSync( - `docker build -t ${imageName} -f ${dockerFile} ${contextPath} ${buildArgs}`, + `docker build --progress=plain -t ${imageName} -f ${dockerFile} ${contextPath} ${buildArgs}`, { - stdio: "ignore", + stdio: "pipe", + maxBuffer: 10 * 1024 * 1024, // Default is 1MB, increase to 10MB to account for large build logs } ); } catch (error) { + // Only print the build logs if the build fails + if (error.stdout) console.log(error.stdout.toString()); + if (error.stderr) console.error(error.stderr.toString()); throw new Error(`Failed to build Docker image: ${error.message}`); } } diff --git a/test/e2e/Dockerfile b/test/e2e/Dockerfile index c8d9c9c..bc7ffc2 100644 --- a/test/e2e/Dockerfile +++ b/test/e2e/Dockerfile @@ -41,7 +41,7 @@ RUN apt-get install -y fish && \ touch /root/.config/fish/config.fish # Install Volta and Node.js -RUN curl https://get.volta.sh | bash +RUN curl -fsSL https://get.volta.sh | bash RUN volta install node@${NODE_VERSION} RUN volta install npm@${NPM_VERSION} RUN volta install yarn@${YARN_VERSION}