mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
Merge pull request #326 from liuxiaopai-ai/fix/issue-309-command-execution-error
fix(cli): surface missing runtime command errors
This commit is contained in:
commit
9749990dcc
11 changed files with 95 additions and 63 deletions
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { ui } from "../../environment/userInteraction.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Centralized logging for package-manager command launch failures.
|
||||||
|
*
|
||||||
|
* @param {any} error - Error thrown by safeSpawn while preparing/running the command.
|
||||||
|
* @param {string} command - Command name that failed to execute.
|
||||||
|
* @returns {{status: number}}
|
||||||
|
*/
|
||||||
|
export function reportCommandExecutionFailure(error, command) {
|
||||||
|
const message = typeof error?.message === "string" ? error.message : "Unknown error";
|
||||||
|
ui.writeError(`Error executing command: ${message}`);
|
||||||
|
|
||||||
|
ui.writeError(`Is '${command}' installed and available on your system?`);
|
||||||
|
|
||||||
|
return { status: typeof error?.status === "number" ? error.status : 1 };
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { describe, it, beforeEach, afterEach, mock } from "node:test";
|
||||||
|
import assert from "node:assert";
|
||||||
|
|
||||||
|
describe("reportCommandExecutionFailure", () => {
|
||||||
|
let errorLines;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
errorLines = [];
|
||||||
|
|
||||||
|
mock.module("../../environment/userInteraction.js", {
|
||||||
|
namedExports: {
|
||||||
|
ui: {
|
||||||
|
writeError: (...args) => {
|
||||||
|
errorLines.push(args.join(" "));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
mock.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reports command errors while preserving exit status", async () => {
|
||||||
|
const { reportCommandExecutionFailure } = await import("./commandErrors.js");
|
||||||
|
|
||||||
|
const result = reportCommandExecutionFailure(
|
||||||
|
{
|
||||||
|
status: 127,
|
||||||
|
message: "Command failed: command -v bun",
|
||||||
|
},
|
||||||
|
"bun",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepStrictEqual(result, { status: 127 });
|
||||||
|
assert.deepStrictEqual(errorLines, [
|
||||||
|
"Error executing command: Command failed: command -v bun",
|
||||||
|
"Is 'bun' installed and available on your system?",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to exit code 1 when status is missing", async () => {
|
||||||
|
const { reportCommandExecutionFailure } = await import("./commandErrors.js");
|
||||||
|
|
||||||
|
const result = reportCommandExecutionFailure(
|
||||||
|
{
|
||||||
|
message: "Network error",
|
||||||
|
},
|
||||||
|
"npm",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepStrictEqual(result, { status: 1 });
|
||||||
|
assert.deepStrictEqual(errorLines, [
|
||||||
|
"Error executing command: Network error",
|
||||||
|
"Is 'npm' installed and available on your system?",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { ui } from "../../environment/userInteraction.js";
|
|
||||||
import { safeSpawn } from "../../utils/safeSpawn.js";
|
import { safeSpawn } from "../../utils/safeSpawn.js";
|
||||||
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
||||||
|
import { reportCommandExecutionFailure } from "../_shared/commandErrors.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {import("../currentPackageManager.js").PackageManager}
|
* @returns {import("../currentPackageManager.js").PackageManager}
|
||||||
|
|
@ -43,11 +43,6 @@ async function runBunCommand(command, args) {
|
||||||
});
|
});
|
||||||
return { status: result.status };
|
return { status: result.status };
|
||||||
} catch (/** @type any */ error) {
|
} catch (/** @type any */ error) {
|
||||||
if (error.status) {
|
return reportCommandExecutionFailure(error, command);
|
||||||
return { status: error.status };
|
|
||||||
} else {
|
|
||||||
ui.writeError("Error executing command:", error.message);
|
|
||||||
return { status: 1 };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { ui } from "../../environment/userInteraction.js";
|
|
||||||
import { safeSpawn } from "../../utils/safeSpawn.js";
|
import { safeSpawn } from "../../utils/safeSpawn.js";
|
||||||
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
||||||
|
import { reportCommandExecutionFailure } from "../_shared/commandErrors.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string[]} args
|
* @param {string[]} args
|
||||||
|
|
@ -15,11 +15,6 @@ export async function runNpm(args) {
|
||||||
});
|
});
|
||||||
return { status: result.status };
|
return { status: result.status };
|
||||||
} catch (/** @type any */ error) {
|
} catch (/** @type any */ error) {
|
||||||
if (error.status) {
|
return reportCommandExecutionFailure(error, "npm");
|
||||||
return { status: error.status };
|
|
||||||
} else {
|
|
||||||
ui.writeError("Error executing command:", error.message);
|
|
||||||
return { status: 1 };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { ui } from "../../environment/userInteraction.js";
|
|
||||||
import { safeSpawn } from "../../utils/safeSpawn.js";
|
import { safeSpawn } from "../../utils/safeSpawn.js";
|
||||||
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
||||||
|
import { reportCommandExecutionFailure } from "../_shared/commandErrors.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string[]} args
|
* @param {string[]} args
|
||||||
|
|
@ -15,11 +15,6 @@ export async function runNpx(args) {
|
||||||
});
|
});
|
||||||
return { status: result.status };
|
return { status: result.status };
|
||||||
} catch (/** @type any */ error) {
|
} catch (/** @type any */ error) {
|
||||||
if (error.status) {
|
return reportCommandExecutionFailure(error, "npx");
|
||||||
return { status: error.status };
|
|
||||||
} else {
|
|
||||||
ui.writeError("Error executing command:", error.message);
|
|
||||||
return { status: 1 };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import ini from "ini";
|
import ini from "ini";
|
||||||
import { spawn } from "child_process";
|
import { spawn } from "child_process";
|
||||||
|
import { reportCommandExecutionFailure } from "../_shared/commandErrors.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if this pip invocation should bypass safe-chain and spawn directly.
|
* Checks if this pip invocation should bypass safe-chain and spawn directly.
|
||||||
|
|
@ -203,12 +204,6 @@ export async function runPip(command, args) {
|
||||||
|
|
||||||
return { status: result.status };
|
return { status: result.status };
|
||||||
} catch (/** @type any */ error) {
|
} catch (/** @type any */ error) {
|
||||||
if (error.status) {
|
return reportCommandExecutionFailure(error, command);
|
||||||
return { status: error.status };
|
|
||||||
} else {
|
|
||||||
ui.writeError(`Error executing command: ${error.message}`);
|
|
||||||
ui.writeError(`Is '${command}' installed and available on your system?`);
|
|
||||||
return { status: 1 };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { ui } from "../../environment/userInteraction.js";
|
||||||
import { safeSpawn } from "../../utils/safeSpawn.js";
|
import { safeSpawn } from "../../utils/safeSpawn.js";
|
||||||
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
||||||
import { getCombinedCaBundlePath } from "../../registryProxy/certBundle.js";
|
import { getCombinedCaBundlePath } from "../../registryProxy/certBundle.js";
|
||||||
|
import { reportCommandExecutionFailure } from "../_shared/commandErrors.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets CA bundle environment variables used by Python libraries and pipx.
|
* Sets CA bundle environment variables used by Python libraries and pipx.
|
||||||
|
|
@ -54,12 +55,6 @@ export async function runPipX(command, args) {
|
||||||
|
|
||||||
return { status: result.status };
|
return { status: result.status };
|
||||||
} catch (/** @type any */ error) {
|
} catch (/** @type any */ error) {
|
||||||
if (error.status) {
|
return reportCommandExecutionFailure(error, command);
|
||||||
return { status: error.status };
|
|
||||||
} else {
|
|
||||||
ui.writeError(`Error executing command: ${error.message}`);
|
|
||||||
ui.writeError(`Is '${command}' installed and available on your system?`);
|
|
||||||
return { status: 1 };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { ui } from "../../environment/userInteraction.js";
|
|
||||||
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
||||||
import { safeSpawn } from "../../utils/safeSpawn.js";
|
import { safeSpawn } from "../../utils/safeSpawn.js";
|
||||||
|
import { reportCommandExecutionFailure } from "../_shared/commandErrors.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string[]} args
|
* @param {string[]} args
|
||||||
|
|
@ -26,11 +26,7 @@ export async function runPnpmCommand(args, toolName = "pnpm") {
|
||||||
|
|
||||||
return { status: result.status };
|
return { status: result.status };
|
||||||
} catch (/** @type any */ error) {
|
} catch (/** @type any */ error) {
|
||||||
if (error.status) {
|
const target = toolName === "pnpm" ? "pnpm" : "pnpx";
|
||||||
return { status: error.status };
|
return reportCommandExecutionFailure(error, target);
|
||||||
} else {
|
|
||||||
ui.writeError("Error executing command:", error.message);
|
|
||||||
return { status: 1 };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { ui } from "../../environment/userInteraction.js";
|
||||||
import { safeSpawn } from "../../utils/safeSpawn.js";
|
import { safeSpawn } from "../../utils/safeSpawn.js";
|
||||||
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
||||||
import { getCombinedCaBundlePath } from "../../registryProxy/certBundle.js";
|
import { getCombinedCaBundlePath } from "../../registryProxy/certBundle.js";
|
||||||
|
import { reportCommandExecutionFailure } from "../_shared/commandErrors.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {import("../currentPackageManager.js").PackageManager}
|
* @returns {import("../currentPackageManager.js").PackageManager}
|
||||||
|
|
@ -66,12 +67,6 @@ async function runPoetryCommand(args) {
|
||||||
|
|
||||||
return { status: result.status };
|
return { status: result.status };
|
||||||
} catch (/** @type any */ error) {
|
} catch (/** @type any */ error) {
|
||||||
if (error.status) {
|
return reportCommandExecutionFailure(error, "poetry");
|
||||||
return { status: error.status };
|
|
||||||
} else {
|
|
||||||
ui.writeError("Error executing command:", error.message);
|
|
||||||
ui.writeError("Is 'poetry' installed and available on your system?");
|
|
||||||
return { status: 1 };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { ui } from "../../environment/userInteraction.js";
|
||||||
import { safeSpawn } from "../../utils/safeSpawn.js";
|
import { safeSpawn } from "../../utils/safeSpawn.js";
|
||||||
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
||||||
import { getCombinedCaBundlePath } from "../../registryProxy/certBundle.js";
|
import { getCombinedCaBundlePath } from "../../registryProxy/certBundle.js";
|
||||||
|
import { reportCommandExecutionFailure } from "../_shared/commandErrors.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets CA bundle environment variables used by Python libraries and uv.
|
* Sets CA bundle environment variables used by Python libraries and uv.
|
||||||
|
|
@ -60,12 +61,6 @@ export async function runUv(command, args) {
|
||||||
|
|
||||||
return { status: result.status };
|
return { status: result.status };
|
||||||
} catch (/** @type any */ error) {
|
} catch (/** @type any */ error) {
|
||||||
if (error.status) {
|
return reportCommandExecutionFailure(error, command);
|
||||||
return { status: error.status };
|
|
||||||
} else {
|
|
||||||
ui.writeError(`Error executing command: ${error.message}`);
|
|
||||||
ui.writeError(`Is '${command}' installed and available on your system?`);
|
|
||||||
return { status: 1 };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { ui } from "../../environment/userInteraction.js";
|
|
||||||
import { safeSpawn } from "../../utils/safeSpawn.js";
|
import { safeSpawn } from "../../utils/safeSpawn.js";
|
||||||
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
|
||||||
|
import { reportCommandExecutionFailure } from "../_shared/commandErrors.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string[]} args
|
* @param {string[]} args
|
||||||
|
|
@ -18,12 +18,7 @@ export async function runYarnCommand(args) {
|
||||||
});
|
});
|
||||||
return { status: result.status };
|
return { status: result.status };
|
||||||
} catch (/** @type any */ error) {
|
} catch (/** @type any */ error) {
|
||||||
if (error.status) {
|
return reportCommandExecutionFailure(error, "yarn");
|
||||||
return { status: error.status };
|
|
||||||
} else {
|
|
||||||
ui.writeError("Error executing command:", error.message);
|
|
||||||
return { status: 1 };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue