diff --git a/help/cli-commands/sbom.md b/help/cli-commands/sbom.md index 8323201c91..4767db2779 100644 --- a/help/cli-commands/sbom.md +++ b/help/cli-commands/sbom.md @@ -87,6 +87,16 @@ Optional. Use for monorepos and directories with multiple projects or manifest f Auto-detect all projects in the working directory (including Yarn workspaces) and generate a single SBOM based on their contents. +### `--fail-fast=true|false` + +Use with `--all-projects` to control whether SBOM generation continues when errors occur. + +**Default:** `true` (fail-fast behavior) + +- `--fail-fast=true` or `--fail-fast` (default): When errors occur, SBOM generation is interrupted immediately. The exit code is 2 and the scan ends. No SBOM is generated for projects that had errors. + +- `--fail-fast=false`: Continue processing all projects even when some fail. All successful projects will be included in the SBOM, and errors for failed projects will be reported at the end. + ### `[--name=]` Use in combination with `--all-projects` to provide the name of the software which the SBOM describes. If not specified, this defaults to the name of the current working directory. diff --git a/src/cli/args.ts b/src/cli/args.ts index d11dc37cf6..a54b1e5288 100644 --- a/src/cli/args.ts +++ b/src/cli/args.ts @@ -255,6 +255,14 @@ export function args(rawArgv: string[]): Args { } } + if (argv['fail-fast'] !== undefined) { + if (argv['fail-fast'] === 'false' || argv['fail-fast'] === false) { + argv['fail-fast'] = false; + } else { + argv['fail-fast'] = true; + } + } + // Alias const aliases = { gradleSubProject: 'subProject', diff --git a/test/jest/acceptance/snyk-sbom/all-projects.spec.ts b/test/jest/acceptance/snyk-sbom/all-projects.spec.ts index 5252c94d54..1cbc948667 100644 --- a/test/jest/acceptance/snyk-sbom/all-projects.spec.ts +++ b/test/jest/acceptance/snyk-sbom/all-projects.spec.ts @@ -1,4 +1,5 @@ import { createProjectFromWorkspace } from '../../util/createProject'; +import { createProject } from '../../util/createProject'; import { runSnykCLI } from '../../util/runSnykCLI'; import { fakeServer } from '../../../acceptance/fake-server'; import { getAvailableServerPort } from '../../util/getServerPort'; @@ -113,4 +114,57 @@ describe('snyk sbom --all-projects (mocked server only)', () => { ); expect(bom.components).toHaveLength(37); }); + + describe('--fail-fast option', () => { + test('`sbom --all-projects` with projects that have errors exits with code 2 (fail-fast by default)', async () => { + server.setFeatureFlag('enableMavenDverboseExhaustiveDeps', false); + const project = await createProject( + 'snyk-test-all-projects-exit-codes/project-with-no-issues-and-project-with-error', + ); + + const { code, stderr } = await runSnykCLI( + `sbom --org aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee --format cyclonedx1.4+json --debug --all-projects`, + { + cwd: project.path(), + env, + }, + ); + + // With default fail-fast behavior, should exit on first error + expect(code).toEqual(2); + // Should indicate that processing was interrupted + expect(stderr).toBeTruthy(); + }); + + test('`sbom --all-projects --fail-fast=false` with projects that have errors continues processing', async () => { + server.setFeatureFlag('enableMavenDverboseExhaustiveDeps', false); + const project = await createProject( + 'snyk-test-all-projects-exit-codes/project-with-no-issues-and-project-with-error', + ); + + const { code, stdout, stderr } = await runSnykCLI( + `sbom --org aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee --format cyclonedx1.4+json --debug --all-projects --fail-fast=false`, + { + cwd: project.path(), + env, + }, + ); + + // With --fail-fast=false, should continue processing even if some projects fail + // If at least one project succeeds, should generate SBOM + if (code === 0) { + // Success case: at least one project succeeded, SBOM was generated + let bom; + expect(() => { + bom = JSON.parse(stdout); + }).not.toThrow(); + expect(bom).toBeDefined(); + } else { + // Error case: all projects failed, but we tried all of them + expect(code).toBeGreaterThan(0); + } + // Should indicate that errors occurred but processing continued + expect(stderr).toBeTruthy(); + }); + }); });