dependency-radar 0.2.0 → 0.3.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.
@@ -6,27 +6,228 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.runNpmLs = runNpmLs;
7
7
  const path_1 = __importDefault(require("path"));
8
8
  const utils_1 = require("../utils");
9
- async function runNpmLs(projectPath, tempDir) {
10
- const targetFile = path_1.default.join(tempDir, 'npm-ls.json');
9
+ // Normalize package-manager-specific list output into a shared dependency tree.
10
+ async function runNpmLs(projectPath, tempDir, tool = 'npm') {
11
+ const targetFile = path_1.default.join(tempDir, `${tool}-ls.json`);
11
12
  try {
12
- const result = await (0, utils_1.runCommand)('npm', ['ls', '--json', '--all'], { cwd: projectPath });
13
- let parsed;
14
- try {
15
- parsed = JSON.parse(result.stdout || '{}');
16
- }
17
- catch (err) {
18
- parsed = undefined;
19
- }
20
- if (parsed) {
21
- await (0, utils_1.writeJsonFile)(targetFile, parsed);
22
- return { ok: true, data: parsed, file: targetFile };
13
+ const { args, normalize } = buildLsCommand(tool);
14
+ const result = await (0, utils_1.runCommand)(tool, args, { cwd: projectPath });
15
+ const parsed = parseJsonOutput(result.stdout);
16
+ const normalized = normalize(parsed);
17
+ if (normalized) {
18
+ await (0, utils_1.writeJsonFile)(targetFile, normalized);
19
+ return { ok: true, data: normalized, file: targetFile };
23
20
  }
24
21
  await (0, utils_1.writeJsonFile)(targetFile, { stdout: result.stdout, stderr: result.stderr, code: result.code });
25
- const error = result.code && result.code !== 0 ? `npm ls exited with code ${result.code}` : 'Failed to parse npm ls output';
22
+ const error = result.code && result.code !== 0
23
+ ? `${tool} ls exited with code ${result.code}`
24
+ : `Failed to parse ${tool} ls output`;
26
25
  return { ok: false, error, file: targetFile };
27
26
  }
28
27
  catch (err) {
29
28
  await (0, utils_1.writeJsonFile)(targetFile, { error: String(err) });
30
- return { ok: false, error: `npm ls failed: ${String(err)}`, file: targetFile };
29
+ return { ok: false, error: `${tool} ls failed: ${String(err)}`, file: targetFile };
30
+ }
31
+ }
32
+ function buildLsCommand(tool) {
33
+ if (tool === 'pnpm') {
34
+ return {
35
+ args: ['list', '--json', '--depth', 'Infinity'],
36
+ normalize: normalizePnpmTree
37
+ };
38
+ }
39
+ if (tool === 'yarn') {
40
+ return {
41
+ args: ['list', '--json', '--depth', 'Infinity'],
42
+ normalize: normalizeYarnTree
43
+ };
44
+ }
45
+ return {
46
+ args: ['ls', '--json', '--all', '--long'],
47
+ normalize: normalizeNpmTree
48
+ };
49
+ }
50
+ function parseJsonOutput(raw) {
51
+ if (!raw)
52
+ return undefined;
53
+ try {
54
+ return JSON.parse(raw);
55
+ }
56
+ catch {
57
+ // Some tools emit JSONL (yarn). Parse best-effort into an array of objects.
58
+ const lines = raw.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
59
+ const parsed = [];
60
+ for (const line of lines) {
61
+ try {
62
+ parsed.push(JSON.parse(line));
63
+ }
64
+ catch {
65
+ // ignore non-JSON lines
66
+ }
67
+ }
68
+ return parsed.length > 0 ? parsed : undefined;
69
+ }
70
+ }
71
+ function normalizeNpmTree(data) {
72
+ if (!data || typeof data !== 'object')
73
+ return undefined;
74
+ const deps = data.dependencies && typeof data.dependencies === 'object' ? data.dependencies : {};
75
+ const normalized = { dependencies: {} };
76
+ for (const [name, node] of Object.entries(deps)) {
77
+ const normalizedNode = normalizeNpmNode(name, node);
78
+ if (normalizedNode)
79
+ normalized.dependencies[name] = normalizedNode;
80
+ }
81
+ return normalized;
82
+ }
83
+ function normalizeNpmNode(name, node) {
84
+ if (!node || typeof node !== 'object')
85
+ return undefined;
86
+ if (node.missing || node.extraneous)
87
+ return undefined;
88
+ const version = typeof (node === null || node === void 0 ? void 0 : node.version) === 'string' ? node.version.trim() : '';
89
+ if (!version || version === 'unknown' || version === 'missing' || version === 'invalid')
90
+ return undefined;
91
+ const out = { name, version, dependencies: {} };
92
+ if (typeof node.path === 'string' && node.path.trim()) {
93
+ out.path = node.path.trim();
94
+ }
95
+ if ((node === null || node === void 0 ? void 0 : node.dependencies) && typeof node.dependencies === 'object') {
96
+ for (const [childName, child] of Object.entries(node.dependencies)) {
97
+ const normalizedChild = normalizeNpmNode(childName, child);
98
+ if (normalizedChild)
99
+ out.dependencies[childName] = normalizedChild;
100
+ }
101
+ }
102
+ if ((node === null || node === void 0 ? void 0 : node.dev) !== undefined)
103
+ out.dev = Boolean(node.dev);
104
+ if (out.dependencies && Object.keys(out.dependencies).length === 0) {
105
+ delete out.dependencies;
106
+ }
107
+ return out;
108
+ }
109
+ function normalizePnpmTree(data) {
110
+ const roots = Array.isArray(data) ? data : [data];
111
+ const root = roots.find((entry) => entry && typeof entry === 'object');
112
+ if (!root || typeof root !== 'object')
113
+ return undefined;
114
+ const dependencies = collectPnpmDependencyMap(root);
115
+ return { dependencies };
116
+ }
117
+ function collectPnpmDependencyMap(node) {
118
+ const out = {};
119
+ const groups = ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'];
120
+ for (const group of groups) {
121
+ const value = node === null || node === void 0 ? void 0 : node[group];
122
+ if (!value)
123
+ continue;
124
+ if (Array.isArray(value)) {
125
+ for (const entry of value) {
126
+ if (!entry || typeof entry !== 'object')
127
+ continue;
128
+ const name = typeof entry.name === 'string' ? entry.name : undefined;
129
+ if (!name)
130
+ continue;
131
+ const normalized = normalizePnpmNode(name, entry);
132
+ if (normalized)
133
+ out[name] = normalized;
134
+ }
135
+ continue;
136
+ }
137
+ if (typeof value === 'object') {
138
+ for (const [name, entry] of Object.entries(value)) {
139
+ if (!entry || typeof entry !== 'object')
140
+ continue;
141
+ const normalized = normalizePnpmNode(name, entry);
142
+ if (normalized)
143
+ out[name] = normalized;
144
+ }
145
+ }
146
+ }
147
+ if (Object.keys(out).length > 0)
148
+ return out;
149
+ if ((node === null || node === void 0 ? void 0 : node.dependencies) && typeof node.dependencies === 'object') {
150
+ for (const [name, entry] of Object.entries(node.dependencies)) {
151
+ const normalized = normalizePnpmNode(name, entry);
152
+ if (normalized)
153
+ out[name] = normalized;
154
+ }
155
+ }
156
+ return out;
157
+ }
158
+ function normalizePnpmNode(name, node) {
159
+ const version = typeof (node === null || node === void 0 ? void 0 : node.version) === 'string' ? node.version.trim() : '';
160
+ if (!version || version === 'unknown' || version === 'missing' || version === 'invalid')
161
+ return undefined;
162
+ const out = { name, version, dependencies: {} };
163
+ const childMap = collectPnpmDependencyMap(node);
164
+ if (Object.keys(childMap).length > 0) {
165
+ out.dependencies = childMap;
166
+ }
167
+ else {
168
+ delete out.dependencies;
169
+ }
170
+ if ((node === null || node === void 0 ? void 0 : node.dev) !== undefined)
171
+ out.dev = Boolean(node.dev);
172
+ return out;
173
+ }
174
+ function normalizeYarnTree(data) {
175
+ const treePayload = resolveYarnTreePayload(data);
176
+ if (!treePayload || !Array.isArray(treePayload.trees))
177
+ return undefined;
178
+ const out = { dependencies: {} };
179
+ for (const node of treePayload.trees) {
180
+ const parsed = normalizeYarnNode(node);
181
+ if (parsed)
182
+ out.dependencies[parsed.name] = parsed.node;
183
+ }
184
+ return out;
185
+ }
186
+ function resolveYarnTreePayload(data) {
187
+ var _a;
188
+ if (!data)
189
+ return undefined;
190
+ if ((_a = data === null || data === void 0 ? void 0 : data.data) === null || _a === void 0 ? void 0 : _a.trees)
191
+ return data.data;
192
+ if (data === null || data === void 0 ? void 0 : data.trees)
193
+ return data;
194
+ if (Array.isArray(data)) {
195
+ const treeItem = data.find((item) => { var _a; return item && typeof item === 'object' && item.type === 'tree' && ((_a = item.data) === null || _a === void 0 ? void 0 : _a.trees); });
196
+ return treeItem === null || treeItem === void 0 ? void 0 : treeItem.data;
197
+ }
198
+ return undefined;
199
+ }
200
+ function normalizeYarnNode(node) {
201
+ if (!node || typeof node !== 'object')
202
+ return undefined;
203
+ const label = typeof node.name === 'string' ? node.name : '';
204
+ const parsed = splitYarnLabel(label);
205
+ if (!parsed.version || parsed.version === 'unknown' || parsed.version === 'missing' || parsed.version === 'invalid')
206
+ return undefined;
207
+ const out = { name: parsed.name, version: parsed.version, dependencies: {} };
208
+ if (Array.isArray(node.children)) {
209
+ for (const child of node.children) {
210
+ const parsedChild = normalizeYarnNode(child);
211
+ if (parsedChild) {
212
+ out.dependencies[parsedChild.name] = parsedChild.node;
213
+ }
214
+ }
215
+ }
216
+ if (out.dependencies && Object.keys(out.dependencies).length === 0) {
217
+ delete out.dependencies;
218
+ }
219
+ return { name: parsed.name, node: out };
220
+ }
221
+ function splitYarnLabel(label) {
222
+ if (!label)
223
+ return { name: 'unknown', version: 'unknown' };
224
+ const lastAt = label.lastIndexOf('@');
225
+ if (lastAt <= 0)
226
+ return { name: label, version: 'unknown' };
227
+ const name = label.slice(0, lastAt);
228
+ let version = label.slice(lastAt + 1);
229
+ if (version.startsWith('npm:')) {
230
+ version = version.slice(4);
31
231
  }
232
+ return { name, version: version || 'unknown' };
32
233
  }
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runPackageOutdated = runPackageOutdated;
7
+ const path_1 = __importDefault(require("path"));
8
+ const utils_1 = require("../utils");
9
+ function normalizeOutdatedOutput(tool, data) {
10
+ if (!data)
11
+ return undefined;
12
+ if (typeof data === "object" && !Array.isArray(data) && !data.type)
13
+ return data;
14
+ if (Array.isArray(data)) {
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
32
+ const entries = Array.isArray(data) ? data : [data];
33
+ 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
+ const table = (tableEntry === null || tableEntry === void 0 ? void 0 : tableEntry.data) || tableEntry;
35
+ if (table && Array.isArray(table.head) && Array.isArray(table.body)) {
36
+ const head = table.head.map((h) => String(h).toLowerCase());
37
+ const idx = {
38
+ name: head.findIndex((h) => h.includes("package") || h.includes("name")),
39
+ current: head.findIndex((h) => h.includes("current")),
40
+ latest: head.findIndex((h) => h.includes("latest")),
41
+ wanted: head.findIndex((h) => h.includes("wanted")),
42
+ };
43
+ const out = {};
44
+ for (const row of table.body) {
45
+ if (!Array.isArray(row))
46
+ continue;
47
+ const name = idx.name >= 0 ? String(row[idx.name]) : "";
48
+ if (!name)
49
+ continue;
50
+ out[name] = {
51
+ current: idx.current >= 0 ? String(row[idx.current]) : undefined,
52
+ latest: idx.latest >= 0 ? String(row[idx.latest]) : undefined,
53
+ wanted: idx.wanted >= 0 ? String(row[idx.wanted]) : undefined,
54
+ };
55
+ }
56
+ return Object.keys(out).length > 0 ? out : undefined;
57
+ }
58
+ if (tool === "pnpm" && data.outdated)
59
+ return data.outdated;
60
+ return undefined;
61
+ }
62
+ function buildOutdatedCommand(tool) {
63
+ if (tool === "pnpm") {
64
+ return {
65
+ cmd: "pnpm",
66
+ args: ["outdated", "--json"],
67
+ lockFiles: ["pnpm-lock.yaml"],
68
+ };
69
+ }
70
+ if (tool === "yarn") {
71
+ return {
72
+ cmd: "yarn",
73
+ args: ["outdated", "--json"],
74
+ lockFiles: ["yarn.lock"],
75
+ };
76
+ }
77
+ return {
78
+ cmd: "npm",
79
+ args: ["outdated", "--json", "--long"],
80
+ lockFiles: ["package-lock.json", "npm-shrinkwrap.json"],
81
+ };
82
+ }
83
+ async function runPackageOutdated(projectPath, tempDir, tool) {
84
+ const targetFile = path_1.default.join(tempDir, `${tool}-outdated.json`);
85
+ try {
86
+ const { cmd, args, lockFiles } = buildOutdatedCommand(tool);
87
+ const lockDir = await (0, utils_1.findLockDir)(projectPath, lockFiles);
88
+ const cwd = lockDir || projectPath;
89
+ const result = await (0, utils_1.runCommand)(cmd, args, { cwd });
90
+ const parsed = (0, utils_1.parseJsonOutput)(result.stdout);
91
+ const normalized = normalizeOutdatedOutput(tool, parsed);
92
+ if (normalized && typeof normalized === "object") {
93
+ await (0, utils_1.writeJsonFile)(targetFile, normalized);
94
+ return { ok: true, data: normalized, file: targetFile };
95
+ }
96
+ await (0, utils_1.writeJsonFile)(targetFile, {
97
+ stdout: result.stdout,
98
+ stderr: result.stderr,
99
+ code: result.code,
100
+ });
101
+ return {
102
+ ok: false,
103
+ error: `Failed to parse ${tool} outdated output`,
104
+ file: targetFile,
105
+ };
106
+ }
107
+ catch (err) {
108
+ await (0, utils_1.writeJsonFile)(targetFile, { error: String(err) });
109
+ return {
110
+ ok: false,
111
+ error: `${tool} outdated failed: ${String(err)}`,
112
+ file: targetFile,
113
+ };
114
+ }
115
+ }
package/dist/utils.js CHANGED
@@ -4,7 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.runCommand = runCommand;
7
- exports.delay = delay;
8
7
  exports.getDependencyRadarVersion = getDependencyRadarVersion;
9
8
  exports.ensureDir = ensureDir;
10
9
  exports.writeJsonFile = writeJsonFile;
@@ -15,8 +14,11 @@ exports.readPackageJson = readPackageJson;
15
14
  exports.findBin = findBin;
16
15
  exports.licenseRiskLevel = licenseRiskLevel;
17
16
  exports.vulnRiskLevel = vulnRiskLevel;
18
- exports.maintenanceRisk = maintenanceRisk;
17
+ exports.resolvePackageJsonPath = resolvePackageJsonPath;
19
18
  exports.readLicenseFromPackageJson = readLicenseFromPackageJson;
19
+ exports.readLicenseFromPackageDir = readLicenseFromPackageDir;
20
+ exports.findLockDir = findLockDir;
21
+ exports.parseJsonOutput = parseJsonOutput;
20
22
  const child_process_1 = require("child_process");
21
23
  const fs_1 = __importDefault(require("fs"));
22
24
  const promises_1 = __importDefault(require("fs/promises"));
@@ -41,9 +43,6 @@ function runCommand(command, args, options = {}) {
41
43
  });
42
44
  });
43
45
  }
44
- function delay(ms) {
45
- return new Promise((resolve) => setTimeout(resolve, ms));
46
- }
47
46
  function getDependencyRadarVersion() {
48
47
  try {
49
48
  const pkgPath = path_1.default.join(__dirname, '..', 'package.json');
@@ -118,32 +117,133 @@ function vulnRiskLevel(counts) {
118
117
  return 'amber';
119
118
  return 'green';
120
119
  }
121
- function maintenanceRisk(lastPublished) {
122
- if (!lastPublished)
123
- return 'unknown';
124
- const last = new Date(lastPublished).getTime();
125
- if (Number.isNaN(last))
126
- return 'unknown';
127
- const now = Date.now();
128
- const months = (now - last) / (1000 * 60 * 60 * 24 * 30);
129
- if (months <= 12)
130
- return 'green';
131
- if (months <= 36)
132
- return 'amber';
133
- return 'red';
120
+ const pnpmStoreEntriesCache = new Map();
121
+ const pnpmStoreIndexCache = new Map();
122
+ function encodePnpmStoreName(pkgName) {
123
+ if (pkgName.startsWith('@')) {
124
+ const parts = pkgName.slice(1).split('/');
125
+ if (parts.length >= 2) {
126
+ return `@${parts[0]}+${parts.slice(1).join('+')}`;
127
+ }
128
+ }
129
+ return pkgName.replace(/\//g, '+');
130
+ }
131
+ async function getPnpmStoreEntries(pnpmDir) {
132
+ if (pnpmStoreEntriesCache.has(pnpmDir))
133
+ return pnpmStoreEntriesCache.get(pnpmDir);
134
+ try {
135
+ const entries = await promises_1.default.readdir(pnpmDir, { withFileTypes: true });
136
+ const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
137
+ pnpmStoreEntriesCache.set(pnpmDir, dirs);
138
+ return dirs;
139
+ }
140
+ catch {
141
+ pnpmStoreEntriesCache.set(pnpmDir, []);
142
+ return [];
143
+ }
144
+ }
145
+ async function getPnpmStoreIndex(pnpmDir) {
146
+ if (pnpmStoreIndexCache.has(pnpmDir))
147
+ return pnpmStoreIndexCache.get(pnpmDir);
148
+ const entries = await getPnpmStoreEntries(pnpmDir);
149
+ const index = new Map();
150
+ for (const entry of entries) {
151
+ const prefix = entry.split('(')[0];
152
+ if (!prefix)
153
+ continue;
154
+ if (!index.has(prefix))
155
+ index.set(prefix, []);
156
+ index.get(prefix).push(entry);
157
+ }
158
+ pnpmStoreIndexCache.set(pnpmDir, index);
159
+ return index;
160
+ }
161
+ async function resolvePnpmPackageJsonPath(pkgName, version, resolvePaths) {
162
+ if (!version || version.startsWith('link:') || version.startsWith('workspace:') || version.startsWith('file:')) {
163
+ return undefined;
164
+ }
165
+ const encoded = encodePnpmStoreName(pkgName);
166
+ const prefix = `${encoded}@${version}`;
167
+ for (const basePath of resolvePaths) {
168
+ const pnpmDir = path_1.default.join(basePath, 'node_modules', '.pnpm');
169
+ if (!(await pathExists(pnpmDir)))
170
+ continue;
171
+ const index = await getPnpmStoreIndex(pnpmDir);
172
+ const matches = index.get(prefix) || [];
173
+ for (const entry of matches) {
174
+ const candidate = path_1.default.join(pnpmDir, entry, 'node_modules', pkgName, 'package.json');
175
+ if (await pathExists(candidate))
176
+ return candidate;
177
+ }
178
+ }
179
+ return undefined;
180
+ }
181
+ async function resolvePackageJsonPath(pkgName, resolvePaths, version) {
182
+ if (version) {
183
+ const pnpmResolved = await resolvePnpmPackageJsonPath(pkgName, version, resolvePaths);
184
+ if (pnpmResolved)
185
+ return pnpmResolved;
186
+ }
187
+ try {
188
+ const direct = require.resolve(path_1.default.join(pkgName, 'package.json'), { paths: resolvePaths });
189
+ if (direct)
190
+ return direct;
191
+ }
192
+ catch {
193
+ // fall through to entry resolution
194
+ }
195
+ let entryPath;
196
+ try {
197
+ entryPath = require.resolve(pkgName, { paths: resolvePaths });
198
+ }
199
+ catch {
200
+ return undefined;
201
+ }
202
+ let current = path_1.default.dirname(entryPath);
203
+ const root = path_1.default.parse(current).root;
204
+ while (true) {
205
+ const candidate = path_1.default.join(current, 'package.json');
206
+ if (await pathExists(candidate))
207
+ return candidate;
208
+ const parent = path_1.default.dirname(current);
209
+ if (parent === current || parent === root)
210
+ break;
211
+ current = parent;
212
+ }
213
+ return undefined;
214
+ }
215
+ async function readLicenseFromPackageJson(pkgName, resolvePaths, version) {
216
+ try {
217
+ const pkgJsonPath = await resolvePackageJsonPath(pkgName, resolvePaths, version);
218
+ if (!pkgJsonPath)
219
+ return undefined;
220
+ return await readLicenseFromPackageDir(path_1.default.dirname(pkgJsonPath));
221
+ }
222
+ catch (err) {
223
+ return undefined;
224
+ }
134
225
  }
135
- async function readLicenseFromPackageJson(pkgName, projectPath) {
226
+ async function readLicenseFromPackageDir(packageDir) {
136
227
  try {
137
- const pkgJsonPath = require.resolve(path_1.default.join(pkgName, 'package.json'), { paths: [projectPath] });
138
- const pkgRaw = await promises_1.default.readFile(pkgJsonPath, 'utf8');
228
+ const pkgPath = path_1.default.join(packageDir, 'package.json');
229
+ const pkgRaw = await promises_1.default.readFile(pkgPath, 'utf8');
139
230
  const pkg = JSON.parse(pkgRaw);
140
231
  const license = pkg.license || (Array.isArray(pkg.licenses) ? pkg.licenses.map((l) => (typeof l === 'string' ? l : l === null || l === void 0 ? void 0 : l.type)).filter(Boolean).join(' OR ') : undefined);
141
- const licenseFile = await findLicenseFile(path_1.default.dirname(pkgJsonPath));
232
+ const licenseFile = await findLicenseFile(packageDir);
142
233
  if (!license && !licenseFile)
143
234
  return undefined;
144
- return { license, licenseFile };
235
+ let licenseText;
236
+ if (licenseFile) {
237
+ try {
238
+ licenseText = await promises_1.default.readFile(licenseFile, 'utf8');
239
+ }
240
+ catch {
241
+ licenseText = undefined;
242
+ }
243
+ }
244
+ return { license, licenseFile, licenseText };
145
245
  }
146
- catch (err) {
246
+ catch {
147
247
  return undefined;
148
248
  }
149
249
  }
@@ -162,3 +262,38 @@ async function findLicenseFile(dir) {
162
262
  return undefined;
163
263
  }
164
264
  }
265
+ async function findLockDir(startPath, lockFiles) {
266
+ let current = startPath;
267
+ while (true) {
268
+ for (const file of lockFiles) {
269
+ if (await pathExists(path_1.default.join(current, file))) {
270
+ return current;
271
+ }
272
+ }
273
+ const parent = path_1.default.dirname(current);
274
+ if (parent === current)
275
+ break;
276
+ current = parent;
277
+ }
278
+ return undefined;
279
+ }
280
+ function parseJsonOutput(raw) {
281
+ if (!raw)
282
+ return undefined;
283
+ try {
284
+ return JSON.parse(raw);
285
+ }
286
+ catch {
287
+ const lines = raw.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
288
+ const parsed = [];
289
+ for (const line of lines) {
290
+ try {
291
+ parsed.push(JSON.parse(line));
292
+ }
293
+ catch {
294
+ // ignore non-JSON lines
295
+ }
296
+ }
297
+ return parsed.length > 0 ? parsed : undefined;
298
+ }
299
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dependency-radar",
3
- "version": "0.2.0",
3
+ "version": "0.3.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": {
@@ -11,11 +11,30 @@
11
11
  ],
12
12
  "scripts": {
13
13
  "dev:report": "cd report-ui && npx vite",
14
+ "build:spdx": "ts-node scripts/build-spdx.ts",
14
15
  "build:report-ui": "cd report-ui && npx vite build",
15
16
  "build:report": "npm run build:report-ui && npx ts-node scripts/build-report.ts",
16
- "build": "npm run build:report && tsc",
17
+ "build": "npm run build:spdx && npm run build:report && tsc",
17
18
  "dev": "ts-node src/cli.ts scan",
18
19
  "scan": "node dist/cli.js scan",
20
+ "fixtures:install:npm": "cd test-fixtures/npm-basic && npm install",
21
+ "fixtures:install:npm-heavy": "cd test-fixtures/npm-heavy && npm install --force",
22
+ "fixtures:install:pnpm": "cd test-fixtures/pnpm-workspace && pnpm install",
23
+ "fixtures:install:pnpm-hoisted": "cd test-fixtures/pnpm-workspace-hoisted && pnpm install",
24
+ "fixtures:install:yarn": "cd test-fixtures/yarn-workspace && yarn install",
25
+ "fixtures:install:yarn-berry": "cd test-fixtures/yarn-berry-workspace && yarn install",
26
+ "fixtures:install:optional": "cd test-fixtures/optional-deps && npm install",
27
+ "fixtures:scan:npm": "npm run dev -- --project test-fixtures/npm-basic --out test-fixtures/npm-basic/dependency-radar.html --json --keep-temp",
28
+ "fixtures:scan:npm-heavy": "npm run dev -- --project test-fixtures/npm-heavy --out test-fixtures/npm-heavy/dependency-radar.html --json --keep-temp",
29
+ "fixtures:scan:pnpm": "npm run dev -- --project test-fixtures/pnpm-workspace --out test-fixtures/pnpm-workspace/dependency-radar.html --json --keep-temp",
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",
19
38
  "prepublishOnly": "npm run build",
20
39
  "test": "echo \"Error: no test specified\" && exit 1"
21
40
  },
@@ -52,5 +71,6 @@
52
71
  "ts-node": "^10.9.2",
53
72
  "typescript": "^5.4.3",
54
73
  "vite": "^5.4.0"
55
- }
74
+ },
75
+ "packageManager": "pnpm@9.5.0+sha512.140036830124618d624a2187b50d04289d5a087f326c9edfc0ccd733d76c4f52c3a313d4fc148794a2a9d81553016004e6742e8cf850670268a7387fc220c903"
56
76
  }