Skip to content
Open
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
51 changes: 27 additions & 24 deletions src/cli/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ function parseFailOn(value: string | undefined): string {
return value;
}

function requireFlagValue(flag: string, value: string | undefined, noun: string): string {
if (value === undefined || value === "" || value.startsWith("-")) {
throw new Error(`${flag} requires a ${noun} argument`);
}
return value;
}

function parseSbomFormat(value: string | undefined): SbomFormat {
const valid = Object.keys(SBOM_FORMAT_ALIASES).join(", ");
if (value === undefined || value.startsWith("-")) {
Expand Down Expand Up @@ -243,19 +250,19 @@ export function parseArgs(argv: string[]): {
continue;
}
if (arg === FLAG_AUDIT_LOG) {
options.auditLog = argv[++i];
options.auditLog = requireFlagValue(FLAG_AUDIT_LOG, argv[++i], "path");
continue;
}
if (arg.startsWith(`${FLAG_AUDIT_LOG}=`)) {
options.auditLog = arg.slice(FLAG_AUDIT_LOG.length + 1);
options.auditLog = requireFlagValue(FLAG_AUDIT_LOG, arg.slice(FLAG_AUDIT_LOG.length + 1), "path");
continue;
}
if (arg === FLAG_RULE) {
options.rule = argv[++i];
options.rule = requireFlagValue(FLAG_RULE, argv[++i], "rule id");
continue;
}
if (arg.startsWith(`${FLAG_RULE}=`)) {
options.rule = arg.slice(FLAG_RULE.length + 1);
options.rule = requireFlagValue(FLAG_RULE, arg.slice(FLAG_RULE.length + 1), "rule id");
continue;
}
if (arg === FLAG_FAIL_ON) {
Expand Down Expand Up @@ -338,11 +345,11 @@ export function parseArgs(argv: string[]): {
continue;
}
if (arg === FLAG_OFFLINE_DB) {
options.offlineDb = argv[++i];
options.offlineDb = requireFlagValue(FLAG_OFFLINE_DB, argv[++i], "path");
continue;
}
if (arg.startsWith(`${FLAG_OFFLINE_DB}=`)) {
options.offlineDb = arg.slice(FLAG_OFFLINE_DB.length + 1);
options.offlineDb = requireFlagValue(FLAG_OFFLINE_DB, arg.slice(FLAG_OFFLINE_DB.length + 1), "path");
continue;
}
if (arg === FLAG_ALL) {
Expand All @@ -358,27 +365,27 @@ export function parseArgs(argv: string[]): {
continue;
}
if (arg === FLAG_BATCH_SIZE) {
options.batchSize = argv[++i] ?? options.batchSize;
options.batchSize = requireFlagValue(FLAG_BATCH_SIZE, argv[++i], "number");
continue;
}
if (arg.startsWith(`${FLAG_BATCH_SIZE}=`)) {
options.batchSize = arg.slice(FLAG_BATCH_SIZE.length + 1);
options.batchSize = requireFlagValue(FLAG_BATCH_SIZE, arg.slice(FLAG_BATCH_SIZE.length + 1), "number");
continue;
}
if (arg === FLAG_CACHE_DIR) {
options.cacheDir = argv[++i];
options.cacheDir = requireFlagValue(FLAG_CACHE_DIR, argv[++i], "path");
continue;
}
if (arg.startsWith(`${FLAG_CACHE_DIR}=`)) {
options.cacheDir = arg.slice(FLAG_CACHE_DIR.length + 1);
options.cacheDir = requireFlagValue(FLAG_CACHE_DIR, arg.slice(FLAG_CACHE_DIR.length + 1), "path");
continue;
}
if (arg === FLAG_OSV_URL) {
options.osvUrl = argv[++i];
options.osvUrl = requireFlagValue(FLAG_OSV_URL, argv[++i], "url");
continue;
}
if (arg.startsWith(`${FLAG_OSV_URL}=`)) {
options.osvUrl = arg.slice(FLAG_OSV_URL.length + 1);
options.osvUrl = requireFlagValue(FLAG_OSV_URL, arg.slice(FLAG_OSV_URL.length + 1), "url");
continue;
}
if (arg === FLAG_ALLOW_PRIVATE_OSV_URL) {
Expand All @@ -402,19 +409,19 @@ export function parseArgs(argv: string[]): {
continue;
}
if (arg === FLAG_SEARCH_DEPTH) {
options.searchDepth = argv[++i] ?? options.searchDepth;
options.searchDepth = requireFlagValue(FLAG_SEARCH_DEPTH, argv[++i], "number");
continue;
}
if (arg.startsWith(`${FLAG_SEARCH_DEPTH}=`)) {
options.searchDepth = arg.slice(FLAG_SEARCH_DEPTH.length + 1);
options.searchDepth = requireFlagValue(FLAG_SEARCH_DEPTH, arg.slice(FLAG_SEARCH_DEPTH.length + 1), "number");
continue;
}
if (arg === FLAG_MIN_SEVERITY) {
options.minSeverity = argv[++i] ?? options.minSeverity;
options.minSeverity = requireFlagValue(FLAG_MIN_SEVERITY, argv[++i], "level");
continue;
}
if (arg.startsWith(`${FLAG_MIN_SEVERITY}=`)) {
options.minSeverity = arg.slice(FLAG_MIN_SEVERITY.length + 1);
options.minSeverity = requireFlagValue(FLAG_MIN_SEVERITY, arg.slice(FLAG_MIN_SEVERITY.length + 1), "level");
continue;
}
if (arg === FLAG_USAGE || arg === FLAG_USAGE_HINTS) {
Expand Down Expand Up @@ -449,15 +456,11 @@ export function parseArgs(argv: string[]): {
continue;
}
if (arg === FLAG_CA_CERT) {
const val = argv[++i];
if (!val) {
throw new Error("--ca-cert requires a path argument");
}
options.caCert = val;
options.caCert = requireFlagValue(FLAG_CA_CERT, argv[++i], "path");
continue;
}
if (arg.startsWith(`${FLAG_CA_CERT}=`)) {
options.caCert = arg.slice(FLAG_CA_CERT.length + 1);
options.caCert = requireFlagValue(FLAG_CA_CERT, arg.slice(FLAG_CA_CERT.length + 1), "path");
continue;
}
if (arg === FLAG_SARIF) {
Expand All @@ -483,11 +486,11 @@ export function parseArgs(argv: string[]): {
continue;
}
if (arg === FLAG_AUDIT_LOG) {
options.auditLog = argv[++i];
options.auditLog = requireFlagValue(FLAG_AUDIT_LOG, argv[++i], "path");
continue;
}
if (arg.startsWith(`${FLAG_AUDIT_LOG}=`)) {
options.auditLog = arg.slice(FLAG_AUDIT_LOG.length + 1);
options.auditLog = requireFlagValue(FLAG_AUDIT_LOG, arg.slice(FLAG_AUDIT_LOG.length + 1), "path");
continue;
}
if (arg === FLAG_CHECK_OVERRIDES) {
Expand Down
38 changes: 38 additions & 0 deletions tests/cli/scan-value-threshold-args.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { parseArgs } from "../../src/cli/args.js";

const SCAN_THRESHOLD_FLAGS: Array<{
flag: string;
noun: string;
option: "minSeverity" | "searchDepth" | "batchSize";
value: string;
}> = [
{ flag: "--min-severity", noun: "level", option: "minSeverity", value: "high" },
{ flag: "--search-depth", noun: "number", option: "searchDepth", value: "6" },
{ flag: "--batch-size", noun: "number", option: "batchSize", value: "50" },
];

describe("parseArgs - threshold and size flags (scan)", () => {
it.each(SCAN_THRESHOLD_FLAGS)(
"rejects $flag when the next token is another flag",
({ flag, noun }) => {
expect(() => parseArgs([".", flag, "--all"])).toThrow(`${flag} requires a ${noun} argument`);
},
);

it.each(SCAN_THRESHOLD_FLAGS)("rejects $flag when it is missing at the end", ({ flag, noun }) => {
expect(() => parseArgs([".", flag])).toThrow(`${flag} requires a ${noun} argument`);
});

it.each(SCAN_THRESHOLD_FLAGS)("rejects $flag with an empty = value", ({ flag, noun }) => {
expect(() => parseArgs([".", `${flag}=`])).toThrow(`${flag} requires a ${noun} argument`);
});

it.each(SCAN_THRESHOLD_FLAGS)("rejects $flag when the = value is another flag", ({ flag, noun }) => {
expect(() => parseArgs([".", `${flag}=--all`])).toThrow(`${flag} requires a ${noun} argument`);
});

it.each(SCAN_THRESHOLD_FLAGS)("parses $flag with a value in both forms", ({ flag, option, value }) => {
expect(parseArgs([".", flag, value]).options[option]).toBe(value);
expect(parseArgs([".", `${flag}=${value}`]).options[option]).toBe(value);
});
});
75 changes: 75 additions & 0 deletions tests/cli/value-flag-args.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { parseArgs } from "../../src/cli/args.js";

const SCAN_VALUE_FLAGS: Array<{
flag: string;
noun: string;
option: "offlineDb" | "cacheDir" | "osvUrl" | "auditLog" | "caCert";
value: string;
}> = [
{ flag: "--offline-db", noun: "path", option: "offlineDb", value: "/tmp/advisories.db" },
{ flag: "--cache-dir", noun: "path", option: "cacheDir", value: "/tmp/cache" },
{ flag: "--osv-url", noun: "url", option: "osvUrl", value: "https://example.com/osv" },
{ flag: "--audit-log", noun: "path", option: "auditLog", value: "/tmp/audit.ndjson" },
{ flag: "--ca-cert", noun: "path", option: "caCert", value: "/tmp/ca.pem" },
];

const OVERRIDES_VALUE_FLAGS: Array<{
flag: string;
noun: string;
option: "auditLog" | "rule";
value: string;
}> = [
{ flag: "--audit-log", noun: "path", option: "auditLog", value: "/tmp/audit.ndjson" },
{ flag: "--rule", noun: "rule id", option: "rule", value: "OA001" },
];

describe("parseArgs - required value flags (scan)", () => {
it.each(SCAN_VALUE_FLAGS)(
"rejects $flag when the next token is another flag",
({ flag, noun }) => {
expect(() => parseArgs([".", flag, "--json"])).toThrow(`${flag} requires a ${noun} argument`);
},
);

it.each(SCAN_VALUE_FLAGS)("rejects $flag when it is missing at the end", ({ flag, noun }) => {
expect(() => parseArgs([".", flag])).toThrow(`${flag} requires a ${noun} argument`);
});

it.each(SCAN_VALUE_FLAGS)("rejects $flag with an empty = value", ({ flag, noun }) => {
expect(() => parseArgs([".", `${flag}=`])).toThrow(`${flag} requires a ${noun} argument`);
});

it.each(SCAN_VALUE_FLAGS)("parses $flag with a value in both forms", ({ flag, option, value }) => {
expect(parseArgs([".", flag, value]).options[option]).toBe(value);
expect(parseArgs([".", `${flag}=${value}`]).options[option]).toBe(value);
});
});

describe("parseArgs - required value flags (overrides)", () => {
it.each(OVERRIDES_VALUE_FLAGS)(
"rejects $flag when the next token is another flag",
({ flag, noun }) => {
expect(() => parseArgs(["overrides", ".", flag, "--json"])).toThrow(
`${flag} requires a ${noun} argument`,
);
},
);

it.each(OVERRIDES_VALUE_FLAGS)("rejects $flag when it is missing at the end", ({ flag, noun }) => {
expect(() => parseArgs(["overrides", ".", flag])).toThrow(`${flag} requires a ${noun} argument`);
});

it.each(OVERRIDES_VALUE_FLAGS)("rejects $flag with an empty = value", ({ flag, noun }) => {
expect(() => parseArgs(["overrides", ".", `${flag}=`])).toThrow(
`${flag} requires a ${noun} argument`,
);
});

it.each(OVERRIDES_VALUE_FLAGS)(
"parses $flag with a value in both forms",
({ flag, option, value }) => {
expect(parseArgs(["overrides", ".", flag, value]).options[option]).toBe(value);
expect(parseArgs(["overrides", ".", `${flag}=${value}`]).options[option]).toBe(value);
},
);
});
Loading