dependency-radar 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +227 -32
- package/dist/aggregator.js +386 -45
- package/dist/cli.js +433 -93
- package/dist/cta.js +11 -0
- package/dist/report-assets.js +2 -2
- package/dist/report.js +203 -151
- package/dist/runners/npmLs.js +234 -20
- package/dist/runners/npmOutdated.js +34 -18
- package/dist/utils.js +17 -2
- package/package.json +13 -22
package/dist/runners/npmLs.js
CHANGED
|
@@ -5,11 +5,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.runNpmLs = runNpmLs;
|
|
7
7
|
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
9
|
const utils_1 = require("../utils");
|
|
10
|
+
const PNPM_DEPTH_ATTEMPTS = ['Infinity', '8', '4', '2', '1'];
|
|
11
|
+
const PNPM_MAX_OLD_SPACE_SIZE_MB = '8192';
|
|
9
12
|
// Normalize package-manager-specific list output into a shared dependency tree.
|
|
10
|
-
async function runNpmLs(projectPath, tempDir, tool = 'npm') {
|
|
13
|
+
async function runNpmLs(projectPath, tempDir, tool = 'npm', options = {}) {
|
|
11
14
|
const targetFile = path_1.default.join(tempDir, `${tool}-ls.json`);
|
|
12
15
|
try {
|
|
16
|
+
if (tool === 'pnpm') {
|
|
17
|
+
return await runPnpmLsWithFallback(projectPath, targetFile, options);
|
|
18
|
+
}
|
|
13
19
|
const { args, normalize } = buildLsCommand(tool);
|
|
14
20
|
const result = await (0, utils_1.runCommand)(tool, args, { cwd: projectPath });
|
|
15
21
|
const parsed = parseJsonOutput(result.stdout);
|
|
@@ -19,9 +25,7 @@ async function runNpmLs(projectPath, tempDir, tool = 'npm') {
|
|
|
19
25
|
return { ok: true, data: normalized, file: targetFile };
|
|
20
26
|
}
|
|
21
27
|
await (0, utils_1.writeJsonFile)(targetFile, { stdout: result.stdout, stderr: result.stderr, code: result.code });
|
|
22
|
-
const error = result.code
|
|
23
|
-
? `${tool} ls exited with code ${result.code}`
|
|
24
|
-
: `Failed to parse ${tool} ls output`;
|
|
28
|
+
const error = buildLsFailureMessage(tool, result.code, result.stderr);
|
|
25
29
|
return { ok: false, error, file: targetFile };
|
|
26
30
|
}
|
|
27
31
|
catch (err) {
|
|
@@ -30,12 +34,6 @@ async function runNpmLs(projectPath, tempDir, tool = 'npm') {
|
|
|
30
34
|
}
|
|
31
35
|
}
|
|
32
36
|
function buildLsCommand(tool) {
|
|
33
|
-
if (tool === 'pnpm') {
|
|
34
|
-
return {
|
|
35
|
-
args: ['list', '--json', '--depth', 'Infinity'],
|
|
36
|
-
normalize: normalizePnpmTree
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
37
|
if (tool === 'yarn') {
|
|
40
38
|
return {
|
|
41
39
|
args: ['list', '--json', '--depth', 'Infinity'],
|
|
@@ -47,6 +45,115 @@ function buildLsCommand(tool) {
|
|
|
47
45
|
normalize: normalizeNpmTree
|
|
48
46
|
};
|
|
49
47
|
}
|
|
48
|
+
async function runPnpmLsWithFallback(projectPath, targetFile, options) {
|
|
49
|
+
const installState = createPnpmInstallState(projectPath);
|
|
50
|
+
const attempts = [];
|
|
51
|
+
const env = {
|
|
52
|
+
NODE_OPTIONS: ensureNodeMaxOldSpaceSize(process.env.NODE_OPTIONS, PNPM_MAX_OLD_SPACE_SIZE_MB)
|
|
53
|
+
};
|
|
54
|
+
for (let index = 0; index < PNPM_DEPTH_ATTEMPTS.length; index++) {
|
|
55
|
+
const depth = PNPM_DEPTH_ATTEMPTS[index];
|
|
56
|
+
const result = await (0, utils_1.runCommand)('pnpm', ['list', '--json', '--depth', depth], {
|
|
57
|
+
cwd: projectPath,
|
|
58
|
+
env
|
|
59
|
+
});
|
|
60
|
+
const parsed = parseJsonOutput(result.stdout);
|
|
61
|
+
const normalized = normalizePnpmTree(parsed, installState);
|
|
62
|
+
const outOfMemory = isOutOfMemoryError(result.stderr);
|
|
63
|
+
attempts.push({
|
|
64
|
+
depth,
|
|
65
|
+
code: result.code,
|
|
66
|
+
stdoutBytes: Buffer.byteLength(result.stdout || '', 'utf8'),
|
|
67
|
+
stderrPreview: trimText(result.stderr, 1200),
|
|
68
|
+
outOfMemory
|
|
69
|
+
});
|
|
70
|
+
if (normalized) {
|
|
71
|
+
if (index > 0) {
|
|
72
|
+
progress(options, `✔ PNPM ls recovered for workspace: ${formatContextLabel(options)} (depth=${depth})`);
|
|
73
|
+
}
|
|
74
|
+
await (0, utils_1.writeJsonFile)(targetFile, normalized);
|
|
75
|
+
return { ok: true, data: normalized, file: targetFile };
|
|
76
|
+
}
|
|
77
|
+
const reason = describeAttemptFailure(result.code, result.stderr);
|
|
78
|
+
progress(options, `✖ Failed pnpm ls for workspace: ${formatContextLabel(options)} (depth=${depth}; ${reason})`);
|
|
79
|
+
const nextDepth = PNPM_DEPTH_ATTEMPTS[index + 1];
|
|
80
|
+
if (nextDepth) {
|
|
81
|
+
progress(options, `✔ Retrying pnpm ls for workspace: ${formatContextLabel(options)} (depth=${nextDepth})`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
await (0, utils_1.writeJsonFile)(targetFile, {
|
|
85
|
+
error: 'pnpm ls retries exhausted',
|
|
86
|
+
nodeOptions: env.NODE_OPTIONS,
|
|
87
|
+
attempts
|
|
88
|
+
});
|
|
89
|
+
const sawOom = attempts.some((attempt) => attempt.outOfMemory);
|
|
90
|
+
const lastAttempt = attempts[attempts.length - 1];
|
|
91
|
+
if (sawOom) {
|
|
92
|
+
return {
|
|
93
|
+
ok: false,
|
|
94
|
+
error: 'pnpm ls ran out of memory while building the dependency tree (retried with lower depths).',
|
|
95
|
+
file: targetFile
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
const suffix = lastAttempt && typeof lastAttempt.code === 'number'
|
|
99
|
+
? ` Last exit code: ${lastAttempt.code}.`
|
|
100
|
+
: '';
|
|
101
|
+
return {
|
|
102
|
+
ok: false,
|
|
103
|
+
error: `Failed to parse pnpm ls output after retries.${suffix}`,
|
|
104
|
+
file: targetFile
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function progress(options, line) {
|
|
108
|
+
if (typeof options.onProgress === 'function') {
|
|
109
|
+
options.onProgress(line);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function formatContextLabel(options) {
|
|
113
|
+
var _a;
|
|
114
|
+
const label = (_a = options.contextLabel) === null || _a === void 0 ? void 0 : _a.trim();
|
|
115
|
+
return label || '(unknown package)';
|
|
116
|
+
}
|
|
117
|
+
function describeAttemptFailure(code, stderr) {
|
|
118
|
+
if (isOutOfMemoryError(stderr))
|
|
119
|
+
return 'out of memory';
|
|
120
|
+
if (typeof code === 'number' && code !== 0)
|
|
121
|
+
return `exit code ${code}`;
|
|
122
|
+
if (code === null && stderr && stderr.trim())
|
|
123
|
+
return 'terminated before completion';
|
|
124
|
+
return 'no parseable JSON output';
|
|
125
|
+
}
|
|
126
|
+
function ensureNodeMaxOldSpaceSize(existing, megabytes) {
|
|
127
|
+
const token = '--max-old-space-size=';
|
|
128
|
+
if (typeof existing === 'string' && existing.includes(token)) {
|
|
129
|
+
return existing;
|
|
130
|
+
}
|
|
131
|
+
const option = `${token}${megabytes}`;
|
|
132
|
+
return existing && existing.trim() ? `${existing.trim()} ${option}` : option;
|
|
133
|
+
}
|
|
134
|
+
function isOutOfMemoryError(stderr) {
|
|
135
|
+
return /heap out of memory|Reached heap limit|Allocation failed - JavaScript heap out of memory/i.test(stderr || '');
|
|
136
|
+
}
|
|
137
|
+
function trimText(text, maxChars) {
|
|
138
|
+
if (!text)
|
|
139
|
+
return '';
|
|
140
|
+
const trimmed = text.trim();
|
|
141
|
+
if (trimmed.length <= maxChars)
|
|
142
|
+
return trimmed;
|
|
143
|
+
return trimmed.slice(trimmed.length - maxChars);
|
|
144
|
+
}
|
|
145
|
+
function buildLsFailureMessage(tool, code, stderr) {
|
|
146
|
+
if (isOutOfMemoryError(stderr)) {
|
|
147
|
+
return `${tool} ls ran out of memory while building the dependency tree`;
|
|
148
|
+
}
|
|
149
|
+
if (typeof code === 'number' && code !== 0) {
|
|
150
|
+
return `${tool} ls exited with code ${code}`;
|
|
151
|
+
}
|
|
152
|
+
if (code === null && stderr && stderr.trim()) {
|
|
153
|
+
return `${tool} ls failed before completion`;
|
|
154
|
+
}
|
|
155
|
+
return `Failed to parse ${tool} ls output`;
|
|
156
|
+
}
|
|
50
157
|
function parseJsonOutput(raw) {
|
|
51
158
|
if (!raw)
|
|
52
159
|
return undefined;
|
|
@@ -106,15 +213,23 @@ function normalizeNpmNode(name, node) {
|
|
|
106
213
|
}
|
|
107
214
|
return out;
|
|
108
215
|
}
|
|
109
|
-
function normalizePnpmTree(data) {
|
|
110
|
-
const roots = Array.isArray(data) ? data : [data];
|
|
111
|
-
const root = roots.find((entry) => entry
|
|
216
|
+
function normalizePnpmTree(data, installState) {
|
|
217
|
+
const roots = (Array.isArray(data) ? data : [data]).filter((entry) => entry && typeof entry === 'object');
|
|
218
|
+
const root = roots.find((entry) => !isPnpmErrorPayload(entry));
|
|
112
219
|
if (!root || typeof root !== 'object')
|
|
113
220
|
return undefined;
|
|
114
|
-
const dependencies = collectPnpmDependencyMap(root);
|
|
221
|
+
const dependencies = collectPnpmDependencyMap(root, installState);
|
|
115
222
|
return { dependencies };
|
|
116
223
|
}
|
|
117
|
-
function
|
|
224
|
+
function isPnpmErrorPayload(node) {
|
|
225
|
+
if (!node || typeof node !== 'object')
|
|
226
|
+
return false;
|
|
227
|
+
if (!('error' in node))
|
|
228
|
+
return false;
|
|
229
|
+
const error = node.error;
|
|
230
|
+
return typeof error === 'string' || (error !== null && typeof error === 'object');
|
|
231
|
+
}
|
|
232
|
+
function collectPnpmDependencyMap(node, installState) {
|
|
118
233
|
const out = {};
|
|
119
234
|
const groups = ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'];
|
|
120
235
|
for (const group of groups) {
|
|
@@ -128,7 +243,7 @@ function collectPnpmDependencyMap(node) {
|
|
|
128
243
|
const name = typeof entry.name === 'string' ? entry.name : undefined;
|
|
129
244
|
if (!name)
|
|
130
245
|
continue;
|
|
131
|
-
const normalized = normalizePnpmNode(name, entry);
|
|
246
|
+
const normalized = normalizePnpmNode(name, entry, installState);
|
|
132
247
|
if (normalized)
|
|
133
248
|
out[name] = normalized;
|
|
134
249
|
}
|
|
@@ -138,7 +253,7 @@ function collectPnpmDependencyMap(node) {
|
|
|
138
253
|
for (const [name, entry] of Object.entries(value)) {
|
|
139
254
|
if (!entry || typeof entry !== 'object')
|
|
140
255
|
continue;
|
|
141
|
-
const normalized = normalizePnpmNode(name, entry);
|
|
256
|
+
const normalized = normalizePnpmNode(name, entry, installState);
|
|
142
257
|
if (normalized)
|
|
143
258
|
out[name] = normalized;
|
|
144
259
|
}
|
|
@@ -148,19 +263,21 @@ function collectPnpmDependencyMap(node) {
|
|
|
148
263
|
return out;
|
|
149
264
|
if ((node === null || node === void 0 ? void 0 : node.dependencies) && typeof node.dependencies === 'object') {
|
|
150
265
|
for (const [name, entry] of Object.entries(node.dependencies)) {
|
|
151
|
-
const normalized = normalizePnpmNode(name, entry);
|
|
266
|
+
const normalized = normalizePnpmNode(name, entry, installState);
|
|
152
267
|
if (normalized)
|
|
153
268
|
out[name] = normalized;
|
|
154
269
|
}
|
|
155
270
|
}
|
|
156
271
|
return out;
|
|
157
272
|
}
|
|
158
|
-
function normalizePnpmNode(name, node) {
|
|
273
|
+
function normalizePnpmNode(name, node, installState) {
|
|
159
274
|
const version = typeof (node === null || node === void 0 ? void 0 : node.version) === 'string' ? node.version.trim() : '';
|
|
160
275
|
if (!version || version === 'unknown' || version === 'missing' || version === 'invalid')
|
|
161
276
|
return undefined;
|
|
277
|
+
if (!isPnpmPackageInstalled(name, version, installState))
|
|
278
|
+
return undefined;
|
|
162
279
|
const out = { name, version, dependencies: {} };
|
|
163
|
-
const childMap = collectPnpmDependencyMap(node);
|
|
280
|
+
const childMap = collectPnpmDependencyMap(node, installState);
|
|
164
281
|
if (Object.keys(childMap).length > 0) {
|
|
165
282
|
out.dependencies = childMap;
|
|
166
283
|
}
|
|
@@ -171,6 +288,103 @@ function normalizePnpmNode(name, node) {
|
|
|
171
288
|
out.dev = Boolean(node.dev);
|
|
172
289
|
return out;
|
|
173
290
|
}
|
|
291
|
+
function createPnpmInstallState(projectPath) {
|
|
292
|
+
const nodeModulesRoots = findNodeModulesRoots(projectPath);
|
|
293
|
+
const virtualStoreEntries = new Set();
|
|
294
|
+
for (const root of nodeModulesRoots) {
|
|
295
|
+
const virtualStoreDir = path_1.default.join(root, '.pnpm');
|
|
296
|
+
if (!safePathExists(virtualStoreDir))
|
|
297
|
+
continue;
|
|
298
|
+
for (const entry of safeReadDirNames(virtualStoreDir)) {
|
|
299
|
+
virtualStoreEntries.add(entry);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
enabled: virtualStoreEntries.size > 0 || nodeModulesRoots.length > 0,
|
|
304
|
+
virtualStoreEntries,
|
|
305
|
+
nodeModulesRoots,
|
|
306
|
+
installedCache: new Map()
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
function findNodeModulesRoots(startPath) {
|
|
310
|
+
const roots = [];
|
|
311
|
+
let current = path_1.default.resolve(startPath);
|
|
312
|
+
while (true) {
|
|
313
|
+
const candidate = path_1.default.join(current, 'node_modules');
|
|
314
|
+
if (safePathExists(candidate)) {
|
|
315
|
+
roots.push(candidate);
|
|
316
|
+
}
|
|
317
|
+
const parent = path_1.default.dirname(current);
|
|
318
|
+
if (parent === current)
|
|
319
|
+
break;
|
|
320
|
+
current = parent;
|
|
321
|
+
}
|
|
322
|
+
return roots;
|
|
323
|
+
}
|
|
324
|
+
function isPnpmPackageInstalled(name, version, installState) {
|
|
325
|
+
if (!installState.enabled)
|
|
326
|
+
return true;
|
|
327
|
+
const cacheKey = `${name}@${version}`;
|
|
328
|
+
const cached = installState.installedCache.get(cacheKey);
|
|
329
|
+
if (cached !== undefined)
|
|
330
|
+
return cached;
|
|
331
|
+
const normalizedName = normalizeScopedPackageNameForPnpmStore(name);
|
|
332
|
+
const storePrefix = `${normalizedName}@${version}`;
|
|
333
|
+
for (const entry of installState.virtualStoreEntries) {
|
|
334
|
+
if (entry === storePrefix || entry.startsWith(`${storePrefix}_`) || entry.startsWith(`${storePrefix}(`)) {
|
|
335
|
+
installState.installedCache.set(cacheKey, true);
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
// Workspace links may resolve outside the virtual store, but still exist in node_modules.
|
|
340
|
+
for (const nodeModulesRoot of installState.nodeModulesRoots) {
|
|
341
|
+
const packageDir = path_1.default.join(nodeModulesRoot, ...name.split('/'));
|
|
342
|
+
if (safePathExists(packageDir) && packageDirectoryMatchesVersion(packageDir, version)) {
|
|
343
|
+
installState.installedCache.set(cacheKey, true);
|
|
344
|
+
return true;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
installState.installedCache.set(cacheKey, false);
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
function normalizeScopedPackageNameForPnpmStore(name) {
|
|
351
|
+
if (!name.startsWith('@'))
|
|
352
|
+
return name;
|
|
353
|
+
const slashIndex = name.indexOf('/');
|
|
354
|
+
if (slashIndex <= 0)
|
|
355
|
+
return name;
|
|
356
|
+
return `${name.slice(0, slashIndex)}+${name.slice(slashIndex + 1)}`;
|
|
357
|
+
}
|
|
358
|
+
function safePathExists(targetPath) {
|
|
359
|
+
try {
|
|
360
|
+
return fs_1.default.existsSync(targetPath);
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function safeReadDirNames(dirPath) {
|
|
367
|
+
try {
|
|
368
|
+
return fs_1.default.readdirSync(dirPath);
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
return [];
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
function packageDirectoryMatchesVersion(packageDir, expectedVersion) {
|
|
375
|
+
const pkgJsonPath = path_1.default.join(packageDir, 'package.json');
|
|
376
|
+
if (!safePathExists(pkgJsonPath))
|
|
377
|
+
return true;
|
|
378
|
+
try {
|
|
379
|
+
const raw = fs_1.default.readFileSync(pkgJsonPath, 'utf8');
|
|
380
|
+
const parsed = JSON.parse(raw);
|
|
381
|
+
const version = typeof (parsed === null || parsed === void 0 ? void 0 : parsed.version) === 'string' ? parsed.version.trim() : '';
|
|
382
|
+
return !version || version === expectedVersion;
|
|
383
|
+
}
|
|
384
|
+
catch {
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
174
388
|
function normalizeYarnTree(data) {
|
|
175
389
|
const treePayload = resolveYarnTreePayload(data);
|
|
176
390
|
if (!treePayload || !Array.isArray(treePayload.trees))
|
|
@@ -11,24 +11,7 @@ function normalizeOutdatedOutput(tool, data) {
|
|
|
11
11
|
return undefined;
|
|
12
12
|
if (typeof data === "object" && !Array.isArray(data) && !data.type)
|
|
13
13
|
return data;
|
|
14
|
-
|
|
15
|
-
// pnpm often returns arrays of rows
|
|
16
|
-
const out = {};
|
|
17
|
-
for (const entry of data) {
|
|
18
|
-
if (!entry || typeof entry !== "object")
|
|
19
|
-
continue;
|
|
20
|
-
const name = entry.name || entry.packageName;
|
|
21
|
-
if (typeof name !== "string" || !name.trim())
|
|
22
|
-
continue;
|
|
23
|
-
out[name] = {
|
|
24
|
-
current: entry.current || entry.currentVersion || entry.from,
|
|
25
|
-
latest: entry.latest || entry.latestVersion || entry.to,
|
|
26
|
-
wanted: entry.wanted,
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
return Object.keys(out).length > 0 ? out : undefined;
|
|
30
|
-
}
|
|
31
|
-
// Yarn JSONL table output
|
|
14
|
+
// Yarn JSONL table output (classic) must be checked before generic array parsing.
|
|
32
15
|
const entries = Array.isArray(data) ? data : [data];
|
|
33
16
|
const tableEntry = entries.find((e) => { var _a; return e && typeof e === "object" && (e.type === "table" || ((_a = e.data) === null || _a === void 0 ? void 0 : _a.body)); });
|
|
34
17
|
const table = (tableEntry === null || tableEntry === void 0 ? void 0 : tableEntry.data) || tableEntry;
|
|
@@ -55,10 +38,31 @@ function normalizeOutdatedOutput(tool, data) {
|
|
|
55
38
|
}
|
|
56
39
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
57
40
|
}
|
|
41
|
+
if (Array.isArray(data)) {
|
|
42
|
+
// pnpm often returns arrays of rows
|
|
43
|
+
const out = {};
|
|
44
|
+
for (const entry of data) {
|
|
45
|
+
if (!entry || typeof entry !== "object")
|
|
46
|
+
continue;
|
|
47
|
+
const name = entry.name || entry.packageName;
|
|
48
|
+
if (typeof name !== "string" || !name.trim())
|
|
49
|
+
continue;
|
|
50
|
+
out[name] = {
|
|
51
|
+
current: entry.current || entry.currentVersion || entry.from,
|
|
52
|
+
latest: entry.latest || entry.latestVersion || entry.to,
|
|
53
|
+
wanted: entry.wanted,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
57
|
+
}
|
|
58
58
|
if (tool === "pnpm" && data.outdated)
|
|
59
59
|
return data.outdated;
|
|
60
60
|
return undefined;
|
|
61
61
|
}
|
|
62
|
+
function isYarnOutdatedUnsupported(result) {
|
|
63
|
+
const output = `${result.stdout}\n${result.stderr}`;
|
|
64
|
+
return /Couldn't find a script named "outdated"/i.test(output);
|
|
65
|
+
}
|
|
62
66
|
function buildOutdatedCommand(tool) {
|
|
63
67
|
if (tool === "pnpm") {
|
|
64
68
|
return {
|
|
@@ -93,6 +97,18 @@ async function runPackageOutdated(projectPath, tempDir, tool) {
|
|
|
93
97
|
await (0, utils_1.writeJsonFile)(targetFile, normalized);
|
|
94
98
|
return { ok: true, data: normalized, file: targetFile };
|
|
95
99
|
}
|
|
100
|
+
if (tool === "yarn" && isYarnOutdatedUnsupported(result)) {
|
|
101
|
+
await (0, utils_1.writeJsonFile)(targetFile, {
|
|
102
|
+
stdout: result.stdout,
|
|
103
|
+
stderr: result.stderr,
|
|
104
|
+
code: result.code,
|
|
105
|
+
});
|
|
106
|
+
return {
|
|
107
|
+
ok: false,
|
|
108
|
+
error: 'Yarn outdated is not available in this Yarn release (common on Yarn Berry).',
|
|
109
|
+
file: targetFile,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
96
112
|
await (0, utils_1.writeJsonFile)(targetFile, {
|
|
97
113
|
stdout: result.stdout,
|
|
98
114
|
stderr: result.stderr,
|
package/dist/utils.js
CHANGED
|
@@ -27,7 +27,8 @@ function runCommand(command, args, options = {}) {
|
|
|
27
27
|
return new Promise((resolve, reject) => {
|
|
28
28
|
const child = (0, child_process_1.spawn)(command, args, {
|
|
29
29
|
cwd: options.cwd,
|
|
30
|
-
shell: false
|
|
30
|
+
shell: false,
|
|
31
|
+
env: options.env ? { ...process.env, ...options.env } : process.env
|
|
31
32
|
});
|
|
32
33
|
const stdoutChunks = [];
|
|
33
34
|
const stderrChunks = [];
|
|
@@ -148,7 +149,7 @@ async function getPnpmStoreIndex(pnpmDir) {
|
|
|
148
149
|
const entries = await getPnpmStoreEntries(pnpmDir);
|
|
149
150
|
const index = new Map();
|
|
150
151
|
for (const entry of entries) {
|
|
151
|
-
const prefix = entry
|
|
152
|
+
const prefix = extractPnpmStoreEntryPrefix(entry);
|
|
152
153
|
if (!prefix)
|
|
153
154
|
continue;
|
|
154
155
|
if (!index.has(prefix))
|
|
@@ -158,6 +159,20 @@ async function getPnpmStoreIndex(pnpmDir) {
|
|
|
158
159
|
pnpmStoreIndexCache.set(pnpmDir, index);
|
|
159
160
|
return index;
|
|
160
161
|
}
|
|
162
|
+
function extractPnpmStoreEntryPrefix(entry) {
|
|
163
|
+
const atIndex = entry.startsWith('@') ? entry.indexOf('@', 1) : entry.indexOf('@');
|
|
164
|
+
if (atIndex <= 0)
|
|
165
|
+
return entry.split('(')[0];
|
|
166
|
+
const versionAndSuffix = entry.slice(atIndex + 1);
|
|
167
|
+
const underscoreIndex = versionAndSuffix.indexOf('_');
|
|
168
|
+
const parenIndex = versionAndSuffix.indexOf('(');
|
|
169
|
+
let cutoff = versionAndSuffix.length;
|
|
170
|
+
if (underscoreIndex >= 0)
|
|
171
|
+
cutoff = Math.min(cutoff, underscoreIndex);
|
|
172
|
+
if (parenIndex >= 0)
|
|
173
|
+
cutoff = Math.min(cutoff, parenIndex);
|
|
174
|
+
return entry.slice(0, atIndex + 1 + cutoff);
|
|
175
|
+
}
|
|
161
176
|
async function resolvePnpmPackageJsonPath(pkgName, version, resolvePaths) {
|
|
162
177
|
if (!version || version.startsWith('link:') || version.startsWith('workspace:') || version.startsWith('file:')) {
|
|
163
178
|
return undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dependency-radar",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Local-first dependency analysis tool that generates a single HTML report showing risk, size, usage, and structure of your project's dependencies.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -17,26 +17,16 @@
|
|
|
17
17
|
"build": "npm run build:spdx && npm run build:report && tsc",
|
|
18
18
|
"dev": "ts-node src/cli.ts scan",
|
|
19
19
|
"scan": "node dist/cli.js scan",
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"fixtures
|
|
24
|
-
"fixtures:
|
|
25
|
-
"fixtures:
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"fixtures:scan:pnpm-hoisted": "npm run dev -- --project test-fixtures/pnpm-workspace-hoisted --out test-fixtures/pnpm-workspace-hoisted/dependency-radar.html --json --keep-temp",
|
|
31
|
-
"fixtures:scan:yarn": "npm run dev -- --project test-fixtures/yarn-workspace --out test-fixtures/yarn-workspace/dependency-radar.html --json --keep-temp",
|
|
32
|
-
"fixtures:scan:yarn-berry": "npm run dev -- --project test-fixtures/yarn-berry-workspace --out test-fixtures/yarn-berry-workspace/dependency-radar.html --json --keep-temp",
|
|
33
|
-
"fixtures:scan:optional": "npm run dev -- --project test-fixtures/optional-deps --out test-fixtures/optional-deps/dependency-radar.html --json --keep-temp",
|
|
34
|
-
"fixtures:scan:no-node-modules": "npm run dev -- --project test-fixtures/no-node-modules --out test-fixtures/no-node-modules/dependency-radar.html --json --keep-temp",
|
|
35
|
-
"fixtures:install": "npm run fixtures:install:npm && npm run fixtures:install:npm-heavy && npm run fixtures:install:pnpm && npm run fixtures:install:yarn",
|
|
36
|
-
"fixtures:scan": "npm run fixtures:scan:npm && npm run fixtures:scan:npm-heavy && npm run fixtures:scan:pnpm && npm run fixtures:scan:pnpm-hoisted && npm run fixtures:scan:yarn && npm run fixtures:scan:yarn-berry && npm run fixtures:scan:optional",
|
|
37
|
-
"fixtures:install:all": "npm run fixtures:install:npm && npm run fixtures:install:npm-heavy && npm run fixtures:install:pnpm && npm run fixtures:install:pnpm-hoisted && npm run fixtures:install:yarn && npm run fixtures:install:yarn-berry && npm run fixtures:install:optional",
|
|
38
|
-
"prepublishOnly": "npm run build",
|
|
39
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
20
|
+
"test": "npm run test:unit",
|
|
21
|
+
"test:unit": "vitest run",
|
|
22
|
+
"test:unit:watch": "vitest",
|
|
23
|
+
"test:fixtures": "npm --prefix test-fixtures run test",
|
|
24
|
+
"test:fixtures:online": "npm --prefix test-fixtures run test:online",
|
|
25
|
+
"test:fixtures:all": "npm --prefix test-fixtures run test:all",
|
|
26
|
+
"test:pack": "npm pack --dry-run",
|
|
27
|
+
"test:release:ok": "node -e \"console.log('\\n✅ test:release passed')\"",
|
|
28
|
+
"test:release": "npm run build && npm run test:unit && npm run test:fixtures:all && npm run test:pack && npm run test:release:ok",
|
|
29
|
+
"prepublishOnly": "npm run build"
|
|
40
30
|
},
|
|
41
31
|
"author": " ",
|
|
42
32
|
"license": "MIT",
|
|
@@ -70,7 +60,8 @@
|
|
|
70
60
|
"terser": "^5.27.0",
|
|
71
61
|
"ts-node": "^10.9.2",
|
|
72
62
|
"typescript": "^5.4.3",
|
|
73
|
-
"vite": "^5.4.0"
|
|
63
|
+
"vite": "^5.4.0",
|
|
64
|
+
"vitest": "^2.1.8"
|
|
74
65
|
},
|
|
75
66
|
"packageManager": "pnpm@9.5.0+sha512.140036830124618d624a2187b50d04289d5a087f326c9edfc0ccd733d76c4f52c3a313d4fc148794a2a9d81553016004e6742e8cf850670268a7387fc220c903"
|
|
76
67
|
}
|