easy-dep-graph 1.1.1 → 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 +297 -286
  2. package/package.json +3 -1
package/bin/index.js CHANGED
@@ -5,313 +5,299 @@ 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
- // Check if peer dependencies mode
11
- const peerDependenciesMode =
12
- process.argv.findIndex((a) => a.toLowerCase() === "--peer-dependencies") !==
13
- -1;
14
- // List of packages user wants to see the dependencies
15
- const packagesArgIndex = process.argv.findIndex(
16
- (a) => a.toLowerCase() === "--packages",
17
- );
18
- const packagesFilter =
19
- packagesArgIndex !== -1 ? process.argv[packagesArgIndex + 1] : undefined;
20
- // Only get the dependents of a specific package
21
- const packageArgIndex = process.argv.findIndex(
22
- (a) => a.toLowerCase() === "--package-dependents",
23
- );
24
- const packageName =
25
- packageArgIndex !== -1 ? process.argv[packageArgIndex + 1] : undefined;
26
- // Get port number
27
- const portArgIndex = process.argv.findIndex(
28
- (a) => a.toLowerCase() === "--port",
29
- );
30
- const port = portArgIndex !== -1 ? +process.argv[portArgIndex + 1] : 8080;
31
- // Get if should not open browser
32
- const shouldOpenBrowser =
33
- process.argv.findIndex((a) => a.toLowerCase() === "--no-open") === -1;
34
- // Get if should not apply force layout
35
- const shouldApplyForceLayout =
36
- process.argv.findIndex((a) => a.toLowerCase() === "--no-force-layout") ===
37
- -1;
38
- if (peerDependenciesMode) {
39
- await runPeerDependenciesMode(port, shouldOpenBrowser);
40
- return;
41
- }
42
- // Run npm list command
43
- const result = shell.exec(`npm list --json ${packageName ?? "--all"}`, {
44
- windowsHide: true,
45
- silent: true,
46
- });
47
- // Parse the result to a JSON object
48
- const packageInfo = JSON.parse(result.stdout);
49
- console.log(`Generating dependency graph for: "${packageInfo.name}"...`);
50
- // Filter dependencies if needed
51
- if (packagesFilter != null) {
52
- const packagesFilterList = packagesFilter.split(",").map((d) => d.trim());
53
- for (const key of Object.keys(packageInfo.dependencies)) {
54
- if (!packagesFilterList.includes(key)) {
55
- delete packageInfo.dependencies[key];
56
- }
11
+ // Check if peer dependencies mode
12
+ const peerDependenciesMode = process.argv.findIndex((a) => a.toLowerCase() === "--peer-dependencies") !==
13
+ -1;
14
+ // List of packages user wants to see the dependencies
15
+ const packagesArgIndex = process.argv.findIndex((a) => a.toLowerCase() === "--packages");
16
+ const packagesFilter = packagesArgIndex !== -1 ? process.argv[packagesArgIndex + 1] : undefined;
17
+ // Only get the dependents of a specific package
18
+ const packageArgIndex = process.argv.findIndex((a) => a.toLowerCase() === "--package-dependents");
19
+ const packageName = packageArgIndex !== -1 ? process.argv[packageArgIndex + 1] : undefined;
20
+ // Get port number
21
+ const portArgIndex = process.argv.findIndex((a) => a.toLowerCase() === "--port");
22
+ const port = portArgIndex !== -1 ? +process.argv[portArgIndex + 1] : 8080;
23
+ // Get if should not open browser
24
+ const shouldOpenBrowser = process.argv.findIndex((a) => a.toLowerCase() === "--no-open") === -1;
25
+ // Get if should not apply force layout
26
+ const shouldApplyForceLayout = process.argv.findIndex((a) => a.toLowerCase() === "--no-force-layout") ===
27
+ -1;
28
+ if (peerDependenciesMode) {
29
+ await runPeerDependenciesMode(port, shouldOpenBrowser);
30
+ return;
57
31
  }
58
- }
59
- // Flat the dependencies from npm list
60
- flatDeps = {};
61
- flatDepsRecursive(packageInfo.dependencies);
62
- // Turn deps into nodes and edges
63
- const nodeEntries = Object.entries(flatDeps);
64
- const nodeCount = nodeEntries.length;
65
- const data = {
66
- name: packageInfo.name,
67
- applyForceLayout: shouldApplyForceLayout,
68
- nodes: JSON.stringify(
69
- nodeEntries.map((dep, index) => {
70
- return {
71
- key: dep[0],
72
- label: `${dep[0]} V${dep[1].version}`,
73
- x: Math.random() * 100 - 50,
74
- y: Math.random() * 100 - 50,
75
- size: 10,
76
- color: dep[1].isRoot ? "#22c55e" : "#3b82f6",
77
- };
78
- }),
79
- ),
80
- edges: JSON.stringify(
81
- Object.entries(flatDeps)
82
- .filter((dep) => !!dep[1].dependencies.length)
83
- .flatMap((dep) =>
84
- dep[1].dependencies.map((d) => ({
32
+ // Run npm list command
33
+ const result = shell.exec(`npm list --json ${packageName ?? "--all"}`, {
34
+ windowsHide: true,
35
+ silent: true,
36
+ });
37
+ // Parse the result to a JSON object
38
+ const packageInfo = JSON.parse(result.stdout);
39
+ console.log(`Generating dependency graph for: "${packageInfo.name}"...`);
40
+ // Filter dependencies if needed
41
+ if (packagesFilter != null) {
42
+ const packagesFilterList = packagesFilter.split(",").map((d) => d.trim());
43
+ for (const key of Object.keys(packageInfo.dependencies)) {
44
+ if (!packagesFilterList.includes(key)) {
45
+ delete packageInfo.dependencies[key];
46
+ }
47
+ }
48
+ }
49
+ // Flat the dependencies from npm list
50
+ flatDeps = {};
51
+ flatDepsRecursive(packageInfo.dependencies);
52
+ // Turn deps into nodes and edges
53
+ const nodeEntries = Object.entries(flatDeps);
54
+ const nodeCount = nodeEntries.length;
55
+ const data = {
56
+ name: packageInfo.name,
57
+ applyForceLayout: shouldApplyForceLayout,
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
+ }
72
+ return {
73
+ key: dep[0],
74
+ label: `${dep[0]} V${dep[1].version}`,
75
+ x,
76
+ y,
77
+ size: 10,
78
+ color: dep[1].isRoot ? "#22c55e" : "#3b82f6",
79
+ };
80
+ })),
81
+ edges: JSON.stringify(Object.entries(flatDeps)
82
+ .filter((dep) => !!dep[1].dependencies.length)
83
+ .flatMap((dep) => dep[1].dependencies.map((d) => ({
85
84
  source: dep[0],
86
85
  target: d,
87
- })),
88
- ),
89
- ),
90
- };
91
- // Render the UI using mustache
92
- const html = mustache.render(getTemplate(), data);
93
- // Initialize server to serve graph
94
- const app = fastify({
95
- logger: false,
96
- });
97
- app.get("/", (_req, resp) => resp.type("text/html").send(html));
98
- // Run the server
99
- app.listen({ port }, (err, address) => {
100
- if (err) throw err;
101
- console.log(`Done! Visit "${address}" to see dependecy graph.`);
102
- if (shouldOpenBrowser) open(address);
103
- });
86
+ })))),
87
+ };
88
+ // Render the UI using mustache
89
+ const html = mustache.render(getTemplate(), data);
90
+ // Initialize server to serve graph
91
+ const app = fastify({
92
+ logger: false,
93
+ });
94
+ app.get("/", (_req, resp) => resp.type("text/html").send(html));
95
+ // Run the server
96
+ app.listen({ port }, (err, address) => {
97
+ if (err)
98
+ throw err;
99
+ console.log(`Done! Visit "${address}" to see dependecy graph.`);
100
+ if (shouldOpenBrowser)
101
+ open(address);
102
+ });
104
103
  })();
105
104
  function flatDepsRecursive(deps, parentDepName) {
106
- if (deps == null) return;
107
- for (const dep of Object.entries(deps)) {
108
- const depName = dep[0];
109
- const depInfo = dep[1];
110
- const isRoot = parentDepName == null;
111
- if (!isRoot) {
112
- const parentDep = flatDeps[parentDepName];
113
- parentDep.dependencies.push(depName);
114
- }
115
- if (flatDeps[depName] == null) {
116
- flatDeps[depName] = {
117
- version: depInfo.version,
118
- isRoot,
119
- dependencies: [],
120
- };
105
+ if (deps == null)
106
+ return;
107
+ for (const dep of Object.entries(deps)) {
108
+ const depName = dep[0];
109
+ const depInfo = dep[1];
110
+ const isRoot = parentDepName == null;
111
+ if (!isRoot) {
112
+ const parentDep = flatDeps[parentDepName];
113
+ parentDep.dependencies.push(depName);
114
+ }
115
+ if (flatDeps[depName] == null) {
116
+ flatDeps[depName] = {
117
+ version: depInfo.version,
118
+ isRoot,
119
+ dependencies: [],
120
+ };
121
+ }
122
+ flatDepsRecursive(depInfo.dependencies, depName);
121
123
  }
122
- flatDepsRecursive(depInfo.dependencies, depName);
123
- }
124
124
  }
125
125
  async function runPeerDependenciesMode(port, shouldOpenBrowser) {
126
- console.log("Analyzing peer dependencies...");
127
- // Get the current project's package.json
128
- const packageJsonPath = path.join(process.cwd(), "package.json");
129
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
130
- const installedPackages = {
131
- ...packageJson.dependencies,
132
- ...packageJson.devDependencies,
133
- };
134
- // Run npm list to get all dependencies
135
- const result = shell.exec("npm list --json --all", {
136
- windowsHide: true,
137
- silent: true,
138
- });
139
- const packageInfo = JSON.parse(result.stdout);
140
- // Collect all package names from the dependency tree
141
- const allPackageNames = new Set();
142
- collectAllPackageNames(packageInfo.dependencies, allPackageNames);
143
- // Collect all peer dependencies by reading package.json files
144
- const peerDeps = collectPeerDependenciesFromNodeModules(allPackageNames);
145
- // Resolve versions
146
- const peerDepsList = Object.entries(peerDeps).map(([name, info]) => {
147
- const resolvedVersion = resolveVersion(info.versions);
148
- const isInstalled = installedPackages[name] !== undefined;
149
- return {
150
- name,
151
- version: resolvedVersion.version,
152
- isConflict: resolvedVersion.isConflict,
153
- requiredBy: info.requiredBy,
154
- isInstalled,
126
+ console.log("Analyzing peer dependencies...");
127
+ // Get the current project's package.json
128
+ const packageJsonPath = path.join(process.cwd(), "package.json");
129
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
130
+ const installedPackages = {
131
+ ...packageJson.dependencies,
132
+ ...packageJson.devDependencies,
155
133
  };
156
- });
157
- // Sort by name
158
- peerDepsList.sort((a, b) => a.name.localeCompare(b.name));
159
- const data = {
160
- projectName: packageJson.name,
161
- peerDeps: peerDepsList,
162
- };
163
- // Render the UI using mustache
164
- const html = mustache.render(getPeerDepsTemplate(), data);
165
- // Initialize server to serve graph
166
- const app = fastify({
167
- logger: false,
168
- });
169
- app.get("/", (_req, resp) => resp.type("text/html").send(html));
170
- app.post("/install", async (req, resp) => {
171
- const { package: pkg, version } = req.body;
172
- console.log(`Installing ${pkg}@${version}...`);
173
- const installResult = shell.exec(`npm install ${pkg}@${version}`, {
174
- windowsHide: true,
175
- silent: true,
134
+ // Collect all peer dependencies recursively starting from installed packages
135
+ const peerDeps = collectPeerDependenciesRecursively(installedPackages);
136
+ // Resolve versions
137
+ const peerDepsList = Object.entries(peerDeps).map(([name, info]) => {
138
+ const resolvedVersion = resolveVersion(info.versions);
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;
145
+ return {
146
+ name,
147
+ version: resolvedVersion.version,
148
+ isConflict: resolvedVersion.isConflict,
149
+ requiredBy: info.requiredBy,
150
+ isInstalled: isInPackageJson,
151
+ isInstalledByDependency,
152
+ };
176
153
  });
177
- if (installResult.code === 0) {
178
- console.log(`Successfully installed ${pkg}@${version}`);
179
- return {
180
- success: true,
181
- message: `Successfully installed ${pkg}@${version}`,
182
- };
183
- } else {
184
- console.error(`Failed to install ${pkg}@${version}`);
185
- return {
186
- success: false,
187
- message: installResult.stderr || "Installation failed",
188
- };
189
- }
190
- });
191
- // Run the server
192
- app.listen({ port }, (err, address) => {
193
- if (err) throw err;
194
- console.log(`Done! Visit "${address}" to see peer dependencies.`);
195
- if (shouldOpenBrowser) open(address);
196
- });
197
- }
198
- function collectAllPackageNames(deps, packageNames) {
199
- if (deps == null) return;
200
- for (const [depName, depInfo] of Object.entries(deps)) {
201
- packageNames.add(depName);
202
- if (depInfo.dependencies) {
203
- collectAllPackageNames(depInfo.dependencies, packageNames);
204
- }
205
- }
206
- }
207
- function collectPeerDependenciesFromNodeModules(packageNames) {
208
- const peerDeps = {};
209
- const nodeModulesPath = path.join(process.cwd(), "node_modules");
210
- for (const packageName of packageNames) {
211
- try {
212
- const pkgJsonPath = path.join(
213
- nodeModulesPath,
214
- packageName,
215
- "package.json",
216
- );
217
- if (!fs.existsSync(pkgJsonPath)) {
218
- continue;
219
- }
220
- const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
221
- if (pkgJson.peerDependencies) {
222
- for (const [peerDepName, peerDepVersion] of Object.entries(
223
- pkgJson.peerDependencies,
224
- )) {
225
- if (!peerDeps[peerDepName]) {
226
- peerDeps[peerDepName] = {
227
- versions: [],
228
- requiredBy: [],
154
+ // Sort by name
155
+ peerDepsList.sort((a, b) => a.name.localeCompare(b.name));
156
+ const data = {
157
+ projectName: packageJson.name,
158
+ peerDeps: peerDepsList,
159
+ };
160
+ // Render the UI using mustache
161
+ const html = mustache.render(getPeerDepsTemplate(), data);
162
+ // Initialize server to serve graph
163
+ const app = fastify({
164
+ logger: false,
165
+ });
166
+ app.get("/", (_req, resp) => resp.type("text/html").send(html));
167
+ app.post("/install", async (req, resp) => {
168
+ const { package: pkg, version } = req.body;
169
+ console.log(`Installing ${pkg}@${version}...`);
170
+ const installResult = shell.exec(`npm install ${pkg}@${version}`, {
171
+ windowsHide: true,
172
+ silent: true,
173
+ });
174
+ if (installResult.code === 0) {
175
+ console.log(`Successfully installed ${pkg}@${version}`);
176
+ return {
177
+ success: true,
178
+ message: `Successfully installed ${pkg}@${version}`,
229
179
  };
230
- }
231
- const versionStr = peerDepVersion;
232
- if (!peerDeps[peerDepName].versions.includes(versionStr)) {
233
- peerDeps[peerDepName].versions.push(versionStr);
234
- }
235
- if (!peerDeps[peerDepName].requiredBy.includes(packageName)) {
236
- peerDeps[peerDepName].requiredBy.push(packageName);
237
- }
238
180
  }
239
- }
240
- } catch (error) {
241
- // Skip packages that can't be read
242
- console.warn(`Warning: Could not read package.json for ${packageName}`);
243
- }
244
- }
245
- return peerDeps;
181
+ else {
182
+ console.error(`Failed to install ${pkg}@${version}`);
183
+ return {
184
+ success: false,
185
+ message: installResult.stderr || "Installation failed",
186
+ };
187
+ }
188
+ });
189
+ // Run the server
190
+ app.listen({ port }, (err, address) => {
191
+ if (err)
192
+ throw err;
193
+ console.log(`Done! Visit "${address}" to see peer dependencies.`);
194
+ if (shouldOpenBrowser)
195
+ open(address);
196
+ });
246
197
  }
247
- function collectPeerDependencies(deps, peerDeps = {}, parentName) {
248
- if (deps == null) return peerDeps;
249
- for (const [depName, depInfo] of Object.entries(deps)) {
250
- // Check if this dependency has peer dependencies
251
- if (depInfo.peerDependencies) {
252
- for (const [peerDepName, peerDepVersion] of Object.entries(
253
- depInfo.peerDependencies,
254
- )) {
255
- if (!peerDeps[peerDepName]) {
256
- peerDeps[peerDepName] = {
257
- versions: [],
258
- requiredBy: [],
259
- };
198
+ function collectPeerDependenciesRecursively(initialPackages) {
199
+ const peerDeps = {};
200
+ const nodeModulesPath = path.join(process.cwd(), "node_modules");
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;
260
208
  }
261
- if (!peerDeps[peerDepName].versions.includes(peerDepVersion)) {
262
- peerDeps[peerDepName].versions.push(peerDepVersion);
209
+ visited.add(packageName);
210
+ try {
211
+ const pkgJsonPath = path.join(nodeModulesPath, packageName, "package.json");
212
+ if (!fs.existsSync(pkgJsonPath)) {
213
+ continue;
214
+ }
215
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
216
+ // Collect peer dependencies
217
+ if (pkgJson.peerDependencies) {
218
+ for (const [peerDepName, peerDepVersion] of Object.entries(pkgJson.peerDependencies)) {
219
+ if (!peerDeps[peerDepName]) {
220
+ peerDeps[peerDepName] = {
221
+ versions: [],
222
+ requiredBy: [],
223
+ };
224
+ }
225
+ const versionStr = peerDepVersion;
226
+ if (!peerDeps[peerDepName].versions.includes(versionStr)) {
227
+ peerDeps[peerDepName].versions.push(versionStr);
228
+ }
229
+ if (!peerDeps[peerDepName].requiredBy.includes(packageName)) {
230
+ peerDeps[peerDepName].requiredBy.push(packageName);
231
+ }
232
+ // Add peer dependency to queue for processing
233
+ if (!visited.has(peerDepName)) {
234
+ queue.push(peerDepName);
235
+ }
236
+ }
237
+ }
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);
246
+ }
247
+ }
263
248
  }
264
- if (!peerDeps[peerDepName].requiredBy.includes(depName)) {
265
- peerDeps[peerDepName].requiredBy.push(depName);
249
+ catch (error) {
250
+ console.warn(`Warning: Could not read package.json for ${packageName}`);
266
251
  }
267
- }
268
- }
269
- // Recursively check nested dependencies
270
- if (depInfo.dependencies) {
271
- collectPeerDependencies(depInfo.dependencies, peerDeps, depName);
272
252
  }
273
- }
274
- return peerDeps;
253
+ return peerDeps;
275
254
  }
276
255
  function resolveVersion(versions) {
277
- if (versions.length === 0) {
278
- return { version: "*", isConflict: false };
279
- }
280
- if (versions.length === 1) {
281
- return { version: versions[0], isConflict: false };
282
- }
283
- // Try to find a compatible version
284
- // For simplicity, if all versions are the same, use that
285
- const uniqueVersions = [...new Set(versions)];
286
- if (uniqueVersions.length === 1) {
287
- return { version: uniqueVersions[0], isConflict: false };
288
- }
289
- // Check if all are compatible (simple heuristic: same major version for ^)
290
- const allCompatible = uniqueVersions.every((v) => {
291
- const baseVersion = uniqueVersions[0];
292
- // If both start with ^, check major version
293
- if (v.startsWith("^") && baseVersion.startsWith("^")) {
294
- const vMajor = v.substring(1).split(".")[0];
295
- const baseMajor = baseVersion.substring(1).split(".")[0];
296
- return vMajor === baseMajor;
256
+ if (versions.length === 0) {
257
+ return { version: "*", isConflict: false };
258
+ }
259
+ if (versions.length === 1) {
260
+ return { version: versions[0], isConflict: false };
261
+ }
262
+ // Get unique versions
263
+ const uniqueVersions = [...new Set(versions)];
264
+ if (uniqueVersions.length === 1) {
265
+ return { version: uniqueVersions[0], isConflict: false };
266
+ }
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
+ }
291
+ }
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 };
297
297
  }
298
- // If both are exact versions, they must match
299
- return v === baseVersion;
300
- });
301
- if (allCompatible && uniqueVersions[0].startsWith("^")) {
302
- // Return the highest version requirement
303
- const sorted = [...uniqueVersions].sort((a, b) => {
304
- const aVer = a.substring(1);
305
- const bVer = b.substring(1);
306
- return bVer.localeCompare(aVer, undefined, { numeric: true });
307
- });
308
- return { version: sorted[0], isConflict: false };
309
- }
310
- // Conflict detected
311
- return { version: uniqueVersions.join(" | "), isConflict: true };
312
298
  }
313
299
  function getPeerDepsTemplate() {
314
- return `
300
+ return `
315
301
  <html>
316
302
  <head>
317
303
  <title>{{projectName}} - Peer Dependencies</title>
@@ -470,11 +456,28 @@ function getPeerDepsTemplate() {
470
456
  font-weight: 600;
471
457
  }
472
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
+
473
471
  .checkmark {
474
472
  color: #10b981;
475
473
  font-size: 1.25rem;
476
474
  }
477
475
 
476
+ .info-icon {
477
+ color: #4f46e5;
478
+ font-size: 1.25rem;
479
+ }
480
+
478
481
  .message {
479
482
  margin-top: 0.5rem;
480
483
  padding: 0.5rem;
@@ -531,11 +534,19 @@ function getPeerDepsTemplate() {
531
534
  Installed
532
535
  </div>
533
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}}
534
543
  {{^isInstalled}}
544
+ {{^isInstalledByDependency}}
535
545
  <button class="install-btn" onclick="installPackage('{{name}}', '{{version}}', this)">
536
546
  npm install
537
547
  </button>
538
548
  <div class="message" id="msg-{{name}}"></div>
549
+ {{/isInstalledByDependency}}
539
550
  {{/isInstalled}}
540
551
  </div>
541
552
  </div>
@@ -600,7 +611,7 @@ function getPeerDepsTemplate() {
600
611
  `;
601
612
  }
602
613
  function getTemplate() {
603
- return `
614
+ return `
604
615
  <html>
605
616
  <head>
606
617
  <title>{{name}}'s Dependency Graph</title>
@@ -611,8 +622,8 @@ function getTemplate() {
611
622
  var nodes = graph.nodes();
612
623
  var edges = graph.edges();
613
624
 
614
- // Physics constants
615
- var repulsionStrength = 100;
625
+ // Physics constants - lower repulsion for better spacing
626
+ var repulsionStrength = 50;
616
627
  var attractionStrength = 0.01;
617
628
  var damping = 0.5;
618
629
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "easy-dep-graph",
3
- "version": "1.1.1",
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
  }