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