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.
- package/LICENSE +21 -21
- package/README.md +130 -87
- package/bin/index.js +709 -101
- 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
|
-
- [
|
|
13
|
-
- [
|
|
14
|
-
- [
|
|
15
|
-
- [
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
```cmd
|
|
76
|
-
npx easy-dep-graph --
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
-
|
|
94
|
-
<html>
|
|
95
|
-
<head>
|
|
96
|
-
<title>{{name}}'s Dependency Graph</title>
|
|
97
|
-
<script
|
|
98
|
-
<script
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
var
|
|
102
|
-
var
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
var
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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.
|
|
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
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
"
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"
|
|
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
|
+
}
|