dependency-radar 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -437,6 +437,7 @@ function parseNpmTree(projectPath, searchRoot) {
437
437
  * @returns A ResolvedTree mapping root dependency names to resolved nodes, or `undefined` if no valid root entry exists in `packages`
438
438
  */
439
439
  function parseNpmTreeFromPackages(packages, projectPath, lockDir) {
440
+ const installState = createNpmInstallState(projectPath, lockDir);
440
441
  const projectRel = toPosixRelative(lockDir, projectPath);
441
442
  const rootKey = projectRel === '' ? '' : projectRel;
442
443
  if (!(rootKey in packages) && rootKey !== '') {
@@ -454,7 +455,7 @@ function parseNpmTreeFromPackages(packages, projectPath, lockDir) {
454
455
  const childKey = resolveNpmPackagePath(packageKey, depName, packages);
455
456
  if (!childKey)
456
457
  continue;
457
- const childNode = buildNpmNodeFromPackages(childKey, depName, packages, memo, stack);
458
+ const childNode = buildNpmNodeFromPackages(childKey, depName, packages, memo, stack, installState);
458
459
  if (childNode)
459
460
  dependencies[childNode.name] = childNode;
460
461
  }
@@ -470,18 +471,22 @@ function parseNpmTreeFromPackages(packages, projectPath, lockDir) {
470
471
  * @param stack - Recursion stack used to detect and avoid cycles while building the dependency graph.
471
472
  * @returns The constructed `ResolvedNode` for `packageKey`, or `undefined` if the entry is missing, invalid, cyclic, or cannot be resolved.
472
473
  */
473
- function buildNpmNodeFromPackages(packageKey, fallbackName, packages, memo, stack) {
474
+ function buildNpmNodeFromPackages(packageKey, fallbackName, packages, memo, stack, installState) {
474
475
  if (memo.has(packageKey))
475
476
  return memo.get(packageKey);
476
477
  if (stack.has(packageKey))
477
478
  return undefined;
479
+ if (!isNpmPackageInstalled(packageKey, installState)) {
480
+ memo.set(packageKey, undefined);
481
+ return undefined;
482
+ }
478
483
  const entry = packages[packageKey];
479
484
  if (!entry || typeof entry !== 'object')
480
485
  return undefined;
481
486
  if (entry.link === true && typeof entry.resolved === 'string') {
482
487
  const linkedKey = normalizeLockPackageKey(entry.resolved);
483
488
  if (linkedKey && linkedKey in packages) {
484
- const linkedNode = buildNpmNodeFromPackages(linkedKey, fallbackName, packages, memo, stack);
489
+ const linkedNode = buildNpmNodeFromPackages(linkedKey, fallbackName, packages, memo, stack, installState);
485
490
  memo.set(packageKey, linkedNode);
486
491
  return linkedNode;
487
492
  }
@@ -506,7 +511,7 @@ function buildNpmNodeFromPackages(packageKey, fallbackName, packages, memo, stac
506
511
  const childKey = resolveNpmPackagePath(packageKey, depName, packages);
507
512
  if (!childKey)
508
513
  continue;
509
- const childNode = buildNpmNodeFromPackages(childKey, depName, packages, memo, stack);
514
+ const childNode = buildNpmNodeFromPackages(childKey, depName, packages, memo, stack, installState);
510
515
  if (!childNode)
511
516
  continue;
512
517
  out.dependencies[childNode.name] = childNode;
@@ -1037,6 +1042,21 @@ function createPnpmInstallState(projectPath) {
1037
1042
  installedCache: new Map()
1038
1043
  };
1039
1044
  }
1045
+ /**
1046
+ * Create npm installation state used to filter lockfile package entries to actually installed paths.
1047
+ *
1048
+ * @param projectPath - Filesystem path inside the project to start discovery from
1049
+ * @param lockDir - Directory containing the npm lockfile
1050
+ * @returns Installation state with detected node_modules roots and per-package-key cache
1051
+ */
1052
+ function createNpmInstallState(projectPath, lockDir) {
1053
+ const nodeModulesRoots = findNodeModulesRoots(projectPath);
1054
+ return {
1055
+ enabled: nodeModulesRoots.length > 0,
1056
+ lockDir,
1057
+ installedByKeyCache: new Map()
1058
+ };
1059
+ }
1040
1060
  /**
1041
1061
  * Discover node_modules directories by walking upward from a starting path.
1042
1062
  *
@@ -1058,6 +1078,27 @@ function findNodeModulesRoots(startPath) {
1058
1078
  }
1059
1079
  return roots;
1060
1080
  }
1081
+ /**
1082
+ * Determine whether a package-lock `packages` entry key points to an installed package path.
1083
+ *
1084
+ * @param packageKey - Key from package-lock `packages` map
1085
+ * @param installState - npm installation state with lockDir and cache
1086
+ * @returns `true` when the key should be considered installed, `false` otherwise
1087
+ */
1088
+ function isNpmPackageInstalled(packageKey, installState) {
1089
+ if (!installState.enabled)
1090
+ return true;
1091
+ const normalizedKey = normalizeLockPackageKey(packageKey);
1092
+ if (!normalizedKey)
1093
+ return true;
1094
+ const cached = installState.installedByKeyCache.get(normalizedKey);
1095
+ if (cached !== undefined)
1096
+ return cached;
1097
+ const candidate = path_1.default.join(installState.lockDir, ...normalizedKey.split('/'));
1098
+ const installed = safePathExists(candidate);
1099
+ installState.installedByKeyCache.set(normalizedKey, installed);
1100
+ return installed;
1101
+ }
1061
1102
  /**
1062
1103
  * Read the names of entries in a directory, returning an empty array if the directory cannot be read.
1063
1104
  *
package/dist/utils.js CHANGED
@@ -58,11 +58,46 @@ function getDependencyRadarVersion() {
58
58
  async function ensureDir(dir) {
59
59
  await promises_1.default.mkdir(dir, { recursive: true });
60
60
  }
61
+ /**
62
+ * Write JSON data to a file, creating parent directories as needed.
63
+ *
64
+ * Attempts to write a pretty-printed JSON representation of `data` to `filePath`. If pretty-printing fails due to an "Invalid string length" RangeError, falls back to a compact JSON representation.
65
+ *
66
+ * @param filePath - The path of the file to write
67
+ * @param data - The value to serialize to JSON (typically JSON-serializable)
68
+ * @throws Rethrows errors from JSON serialization (except handled "Invalid string length" for pretty-printing) and filesystem write operations
69
+ */
61
70
  async function writeJsonFile(filePath, data) {
62
71
  await ensureDir(path_1.default.dirname(filePath));
63
- const content = JSON.stringify(data, null, 2);
72
+ let content;
73
+ try {
74
+ content = JSON.stringify(data, null, 2);
75
+ }
76
+ catch (err) {
77
+ // Large lockfile-derived trees can overflow string length when pretty-printing.
78
+ if (!isInvalidStringLengthError(err))
79
+ throw err;
80
+ content = JSON.stringify(data);
81
+ }
64
82
  await promises_1.default.writeFile(filePath, content, 'utf8');
65
83
  }
84
+ /**
85
+ * Determines whether a value is a RangeError whose message indicates an "Invalid string length".
86
+ *
87
+ * @param error - The value to inspect
88
+ * @returns `true` if `error` is a `RangeError` with a message matching "Invalid string length" (case-insensitive), `false` otherwise.
89
+ */
90
+ function isInvalidStringLengthError(error) {
91
+ if (!(error instanceof RangeError))
92
+ return false;
93
+ return /Invalid string length/i.test(error.message || '');
94
+ }
95
+ /**
96
+ * Check whether a filesystem path exists and is accessible.
97
+ *
98
+ * @param target - The path to check
99
+ * @returns `true` if the path exists and is accessible, `false` otherwise
100
+ */
66
101
  async function pathExists(target) {
67
102
  try {
68
103
  await promises_1.default.access(target);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dependency-radar",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Local-first dependency analysis tool that generates a single HTML report showing risk, size, usage, and structure of your project's dependencies.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {