Skip to content

Feat: Lockfile First Dependency Graph - #12

Merged
JosephMaynard merged 4 commits into
masterfrom
feat/lockfile-first-dependency-graph
Feb 21, 2026
Merged

JosephMaynard merged 4 commits into
masterfrom
feat/lockfile-first-dependency-graph

Conversation

@JosephMaynard

@JosephMaynard JosephMaynard commented Feb 21, 2026 •

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Lockfile-first dependency analysis for npm, pnpm, and Yarn with package-manager CLI fallback and workspace-aware behavior.
    • CLI scan workflow with per-package collectors, merged workspace reports, optional JSON/HTML output, and temp-dir cleanup; warns when node_modules is missing.
  • Enhancements

    • Improved aggregation of licenses, advisories, origin/runtime heuristics, and local metadata.
    • Safer HTML reports via inline CSS/JS sanitization.
    • PNPM guidance: prefer lockfile-derived graphs and verify installed artifacts.
  • Tests

    • New lockfile-first parsing tests covering npm, pnpm, Yarn, workspace and boundary cases.
  • Documentation

    • Updated docs to reflect lockfile-first workflow and PNPM hardening notes.

@coderabbitai

coderabbitai Bot commented Feb 21, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Implements a lockfile-first dependency-graph workflow: adds lockfile parsers for pnpm/npm/Yarn, integrates lockfile-based tree construction into the collection pipeline with package-manager CLI fallback, extends CLI orchestration, sanitizes HTML report embedding, and adds two runtime dependencies.

Changes

Cohort / File(s) Summary
Documentation
README.md
Rewrote workflow to a lockfile-first approach, reorganized pipeline steps, added "Lockfile-first dependency graphing" subsection, updated PNPM/Yarn/npm behavior notes and final output wording.
Manifest / Dependencies
package.json
Added runtime dependencies: @yarnpkg/lockfile (^1.1.0) and yaml (^2.8.2).
Lockfile Graph Parsing
src/runners/lockfileGraph.ts
New module implementing parsers/constructors for pnpm-lock.yaml, package-lock.json/npm-shrinkwrap.json, and yarn.lock/Yarn v2 YAML; exports ResolvedNode, ResolvedTree, LockfileTreeResult, and tryBuildDependencyTreeFromLockfile; includes caching, workspace handling, and graceful undefined on no-valid-lockfile.
npmLs Integration & Types
src/runners/npmLs.ts, src/runners/npmLs.test.ts
runNpmLs now tries tryBuildDependencyTreeFromLockfile first and writes tree on success; added lockfileSearchRoot option; tests added for lockfile-first behavior, boundary checks, and workspace fallback.
CLI Orchestration
src/cli.ts
Added run() orchestration for scan workflow (workspace detection, per-package collectors, merging, temp dir handling, JSON/HTML output and flags), and passes lockfileSearchRoot into per-package collectors.
Report Generation
src/report.ts
Added sanitizers sanitizeInlineStyleTagPayload and sanitizeInlineScriptTagPayload and switched inline CSS/JS embedding to sanitized content while preserving HTML write flow.

Sequence Diagram

sequenceDiagram
    participant CLI as CLI Runner
    participant npmLs as npmLs Handler
    participant LockfileGraph as Lockfile<br/>Graph Builder
    participant Fallback as Package Manager<br/>CLI
    participant Report as Report<br/>Generator

    CLI->>npmLs: runNpmLs(projectPath, tool, lockfileSearchRoot)
    
    rect rgba(100,150,200,0.5)
    Note over npmLs,LockfileGraph: Lockfile-first path
    npmLs->>LockfileGraph: tryBuildDependencyTreeFromLockfile(projectPath, tool, lockfileSearchRoot)
    alt Lockfile parsed successfully
        LockfileGraph-->>npmLs: LockfileTreeResult
        npmLs->>npmLs: write tree file
        npmLs-->>CLI: success
    else No valid lockfile
        LockfileGraph-->>npmLs: undefined
    end
    end

    rect rgba(200,150,100,0.5)
    Note over npmLs,Fallback: CLI fallback path
    npmLs->>Fallback: npm/pnpm/yarn list
    Fallback-->>npmLs: dependency tree
    npmLs->>npmLs: write tree file
    npmLs-->>CLI: success
    end

    CLI->>Report: renderReport(scanResult)
    Report->>Report: sanitize CSS/JS
    Report->>Report: write HTML
    Report-->>CLI: report generated
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰
I nibble locks of YAML, JSON bright,
I hop through graphs by lantern-light.
PNPM, npm, Yarn in tune,
Trees unfurl beneath my moon.
If lockfiles hide, I fall back soon. ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feat: Lockfile First Dependency Graph' accurately and specifically summarizes the main change—introducing a lockfile-first approach to building dependency graphs, which is the core feature across all modified files.
Docstring Coverage ✅ Passed Docstring coverage is 94.34% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/lockfile-first-dependency-graph

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

@coderabbitai

coderabbitai Bot commented Feb 21, 2026 •

Copy link
Copy Markdown
Contributor

Note

Docstrings generation - SUCCESS
Generated docstrings and committed to branch feat/lockfile-first-dependency-graph (commit: c8cfd2c2cda3a935e7756f3e1086e91da066f565)

Docstrings generation was requested by @JosephMaynard.

The following files were modified:

* `src/cli.ts`
* `src/report.ts`
* `src/runners/lockfileGraph.ts`
* `src/runners/npmLs.ts`

These files were ignored:
* `src/runners/npmLs.test.ts`

These file types are not supported:
* `README.md`
* `package.json`

@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 (1)
src/cli.ts (1)

1181-1206: Add a preflight warning when node_modules is missing.

Runners can return partial results without local installs. Consider warning early so users know to install dependencies before scanning.

🛠️ Suggested warning
   const tempDir = path.join(projectPath, ".dependency-radar");
+  const nodeModulesPath = path.join(projectPath, "node_modules");
+  if (!(await pathExists(nodeModulesPath))) {
+    console.warn(
+      "⚠ node_modules not found. Install dependencies for complete results.",
+    );
+  }

Based on learnings "Runners rely on project-local node_modules; ensure the target project is installed before scanning".

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

In `@src/cli.ts` around lines 1181 - 1206, Add a preflight check after detecting
the workspace (after detectWorkspace(projectPath) and yarnPnP check) to detect
if the project-local node_modules directory is missing (e.g.,
fs.existsSync(path.join(projectPath, "node_modules")) === false) and emit a
clear console.warn advising users to run their package manager install
(npm/yarn/pnpm) before scanning; keep execution flowing (do not exit) so
existing behavior around rootPkg, detectPackageManager, and detectScanManager
remains unchanged, and reference workspace.type/yarnPnP in the message when
relevant (e.g., if workspace.type === "yarn" or yarnPnP is true) to provide
contextual guidance.
🤖 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/runners/lockfileGraph.ts`:
- Around line 507-511: The code currently computes projectRel/rootKey and sets
packageKey = rootKey in packages ? rootKey : '' which silently falls back to the
workspace root when a specific workspace package key is missing; change the
logic so that if rootKey is not present in packages and rootKey is not the empty
string (i.e., not the root package) the function returns undefined instead of
using the root entry. Concretely, in the block using
projectRel/rootKey/packageKey/rootEntry (symbols: projectRel, rootKey,
packageKey, rootEntry, packages, lockDir, projectPath) check membership with
`rootKey in packages`; if false and rootKey !== '' return undefined immediately,
otherwise proceed to read packages[rootKey] for the root case.

---

Nitpick comments:
In `@src/cli.ts`:
- Around line 1181-1206: Add a preflight check after detecting the workspace
(after detectWorkspace(projectPath) and yarnPnP check) to detect if the
project-local node_modules directory is missing (e.g.,
fs.existsSync(path.join(projectPath, "node_modules")) === false) and emit a
clear console.warn advising users to run their package manager install
(npm/yarn/pnpm) before scanning; keep execution flowing (do not exit) so
existing behavior around rootPkg, detectPackageManager, and detectScanManager
remains unchanged, and reference workspace.type/yarnPnP in the message when
relevant (e.g., if workspace.type === "yarn" or yarnPnP is true) to provide
contextual guidance.

Comment thread src/runners/lockfileGraph.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

🤖 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 1194-1208: The new warning strings in the node_modules check use
double quotes instead of the project's TypeScript style (single quotes); update
the string literals constructed in the hasProjectNodeModules block—specifically
the workspaceHint and yarnHint assignments and the console.warn message that
references projectPath, workspaceHint, and yarnHint—to use single quotes and
keep existing interpolation/templating intact; ensure you preserve semicolons
and existing spacing/indentation while changing only the quote characters.

Comment thread src/cli.ts
Comment on lines +1194 to +1208
const hasProjectNodeModules = await pathExists(
path.join(projectPath, "node_modules"),
);
if (!hasProjectNodeModules) {
const workspaceHint =
workspace.type === "none"
? "single project"
: `${workspace.type.toUpperCase()} workspace`;
const yarnHint = yarnPnP
? " Yarn Plug'n'Play appears enabled; Dependency Radar currently requires node_modules linker."
: "";
console.warn(
`⚠ node_modules was not found at ${projectPath}. Scan completeness may be reduced for this ${workspaceHint}. Run your package manager install (npm install, pnpm install, or yarn install) before scanning.${yarnHint}`,
);
}

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 | 🟡 Minor

Use single quotes for the new warning literals.

These new string literals use double quotes; the TS style guide here calls for single quotes.

✍️ Suggested diff
-  const hasProjectNodeModules = await pathExists(
-    path.join(projectPath, "node_modules"),
-  );
+  const hasProjectNodeModules = await pathExists(
+    path.join(projectPath, 'node_modules'),
+  );
   if (!hasProjectNodeModules) {
     const workspaceHint =
-      workspace.type === "none"
-        ? "single project"
+      workspace.type === 'none'
+        ? 'single project'
         : `${workspace.type.toUpperCase()} workspace`;
     const yarnHint = yarnPnP
-      ? " Yarn Plug'n'Play appears enabled; Dependency Radar currently requires node_modules linker."
-      : "";
+      ? ' Yarn Plug\'n\'Play appears enabled; Dependency Radar currently requires node_modules linker.'
+      : '';

As per coding guidelines, "Follow TypeScript style: 2-space indentation, single quotes, semicolons, and explicit return/parameter types under strict mode".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const hasProjectNodeModules = await pathExists(
path.join(projectPath, "node_modules"),
);
if (!hasProjectNodeModules) {
const workspaceHint =
workspace.type === "none"
? "single project"
: `${workspace.type.toUpperCase()} workspace`;
const yarnHint = yarnPnP
? " Yarn Plug'n'Play appears enabled; Dependency Radar currently requires node_modules linker."
: "";
console.warn(
`⚠ node_modules was not found at ${projectPath}. Scan completeness may be reduced for this ${workspaceHint}. Run your package manager install (npm install, pnpm install, or yarn install) before scanning.${yarnHint}`,
);
}
const hasProjectNodeModules = await pathExists(
path.join(projectPath, 'node_modules'),
);
if (!hasProjectNodeModules) {
const workspaceHint =
workspace.type === 'none'
? 'single project'
: `${workspace.type.toUpperCase()} workspace`;
const yarnHint = yarnPnP
? ' Yarn Plug\'n\'Play appears enabled; Dependency Radar currently requires node_modules linker.'
: '';
console.warn(
`⚠ node_modules was not found at ${projectPath}. Scan completeness may be reduced for this ${workspaceHint}. Run your package manager install (npm install, pnpm install, or yarn install) before scanning.${yarnHint}`,
);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli.ts` around lines 1194 - 1208, The new warning strings in the
node_modules check use double quotes instead of the project's TypeScript style
(single quotes); update the string literals constructed in the
hasProjectNodeModules block—specifically the workspaceHint and yarnHint
assignments and the console.warn message that references projectPath,
workspaceHint, and yarnHint—to use single quotes and keep existing
interpolation/templating intact; ensure you preserve semicolons and existing
spacing/indentation while changing only the quote characters.

@JosephMaynard
JosephMaynard merged commit 1233dee into master Feb 21, 2026
1 check passed
@JosephMaynard
JosephMaynard deleted the feat/lockfile-first-dependency-graph branch February 21, 2026 22:53
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