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.
package/dist/report.js CHANGED
@@ -13,17 +13,39 @@ async function renderReport(data, outputPath) {
13
13
  await promises_1.default.writeFile(outputPath, html, 'utf8');
14
14
  }
15
15
  function buildHtml(data) {
16
- var _a, _b, _c, _d;
16
+ var _a, _b, _c;
17
17
  const json = JSON.stringify(data).replace(/</g, '\\u003c');
18
- const gitBranchHtml = data.gitBranch ? `<br/>Branch: <strong>${escapeHtml(data.gitBranch)}</strong>` : '';
19
- const runtimeVersion = ((_b = (_a = data.environment) === null || _a === void 0 ? void 0 : _a.node) === null || _b === void 0 ? void 0 : _b.runtimeVersion)
20
- ? data.environment.node.runtimeVersion.replace(/^v/, '')
21
- : 'unknown';
22
- const minRequiredMajor = (_d = (_c = data.environment) === null || _c === void 0 ? void 0 : _c.node) === null || _d === void 0 ? void 0 : _d.minRequiredMajor;
23
- const nodeRequirement = minRequiredMajor !== undefined ? ` · dependency engines require ≥${minRequiredMajor}` : '';
24
- const nodeBlockHtml = minRequiredMajor !== undefined
25
- ? `Node: run on ${escapeHtml(runtimeVersion)}${nodeRequirement}<br/><span class="header-disclaimer">Derived from declared dependency engine ranges; does not guarantee runtime compatibility.</span>`
26
- : `Node: run on ${escapeHtml(runtimeVersion)}`;
18
+ // Format the generated date
19
+ let formattedDate = data.generatedAt;
20
+ try {
21
+ const date = new Date(data.generatedAt);
22
+ if (Number.isNaN(date.getTime())) {
23
+ // Keep the original if parsing fails
24
+ }
25
+ else {
26
+ formattedDate = new Intl.DateTimeFormat(undefined, {
27
+ day: 'numeric',
28
+ month: 'short',
29
+ year: 'numeric',
30
+ hour: '2-digit',
31
+ minute: '2-digit'
32
+ }).format(date);
33
+ }
34
+ }
35
+ catch {
36
+ // Keep the original if parsing fails
37
+ }
38
+ // Build conditional meta items
39
+ const runtimeVersion = ((_a = data.environment) === null || _a === void 0 ? void 0 : _a.runtimeVersion)
40
+ ? data.environment.runtimeVersion.replace(/^v/, '')
41
+ : null;
42
+ const minRequiredMajor = (_b = data.environment) === null || _b === void 0 ? void 0 : _b.minRequiredMajor;
43
+ const nodeVersionText = runtimeVersion
44
+ ? `${runtimeVersion}${minRequiredMajor && minRequiredMajor > 0 ? ` (requires ≥${minRequiredMajor})` : ''}`
45
+ : null;
46
+ const nodeDisclaimer = minRequiredMajor && minRequiredMajor > 0
47
+ ? 'Node requirement derived from dependency engine ranges.'
48
+ : null;
27
49
  return `<!doctype html>
28
50
  <html lang="en">
29
51
  <head>
@@ -43,54 +65,21 @@ ${report_assets_1.CSS_CONTENT}
43
65
  viewBox="0 0 1024 1024" class="logo">
44
66
  <defs>
45
67
  <style>
46
- .st0, .st1 {
47
- fill: #ff8000;
48
- }
49
-
50
- .st2 {
51
- fill: #191772;
52
- }
53
-
54
- .st2, .st3, .st4, .st5 {
55
- stroke: #55fffa;
56
- stroke-miterlimit: 10;
57
- stroke-width: 8px;
58
- }
59
-
60
- .st6 {
61
- fill: #0a0a33;
62
- }
63
-
64
- .st3 {
65
- fill: #161466;
66
- }
67
-
68
- .st7 {
69
- fill: url(#linear-gradient);
70
- }
71
-
72
- .st8, .st1 {
73
- opacity: .4;
74
- }
75
-
76
- .st8, .st9 {
77
- fill: red;
78
- }
79
-
80
- .st4 {
81
- fill: #1c197f;
82
- }
83
-
84
- .st10 {
85
- fill: #55fffa;
86
- }
87
-
88
- .st5 {
89
- fill: #141259;
90
- }
68
+ .st0, .st1 { fill: #ff8000; }
69
+ .st2 { fill: #191772; }
70
+ .st2, .st3, .st4, .st5 { stroke: #55fffa; stroke-miterlimit: 10; stroke-width: 6px; }
71
+ .st6 { fill: #0a0a33; }
72
+ .st7 { opacity: .3; }
73
+ .st7, .st8 { fill: #55fffa; }
74
+ .st3 { fill: #161466; }
75
+ .st9 { fill: url(#linear-gradient); }
76
+ .st10, .st1, .st11 { opacity: .4; }
77
+ .st10, .st12 { fill: red; }
78
+ .st4 { fill: #1c197f; }
79
+ .st13, .st11 { fill: #00be00; }
80
+ .st5 { fill: #141259; }
91
81
  </style>
92
- <linearGradient id="linear-gradient" x1="225" y1="287" x2="831.3" y2="287"
93
- gradientUnits="userSpaceOnUse">
82
+ <linearGradient id="linear-gradient" x1="225" y1="287" x2="831.3" y2="287" gradientUnits="userSpaceOnUse">
94
83
  <stop offset=".4" stop-color="#55fffa" stop-opacity="0" />
95
84
  <stop offset="1" stop-color="#55fffa" stop-opacity=".5" />
96
85
  </linearGradient>
@@ -100,31 +89,50 @@ ${report_assets_1.CSS_CONTENT}
100
89
  <circle class="st3" cx="512" cy="512" r="325" />
101
90
  <circle class="st2" cx="512" cy="512" r="200" />
102
91
  <circle class="st4" cx="512" cy="512" r="80" />
103
- <path class="st7"
104
- d="M517.7,512l313.6-317.1c-81.5-82.1-194.5-132.9-319.3-132.9s-209.1,38.8-287,103.4l292.7,346.6Z" />
105
- <rect class="st10" x="664.2" y="129.5" width="16.7" height="450"
106
- transform="translate(447.6 -371.7) rotate(45)" />
107
- <circle class="st8" cx="800" cy="662" r="50" />
108
- <circle class="st9" cx="800" cy="662" r="25" />
109
- <circle class="st8" cx="256.9" cy="315.2" r="50" />
110
- <circle class="st9" cx="256.9" cy="315.2" r="25" />
92
+ <path class="st9" d="M517.7,512l313.6-317.1c-81.5-82.1-194.5-132.9-319.3-132.9s-209.1,38.8-287,103.4l292.7,346.6Z" />
93
+ <path class="st7" d="M891.9,618.4c-64.1,245.1-337.5,365.9-562.6,250.5,0,0-14.3-7.7-14.3-7.7-5.8-3.4-11.7-7.1-17.4-10.5-5.3-3.5-11.7-7.9-16.9-11.4-15-10.9-30.5-23.6-43.9-36.5-5.7-5.3-11.8-11.8-17.3-17.4-37-40.1-66.2-88-84.1-139.6-38.9-110.4-27.2-234.3,31.1-335.8,37.6-65.3,93.5-119.6,159.6-155.7,0,0,15-7.7,15-7.7,4.5-2.4,10.7-4.9,15.3-7.1,2.1-.9,5.6-2.6,7.7-3.4,3.9-1.5,11.8-4.7,15.7-6.2,4.7-1.8,11.1-3.8,15.9-5.4,0,0,4-1.3,4-1.3,6.7-1.8,13.5-4,20.3-5.7,116.4-29.7,241.1-7.4,339.7,61.6,18.6,13.1,36.2,27.6,52.6,43.4l-42.9,42.9c-17.1-17.4-35.9-33.2-55.9-47.1-4.7-2.9-13.9-9.3-18.6-11.7-3.1-1.8-8.1-4.7-11.1-6.4-4-2-10.7-5.5-14.7-7.6-5.1-2.4-11.6-5.3-16.7-7.6-5.7-2.3-11.4-4.5-17.1-6.8-60.2-21.9-126.3-27.5-189.3-16.1,0,0-14.6,2.9-14.6,2.9-6,1.4-12,3.1-18,4.5-5.5,1.7-12.3,3.7-17.8,5.5-4.4,1.5-11.4,4.1-15.8,5.7-3,1.2-9,3.7-12,5-43.3,18.6-83.4,46-116.3,79.8-105.7,106.7-134.5,269.3-74.7,406.7,49.8,114.5,155.4,198.1,278.4,219.7,94.7,17.4,195.6-3.1,276-56.1,76.9-50.4,135.7-128.5,160.8-217.2h0Z" />
94
+ <path class="st7" d="M770.9,586c-20.5,84.7-85.9,156.4-167.8,185.8-103.6,37.8-221.1,7.9-294.5-74.2-73.1-80.1-91.2-199-47.6-298,28-63.9,80.3-116.6,144.1-144.9,91-41.1,199.6-30.9,281.6,26,13.1,9.1,25.5,19.2,37,30.2,0,0-42.9,42.9-42.9,42.9-21.5-22.4-47.3-40.6-75.7-53.1-18.3-7.8-37.9-13.6-57.6-16.7-38.4-6-78.3-2.5-115,10.4-34,11.7-65.3,31.6-90.7,57.1-89.3,88.8-94.6,233.1-14.3,329.6,37,45.2,90.4,76.8,147.9,87.3,130.1,24.7,258-55.6,295.4-182.4h0Z" />
95
+ <path class="st7" d="M649.9,553.6c-13.4,61.2-70.2,107.5-133.1,109.4-48.9,2.2-96.8-21.6-125.4-61.3-75.1-106.5,4.4-248.5,133.3-247.2,40.8.5,80.9,16.7,110.7,44.7,0,0-42.9,42.9-42.9,42.9-11.9-13.1-27-23.5-43.8-29.5-28.9-10.5-62-8.3-89.4,5.9-61.5,31.6-81.6,109.8-45.9,168.4,17.8,29.7,48.4,51.3,82.3,58.2,66.3,14.4,134-26.8,154.1-91.6h0Z" />
96
+ <rect class="st8" x="664.2" y="129.5" width="16.7" height="450" transform="translate(447.6 -371.7) rotate(45)" />
97
+ <circle class="st8" cx="512" cy="512" r="32" />
98
+ <circle class="st10" cx="800" cy="662" r="50" />
99
+ <circle class="st12" cx="800" cy="662" r="25" />
100
+ <circle class="st10" cx="256.9" cy="315.2" r="50" />
101
+ <circle class="st12" cx="256.9" cy="315.2" r="25" />
111
102
  <circle class="st1" cx="400.1" cy="673" r="50" />
112
103
  <circle class="st0" cx="400.1" cy="673" r="25" />
104
+ <circle class="st1" cx="578.1" cy="135" r="50" />
105
+ <circle class="st0" cx="578.1" cy="135" r="25" />
106
+ <circle class="st11" cx="187" cy="569.5" r="50" />
107
+ <circle class="st13" cx="187" cy="569.5" r="25" />
108
+ <circle class="st11" cx="592" cy="894.1" r="50" />
109
+ <circle class="st13" cx="592" cy="894.1" r="25" />
110
+ <circle class="st11" cx="512" cy="314" r="50" />
111
+ <circle class="st13" cx="512" cy="314" r="25" />
112
+ <circle class="st10" cx="329" cy="854.6" r="50" />
113
+ <circle class="st12" cx="329" cy="854.6" r="25" />
113
114
  </svg>
114
115
  <div class="header-text">
115
116
  <h1>Dependency Radar</h1>
116
- <p class="header-meta">
117
- Project: <strong id="project-path">${escapeHtml(data.projectPath)}</strong>${gitBranchHtml}<br id="git-branch-br" /><span id="git-branch-text"></span>
118
- <span id="node-block">${nodeBlockHtml}</span><br/>
119
- Generated: <span id="formatted-date">${data.generatedAt}</span>
120
- </p>
117
+ <div class="header-meta">
118
+ <span class="meta-item"><span class="meta-label">Project</span> <strong id="project-path">${escapeHtml(data.project.projectDir)}</strong></span>
119
+ ${((_c = data.git) === null || _c === void 0 ? void 0 : _c.branch) ? `<span class="meta-item"><span class="meta-label">Branch</span> <strong>${escapeHtml(data.git.branch)}</strong></span>` : ''}
120
+ ${nodeVersionText ? `<span class="meta-item"><span class="meta-label">Node</span> <strong>${escapeHtml(nodeVersionText)}</strong></span>` : ''}
121
+ <span class="meta-item"><span class="meta-label">Generated</span> <strong id="formatted-date">${escapeHtml(formattedDate)}</strong></span>
122
+ ${nodeDisclaimer ? `<span class="header-disclaimer">${escapeHtml(nodeDisclaimer)}</span>` : ''}
123
+ </div>
121
124
  </div>
122
125
  </div>
123
126
  <div class="cta-section">
124
127
  <a href="https://dependency-radar.com" class="cta-link" target="_blank" rel="noopener">
125
- Get risk analysis & summary
128
+ Get full analysis report
126
129
  <span class="cta-arrow">→</span>
127
130
  </a>
131
+ <div class="cta-benefits">
132
+ <span>AI-powered risk summary for stakeholders</span>
133
+ <span>Charts & assets for presentations</span>
134
+ <span>Actionable upgrade recommendations</span>
135
+ </div>
128
136
  <div class="cta-text">dependency-radar.com</div>
129
137
  </div>
130
138
  </div>
@@ -150,12 +158,13 @@ ${report_assets_1.CSS_CONTENT}
150
158
  </div>
151
159
 
152
160
  <div class="filter-group">
153
- <span class="filter-label">Runtime</span>
161
+ <span class="filter-label">Scope</span>
154
162
  <select id="runtime-filter">
155
163
  <option value="all">All</option>
156
164
  <option value="runtime">Runtime</option>
157
- <option value="build-time">Build-time</option>
158
- <option value="dev-only">Dev-only</option>
165
+ <option value="dev">Dev</option>
166
+ <option value="optional">Optional</option>
167
+ <option value="peer">Peer</option>
159
168
  </select>
160
169
  </div>
161
170
 
@@ -165,7 +174,6 @@ ${report_assets_1.CSS_CONTENT}
165
174
  <option value="name">Name</option>
166
175
  <option value="severity">Severity</option>
167
176
  <option value="depth">Depth</option>
168
- <option value="size">Size</option>
169
177
  </select>
170
178
  <button type="button" class="sort-direction-btn" id="sort-direction" title="Toggle sort direction">↑</button>
171
179
  </div>
@@ -180,11 +188,6 @@ ${report_assets_1.CSS_CONTENT}
180
188
  Has vulnerabilities
181
189
  </label>
182
190
 
183
- <label class="checkbox-filter">
184
- <input type="checkbox" id="unused-only" />
185
- Not statically imported
186
- </label>
187
-
188
191
  <div class="theme-toggle">
189
192
  <span class="theme-toggle-label">Theme</span>
190
193
  <div class="theme-switch" id="theme-switch" title="Toggle dark/light mode"></div>
@@ -227,13 +230,16 @@ ${report_assets_1.CSS_CONTENT}
227
230
  </div>
228
231
  </div>
229
232
 
230
- ${renderToolErrors(data)}
231
-
232
233
  <!-- Main Content -->
233
234
  <main class="main-content">
234
235
  <div class="results-summary" id="results-summary"></div>
235
236
  <div id="dependency-list" class="dependency-grid"></div>
236
237
  </main>
238
+
239
+ <footer class="report-footer">
240
+ <p><strong>About this report</strong></p>
241
+ <p>Dependency Radar does not perform malware scanning or security auditing. It surfaces factual signals from dependency metadata, known vulnerabilities (npm audit), dependency graphs, and install-time behaviour to support informed review.</p>
242
+ </footer>
237
243
 
238
244
  <script type="application/json" id="radar-data">${json}</script>
239
245
  <script>
@@ -245,10 +251,3 @@ ${report_assets_1.JS_CONTENT}
245
251
  function escapeHtml(str) {
246
252
  return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
247
253
  }
248
- function renderToolErrors(data) {
249
- const entries = Object.entries(data.toolErrors || {});
250
- if (!entries.length)
251
- return '';
252
- const list = entries.map(([tool, err]) => `<div><strong>${escapeHtml(tool)}:</strong> ${escapeHtml(err)}</div>`).join('');
253
- return `<div class="tool-errors"><strong>Some tools failed:</strong>${list}</div>`;
254
- }
@@ -19,6 +19,7 @@ async function runImportGraph(projectPath, tempDir) {
19
19
  const files = await collectSourceFiles(entry);
20
20
  const fileGraph = {};
21
21
  const packageGraph = {};
22
+ const packageCounts = {};
22
23
  const unresolvedImports = [];
23
24
  for (const file of files) {
24
25
  const rel = normalizePath(projectPath, file);
@@ -27,9 +28,10 @@ async function runImportGraph(projectPath, tempDir) {
27
28
  const resolved = await resolveImports(imports, path_1.default.dirname(file), projectPath);
28
29
  fileGraph[rel] = resolved.files;
29
30
  packageGraph[rel] = resolved.packages;
31
+ packageCounts[rel] = resolved.packageCounts;
30
32
  unresolvedImports.push(...resolved.unresolved.map((spec) => ({ importer: rel, specifier: spec })));
31
33
  }
32
- const output = { files: fileGraph, packages: packageGraph, unresolvedImports };
34
+ const output = { files: fileGraph, packages: packageGraph, packageCounts, unresolvedImports };
33
35
  await (0, utils_1.writeJsonFile)(targetFile, output);
34
36
  return { ok: true, data: output, file: targetFile };
35
37
  }
@@ -62,7 +64,7 @@ async function collectSourceFiles(rootDir) {
62
64
  return files;
63
65
  }
64
66
  function extractImports(content) {
65
- const matches = new Set();
67
+ const matches = [];
66
68
  const patterns = [
67
69
  /\bimport\s+(?:[^'"]+from\s+)?['"]([^'"]+)['"]/g,
68
70
  /\bexport\s+(?:[^'"]+from\s+)?['"]([^'"]+)['"]/g,
@@ -73,14 +75,15 @@ function extractImports(content) {
73
75
  let match;
74
76
  while ((match = pattern.exec(content)) !== null) {
75
77
  if (match[1])
76
- matches.add(match[1]);
78
+ matches.push(match[1]);
77
79
  }
78
80
  }
79
- return Array.from(matches);
81
+ return matches;
80
82
  }
81
83
  async function resolveImports(specifiers, fileDir, projectPath) {
82
84
  const resolvedFiles = [];
83
85
  const resolvedPackages = [];
86
+ const packageCounts = {};
84
87
  const unresolved = [];
85
88
  for (const spec of specifiers) {
86
89
  if (isBuiltinModule(spec))
@@ -95,12 +98,15 @@ async function resolveImports(specifiers, fileDir, projectPath) {
95
98
  }
96
99
  }
97
100
  else {
98
- resolvedPackages.push(toPackageName(spec));
101
+ const pkgName = toPackageName(spec);
102
+ resolvedPackages.push(pkgName);
103
+ packageCounts[pkgName] = (packageCounts[pkgName] || 0) + 1;
99
104
  }
100
105
  }
101
106
  return {
102
107
  files: uniqSorted(resolvedFiles),
103
108
  packages: uniqSorted(resolvedPackages),
109
+ packageCounts,
104
110
  unresolved: uniqSorted(unresolved)
105
111
  };
106
112
  }
@@ -3,29 +3,94 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.runNpmAudit = runNpmAudit;
6
+ exports.runPackageAudit = runPackageAudit;
7
7
  const path_1 = __importDefault(require("path"));
8
8
  const utils_1 = require("../utils");
9
- async function runNpmAudit(projectPath, tempDir) {
10
- const targetFile = path_1.default.join(tempDir, 'npm-audit.json');
11
- try {
12
- const result = await (0, utils_1.runCommand)('npm', ['audit', '--json'], { cwd: projectPath });
13
- let parsed;
14
- try {
15
- parsed = JSON.parse(result.stdout || '{}');
9
+ function normalizeAuditOutput(tool, data) {
10
+ var _a;
11
+ if (!data)
12
+ return undefined;
13
+ if (data.vulnerabilities || data.advisories)
14
+ return data;
15
+ // Yarn (classic/berry) often emits JSONL with auditAdvisory entries.
16
+ const entries = Array.isArray(data) ? data : [data];
17
+ const advisories = {};
18
+ for (const entry of entries) {
19
+ if (!entry || typeof entry !== "object")
20
+ continue;
21
+ if (entry.type === "auditAdvisory" && ((_a = entry.data) === null || _a === void 0 ? void 0 : _a.advisory)) {
22
+ const adv = entry.data.advisory;
23
+ const key = String(adv.id ||
24
+ adv.github_advisory_id ||
25
+ `${adv.module_name || adv.module}:${adv.title || "advisory"}`);
26
+ advisories[key] = adv;
16
27
  }
17
- catch (err) {
18
- parsed = undefined;
28
+ }
29
+ if (Object.keys(advisories).length > 0) {
30
+ return { advisories };
31
+ }
32
+ if (tool === "pnpm" && data.result && data.advisories) {
33
+ return data;
34
+ }
35
+ return undefined;
36
+ }
37
+ function buildAuditCommand(tool, yarnVersion) {
38
+ if (tool === "pnpm") {
39
+ return {
40
+ cmd: "pnpm",
41
+ args: ["audit", "--json"],
42
+ lockFiles: ["pnpm-lock.yaml"],
43
+ };
44
+ }
45
+ if (tool === "yarn") {
46
+ const major = yarnVersion
47
+ ? Number.parseInt(yarnVersion.split(".")[0], 10)
48
+ : NaN;
49
+ if (!Number.isNaN(major) && major >= 2) {
50
+ return {
51
+ cmd: "yarn",
52
+ args: ["npm", "audit", "--all", "--json"],
53
+ lockFiles: ["yarn.lock"],
54
+ };
19
55
  }
20
- if (parsed) {
21
- await (0, utils_1.writeJsonFile)(targetFile, parsed);
22
- return { ok: true, data: parsed, file: targetFile };
56
+ return { cmd: "yarn", args: ["audit", "--json"], lockFiles: ["yarn.lock"] };
57
+ }
58
+ return {
59
+ cmd: "npm",
60
+ args: ["audit", "--json"],
61
+ lockFiles: ["package-lock.json", "npm-shrinkwrap.json"],
62
+ };
63
+ }
64
+ async function runPackageAudit(projectPath, tempDir, tool, yarnVersion) {
65
+ const targetFile = path_1.default.join(tempDir, `${tool}-audit.json`);
66
+ try {
67
+ const { cmd, args, lockFiles } = buildAuditCommand(tool, yarnVersion);
68
+ const lockDir = await (0, utils_1.findLockDir)(projectPath, lockFiles);
69
+ const cwd = lockDir || projectPath;
70
+ const result = await (0, utils_1.runCommand)(cmd, args, { cwd });
71
+ const parsed = (0, utils_1.parseJsonOutput)(result.stdout);
72
+ const normalized = normalizeAuditOutput(tool, parsed);
73
+ if (normalized) {
74
+ await (0, utils_1.writeJsonFile)(targetFile, normalized);
75
+ return { ok: true, data: normalized, file: targetFile };
23
76
  }
24
- await (0, utils_1.writeJsonFile)(targetFile, { stdout: result.stdout, stderr: result.stderr, code: result.code });
25
- return { ok: false, error: 'Failed to parse npm audit output', file: targetFile };
77
+ await (0, utils_1.writeJsonFile)(targetFile, {
78
+ stdout: result.stdout,
79
+ stderr: result.stderr,
80
+ code: result.code,
81
+ });
82
+ return {
83
+ ok: false,
84
+ error: `Failed to parse ${tool} audit output`,
85
+ file: targetFile,
86
+ };
26
87
  }
27
88
  catch (err) {
28
89
  await (0, utils_1.writeJsonFile)(targetFile, { error: String(err) });
29
- return { ok: false, error: `npm audit failed: ${String(err)}`, file: targetFile };
90
+ return {
91
+ ok: false,
92
+ error: `${tool} audit failed: ${String(err)}`,
93
+ file: targetFile,
94
+ };
30
95
  }
31
96
  }