From 3bd135e13416eb6a2e4ab351e6a926a6891d3e5f Mon Sep 17 00:00:00 2001 From: Martin Torp Date: Tue, 12 Aug 2025 13:34:19 +0200 Subject: [PATCH 1/7] add some extra reachability-specific options --- src/commands/ci/handle-ci.mts | 7 +- src/commands/scan/cmd-scan-create.mts | 73 +++++++++++++++---- src/commands/scan/cmd-scan-create.test.mts | 6 ++ src/commands/scan/create-scan-from-github.mts | 7 +- src/commands/scan/handle-create-new-scan.mts | 41 ++++++++--- 5 files changed, 110 insertions(+), 24 deletions(-) diff --git a/src/commands/ci/handle-ci.mts b/src/commands/ci/handle-ci.mts index 1581ce4d1..4259166bd 100644 --- a/src/commands/ci/handle-ci.mts +++ b/src/commands/ci/handle-ci.mts @@ -37,7 +37,12 @@ export async function handleCi(autoManifest: boolean): Promise { // When 'pendingHead' is true, it requires 'branchName' set and 'tmp' false. pendingHead: true, pullRequest: 0, - reach: false, + reach: { + runReachabilityAnalysis: false, + disableReachAnalytics: false, + reachAnalysisTimeout: 0, + reachAnalysisMemoryLimit: 0, + }, repoName, readOnly: false, report: true, diff --git a/src/commands/scan/cmd-scan-create.mts b/src/commands/scan/cmd-scan-create.mts index b1a3a24ee..fafa8a294 100644 --- a/src/commands/scan/cmd-scan-create.mts +++ b/src/commands/scan/cmd-scan-create.mts @@ -7,7 +7,7 @@ import { outputCreateNewScan } from './output-create-new-scan.mts' import { suggestOrgSlug } from './suggest-org-slug.mts' import { suggestTarget } from './suggest_target.mts' import constants from '../../constants.mts' -import { commonFlags, outputFlags } from '../../flags.mts' +import { type MeowFlags, commonFlags, outputFlags } from '../../flags.mts' import { checkCommandInput } from '../../utils/check-input.mts' import { determineOrgSlug } from '../../utils/determine-org-slug.mts' import { getOutputKind } from '../../utils/get-output-kind.mts' @@ -26,6 +26,25 @@ const { SOCKET_DEFAULT_REPOSITORY, } = constants +const reachabilityFlags: MeowFlags = { + disableReachAnalytics: { + type: 'boolean', + description: + 'Disable reachability analytics sharing with Socket. Also disables caching-based optimizations.', + }, + reachAnalysisMemoryLimit: { + type: 'number', + description: + 'The maximum memory in MB to use for the reachability analysis. The default is 8192MB.', + default: 8192, + }, + reachAnalysisTimeout: { + type: 'number', + description: + 'Set timeout for the reachability analysis. Split analysis runs may cause the total scan time to exceed this timeout significantly.', + }, +} + const config: CliCommandConfig = { commandName: 'create', description: 'Create a new Socket scan and report', @@ -87,19 +106,16 @@ const config: CliCommandConfig = { description: 'Force override the organization slug, overrides the default org from config', }, - readOnly: { + reach: { type: 'boolean', default: false, - description: - 'Similar to --dry-run except it can read from remote, stops before it would create an actual report', + description: 'Run tier 1 full application reachability analysis', }, - reach: { + readOnly: { type: 'boolean', default: false, - // TODO: Temporarily hide option until Coana side is ironed out. - hidden: true, description: - 'Run tier 1 full application reachability analysis during the scanning process', + 'Similar to --dry-run except it can read from remote, stops before it would create an actual report', }, repo: { type: 'string', @@ -125,9 +141,23 @@ const config: CliCommandConfig = { description: 'Set the visibility (true/false) of the scan in your dashboard.', }, + + // Reachability scan flags + ...reachabilityFlags, }, // TODO: Your project's "socket.yml" file's "projectIgnorePaths". - help: (command, config) => ` + help: (command, config) => { + const allFlags = config.flags || {} + const generalFlags: MeowFlags = {} + + // Separate general flags from reachability flags + for (const [key, value] of Object.entries(allFlags)) { + if (!reachabilityFlags[key]) { + generalFlags[key] = value + } + } + + return ` Usage $ ${command} [options] [TARGET...] @@ -136,7 +166,10 @@ const config: CliCommandConfig = { - Permissions: full-scans:create Options - ${getFlagListOutput(config.flags)} + ${getFlagListOutput(generalFlags)} + + Reachability Options (when --reach is used) + ${getFlagListOutput(reachabilityFlags)} Uploads the specified dependency manifest files for Go, Gradle, JavaScript, Kotlin, Python, and Scala. Files like "package.json" and "requirements.txt". @@ -172,7 +205,8 @@ const config: CliCommandConfig = { $ ${command} $ ${command} ./proj --json $ ${command} --repo=test-repo --branch=main ./package.json - `, + ` + }, } export const cmdScanCreate = { @@ -199,6 +233,7 @@ async function run( committers, cwd: cwdOverride, defaultBranch, + disableReachAnalytics, dryRun = false, interactive = true, json, @@ -206,6 +241,8 @@ async function run( org: orgFlag, pullRequest, reach, + reachAnalysisMemoryLimit, + reachAnalysisTimeout, readOnly, setAsAlertsPage: pendingHeadFlag, tmp, @@ -221,10 +258,15 @@ async function run( markdown: boolean org: string pullRequest: number - reach: boolean readOnly: boolean setAsAlertsPage: boolean tmp: boolean + + // reachability flags + reach: boolean + disableReachAnalytics: boolean + reachAnalysisTimeout?: number + reachAnalysisMemoryLimit?: number } let { autoManifest, @@ -423,7 +465,12 @@ async function run( outputKind, pendingHead: Boolean(pendingHead), pullRequest: Number(pullRequest), - reach: Boolean(reach), + reach: { + runReachabilityAnalysis: Boolean(reach), + disableReachAnalytics: Boolean(disableReachAnalytics), + reachAnalysisTimeout: Number(reachAnalysisTimeout), + reachAnalysisMemoryLimit: Number(reachAnalysisMemoryLimit), + }, readOnly: Boolean(readOnly), repoName, report, diff --git a/src/commands/scan/cmd-scan-create.test.mts b/src/commands/scan/cmd-scan-create.test.mts index bc783fd64..2674073a0 100644 --- a/src/commands/scan/cmd-scan-create.test.mts +++ b/src/commands/scan/cmd-scan-create.test.mts @@ -35,12 +35,18 @@ describe('socket scan create', async () => { --markdown Output result as markdown --org Force override the organization slug, overrides the default org from config --pull-request Commit hash + --reach Run tier 1 full application reachability analysis --read-only Similar to --dry-run except it can read from remote, stops before it would create an actual report --repo Repository name --report Wait for the scan creation to complete, then basically run \`socket scan report\` on it --set-as-alerts-page When true and if this is the "default branch" then this Scan will be the one reflected on your alerts page. See help for details. Defaults to true. --tmp Set the visibility (true/false) of the scan in your dashboard. + Reachability Options (when --reach is used) + --disable-reach-analytics Disable reachability analytics sharing with Socket. Also disables caching-based optimizations. + --reach-analysis-memory-limit The maximum memory in MB to use for the reachability analysis. The default is 8192MB. + --reach-analysis-timeout Set timeout for the reachability analysis. Split analysis runs may cause the total scan time to exceed this timeout significantly. + Uploads the specified dependency manifest files for Go, Gradle, JavaScript, Kotlin, Python, and Scala. Files like "package.json" and "requirements.txt". If any folder is specified, the ones found in there recursively are uploaded. diff --git a/src/commands/scan/create-scan-from-github.mts b/src/commands/scan/create-scan-from-github.mts index 409ab0389..7b81b6176 100644 --- a/src/commands/scan/create-scan-from-github.mts +++ b/src/commands/scan/create-scan-from-github.mts @@ -239,7 +239,12 @@ async function scanOneRepo( outputKind, pendingHead: true, pullRequest: 0, - reach: false, + reach: { + runReachabilityAnalysis: false, + disableReachAnalytics: false, + reachAnalysisTimeout: 0, + reachAnalysisMemoryLimit: 0, + }, readOnly: false, repoName: repoSlug, report: false, diff --git a/src/commands/scan/handle-create-new-scan.mts b/src/commands/scan/handle-create-new-scan.mts index 579b7fff4..fa2ff771b 100644 --- a/src/commands/scan/handle-create-new-scan.mts +++ b/src/commands/scan/handle-create-new-scan.mts @@ -50,7 +50,12 @@ export async function handleCreateNewScan({ pendingHead: boolean pullRequest: number outputKind: OutputKind - reach: boolean + reach: { + runReachabilityAnalysis: boolean + disableReachAnalytics: boolean + reachAnalysisTimeout: number + reachAnalysisMemoryLimit: number + } readOnly: boolean repoName: string report: boolean @@ -114,15 +119,14 @@ export async function handleCreateNewScan({ let scanPaths: string[] = packagePaths // If reachability is enabled, perform reachability analysis - if (reach) { + if (reach.runReachabilityAnalysis) { const reachResult = await performReachabilityAnalysis({ packagePaths, orgSlug, cwd, repoName, branchName, - outputKind, - interactive, + reachabilityOptions: reach, }) if (!reachResult.ok) { @@ -186,15 +190,19 @@ async function performReachabilityAnalysis({ cwd, orgSlug, packagePaths, + reachabilityOptions, repoName, }: { - packagePaths: string[] - orgSlug: string + branchName: string cwd: string + orgSlug: string + packagePaths: string[] + reachabilityOptions: { + disableReachAnalytics: boolean + reachAnalysisTimeout: number + reachAnalysisMemoryLimit: number + } repoName: string - branchName: string - outputKind: OutputKind - interactive: boolean }): Promise> { logger.info('Starting reachability analysis...') @@ -249,6 +257,21 @@ async function performReachabilityAnalysis({ '--socket-mode', constants.DOT_SOCKET_DOT_FACTS_JSON, '--disable-report-submission', + ...(reachabilityOptions.reachAnalysisTimeout + ? [ + '--analysis-timeout', + reachabilityOptions.reachAnalysisTimeout.toString(), + ] + : []), + ...(reachabilityOptions.reachAnalysisMemoryLimit + ? [ + '--memory-limit', + reachabilityOptions.reachAnalysisMemoryLimit.toString(), + ] + : []), + ...(reachabilityOptions.disableReachAnalytics + ? ['--disable-analytics-sharing'] + : []), '--manifests-tar-hash', tarHash, ], From 6ffb6e8f05fd00540804791cecccdc9fd8763639 Mon Sep 17 00:00:00 2001 From: Martin Torp Date: Wed, 13 Aug 2025 15:37:15 +0200 Subject: [PATCH 2/7] add --reachEcosystems option. rename --disableReachAnalytics to --reachDisableAnalytics --- src/commands/ci/handle-ci.mts | 3 +- src/commands/scan/cmd-scan-create.mts | 22 +++- src/commands/scan/cmd-scan-create.test.mts | 3 +- src/commands/scan/create-scan-from-github.mts | 3 +- src/commands/scan/handle-create-new-scan.mts | 21 ++- src/utils/ecosystem.mts | 120 ++++++++++++++++++ 6 files changed, 162 insertions(+), 10 deletions(-) create mode 100644 src/utils/ecosystem.mts diff --git a/src/commands/ci/handle-ci.mts b/src/commands/ci/handle-ci.mts index 4259166bd..48089d248 100644 --- a/src/commands/ci/handle-ci.mts +++ b/src/commands/ci/handle-ci.mts @@ -39,9 +39,10 @@ export async function handleCi(autoManifest: boolean): Promise { pullRequest: 0, reach: { runReachabilityAnalysis: false, - disableReachAnalytics: false, + reachDisableAnalytics: false, reachAnalysisTimeout: 0, reachAnalysisMemoryLimit: 0, + reachEcosystems: [], }, repoName, readOnly: false, diff --git a/src/commands/scan/cmd-scan-create.mts b/src/commands/scan/cmd-scan-create.mts index fafa8a294..551cd65f2 100644 --- a/src/commands/scan/cmd-scan-create.mts +++ b/src/commands/scan/cmd-scan-create.mts @@ -10,6 +10,10 @@ import constants from '../../constants.mts' import { type MeowFlags, commonFlags, outputFlags } from '../../flags.mts' import { checkCommandInput } from '../../utils/check-input.mts' import { determineOrgSlug } from '../../utils/determine-org-slug.mts' +import { + type EcosystemString, + getEcosystemChoicesForMeow, +} from '../../utils/ecosystem.mts' import { getOutputKind } from '../../utils/get-output-kind.mts' import { getRepoName, gitBranch } from '../../utils/git.mts' import { meowOrExit } from '../../utils/meow-with-subcommands.mts' @@ -27,7 +31,7 @@ const { } = constants const reachabilityFlags: MeowFlags = { - disableReachAnalytics: { + reachDisableAnalytics: { type: 'boolean', description: 'Disable reachability analytics sharing with Socket. Also disables caching-based optimizations.', @@ -43,6 +47,13 @@ const reachabilityFlags: MeowFlags = { description: 'Set timeout for the reachability analysis. Split analysis runs may cause the total scan time to exceed this timeout significantly.', }, + reachEcosystems: { + type: 'string', + isMultiple: true, + choices: getEcosystemChoicesForMeow(), + description: + 'List of ecosystems to conduct reachability analysis on. Defaults to all ecosystems.', + }, } const config: CliCommandConfig = { @@ -233,7 +244,6 @@ async function run( committers, cwd: cwdOverride, defaultBranch, - disableReachAnalytics, dryRun = false, interactive = true, json, @@ -243,6 +253,8 @@ async function run( reach, reachAnalysisMemoryLimit, reachAnalysisTimeout, + reachDisableAnalytics, + reachEcosystems, readOnly, setAsAlertsPage: pendingHeadFlag, tmp, @@ -264,9 +276,10 @@ async function run( // reachability flags reach: boolean - disableReachAnalytics: boolean reachAnalysisTimeout?: number reachAnalysisMemoryLimit?: number + reachEcosystems: EcosystemString[] + reachDisableAnalytics: boolean } let { autoManifest, @@ -467,9 +480,10 @@ async function run( pullRequest: Number(pullRequest), reach: { runReachabilityAnalysis: Boolean(reach), - disableReachAnalytics: Boolean(disableReachAnalytics), + reachDisableAnalytics: Boolean(reachDisableAnalytics), reachAnalysisTimeout: Number(reachAnalysisTimeout), reachAnalysisMemoryLimit: Number(reachAnalysisMemoryLimit), + reachEcosystems, }, readOnly: Boolean(readOnly), repoName, diff --git a/src/commands/scan/cmd-scan-create.test.mts b/src/commands/scan/cmd-scan-create.test.mts index 2674073a0..ade1fdbbe 100644 --- a/src/commands/scan/cmd-scan-create.test.mts +++ b/src/commands/scan/cmd-scan-create.test.mts @@ -43,9 +43,10 @@ describe('socket scan create', async () => { --tmp Set the visibility (true/false) of the scan in your dashboard. Reachability Options (when --reach is used) - --disable-reach-analytics Disable reachability analytics sharing with Socket. Also disables caching-based optimizations. --reach-analysis-memory-limit The maximum memory in MB to use for the reachability analysis. The default is 8192MB. --reach-analysis-timeout Set timeout for the reachability analysis. Split analysis runs may cause the total scan time to exceed this timeout significantly. + --reach-disable-analytics Disable reachability analytics sharing with Socket. Also disables caching-based optimizations. + --reach-ecosystems List of ecosystems to conduct reachability analysis on. Defaults to all ecosystems. Uploads the specified dependency manifest files for Go, Gradle, JavaScript, Kotlin, Python, and Scala. Files like "package.json" and "requirements.txt". diff --git a/src/commands/scan/create-scan-from-github.mts b/src/commands/scan/create-scan-from-github.mts index 7b81b6176..e4c5dc23b 100644 --- a/src/commands/scan/create-scan-from-github.mts +++ b/src/commands/scan/create-scan-from-github.mts @@ -241,9 +241,10 @@ async function scanOneRepo( pullRequest: 0, reach: { runReachabilityAnalysis: false, - disableReachAnalytics: false, + reachDisableAnalytics: false, reachAnalysisTimeout: 0, reachAnalysisMemoryLimit: 0, + reachEcosystems: [], }, readOnly: false, repoName: repoSlug, diff --git a/src/commands/scan/handle-create-new-scan.mts b/src/commands/scan/handle-create-new-scan.mts index 5dd155abf..f4403db02 100644 --- a/src/commands/scan/handle-create-new-scan.mts +++ b/src/commands/scan/handle-create-new-scan.mts @@ -14,6 +14,10 @@ import { extractTier1ReachabilityScanId, spawnCoana, } from '../../utils/coana.mts' +import { + type EcosystemString, + convertToCoanaEcosystems, +} from '../../utils/ecosystem.mts' import { getPackageFilesForScan } from '../../utils/path-resolve.mts' import { setupSdk } from '../../utils/sdk.mts' import { readOrDefaultSocketJson } from '../../utils/socketjson.mts' @@ -56,9 +60,10 @@ export async function handleCreateNewScan({ outputKind: OutputKind reach: { runReachabilityAnalysis: boolean - disableReachAnalytics: boolean + reachDisableAnalytics: boolean reachAnalysisTimeout: number reachAnalysisMemoryLimit: number + reachEcosystems: EcosystemString[] } readOnly: boolean repoName: string @@ -213,9 +218,10 @@ async function performReachabilityAnalysis({ orgSlug: string packagePaths: string[] reachabilityOptions: { - disableReachAnalytics: boolean + reachDisableAnalytics: boolean reachAnalysisTimeout: number reachAnalysisMemoryLimit: number + reachEcosystems: EcosystemString[] } repoName: string }): Promise< @@ -286,9 +292,18 @@ async function performReachabilityAnalysis({ reachabilityOptions.reachAnalysisMemoryLimit.toString(), ] : []), - ...(reachabilityOptions.disableReachAnalytics + ...(reachabilityOptions.reachDisableAnalytics ? ['--disable-analytics-sharing'] : []), + // empty reachEcosystems implies scan all ecosystems + ...(reachabilityOptions.reachEcosystems.length + ? [ + '--ecosystems', + convertToCoanaEcosystems(reachabilityOptions.reachEcosystems).join( + ' ', + ), + ] + : []), '--manifests-tar-hash', tarHash, ], diff --git a/src/utils/ecosystem.mts b/src/utils/ecosystem.mts new file mode 100644 index 000000000..c6c813a6f --- /dev/null +++ b/src/utils/ecosystem.mts @@ -0,0 +1,120 @@ +import type { components } from '@socketsecurity/sdk/types/api' + +// Use the SDK type which matches what the API actually accepts +export type EcosystemString = components['schemas']['SocketPURL_Type'] + +// This array must contain ALL ecosystem values +// These match the SocketPURL_Type from the SDK +const ALL_ECOSYSTEMS = [ + 'apk', + 'bitbucket', + 'cargo', + 'chrome', + 'cocoapods', + 'composer', + 'conan', + 'conda', + 'cran', + 'deb', + 'docker', + 'gem', + 'generic', + 'github', + 'golang', + 'hackage', + 'hex', + 'huggingface', + 'maven', + 'mlflow', + 'npm', + 'nuget', + 'oci', + 'pub', + 'pypi', + 'qpkg', + 'rpm', + 'swift', + 'swid', + 'unknown', +] as const + +const COANA_SUPPORTED_ECOSYSTEMS: Set = new Set([ + 'composer', + 'hex', + 'github', + 'golang', + 'maven', + 'npm', + 'nuget', + 'pypi', + 'pub', + 'gem', + 'cargo', + 'swift', +]) + +// Helper type to check if our array contains all possible EcosystemString values +type CheckExhaustive = + EcosystemString extends T[number] ? T : never + +// This will cause a TypeScript error if ALL_ECOSYSTEMS doesn't contain all EcosystemString values +export const ecosystemChoices: CheckExhaustive = + ALL_ECOSYSTEMS + +// Type guard to check if a string is a valid ecosystem +export function isValidEcosystem(value: string): value is EcosystemString { + return (ecosystemChoices as readonly string[]).includes(value) +} + +// Parse and validate ecosystem values from string or array +export function parseEcosystems( + value: string | string[] | undefined, +): EcosystemString[] { + if (!value) { + return [] + } + + const values = + typeof value === 'string' + ? value.split(',').map(v => v.trim().toLowerCase()) + : value.map(v => v.toLowerCase()) + + return values.filter(isValidEcosystem) +} + +// Get string array for use with meow choices +/** + * Ecosystems/Purl types are slightly different in Coana. + * This function converts the EcosystemString[] to a string of Coana compatible ecosystem names. + * Ecosystems that are not supported by Coana are ignored. + */ +export function getEcosystemChoicesForMeow(): string[] { + return ecosystemChoices as unknown as string[] +} + +export function convertToCoanaEcosystems( + ecosystems: EcosystemString[], +): string[] { + return ecosystems + .filter(ecosystem => + COANA_SUPPORTED_ECOSYSTEMS.has(ecosystem as EcosystemString), + ) + .map(ecosystem => { + switch (ecosystem) { + case 'hex': + return 'ERLANG' + case 'github': + return 'ACTIONS' + case 'golang': + return 'GO' + case 'pypi': + return 'PIP' + case 'gem': + return 'RUBYGEMS' + case 'cargo': + return 'RUST' + default: + return ecosystem.toUpperCase() + } + }) +} From b5d6e76f3e7e26799baa2065f0c99187a2599530 Mon Sep 17 00:00:00 2001 From: Martin Torp Date: Wed, 13 Aug 2025 16:19:19 +0200 Subject: [PATCH 3/7] ensure reachability options cannot be used without the --reach flag --- src/commands/scan/cmd-scan-create.mts | 31 ++++ src/commands/scan/cmd-scan-create.test.mts | 185 +++++++++++++++++++++ 2 files changed, 216 insertions(+) diff --git a/src/commands/scan/cmd-scan-create.mts b/src/commands/scan/cmd-scan-create.mts index 551cd65f2..a889f2d2e 100644 --- a/src/commands/scan/cmd-scan-create.mts +++ b/src/commands/scan/cmd-scan-create.mts @@ -454,6 +454,37 @@ async function run( pass: 'ok', fail: 'missing branch name', }, + { + nook: true, + test: reach || !reachDisableAnalytics, + message: 'The --reachDisableAnalytics flag requires --reach to be set', + pass: 'ok', + fail: 'missing --reach flag', + }, + { + nook: true, + test: + reach || + reachAnalysisMemoryLimit === undefined || + reachAnalysisMemoryLimit === 8192, + message: 'The --reachAnalysisMemoryLimit flag requires --reach to be set', + pass: 'ok', + fail: 'missing --reach flag', + }, + { + nook: true, + test: reach || !reachAnalysisTimeout, + message: 'The --reachAnalysisTimeout flag requires --reach to be set', + pass: 'ok', + fail: 'missing --reach flag', + }, + { + nook: true, + test: reach || !reachEcosystems?.length, + message: 'The --reachEcosystems flag requires --reach to be set', + pass: 'ok', + fail: 'missing --reach flag', + }, ) if (!wasValidInput) { return diff --git a/src/commands/scan/cmd-scan-create.test.mts b/src/commands/scan/cmd-scan-create.test.mts index ade1fdbbe..fbc70a98a 100644 --- a/src/commands/scan/cmd-scan-create.test.mts +++ b/src/commands/scan/cmd-scan-create.test.mts @@ -128,4 +128,189 @@ describe('socket scan create', async () => { expect(code, 'dry-run should exit with code 0 if input ok').toBe(0) }, ) + + cmdit( + [ + 'scan', + 'create', + '--org', + 'fakeorg', + 'target', + '--dry-run', + '--repo', + 'xyz', + '--branch', + 'abc', + '--reachDisableAnalytics', + '--config', + '{"apiToken": "abc"}', + ], + 'should fail when --reachDisableAnalytics is used without --reach', + async cmd => { + const { code, stderr, stdout } = await invokeNpm(binCliPath, cmd) + const output = stdout + stderr + expect(output).toContain( + 'The --reachDisableAnalytics flag requires --reach to be set', + ) + expect(output).toContain('missing --reach flag') + expect( + code, + 'should exit with non-zero code when validation fails', + ).not.toBe(0) + }, + ) + + cmdit( + [ + 'scan', + 'create', + '--org', + 'fakeorg', + 'target', + '--dry-run', + '--repo', + 'xyz', + '--branch', + 'abc', + '--reachAnalysisMemoryLimit', + '8192', + '--config', + '{"apiToken": "abc"}', + ], + 'should succeed when --reachAnalysisMemoryLimit is used with default value without --reach', + async cmd => { + const { code, stderr, stdout } = await invokeNpm(binCliPath, cmd) + expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Bailing now"`) + expect(code, 'should exit with code 0 when using default value').toBe(0) + }, + ) + + cmdit( + [ + 'scan', + 'create', + '--org', + 'fakeorg', + 'target', + '--dry-run', + '--repo', + 'xyz', + '--branch', + 'abc', + '--reachAnalysisMemoryLimit', + '4096', + '--config', + '{"apiToken": "abc"}', + ], + 'should fail when --reachAnalysisMemoryLimit is used with non-default value without --reach', + async cmd => { + const { code, stderr, stdout } = await invokeNpm(binCliPath, cmd) + const output = stdout + stderr + expect(output).toContain( + 'The --reachAnalysisMemoryLimit flag requires --reach to be set', + ) + expect(output).toContain('missing --reach flag') + expect( + code, + 'should exit with non-zero code when validation fails', + ).not.toBe(0) + }, + ) + + cmdit( + [ + 'scan', + 'create', + '--org', + 'fakeorg', + 'target', + '--dry-run', + '--repo', + 'xyz', + '--branch', + 'abc', + '--reachAnalysisTimeout', + '3600', + '--config', + '{"apiToken": "abc"}', + ], + 'should fail when --reachAnalysisTimeout is used without --reach', + async cmd => { + const { code, stderr, stdout } = await invokeNpm(binCliPath, cmd) + const output = stdout + stderr + expect(output).toContain( + 'The --reachAnalysisTimeout flag requires --reach to be set', + ) + expect(output).toContain('missing --reach flag') + expect( + code, + 'should exit with non-zero code when validation fails', + ).not.toBe(0) + }, + ) + + cmdit( + [ + 'scan', + 'create', + '--org', + 'fakeorg', + 'target', + '--dry-run', + '--repo', + 'xyz', + '--branch', + 'abc', + '--reachEcosystems', + 'npm', + '--reachEcosystems', + 'pypi', + '--config', + '{"apiToken": "abc"}', + ], + 'should fail when --reachEcosystems is used without --reach', + async cmd => { + const { code, stderr, stdout } = await invokeNpm(binCliPath, cmd) + const output = stdout + stderr + expect(output).toContain( + 'The --reachEcosystems flag requires --reach to be set', + ) + expect(output).toContain('missing --reach flag') + expect( + code, + 'should exit with non-zero code when validation fails', + ).not.toBe(0) + }, + ) + + cmdit( + [ + 'scan', + 'create', + '--org', + 'fakeorg', + 'target', + '--dry-run', + '--repo', + 'xyz', + '--branch', + 'abc', + '--reach', + '--reachDisableAnalytics', + '--reachAnalysisMemoryLimit', + '4096', + '--reachAnalysisTimeout', + '3600', + '--reachEcosystems', + 'npm', + '--config', + '{"apiToken": "abc"}', + ], + 'should succeed when reachability options are used with --reach', + async cmd => { + const { code, stderr, stdout } = await invokeNpm(binCliPath, cmd) + expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Bailing now"`) + expect(code, 'should exit with code 0 when all flags are valid').toBe(0) + }, + ) }) From 3df0bb0d1d94ef99f9341d476332ab28f7dde3b5 Mon Sep 17 00:00:00 2001 From: Martin Torp Date: Wed, 13 Aug 2025 20:01:17 +0200 Subject: [PATCH 4/7] add --reach-continue-on-failing-projects flag --- src/commands/ci/handle-ci.mts | 1 + src/commands/scan/cmd-scan-create.mts | 16 +++++ src/commands/scan/cmd-scan-create.test.mts | 64 +++++++++++++++++++ src/commands/scan/create-scan-from-github.mts | 1 + src/commands/scan/handle-create-new-scan.mts | 5 ++ 5 files changed, 87 insertions(+) diff --git a/src/commands/ci/handle-ci.mts b/src/commands/ci/handle-ci.mts index 48089d248..7dfe92da3 100644 --- a/src/commands/ci/handle-ci.mts +++ b/src/commands/ci/handle-ci.mts @@ -39,6 +39,7 @@ export async function handleCi(autoManifest: boolean): Promise { pullRequest: 0, reach: { runReachabilityAnalysis: false, + reachContinueOnFailingProjects: false, reachDisableAnalytics: false, reachAnalysisTimeout: 0, reachAnalysisMemoryLimit: 0, diff --git a/src/commands/scan/cmd-scan-create.mts b/src/commands/scan/cmd-scan-create.mts index a889f2d2e..f9c1e8db7 100644 --- a/src/commands/scan/cmd-scan-create.mts +++ b/src/commands/scan/cmd-scan-create.mts @@ -54,6 +54,11 @@ const reachabilityFlags: MeowFlags = { description: 'List of ecosystems to conduct reachability analysis on. Defaults to all ecosystems.', }, + reachContinueOnFailingProjects: { + type: 'boolean', + description: + 'Continue reachability analysis even when some projects/workspaces fail. Default is to crash the CLI at the first failing project/workspace.', + }, } const config: CliCommandConfig = { @@ -253,6 +258,7 @@ async function run( reach, reachAnalysisMemoryLimit, reachAnalysisTimeout, + reachContinueOnFailingProjects, reachDisableAnalytics, reachEcosystems, readOnly, @@ -278,6 +284,7 @@ async function run( reach: boolean reachAnalysisTimeout?: number reachAnalysisMemoryLimit?: number + reachContinueOnFailingProjects: boolean reachEcosystems: EcosystemString[] reachDisableAnalytics: boolean } @@ -485,6 +492,14 @@ async function run( pass: 'ok', fail: 'missing --reach flag', }, + { + nook: true, + test: reach || !reachContinueOnFailingProjects, + message: + 'The --reachContinueOnFailingProjects flag requires --reach to be set', + pass: 'ok', + fail: 'missing --reach flag', + }, ) if (!wasValidInput) { return @@ -511,6 +526,7 @@ async function run( pullRequest: Number(pullRequest), reach: { runReachabilityAnalysis: Boolean(reach), + reachContinueOnFailingProjects: Boolean(reachContinueOnFailingProjects), reachDisableAnalytics: Boolean(reachDisableAnalytics), reachAnalysisTimeout: Number(reachAnalysisTimeout), reachAnalysisMemoryLimit: Number(reachAnalysisMemoryLimit), diff --git a/src/commands/scan/cmd-scan-create.test.mts b/src/commands/scan/cmd-scan-create.test.mts index fbc70a98a..ae06904e3 100644 --- a/src/commands/scan/cmd-scan-create.test.mts +++ b/src/commands/scan/cmd-scan-create.test.mts @@ -45,6 +45,7 @@ describe('socket scan create', async () => { Reachability Options (when --reach is used) --reach-analysis-memory-limit The maximum memory in MB to use for the reachability analysis. The default is 8192MB. --reach-analysis-timeout Set timeout for the reachability analysis. Split analysis runs may cause the total scan time to exceed this timeout significantly. + --reach-continue-on-failing-projects Continue reachability analysis even when some projects/workspaces fail. Default is to crash the CLI at the first failing project/workspace. --reach-disable-analytics Disable reachability analytics sharing with Socket. Also disables caching-based optimizations. --reach-ecosystems List of ecosystems to conduct reachability analysis on. Defaults to all ecosystems. @@ -283,6 +284,37 @@ describe('socket scan create', async () => { }, ) + cmdit( + [ + 'scan', + 'create', + '--org', + 'fakeorg', + 'target', + '--dry-run', + '--repo', + 'xyz', + '--branch', + 'abc', + '--reach-continue-on-failing-projects', + '--config', + '{"apiToken": "abc"}', + ], + 'should fail when --reach-continue-on-failing-projects is used without --reach', + async cmd => { + const { code, stderr, stdout } = await invokeNpm(binCliPath, cmd) + const output = stdout + stderr + expect(output).toContain( + 'The --reachContinueOnFailingProjects flag requires --reach to be set', + ) + expect(output).toContain('missing --reach flag') + expect( + code, + 'should exit with non-zero code when validation fails', + ).not.toBe(0) + }, + ) + cmdit( [ 'scan', @@ -313,4 +345,36 @@ describe('socket scan create', async () => { expect(code, 'should exit with code 0 when all flags are valid').toBe(0) }, ) + + cmdit( + [ + 'scan', + 'create', + '--org', + 'fakeorg', + 'target', + '--dry-run', + '--repo', + 'xyz', + '--branch', + 'abc', + '--reach', + '--reach-continue-on-failing-projects', + '--reach-disable-analytics', + '--reach-analysis-memory-limit', + '4096', + '--reach-analysis-timeout', + '3600', + '--reach-ecosystems', + 'npm', + '--config', + '{"apiToken": "abc"}', + ], + 'should succeed when all reachability options including reachContinueOnFailingProjects are used with --reach', + async cmd => { + const { code, stderr, stdout } = await invokeNpm(binCliPath, cmd) + expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Bailing now"`) + expect(code, 'should exit with code 0 when all flags are valid').toBe(0) + }, + ) }) diff --git a/src/commands/scan/create-scan-from-github.mts b/src/commands/scan/create-scan-from-github.mts index e4c5dc23b..c28e614f5 100644 --- a/src/commands/scan/create-scan-from-github.mts +++ b/src/commands/scan/create-scan-from-github.mts @@ -241,6 +241,7 @@ async function scanOneRepo( pullRequest: 0, reach: { runReachabilityAnalysis: false, + reachContinueOnFailingProjects: false, reachDisableAnalytics: false, reachAnalysisTimeout: 0, reachAnalysisMemoryLimit: 0, diff --git a/src/commands/scan/handle-create-new-scan.mts b/src/commands/scan/handle-create-new-scan.mts index f4403db02..3b83ec667 100644 --- a/src/commands/scan/handle-create-new-scan.mts +++ b/src/commands/scan/handle-create-new-scan.mts @@ -60,6 +60,7 @@ export async function handleCreateNewScan({ outputKind: OutputKind reach: { runReachabilityAnalysis: boolean + reachContinueOnFailingProjects: boolean reachDisableAnalytics: boolean reachAnalysisTimeout: number reachAnalysisMemoryLimit: number @@ -218,6 +219,7 @@ async function performReachabilityAnalysis({ orgSlug: string packagePaths: string[] reachabilityOptions: { + reachContinueOnFailingProjects: boolean reachDisableAnalytics: boolean reachAnalysisTimeout: number reachAnalysisMemoryLimit: number @@ -295,6 +297,9 @@ async function performReachabilityAnalysis({ ...(reachabilityOptions.reachDisableAnalytics ? ['--disable-analytics-sharing'] : []), + ...(reachabilityOptions.reachContinueOnFailingProjects + ? ['--ignore-failing-workspaces'] + : []), // empty reachEcosystems implies scan all ecosystems ...(reachabilityOptions.reachEcosystems.length ? [ From 3b1d647cd0563243bd5a2618ec70ebe862a82e6f Mon Sep 17 00:00:00 2001 From: Martin Torp Date: Wed, 13 Aug 2025 20:39:03 +0200 Subject: [PATCH 5/7] add --reach-exclude-paths option --- src/commands/ci/handle-ci.mts | 1 + src/commands/scan/cmd-scan-create.mts | 15 ++++ src/commands/scan/cmd-scan-create.test.mts | 71 +++++++++++++++++++ src/commands/scan/create-scan-from-github.mts | 1 + src/commands/scan/handle-create-new-scan.mts | 5 ++ 5 files changed, 93 insertions(+) diff --git a/src/commands/ci/handle-ci.mts b/src/commands/ci/handle-ci.mts index 7dfe92da3..b0a684eec 100644 --- a/src/commands/ci/handle-ci.mts +++ b/src/commands/ci/handle-ci.mts @@ -44,6 +44,7 @@ export async function handleCi(autoManifest: boolean): Promise { reachAnalysisTimeout: 0, reachAnalysisMemoryLimit: 0, reachEcosystems: [], + reachExcludePaths: [], }, repoName, readOnly: false, diff --git a/src/commands/scan/cmd-scan-create.mts b/src/commands/scan/cmd-scan-create.mts index f9c1e8db7..6ab47f37c 100644 --- a/src/commands/scan/cmd-scan-create.mts +++ b/src/commands/scan/cmd-scan-create.mts @@ -59,6 +59,11 @@ const reachabilityFlags: MeowFlags = { description: 'Continue reachability analysis even when some projects/workspaces fail. Default is to crash the CLI at the first failing project/workspace.', }, + reachExcludePaths: { + type: 'string', + isMultiple: true, + description: 'List of paths to exclude from reachability analysis.', + }, } const config: CliCommandConfig = { @@ -261,6 +266,7 @@ async function run( reachContinueOnFailingProjects, reachDisableAnalytics, reachEcosystems, + reachExcludePaths, readOnly, setAsAlertsPage: pendingHeadFlag, tmp, @@ -286,6 +292,7 @@ async function run( reachAnalysisMemoryLimit?: number reachContinueOnFailingProjects: boolean reachEcosystems: EcosystemString[] + reachExcludePaths: string[] reachDisableAnalytics: boolean } let { @@ -500,6 +507,13 @@ async function run( pass: 'ok', fail: 'missing --reach flag', }, + { + nook: true, + test: reach || !reachExcludePaths?.length, + message: 'The --reachExcludePaths flag requires --reach to be set', + pass: 'ok', + fail: 'missing --reach flag', + }, ) if (!wasValidInput) { return @@ -531,6 +545,7 @@ async function run( reachAnalysisTimeout: Number(reachAnalysisTimeout), reachAnalysisMemoryLimit: Number(reachAnalysisMemoryLimit), reachEcosystems, + reachExcludePaths: reachExcludePaths || [], }, readOnly: Boolean(readOnly), repoName, diff --git a/src/commands/scan/cmd-scan-create.test.mts b/src/commands/scan/cmd-scan-create.test.mts index ae06904e3..d2aa9d8b7 100644 --- a/src/commands/scan/cmd-scan-create.test.mts +++ b/src/commands/scan/cmd-scan-create.test.mts @@ -48,6 +48,7 @@ describe('socket scan create', async () => { --reach-continue-on-failing-projects Continue reachability analysis even when some projects/workspaces fail. Default is to crash the CLI at the first failing project/workspace. --reach-disable-analytics Disable reachability analytics sharing with Socket. Also disables caching-based optimizations. --reach-ecosystems List of ecosystems to conduct reachability analysis on. Defaults to all ecosystems. + --reach-exclude-paths List of paths to exclude from reachability analysis. Uploads the specified dependency manifest files for Go, Gradle, JavaScript, Kotlin, Python, and Scala. Files like "package.json" and "requirements.txt". @@ -346,6 +347,40 @@ describe('socket scan create', async () => { }, ) + cmdit( + [ + 'scan', + 'create', + '--org', + 'fakeorg', + 'target', + '--dry-run', + '--repo', + 'xyz', + '--branch', + 'abc', + '--reach-exclude-paths', + 'node_modules', + '--reach-exclude-paths', + 'dist', + '--config', + '{"apiToken": "abc"}', + ], + 'should fail when --reach-exclude-paths is used without --reach', + async cmd => { + const { code, stderr, stdout } = await invokeNpm(binCliPath, cmd) + const output = stdout + stderr + expect(output).toContain( + 'The --reachExcludePaths flag requires --reach to be set', + ) + expect(output).toContain('missing --reach flag') + expect( + code, + 'should exit with non-zero code when validation fails', + ).not.toBe(0) + }, + ) + cmdit( [ 'scan', @@ -377,4 +412,40 @@ describe('socket scan create', async () => { expect(code, 'should exit with code 0 when all flags are valid').toBe(0) }, ) + + cmdit( + [ + 'scan', + 'create', + '--org', + 'fakeorg', + 'target', + '--dry-run', + '--repo', + 'xyz', + '--branch', + 'abc', + '--reach', + '--reach-continue-on-failing-projects', + '--reach-disable-analytics', + '--reach-analysis-memory-limit', + '4096', + '--reach-analysis-timeout', + '3600', + '--reach-ecosystems', + 'npm', + '--reach-exclude-paths', + 'node_modules', + '--reach-exclude-paths', + 'dist', + '--config', + '{"apiToken": "abc"}', + ], + 'should succeed when all reachability options including reachExcludePaths are used with --reach', + async cmd => { + const { code, stderr, stdout } = await invokeNpm(binCliPath, cmd) + expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Bailing now"`) + expect(code, 'should exit with code 0 when all flags are valid').toBe(0) + }, + ) }) diff --git a/src/commands/scan/create-scan-from-github.mts b/src/commands/scan/create-scan-from-github.mts index c28e614f5..9b10f1394 100644 --- a/src/commands/scan/create-scan-from-github.mts +++ b/src/commands/scan/create-scan-from-github.mts @@ -246,6 +246,7 @@ async function scanOneRepo( reachAnalysisTimeout: 0, reachAnalysisMemoryLimit: 0, reachEcosystems: [], + reachExcludePaths: [], }, readOnly: false, repoName: repoSlug, diff --git a/src/commands/scan/handle-create-new-scan.mts b/src/commands/scan/handle-create-new-scan.mts index 3b83ec667..0c8e6ffc4 100644 --- a/src/commands/scan/handle-create-new-scan.mts +++ b/src/commands/scan/handle-create-new-scan.mts @@ -65,6 +65,7 @@ export async function handleCreateNewScan({ reachAnalysisTimeout: number reachAnalysisMemoryLimit: number reachEcosystems: EcosystemString[] + reachExcludePaths: string[] } readOnly: boolean repoName: string @@ -224,6 +225,7 @@ async function performReachabilityAnalysis({ reachAnalysisTimeout: number reachAnalysisMemoryLimit: number reachEcosystems: EcosystemString[] + reachExcludePaths: string[] } repoName: string }): Promise< @@ -309,6 +311,9 @@ async function performReachabilityAnalysis({ ), ] : []), + ...(reachabilityOptions.reachExcludePaths.length + ? ['--exclude-dirs', reachabilityOptions.reachExcludePaths.join(' ')] + : []), '--manifests-tar-hash', tarHash, ], From 007beac9966d282cb1a837db966eba912ddfe48e Mon Sep 17 00:00:00 2001 From: Martin Torp Date: Wed, 13 Aug 2025 20:58:55 +0200 Subject: [PATCH 6/7] allow ,-separated values for ----reach-continue-failing-projects and --reach-exclude-paths --- src/commands/scan/cmd-scan-create.mts | 34 +++-- src/commands/scan/cmd-scan-create.test.mts | 156 ++++++++++++++++++++- 2 files changed, 178 insertions(+), 12 deletions(-) diff --git a/src/commands/scan/cmd-scan-create.mts b/src/commands/scan/cmd-scan-create.mts index 6ab47f37c..ad6288fd0 100644 --- a/src/commands/scan/cmd-scan-create.mts +++ b/src/commands/scan/cmd-scan-create.mts @@ -9,6 +9,7 @@ import { suggestTarget } from './suggest_target.mts' import constants from '../../constants.mts' import { type MeowFlags, commonFlags, outputFlags } from '../../flags.mts' import { checkCommandInput } from '../../utils/check-input.mts' +import { cmdFlagValueToArray } from '../../utils/cmd.mts' import { determineOrgSlug } from '../../utils/determine-org-slug.mts' import { type EcosystemString, @@ -50,9 +51,8 @@ const reachabilityFlags: MeowFlags = { reachEcosystems: { type: 'string', isMultiple: true, - choices: getEcosystemChoicesForMeow(), description: - 'List of ecosystems to conduct reachability analysis on. Defaults to all ecosystems.', + 'List of ecosystems to conduct reachability analysis on, as either a comma separated value or as multiple flags. Defaults to all ecosystems.', }, reachContinueOnFailingProjects: { type: 'boolean', @@ -62,7 +62,8 @@ const reachabilityFlags: MeowFlags = { reachExcludePaths: { type: 'string', isMultiple: true, - description: 'List of paths to exclude from reachability analysis.', + description: + 'List of paths to exclude from reachability analysis, as either a comma separated value or as multiple flags.', }, } @@ -265,8 +266,6 @@ async function run( reachAnalysisTimeout, reachContinueOnFailingProjects, reachDisableAnalytics, - reachEcosystems, - reachExcludePaths, readOnly, setAsAlertsPage: pendingHeadFlag, tmp, @@ -291,10 +290,25 @@ async function run( reachAnalysisTimeout?: number reachAnalysisMemoryLimit?: number reachContinueOnFailingProjects: boolean - reachEcosystems: EcosystemString[] - reachExcludePaths: string[] reachDisableAnalytics: boolean } + + // Process comma-separated values for isMultiple flags + const reachEcosystemsRaw = cmdFlagValueToArray(cli.flags['reachEcosystems']) + const reachExcludePaths = cmdFlagValueToArray(cli.flags['reachExcludePaths']) + + // Validate ecosystem values + const validEcosystems = getEcosystemChoicesForMeow() + const reachEcosystems: EcosystemString[] = [] + for (const ecosystem of reachEcosystemsRaw) { + if (!validEcosystems.includes(ecosystem)) { + throw new Error( + `Invalid ecosystem: "${ecosystem}". Valid values are: ${validEcosystems.join(', ')}`, + ) + } + reachEcosystems.push(ecosystem as EcosystemString) + } + let { autoManifest, branch: branchName, @@ -494,7 +508,7 @@ async function run( }, { nook: true, - test: reach || !reachEcosystems?.length, + test: reach || !reachEcosystems.length, message: 'The --reachEcosystems flag requires --reach to be set', pass: 'ok', fail: 'missing --reach flag', @@ -509,7 +523,7 @@ async function run( }, { nook: true, - test: reach || !reachExcludePaths?.length, + test: reach || !reachExcludePaths.length, message: 'The --reachExcludePaths flag requires --reach to be set', pass: 'ok', fail: 'missing --reach flag', @@ -545,7 +559,7 @@ async function run( reachAnalysisTimeout: Number(reachAnalysisTimeout), reachAnalysisMemoryLimit: Number(reachAnalysisMemoryLimit), reachEcosystems, - reachExcludePaths: reachExcludePaths || [], + reachExcludePaths, }, readOnly: Boolean(readOnly), repoName, diff --git a/src/commands/scan/cmd-scan-create.test.mts b/src/commands/scan/cmd-scan-create.test.mts index d2aa9d8b7..217d3cf61 100644 --- a/src/commands/scan/cmd-scan-create.test.mts +++ b/src/commands/scan/cmd-scan-create.test.mts @@ -47,8 +47,8 @@ describe('socket scan create', async () => { --reach-analysis-timeout Set timeout for the reachability analysis. Split analysis runs may cause the total scan time to exceed this timeout significantly. --reach-continue-on-failing-projects Continue reachability analysis even when some projects/workspaces fail. Default is to crash the CLI at the first failing project/workspace. --reach-disable-analytics Disable reachability analytics sharing with Socket. Also disables caching-based optimizations. - --reach-ecosystems List of ecosystems to conduct reachability analysis on. Defaults to all ecosystems. - --reach-exclude-paths List of paths to exclude from reachability analysis. + --reach-ecosystems List of ecosystems to conduct reachability analysis on, as either a comma separated value or as multiple flags. Defaults to all ecosystems. + --reach-exclude-paths List of paths to exclude from reachability analysis, as either a comma separated value or as multiple flags. Uploads the specified dependency manifest files for Go, Gradle, JavaScript, Kotlin, Python, and Scala. Files like "package.json" and "requirements.txt". @@ -448,4 +448,156 @@ describe('socket scan create', async () => { expect(code, 'should exit with code 0 when all flags are valid').toBe(0) }, ) + + cmdit( + [ + 'scan', + 'create', + '--org', + 'fakeorg', + 'target', + '--dry-run', + '--repo', + 'xyz', + '--branch', + 'abc', + '--reach', + '--reach-ecosystems', + 'npm,pypi,cargo', + '--config', + '{"apiToken": "abc"}', + ], + 'should succeed when --reach-ecosystems is used with comma-separated values', + async cmd => { + const { code, stderr, stdout } = await invokeNpm(binCliPath, cmd) + expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Bailing now"`) + expect( + code, + 'should exit with code 0 when comma-separated values are used', + ).toBe(0) + }, + ) + + cmdit( + [ + 'scan', + 'create', + '--org', + 'fakeorg', + 'target', + '--dry-run', + '--repo', + 'xyz', + '--branch', + 'abc', + '--reach', + '--reach-exclude-paths', + 'node_modules,dist,build', + '--config', + '{"apiToken": "abc"}', + ], + 'should succeed when --reach-exclude-paths is used with comma-separated values', + async cmd => { + const { code, stderr, stdout } = await invokeNpm(binCliPath, cmd) + expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Bailing now"`) + expect( + code, + 'should exit with code 0 when comma-separated values are used', + ).toBe(0) + }, + ) + + cmdit( + [ + 'scan', + 'create', + '--org', + 'fakeorg', + 'target', + '--dry-run', + '--repo', + 'xyz', + '--branch', + 'abc', + '--reach-ecosystems', + 'npm,pypi', + '--config', + '{"apiToken": "abc"}', + ], + 'should fail when --reach-ecosystems with comma-separated values is used without --reach', + async cmd => { + const { code, stderr, stdout } = await invokeNpm(binCliPath, cmd) + const output = stdout + stderr + expect(output).toContain( + 'The --reachEcosystems flag requires --reach to be set', + ) + expect(output).toContain('missing --reach flag') + expect( + code, + 'should exit with non-zero code when validation fails', + ).not.toBe(0) + }, + ) + + cmdit( + [ + 'scan', + 'create', + '--org', + 'fakeorg', + 'target', + '--dry-run', + '--repo', + 'xyz', + '--branch', + 'abc', + '--reach-exclude-paths', + 'node_modules,dist', + '--config', + '{"apiToken": "abc"}', + ], + 'should fail when --reach-exclude-paths with comma-separated values is used without --reach', + async cmd => { + const { code, stderr, stdout } = await invokeNpm(binCliPath, cmd) + const output = stdout + stderr + expect(output).toContain( + 'The --reachExcludePaths flag requires --reach to be set', + ) + expect(output).toContain('missing --reach flag') + expect( + code, + 'should exit with non-zero code when validation fails', + ).not.toBe(0) + }, + ) + + cmdit( + [ + 'scan', + 'create', + '--org', + 'fakeorg', + 'target', + '--dry-run', + '--repo', + 'xyz', + '--branch', + 'abc', + '--reach', + '--reach-ecosystems', + 'npm,invalid-ecosystem', + '--config', + '{"apiToken": "abc"}', + ], + 'should fail when --reach-ecosystems contains invalid values', + async cmd => { + const { code, stderr, stdout } = await invokeNpm(binCliPath, cmd) + const output = stdout + stderr + expect(output).toContain('Invalid ecosystem: "invalid-ecosystem"') + expect( + code, + 'should exit with non-zero code when invalid ecosystem is provided', + ).not.toBe(0) + }, + ) }) From 0b48534d98bb88e51535c18f3cd1c20d0e3b547b Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Wed, 13 Aug 2025 16:27:32 -0400 Subject: [PATCH 7/7] Fix dup packagePaths Signed-off-by: John-David Dalton --- src/commands/scan/handle-create-new-scan.mts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/commands/scan/handle-create-new-scan.mts b/src/commands/scan/handle-create-new-scan.mts index 9ea9cb693..8af4687c3 100644 --- a/src/commands/scan/handle-create-new-scan.mts +++ b/src/commands/scan/handle-create-new-scan.mts @@ -228,7 +228,6 @@ export async function handleCreateNewScan({ } type ReachabilityAnalysisConfig = { - packagePaths: string[] branchName: string cwd: string orgSlug: string