Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,9 @@ if (parsedArgs) {
// route to dedicated multi-folder handler instead of single-lockfile scan
const nestedLockfiles = findNestedLockfiles(projectPath, searchDepth);
if (!hasRootLockfile(projectPath) && nestedLockfiles.length >= 2) {
await handleMultiFolderScan({ projectRoot: projectPath, batchSize, options });
return;
const exitCode = await handleMultiFolderScan({ projectRoot: projectPath, batchSize, options });
auditLogHandle.close();
process.exit(exitCode);
}

let advisorySourceLine = "";
Expand Down
16 changes: 7 additions & 9 deletions src/scan/multi-folder-scan.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import path from "node:path";
import process from "node:process";
import type { Finding, ParsedOptions, PackageRef, SeverityLabel } from "../types.js";
import type { Finding, ParsedOptions, PackageRef, SeverityLabel, ExitCode } from "../types.js";
import { EXIT_OK, EXIT_FINDINGS, EXIT_ERROR } from "../types.js";
import type { SuggestedFixCommandPlan } from "../remediation/fix-commands.js";
import type { ScanInput } from "../types.js";
import { loadMultiplePackages } from "../parsers/multi-package.js";
Expand Down Expand Up @@ -96,25 +97,22 @@ export async function handleMultiFolderScan(params: {
projectRoot: string;
batchSize: number;
options: ParsedOptions;
}): Promise<void> {
}): Promise<ExitCode> {
const results = await runMultiFolderScan(params);

if (results.length === 0) {
console.log(chalk.yellow("No scannable packages found in any subfolder."));
process.exit(0);
return;
return EXIT_OK;
}

if (params.options.fix) {
console.error(chalk.yellow("--fix is not yet supported in multi-folder mode. Run cve-lite . from each subfolder individually."));
process.exit(1);
return;
return EXIT_ERROR;
}

if (params.options.sarif || params.options.cdx) {
console.error(chalk.yellow("--sarif and --cdx are not yet supported in multi-folder mode."));
process.exit(1);
return;
return EXIT_ERROR;
}

if (params.options.json) {
Expand Down Expand Up @@ -173,5 +171,5 @@ export async function handleMultiFolderScan(params: {
const failLevel = normalizeSeverity(params.options.failOn);
const allSorted = results.flatMap(r => r.sorted);
const shouldFail = allSorted.some(f => severityOrder[f.severity] >= severityOrder[failLevel]);
process.exit(shouldFail ? 1 : 0);
return shouldFail ? EXIT_FINDINGS : EXIT_OK;
}
5 changes: 2 additions & 3 deletions tests/cli-integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -669,14 +669,13 @@ describe("CLI integration", () => {
"/project/sessionManager/package-lock.json",
"/project/apiServer/package-lock.json",
]);
// real handleMultiFolderScan always calls process.exit; mirror that in the mock
handleMultiFolderScanMock.mockImplementation(async () => { process.exit(0); });
const { EXIT_OK } = await import("../src/types.js");
handleMultiFolderScanMock.mockResolvedValue(EXIT_OK);

const result = await runIndexModule();

expect(result.exitCode).toBe(0);
expect(handleMultiFolderScanMock).toHaveBeenCalledTimes(1);
expect(loadPackagesMock).not.toHaveBeenCalled();
});

it("does not route to multi-folder scan when a root lockfile exists", async () => {
Expand Down
31 changes: 31 additions & 0 deletions tests/multi-folder-scan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,14 @@ const baseOptions = {
} as any;

let runMultiFolderScan: any;
let handleMultiFolderScan: any;
let EXIT_ERROR: number;

beforeAll(async () => {
const mod = await import("../src/scan/multi-folder-scan.js");
runMultiFolderScan = mod.runMultiFolderScan;
handleMultiFolderScan = mod.handleMultiFolderScan;
({ EXIT_ERROR } = await import("../src/types.js"));
});

describe("runMultiFolderScan", () => {
Expand Down Expand Up @@ -226,3 +230,30 @@ describe("runMultiFolderScan", () => {
);
});
});

describe("handleMultiFolderScan", () => {
beforeEach(() => {
loadMultiplePackagesMock.mockReturnValue([
{ subfolder: "sessionManager", scanInput: makeScanInput() },
]);
});

it("returns EXIT_ERROR when --fix is requested in multi-folder mode", async () => {
await expect(handleMultiFolderScan({
projectRoot: "/project",
batchSize: 100,
options: { ...baseOptions, fix: true },
})).resolves.toBe(EXIT_ERROR);
});

it.each([
["--sarif", { sarif: true }],
["--cdx", { cdx: true }],
])("returns EXIT_ERROR when %s is requested in multi-folder mode", async (_flag, option) => {
await expect(handleMultiFolderScan({
projectRoot: "/project",
batchSize: 100,
options: { ...baseOptions, ...option },
})).resolves.toBe(EXIT_ERROR);
});
});