Remove ora dependency

This commit is contained in:
Sander Declerck 2025-11-25 14:22:31 +01:00
parent d158e15c08
commit c8df7566b5
No known key found for this signature in database
5 changed files with 17 additions and 426 deletions

226
package-lock.json generated
View file

@ -659,33 +659,6 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/cli-cursor": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
"integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
"license": "MIT",
"dependencies": {
"restore-cursor": "^5.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cli-spinners": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
"integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
"license": "MIT",
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/color-convert": { "node_modules/color-convert": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@ -909,18 +882,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/get-east-asian-width": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz",
"integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/get-intrinsic": { "node_modules/get-intrinsic": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@ -1128,30 +1089,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/is-interactive": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz",
"integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-unicode-supported": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz",
"integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/isexe": { "node_modules/isexe": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@ -1188,34 +1125,6 @@
], ],
"license": "MIT" "license": "MIT"
}, },
"node_modules/log-symbols": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz",
"integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==",
"license": "MIT",
"dependencies": {
"chalk": "^5.3.0",
"is-unicode-supported": "^1.3.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/log-symbols/node_modules/is-unicode-supported": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz",
"integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lru-cache": { "node_modules/lru-cache": {
"version": "10.4.3", "version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
@ -1274,18 +1183,6 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/mimic-function": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
"integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/minipass": { "node_modules/minipass": {
"version": "7.1.2", "version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
@ -1515,79 +1412,6 @@
"node": "^18.17.0 || >=20.5.0" "node": "^18.17.0 || >=20.5.0"
} }
}, },
"node_modules/ora": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz",
"integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==",
"license": "MIT",
"dependencies": {
"chalk": "^5.3.0",
"cli-cursor": "^5.0.0",
"cli-spinners": "^2.9.2",
"is-interactive": "^2.0.0",
"is-unicode-supported": "^2.0.0",
"log-symbols": "^6.0.0",
"stdin-discarder": "^0.2.2",
"string-width": "^7.2.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ora/node_modules/ansi-regex": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/ora/node_modules/emoji-regex": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
"integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
"license": "MIT"
},
"node_modules/ora/node_modules/string-width": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^10.3.0",
"get-east-asian-width": "^1.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ora/node_modules/strip-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/oxlint": { "node_modules/oxlint": {
"version": "1.22.0", "version": "1.22.0",
"resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.22.0.tgz", "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.22.0.tgz",
@ -1687,37 +1511,6 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/restore-cursor": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
"integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
"license": "MIT",
"dependencies": {
"onetime": "^7.0.0",
"signal-exit": "^4.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/restore-cursor/node_modules/onetime": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
"integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
"license": "MIT",
"dependencies": {
"mimic-function": "^5.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/retry": { "node_modules/retry": {
"version": "0.12.0", "version": "0.12.0",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
@ -1835,18 +1628,6 @@
"node": "^18.17.0 || >=20.5.0" "node": "^18.17.0 || >=20.5.0"
} }
}, },
"node_modules/stdin-discarder": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz",
"integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/string-width": { "node_modules/string-width": {
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
@ -2096,14 +1877,13 @@
"version": "1.0.0", "version": "1.0.0",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"certifi": "^14.5.15", "certifi": "14.5.15",
"chalk": "5.4.1", "chalk": "5.4.1",
"https-proxy-agent": "7.0.6", "https-proxy-agent": "7.0.6",
"ini": "^6.0.0", "ini": "^6.0.0",
"make-fetch-happen": "14.0.3", "make-fetch-happen": "14.0.3",
"node-forge": "1.3.1", "node-forge": "1.3.1",
"npm-registry-fetch": "18.0.2", "npm-registry-fetch": "18.0.2",
"ora": "8.2.0",
"semver": "7.7.2" "semver": "7.7.2"
}, },
"bin": { "bin": {
@ -2113,10 +1893,10 @@
"aikido-npx": "bin/aikido-npx.js", "aikido-npx": "bin/aikido-npx.js",
"aikido-pip": "bin/aikido-pip.js", "aikido-pip": "bin/aikido-pip.js",
"aikido-pip3": "bin/aikido-pip3.js", "aikido-pip3": "bin/aikido-pip3.js",
"aikido-python": "bin/aikido-python.js",
"aikido-python3": "bin/aikido-python3.js",
"aikido-pnpm": "bin/aikido-pnpm.js", "aikido-pnpm": "bin/aikido-pnpm.js",
"aikido-pnpx": "bin/aikido-pnpx.js", "aikido-pnpx": "bin/aikido-pnpx.js",
"aikido-python": "bin/aikido-python.js",
"aikido-python3": "bin/aikido-python3.js",
"aikido-yarn": "bin/aikido-yarn.js", "aikido-yarn": "bin/aikido-yarn.js",
"safe-chain": "bin/safe-chain.js" "safe-chain": "bin/safe-chain.js"
}, },

View file

@ -35,23 +35,22 @@
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"description": "The Aikido Safe Chain wraps around the [npm cli](https://github.com/npm/cli), [npx](https://github.com/npm/cli/blob/latest/docs/content/commands/npx.md), [yarn](https://yarnpkg.com/), [pnpm](https://pnpm.io/), [pnpx](https://pnpm.io/cli/dlx), [bun](https://bun.sh/), and [bunx](https://bun.sh/docs/cli/bunx) to provide extra checks before installing new packages. This tool will detect when a package contains malware and prompt you to exit, preventing npm, npx, yarn, pnpm, pnpx, bun, or bunx from downloading or running the malware.", "description": "The Aikido Safe Chain wraps around the [npm cli](https://github.com/npm/cli), [npx](https://github.com/npm/cli/blob/latest/docs/content/commands/npx.md), [yarn](https://yarnpkg.com/), [pnpm](https://pnpm.io/), [pnpx](https://pnpm.io/cli/dlx), [bun](https://bun.sh/), and [bunx](https://bun.sh/docs/cli/bunx) to provide extra checks before installing new packages. This tool will detect when a package contains malware and prompt you to exit, preventing npm, npx, yarn, pnpm, pnpx, bun, or bunx from downloading or running the malware.",
"dependencies": { "dependencies": {
"certifi": "^14.5.15", "certifi": "14.5.15",
"chalk": "5.4.1", "chalk": "5.4.1",
"https-proxy-agent": "7.0.6", "https-proxy-agent": "7.0.6",
"ini": "^6.0.0", "ini": "6.0.0",
"make-fetch-happen": "14.0.3", "make-fetch-happen": "14.0.3",
"node-forge": "1.3.1", "node-forge": "1.3.1",
"npm-registry-fetch": "18.0.2", "npm-registry-fetch": "18.0.2",
"ora": "8.2.0",
"semver": "7.7.2" "semver": "7.7.2"
}, },
"devDependencies": { "devDependencies": {
"@types/ini": "^4.1.1", "@types/ini": "^4.1.1",
"@types/make-fetch-happen": "^10.0.4", "@types/make-fetch-happen": "^10.0.4",
"@types/node": "^18.19.130", "@types/node": "^18.19.130",
"@types/node-forge": "^1.3.14",
"@types/npm-registry-fetch": "^8.0.9", "@types/npm-registry-fetch": "^8.0.9",
"@types/semver": "^7.7.1", "@types/semver": "^7.7.1",
"@types/node-forge": "^1.3.14",
"typescript": "^5.9.3" "typescript": "^5.9.3"
}, },
"main": "src/main.js", "main": "src/main.js",

View file

@ -1,6 +1,5 @@
// oxlint-disable no-console // oxlint-disable no-console
import chalk from "chalk"; import chalk from "chalk";
import ora from "ora";
import { isCi } from "./environment.js"; import { isCi } from "./environment.js";
import { import {
getLoggingLevel, getLoggingLevel,
@ -98,61 +97,6 @@ function writeOrBuffer(messageFunction) {
} }
} }
/**
* @typedef {Object} Spinner
* @property {(message: string) => void} succeed
* @property {(message: string) => void} fail
* @property {() => void} stop
* @property {(message: string) => void} setText
*/
/**
* @param {string} message
*
* @returns {Spinner}
*/
function startProcess(message) {
if (isSilentMode()) {
return {
succeed: () => {},
fail: () => {},
stop: () => {},
setText: () => {},
};
}
if (isCi()) {
return {
succeed: (message) => {
writeInformation(message);
},
fail: (message) => {
writeError(message);
},
stop: () => {},
setText: (message) => {
writeInformation(message);
},
};
} else {
const spinner = ora(message).start();
return {
succeed: (message) => {
spinner.succeed(message);
},
fail: (message) => {
spinner.fail(message);
},
stop: () => {
spinner.stop();
},
setText: (message) => {
spinner.text = message;
},
};
}
}
function startBufferingLogs() { function startBufferingLogs() {
state.bufferOutput = true; state.bufferOutput = true;
state.bufferedMessages = []; state.bufferedMessages = [];
@ -173,7 +117,6 @@ export const ui = {
writeError, writeError,
writeExitWithoutInstallingMaliciousPackages, writeExitWithoutInstallingMaliciousPackages,
emptyLine, emptyLine,
startProcess,
startBufferingLogs, startBufferingLogs,
writeBufferedLogsAndStopBuffering, writeBufferedLogsAndStopBuffering,
}; };

View file

@ -29,36 +29,19 @@ export async function scanCommand(args) {
} }
let timedOut = false; let timedOut = false;
const spinner = ui.startProcess(
"Safe-chain: Scanning for malicious packages..."
);
/** @type {import("./audit/index.js").AuditResult | undefined} */ /** @type {import("./audit/index.js").AuditResult | undefined} */
let audit; let audit;
await Promise.race([ await Promise.race([
(async () => { (async () => {
try { const packageManager = getPackageManager();
const packageManager = getPackageManager(); const changes = await packageManager.getDependencyUpdatesForCommand(args);
const changes = await packageManager.getDependencyUpdatesForCommand(
args
);
if (timedOut) { if (timedOut) {
return; return;
}
if (changes.length > 0) {
spinner.setText(
`Safe-chain: Scanning ${changes.length} package(s)...`
);
}
audit = await auditChanges(changes);
} catch (/** @type any */ error) {
spinner.fail(`Safe-chain: Error while scanning.`);
throw error;
} }
audit = await auditChanges(changes);
})(), })(),
setTimeout(getScanTimeout()).then(() => { setTimeout(getScanTimeout()).then(() => {
timedOut = true; timedOut = true;
@ -66,15 +49,13 @@ export async function scanCommand(args) {
]); ]);
if (timedOut) { if (timedOut) {
spinner.fail("Safe-chain: Timeout exceeded while scanning.");
throw new Error("Timeout exceeded while scanning npm install command."); throw new Error("Timeout exceeded while scanning npm install command.");
} }
if (!audit || audit.isAllowed) { if (!audit || audit.isAllowed) {
spinner.stop();
return 0; return 0;
} else { } else {
printMaliciousChanges(audit.disallowedChanges, spinner); printMaliciousChanges(audit.disallowedChanges);
onMalwareFound(); onMalwareFound();
return 1; return 1;
} }
@ -82,12 +63,12 @@ export async function scanCommand(args) {
/** /**
* @param {import("./audit/index.js").PackageChange[]} changes * @param {import("./audit/index.js").PackageChange[]} changes
* @param spinner {import("../environment/userInteraction.js").Spinner}
*
* @return {void} * @return {void}
*/ */
function printMaliciousChanges(changes, spinner) { function printMaliciousChanges(changes) {
spinner.fail("Safe-chain: " + chalk.bold("Malicious changes detected:")); ui.writeInformation(
chalk.red("✖") + " Safe-chain: " + chalk.bold("Malicious changes detected:")
);
for (const change of changes) { for (const change of changes) {
ui.writeInformation(` - ${change.name}@${change.version}`); ui.writeInformation(` - ${change.name}@${change.version}`);

View file

@ -5,12 +5,6 @@ import { setTimeout } from "node:timers/promises";
describe("scanCommand", async () => { describe("scanCommand", async () => {
const getScanTimeoutMock = mock.fn(() => 1000); const getScanTimeoutMock = mock.fn(() => 1000);
const mockGetDependencyUpdatesForCommand = mock.fn(); const mockGetDependencyUpdatesForCommand = mock.fn();
const mockStartProcess = mock.fn(() => ({
setText: () => {},
succeed: () => {},
fail: () => {},
stop: () => {},
}));
// import { getPackageManager } from "../packagemanager/currentPackageManager.js"; // import { getPackageManager } from "../packagemanager/currentPackageManager.js";
mock.module("../packagemanager/currentPackageManager.js", { mock.module("../packagemanager/currentPackageManager.js", {
@ -36,7 +30,6 @@ describe("scanCommand", async () => {
mock.module("../environment/userInteraction.js", { mock.module("../environment/userInteraction.js", {
namedExports: { namedExports: {
ui: { ui: {
startProcess: mockStartProcess,
writeError: () => {}, writeError: () => {},
writeInformation: () => {}, writeInformation: () => {},
writeWarning: () => {}, writeWarning: () => {},
@ -75,51 +68,20 @@ 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 progressWasStopped = false;
mockStartProcess.mock.mockImplementationOnce(() => ({
setText: () => {},
succeed: () => {},
fail: () => {},
stop: () => {
progressWasStopped = true;
},
}));
mockGetDependencyUpdatesForCommand.mock.mockImplementation(() => []); mockGetDependencyUpdatesForCommand.mock.mockImplementation(() => []);
await scanCommand(["install", "lodash"]); await scanCommand(["install", "lodash"]);
assert.equal(progressWasStopped, true);
}); });
it("should succeed when changes are not malicious", async () => { it("should succeed when changes are not malicious", async () => {
let progressWasStopped = false;
mockStartProcess.mock.mockImplementationOnce(() => ({
setText: () => {},
succeed: () => {},
fail: () => {},
stop: () => {
progressWasStopped = true;
},
}));
mockGetDependencyUpdatesForCommand.mock.mockImplementation(() => [ mockGetDependencyUpdatesForCommand.mock.mockImplementation(() => [
{ name: "lodash", version: "4.17.21" }, { name: "lodash", version: "4.17.21" },
]); ]);
await scanCommand(["install", "lodash"]); await scanCommand(["install", "lodash"]);
assert.equal(progressWasStopped, true);
}); });
it("should throw an error when timing out", async () => { it("should throw an error when timing out", async () => {
let failureMessageWasSet = false;
mockStartProcess.mock.mockImplementationOnce(() => ({
setText: () => {},
succeed: () => {},
fail: () => {
failureMessageWasSet = true;
},
stop: () => {},
}));
getScanTimeoutMock.mock.mockImplementationOnce(() => 100); getScanTimeoutMock.mock.mockImplementationOnce(() => 100);
mockGetDependencyUpdatesForCommand.mock.mockImplementation(async () => { mockGetDependencyUpdatesForCommand.mock.mockImplementation(async () => {
await setTimeout(150); await setTimeout(150);
@ -127,83 +89,9 @@ describe("scanCommand", async () => {
}); });
await assert.rejects(scanCommand(["install", "lodash"])); await assert.rejects(scanCommand(["install", "lodash"]));
assert.equal(failureMessageWasSet, true);
}); });
it("should fail and return 1 malicious changes are detected", async () => { it("should fail and return 1 malicious changes are detected", async () => {
let failureMessageWasSet = false;
mockStartProcess.mock.mockImplementationOnce(() => ({
setText: () => {},
succeed: () => {},
fail: () => {
failureMessageWasSet = true;
},
stop: () => {},
}));
mockGetDependencyUpdatesForCommand.mock.mockImplementation(() => [
{ name: "malicious", version: "1.0.0" },
]);
const result = await scanCommand(["install", "malicious"]);
assert.equal(failureMessageWasSet, true);
assert.equal(result, 1);
});
it("should not report a timeout when the user takes a long time to respond (it should not affect the timeout)", async () => {
let failureMessages = [];
mockStartProcess.mock.mockImplementationOnce(() => ({
setText: () => {},
succeed: () => {},
fail: (message) => {
failureMessages.push(message);
},
stop: () => {},
}));
getScanTimeoutMock.mock.mockImplementationOnce(() => 100);
mockGetDependencyUpdatesForCommand.mock.mockImplementation(async () => {
return [{ name: "malicious", version: "4.17.21" }];
});
await scanCommand(["install", "malicious"]);
assert.equal(failureMessages.length, 1);
const failureMessage = failureMessages[0];
assert.equal(failureMessage.toLowerCase().includes("timeout"), false);
assert.equal(failureMessage.toLowerCase().includes("malicious"), true);
});
it("should exit immediately when malicious changes are detected in block mode", async () => {
let failureMessageWasSet = false;
mockStartProcess.mock.mockImplementationOnce(() => ({
setText: () => {},
succeed: () => {},
fail: () => {
failureMessageWasSet = true;
},
stop: () => {},
}));
mockGetDependencyUpdatesForCommand.mock.mockImplementation(() => [
{ name: "malicious", version: "1.0.0" },
]);
const result = await scanCommand(["install", "malicious"]);
assert.equal(failureMessageWasSet, true);
assert.equal(result, 1);
});
it("should exit immediately when malicious changes are detected in block mode without prompting", async () => {
mockStartProcess.mock.mockImplementationOnce(() => ({
setText: () => {},
succeed: () => {},
fail: () => {},
stop: () => {},
}));
mockGetDependencyUpdatesForCommand.mock.mockImplementation(() => [ mockGetDependencyUpdatesForCommand.mock.mockImplementation(() => [
{ name: "malicious", version: "1.0.0" }, { name: "malicious", version: "1.0.0" },
]); ]);