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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [Unreleased]

### Fixed
- CLI no longer crashes on the default (no `--format`) invocation. When `--format` was omitted, the flag value fell back to the first positional path, producing `Error: Unsupported format: <path>`. The format now correctly defaults to `text`, and an unknown `--format` value prints a clear one-line error instead of a stack trace.

### Added
- `src/args.ts` — extracted, unit-tested CLI argument parsing (`parseArgs`)
- Real devDependencies: `typescript`, `vitest`, `@vitest/coverage-v8`, `typescript-eslint`, `@types/node`
- `src/types.ts` — Full domain model: `SBOM`, `Component`, `CVEEntry`, `ChangeReport`, `VersionChange`, `SBOMFormat`, `ReportFormat`
- `src/parser.ts` — `parse()` / `parseCycloneDX()` / `parseSPDX()`: auto-detect and parse CycloneDX + SPDX JSON SBOMs, extracts purls, ecosystems, licenses, suppliers, CVEs
Expand Down
33 changes: 3 additions & 30 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions src/__tests__/args.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { describe, it, expect } from 'vitest';
import { parseArgs } from '../args.js';

describe('parseArgs', () => {
it('defaults to text format when --format is omitted', () => {
const args = parseArgs(['old.json', 'new.json']);
expect(args).toEqual({ oldPath: 'old.json', newPath: 'new.json', format: 'text' });
});

it('parses the spaced form: --format json', () => {
const args = parseArgs(['old.json', 'new.json', '--format', 'json']);
expect(args.format).toBe('json');
});

it('parses the inline form: --format=markdown', () => {
const args = parseArgs(['old.json', 'new.json', '--format=markdown']);
expect(args.format).toBe('markdown');
});

it('throws when fewer than two positional args are given', () => {
expect(() => parseArgs(['only-one.json'])).toThrow(/Usage:/);
});

it('throws on an unknown format', () => {
expect(() => parseArgs(['old.json', 'new.json', '--format', 'xml'])).toThrow(/Invalid format/);
});
});
43 changes: 43 additions & 0 deletions src/args.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { ReportFormat } from './types.js';

/** Report formats the CLI accepts. */
export const VALID_FORMATS: readonly ReportFormat[] = ['text', 'json', 'markdown'];

/** Parsed CLI arguments. */
export interface CliArgs {
oldPath: string;
newPath: string;
format: ReportFormat;
}

const USAGE = 'Usage: sbom-diff <old.json> <new.json> [--format text|json|markdown]';

/**
* Parse CLI arguments (the slice after `node cli.js`).
*
* Supports both `--format=json` and `--format json`. When `--format` is
* omitted, the format defaults to `text`.
*
* Throws an Error with a user-facing message on invalid input.
*/
export function parseArgs(argv: string[]): CliArgs {
const positional = argv.filter((a) => !a.startsWith('--'));
if (positional.length < 2) {
throw new Error(USAGE);
}
const [oldPath, newPath] = positional;

// Support `--format=json` (inline) and `--format json` (spaced).
const inlineFormat = argv.find((a) => a.startsWith('--format='))?.split('=')[1];
const flagIndex = argv.indexOf('--format');
const spacedFormat = flagIndex !== -1 ? argv[flagIndex + 1] : undefined;
const format = inlineFormat ?? spacedFormat ?? 'text';

if (!VALID_FORMATS.includes(format as ReportFormat)) {
throw new Error(
`Invalid format: "${format}". Valid formats: ${VALID_FORMATS.join(', ')}.`,
);
}

return { oldPath, newPath, format: format as ReportFormat };
}
18 changes: 8 additions & 10 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,20 @@ import { readFile } from 'node:fs/promises';
import { parse } from './parser.js';
import { diff } from './diff.js';
import { renderReport } from './reporter.js';
import { parseArgs } from './args.js';
import type { ReportFormat } from './types.js';

async function main(): Promise<void> {
const args = process.argv.slice(2);

const positional = args.filter(a => !a.startsWith('--'));
if (positional.length < 2) {
console.error('Usage: sbom-diff <old.json> <new.json> [--format text|json|markdown]');
let oldPath: string;
let newPath: string;
let format: ReportFormat;
try {
({ oldPath, newPath, format } = parseArgs(process.argv.slice(2)));
} catch (err) {
console.error((err as Error).message);
process.exit(1);
}

const [oldPath, newPath] = positional;
const formatArg = args.find(a => a.startsWith('--format='))?.split('=')[1]
?? args[args.indexOf('--format') + 1];
const format: ReportFormat = (formatArg as ReportFormat) ?? 'text';

const [oldRaw, newRaw] = await Promise.all([
readFile(oldPath, 'utf-8'),
readFile(newPath, 'utf-8'),
Expand Down
Loading