easy-dep-graph 1.0.0 → 1.1.0

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 (4) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +130 -87
  3. package/bin/index.js +624 -32
  4. package/package.json +65 -62
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2023 danisss9
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2023 danisss9
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,87 +1,130 @@
1
- # Easy Dep Graph
2
-
3
- Easily see the dependency graph of your npm project!
4
-
5
- ## Table of Contents
6
-
7
- - [Easy Dep Graph](#easy-dep-graph)
8
- - [Table of Contents](#table-of-contents)
9
- - [Install](#install)
10
- - [Use](#use)
11
- - [Arguments](#arguments)
12
- - [Packages](#packages)
13
- - [Package Dependents](#package-dependents)
14
- - [Port](#port)
15
- - [No Open](#no-open)
16
- - [Changelog](#changelog)
17
- - [FAQs](#faqs)
18
-
19
- ## Install
20
-
21
- ```cmd
22
- npm install -g easy-dep-graph
23
- ```
24
-
25
- ## Use
26
-
27
- Run the following command on the folder where you package.json is:
28
-
29
- ```cmd
30
- npx easy-dep-graph
31
- ```
32
-
33
- ## Arguments
34
-
35
- ### Packages
36
-
37
- A list of packages to show on the graph separated by ','. (By default it shows all packages)
38
-
39
- Command: `--packages <packages names>`
40
-
41
- Example:
42
- ```cmd
43
- npx easy-dep-graph --packages open,mustache,fastify
44
- ```
45
-
46
- ### Package Dependents
47
-
48
- This option will only show on graph the packages that depend on the submited package.
49
-
50
- Command: `--package-dependents <package name>`
51
-
52
- Example:
53
- ```cmd
54
- npx easy-dep-graph --package-dependents is-docker
55
- ```
56
-
57
- ### Port
58
-
59
- The port number to be used when serving the dependency graph. (Default is 8080)
60
-
61
- Command: `--port <port number>`
62
-
63
- Example:
64
- ```cmd
65
- npx easy-dep-graph --port 8000
66
- ```
67
-
68
- ### No Open
69
-
70
- Flag to not open the browser after the depedency graph is done.
71
-
72
- Command: `--no-open`
73
-
74
- Example:
75
- ```cmd
76
- npx easy-dep-graph --no-open
77
- ```
78
-
79
- ## Changelog
80
-
81
- **Version 1.0:**
82
-
83
- - published library
84
-
85
- ## FAQs
86
-
87
- No FAQs for now. (⌐■_■)
1
+ # Easy Dep Graph
2
+
3
+ Easily see the dependency graph of your npm project!
4
+
5
+ ## Table of Contents
6
+
7
+ - [Easy Dep Graph](#easy-dep-graph)
8
+ - [Table of Contents](#table-of-contents)
9
+ - [Install](#install)
10
+ - [Use](#use)
11
+ - [Arguments](#arguments)
12
+ - [Peer Dependencies](#peer-dependencies)
13
+ - [Packages](#packages)
14
+ - [Package Dependents](#package-dependents)
15
+ - [Port](#port)
16
+ - [No Open](#no-open)
17
+ - [No Force Layout](#no-force-layout)
18
+ - [Changelog](#changelog)
19
+ - [FAQs](#faqs)
20
+
21
+ ## Install
22
+
23
+ ```cmd
24
+ npm install -g easy-dep-graph
25
+ ```
26
+
27
+ ## Use
28
+
29
+ Run the following command on the folder where you package.json is:
30
+
31
+ ```cmd
32
+ npx easy-dep-graph
33
+ ```
34
+
35
+ ## Arguments
36
+
37
+ ### Peer Dependencies
38
+
39
+ Display a comprehensive view of all peer dependencies required by your project's packages. This includes:
40
+
41
+ - Package names and required versions
42
+ - Which packages require each peer dependency
43
+ - Automatic conflict detection when multiple incompatible versions are required
44
+ - Installation status (showing which peer dependencies are already installed)
45
+ - One-click installation for missing peer dependencies
46
+
47
+ Command: `--peer-dependencies`
48
+
49
+ Example:
50
+
51
+ ```cmd
52
+ npx easy-dep-graph --peer-dependencies
53
+ ```
54
+
55
+ ### Packages
56
+
57
+ A list of packages to show on the graph separated by ','. (By default it shows all packages)
58
+
59
+ Command: `--packages <packages names>`
60
+
61
+ Example:
62
+
63
+ ```cmd
64
+ npx easy-dep-graph --packages open,mustache,fastify
65
+ ```
66
+
67
+ ### Package Dependents
68
+
69
+ This option will only show on graph the packages that depend on the submited package.
70
+
71
+ Command: `--package-dependents <package name>`
72
+
73
+ Example:
74
+
75
+ ```cmd
76
+ npx easy-dep-graph --package-dependents is-docker
77
+ ```
78
+
79
+ ### Port
80
+
81
+ The port number to be used when serving the dependency graph. (Default is 8080)
82
+
83
+ Command: `--port <port number>`
84
+
85
+ Example:
86
+
87
+ ```cmd
88
+ npx easy-dep-graph --port 8000
89
+ ```
90
+
91
+ ### No Open
92
+
93
+ Flag to not open the browser after the depedency graph is done.
94
+
95
+ Command: `--no-open`
96
+
97
+ Example:
98
+
99
+ ```cmd
100
+ npx easy-dep-graph --no-open
101
+ ```
102
+
103
+ ### No Force Layout
104
+
105
+ Flag to skip applying the force-directed layout algorithm to the dependency graph. When this flag is used, the graph will display nodes in their initial random positions without automatic layout optimization. This can be useful for very large graphs where the layout calculation might take too long, or when you want to manually arrange nodes.
106
+
107
+ Command: `--no-force-layout`
108
+
109
+ Example:
110
+
111
+ ```cmd
112
+ npx easy-dep-graph --no-force-layout
113
+ ```
114
+
115
+ ## Changelog
116
+
117
+ **Version 1.1:**
118
+
119
+ - Added `--peer-dependencies` flag to display all peer dependencies in your project
120
+ - Interactive peer dependency viewer with one-click installation
121
+ - Automatic version conflict detection for peer dependencies
122
+ - Replaced vis-network with sigma.js for dependency view
123
+
124
+ **Version 1.0:**
125
+
126
+ - published library
127
+
128
+ ## FAQs
129
+
130
+ No FAQs for now. (⌐■_■)
package/bin/index.js CHANGED
@@ -3,8 +3,13 @@ import shell from "shelljs";
3
3
  import mustache from "mustache";
4
4
  import fastify from "fastify";
5
5
  import open from "open";
6
+ import fs from "node:fs";
7
+ import path from "node:path";
6
8
  let flatDeps;
7
9
  void (async function main() {
10
+ // Check if peer dependencies mode
11
+ const peerDependenciesMode = process.argv.findIndex((a) => a.toLowerCase() === "--peer-dependencies") !==
12
+ -1;
8
13
  // List of packages user wants to see the dependencies
9
14
  const packagesArgIndex = process.argv.findIndex((a) => a.toLowerCase() === "--packages");
10
15
  const packagesFilter = packagesArgIndex !== -1 ? process.argv[packagesArgIndex + 1] : undefined;
@@ -16,6 +21,13 @@ void (async function main() {
16
21
  const port = portArgIndex !== -1 ? +process.argv[portArgIndex + 1] : 8080;
17
22
  // Get if should not open browser
18
23
  const shouldOpenBrowser = process.argv.findIndex((a) => a.toLowerCase() === "--no-open") === -1;
24
+ // Get if should not apply force layout
25
+ const shouldApplyForceLayout = process.argv.findIndex((a) => a.toLowerCase() === "--no-force-layout") ===
26
+ -1;
27
+ if (peerDependenciesMode) {
28
+ await runPeerDependenciesMode(port, shouldOpenBrowser);
29
+ return;
30
+ }
19
31
  // Run npm list command
20
32
  const result = shell.exec(`npm list --json ${packageName ?? "--all"}`, {
21
33
  windowsHide: true,
@@ -37,19 +49,26 @@ void (async function main() {
37
49
  flatDeps = {};
38
50
  flatDepsRecursive(packageInfo.dependencies);
39
51
  // Turn deps into nodes and edges
52
+ const nodeEntries = Object.entries(flatDeps);
53
+ const nodeCount = nodeEntries.length;
40
54
  const data = {
41
55
  name: packageInfo.name,
42
- nodes: JSON.stringify(Object.entries(flatDeps).map((dep) => ({
43
- id: dep[0],
44
- label: `${dep[0]} V${dep[1].version}`,
45
- color: { border: dep[1].isRoot ? "green" : "blue" },
46
- }))),
56
+ applyForceLayout: shouldApplyForceLayout,
57
+ nodes: JSON.stringify(nodeEntries.map((dep, index) => {
58
+ return {
59
+ key: dep[0],
60
+ label: `${dep[0]} V${dep[1].version}`,
61
+ x: Math.random() * 100 - 50,
62
+ y: Math.random() * 100 - 50,
63
+ size: 10,
64
+ color: dep[1].isRoot ? "#22c55e" : "#3b82f6",
65
+ };
66
+ })),
47
67
  edges: JSON.stringify(Object.entries(flatDeps)
48
68
  .filter((dep) => !!dep[1].dependencies.length)
49
69
  .flatMap((dep) => dep[1].dependencies.map((d) => ({
50
- from: dep[0],
51
- to: d,
52
- arrows: "to",
70
+ source: dep[0],
71
+ target: d,
53
72
  })))),
54
73
  };
55
74
  // Render the UI using mustache
@@ -89,30 +108,603 @@ function flatDepsRecursive(deps, parentDepName) {
89
108
  flatDepsRecursive(depInfo.dependencies, depName);
90
109
  }
91
110
  }
111
+ async function runPeerDependenciesMode(port, shouldOpenBrowser) {
112
+ console.log("Analyzing peer dependencies...");
113
+ // Get the current project's package.json
114
+ const packageJsonPath = path.join(process.cwd(), "package.json");
115
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
116
+ const installedPackages = {
117
+ ...packageJson.dependencies,
118
+ ...packageJson.devDependencies,
119
+ };
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);
131
+ // Resolve versions
132
+ const peerDepsList = Object.entries(peerDeps).map(([name, info]) => {
133
+ const resolvedVersion = resolveVersion(info.versions);
134
+ const isInstalled = installedPackages[name] !== undefined;
135
+ return {
136
+ name,
137
+ version: resolvedVersion.version,
138
+ isConflict: resolvedVersion.isConflict,
139
+ requiredBy: info.requiredBy,
140
+ isInstalled,
141
+ };
142
+ });
143
+ // Sort by name
144
+ peerDepsList.sort((a, b) => a.name.localeCompare(b.name));
145
+ const data = {
146
+ projectName: packageJson.name,
147
+ peerDeps: peerDepsList,
148
+ };
149
+ // Render the UI using mustache
150
+ const html = mustache.render(getPeerDepsTemplate(), data);
151
+ // Initialize server to serve graph
152
+ const app = fastify({
153
+ logger: false,
154
+ });
155
+ app.get("/", (_req, resp) => resp.type("text/html").send(html));
156
+ app.post("/install", async (req, resp) => {
157
+ const { package: pkg, version } = req.body;
158
+ console.log(`Installing ${pkg}@${version}...`);
159
+ const installResult = shell.exec(`npm install ${pkg}@${version}`, {
160
+ windowsHide: true,
161
+ silent: true,
162
+ });
163
+ if (installResult.code === 0) {
164
+ console.log(`Successfully installed ${pkg}@${version}`);
165
+ return {
166
+ success: true,
167
+ message: `Successfully installed ${pkg}@${version}`,
168
+ };
169
+ }
170
+ else {
171
+ console.error(`Failed to install ${pkg}@${version}`);
172
+ return {
173
+ success: false,
174
+ message: installResult.stderr || "Installation failed",
175
+ };
176
+ }
177
+ });
178
+ // Run the server
179
+ app.listen({ port }, (err, address) => {
180
+ if (err)
181
+ throw err;
182
+ console.log(`Done! Visit "${address}" to see peer dependencies.`);
183
+ if (shouldOpenBrowser)
184
+ open(address);
185
+ });
186
+ }
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
+ const peerDeps = {};
199
+ const nodeModulesPath = path.join(process.cwd(), "node_modules");
200
+ for (const packageName of packageNames) {
201
+ try {
202
+ const pkgJsonPath = path.join(nodeModulesPath, packageName, "package.json");
203
+ if (!fs.existsSync(pkgJsonPath)) {
204
+ continue;
205
+ }
206
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
207
+ if (pkgJson.peerDependencies) {
208
+ for (const [peerDepName, peerDepVersion] of Object.entries(pkgJson.peerDependencies)) {
209
+ if (!peerDeps[peerDepName]) {
210
+ peerDeps[peerDepName] = {
211
+ versions: [],
212
+ requiredBy: [],
213
+ };
214
+ }
215
+ const versionStr = peerDepVersion;
216
+ if (!peerDeps[peerDepName].versions.includes(versionStr)) {
217
+ peerDeps[peerDepName].versions.push(versionStr);
218
+ }
219
+ if (!peerDeps[peerDepName].requiredBy.includes(packageName)) {
220
+ peerDeps[peerDepName].requiredBy.push(packageName);
221
+ }
222
+ }
223
+ }
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);
250
+ }
251
+ }
252
+ }
253
+ // Recursively check nested dependencies
254
+ if (depInfo.dependencies) {
255
+ collectPeerDependencies(depInfo.dependencies, peerDeps, depName);
256
+ }
257
+ }
258
+ return peerDeps;
259
+ }
260
+ function resolveVersion(versions) {
261
+ if (versions.length === 0) {
262
+ return { version: "*", isConflict: false };
263
+ }
264
+ if (versions.length === 1) {
265
+ return { version: versions[0], isConflict: false };
266
+ }
267
+ // Try to find a compatible version
268
+ // For simplicity, if all versions are the same, use that
269
+ const uniqueVersions = [...new Set(versions)];
270
+ if (uniqueVersions.length === 1) {
271
+ return { version: uniqueVersions[0], isConflict: false };
272
+ }
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;
281
+ }
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 };
293
+ }
294
+ // Conflict detected
295
+ return { version: uniqueVersions.join(" | "), isConflict: true };
296
+ }
297
+ function getPeerDepsTemplate() {
298
+ return `
299
+ <html>
300
+ <head>
301
+ <title>{{projectName}} - Peer Dependencies</title>
302
+ <style>
303
+ * {
304
+ margin: 0;
305
+ padding: 0;
306
+ box-sizing: border-box;
307
+ }
308
+
309
+ body {
310
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
311
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
312
+ min-height: 100vh;
313
+ padding: 2rem;
314
+ }
315
+
316
+ .container {
317
+ max-width: 1200px;
318
+ margin: 0 auto;
319
+ }
320
+
321
+ h1 {
322
+ color: white;
323
+ margin-bottom: 2rem;
324
+ font-size: 2.5rem;
325
+ text-align: center;
326
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
327
+ }
328
+
329
+ .peer-deps-list {
330
+ background: white;
331
+ border-radius: 12px;
332
+ box-shadow: 0 10px 40px rgba(0,0,0,0.1);
333
+ overflow: hidden;
334
+ }
335
+
336
+ .peer-dep-item {
337
+ padding: 1.5rem;
338
+ border-bottom: 1px solid #e5e7eb;
339
+ transition: background-color 0.2s;
340
+ }
341
+
342
+ .peer-dep-item:last-child {
343
+ border-bottom: none;
344
+ }
345
+
346
+ .peer-dep-item:hover {
347
+ background-color: #f9fafb;
348
+ }
349
+
350
+ .peer-dep-header {
351
+ display: flex;
352
+ justify-content: space-between;
353
+ align-items: center;
354
+ margin-bottom: 0.75rem;
355
+ }
356
+
357
+ .peer-dep-name {
358
+ font-size: 1.25rem;
359
+ font-weight: 600;
360
+ color: #1f2937;
361
+ }
362
+
363
+ .peer-dep-version {
364
+ font-family: 'Courier New', monospace;
365
+ padding: 0.25rem 0.75rem;
366
+ background: #f3f4f6;
367
+ border-radius: 6px;
368
+ font-size: 0.875rem;
369
+ color: #4b5563;
370
+ }
371
+
372
+ .peer-dep-version.conflict {
373
+ background: #fee2e2;
374
+ color: #dc2626;
375
+ font-weight: 600;
376
+ }
377
+
378
+ .peer-dep-info {
379
+ display: flex;
380
+ justify-content: space-between;
381
+ align-items: center;
382
+ flex-wrap: wrap;
383
+ gap: 1rem;
384
+ }
385
+
386
+ .required-by {
387
+ flex: 1;
388
+ min-width: 200px;
389
+ }
390
+
391
+ .required-by-label {
392
+ font-size: 0.75rem;
393
+ color: #6b7280;
394
+ text-transform: uppercase;
395
+ letter-spacing: 0.05em;
396
+ margin-bottom: 0.25rem;
397
+ }
398
+
399
+ .required-by-list {
400
+ display: flex;
401
+ flex-wrap: wrap;
402
+ gap: 0.5rem;
403
+ }
404
+
405
+ .required-by-tag {
406
+ display: inline-block;
407
+ padding: 0.25rem 0.5rem;
408
+ background: #dbeafe;
409
+ color: #1e40af;
410
+ border-radius: 4px;
411
+ font-size: 0.75rem;
412
+ font-weight: 500;
413
+ }
414
+
415
+ .install-btn {
416
+ padding: 0.5rem 1.5rem;
417
+ background: #3b82f6;
418
+ color: white;
419
+ border: none;
420
+ border-radius: 6px;
421
+ font-size: 0.875rem;
422
+ font-weight: 500;
423
+ cursor: pointer;
424
+ transition: all 0.2s;
425
+ box-shadow: 0 2px 4px rgba(59, 130, 246, 0.3);
426
+ }
427
+
428
+ .install-btn:hover {
429
+ background: #2563eb;
430
+ transform: translateY(-1px);
431
+ box-shadow: 0 4px 8px rgba(59, 130, 246, 0.4);
432
+ }
433
+
434
+ .install-btn:active {
435
+ transform: translateY(0);
436
+ }
437
+
438
+ .install-btn:disabled {
439
+ background: #9ca3af;
440
+ cursor: not-allowed;
441
+ transform: none;
442
+ box-shadow: none;
443
+ }
444
+
445
+ .installed-badge {
446
+ display: flex;
447
+ align-items: center;
448
+ gap: 0.5rem;
449
+ padding: 0.5rem 1rem;
450
+ background: #d1fae5;
451
+ color: #065f46;
452
+ border-radius: 6px;
453
+ font-size: 0.875rem;
454
+ font-weight: 600;
455
+ }
456
+
457
+ .checkmark {
458
+ color: #10b981;
459
+ font-size: 1.25rem;
460
+ }
461
+
462
+ .message {
463
+ margin-top: 0.5rem;
464
+ padding: 0.5rem;
465
+ border-radius: 4px;
466
+ font-size: 0.875rem;
467
+ display: none;
468
+ }
469
+
470
+ .message.success {
471
+ background: #d1fae5;
472
+ color: #065f46;
473
+ display: block;
474
+ }
475
+
476
+ .message.error {
477
+ background: #fee2e2;
478
+ color: #dc2626;
479
+ display: block;
480
+ }
481
+
482
+ .no-deps {
483
+ padding: 3rem;
484
+ text-align: center;
485
+ color: #6b7280;
486
+ font-size: 1.125rem;
487
+ }
488
+ </style>
489
+ </head>
490
+ <body>
491
+ <div class="container">
492
+ <h1>Peer Dependencies for {{projectName}}</h1>
493
+ <div class="peer-deps-list">
494
+ {{#peerDeps}}
495
+ <div class="peer-dep-item">
496
+ <div class="peer-dep-header">
497
+ <span class="peer-dep-name">{{name}}</span>
498
+ <span class="peer-dep-version {{#isConflict}}conflict{{/isConflict}}">
499
+ {{#isConflict}}Conflict: {{/isConflict}}{{version}}
500
+ </span>
501
+ </div>
502
+ <div class="peer-dep-info">
503
+ <div class="required-by">
504
+ <div class="required-by-label">Required by:</div>
505
+ <div class="required-by-list">
506
+ {{#requiredBy}}
507
+ <span class="required-by-tag">{{.}}</span>
508
+ {{/requiredBy}}
509
+ </div>
510
+ </div>
511
+ <div class="action-container">
512
+ {{#isInstalled}}
513
+ <div class="installed-badge">
514
+ <span class="checkmark">✓</span>
515
+ Installed
516
+ </div>
517
+ {{/isInstalled}}
518
+ {{^isInstalled}}
519
+ <button class="install-btn" onclick="installPackage('{{name}}', '{{version}}', this)">
520
+ npm install
521
+ </button>
522
+ <div class="message" id="msg-{{name}}"></div>
523
+ {{/isInstalled}}
524
+ </div>
525
+ </div>
526
+ </div>
527
+ {{/peerDeps}}
528
+ {{^peerDeps}}
529
+ <div class="no-deps">
530
+ No peer dependencies found!
531
+ </div>
532
+ {{/peerDeps}}
533
+ </div>
534
+ </div>
535
+
536
+ <script>
537
+ async function installPackage(packageName, version, button) {
538
+ const messageEl = document.getElementById('msg-' + packageName);
539
+
540
+ button.disabled = true;
541
+ button.textContent = 'Installing...';
542
+ messageEl.className = 'message';
543
+ messageEl.textContent = '';
544
+
545
+ try {
546
+ const response = await fetch('/install', {
547
+ method: 'POST',
548
+ headers: {
549
+ 'Content-Type': 'application/json',
550
+ },
551
+ body: JSON.stringify({
552
+ package: packageName,
553
+ version: version
554
+ })
555
+ });
556
+
557
+ const result = await response.json();
558
+
559
+ if (result.success) {
560
+ messageEl.className = 'message success';
561
+ messageEl.textContent = result.message;
562
+ button.style.display = 'none';
563
+
564
+ // Optionally reload after a delay
565
+ setTimeout(() => {
566
+ location.reload();
567
+ }, 2000);
568
+ } else {
569
+ messageEl.className = 'message error';
570
+ messageEl.textContent = result.message;
571
+ button.disabled = false;
572
+ button.textContent = 'npm install';
573
+ }
574
+ } catch (error) {
575
+ messageEl.className = 'message error';
576
+ messageEl.textContent = 'Failed to install package: ' + error.message;
577
+ button.disabled = false;
578
+ button.textContent = 'npm install';
579
+ }
580
+ }
581
+ </script>
582
+ </body>
583
+ </html>
584
+ `;
585
+ }
92
586
  function getTemplate() {
93
- return `
94
- <html>
95
- <head>
96
- <title>{{name}}'s Dependency Graph</title>
97
- <script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
98
- <script type="text/javascript">
99
- function renderGraph() {
100
- var nodes = new vis.DataSet({{{nodes}}});
101
- var edges = new vis.DataSet({{{edges}}});
102
- var container = document.getElementById("dep-graph");
103
- var data = { nodes: nodes, edges: edges };
104
- var options = { };
105
- var network = new vis.Network(container, data, options);
106
- }
107
- </script>
108
- <style>
109
- body { margin: 0; padding: 0; }
110
- #dep-graph { width: 100vw; height: 100vh; }
111
- </style>
112
- </head>
113
- <body onload="renderGraph()">
114
- <div id="dep-graph"></div>
115
- </body>
116
- </html>
587
+ return `
588
+ <html>
589
+ <head>
590
+ <title>{{name}}'s Dependency Graph</title>
591
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/sigma.js/2.4.0/sigma.min.js"></script>
592
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/graphology/0.25.4/graphology.umd.min.js"></script>
593
+ <script type="text/javascript">
594
+ function applyForceLayout(graph, iterations) {
595
+ var nodes = graph.nodes();
596
+ var edges = graph.edges();
597
+
598
+ // Physics constants
599
+ var repulsionStrength = 100;
600
+ var attractionStrength = 0.01;
601
+ var damping = 0.5;
602
+
603
+ for (var iter = 0; iter < iterations; iter++) {
604
+ var forces = {};
605
+
606
+ // Initialize forces
607
+ nodes.forEach(function(nodeId) {
608
+ forces[nodeId] = { x: 0, y: 0 };
609
+ });
610
+
611
+ // Repulsive forces between all nodes
612
+ for (var i = 0; i < nodes.length; i++) {
613
+ for (var j = i + 1; j < nodes.length; j++) {
614
+ var node1 = nodes[i];
615
+ var node2 = nodes[j];
616
+ var attrs1 = graph.getNodeAttributes(node1);
617
+ var attrs2 = graph.getNodeAttributes(node2);
618
+
619
+ var dx = attrs2.x - attrs1.x;
620
+ var dy = attrs2.y - attrs1.y;
621
+ var distance = Math.sqrt(dx * dx + dy * dy) || 0.1;
622
+ var force = repulsionStrength / (distance * distance);
623
+
624
+ var fx = (dx / distance) * force;
625
+ var fy = (dy / distance) * force;
626
+
627
+ forces[node1].x -= fx;
628
+ forces[node1].y -= fy;
629
+ forces[node2].x += fx;
630
+ forces[node2].y += fy;
631
+ }
632
+ }
633
+
634
+ // Attractive forces along edges
635
+ edges.forEach(function(edgeId) {
636
+ var edge = graph.extremities(edgeId);
637
+ var source = edge.source || edge[0];
638
+ var target = edge.target || edge[1];
639
+ var attrs1 = graph.getNodeAttributes(source);
640
+ var attrs2 = graph.getNodeAttributes(target);
641
+
642
+ var dx = attrs2.x - attrs1.x;
643
+ var dy = attrs2.y - attrs1.y;
644
+ var distance = Math.sqrt(dx * dx + dy * dy) || 0.1;
645
+
646
+ var fx = dx * attractionStrength;
647
+ var fy = dy * attractionStrength;
648
+
649
+ forces[source].x += fx;
650
+ forces[source].y += fy;
651
+ forces[target].x -= fx;
652
+ forces[target].y -= fy;
653
+ });
654
+
655
+ // Apply forces with damping
656
+ nodes.forEach(function(nodeId) {
657
+ var attrs = graph.getNodeAttributes(nodeId);
658
+ attrs.x += forces[nodeId].x * damping;
659
+ attrs.y += forces[nodeId].y * damping;
660
+ });
661
+ }
662
+ }
663
+
664
+ function renderGraph() {
665
+ var nodes = {{{nodes}}};
666
+ var edges = {{{edges}}};
667
+
668
+ var graph = new graphology.Graph();
669
+
670
+ nodes.forEach(function(node) {
671
+ graph.addNode(node.key, {
672
+ label: node.label,
673
+ x: node.x,
674
+ y: node.y,
675
+ size: node.size,
676
+ color: node.color
677
+ });
678
+ });
679
+
680
+ edges.forEach(function(edge, index) {
681
+ graph.addEdge(edge.source, edge.target, {
682
+ size: 2,
683
+ color: '#94a3b8'
684
+ });
685
+ });
686
+
687
+ // Apply custom force-directed layout
688
+ {{#applyForceLayout}}
689
+ applyForceLayout(graph, 100);
690
+ {{/applyForceLayout}}
691
+
692
+ var container = document.getElementById("dep-graph");
693
+ var renderer = new Sigma(graph, container, {
694
+ renderEdgeLabels: false,
695
+ defaultNodeColor: '#3b82f6',
696
+ defaultEdgeColor: '#94a3b8'
697
+ });
698
+ }
699
+ </script>
700
+ <style>
701
+ body { margin: 0; padding: 0; }
702
+ #dep-graph { width: 100vw; height: 100vh; background: #f8fafc; }
703
+ </style>
704
+ </head>
705
+ <body onload="renderGraph()">
706
+ <div id="dep-graph"></div>
707
+ </body>
708
+ </html>
117
709
  `;
118
710
  }
package/package.json CHANGED
@@ -1,62 +1,65 @@
1
- {
2
- "name": "easy-dep-graph",
3
- "version": "1.0.0",
4
- "description": "Easily see the dependency graph of your npm project",
5
- "homepage": "https://github.com/danisss9/easy-dep-graph#readme",
6
- "repository": {
7
- "type": "git",
8
- "url": "https://github.com/danisss9/easy-dep-graph.git"
9
- },
10
- "bugs": {
11
- "url": "https://github.com/danisss9/easy-dep-graph/issues"
12
- },
13
- "bin": {
14
- "easy-dep-graph": "bin/index.js"
15
- },
16
- "type": "module",
17
- "license": "MIT",
18
- "keywords": [
19
- "node",
20
- "nodejs",
21
- "npm",
22
- "npx",
23
- "list",
24
- "la",
25
- "la",
26
- "easy",
27
- "dep",
28
- "circular",
29
- "dependencies",
30
- "dependency",
31
- "dependent",
32
- "graph",
33
- "visual",
34
- "browser",
35
- "angular",
36
- "ng",
37
- "react",
38
- "vue",
39
- "svelte",
40
- "javascript",
41
- "typescript",
42
- "js",
43
- "ts"
44
- ],
45
- "scripts": {
46
- "release": "npm run compile:lib && npm run publish:lib",
47
- "compile:lib": "tsc --project ./src/tsconfig.json",
48
- "publish:lib": "npm publish --access public"
49
- },
50
- "dependencies": {
51
- "fastify": "^4.25.2",
52
- "mustache": "^4.2.0",
53
- "open": "^10.0.3",
54
- "shelljs": "^0.8.5"
55
- },
56
- "devDependencies": {
57
- "@types/mustache": "^4.2.5",
58
- "@types/node": "^20.11.5",
59
- "@types/shelljs": "^0.8.15",
60
- "typescript": "^5.3.3"
61
- }
62
- }
1
+ {
2
+ "name": "easy-dep-graph",
3
+ "version": "1.1.0",
4
+ "description": "Easily see the dependency graph of your npm project",
5
+ "homepage": "https://github.com/danisss9/easy-dep-graph#readme",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/danisss9/easy-dep-graph.git"
9
+ },
10
+ "bugs": {
11
+ "url": "https://github.com/danisss9/easy-dep-graph/issues"
12
+ },
13
+ "bin": {
14
+ "easy-dep-graph": "bin/index.js"
15
+ },
16
+ "type": "module",
17
+ "license": "MIT",
18
+ "keywords": [
19
+ "node",
20
+ "nodejs",
21
+ "npm",
22
+ "npx",
23
+ "list",
24
+ "la",
25
+ "la",
26
+ "easy",
27
+ "dep",
28
+ "circular",
29
+ "dependencies",
30
+ "dependency",
31
+ "dependent",
32
+ "peerDeps",
33
+ "peerDependencies",
34
+ "graph",
35
+ "visual",
36
+ "browser",
37
+ "angular",
38
+ "ng",
39
+ "react",
40
+ "vue",
41
+ "svelte",
42
+ "javascript",
43
+ "typescript",
44
+ "js",
45
+ "ts"
46
+ ],
47
+ "scripts": {
48
+ "start": "node ./bin/index.js",
49
+ "release": "npm run compile:lib && npm run publish:lib",
50
+ "compile:lib": "tsc --project ./src/tsconfig.json",
51
+ "publish:lib": "npm publish --access public"
52
+ },
53
+ "dependencies": {
54
+ "fastify": "^5.7.4",
55
+ "mustache": "^4.2.0",
56
+ "open": "^11.0.0",
57
+ "shelljs": "^0.10.0"
58
+ },
59
+ "devDependencies": {
60
+ "@types/mustache": "^4.2.6",
61
+ "@types/node": "^25.2.1",
62
+ "@types/shelljs": "^0.10.0",
63
+ "typescript": "^5.9.3"
64
+ }
65
+ }