easy-dep-graph 1.1.0 → 1.1.2

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.
Files changed (2) hide show
  1. package/bin/index.js +109 -82
  2. package/package.json +3 -1
package/bin/index.js CHANGED
@@ -5,6 +5,7 @@ import fastify from "fastify";
5
5
  import open from "open";
6
6
  import fs from "node:fs";
7
7
  import path from "node:path";
8
+ import semver from "semver";
8
9
  let flatDeps;
9
10
  void (async function main() {
10
11
  // Check if peer dependencies mode
@@ -55,11 +56,24 @@ void (async function main() {
55
56
  name: packageInfo.name,
56
57
  applyForceLayout: shouldApplyForceLayout,
57
58
  nodes: JSON.stringify(nodeEntries.map((dep, index) => {
59
+ let x, y;
60
+ if (shouldApplyForceLayout) {
61
+ // Random initial position for force layout
62
+ x = Math.random() * 100 - 50;
63
+ y = Math.random() * 100 - 50;
64
+ }
65
+ else {
66
+ // Arrange in a circle with equal spacing
67
+ const radius = Math.max(50, nodeCount * 3);
68
+ const angle = (index / nodeCount) * 2 * Math.PI;
69
+ x = Math.cos(angle) * radius;
70
+ y = Math.sin(angle) * radius;
71
+ }
58
72
  return {
59
73
  key: dep[0],
60
74
  label: `${dep[0]} V${dep[1].version}`,
61
- x: Math.random() * 100 - 50,
62
- y: Math.random() * 100 - 50,
75
+ x,
76
+ y,
63
77
  size: 10,
64
78
  color: dep[1].isRoot ? "#22c55e" : "#3b82f6",
65
79
  };
@@ -117,27 +131,24 @@ async function runPeerDependenciesMode(port, shouldOpenBrowser) {
117
131
  ...packageJson.dependencies,
118
132
  ...packageJson.devDependencies,
119
133
  };
120
- // Run npm list to get all dependencies
121
- const result = shell.exec("npm list --json --all", {
122
- windowsHide: true,
123
- silent: true,
124
- });
125
- const packageInfo = JSON.parse(result.stdout);
126
- // Collect all package names from the dependency tree
127
- const allPackageNames = new Set();
128
- collectAllPackageNames(packageInfo.dependencies, allPackageNames);
129
- // Collect all peer dependencies by reading package.json files
130
- const peerDeps = collectPeerDependenciesFromNodeModules(allPackageNames);
134
+ // Collect all peer dependencies recursively starting from installed packages
135
+ const peerDeps = collectPeerDependenciesRecursively(installedPackages);
131
136
  // Resolve versions
132
137
  const peerDepsList = Object.entries(peerDeps).map(([name, info]) => {
133
138
  const resolvedVersion = resolveVersion(info.versions);
134
- const isInstalled = installedPackages[name] !== undefined;
139
+ const isInPackageJson = installedPackages[name] !== undefined;
140
+ // Check if installed in node_modules (even if not in package.json)
141
+ const nodeModulesPath = path.join(process.cwd(), "node_modules");
142
+ const pkgPath = path.join(nodeModulesPath, name, "package.json");
143
+ const existsInNodeModules = fs.existsSync(pkgPath);
144
+ const isInstalledByDependency = existsInNodeModules && !isInPackageJson;
135
145
  return {
136
146
  name,
137
147
  version: resolvedVersion.version,
138
148
  isConflict: resolvedVersion.isConflict,
139
149
  requiredBy: info.requiredBy,
140
- isInstalled,
150
+ isInstalled: isInPackageJson,
151
+ isInstalledByDependency,
141
152
  };
142
153
  });
143
154
  // Sort by name
@@ -184,26 +195,25 @@ async function runPeerDependenciesMode(port, shouldOpenBrowser) {
184
195
  open(address);
185
196
  });
186
197
  }
187
- function collectAllPackageNames(deps, packageNames) {
188
- if (deps == null)
189
- return;
190
- for (const [depName, depInfo] of Object.entries(deps)) {
191
- packageNames.add(depName);
192
- if (depInfo.dependencies) {
193
- collectAllPackageNames(depInfo.dependencies, packageNames);
194
- }
195
- }
196
- }
197
- function collectPeerDependenciesFromNodeModules(packageNames) {
198
+ function collectPeerDependenciesRecursively(initialPackages) {
198
199
  const peerDeps = {};
199
200
  const nodeModulesPath = path.join(process.cwd(), "node_modules");
200
- for (const packageName of packageNames) {
201
+ const visited = new Set();
202
+ const queue = Object.keys(initialPackages);
203
+ while (queue.length > 0) {
204
+ const packageName = queue.shift();
205
+ // Skip if already processed
206
+ if (visited.has(packageName)) {
207
+ continue;
208
+ }
209
+ visited.add(packageName);
201
210
  try {
202
211
  const pkgJsonPath = path.join(nodeModulesPath, packageName, "package.json");
203
212
  if (!fs.existsSync(pkgJsonPath)) {
204
213
  continue;
205
214
  }
206
215
  const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
216
+ // Collect peer dependencies
207
217
  if (pkgJson.peerDependencies) {
208
218
  for (const [peerDepName, peerDepVersion] of Object.entries(pkgJson.peerDependencies)) {
209
219
  if (!peerDeps[peerDepName]) {
@@ -219,40 +229,25 @@ function collectPeerDependenciesFromNodeModules(packageNames) {
219
229
  if (!peerDeps[peerDepName].requiredBy.includes(packageName)) {
220
230
  peerDeps[peerDepName].requiredBy.push(packageName);
221
231
  }
232
+ // Add peer dependency to queue for processing
233
+ if (!visited.has(peerDepName)) {
234
+ queue.push(peerDepName);
235
+ }
222
236
  }
223
237
  }
224
- }
225
- catch (error) {
226
- // Skip packages that can't be read
227
- console.warn(`Warning: Could not read package.json for ${packageName}`);
228
- }
229
- }
230
- return peerDeps;
231
- }
232
- function collectPeerDependencies(deps, peerDeps = {}, parentName) {
233
- if (deps == null)
234
- return peerDeps;
235
- for (const [depName, depInfo] of Object.entries(deps)) {
236
- // Check if this dependency has peer dependencies
237
- if (depInfo.peerDependencies) {
238
- for (const [peerDepName, peerDepVersion] of Object.entries(depInfo.peerDependencies)) {
239
- if (!peerDeps[peerDepName]) {
240
- peerDeps[peerDepName] = {
241
- versions: [],
242
- requiredBy: [],
243
- };
244
- }
245
- if (!peerDeps[peerDepName].versions.includes(peerDepVersion)) {
246
- peerDeps[peerDepName].versions.push(peerDepVersion);
247
- }
248
- if (!peerDeps[peerDepName].requiredBy.includes(depName)) {
249
- peerDeps[peerDepName].requiredBy.push(depName);
238
+ // Collect dependencies and devDependencies to continue traversal
239
+ const allDeps = {
240
+ ...pkgJson.dependencies,
241
+ ...pkgJson.devDependencies,
242
+ };
243
+ for (const depName of Object.keys(allDeps)) {
244
+ if (!visited.has(depName)) {
245
+ queue.push(depName);
250
246
  }
251
247
  }
252
248
  }
253
- // Recursively check nested dependencies
254
- if (depInfo.dependencies) {
255
- collectPeerDependencies(depInfo.dependencies, peerDeps, depName);
249
+ catch (error) {
250
+ console.warn(`Warning: Could not read package.json for ${packageName}`);
256
251
  }
257
252
  }
258
253
  return peerDeps;
@@ -264,35 +259,42 @@ function resolveVersion(versions) {
264
259
  if (versions.length === 1) {
265
260
  return { version: versions[0], isConflict: false };
266
261
  }
267
- // Try to find a compatible version
268
- // For simplicity, if all versions are the same, use that
262
+ // Get unique versions
269
263
  const uniqueVersions = [...new Set(versions)];
270
264
  if (uniqueVersions.length === 1) {
271
265
  return { version: uniqueVersions[0], isConflict: false };
272
266
  }
273
- // Check if all are compatible (simple heuristic: same major version for ^)
274
- const allCompatible = uniqueVersions.every((v) => {
275
- const baseVersion = uniqueVersions[0];
276
- // If both start with ^, check major version
277
- if (v.startsWith("^") && baseVersion.startsWith("^")) {
278
- const vMajor = v.substring(1).split(".")[0];
279
- const baseMajor = baseVersion.substring(1).split(".")[0];
280
- return vMajor === baseMajor;
267
+ // Use semver to find intersecting range
268
+ try {
269
+ // Try to find a version range that satisfies all requirements
270
+ let intersection = uniqueVersions[0];
271
+ for (let i = 1; i < uniqueVersions.length; i++) {
272
+ const range1 = intersection;
273
+ const range2 = uniqueVersions[i];
274
+ // Check if ranges intersect
275
+ if (semver.intersects(range1, range2)) {
276
+ // Find the more restrictive range
277
+ // Use the one with higher minimum version
278
+ const min1 = semver.minVersion(range1);
279
+ const min2 = semver.minVersion(range2);
280
+ if (min1 && min2) {
281
+ intersection = semver.gt(min1, min2) ? range1 : range2;
282
+ }
283
+ else {
284
+ intersection = range1;
285
+ }
286
+ }
287
+ else {
288
+ // Ranges don't intersect - conflict detected
289
+ return { version: uniqueVersions.join(" | "), isConflict: true };
290
+ }
281
291
  }
282
- // If both are exact versions, they must match
283
- return v === baseVersion;
284
- });
285
- if (allCompatible && uniqueVersions[0].startsWith("^")) {
286
- // Return the highest version requirement
287
- const sorted = [...uniqueVersions].sort((a, b) => {
288
- const aVer = a.substring(1);
289
- const bVer = b.substring(1);
290
- return bVer.localeCompare(aVer, undefined, { numeric: true });
291
- });
292
- return { version: sorted[0], isConflict: false };
292
+ return { version: intersection, isConflict: false };
293
+ }
294
+ catch (error) {
295
+ // If semver parsing fails, fall back to showing all versions
296
+ return { version: uniqueVersions.join(" | "), isConflict: true };
293
297
  }
294
- // Conflict detected
295
- return { version: uniqueVersions.join(" | "), isConflict: true };
296
298
  }
297
299
  function getPeerDepsTemplate() {
298
300
  return `
@@ -454,11 +456,28 @@ function getPeerDepsTemplate() {
454
456
  font-weight: 600;
455
457
  }
456
458
 
459
+ .installed-by-dep-badge {
460
+ display: flex;
461
+ align-items: center;
462
+ gap: 0.5rem;
463
+ padding: 0.5rem 1rem;
464
+ background: #e0e7ff;
465
+ color: #3730a3;
466
+ border-radius: 6px;
467
+ font-size: 0.875rem;
468
+ font-weight: 600;
469
+ }
470
+
457
471
  .checkmark {
458
472
  color: #10b981;
459
473
  font-size: 1.25rem;
460
474
  }
461
475
 
476
+ .info-icon {
477
+ color: #4f46e5;
478
+ font-size: 1.25rem;
479
+ }
480
+
462
481
  .message {
463
482
  margin-top: 0.5rem;
464
483
  padding: 0.5rem;
@@ -515,11 +534,19 @@ function getPeerDepsTemplate() {
515
534
  Installed
516
535
  </div>
517
536
  {{/isInstalled}}
537
+ {{#isInstalledByDependency}}
538
+ <div class="installed-by-dep-badge">
539
+ <span class="info-icon">ℹ</span>
540
+ Install by dependency
541
+ </div>
542
+ {{/isInstalledByDependency}}
518
543
  {{^isInstalled}}
544
+ {{^isInstalledByDependency}}
519
545
  <button class="install-btn" onclick="installPackage('{{name}}', '{{version}}', this)">
520
546
  npm install
521
547
  </button>
522
548
  <div class="message" id="msg-{{name}}"></div>
549
+ {{/isInstalledByDependency}}
523
550
  {{/isInstalled}}
524
551
  </div>
525
552
  </div>
@@ -595,8 +622,8 @@ function getTemplate() {
595
622
  var nodes = graph.nodes();
596
623
  var edges = graph.edges();
597
624
 
598
- // Physics constants
599
- var repulsionStrength = 100;
625
+ // Physics constants - lower repulsion for better spacing
626
+ var repulsionStrength = 50;
600
627
  var attractionStrength = 0.01;
601
628
  var damping = 0.5;
602
629
 
@@ -678,7 +705,7 @@ function getTemplate() {
678
705
  });
679
706
 
680
707
  edges.forEach(function(edge, index) {
681
- graph.addEdge(edge.source, edge.target, {
708
+ graph.mergeEdge(edge.source, edge.target, {
682
709
  size: 2,
683
710
  color: '#94a3b8'
684
711
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "easy-dep-graph",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Easily see the dependency graph of your npm project",
5
5
  "homepage": "https://github.com/danisss9/easy-dep-graph#readme",
6
6
  "repository": {
@@ -54,11 +54,13 @@
54
54
  "fastify": "^5.7.4",
55
55
  "mustache": "^4.2.0",
56
56
  "open": "^11.0.0",
57
+ "semver": "^7.7.3",
57
58
  "shelljs": "^0.10.0"
58
59
  },
59
60
  "devDependencies": {
60
61
  "@types/mustache": "^4.2.6",
61
62
  "@types/node": "^25.2.1",
63
+ "@types/semver": "^7.7.1",
62
64
  "@types/shelljs": "^0.10.0",
63
65
  "typescript": "^5.9.3"
64
66
  }