Improve cli output.

This commit is contained in:
Sander Declerck 2025-10-03 16:21:55 +02:00
parent 32f5ef9b16
commit ccaa7934ee
No known key found for this signature in database
9 changed files with 41 additions and 25 deletions

View file

@ -5,6 +5,7 @@ import { ui } from "./environment/userInteraction.js";
import { getPackageManager } from "./packagemanager/currentPackageManager.js"; import { getPackageManager } from "./packagemanager/currentPackageManager.js";
import { initializeCliArguments } from "./config/cliArguments.js"; import { initializeCliArguments } from "./config/cliArguments.js";
import { createSafeChainProxy } from "./registryProxy/registryProxy.js"; import { createSafeChainProxy } from "./registryProxy/registryProxy.js";
import chalk from "chalk";
export async function main(args) { export async function main(args) {
const proxy = createSafeChainProxy(); const proxy = createSafeChainProxy();
@ -27,5 +28,12 @@ export async function main(args) {
await proxy.stopServer(); await proxy.stopServer();
proxy.verifyNoMaliciousPackages(); proxy.verifyNoMaliciousPackages();
ui.emptyLine();
ui.writeInformation(
`${chalk.green(
"✔"
)} Safe-chain: Command completed, no malicious packages found.`
);
return result.status; return result.status;
} }

View file

@ -61,7 +61,7 @@ export async function scanCommand(args) {
} }
if (!audit || audit.isAllowed) { if (!audit || audit.isAllowed) {
spinner.succeed("Safe-chain: No malicious packages detected."); spinner.stop();
} else { } else {
printMaliciousChanges(audit.disallowedChanges, spinner); printMaliciousChanges(audit.disallowedChanges, spinner);
await onMalwareFound(); await onMalwareFound();

View file

@ -13,6 +13,7 @@ describe("scanCommand", async () => {
setText: () => {}, setText: () => {},
succeed: () => {}, succeed: () => {},
fail: () => {}, fail: () => {},
stop: () => {},
})); }));
const mockConfirm = mock.fn(() => true); const mockConfirm = mock.fn(() => true);
let malwareAction = MALWARE_ACTION_PROMPT; let malwareAction = MALWARE_ACTION_PROMPT;
@ -88,29 +89,31 @@ describe("scanCommand", async () => {
const { scanCommand } = await import("./index.js"); const { scanCommand } = await import("./index.js");
it("should succeed when there are no changes", async () => { it("should succeed when there are no changes", async () => {
let successMessageWasSet = false; let progressWasStopped = false;
mockStartProcess.mock.mockImplementationOnce(() => ({ mockStartProcess.mock.mockImplementationOnce(() => ({
setText: () => {}, setText: () => {},
succeed: () => { succeed: () => {},
successMessageWasSet = true;
},
fail: () => {}, fail: () => {},
stop: () => {
progressWasStopped = true;
},
})); }));
mockGetDependencyUpdatesForCommand.mock.mockImplementation(() => []); mockGetDependencyUpdatesForCommand.mock.mockImplementation(() => []);
await scanCommand(["install", "lodash"]); await scanCommand(["install", "lodash"]);
assert.equal(successMessageWasSet, true); assert.equal(progressWasStopped, true);
}); });
it("should succeed when changes are not malicious", async () => { it("should succeed when changes are not malicious", async () => {
let successMessageWasSet = false; let progressWasStopped = false;
mockStartProcess.mock.mockImplementationOnce(() => ({ mockStartProcess.mock.mockImplementationOnce(() => ({
setText: () => {}, setText: () => {},
succeed: () => { succeed: () => {},
successMessageWasSet = true;
},
fail: () => {}, fail: () => {},
stop: () => {
progressWasStopped = true;
},
})); }));
mockGetDependencyUpdatesForCommand.mock.mockImplementation(() => [ mockGetDependencyUpdatesForCommand.mock.mockImplementation(() => [
{ name: "lodash", version: "4.17.21" }, { name: "lodash", version: "4.17.21" },
@ -118,7 +121,7 @@ describe("scanCommand", async () => {
await scanCommand(["install", "lodash"]); await scanCommand(["install", "lodash"]);
assert.equal(successMessageWasSet, true); assert.equal(progressWasStopped, true);
}); });
it("should throw an error when timing out", async () => { it("should throw an error when timing out", async () => {
@ -129,6 +132,7 @@ describe("scanCommand", async () => {
fail: () => { fail: () => {
failureMessageWasSet = true; failureMessageWasSet = true;
}, },
stop: () => {},
})); }));
getScanTimeoutMock.mock.mockImplementationOnce(() => 100); getScanTimeoutMock.mock.mockImplementationOnce(() => 100);
mockGetDependencyUpdatesForCommand.mock.mockImplementation(async () => { mockGetDependencyUpdatesForCommand.mock.mockImplementation(async () => {
@ -149,6 +153,7 @@ describe("scanCommand", async () => {
fail: () => { fail: () => {
failureMessageWasSet = true; failureMessageWasSet = true;
}, },
stop: () => {},
})); }));
mockGetDependencyUpdatesForCommand.mock.mockImplementation(() => [ mockGetDependencyUpdatesForCommand.mock.mockImplementation(() => [
{ name: "malicious", version: "1.0.0" }, { name: "malicious", version: "1.0.0" },
@ -173,6 +178,7 @@ describe("scanCommand", async () => {
fail: (message) => { fail: (message) => {
failureMessages.push(message); failureMessages.push(message);
}, },
stop: () => {},
})); }));
getScanTimeoutMock.mock.mockImplementationOnce(() => 100); getScanTimeoutMock.mock.mockImplementationOnce(() => 100);
mockGetDependencyUpdatesForCommand.mock.mockImplementation(async () => { mockGetDependencyUpdatesForCommand.mock.mockImplementation(async () => {
@ -194,21 +200,22 @@ describe("scanCommand", async () => {
it("should exit immediately when malicious changes are detected in block mode", async () => { it("should exit immediately when malicious changes are detected in block mode", async () => {
// Set malware action to block mode for this test // Set malware action to block mode for this test
malwareAction = MALWARE_ACTION_BLOCK; malwareAction = MALWARE_ACTION_BLOCK;
// Reset mock call count // Reset mock call count
mockConfirm.mock.resetCalls(); mockConfirm.mock.resetCalls();
let failureMessageWasSet = false; let failureMessageWasSet = false;
let exitCode = null; let exitCode = null;
mockStartProcess.mock.mockImplementationOnce(() => ({ mockStartProcess.mock.mockImplementationOnce(() => ({
setText: () => {}, setText: () => {},
succeed: () => {}, succeed: () => {},
fail: () => { fail: () => {
failureMessageWasSet = true; failureMessageWasSet = true;
}, },
stop: () => {},
})); }));
mockGetDependencyUpdatesForCommand.mock.mockImplementation(() => [ mockGetDependencyUpdatesForCommand.mock.mockImplementation(() => [
{ name: "malicious", version: "1.0.0" }, { name: "malicious", version: "1.0.0" },
]); ]);
@ -241,19 +248,20 @@ describe("scanCommand", async () => {
it("should exit immediately when malicious changes are detected in block mode without prompting", async () => { it("should exit immediately when malicious changes are detected in block mode without prompting", async () => {
// Set malware action to block mode for this test // Set malware action to block mode for this test
malwareAction = MALWARE_ACTION_BLOCK; malwareAction = MALWARE_ACTION_BLOCK;
// Reset mock call count // Reset mock call count
mockConfirm.mock.resetCalls(); mockConfirm.mock.resetCalls();
let processExited = false; let processExited = false;
let userWasPrompted = false; let userWasPrompted = false;
mockStartProcess.mock.mockImplementationOnce(() => ({ mockStartProcess.mock.mockImplementationOnce(() => ({
setText: () => {}, setText: () => {},
succeed: () => {}, succeed: () => {},
fail: () => {}, fail: () => {},
stop: () => {},
})); }));
mockGetDependencyUpdatesForCommand.mock.mockImplementation(() => [ mockGetDependencyUpdatesForCommand.mock.mockImplementation(() => [
{ name: "malicious", version: "1.0.0" }, { name: "malicious", version: "1.0.0" },
]); ]);

View file

@ -36,7 +36,7 @@ describe("E2E: npm coverage using PATH", () => {
const result = await shell.runCommand("npm i axios"); const result = await shell.runCommand("npm i axios");
assert.ok( assert.ok(
result.output.includes("No malicious packages detected."), result.output.includes("no malicious packages found."),
`Output did not include expected text. Output was:\n${result.output}` `Output did not include expected text. Output was:\n${result.output}`
); );
}); });

View file

@ -31,7 +31,7 @@ describe("E2E: npm coverage", () => {
const result = await shell.runCommand("npm i axios"); const result = await shell.runCommand("npm i axios");
assert.ok( assert.ok(
result.output.includes("No malicious packages detected."), result.output.includes("no malicious packages found."),
`Output did not include expected text. Output was:\n${result.output}` `Output did not include expected text. Output was:\n${result.output}`
); );
}); });

View file

@ -36,7 +36,7 @@ describe("E2E: pnpm coverage", () => {
const result = await shell.runCommand("pnpm add axios"); const result = await shell.runCommand("pnpm add axios");
assert.ok( assert.ok(
result.output.includes("No malicious packages detected."), result.output.includes("no malicious packages found."),
`Output did not include expected text. Output was:\n${result.output}` `Output did not include expected text. Output was:\n${result.output}`
); );
}); });

View file

@ -31,7 +31,7 @@ describe("E2E: pnpm coverage", () => {
const result = await shell.runCommand("pnpm add axios"); const result = await shell.runCommand("pnpm add axios");
assert.ok( assert.ok(
result.output.includes("No malicious packages detected."), result.output.includes("no malicious packages found."),
`Output did not include expected text. Output was:\n${result.output}` `Output did not include expected text. Output was:\n${result.output}`
); );
}); });

View file

@ -36,7 +36,7 @@ describe("E2E: yarn coverage", () => {
const result = await shell.runCommand("yarn add axios"); const result = await shell.runCommand("yarn add axios");
assert.ok( assert.ok(
result.output.includes("No malicious packages detected."), result.output.includes("no malicious packages found."),
`Output did not include expected text. Output was:\n${result.output}` `Output did not include expected text. Output was:\n${result.output}`
); );
}); });

View file

@ -31,7 +31,7 @@ describe("E2E: yarn coverage", () => {
const result = await shell.runCommand("yarn add axios"); const result = await shell.runCommand("yarn add axios");
assert.ok( assert.ok(
result.output.includes("No malicious packages detected."), result.output.includes("no malicious packages found."),
`Output did not include expected text. Output was:\n${result.output}` `Output did not include expected text. Output was:\n${result.output}`
); );
}); });