Skip to content

Add supply-chain signals, schema output, and workspace filtering - #27

Merged
JosephMaynard merged 11 commits into
masterfrom
feat/cli-tool-improvements
Apr 28, 2026
Merged

JosephMaynard merged 11 commits into
masterfrom
feat/cli-tool-improvements

Conversation

@JosephMaynard

@JosephMaynard JosephMaynard commented Apr 27, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Add lockfile supply-chain signal collection, normalized findings, and a new --fail-on supply-chain-source policy
  • Add --schema support plus a versioned JSON schema for CI consumers
  • Expand package-manager coverage with Bun text lock parsing, bun.lockb guidance, and Yarn PnP-aware behavior
  • Add a workspace filter dropdown in the list view and keep it aligned with graph-view workspace ordering
  • Add unit and CLI coverage for the new outputs, filters, and lockfile edge cases

Testing

  • Added focused unit tests for lockfile signal extraction, finding normalization, workspace filter ordering, Bun/Yarn parsing, and schema shape
  • Added CLI tests for --schema, --audit-signatures, --fail-on supply-chain-source, Bun scans, Yarn PnP scans, and JSON output fields
  • Ran typecheck, unit tests, fixture suite, build, and pack dry-run successfully

Summary by CodeRabbit

  • New Features

    • Added why and compare commands; multi-format exports (SARIF, CycloneDX, SPDX, JSON/HTML), --schema/--format/--sbom, optional npm signature checks (--audit-signatures), --target-node compatibility checks, workspace filter in the report UI, and lockfile supply-chain signal collection.
  • Documentation

    • README and CI policy docs updated, including a supply-chain-source fail rule.
  • Chores

    • Report schema bumped to v1.4; reports can include supply-chain summaries, normalized findings, and Bun/tooling metadata.
  • Tests

    • Expanded test coverage for exports, supply-chain signals, Bun/Yarn handling, findings, and compare/why behaviors.

@coderabbitai

coderabbitai Bot commented Apr 27, 2026 •

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds SARIF/CycloneDX/SPDX outputs, why and compare CLI commands, lockfile supply-chain signal collection and optional signature audits, Node engine compatibility checks, Bun package-manager support, normalized findings generation, workspace filtering in the report UI, and bumps report schema to v1.4.

Changes

Cohort / File(s) Summary
CLI & Commands
src/cli.ts, src/cli.test.ts, src/why.ts, src/compare.ts
Adds why and compare commands; unified --format/--sbom/--schema output modes; --target-node and --audit-signatures flags; passes supply-chain and targetNodeMajor into aggregation; extensive CLI tests.
Output Renderers & Schema
src/outputFormats.ts, src/schema.ts
New SARIF, CycloneDX, and SPDX renderers; ReportFormat type and default output names; exports JSON Schema for report v1.4.
Findings & Supply‑chain
src/findings.ts, src/findings.test.ts, src/runners/lockfileSignals.ts, src/runners/lockfileSignals.test.ts
Implements findings builder that derives normalized findings from dependencies and supply-chain signals; adds lockfile supply-chain signal collector (parses multiple lockfiles including Bun JSONC), optional npm audit signatures run, persistence and tests.
Policy Enforcement
src/failOn.ts, src/failOn.test.ts
Adds supply-chain-source fail-on rule; counts matching supply-chain signals and emits policy violations; tests updated.
Lockfile parsing & Bun support
src/runners/lockfileGraph.ts, src/runners/npmLs.ts, src/runners/npmLs.test.ts
Adds bun to tool unions; Bun lockfile parsing (JSONC), lockfile-first handling, explicit error for binary bun.lockb, and tests for Bun lockfile behaviors.
Types & Aggregation
src/types.ts, src/aggregator.ts, report-ui/types.ts
Bumps schemaVersion to '1.4'; adds supplyChain, findings[], summary.findingCount, environment.targetNodeMajor, toolVersions.bun, and upgrade.targetNodeCompatible; aggregator accepts supplyChainResult/targetNodeMajor and generates findings.
Workspace Filter UI
src/report.ts, report-ui/main.ts, report-ui/style.css, src/workspaceFilter.ts, src/workspaceFilter.test.ts
Adds workspace select control (hidden when disabled/one workspace), builder for workspace options, UI wiring and filtering logic, CSS to hide control, and tests.
Build & Utilities
package.json, src/utils.ts, src/runners/importGraphRunner.ts, src/packageManifest.test.ts
Adds clean script invoked by build; runCommand gains timeout/maxOutputBytes, bounded buffering and escalation termination; import graph traversal ignore-list updated; package manifest test covers build/clean script.
Rendering & Export Tests
report-ui/main.ts, report-ui/types.ts, report-ui/style.css, src/cli.test.ts
UI workspace filtering wiring and types adjusted; extensive tests for SARIF/SBOM outputs, why/compare outputs, schema export, and offline audit behavior.
Helpers & Engine Compatibility
src/nodeEngine.ts, src/nodeEngine.test.ts, src/why.ts, src/workspaceFilter.ts
Adds Node engine compatibility checker, dependency-path (why) finder/formatter, workspace option builder, and tests.
Misc tests & small updates
src/*.test.ts, src/findings.test.ts, src/workspaceFilter.test.ts
Numerous new/expanded tests covering findings normalization, node-engine logic, workspace filtering, lockfile variants (Bun, PnP), and CLI behaviors.

Sequence Diagram

sequenceDiagram
    participant CLI as CLI
    participant LockfileSig as LockfileSignals
    participant Aggregator as Aggregator
    participant Findings as FindingsBuilder
    participant Renderer as OutputRenderer
    participant FS as Filesystem

    CLI->>LockfileSig: runLockfileSupplyChainSignals(projectPath,tempDir,opts)
    LockfileSig->>LockfileSig: parse lockfiles (npm/pnpm/yarn/bun), emit signals, run signature audit?
    LockfileSig-->>CLI: ToolResult { signals[], signatureAudit }

    CLI->>Aggregator: aggregateData(scanData, supplyChainResult, targetNodeMajor)
    Aggregator->>Aggregator: normalize supplyChain, set schemaVersion "1.4", compute targetNodeCompatible
    Aggregator->>Findings: buildDependencyFindings(aggregated, {targetNodeMajor})
    Findings-->>Aggregator: DependencyFinding[]

    Aggregator->>Renderer: render(format, aggregatedData)
    Renderer->>FS: write output file(s) (SARIF/CycloneDX/SPDX/JSON/HTML)
    Renderer-->>CLI: file paths / stdout
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

"I’m a rabbit tracing every chain,
hopping lockfile to lockfile down the lane,
SARIF and SBOM in my satchel bright,
Bun joins the party, signals in sight,
Findings lined up — devs nibble delight." 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.58% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Add supply-chain signals, schema output, and workspace filtering' clearly and concisely summarizes the main changes across three major feature areas introduced in the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cli-tool-improvements

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🧹 Nitpick comments (7)
src/runners/importGraphRunner.ts (1)

7-20: Trim redundant dot-prefixed ignore entries (or make the check order intentional).

At Line 85, dot-prefixed entries are skipped before directory filtering, so entries like .git, .yarn, .pnpm-store, .next, .nuxt, .svelte-kit, and .dependency-radar in IGNORED_DIRS are currently redundant. Consider simplifying the set to avoid drift.

Also applies to: 85-89

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runners/importGraphRunner.ts` around lines 7 - 20, The IGNORED_DIRS set
contains redundant dot-prefixed entries that are already excluded earlier by the
hidden-entry check; update IGNORED_DIRS in importGraphRunner.ts to remove
dot-prefixed names (e.g., '.git', '.yarn', '.pnpm-store', '.next', '.nuxt',
'.svelte-kit', '.dependency-radar') or alternatively adjust the traversal logic
so the dot-prefix check runs after the set membership test; modify the
IGNORED_DIRS declaration (symbol: IGNORED_DIRS) to only include non-dot
directory names or reorder the hidden-entry filter used by the directory
scanning function so there is no duplication.
src/runners/npmLs.test.ts (1)

264-275: Add real-world bun.lock fixtures with JSONC features to catch parser gaps.

The fixture at line 264 is strict JSON created with JSON.stringify(). The official Bun v1.2+ bun.lock format is JSONC, supporting comments and trailing commas. While the code strips comments via stripJsonComments(), it does not handle trailing commas—a feature that real Bun lockfiles can contain. The current test only validates that strict JSON fixtures work, leaving the parser's gap with trailing commas undetected. Add at least one fixture with trailing commas or pull an actual bun install output to ensure the parser handles real-world Bun lockfiles.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runners/npmLs.test.ts` around lines 264 - 275, The test writes a strict
JSON bun.lock via JSON.stringify which misses real-world JSONC features; update
the test in npmLs.test.ts that writes to path.join(projectPath, 'bun.lock') to
use a raw string fixture containing JSONC features (comments and trailing
commas) instead of JSON.stringify, or add a new bun.lock fixture with comments
and trailing commas (and optionally a real bun install output) so the parser
path exercised by stripJsonComments() plus the bun lock parsing logic handles
trailing commas; locate the write in the test (the fs.writeFile call creating
'bun.lock') and replace or add the fixture string accordingly.
src/outputFormats.ts (2)

60-67: SARIF locations use hardcoded package.json which may not reflect actual finding location.

All findings point to package.json line 1, regardless of where the issue originates (e.g., lockfile, transitive dependency). While SARIF allows this, it reduces the actionability of findings in IDE integrations.

This is acceptable for v1 but consider enriching locations in future iterations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/outputFormats.ts` around lines 60 - 67, The SARIF output currently
hardcodes every finding's location to package.json line 1 (see the locations ->
physicalLocation -> artifactLocation block), which makes IDE integrations
non-actionable; update the code that builds SARIF locations to use the actual
source file and line (for example derive artifactLocation.uri and
region.startLine from the finding metadata such as sourceFile/sourcePath and
startLine in the finding object) and fall back to package.json line 1 only when
no precise location is available so existing consumers keep working.

110-116: CycloneDX dependencies mapping has complex type casting that may silently produce incorrect output.

The chain .flatMap(...).map((entry) => entry[1]) assumes SubDependencyEntry is [string, string | null], but the intermediate Object.values(group || {}) returns unknown[] due to the as Array<...> cast. If the actual shape differs, this will silently produce incorrect data.

Consider adding validation or simplifying:

♻️ Safer extraction
     dependencies: dependencies.map((dep) => ({
       ref: dep.package.id,
-      dependsOn: Object.values(dep.graph.subDeps || {})
-        .flatMap((group) => Object.values(group || {}) as Array<[string, string | null]>)
-        .map((entry) => entry[1])
-        .filter((resolved): resolved is string => Boolean(resolved))
+      dependsOn: Object.values(dep.graph.subDeps || {})
+        .flatMap((group) => Object.values(group || {}))
+        .map((entry) => Array.isArray(entry) ? entry[1] : null)
+        .filter((resolved): resolved is string => typeof resolved === 'string' && resolved.length > 0)
     }))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/outputFormats.ts` around lines 110 - 116, The CycloneDX dependencies
mapping in dependencies: dependencies.map(...) uses an unsafe cast to
Array<[string, string | null]> when reading dep.graph.subDeps which can silently
produce wrong refs; update the extraction to validate shapes instead of casting:
iterate Object.values(dep.graph.subDeps || {}), then for each group iterate its
Object.values entries and use a type guard (e.g., check Array.isArray(entry) &&
entry.length >= 2 && (entry[1] === null || typeof entry[1] === 'string')) before
mapping entry[1], then filter to keep only strings; apply this validation where
dep.graph.subDeps is referenced (the dependencies mapping block) to ensure only
bona fide string refs are emitted.
src/runners/lockfileSignals.ts (3)

240-266: Move ReturnTypePlaceholder definition before its first usage.

ReturnTypePlaceholder is defined at lines 259-266 but first referenced at line 240. While TypeScript allows forward references to type aliases, placing the type definition after its usage reduces readability.

♻️ Move type definition before runNpmAuditSignatures
+type SignatureAuditResult = {
+  attempted: boolean;
+  ok: boolean;
+  output?: string;
+  error?: string;
+};
+
-async function runNpmAuditSignatures(projectPath: string): Promise<NonNullable<ReturnTypePlaceholder['signatureAudit']>> {
+async function runNpmAuditSignatures(projectPath: string): Promise<SignatureAuditResult> {
   try {
     ...
   }
 }
 
-type ReturnTypePlaceholder = {
-  signatureAudit?: {
-    attempted: boolean;
-    ok: boolean;
-    output?: string;
-    error?: string;
-  };
-};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runners/lockfileSignals.ts` around lines 240 - 266, Move the type alias
ReturnTypePlaceholder so it is declared before its first usage in
runNpmAuditSignatures; specifically, cut the ReturnTypePlaceholder definition
and paste it above the async function runNpmAuditSignatures so the
signatureAudit type is defined prior to being referenced, which improves
readability and keeps type declarations organized.

15-19: Comment-stripping regex may incorrectly strip content within strings.

The regex on line 18 /(^|[^:])\/\/.*$/gm attempts to preserve :// in URLs but will still incorrectly strip content in strings like "foo // bar" → "foo . Since bun.lock can contain string values with arbitrary content, this could corrupt JSON parsing.

Consider using a proper JSON5/JSONC parser or only stripping comments outside of string literals.

♻️ Safer approach: only strip if bun.lock format guarantees no inline comments in strings
 function stripJsonComments(raw: string): string {
-  return raw
-    .replace(/\/\*[\s\S]*?\*\//g, '')
-    .replace(/(^|[^:])\/\/.*$/gm, '$1');
+  // Bun lockfiles use block comments only at the top; line comments appear
+  // outside string values. A full JSONC parser would be safer if format evolves.
+  return raw
+    .replace(/\/\*[\s\S]*?\*\//g, '')
+    .replace(/^(\s*)\/\/.*$/gm, '$1');
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runners/lockfileSignals.ts` around lines 15 - 19, stripJsonComments
currently uses a regex (in function stripJsonComments) that can remove content
inside string literals (e.g., "foo // bar"); replace this with a safe approach:
either (a) switch to a proper JSONC/JSON5 parser or comment-stripping library
(e.g., jsonc-parser or strip-json-comments) and call that from
stripJsonComments, or (b) implement a simple state-machine in stripJsonComments
that walks the input and only removes // and /* */ comments when not inside
string literals (handling escapes and both single/double quotes). Update the
function to use the chosen safe method so bun.lock strings are preserved and
JSON parsing is not corrupted.

112-116: Consider adding a minimal type for the lockfile object parameter.

Using any for obj bypasses type checking. A minimal interface would catch typos and improve maintainability.

♻️ Add minimal type definition
+interface NpmLockfileShape {
+  packages?: Record<string, { resolved?: string; integrity?: string; version?: string }>;
+  dependencies?: Record<string, { resolved?: string; integrity?: string; version?: string }>;
+}
+
 function inspectNpmLockObject(
-  obj: any,
+  obj: NpmLockfileShape | null | undefined,
   sourceFile: string,
   expectedHosts: Set<string>
 ): SupplyChainSignal[] {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runners/lockfileSignals.ts` around lines 112 - 116, The parameter obj in
inspectNpmLockObject is typed as any; define a minimal interface (e.g.,
NpmLockfile or NpmLockObject) that includes only the properties the function
uses (for example packages?: Record<string, { resolved?: string; integrity?:
string }>, dependencies?: Record<string, string>, and any index signatures you
access), replace the obj: any signature with obj: NpmLockObject, and update any
property access within inspectNpmLockObject to match the new type (adjust
optional chaining or null checks as needed) so TypeScript catches mismatches and
typos.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Line 310: The Bun table row currently has two blank cells which are ambiguous;
update the row that starts with "Bun" (the cell mentioning "Text `bun.lock`
parsing; binary `bun.lockb` is reported with a migration hint") and replace the
empty Audit and Outdated cells with explicit values (e.g., set Audit to "N/A" if
auditing isn't supported and set Outdated to "⚠️" or "❌" as appropriate) so the
table shows clear, non-empty statuses.

In `@src/aggregator.ts`:
- Around line 625-647: The current isNodeEngineTargetCompatible implementation
incorrectly interprets comparator-only clauses (e.g., "<20", ">18") by
extracting literal majors instead of evaluating comparators; update
isNodeEngineTargetCompatible to use a comparator-aware check (preferably
leveraging semver.Range/semver.satisfies) by converting targetMajor into a full
semver string (e.g., `${targetMajor}.0.0`) and evaluating the original range
string with semver.satisfies(range, version) (or implement equivalent comparator
parsing that honors <, <=, >, >=, =, ~, ^, hyphen and || semantics) so
comparator-only ranges behave correctly for the range and targetMajor
parameters.

In `@src/cli.test.ts`:
- Around line 469-480: Replace the synthetic JSON generation that writes
bun.lock using JSON.stringify with a real bun.lock fixture string that contains
JSONC-style trailing commas (and optional comments) so the test exercises the
actual parser behavior; update the fs.writeFile call in src/cli.test.ts (where
projectPath is used to write 'bun.lock') to write a raw bun.lock sample (copied
from a real Bun example) that includes trailing commas, ensuring the code paths
that call stripJsonComments() and the bun lockfile parser are exercised and will
fail if trailing-comma handling is incorrect.

In `@src/compare.ts`:
- Around line 40-47: The newFindings and resolvedFindings arrays are not sorted,
causing nondeterministic output; update the return in compare.ts to sort both
(e.g., by finding.id — use (a.id || '').localeCompare(b.id) or numeric compare
if ids are numeric) after filtering so the function returns deterministically
ordered newFindings and resolvedFindings; reference the existing
previousFindingIds, currentFindingIds, newFindings, and resolvedFindings
variables when applying the sort.

In `@src/findings.ts`:
- Around line 17-43: parseSupportedNodeMajors is misclassifying comparator-only
ranges (e.g. ">=18") as only the exact major and diverges from
upgrade.targetNodeCompatible; instead of maintaining two parsers, update
supportsTargetNode/parseSupportedNodeMajors to reuse the single compatibility
check used in src/aggregator.ts (upgrade.targetNodeCompatible) or extract that
logic into a shared function and import it here; replace the current
parseSupportedNodeMajors usage in supportsTargetNode with a call to the shared
target-compatibility function so that comparator-only ranges (">=x", "<y",
open-ended bounds) are handled consistently and return the correct
boolean/undefined results.
- Around line 70-74: The title string passed into baseFinding currently builds
"vulnerability" + (vulnCount === 1 ? '' : 'ies') which produces
"vulnerabilityies" for counts >1; update the title expression in the call to
baseFinding (the title argument near vulnCount) to use a correct pluralization,
e.g. choose "vulnerability" when vulnCount === 1 and "vulnerabilities"
otherwise, ensuring the message still uses vulnCount at the front (e.g.
`${vulnCount} ${vulnCount === 1 ? 'vulnerability' : 'vulnerabilities'}`).

In `@src/outputFormats.ts`:
- Around line 5-14: The purl function currently strips the leading '@' from
scoped npm package names which violates the purl spec; update purl (function
purl, parameter DependencyRecord) to percent-encode the '@' as part of the
namespace so scoped names become "%40scope/name". Concretely, when
dep.package.name.startsWith('@'), prepend "%40" instead of removing the '@'
before splitting and encoding the remaining segments, then join and append the
encoded version as before so the returned string follows the purl npm spec.
- Line 150: The documentNamespace currently uses Date.now(), producing
non-deterministic SPDX output; replace that with a stable, input-derived
identifier by computing a deterministic hash (e.g., SHA-256) of stable inputs
such as project name, version, and generatedAt (or other canonicalized input
data) and assign documentNamespace to a fixed URI like
`https://www.dependency-radar.com/spdx/${hash}`; implement the hash computation
where SPDX output is assembled (referencing documentNamespace in
src/outputFormats.ts) and ensure a deterministic canonicalization order and a
sensible fallback when any input fields are missing.

In `@src/report.ts`:
- Around line 247-250: The workspace filter select with id "workspace-filter" is
missing a programmatic label; replace the visual-only <span
class="filter-label">Workspace</span> with a proper <label
for="workspace-filter">Workspace</label> (preserving the "filter-label" class
and placement inside the element with class "filter-group
workspace-filter-group") so screen readers correctly associate the label with
the select element.

In `@src/runners/lockfileGraph.ts`:
- Around line 128-136: buildBunJsonResolvedTree/buildBunJsonNode currently drop
the requested selector (name@ref) and memoize/track by bare package name,
causing findBunPackageEntry to resolve the wrong version when bun.lock has
multiple versions; change the recursion to carry the full selector (e.g.,
"name@specifier") into buildBunJsonNode, use that selector as the key for memo
and stack (instead of dep name), and use that selector when looking up entries
in findBunPackageEntry so the exact requested ref is resolved and the correct
subtree attached; update the loop in buildBunJsonResolvedTree to call
buildBunJsonNode with the full spec from collectPackageJsonDependencySpecs and
ensure dependencies are indexed by the resolved node.name but
memoization/cycle-detection use the selector.

In `@src/runners/lockfileSignals.ts`:
- Around line 283-287: The current conditional sets signatureAudit to {
attempted: false, ok: true, error: 'skipped (--offline)' } when
options.auditSignatures is true but options.offline is true, which incorrectly
signals success; change the skipped branch in the signatureAudit assignment (the
ternary that uses runNpmAuditSignatures, options.auditSignatures and
options.offline) so the returned object clearly indicates a skipped state (for
example set ok: false or add status: 'skipped' and attempted: false) instead of
ok: true, and update any consumers of signatureAudit that rely on ok to handle
the explicit skipped state consistently; reference runNpmAuditSignatures,
signatureAudit, options.auditSignatures and options.offline when making the
change.

In `@src/schema.ts`:
- Around line 42-50: The schema's SupplyChainSignal.type enum incorrectly
includes the signature-audit values; remove "signature-verification-failed" and
"signature-verification-unavailable" from the signals[].type enum in
src/schema.ts so that signals[].type only contains supply-chain signal values
(e.g., 'git-dependency', 'file-dependency', 'non-registry-tarball',
'missing-integrity', 'unexpected-registry-host'), and ensure any signature
verification data remains modeled under supplyChain.signatureAudit (leave
supplyChain.signatureAudit untouched).

In `@src/utils.ts`:
- Around line 43-60: The current collect function enforces maxOutputBytes
separately for stdout and stderr, allowing combined output to exceed the limit;
modify the logic to track and enforce a single combined byte counter (e.g., add
totalBytes variable) and have collect accept/update that shared total (or
compute total = stdoutBytes + stderrBytes before pushing) so both stdout and
stderr event handlers call collect with and update the shared totalBytes; ensure
outputExceeded is set and child.kill('SIGTERM') is invoked when the shared total
reaches maxOutputBytes, and continue pushing only the remaining bytes from the
current chunk into stdoutChunks or stderrChunks as appropriate.

---

Nitpick comments:
In `@src/outputFormats.ts`:
- Around line 60-67: The SARIF output currently hardcodes every finding's
location to package.json line 1 (see the locations -> physicalLocation ->
artifactLocation block), which makes IDE integrations non-actionable; update the
code that builds SARIF locations to use the actual source file and line (for
example derive artifactLocation.uri and region.startLine from the finding
metadata such as sourceFile/sourcePath and startLine in the finding object) and
fall back to package.json line 1 only when no precise location is available so
existing consumers keep working.
- Around line 110-116: The CycloneDX dependencies mapping in dependencies:
dependencies.map(...) uses an unsafe cast to Array<[string, string | null]> when
reading dep.graph.subDeps which can silently produce wrong refs; update the
extraction to validate shapes instead of casting: iterate
Object.values(dep.graph.subDeps || {}), then for each group iterate its
Object.values entries and use a type guard (e.g., check Array.isArray(entry) &&
entry.length >= 2 && (entry[1] === null || typeof entry[1] === 'string')) before
mapping entry[1], then filter to keep only strings; apply this validation where
dep.graph.subDeps is referenced (the dependencies mapping block) to ensure only
bona fide string refs are emitted.

In `@src/runners/importGraphRunner.ts`:
- Around line 7-20: The IGNORED_DIRS set contains redundant dot-prefixed entries
that are already excluded earlier by the hidden-entry check; update IGNORED_DIRS
in importGraphRunner.ts to remove dot-prefixed names (e.g., '.git', '.yarn',
'.pnpm-store', '.next', '.nuxt', '.svelte-kit', '.dependency-radar') or
alternatively adjust the traversal logic so the dot-prefix check runs after the
set membership test; modify the IGNORED_DIRS declaration (symbol: IGNORED_DIRS)
to only include non-dot directory names or reorder the hidden-entry filter used
by the directory scanning function so there is no duplication.

In `@src/runners/lockfileSignals.ts`:
- Around line 240-266: Move the type alias ReturnTypePlaceholder so it is
declared before its first usage in runNpmAuditSignatures; specifically, cut the
ReturnTypePlaceholder definition and paste it above the async function
runNpmAuditSignatures so the signatureAudit type is defined prior to being
referenced, which improves readability and keeps type declarations organized.
- Around line 15-19: stripJsonComments currently uses a regex (in function
stripJsonComments) that can remove content inside string literals (e.g., "foo //
bar"); replace this with a safe approach: either (a) switch to a proper
JSONC/JSON5 parser or comment-stripping library (e.g., jsonc-parser or
strip-json-comments) and call that from stripJsonComments, or (b) implement a
simple state-machine in stripJsonComments that walks the input and only removes
// and /* */ comments when not inside string literals (handling escapes and both
single/double quotes). Update the function to use the chosen safe method so
bun.lock strings are preserved and JSON parsing is not corrupted.
- Around line 112-116: The parameter obj in inspectNpmLockObject is typed as
any; define a minimal interface (e.g., NpmLockfile or NpmLockObject) that
includes only the properties the function uses (for example packages?:
Record<string, { resolved?: string; integrity?: string }>, dependencies?:
Record<string, string>, and any index signatures you access), replace the obj:
any signature with obj: NpmLockObject, and update any property access within
inspectNpmLockObject to match the new type (adjust optional chaining or null
checks as needed) so TypeScript catches mismatches and typos.

In `@src/runners/npmLs.test.ts`:
- Around line 264-275: The test writes a strict JSON bun.lock via JSON.stringify
which misses real-world JSONC features; update the test in npmLs.test.ts that
writes to path.join(projectPath, 'bun.lock') to use a raw string fixture
containing JSONC features (comments and trailing commas) instead of
JSON.stringify, or add a new bun.lock fixture with comments and trailing commas
(and optionally a real bun install output) so the parser path exercised by
stripJsonComments() plus the bun lock parsing logic handles trailing commas;
locate the write in the test (the fs.writeFile call creating 'bun.lock') and
replace or add the fixture string accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b7eb08ff-4770-49fd-a5d6-a190f082e30e

📥 Commits

Reviewing files that changed from the base of the PR and between 59c744b and 5ee2fa8.

⛔ Files ignored due to path filters (23)
  • dist/aggregator.js is excluded by !**/dist/**
  • dist/cli.js is excluded by !**/dist/**
  • dist/compare.js is excluded by !**/dist/**
  • dist/failOn.js is excluded by !**/dist/**
  • dist/findings.js is excluded by !**/dist/**
  • dist/generated/spdx.js is excluded by !**/dist/**, !**/generated/**
  • dist/outputFormats.js is excluded by !**/dist/**
  • dist/report-assets.js is excluded by !**/dist/**
  • dist/report.js is excluded by !**/dist/**
  • dist/runners/depcheckRunner.js is excluded by !**/dist/**
  • dist/runners/importGraphRunner.js is excluded by !**/dist/**
  • dist/runners/licenseChecker.js is excluded by !**/dist/**
  • dist/runners/lockfileGraph.js is excluded by !**/dist/**
  • dist/runners/lockfileSignals.js is excluded by !**/dist/**
  • dist/runners/madgeRunner.js is excluded by !**/dist/**
  • dist/runners/npmLs.js is excluded by !**/dist/**
  • dist/schema.js is excluded by !**/dist/**
  • dist/utils.js is excluded by !**/dist/**
  • dist/why.js is excluded by !**/dist/**
  • dist/workspaceFilter.js is excluded by !**/dist/**
  • report-ui/dist/report.css is excluded by !**/dist/**
  • report-ui/dist/report.iife.js is excluded by !**/dist/**
  • src/generated/spdx.ts is excluded by !**/generated/**
📒 Files selected for processing (29)
  • README.md
  • package.json
  • report-ui/main.ts
  • report-ui/style.css
  • report-ui/types.ts
  • src/aggregator.ts
  • src/cli.test.ts
  • src/cli.ts
  • src/compare.ts
  • src/failOn.test.ts
  • src/failOn.ts
  • src/findings.test.ts
  • src/findings.ts
  • src/outputFormats.ts
  • src/packageManifest.test.ts
  • src/report-assets.ts
  • src/report.ts
  • src/runners/importGraphRunner.ts
  • src/runners/lockfileGraph.ts
  • src/runners/lockfileSignals.test.ts
  • src/runners/lockfileSignals.ts
  • src/runners/npmLs.test.ts
  • src/runners/npmLs.ts
  • src/schema.ts
  • src/types.ts
  • src/utils.ts
  • src/why.ts
  • src/workspaceFilter.test.ts
  • src/workspaceFilter.ts

Comment thread README.md Outdated
Comment thread src/aggregator.ts Outdated
Comment on lines +625 to +647
function isNodeEngineTargetCompatible(range: string, targetMajor: number): boolean {
const normalized = range.trim();
if (!normalized || normalized === '*' || normalized.toLowerCase() === 'x') return true;
const clauses = normalized.split('||').map((clause) => clause.trim()).filter(Boolean);
if (clauses.length === 0) return true;
return clauses.some((clause) => {
const lower = clause.match(/>=\s*v?(\d+)/);
const upper = clause.match(/<\s*v?(\d+)/);
if (lower && upper) {
const min = Number.parseInt(lower[1], 10);
const max = Number.parseInt(upper[1], 10);
return targetMajor >= min && targetMajor < max;
}
const majors = Array.from(clause.matchAll(/(?:^|[\s>=<~^])v?(\d+)/g))
.map((match) => Number.parseInt(match[1], 10))
.filter((major) => Number.isFinite(major));
if (majors.length === 0) return true;
if (/^\s*[~^]?\s*v?\d+/.test(clause) && !/[<>]/.test(clause)) {
return majors.includes(targetMajor);
}
if (lower && !upper) return targetMajor >= Number.parseInt(lower[1], 10);
return majors.includes(targetMajor);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Comparator-only engine ranges are evaluated incorrectly here.

<20 currently marks Node 18 as incompatible and Node 20 as compatible, and >18 marks Node 19 as incompatible. That makes upgrade.targetNodeCompatible unreliable for common engines.node syntax. This needs a shared comparator-aware evaluator rather than extracting literal majors from the clause.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/aggregator.ts` around lines 625 - 647, The current
isNodeEngineTargetCompatible implementation incorrectly interprets
comparator-only clauses (e.g., "<20", ">18") by extracting literal majors
instead of evaluating comparators; update isNodeEngineTargetCompatible to use a
comparator-aware check (preferably leveraging semver.Range/semver.satisfies) by
converting targetMajor into a full semver string (e.g., `${targetMajor}.0.0`)
and evaluating the original range string with semver.satisfies(range, version)
(or implement equivalent comparator parsing that honors <, <=, >, >=, =, ~, ^,
hyphen and || semantics) so comparator-only ranges behave correctly for the
range and targetMajor parameters.

Comment thread src/cli.test.ts Outdated
Comment thread src/compare.ts Outdated
Comment thread src/findings.ts Outdated
Comment thread src/report.ts
Comment thread src/runners/lockfileGraph.ts
Comment thread src/runners/lockfileSignals.ts
Comment thread src/schema.ts Outdated
Comment thread src/utils.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/cli.ts (1)

537-558: ⚠️ Potential issue | 🟠 Major

detectScanManager() still prefers stale Yarn/npm lockfiles over Bun.

A repo with packageManager: "bun" plus a leftover yarn.lock or package-lock.json will never reach the Bun path here, because the earlier lockfile checks win first. That produces the wrong dependency graph for the migration/stale-lockfile case this PR is explicitly trying to support. Prefer the inferred manager when its lockfile exists, or at least check Bun before unrelated lockfiles when fallback === "bun".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli.ts` around lines 537 - 558, detectScanManager currently returns
"yarn" or "npm" when stale yarn/npm lockfiles exist even if the project is
actually using Bun; reorder or add conditional checks so Bun lockfiles are
considered before unrelated lockfiles when the inferred manager should be Bun.
Specifically, inside detectScanManager check for Bun lockfiles (bun.lock or
bun.lockb) and the bun node_modules marker early (or add an explicit branch: if
(fallback === "bun") then check bun lockfiles first) so that functions
referencing detectScanManager will return "bun" when Bun artifacts are present;
update the logic around pathExists checks for bun.lock/bun.lockb and
node_modules/.pnpm/.yarn-state.yml accordingly to ensure Bun is preferred when
appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/cli.ts`:
- Around line 1670-1673: The code currently treats a user-supplied opts.out
equal to "dependency-radar.html" as if it were not provided and overwrites it
with defaultOutputName(opts.format); instead, detect whether --out was passed
explicitly (e.g., via the parsed options metadata or by checking argv) and only
replace the output when the user did not provide --out; update the logic around
opts.out, outputPath and the defaultOutputName(...) call to use this
explicit-flag check rather than comparing against the magic filename, and apply
the same explicit-presence fix to the new --schema path handling so
user-provided schema paths are never overwritten by a filename comparison.

In `@src/nodeEngine.ts`:
- Around line 48-57: isNodeEngineTargetCompatible currently tests only a single
concrete version [targetMajor,0,0], causing false negatives; change the logic to
treat the "target" as the interval [targetMajor.0.0, (targetMajor+1).0.0) and
test whether the parsed range overlaps that interval. Concretely, in
isNodeEngineTargetCompatible replace the single target tuple with minTarget =
[targetMajor,0,0] and maxTarget = [targetMajor+1,0,0], then for each clause use
expandToken to build comparators and determine if those comparators allow any
version in the interval (e.g., implement a small helper that, given comparators
from expandToken and the interval boundaries, returns true if the comparator set
does not exclude the whole interval — you can reuse satisfiesComparator by
checking it against the interval endpoints and/or reasoning about <=/>= bounds).
Update the final .some(...) check to use that interval-overlap helper instead of
satisfiesComparator(target, ...).

In `@src/outputFormats.ts`:
- Around line 144-148: The current license resolution uses declared?.valid to
pick declared.spdxId but doesn't ensure declared.spdxId is truthy; update the
logic around declared, inferred, and license (the variables derived from
dep.compliance.license) to require a non-empty declared.spdxId when
declared.valid is true, e.g. only use declared.spdxId if declared?.valid &&
declared.spdxId is truthy, otherwise fall back to inferred?.spdxId and then
'NOASSERTION'.

In `@src/runners/lockfileGraph.ts`:
- Around line 133-140: The code falls back to Yarn parsing when
readBunJsonLock(raw) fails, which masks Bun JSON parse errors; modify the logic
in the function around the readBunJsonLock call so that if the raw lock content
(raw) trimmed begins with '{' and readBunJsonLock returned falsy, you
immediately return undefined (do not attempt parseYarnV1/parseYarnV2); otherwise
continue to try the Yarn parsers and call buildYarnResolvedTree as before.
Reference: readBunJsonLock, parseYarnV1, parseYarnV2, buildBunJsonResolvedTree,
buildYarnResolvedTree (and leave runNpmLs to handle Bub JSON parse errors).

In `@src/utils.ts`:
- Around line 18-19: The timeoutMs and maxOutputBytes values are being set
directly from options or Number(process.env...) without validating the parsed
numeric result, which allows NaN to disable guards; update the initialization
logic for timeoutMs and maxOutputBytes in utils.ts to: parse the env value (or
options value if provided), check Number.isFinite(value) and value > 0 (or >= 0
if zero allowed), and only use it if valid—otherwise fall back to the existing
default constants (120000 and 50*1024*1024). Ensure you validate both the
options-provided values and the env-parsed values so timeoutMs and
maxOutputBytes are never NaN or non-finite; reference the timeoutMs and
maxOutputBytes variables when implementing these checks.

---

Outside diff comments:
In `@src/cli.ts`:
- Around line 537-558: detectScanManager currently returns "yarn" or "npm" when
stale yarn/npm lockfiles exist even if the project is actually using Bun;
reorder or add conditional checks so Bun lockfiles are considered before
unrelated lockfiles when the inferred manager should be Bun. Specifically,
inside detectScanManager check for Bun lockfiles (bun.lock or bun.lockb) and the
bun node_modules marker early (or add an explicit branch: if (fallback ===
"bun") then check bun lockfiles first) so that functions referencing
detectScanManager will return "bun" when Bun artifacts are present; update the
logic around pathExists checks for bun.lock/bun.lockb and
node_modules/.pnpm/.yarn-state.yml accordingly to ensure Bun is preferred when
appropriate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c885592e-7072-4ae2-a0eb-4e289b71741d

📥 Commits

Reviewing files that changed from the base of the PR and between 5ee2fa8 and c123d3c.

⛔ Files ignored due to path filters (12)
  • dist/aggregator.js is excluded by !**/dist/**
  • dist/cli.js is excluded by !**/dist/**
  • dist/compare.js is excluded by !**/dist/**
  • dist/findings.js is excluded by !**/dist/**
  • dist/nodeEngine.js is excluded by !**/dist/**
  • dist/outputFormats.js is excluded by !**/dist/**
  • dist/report.js is excluded by !**/dist/**
  • dist/runners/importGraphRunner.js is excluded by !**/dist/**
  • dist/runners/lockfileGraph.js is excluded by !**/dist/**
  • dist/runners/lockfileSignals.js is excluded by !**/dist/**
  • dist/schema.js is excluded by !**/dist/**
  • dist/utils.js is excluded by !**/dist/**
📒 Files selected for processing (19)
  • README.md
  • report-ui/types.ts
  • src/aggregator.ts
  • src/cli.test.ts
  • src/cli.ts
  • src/compare.ts
  • src/findings.ts
  • src/nodeEngine.test.ts
  • src/nodeEngine.ts
  • src/outputFormats.ts
  • src/report.ts
  • src/runners/importGraphRunner.ts
  • src/runners/lockfileGraph.ts
  • src/runners/lockfileSignals.test.ts
  • src/runners/lockfileSignals.ts
  • src/runners/npmLs.test.ts
  • src/schema.ts
  • src/types.ts
  • src/utils.ts
✅ Files skipped from review due to trivial changes (1)
  • src/aggregator.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/report.ts
  • src/runners/importGraphRunner.ts
  • src/runners/npmLs.test.ts
  • src/runners/lockfileSignals.ts
  • src/runners/lockfileSignals.test.ts
  • src/cli.test.ts

Comment thread src/cli.ts Outdated
Comment thread src/nodeEngine.ts Outdated
Comment thread src/outputFormats.ts
Comment thread src/runners/lockfileGraph.ts
Comment thread src/utils.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/cli.ts (1)

1827-1838: ⚠️ Potential issue | 🟡 Minor

Skipped registry collectors are reported as failures for Bun scans.

supportsRegistryCollectors(scanManager) correctly skips runPackageAudit/runPackageOutdated for Bun, but the later status checks still treat the resulting undefined entries as unavailable. That makes Bun scans print failure-style audit/outdated messages even when nothing actually failed. Please track support/attempted state separately and report these as skipped instead.

Also applies to: 1867-1901

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli.ts` around lines 1827 - 1838, The code path that gates
runPackageAudit/runPackageOutdated with supportsRegistryCollectors(scanManager)
returns undefined when collectors are unsupported, but downstream status checks
treat undefined as an unavailable failure; adjust the flow so skipped collectors
are explicitly represented and not treated as failures. Change the conditional
that currently returns Promise.resolve(undefined) to instead return a ToolResult
(or similar result shape used elsewhere) indicating a skipped status (e.g., {
ok: true/false as your contract expects, status: 'skipped', error: undefined })
or a distinct marker object, and update the downstream status-check logic (the
code that inspects these results around the audit/outdated reporting) to
recognize this skipped marker rather than treating undefined as unavailable; use
the existing functions/objects supportsRegistryCollectors, runPackageAudit,
runPackageOutdated and ToolResult names to locate and update both the branch
that creates the promise and the later reporting logic so Bun scans show
"skipped" instead of failure-style messages.
♻️ Duplicate comments (1)
src/nodeEngine.ts (1)

40-47: ⚠️ Potential issue | 🟠 Major

The overlap check still uses two sample points instead of the full major interval.

This still produces false negatives for valid mid-major windows. For example, >=18.500.0 <18.600.0 should be compatible with target major 18, but neither 18.0.0 nor 18.999.999 satisfies both comparators, so this returns false. Please compute interval intersection directly from the comparator bounds instead of probing sentinel versions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/nodeEngine.ts` around lines 40 - 47, comparatorsOverlapTargetMajor
currently probes two sentinel versions and misses valid mid-major ranges; fix it
by computing each comparator's effective lower/upper bounds and intersecting
them with the target major interval instead of using satisfiesComparator on
sample points. Update comparatorsOverlapTargetMajor to parse/combine bounds from
each comparator (reuse/combine logic in comparatorAllowsTargetMajor or extract a
helper that returns numeric lower/upper VersionTuple bounds for a comparator),
intersect all comparator bounds to produce a final interval, and return true if
that final interval intersects [targetMajor.0.0, targetMajor+1.0.0) (use
inclusive/exclusive semantics consistent with existing comparator parsing).
Ensure you still use comparatorAllowsTargetMajor, satisfiesComparator only as
helpers if needed but base the decision on interval arithmetic rather than
sentinel samples.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/cli.ts`:
- Around line 1676-1678: The default output path is currently resolved against
the caller CWD using path.resolve(opts.out) when no --out is provided; change it
so when opts.outProvided is false (and you're setting opts.out via
defaultOutputName(opts.format)) you resolve that default against projectPath
instead of the shell CWD. Concretely, in the branch that checks
!opts.outProvided && opts.format !== "html", keep assigning opts.out =
defaultOutputName(opts.format) but set outputPath = path.resolve(projectPath,
opts.out) (and ensure any downstream uses of outputPath/or opts.out expect this
repo-root anchored path). Use the existing symbols opts.outProvided,
opts.format, defaultOutputName, outputPath, projectPath to locate and update the
code.

---

Outside diff comments:
In `@src/cli.ts`:
- Around line 1827-1838: The code path that gates
runPackageAudit/runPackageOutdated with supportsRegistryCollectors(scanManager)
returns undefined when collectors are unsupported, but downstream status checks
treat undefined as an unavailable failure; adjust the flow so skipped collectors
are explicitly represented and not treated as failures. Change the conditional
that currently returns Promise.resolve(undefined) to instead return a ToolResult
(or similar result shape used elsewhere) indicating a skipped status (e.g., {
ok: true/false as your contract expects, status: 'skipped', error: undefined })
or a distinct marker object, and update the downstream status-check logic (the
code that inspects these results around the audit/outdated reporting) to
recognize this skipped marker rather than treating undefined as unavailable; use
the existing functions/objects supportsRegistryCollectors, runPackageAudit,
runPackageOutdated and ToolResult names to locate and update both the branch
that creates the promise and the later reporting logic so Bun scans show
"skipped" instead of failure-style messages.

---

Duplicate comments:
In `@src/nodeEngine.ts`:
- Around line 40-47: comparatorsOverlapTargetMajor currently probes two sentinel
versions and misses valid mid-major ranges; fix it by computing each
comparator's effective lower/upper bounds and intersecting them with the target
major interval instead of using satisfiesComparator on sample points. Update
comparatorsOverlapTargetMajor to parse/combine bounds from each comparator
(reuse/combine logic in comparatorAllowsTargetMajor or extract a helper that
returns numeric lower/upper VersionTuple bounds for a comparator), intersect all
comparator bounds to produce a final interval, and return true if that final
interval intersects [targetMajor.0.0, targetMajor+1.0.0) (use
inclusive/exclusive semantics consistent with existing comparator parsing).
Ensure you still use comparatorAllowsTargetMajor, satisfiesComparator only as
helpers if needed but base the decision on interval arithmetic rather than
sentinel samples.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 34e8b03e-2d74-4fa2-beae-948e94e490de

📥 Commits

Reviewing files that changed from the base of the PR and between c123d3c and 0f20bd5.

⛔ Files ignored due to path filters (5)
  • dist/cli.js is excluded by !**/dist/**
  • dist/nodeEngine.js is excluded by !**/dist/**
  • dist/outputFormats.js is excluded by !**/dist/**
  • dist/runners/lockfileGraph.js is excluded by !**/dist/**
  • dist/utils.js is excluded by !**/dist/**
📒 Files selected for processing (6)
  • src/cli.ts
  • src/nodeEngine.test.ts
  • src/nodeEngine.ts
  • src/outputFormats.ts
  • src/runners/lockfileGraph.ts
  • src/utils.ts
✅ Files skipped from review due to trivial changes (1)
  • src/runners/lockfileGraph.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/nodeEngine.test.ts
  • src/utils.ts

Comment thread src/cli.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/cli.ts (1)

1670-1684: ⚠️ Potential issue | 🟠 Major

Resolve the default output against projectPath, not the caller CWD.

outputPath still starts from path.resolve(opts.out), so scan --project /other/repo writes the default HTML report in the shell's working directory instead of the scanned project root. That regresses the repo-root default behavior.

Suggested fix
-  let outputPath = path.resolve(opts.out);
+  let outputPath = opts.outProvided
+    ? path.resolve(opts.out)
+    : path.resolve(projectPath, opts.out);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli.ts` around lines 1670 - 1684, The code currently sets outputPath =
path.resolve(opts.out) which causes the default report filename to be resolved
against the caller CWD instead of the scanned project root; update the logic so
when you assign the default (in the block checking !opts.outProvided &&
opts.format !== "html") you also set outputPath = path.resolve(projectPath,
opts.out) (i.e., resolve opts.out against projectPath), and ensure the initial
outputPath assignment uses opts.out only if opts.outProvided (or move the
initial path.resolve(opts.out) after projectPath is available) so that
projectPath, outputPath, opts.outProvided, opts.format and defaultOutputName are
consistent.
🧹 Nitpick comments (1)
src/nodeEngine.test.ts (1)

4-20: Add a regression for malformed ranges.

This suite covers the happy path, but it doesn't lock in the new fail-closed behavior for bad comparator tokens. A case like >=18foo should stay rejected once the parser is tightened.

Suggested test
 describe('isNodeEngineTargetCompatible', () => {
+  it('rejects malformed comparator tokens', () => {
+    expect(isNodeEngineTargetCompatible('>=18foo', 18)).toBe(false);
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/nodeEngine.test.ts` around lines 4 - 20, Add a regression test to ensure
malformed comparator tokens are rejected: update the
isNodeEngineTargetCompatible test suite to include a case such as
isNodeEngineTargetCompatible('>=18foo', 18) and assert it returns false (or
otherwise fails closed). Locate the tests around the existing
describe('isNodeEngineTargetCompatible', ...) block and add an it() that
verifies malformed ranges like '>=18foo' (and optionally other malformed
strings) do not pass; reference the isNodeEngineTargetCompatible function in the
assertion so the new test protects the tightened parser behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/nodeEngine.ts`:
- Around line 13-25: satisfiesComparator currently uses a regex that only
matches a prefix, allowing malformed strings like ">=18foo" or "18.0.0-beta" to
be treated as valid; update the comparator parsing in satisfiesComparator to
require a full-string match (anchor the pattern with ^...$) and tighten the
version capture to only allow numeric major/minor/patch parts (no trailing
characters or prerelease tags) so parseVersion/compare are only invoked for
fully-matched, well-formed comparators; apply the same fix to the other similar
parsing blocks referenced (the ones around lines 39-50 and 125-135) so all
comparator parsing is anchored and rejects malformed inputs before proceeding.

---

Duplicate comments:
In `@src/cli.ts`:
- Around line 1670-1684: The code currently sets outputPath =
path.resolve(opts.out) which causes the default report filename to be resolved
against the caller CWD instead of the scanned project root; update the logic so
when you assign the default (in the block checking !opts.outProvided &&
opts.format !== "html") you also set outputPath = path.resolve(projectPath,
opts.out) (i.e., resolve opts.out against projectPath), and ensure the initial
outputPath assignment uses opts.out only if opts.outProvided (or move the
initial path.resolve(opts.out) after projectPath is available) so that
projectPath, outputPath, opts.outProvided, opts.format and defaultOutputName are
consistent.

---

Nitpick comments:
In `@src/nodeEngine.test.ts`:
- Around line 4-20: Add a regression test to ensure malformed comparator tokens
are rejected: update the isNodeEngineTargetCompatible test suite to include a
case such as isNodeEngineTargetCompatible('>=18foo', 18) and assert it returns
false (or otherwise fails closed). Locate the tests around the existing
describe('isNodeEngineTargetCompatible', ...) block and add an it() that
verifies malformed ranges like '>=18foo' (and optionally other malformed
strings) do not pass; reference the isNodeEngineTargetCompatible function in the
assertion so the new test protects the tightened parser behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 130753ba-a8ff-4518-87ee-2d9f43aeed99

📥 Commits

Reviewing files that changed from the base of the PR and between 0f20bd5 and c84c375.

⛔ Files ignored due to path filters (2)
  • dist/cli.js is excluded by !**/dist/**
  • dist/nodeEngine.js is excluded by !**/dist/**
📒 Files selected for processing (4)
  • src/cli.ts
  • src/nodeEngine.test.ts
  • src/nodeEngine.ts
  • src/types.ts

Comment thread src/nodeEngine.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
src/cli.ts (4)

546-563: Redundant Bun lockfile check at Line 558.

The check at Line 558 duplicates the check at Line 549. Once bun.lock/bun.lockb is checked on Line 549, reaching Line 558 implies that check already failed, so the second check will always be false.

🔧 Suggested fix: remove redundant check
   if (await pathExists(path.join(projectPath, "node_modules", ".pnpm")))
     return "pnpm";
-  if ((await pathExists(path.join(projectPath, "bun.lock"))) || (await pathExists(path.join(projectPath, "bun.lockb")))) return "bun";
   if (
     await pathExists(path.join(projectPath, "node_modules", ".yarn-state.yml"))
   )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli.ts` around lines 546 - 563, Remove the duplicated bun lockfile
existence check that repeats "(await pathExists(path.join(projectPath,
'bun.lock'))) || (await pathExists(path.join(projectPath, 'bun.lockb')))" (the
second occurrence after the pnpm/yarn checks); leave the initial bun check that
returns "bun" when fallback === "bun" and the earlier one immediately after, and
ensure all other package manager checks (pnpm, yarn, npm, node_modules checks)
remain unchanged so control flow and returned fallback logic are preserved.

2208-2215: Consider validating the previous report structure.

The JSON is parsed and cast to AggregatedData without schema validation. If the file is from an incompatible schema version or is malformed beyond JSON syntax errors, downstream code may fail with unclear errors. A lightweight version check (e.g., verifying previous.schema matches expected version) would improve robustness.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli.ts` around lines 2208 - 2215, The parsed `previous` report is cast to
AggregatedData without validating its schema; after parsing the JSON from
`previousPath` (the try block that assigns `previous`), verify that the
resulting object has the expected schema/version field (e.g., `previous.schema
=== EXPECTED_SCHEMA_VERSION`) and required top-level properties, and if the
check fails log a clear error via `console.error` including the mismatched or
missing schema value and then `process.exit(1)`; update the try/catch to
separate JSON parse errors from schema validation failures so downstream code
using `previous` (the AggregatedData variable) only runs when the report matches
the expected structure.

2237-2252: --schema takes precedence over all commands.

When --schema is passed, it outputs the schema regardless of the command (e.g., dependency-radar explain foo --schema outputs schema, not explanation). This is similar to how --help works but may surprise users. If this is intentional, consider documenting it in the help text.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli.ts` around lines 2237 - 2252, The current logic makes opts.schema
take precedence over any command; change it so runSchemaCommand is only executed
when no other command is specified or when the command explicitly requests
schema (e.g., opts.command === "schema"), so replace the top-level check if
(opts.schema) { await runSchemaCommand(opts); return; } with a guarded condition
such as if (opts.schema && !opts.command) || opts.command === "schema" then call
runSchemaCommand; leave calls to runExplainCommand, runWhyCommand,
runCompareCommand untouched so commands like "explain"/"why"/"compare" run even
if --schema is present (or alternatively, if you prefer keeping current
behavior, update help text to document that opts.schema supersedes commands).

1889-1896: Use the structured status field instead of magic string comparison.

The SignatureAuditResult type already provides a status?: 'verified' | 'failed' | 'skipped' field. Replace audit?.error === "skipped (--offline)" with audit?.status === 'skipped' to avoid fragility if the error message changes and to rely on the type system rather than string matching.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli.ts` around lines 1889 - 1896, Replace the fragile magic-string check
for skipped audits with the structured status field on SignatureAuditResult: in
the block that reads the audit from supplyChainResult.data?.signatureAudit (used
when opts.auditSignatures is true) change the condition audit?.error ===
"skipped (--offline)" to audit?.status === 'skipped' and leave the
spinner.log/statusLine behavior unchanged so the "skipped" case uses the
structured audit.status instead of matching audit.error text.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/nodeEngine.ts`:
- Around line 103-121: expandToken currently mishandles x-ranges, tilde
semantics, and bare versions; update expandToken to first add an x-range handler
(matching forms like 18.x, 18.* or 18.5.x) before the caret/tilde logic and
treat them as ranged tokens (e.g., 18.x => >=18.0.0 <19.0.0, 18.5.x => >=18.5.0
<18.6.0), then change the tilde branch (function/regex around variable tilde) to
distinguish ~18 (treat like 18.x => major range) versus ~18.5 (treat as >=18.5.0
<18.6.0), and finally update the bare-version branch (the parseVersion usage) to
emit exact match when a patch is present (18.5.1 => =18.5.1), a minor range when
only major+minor are present (18.5 => >=18.5.0 <18.6.0), and a major range when
only major present (18 => >=18.0.0 <19.0.0); keep references to expandToken, the
caret/tilde handlers, and parseVersion to locate the changes.

---

Nitpick comments:
In `@src/cli.ts`:
- Around line 546-563: Remove the duplicated bun lockfile existence check that
repeats "(await pathExists(path.join(projectPath, 'bun.lock'))) || (await
pathExists(path.join(projectPath, 'bun.lockb')))" (the second occurrence after
the pnpm/yarn checks); leave the initial bun check that returns "bun" when
fallback === "bun" and the earlier one immediately after, and ensure all other
package manager checks (pnpm, yarn, npm, node_modules checks) remain unchanged
so control flow and returned fallback logic are preserved.
- Around line 2208-2215: The parsed `previous` report is cast to AggregatedData
without validating its schema; after parsing the JSON from `previousPath` (the
try block that assigns `previous`), verify that the resulting object has the
expected schema/version field (e.g., `previous.schema ===
EXPECTED_SCHEMA_VERSION`) and required top-level properties, and if the check
fails log a clear error via `console.error` including the mismatched or missing
schema value and then `process.exit(1)`; update the try/catch to separate JSON
parse errors from schema validation failures so downstream code using `previous`
(the AggregatedData variable) only runs when the report matches the expected
structure.
- Around line 2237-2252: The current logic makes opts.schema take precedence
over any command; change it so runSchemaCommand is only executed when no other
command is specified or when the command explicitly requests schema (e.g.,
opts.command === "schema"), so replace the top-level check if (opts.schema) {
await runSchemaCommand(opts); return; } with a guarded condition such as if
(opts.schema && !opts.command) || opts.command === "schema" then call
runSchemaCommand; leave calls to runExplainCommand, runWhyCommand,
runCompareCommand untouched so commands like "explain"/"why"/"compare" run even
if --schema is present (or alternatively, if you prefer keeping current
behavior, update help text to document that opts.schema supersedes commands).
- Around line 1889-1896: Replace the fragile magic-string check for skipped
audits with the structured status field on SignatureAuditResult: in the block
that reads the audit from supplyChainResult.data?.signatureAudit (used when
opts.auditSignatures is true) change the condition audit?.error === "skipped
(--offline)" to audit?.status === 'skipped' and leave the spinner.log/statusLine
behavior unchanged so the "skipped" case uses the structured audit.status
instead of matching audit.error text.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 35705b5d-dbc5-43ec-9040-a6aaab0a80c2

📥 Commits

Reviewing files that changed from the base of the PR and between c84c375 and 2df2745.

⛔ Files ignored due to path filters (2)
  • dist/cli.js is excluded by !**/dist/**
  • dist/nodeEngine.js is excluded by !**/dist/**
📒 Files selected for processing (3)
  • src/cli.ts
  • src/nodeEngine.test.ts
  • src/nodeEngine.ts
✅ Files skipped from review due to trivial changes (1)
  • src/nodeEngine.test.ts

Comment thread src/nodeEngine.ts
@JosephMaynard
JosephMaynard merged commit 4ed0533 into master Apr 28, 2026
1 check passed
@JosephMaynard
JosephMaynard deleted the feat/cli-tool-improvements branch April 28, 2026 18:08
@coderabbitai coderabbitai Bot mentioned this pull request Jul 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant