easy-dep-graph 1.0.0 → 1.1.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.
Files changed (4) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +130 -87
  3. package/bin/index.js +709 -101
  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,116 +3,724 @@ 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() {
8
- // List of packages user wants to see the dependencies
9
- const packagesArgIndex = process.argv.findIndex((a) => a.toLowerCase() === "--packages");
10
- const packagesFilter = packagesArgIndex !== -1 ? process.argv[packagesArgIndex + 1] : undefined;
11
- // Only get the dependents of a specific package
12
- const packageArgIndex = process.argv.findIndex((a) => a.toLowerCase() === "--package-dependents");
13
- const packageName = packageArgIndex !== -1 ? process.argv[packageArgIndex + 1] : undefined;
14
- // Get port number
15
- const portArgIndex = process.argv.findIndex((a) => a.toLowerCase() === "--port");
16
- const port = portArgIndex !== -1 ? +process.argv[portArgIndex + 1] : 8080;
17
- // Get if should not open browser
18
- const shouldOpenBrowser = process.argv.findIndex((a) => a.toLowerCase() === "--no-open") === -1;
19
- // Run npm list command
20
- const result = shell.exec(`npm list --json ${packageName ?? "--all"}`, {
21
- windowsHide: true,
22
- silent: true,
23
- });
24
- // Parse the result to a JSON object
25
- const packageInfo = JSON.parse(result.stdout);
26
- console.log(`Generating dependency graph for: "${packageInfo.name}"...`);
27
- // Filter dependencies if needed
28
- if (packagesFilter != null) {
29
- const packagesFilterList = packagesFilter.split(",").map((d) => d.trim());
30
- for (const key of Object.keys(packageInfo.dependencies)) {
31
- if (!packagesFilterList.includes(key)) {
32
- delete packageInfo.dependencies[key];
33
- }
34
- }
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
+ }
35
57
  }
36
- // Flat the dependencies from npm list
37
- flatDeps = {};
38
- flatDepsRecursive(packageInfo.dependencies);
39
- // Turn deps into nodes and edges
40
- const data = {
41
- 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
- }))),
47
- edges: JSON.stringify(Object.entries(flatDeps)
48
- .filter((dep) => !!dep[1].dependencies.length)
49
- .flatMap((dep) => dep[1].dependencies.map((d) => ({
50
- from: dep[0],
51
- to: d,
52
- arrows: "to",
53
- })))),
54
- };
55
- // Render the UI using mustache
56
- const html = mustache.render(getTemplate(), data);
57
- // Initialize server to serve graph
58
- const app = fastify({
59
- logger: false,
60
- });
61
- app.get("/", (_req, resp) => resp.type("text/html").send(html));
62
- // Run the server
63
- app.listen({ port }, (err, address) => {
64
- if (err)
65
- throw err;
66
- console.log(`Done! Visit "${address}" to see dependecy graph.`);
67
- if (shouldOpenBrowser)
68
- open(address);
69
- });
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) => ({
85
+ source: dep[0],
86
+ 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
+ });
70
104
  })();
71
105
  function flatDepsRecursive(deps, parentDepName) {
72
- if (deps == null)
73
- return;
74
- for (const dep of Object.entries(deps)) {
75
- const depName = dep[0];
76
- const depInfo = dep[1];
77
- const isRoot = parentDepName == null;
78
- if (!isRoot) {
79
- const parentDep = flatDeps[parentDepName];
80
- parentDep.dependencies.push(depName);
81
- }
82
- if (flatDeps[depName] == null) {
83
- flatDeps[depName] = {
84
- version: depInfo.version,
85
- isRoot,
86
- dependencies: [],
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
+ };
121
+ }
122
+ flatDepsRecursive(depInfo.dependencies, depName);
123
+ }
124
+ }
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,
155
+ };
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,
176
+ });
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: [],
87
229
  };
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
+ }
88
238
  }
89
- flatDepsRecursive(depInfo.dependencies, depName);
239
+ }
240
+ } catch (error) {
241
+ // Skip packages that can't be read
242
+ console.warn(`Warning: Could not read package.json for ${packageName}`);
90
243
  }
244
+ }
245
+ return peerDeps;
246
+ }
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
+ };
260
+ }
261
+ if (!peerDeps[peerDepName].versions.includes(peerDepVersion)) {
262
+ peerDeps[peerDepName].versions.push(peerDepVersion);
263
+ }
264
+ if (!peerDeps[peerDepName].requiredBy.includes(depName)) {
265
+ peerDeps[peerDepName].requiredBy.push(depName);
266
+ }
267
+ }
268
+ }
269
+ // Recursively check nested dependencies
270
+ if (depInfo.dependencies) {
271
+ collectPeerDependencies(depInfo.dependencies, peerDeps, depName);
272
+ }
273
+ }
274
+ return peerDeps;
275
+ }
276
+ 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;
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
+ }
313
+ function getPeerDepsTemplate() {
314
+ return `
315
+ <html>
316
+ <head>
317
+ <title>{{projectName}} - Peer Dependencies</title>
318
+ <style>
319
+ * {
320
+ margin: 0;
321
+ padding: 0;
322
+ box-sizing: border-box;
323
+ }
324
+
325
+ body {
326
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
327
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
328
+ min-height: 100vh;
329
+ padding: 2rem;
330
+ }
331
+
332
+ .container {
333
+ max-width: 1200px;
334
+ margin: 0 auto;
335
+ }
336
+
337
+ h1 {
338
+ color: white;
339
+ margin-bottom: 2rem;
340
+ font-size: 2.5rem;
341
+ text-align: center;
342
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
343
+ }
344
+
345
+ .peer-deps-list {
346
+ background: white;
347
+ border-radius: 12px;
348
+ box-shadow: 0 10px 40px rgba(0,0,0,0.1);
349
+ overflow: hidden;
350
+ }
351
+
352
+ .peer-dep-item {
353
+ padding: 1.5rem;
354
+ border-bottom: 1px solid #e5e7eb;
355
+ transition: background-color 0.2s;
356
+ }
357
+
358
+ .peer-dep-item:last-child {
359
+ border-bottom: none;
360
+ }
361
+
362
+ .peer-dep-item:hover {
363
+ background-color: #f9fafb;
364
+ }
365
+
366
+ .peer-dep-header {
367
+ display: flex;
368
+ justify-content: space-between;
369
+ align-items: center;
370
+ margin-bottom: 0.75rem;
371
+ }
372
+
373
+ .peer-dep-name {
374
+ font-size: 1.25rem;
375
+ font-weight: 600;
376
+ color: #1f2937;
377
+ }
378
+
379
+ .peer-dep-version {
380
+ font-family: 'Courier New', monospace;
381
+ padding: 0.25rem 0.75rem;
382
+ background: #f3f4f6;
383
+ border-radius: 6px;
384
+ font-size: 0.875rem;
385
+ color: #4b5563;
386
+ }
387
+
388
+ .peer-dep-version.conflict {
389
+ background: #fee2e2;
390
+ color: #dc2626;
391
+ font-weight: 600;
392
+ }
393
+
394
+ .peer-dep-info {
395
+ display: flex;
396
+ justify-content: space-between;
397
+ align-items: center;
398
+ flex-wrap: wrap;
399
+ gap: 1rem;
400
+ }
401
+
402
+ .required-by {
403
+ flex: 1;
404
+ min-width: 200px;
405
+ }
406
+
407
+ .required-by-label {
408
+ font-size: 0.75rem;
409
+ color: #6b7280;
410
+ text-transform: uppercase;
411
+ letter-spacing: 0.05em;
412
+ margin-bottom: 0.25rem;
413
+ }
414
+
415
+ .required-by-list {
416
+ display: flex;
417
+ flex-wrap: wrap;
418
+ gap: 0.5rem;
419
+ }
420
+
421
+ .required-by-tag {
422
+ display: inline-block;
423
+ padding: 0.25rem 0.5rem;
424
+ background: #dbeafe;
425
+ color: #1e40af;
426
+ border-radius: 4px;
427
+ font-size: 0.75rem;
428
+ font-weight: 500;
429
+ }
430
+
431
+ .install-btn {
432
+ padding: 0.5rem 1.5rem;
433
+ background: #3b82f6;
434
+ color: white;
435
+ border: none;
436
+ border-radius: 6px;
437
+ font-size: 0.875rem;
438
+ font-weight: 500;
439
+ cursor: pointer;
440
+ transition: all 0.2s;
441
+ box-shadow: 0 2px 4px rgba(59, 130, 246, 0.3);
442
+ }
443
+
444
+ .install-btn:hover {
445
+ background: #2563eb;
446
+ transform: translateY(-1px);
447
+ box-shadow: 0 4px 8px rgba(59, 130, 246, 0.4);
448
+ }
449
+
450
+ .install-btn:active {
451
+ transform: translateY(0);
452
+ }
453
+
454
+ .install-btn:disabled {
455
+ background: #9ca3af;
456
+ cursor: not-allowed;
457
+ transform: none;
458
+ box-shadow: none;
459
+ }
460
+
461
+ .installed-badge {
462
+ display: flex;
463
+ align-items: center;
464
+ gap: 0.5rem;
465
+ padding: 0.5rem 1rem;
466
+ background: #d1fae5;
467
+ color: #065f46;
468
+ border-radius: 6px;
469
+ font-size: 0.875rem;
470
+ font-weight: 600;
471
+ }
472
+
473
+ .checkmark {
474
+ color: #10b981;
475
+ font-size: 1.25rem;
476
+ }
477
+
478
+ .message {
479
+ margin-top: 0.5rem;
480
+ padding: 0.5rem;
481
+ border-radius: 4px;
482
+ font-size: 0.875rem;
483
+ display: none;
484
+ }
485
+
486
+ .message.success {
487
+ background: #d1fae5;
488
+ color: #065f46;
489
+ display: block;
490
+ }
491
+
492
+ .message.error {
493
+ background: #fee2e2;
494
+ color: #dc2626;
495
+ display: block;
496
+ }
497
+
498
+ .no-deps {
499
+ padding: 3rem;
500
+ text-align: center;
501
+ color: #6b7280;
502
+ font-size: 1.125rem;
503
+ }
504
+ </style>
505
+ </head>
506
+ <body>
507
+ <div class="container">
508
+ <h1>Peer Dependencies for {{projectName}}</h1>
509
+ <div class="peer-deps-list">
510
+ {{#peerDeps}}
511
+ <div class="peer-dep-item">
512
+ <div class="peer-dep-header">
513
+ <span class="peer-dep-name">{{name}}</span>
514
+ <span class="peer-dep-version {{#isConflict}}conflict{{/isConflict}}">
515
+ {{#isConflict}}Conflict: {{/isConflict}}{{version}}
516
+ </span>
517
+ </div>
518
+ <div class="peer-dep-info">
519
+ <div class="required-by">
520
+ <div class="required-by-label">Required by:</div>
521
+ <div class="required-by-list">
522
+ {{#requiredBy}}
523
+ <span class="required-by-tag">{{.}}</span>
524
+ {{/requiredBy}}
525
+ </div>
526
+ </div>
527
+ <div class="action-container">
528
+ {{#isInstalled}}
529
+ <div class="installed-badge">
530
+ <span class="checkmark">✓</span>
531
+ Installed
532
+ </div>
533
+ {{/isInstalled}}
534
+ {{^isInstalled}}
535
+ <button class="install-btn" onclick="installPackage('{{name}}', '{{version}}', this)">
536
+ npm install
537
+ </button>
538
+ <div class="message" id="msg-{{name}}"></div>
539
+ {{/isInstalled}}
540
+ </div>
541
+ </div>
542
+ </div>
543
+ {{/peerDeps}}
544
+ {{^peerDeps}}
545
+ <div class="no-deps">
546
+ No peer dependencies found!
547
+ </div>
548
+ {{/peerDeps}}
549
+ </div>
550
+ </div>
551
+
552
+ <script>
553
+ async function installPackage(packageName, version, button) {
554
+ const messageEl = document.getElementById('msg-' + packageName);
555
+
556
+ button.disabled = true;
557
+ button.textContent = 'Installing...';
558
+ messageEl.className = 'message';
559
+ messageEl.textContent = '';
560
+
561
+ try {
562
+ const response = await fetch('/install', {
563
+ method: 'POST',
564
+ headers: {
565
+ 'Content-Type': 'application/json',
566
+ },
567
+ body: JSON.stringify({
568
+ package: packageName,
569
+ version: version
570
+ })
571
+ });
572
+
573
+ const result = await response.json();
574
+
575
+ if (result.success) {
576
+ messageEl.className = 'message success';
577
+ messageEl.textContent = result.message;
578
+ button.style.display = 'none';
579
+
580
+ // Optionally reload after a delay
581
+ setTimeout(() => {
582
+ location.reload();
583
+ }, 2000);
584
+ } else {
585
+ messageEl.className = 'message error';
586
+ messageEl.textContent = result.message;
587
+ button.disabled = false;
588
+ button.textContent = 'npm install';
589
+ }
590
+ } catch (error) {
591
+ messageEl.className = 'message error';
592
+ messageEl.textContent = 'Failed to install package: ' + error.message;
593
+ button.disabled = false;
594
+ button.textContent = 'npm install';
595
+ }
596
+ }
597
+ </script>
598
+ </body>
599
+ </html>
600
+ `;
91
601
  }
92
602
  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>
603
+ return `
604
+ <html>
605
+ <head>
606
+ <title>{{name}}'s Dependency Graph</title>
607
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/sigma.js/2.4.0/sigma.min.js"></script>
608
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/graphology/0.25.4/graphology.umd.min.js"></script>
609
+ <script type="text/javascript">
610
+ function applyForceLayout(graph, iterations) {
611
+ var nodes = graph.nodes();
612
+ var edges = graph.edges();
613
+
614
+ // Physics constants
615
+ var repulsionStrength = 100;
616
+ var attractionStrength = 0.01;
617
+ var damping = 0.5;
618
+
619
+ for (var iter = 0; iter < iterations; iter++) {
620
+ var forces = {};
621
+
622
+ // Initialize forces
623
+ nodes.forEach(function(nodeId) {
624
+ forces[nodeId] = { x: 0, y: 0 };
625
+ });
626
+
627
+ // Repulsive forces between all nodes
628
+ for (var i = 0; i < nodes.length; i++) {
629
+ for (var j = i + 1; j < nodes.length; j++) {
630
+ var node1 = nodes[i];
631
+ var node2 = nodes[j];
632
+ var attrs1 = graph.getNodeAttributes(node1);
633
+ var attrs2 = graph.getNodeAttributes(node2);
634
+
635
+ var dx = attrs2.x - attrs1.x;
636
+ var dy = attrs2.y - attrs1.y;
637
+ var distance = Math.sqrt(dx * dx + dy * dy) || 0.1;
638
+ var force = repulsionStrength / (distance * distance);
639
+
640
+ var fx = (dx / distance) * force;
641
+ var fy = (dy / distance) * force;
642
+
643
+ forces[node1].x -= fx;
644
+ forces[node1].y -= fy;
645
+ forces[node2].x += fx;
646
+ forces[node2].y += fy;
647
+ }
648
+ }
649
+
650
+ // Attractive forces along edges
651
+ edges.forEach(function(edgeId) {
652
+ var edge = graph.extremities(edgeId);
653
+ var source = edge.source || edge[0];
654
+ var target = edge.target || edge[1];
655
+ var attrs1 = graph.getNodeAttributes(source);
656
+ var attrs2 = graph.getNodeAttributes(target);
657
+
658
+ var dx = attrs2.x - attrs1.x;
659
+ var dy = attrs2.y - attrs1.y;
660
+ var distance = Math.sqrt(dx * dx + dy * dy) || 0.1;
661
+
662
+ var fx = dx * attractionStrength;
663
+ var fy = dy * attractionStrength;
664
+
665
+ forces[source].x += fx;
666
+ forces[source].y += fy;
667
+ forces[target].x -= fx;
668
+ forces[target].y -= fy;
669
+ });
670
+
671
+ // Apply forces with damping
672
+ nodes.forEach(function(nodeId) {
673
+ var attrs = graph.getNodeAttributes(nodeId);
674
+ attrs.x += forces[nodeId].x * damping;
675
+ attrs.y += forces[nodeId].y * damping;
676
+ });
677
+ }
678
+ }
679
+
680
+ function renderGraph() {
681
+ var nodes = {{{nodes}}};
682
+ var edges = {{{edges}}};
683
+
684
+ var graph = new graphology.Graph();
685
+
686
+ nodes.forEach(function(node) {
687
+ graph.addNode(node.key, {
688
+ label: node.label,
689
+ x: node.x,
690
+ y: node.y,
691
+ size: node.size,
692
+ color: node.color
693
+ });
694
+ });
695
+
696
+ edges.forEach(function(edge, index) {
697
+ graph.mergeEdge(edge.source, edge.target, {
698
+ size: 2,
699
+ color: '#94a3b8'
700
+ });
701
+ });
702
+
703
+ // Apply custom force-directed layout
704
+ {{#applyForceLayout}}
705
+ applyForceLayout(graph, 100);
706
+ {{/applyForceLayout}}
707
+
708
+ var container = document.getElementById("dep-graph");
709
+ var renderer = new Sigma(graph, container, {
710
+ renderEdgeLabels: false,
711
+ defaultNodeColor: '#3b82f6',
712
+ defaultEdgeColor: '#94a3b8'
713
+ });
714
+ }
715
+ </script>
716
+ <style>
717
+ body { margin: 0; padding: 0; }
718
+ #dep-graph { width: 100vw; height: 100vh; background: #f8fafc; }
719
+ </style>
720
+ </head>
721
+ <body onload="renderGraph()">
722
+ <div id="dep-graph"></div>
723
+ </body>
724
+ </html>
117
725
  `;
118
726
  }
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.1",
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
+ }