dependency-radar 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +78 -0
- package/dist/aggregator.js +585 -0
- package/dist/cli.js +158 -0
- package/dist/index.js +18 -0
- package/dist/report.js +1525 -0
- package/dist/runners/depcheckRunner.js +23 -0
- package/dist/runners/licenseChecker.js +33 -0
- package/dist/runners/madgeRunner.js +29 -0
- package/dist/runners/npmAudit.js +31 -0
- package/dist/runners/npmLs.js +32 -0
- package/dist/types.js +2 -0
- package/dist/utils.js +148 -0
- package/package.json +56 -0
package/dist/report.js
ADDED
|
@@ -0,0 +1,1525 @@
|
|
|
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.renderReport = renderReport;
|
|
7
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
async function renderReport(data, outputPath) {
|
|
10
|
+
const html = buildHtml(data);
|
|
11
|
+
await promises_1.default.mkdir(path_1.default.dirname(outputPath), { recursive: true });
|
|
12
|
+
await promises_1.default.writeFile(outputPath, html, 'utf8');
|
|
13
|
+
}
|
|
14
|
+
function buildHtml(data) {
|
|
15
|
+
const json = JSON.stringify(data).replace(/</g, '\\u003c');
|
|
16
|
+
const gitBranchHtml = data.gitBranch ? `<br/>Branch: <strong>${escapeHtml(data.gitBranch)}</strong>` : '';
|
|
17
|
+
return `<!doctype html>
|
|
18
|
+
<html lang="en">
|
|
19
|
+
<head>
|
|
20
|
+
<meta charset="UTF-8" />
|
|
21
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
22
|
+
<title>Dependency Radar</title>
|
|
23
|
+
<style>
|
|
24
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
25
|
+
|
|
26
|
+
:root {
|
|
27
|
+
/* Dark theme (default) */
|
|
28
|
+
--bg-primary: #0f172a;
|
|
29
|
+
--bg-secondary: #1e293b;
|
|
30
|
+
--bg-card: rgba(30, 41, 59, 0.8);
|
|
31
|
+
--bg-card-hover: rgba(51, 65, 85, 0.9);
|
|
32
|
+
--text-primary: #f1f5f9;
|
|
33
|
+
--text-secondary: #94a3b8;
|
|
34
|
+
--text-muted: #64748b;
|
|
35
|
+
--border-color: rgba(148, 163, 184, 0.2);
|
|
36
|
+
--border-color-strong: rgba(148, 163, 184, 0.4);
|
|
37
|
+
--accent: #3b82f6;
|
|
38
|
+
--accent-hover: #60a5fa;
|
|
39
|
+
|
|
40
|
+
/* Status colors */
|
|
41
|
+
--green: #22c55e;
|
|
42
|
+
--green-bg: rgba(34, 197, 94, 0.15);
|
|
43
|
+
--amber: #f59e0b;
|
|
44
|
+
--amber-bg: rgba(245, 158, 11, 0.15);
|
|
45
|
+
--red: #ef4444;
|
|
46
|
+
--red-bg: rgba(239, 68, 68, 0.15);
|
|
47
|
+
--gray: #64748b;
|
|
48
|
+
--gray-bg: rgba(100, 116, 139, 0.15);
|
|
49
|
+
|
|
50
|
+
/* License category colors */
|
|
51
|
+
--license-permissive: #22c55e;
|
|
52
|
+
--license-weak-copyleft: #f59e0b;
|
|
53
|
+
--license-strong-copyleft: #ef4444;
|
|
54
|
+
--license-unknown: #64748b;
|
|
55
|
+
|
|
56
|
+
--font-stack: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
57
|
+
--font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
|
|
58
|
+
--transition: 0.2s ease;
|
|
59
|
+
--radius: 8px;
|
|
60
|
+
--radius-lg: 12px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
:root.light {
|
|
64
|
+
--bg-primary: #f8fafc;
|
|
65
|
+
--bg-secondary: #e2e8f0;
|
|
66
|
+
--bg-card: rgba(255, 255, 255, 0.9);
|
|
67
|
+
--bg-card-hover: rgba(241, 245, 249, 1);
|
|
68
|
+
--text-primary: #0f172a;
|
|
69
|
+
--text-secondary: #475569;
|
|
70
|
+
--text-muted: #64748b;
|
|
71
|
+
--border-color: rgba(100, 116, 139, 0.2);
|
|
72
|
+
--border-color-strong: rgba(100, 116, 139, 0.4);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
html { scroll-behavior: smooth; }
|
|
76
|
+
|
|
77
|
+
body {
|
|
78
|
+
font-family: var(--font-stack);
|
|
79
|
+
background: var(--bg-primary);
|
|
80
|
+
color: var(--text-primary);
|
|
81
|
+
margin: 0;
|
|
82
|
+
padding: 0;
|
|
83
|
+
line-height: 1.5;
|
|
84
|
+
transition: background var(--transition), color var(--transition);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* ===== TOP HEADER (Scrollable) ===== */
|
|
88
|
+
.top-header {
|
|
89
|
+
padding: 24px 24px 16px;
|
|
90
|
+
max-width: 1400px;
|
|
91
|
+
margin: 0 auto;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.header-row {
|
|
95
|
+
display: flex;
|
|
96
|
+
align-items: flex-start;
|
|
97
|
+
justify-content: space-between;
|
|
98
|
+
gap: 24px;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.header-content {
|
|
102
|
+
display: flex;
|
|
103
|
+
align-items: center;
|
|
104
|
+
gap: 16px;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.logo, .logo-wrapper {
|
|
108
|
+
display: block;
|
|
109
|
+
width: 72px;
|
|
110
|
+
height: 72px;
|
|
111
|
+
flex-shrink: 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.logo svg {
|
|
115
|
+
width: 100%;
|
|
116
|
+
height: 100%;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.header-text h1 {
|
|
120
|
+
margin: 0;
|
|
121
|
+
font-size: 28px;
|
|
122
|
+
font-weight: 700;
|
|
123
|
+
background: linear-gradient(135deg, var(--accent) 0%, #a855f7 100%);
|
|
124
|
+
-webkit-background-clip: text;
|
|
125
|
+
-webkit-text-fill-color: transparent;
|
|
126
|
+
background-clip: text;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.header-meta {
|
|
130
|
+
margin-top: 4px;
|
|
131
|
+
font-size: 13px;
|
|
132
|
+
color: var(--text-secondary);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.header-meta strong {
|
|
136
|
+
color: var(--text-primary);
|
|
137
|
+
font-family: var(--font-mono);
|
|
138
|
+
font-size: 12px;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* CTA Button */
|
|
142
|
+
.cta-section {
|
|
143
|
+
flex-shrink: 0;
|
|
144
|
+
text-align: right;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.cta-link {
|
|
148
|
+
display: inline-flex;
|
|
149
|
+
align-items: center;
|
|
150
|
+
gap: 8px;
|
|
151
|
+
padding: 12px 20px;
|
|
152
|
+
background: linear-gradient(135deg, #8b5cf6 0%, #ec4899 100%);
|
|
153
|
+
color: white;
|
|
154
|
+
text-decoration: none;
|
|
155
|
+
border-radius: var(--radius-lg);
|
|
156
|
+
font-size: 14px;
|
|
157
|
+
font-weight: 600;
|
|
158
|
+
transition: transform var(--transition), box-shadow var(--transition);
|
|
159
|
+
box-shadow: 0 4px 14px rgba(139, 92, 246, 0.4);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.cta-link:hover {
|
|
163
|
+
transform: translateY(-2px);
|
|
164
|
+
box-shadow: 0 6px 20px rgba(139, 92, 246, 0.5);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.cta-text {
|
|
168
|
+
display: block;
|
|
169
|
+
font-size: 12px;
|
|
170
|
+
color: var(--text-muted);
|
|
171
|
+
margin-top: 6px;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.cta-arrow {
|
|
175
|
+
font-size: 16px;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* ===== STICKY FILTER BAR ===== */
|
|
179
|
+
.filter-bar {
|
|
180
|
+
position: sticky;
|
|
181
|
+
top: 0;
|
|
182
|
+
z-index: 100;
|
|
183
|
+
background: var(--bg-secondary);
|
|
184
|
+
border-bottom: 1px solid var(--border-color);
|
|
185
|
+
backdrop-filter: blur(12px);
|
|
186
|
+
padding: 12px 24px;
|
|
187
|
+
transition: background var(--transition), border-color var(--transition);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.filter-bar-inner {
|
|
191
|
+
max-width: 1400px;
|
|
192
|
+
margin: 0 auto;
|
|
193
|
+
display: flex;
|
|
194
|
+
flex-wrap: wrap;
|
|
195
|
+
align-items: center;
|
|
196
|
+
gap: 12px;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.filter-group {
|
|
200
|
+
display: flex;
|
|
201
|
+
align-items: center;
|
|
202
|
+
gap: 6px;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.filter-label {
|
|
206
|
+
font-size: 12px;
|
|
207
|
+
font-weight: 500;
|
|
208
|
+
color: var(--text-muted);
|
|
209
|
+
text-transform: uppercase;
|
|
210
|
+
letter-spacing: 0.5px;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.search-wrapper {
|
|
214
|
+
position: relative;
|
|
215
|
+
flex: 1;
|
|
216
|
+
min-width: 180px;
|
|
217
|
+
max-width: 280px;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.search-icon {
|
|
221
|
+
position: absolute;
|
|
222
|
+
left: 10px;
|
|
223
|
+
top: 50%;
|
|
224
|
+
transform: translateY(-50%);
|
|
225
|
+
color: var(--text-muted);
|
|
226
|
+
pointer-events: none;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
input[type="search"] {
|
|
230
|
+
width: 100%;
|
|
231
|
+
padding: 8px 12px 8px 32px;
|
|
232
|
+
border: 1px solid var(--border-color);
|
|
233
|
+
border-radius: var(--radius);
|
|
234
|
+
background: var(--bg-primary);
|
|
235
|
+
color: var(--text-primary);
|
|
236
|
+
font-size: 14px;
|
|
237
|
+
transition: border-color var(--transition), background var(--transition);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
input[type="search"]:focus {
|
|
241
|
+
outline: none;
|
|
242
|
+
border-color: var(--accent);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
input[type="search"]::placeholder {
|
|
246
|
+
color: var(--text-muted);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
select {
|
|
250
|
+
padding: 8px 28px 8px 10px;
|
|
251
|
+
border: 1px solid var(--border-color);
|
|
252
|
+
border-radius: var(--radius);
|
|
253
|
+
background: var(--bg-primary);
|
|
254
|
+
color: var(--text-primary);
|
|
255
|
+
font-size: 13px;
|
|
256
|
+
cursor: pointer;
|
|
257
|
+
appearance: none;
|
|
258
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
|
|
259
|
+
background-repeat: no-repeat;
|
|
260
|
+
background-position: right 8px center;
|
|
261
|
+
transition: border-color var(--transition), background var(--transition);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
select:focus {
|
|
265
|
+
outline: none;
|
|
266
|
+
border-color: var(--accent);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.sort-wrapper {
|
|
270
|
+
display: flex;
|
|
271
|
+
align-items: center;
|
|
272
|
+
gap: 4px;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.sort-direction-btn {
|
|
276
|
+
padding: 8px;
|
|
277
|
+
border: 1px solid var(--border-color);
|
|
278
|
+
border-radius: var(--radius);
|
|
279
|
+
background: var(--bg-primary);
|
|
280
|
+
color: var(--text-secondary);
|
|
281
|
+
cursor: pointer;
|
|
282
|
+
font-size: 14px;
|
|
283
|
+
line-height: 1;
|
|
284
|
+
transition: all var(--transition);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.sort-direction-btn:hover {
|
|
288
|
+
border-color: var(--accent);
|
|
289
|
+
color: var(--accent);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.checkbox-filter {
|
|
293
|
+
display: flex;
|
|
294
|
+
align-items: center;
|
|
295
|
+
gap: 6px;
|
|
296
|
+
font-size: 13px;
|
|
297
|
+
color: var(--text-secondary);
|
|
298
|
+
cursor: pointer;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.checkbox-filter input[type="checkbox"] {
|
|
302
|
+
width: 16px;
|
|
303
|
+
height: 16px;
|
|
304
|
+
accent-color: var(--accent);
|
|
305
|
+
cursor: pointer;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/* Theme toggle */
|
|
309
|
+
.theme-toggle {
|
|
310
|
+
margin-left: auto;
|
|
311
|
+
display: flex;
|
|
312
|
+
align-items: center;
|
|
313
|
+
gap: 8px;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.theme-toggle-label {
|
|
317
|
+
font-size: 12px;
|
|
318
|
+
color: var(--text-muted);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.theme-switch {
|
|
322
|
+
position: relative;
|
|
323
|
+
width: 44px;
|
|
324
|
+
height: 24px;
|
|
325
|
+
background: var(--bg-primary);
|
|
326
|
+
border: 1px solid var(--border-color);
|
|
327
|
+
border-radius: 12px;
|
|
328
|
+
cursor: pointer;
|
|
329
|
+
transition: background var(--transition), border-color var(--transition);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.theme-switch::after {
|
|
333
|
+
content: '';
|
|
334
|
+
position: absolute;
|
|
335
|
+
top: 2px;
|
|
336
|
+
left: 2px;
|
|
337
|
+
width: 18px;
|
|
338
|
+
height: 18px;
|
|
339
|
+
background: var(--text-secondary);
|
|
340
|
+
border-radius: 50%;
|
|
341
|
+
transition: transform var(--transition), background var(--transition);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.theme-switch.light::after {
|
|
345
|
+
transform: translateX(20px);
|
|
346
|
+
background: var(--accent);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/* ===== COLLAPSIBLE LICENSE FILTER ===== */
|
|
350
|
+
.license-filter-toggle {
|
|
351
|
+
padding: 6px 12px;
|
|
352
|
+
border: 1px solid var(--border-color);
|
|
353
|
+
border-radius: var(--radius);
|
|
354
|
+
background: transparent;
|
|
355
|
+
color: var(--text-secondary);
|
|
356
|
+
font-size: 12px;
|
|
357
|
+
cursor: pointer;
|
|
358
|
+
display: flex;
|
|
359
|
+
align-items: center;
|
|
360
|
+
gap: 6px;
|
|
361
|
+
transition: all var(--transition);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.license-filter-toggle:hover {
|
|
365
|
+
border-color: var(--accent);
|
|
366
|
+
color: var(--accent);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.license-filter-toggle .chevron {
|
|
370
|
+
transition: transform var(--transition);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.license-filter-toggle.open .chevron {
|
|
374
|
+
transform: rotate(180deg);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.license-filter-panel {
|
|
378
|
+
max-height: 0;
|
|
379
|
+
overflow: hidden;
|
|
380
|
+
transition: max-height 0.3s ease-out;
|
|
381
|
+
background: var(--bg-secondary);
|
|
382
|
+
border-bottom: 1px solid transparent;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.license-filter-panel.open {
|
|
386
|
+
max-height: 200px;
|
|
387
|
+
border-bottom-color: var(--border-color);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.license-filter-inner {
|
|
391
|
+
max-width: 1400px;
|
|
392
|
+
margin: 0 auto;
|
|
393
|
+
padding: 12px 24px;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.license-filter-header {
|
|
397
|
+
display: flex;
|
|
398
|
+
align-items: center;
|
|
399
|
+
gap: 12px;
|
|
400
|
+
margin-bottom: 8px;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.license-filter-title {
|
|
404
|
+
font-size: 12px;
|
|
405
|
+
font-weight: 500;
|
|
406
|
+
color: var(--text-muted);
|
|
407
|
+
text-transform: uppercase;
|
|
408
|
+
letter-spacing: 0.5px;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.license-quick-actions {
|
|
412
|
+
display: flex;
|
|
413
|
+
gap: 8px;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.quick-action-btn {
|
|
417
|
+
padding: 4px 10px;
|
|
418
|
+
font-size: 11px;
|
|
419
|
+
border: 1px solid var(--border-color);
|
|
420
|
+
border-radius: 4px;
|
|
421
|
+
background: transparent;
|
|
422
|
+
color: var(--text-secondary);
|
|
423
|
+
cursor: pointer;
|
|
424
|
+
transition: all var(--transition);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
.quick-action-btn:hover {
|
|
428
|
+
border-color: var(--accent);
|
|
429
|
+
color: var(--accent);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
.license-groups {
|
|
433
|
+
display: flex;
|
|
434
|
+
flex-wrap: wrap;
|
|
435
|
+
gap: 16px;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.license-group-checkbox {
|
|
439
|
+
display: flex;
|
|
440
|
+
align-items: center;
|
|
441
|
+
gap: 6px;
|
|
442
|
+
font-size: 13px;
|
|
443
|
+
color: var(--text-secondary);
|
|
444
|
+
cursor: pointer;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.license-group-checkbox input[type="checkbox"] {
|
|
448
|
+
width: 16px;
|
|
449
|
+
height: 16px;
|
|
450
|
+
accent-color: var(--accent);
|
|
451
|
+
cursor: pointer;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.license-dot {
|
|
455
|
+
width: 8px;
|
|
456
|
+
height: 8px;
|
|
457
|
+
border-radius: 50%;
|
|
458
|
+
flex-shrink: 0;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
.license-dot.permissive { background: var(--license-permissive); }
|
|
462
|
+
.license-dot.weak-copyleft { background: var(--license-weak-copyleft); }
|
|
463
|
+
.license-dot.strong-copyleft { background: var(--license-strong-copyleft); }
|
|
464
|
+
.license-dot.unknown { background: var(--license-unknown); }
|
|
465
|
+
|
|
466
|
+
/* ===== TOOL ERRORS ===== */
|
|
467
|
+
.tool-errors {
|
|
468
|
+
max-width: 1400px;
|
|
469
|
+
margin: 16px auto;
|
|
470
|
+
padding: 12px 16px;
|
|
471
|
+
background: var(--red-bg);
|
|
472
|
+
border: 1px solid var(--red);
|
|
473
|
+
border-radius: var(--radius);
|
|
474
|
+
color: var(--red);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.tool-errors strong {
|
|
478
|
+
display: block;
|
|
479
|
+
margin-bottom: 8px;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/* ===== MAIN CONTENT ===== */
|
|
483
|
+
.main-content {
|
|
484
|
+
max-width: 1400px;
|
|
485
|
+
margin: 0 auto;
|
|
486
|
+
padding: 16px 24px 48px;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.results-summary {
|
|
490
|
+
margin-bottom: 16px;
|
|
491
|
+
font-size: 14px;
|
|
492
|
+
color: var(--text-secondary);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.results-summary strong {
|
|
496
|
+
color: var(--text-primary);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.dependency-grid {
|
|
500
|
+
display: flex;
|
|
501
|
+
flex-direction: column;
|
|
502
|
+
gap: 8px;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/* ===== DEPENDENCY CARD ===== */
|
|
506
|
+
.dep-card {
|
|
507
|
+
background: var(--bg-card);
|
|
508
|
+
border: 1px solid var(--border-color);
|
|
509
|
+
border-radius: var(--radius-lg);
|
|
510
|
+
overflow: hidden;
|
|
511
|
+
transition: border-color var(--transition);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
.dep-card:hover {
|
|
515
|
+
border-color: var(--border-color-strong);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
.dep-card[data-risk="red"] {
|
|
519
|
+
border-left: 3px solid var(--red);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.dep-card[data-risk="amber"] {
|
|
523
|
+
border-left: 3px solid var(--amber);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
.dep-card[data-risk="green"] {
|
|
527
|
+
border-left: 3px solid var(--green);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
.dep-summary {
|
|
531
|
+
display: flex;
|
|
532
|
+
align-items: center;
|
|
533
|
+
gap: 12px;
|
|
534
|
+
padding: 14px 16px;
|
|
535
|
+
cursor: pointer;
|
|
536
|
+
list-style: none;
|
|
537
|
+
transition: background var(--transition);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
.dep-summary:hover {
|
|
541
|
+
background: var(--bg-card-hover);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.dep-summary::-webkit-details-marker { display: none; }
|
|
545
|
+
|
|
546
|
+
.expand-icon {
|
|
547
|
+
color: var(--text-muted);
|
|
548
|
+
transition: transform var(--transition);
|
|
549
|
+
flex-shrink: 0;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
details[open] .expand-icon {
|
|
553
|
+
transform: rotate(90deg);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
.dep-name {
|
|
557
|
+
font-family: var(--font-mono);
|
|
558
|
+
font-size: 14px;
|
|
559
|
+
font-weight: 600;
|
|
560
|
+
color: var(--text-primary);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
.dep-version {
|
|
564
|
+
font-weight: 400;
|
|
565
|
+
color: var(--text-secondary);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
.dep-indicators {
|
|
569
|
+
display: flex;
|
|
570
|
+
flex-wrap: wrap;
|
|
571
|
+
align-items: center;
|
|
572
|
+
gap: 16px;
|
|
573
|
+
margin-left: auto;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
.indicator {
|
|
577
|
+
display: flex;
|
|
578
|
+
align-items: center;
|
|
579
|
+
gap: 6px;
|
|
580
|
+
font-size: 12px;
|
|
581
|
+
color: var(--text-secondary);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
.indicator-dot {
|
|
585
|
+
width: 8px;
|
|
586
|
+
height: 8px;
|
|
587
|
+
border-radius: 50%;
|
|
588
|
+
flex-shrink: 0;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
.indicator-dot.green { background: var(--green); }
|
|
592
|
+
.indicator-dot.amber { background: var(--amber); }
|
|
593
|
+
.indicator-dot.red { background: var(--red); }
|
|
594
|
+
.indicator-dot.gray { background: var(--gray); }
|
|
595
|
+
|
|
596
|
+
.indicator-separator {
|
|
597
|
+
width: 1px;
|
|
598
|
+
height: 16px;
|
|
599
|
+
background: var(--border-color);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/* ===== EXPANDED DETAILS ===== */
|
|
603
|
+
.dep-details {
|
|
604
|
+
padding: 0 16px 16px 16px;
|
|
605
|
+
border-top: 1px solid var(--border-color);
|
|
606
|
+
animation: slideDown 0.2s ease-out;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
@keyframes slideDown {
|
|
610
|
+
from {
|
|
611
|
+
opacity: 0;
|
|
612
|
+
transform: translateY(-8px);
|
|
613
|
+
}
|
|
614
|
+
to {
|
|
615
|
+
opacity: 1;
|
|
616
|
+
transform: translateY(0);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/* ===== PACKAGE LINKS BAR ===== */
|
|
621
|
+
.package-links {
|
|
622
|
+
display: flex;
|
|
623
|
+
flex-wrap: wrap;
|
|
624
|
+
gap: 8px;
|
|
625
|
+
padding: 12px 0;
|
|
626
|
+
margin-bottom: 4px;
|
|
627
|
+
border-bottom: 1px solid var(--border-color);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
.package-link {
|
|
631
|
+
display: inline-flex;
|
|
632
|
+
align-items: center;
|
|
633
|
+
gap: 6px;
|
|
634
|
+
padding: 6px 12px;
|
|
635
|
+
background: var(--bg-hover);
|
|
636
|
+
border: 1px solid var(--border-color);
|
|
637
|
+
border-radius: var(--radius-md);
|
|
638
|
+
color: var(--text-primary);
|
|
639
|
+
text-decoration: none;
|
|
640
|
+
font-size: 13px;
|
|
641
|
+
font-weight: 500;
|
|
642
|
+
transition: all var(--transition);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
.package-link:hover {
|
|
646
|
+
background: var(--accent);
|
|
647
|
+
border-color: var(--accent);
|
|
648
|
+
color: white;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
.package-link svg {
|
|
652
|
+
width: 14px;
|
|
653
|
+
height: 14px;
|
|
654
|
+
flex-shrink: 0;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
.section {
|
|
658
|
+
margin-top: 16px;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
.section:first-child {
|
|
662
|
+
margin-top: 16px;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
.section-header {
|
|
666
|
+
display: flex;
|
|
667
|
+
align-items: center;
|
|
668
|
+
gap: 8px;
|
|
669
|
+
margin-bottom: 10px;
|
|
670
|
+
padding-bottom: 6px;
|
|
671
|
+
border-bottom: 1px solid var(--border-color);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
.section-title {
|
|
675
|
+
font-size: 13px;
|
|
676
|
+
font-weight: 600;
|
|
677
|
+
color: var(--text-primary);
|
|
678
|
+
text-transform: uppercase;
|
|
679
|
+
letter-spacing: 0.5px;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
.section-desc {
|
|
683
|
+
font-size: 11px;
|
|
684
|
+
color: var(--text-muted);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
.kv-grid {
|
|
688
|
+
display: grid;
|
|
689
|
+
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
690
|
+
gap: 12px;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
.kv-item {
|
|
694
|
+
display: flex;
|
|
695
|
+
flex-direction: column;
|
|
696
|
+
gap: 2px;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
.kv-label {
|
|
700
|
+
font-size: 12px;
|
|
701
|
+
font-weight: 500;
|
|
702
|
+
color: var(--text-muted);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
.kv-value {
|
|
706
|
+
font-size: 14px;
|
|
707
|
+
color: var(--text-primary);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
.kv-hint {
|
|
711
|
+
font-size: 11px;
|
|
712
|
+
color: var(--text-muted);
|
|
713
|
+
font-style: italic;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/* Package list in graph section */
|
|
717
|
+
.package-list {
|
|
718
|
+
display: flex;
|
|
719
|
+
flex-wrap: wrap;
|
|
720
|
+
gap: 6px;
|
|
721
|
+
margin-top: 4px;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
.package-tag {
|
|
725
|
+
padding: 2px 8px;
|
|
726
|
+
background: var(--bg-primary);
|
|
727
|
+
border: 1px solid var(--border-color);
|
|
728
|
+
border-radius: 4px;
|
|
729
|
+
font-size: 11px;
|
|
730
|
+
font-family: var(--font-mono);
|
|
731
|
+
color: var(--text-secondary);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
.package-list-toggle {
|
|
735
|
+
font-size: 11px;
|
|
736
|
+
color: var(--accent);
|
|
737
|
+
background: none;
|
|
738
|
+
border: none;
|
|
739
|
+
cursor: pointer;
|
|
740
|
+
padding: 2px 4px;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
.package-list-toggle:hover {
|
|
744
|
+
text-decoration: underline;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/* Vulnerability table */
|
|
748
|
+
.vuln-table {
|
|
749
|
+
width: 100%;
|
|
750
|
+
border-collapse: collapse;
|
|
751
|
+
font-size: 13px;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
.vuln-table th,
|
|
755
|
+
.vuln-table td {
|
|
756
|
+
text-align: left;
|
|
757
|
+
padding: 8px 12px;
|
|
758
|
+
border-bottom: 1px solid var(--border-color);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
.vuln-table th {
|
|
762
|
+
font-weight: 500;
|
|
763
|
+
color: var(--text-muted);
|
|
764
|
+
font-size: 11px;
|
|
765
|
+
text-transform: uppercase;
|
|
766
|
+
letter-spacing: 0.5px;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
.vuln-table tr[data-severity="critical"],
|
|
770
|
+
.vuln-table tr[data-severity="high"] {
|
|
771
|
+
background: var(--red-bg);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
.vuln-table tr[data-severity="moderate"] {
|
|
775
|
+
background: var(--amber-bg);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
.vuln-table a {
|
|
779
|
+
color: var(--accent);
|
|
780
|
+
text-decoration: none;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
.vuln-table a:hover {
|
|
784
|
+
text-decoration: underline;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
.no-vulns {
|
|
788
|
+
font-size: 13px;
|
|
789
|
+
color: var(--text-secondary);
|
|
790
|
+
padding: 8px 0;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/* Raw data toggle */
|
|
794
|
+
.raw-data-toggle {
|
|
795
|
+
margin-top: 16px;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
.raw-data-toggle summary {
|
|
799
|
+
font-size: 12px;
|
|
800
|
+
color: var(--text-muted);
|
|
801
|
+
cursor: pointer;
|
|
802
|
+
padding: 8px 0;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
.raw-data-toggle pre {
|
|
806
|
+
margin: 8px 0 0 0;
|
|
807
|
+
padding: 12px;
|
|
808
|
+
background: var(--bg-primary);
|
|
809
|
+
border: 1px solid var(--border-color);
|
|
810
|
+
border-radius: var(--radius);
|
|
811
|
+
font-family: var(--font-mono);
|
|
812
|
+
font-size: 11px;
|
|
813
|
+
color: var(--text-secondary);
|
|
814
|
+
overflow-x: auto;
|
|
815
|
+
max-height: 300px;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/* Empty state */
|
|
819
|
+
.empty-state {
|
|
820
|
+
text-align: center;
|
|
821
|
+
padding: 48px 24px;
|
|
822
|
+
color: var(--text-muted);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
.empty-state-icon {
|
|
826
|
+
font-size: 48px;
|
|
827
|
+
margin-bottom: 12px;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
.empty-state-text {
|
|
831
|
+
font-size: 16px;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/* ===== RESPONSIVE ===== */
|
|
835
|
+
@media (max-width: 768px) {
|
|
836
|
+
.header-row {
|
|
837
|
+
flex-direction: column;
|
|
838
|
+
align-items: flex-start;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
.cta-section {
|
|
842
|
+
text-align: left;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
.filter-bar-inner {
|
|
846
|
+
flex-direction: column;
|
|
847
|
+
align-items: stretch;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
.search-wrapper {
|
|
851
|
+
max-width: none;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
.theme-toggle {
|
|
855
|
+
margin-left: 0;
|
|
856
|
+
justify-content: flex-end;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
.dep-indicators {
|
|
860
|
+
flex-wrap: wrap;
|
|
861
|
+
gap: 8px;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
.indicator-separator {
|
|
865
|
+
display: none;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
</style>
|
|
869
|
+
</head>
|
|
870
|
+
<body>
|
|
871
|
+
<!-- Top Header (Scrollable) -->
|
|
872
|
+
<header class="top-header">
|
|
873
|
+
<div class="header-row">
|
|
874
|
+
<div class="header-content">
|
|
875
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"
|
|
876
|
+
viewBox="0 0 1024 1024" class="logo">
|
|
877
|
+
<defs>
|
|
878
|
+
<style>
|
|
879
|
+
.st0, .st1 {
|
|
880
|
+
fill: #ff8000;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
.st2 {
|
|
884
|
+
fill: #191772;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
.st2, .st3, .st4, .st5 {
|
|
888
|
+
stroke: #55fffa;
|
|
889
|
+
stroke-miterlimit: 10;
|
|
890
|
+
stroke-width: 8px;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
.st6 {
|
|
894
|
+
fill: #0a0a33;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
.st3 {
|
|
898
|
+
fill: #161466;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
.st7 {
|
|
902
|
+
fill: url(#linear-gradient);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
.st8, .st1 {
|
|
906
|
+
opacity: .4;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
.st8, .st9 {
|
|
910
|
+
fill: red;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
.st4 {
|
|
914
|
+
fill: #1c197f;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
.st10 {
|
|
918
|
+
fill: #55fffa;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
.st5 {
|
|
922
|
+
fill: #141259;
|
|
923
|
+
}
|
|
924
|
+
</style>
|
|
925
|
+
<linearGradient id="linear-gradient" x1="225" y1="287" x2="831.3" y2="287"
|
|
926
|
+
gradientUnits="userSpaceOnUse">
|
|
927
|
+
<stop offset=".4" stop-color="#55fffa" stop-opacity="0" />
|
|
928
|
+
<stop offset="1" stop-color="#55fffa" stop-opacity=".5" />
|
|
929
|
+
</linearGradient>
|
|
930
|
+
</defs>
|
|
931
|
+
<circle class="st6" cx="512" cy="512" r="512" />
|
|
932
|
+
<circle class="st5" cx="512" cy="512" r="450" />
|
|
933
|
+
<circle class="st3" cx="512" cy="512" r="325" />
|
|
934
|
+
<circle class="st2" cx="512" cy="512" r="200" />
|
|
935
|
+
<circle class="st4" cx="512" cy="512" r="80" />
|
|
936
|
+
<path class="st7"
|
|
937
|
+
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" />
|
|
938
|
+
<rect class="st10" x="664.2" y="129.5" width="16.7" height="450"
|
|
939
|
+
transform="translate(447.6 -371.7) rotate(45)" />
|
|
940
|
+
<circle class="st8" cx="800" cy="662" r="50" />
|
|
941
|
+
<circle class="st9" cx="800" cy="662" r="25" />
|
|
942
|
+
<circle class="st8" cx="256.9" cy="315.2" r="50" />
|
|
943
|
+
<circle class="st9" cx="256.9" cy="315.2" r="25" />
|
|
944
|
+
<circle class="st1" cx="400.1" cy="673" r="50" />
|
|
945
|
+
<circle class="st0" cx="400.1" cy="673" r="25" />
|
|
946
|
+
</svg>
|
|
947
|
+
<div class="header-text">
|
|
948
|
+
<h1>Dependency Radar</h1>
|
|
949
|
+
<p class="header-meta">
|
|
950
|
+
Project: <strong>${escapeHtml(data.projectPath)}</strong>${gitBranchHtml}<br/>
|
|
951
|
+
Generated: <span id="formatted-date">${data.generatedAt}</span>
|
|
952
|
+
</p>
|
|
953
|
+
</div>
|
|
954
|
+
</div>
|
|
955
|
+
<div class="cta-section">
|
|
956
|
+
<a href="https://dependency-radar.com" class="cta-link" target="_blank" rel="noopener">
|
|
957
|
+
Get risk analysis & summary
|
|
958
|
+
<span class="cta-arrow">→</span>
|
|
959
|
+
</a>
|
|
960
|
+
<div class="cta-text">dependency-radar.com</div>
|
|
961
|
+
</div>
|
|
962
|
+
</div>
|
|
963
|
+
</header>
|
|
964
|
+
|
|
965
|
+
<!-- Sticky Filter Bar -->
|
|
966
|
+
<div class="filter-bar">
|
|
967
|
+
<div class="filter-bar-inner">
|
|
968
|
+
<div class="search-wrapper">
|
|
969
|
+
<svg class="search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
970
|
+
<circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/>
|
|
971
|
+
</svg>
|
|
972
|
+
<input type="search" id="search" placeholder="Search packages..." />
|
|
973
|
+
</div>
|
|
974
|
+
|
|
975
|
+
<div class="filter-group">
|
|
976
|
+
<span class="filter-label">Type</span>
|
|
977
|
+
<select id="direct-filter">
|
|
978
|
+
<option value="all">All</option>
|
|
979
|
+
<option value="direct">Dependency</option>
|
|
980
|
+
<option value="transitive">Sub-Dependency</option>
|
|
981
|
+
</select>
|
|
982
|
+
</div>
|
|
983
|
+
|
|
984
|
+
<div class="filter-group">
|
|
985
|
+
<span class="filter-label">Runtime</span>
|
|
986
|
+
<select id="runtime-filter">
|
|
987
|
+
<option value="all">All</option>
|
|
988
|
+
<option value="runtime">Runtime</option>
|
|
989
|
+
<option value="build-time">Build-time</option>
|
|
990
|
+
<option value="dev-only">Dev-only</option>
|
|
991
|
+
</select>
|
|
992
|
+
</div>
|
|
993
|
+
|
|
994
|
+
<div class="filter-group sort-wrapper">
|
|
995
|
+
<span class="filter-label">Sort</span>
|
|
996
|
+
<select id="sort-by">
|
|
997
|
+
<option value="name">Name</option>
|
|
998
|
+
<option value="severity">Severity</option>
|
|
999
|
+
<option value="depth">Depth</option>
|
|
1000
|
+
<option value="size">Size</option>
|
|
1001
|
+
</select>
|
|
1002
|
+
<button type="button" class="sort-direction-btn" id="sort-direction" title="Toggle sort direction">↑</button>
|
|
1003
|
+
</div>
|
|
1004
|
+
|
|
1005
|
+
<button type="button" class="license-filter-toggle" id="license-toggle">
|
|
1006
|
+
License Categories
|
|
1007
|
+
<span class="chevron">â–¼</span>
|
|
1008
|
+
</button>
|
|
1009
|
+
|
|
1010
|
+
<label class="checkbox-filter">
|
|
1011
|
+
<input type="checkbox" id="has-vulns" />
|
|
1012
|
+
Has vulnerabilities
|
|
1013
|
+
</label>
|
|
1014
|
+
|
|
1015
|
+
<label class="checkbox-filter">
|
|
1016
|
+
<input type="checkbox" id="unused-only" />
|
|
1017
|
+
Unused only
|
|
1018
|
+
</label>
|
|
1019
|
+
|
|
1020
|
+
<div class="theme-toggle">
|
|
1021
|
+
<span class="theme-toggle-label">Theme</span>
|
|
1022
|
+
<div class="theme-switch" id="theme-switch" title="Toggle dark/light mode"></div>
|
|
1023
|
+
</div>
|
|
1024
|
+
</div>
|
|
1025
|
+
|
|
1026
|
+
<!-- Collapsible License Filter Panel (inside sticky bar) -->
|
|
1027
|
+
<div class="license-filter-panel" id="license-panel">
|
|
1028
|
+
<div class="license-filter-inner">
|
|
1029
|
+
<div class="license-filter-header">
|
|
1030
|
+
<span class="license-filter-title">Filter by License Type</span>
|
|
1031
|
+
<div class="license-quick-actions">
|
|
1032
|
+
<button type="button" class="quick-action-btn" id="license-all">Show All</button>
|
|
1033
|
+
<button type="button" class="quick-action-btn" id="license-friendly">Business-Friendly Only</button>
|
|
1034
|
+
</div>
|
|
1035
|
+
</div>
|
|
1036
|
+
<div class="license-groups">
|
|
1037
|
+
<label class="license-group-checkbox">
|
|
1038
|
+
<input type="checkbox" id="license-permissive" checked />
|
|
1039
|
+
<span class="license-dot permissive"></span>
|
|
1040
|
+
Permissive (MIT, BSD, Apache, ISC)
|
|
1041
|
+
</label>
|
|
1042
|
+
<label class="license-group-checkbox">
|
|
1043
|
+
<input type="checkbox" id="license-weak-copyleft" checked />
|
|
1044
|
+
<span class="license-dot weak-copyleft"></span>
|
|
1045
|
+
Weak Copyleft (LGPL, MPL, EPL)
|
|
1046
|
+
</label>
|
|
1047
|
+
<label class="license-group-checkbox">
|
|
1048
|
+
<input type="checkbox" id="license-strong-copyleft" checked />
|
|
1049
|
+
<span class="license-dot strong-copyleft"></span>
|
|
1050
|
+
Strong Copyleft (GPL, AGPL)
|
|
1051
|
+
</label>
|
|
1052
|
+
<label class="license-group-checkbox">
|
|
1053
|
+
<input type="checkbox" id="license-unknown" checked />
|
|
1054
|
+
<span class="license-dot unknown"></span>
|
|
1055
|
+
Other / Unknown
|
|
1056
|
+
</label>
|
|
1057
|
+
</div>
|
|
1058
|
+
</div>
|
|
1059
|
+
</div>
|
|
1060
|
+
</div>
|
|
1061
|
+
|
|
1062
|
+
${renderToolErrors(data)}
|
|
1063
|
+
|
|
1064
|
+
<!-- Main Content -->
|
|
1065
|
+
<main class="main-content">
|
|
1066
|
+
<div class="results-summary" id="results-summary"></div>
|
|
1067
|
+
<div id="dependency-list" class="dependency-grid"></div>
|
|
1068
|
+
</main>
|
|
1069
|
+
|
|
1070
|
+
<script type="application/json" id="radar-data">${json}</script>
|
|
1071
|
+
<script>
|
|
1072
|
+
(function() {
|
|
1073
|
+
const dataEl = document.getElementById('radar-data');
|
|
1074
|
+
const report = JSON.parse(dataEl.textContent || '{}');
|
|
1075
|
+
const maintenanceEnabled = Boolean(report.maintenanceEnabled);
|
|
1076
|
+
const container = document.getElementById('dependency-list');
|
|
1077
|
+
const summaryEl = document.getElementById('results-summary');
|
|
1078
|
+
|
|
1079
|
+
// Format timestamp
|
|
1080
|
+
const dateEl = document.getElementById('formatted-date');
|
|
1081
|
+
if (dateEl && report.generatedAt) {
|
|
1082
|
+
try {
|
|
1083
|
+
const date = new Date(report.generatedAt);
|
|
1084
|
+
const formatted = new Intl.DateTimeFormat(undefined, {
|
|
1085
|
+
day: 'numeric',
|
|
1086
|
+
month: 'short',
|
|
1087
|
+
year: 'numeric',
|
|
1088
|
+
hour: '2-digit',
|
|
1089
|
+
minute: '2-digit'
|
|
1090
|
+
}).format(date);
|
|
1091
|
+
dateEl.textContent = formatted;
|
|
1092
|
+
} catch (e) {
|
|
1093
|
+
// Fallback: keep ISO format
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// License categorization
|
|
1098
|
+
const LICENSE_CATEGORIES = {
|
|
1099
|
+
permissive: ['MIT', 'ISC', 'BSD-2-Clause', 'BSD-3-Clause', 'Apache-2.0', 'Unlicense', '0BSD', 'CC0-1.0', 'BSD', 'Apache', 'Apache 2.0', 'Apache License 2.0', 'MIT License', 'ISC License'],
|
|
1100
|
+
weakCopyleft: ['LGPL-2.1', 'LGPL-3.0', 'LGPL-2.0', 'LGPL', 'MPL-2.0', 'MPL-1.1', 'MPL', 'EPL-1.0', 'EPL-2.0', 'EPL'],
|
|
1101
|
+
strongCopyleft: ['GPL-2.0', 'GPL-3.0', 'GPL', 'AGPL-3.0', 'AGPL', 'GPL-2.0-only', 'GPL-3.0-only', 'GPL-2.0-or-later', 'GPL-3.0-or-later']
|
|
1102
|
+
};
|
|
1103
|
+
|
|
1104
|
+
function getLicenseCategory(license) {
|
|
1105
|
+
if (!license) return 'unknown';
|
|
1106
|
+
const normalized = license.toUpperCase();
|
|
1107
|
+
for (const [cat, licenses] of Object.entries(LICENSE_CATEGORIES)) {
|
|
1108
|
+
if (licenses.some(l => normalized.includes(l.toUpperCase()))) return cat;
|
|
1109
|
+
}
|
|
1110
|
+
return 'unknown';
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
// Controls
|
|
1114
|
+
const controls = {
|
|
1115
|
+
search: document.getElementById('search'),
|
|
1116
|
+
direct: document.getElementById('direct-filter'),
|
|
1117
|
+
runtime: document.getElementById('runtime-filter'),
|
|
1118
|
+
sort: document.getElementById('sort-by'),
|
|
1119
|
+
sortDirection: document.getElementById('sort-direction'),
|
|
1120
|
+
hasVulns: document.getElementById('has-vulns'),
|
|
1121
|
+
unusedOnly: document.getElementById('unused-only'),
|
|
1122
|
+
themeSwitch: document.getElementById('theme-switch'),
|
|
1123
|
+
licenseToggle: document.getElementById('license-toggle'),
|
|
1124
|
+
licensePanel: document.getElementById('license-panel'),
|
|
1125
|
+
licensePermissive: document.getElementById('license-permissive'),
|
|
1126
|
+
licenseWeakCopyleft: document.getElementById('license-weak-copyleft'),
|
|
1127
|
+
licenseStrongCopyleft: document.getElementById('license-strong-copyleft'),
|
|
1128
|
+
licenseUnknown: document.getElementById('license-unknown'),
|
|
1129
|
+
licenseAll: document.getElementById('license-all'),
|
|
1130
|
+
licenseFriendly: document.getElementById('license-friendly')
|
|
1131
|
+
};
|
|
1132
|
+
|
|
1133
|
+
let sortAscending = true;
|
|
1134
|
+
|
|
1135
|
+
// Theme handling
|
|
1136
|
+
const savedTheme = localStorage.getItem('dependency-radar-theme');
|
|
1137
|
+
if (savedTheme === 'light') {
|
|
1138
|
+
document.documentElement.classList.add('light');
|
|
1139
|
+
controls.themeSwitch.classList.add('light');
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
controls.themeSwitch.addEventListener('click', () => {
|
|
1143
|
+
document.documentElement.classList.toggle('light');
|
|
1144
|
+
controls.themeSwitch.classList.toggle('light');
|
|
1145
|
+
const isLight = document.documentElement.classList.contains('light');
|
|
1146
|
+
localStorage.setItem('dependency-radar-theme', isLight ? 'light' : 'dark');
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1149
|
+
// License panel toggle
|
|
1150
|
+
controls.licenseToggle.addEventListener('click', () => {
|
|
1151
|
+
controls.licenseToggle.classList.toggle('open');
|
|
1152
|
+
controls.licensePanel.classList.toggle('open');
|
|
1153
|
+
});
|
|
1154
|
+
|
|
1155
|
+
// Sort direction toggle
|
|
1156
|
+
controls.sortDirection.addEventListener('click', () => {
|
|
1157
|
+
sortAscending = !sortAscending;
|
|
1158
|
+
controls.sortDirection.textContent = sortAscending ? '↑' : '↓';
|
|
1159
|
+
renderList();
|
|
1160
|
+
});
|
|
1161
|
+
|
|
1162
|
+
// License quick actions
|
|
1163
|
+
controls.licenseAll.addEventListener('click', () => {
|
|
1164
|
+
controls.licensePermissive.checked = true;
|
|
1165
|
+
controls.licenseWeakCopyleft.checked = true;
|
|
1166
|
+
controls.licenseStrongCopyleft.checked = true;
|
|
1167
|
+
controls.licenseUnknown.checked = true;
|
|
1168
|
+
renderList();
|
|
1169
|
+
});
|
|
1170
|
+
|
|
1171
|
+
controls.licenseFriendly.addEventListener('click', () => {
|
|
1172
|
+
controls.licensePermissive.checked = true;
|
|
1173
|
+
controls.licenseWeakCopyleft.checked = false;
|
|
1174
|
+
controls.licenseStrongCopyleft.checked = false;
|
|
1175
|
+
controls.licenseUnknown.checked = false;
|
|
1176
|
+
renderList();
|
|
1177
|
+
});
|
|
1178
|
+
|
|
1179
|
+
function highestSeverity(dep) {
|
|
1180
|
+
return dep.vulnerabilities?.highestSeverity || 'none';
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
const severityOrder = { none: 0, low: 1, moderate: 2, high: 3, critical: 4 };
|
|
1184
|
+
|
|
1185
|
+
function formatBytes(bytes) {
|
|
1186
|
+
if (!bytes) return '0 B';
|
|
1187
|
+
const units = ['B', 'KB', 'MB', 'GB'];
|
|
1188
|
+
let val = bytes;
|
|
1189
|
+
let unit = 0;
|
|
1190
|
+
while (val >= 1024 && unit < units.length - 1) {
|
|
1191
|
+
val /= 1024;
|
|
1192
|
+
unit++;
|
|
1193
|
+
}
|
|
1194
|
+
return val.toFixed(val >= 10 ? 0 : 1) + ' ' + units[unit];
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
function yesNo(flag) {
|
|
1198
|
+
return flag ? 'Yes' : 'No';
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
function escapeHtml(str) {
|
|
1202
|
+
if (!str) return '';
|
|
1203
|
+
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
function getHighestRisk(dep) {
|
|
1207
|
+
const risks = [dep.vulnRisk, dep.licenseRisk];
|
|
1208
|
+
if (risks.includes('red')) return 'red';
|
|
1209
|
+
if (risks.includes('amber')) return 'amber';
|
|
1210
|
+
return 'green';
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
function applyFilters() {
|
|
1214
|
+
const term = (controls.search.value || '').toLowerCase();
|
|
1215
|
+
const directFilter = controls.direct.value;
|
|
1216
|
+
const runtimeFilter = controls.runtime.value;
|
|
1217
|
+
const hasVulns = controls.hasVulns.checked;
|
|
1218
|
+
const unusedOnly = controls.unusedOnly.checked;
|
|
1219
|
+
|
|
1220
|
+
const showPermissive = controls.licensePermissive.checked;
|
|
1221
|
+
const showWeakCopyleft = controls.licenseWeakCopyleft.checked;
|
|
1222
|
+
const showStrongCopyleft = controls.licenseStrongCopyleft.checked;
|
|
1223
|
+
const showUnknown = controls.licenseUnknown.checked;
|
|
1224
|
+
|
|
1225
|
+
return report.dependencies.filter((dep) => {
|
|
1226
|
+
if (term && !(dep.name.toLowerCase().includes(term) || (dep.license.license || '').toLowerCase().includes(term))) return false;
|
|
1227
|
+
if (directFilter === 'direct' && !dep.direct) return false;
|
|
1228
|
+
if (directFilter === 'transitive' && dep.direct) return false;
|
|
1229
|
+
if (runtimeFilter !== 'all' && dep.runtimeClass !== runtimeFilter) return false;
|
|
1230
|
+
if (hasVulns && severityOrder[highestSeverity(dep)] === 0) return false;
|
|
1231
|
+
if (unusedOnly && dep.usage.status !== 'unused') return false;
|
|
1232
|
+
|
|
1233
|
+
// License category filter
|
|
1234
|
+
const licenseCategory = getLicenseCategory(dep.license.license);
|
|
1235
|
+
if (licenseCategory === 'permissive' && !showPermissive) return false;
|
|
1236
|
+
if (licenseCategory === 'weakCopyleft' && !showWeakCopyleft) return false;
|
|
1237
|
+
if (licenseCategory === 'strongCopyleft' && !showStrongCopyleft) return false;
|
|
1238
|
+
if (licenseCategory === 'unknown' && !showUnknown) return false;
|
|
1239
|
+
|
|
1240
|
+
return true;
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
function sortDeps(deps) {
|
|
1245
|
+
const sortBy = controls.sort.value;
|
|
1246
|
+
const sorted = [...deps];
|
|
1247
|
+
|
|
1248
|
+
if (sortBy === 'name') {
|
|
1249
|
+
sorted.sort((a, b) => a.name.localeCompare(b.name));
|
|
1250
|
+
} else if (sortBy === 'depth') {
|
|
1251
|
+
sorted.sort((a, b) => a.depth - b.depth);
|
|
1252
|
+
} else if (sortBy === 'severity') {
|
|
1253
|
+
sorted.sort((a, b) => severityOrder[highestSeverity(b)] - severityOrder[highestSeverity(a)]);
|
|
1254
|
+
} else if (sortBy === 'size') {
|
|
1255
|
+
sorted.sort((a, b) => (b.sizeFootprint?.installedSize || 0) - (a.sizeFootprint?.installedSize || 0));
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
if (!sortAscending) sorted.reverse();
|
|
1259
|
+
return sorted;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
function indicator(text, tone) {
|
|
1263
|
+
return '<div class="indicator"><span class="indicator-dot ' + tone + '"></span>' + escapeHtml(text) + '</div>';
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
function indicatorSeparator() {
|
|
1267
|
+
return '<div class="indicator-separator"></div>';
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
function renderKvItem(label, value, hint) {
|
|
1271
|
+
let html = '<div class="kv-item">';
|
|
1272
|
+
html += '<span class="kv-label">' + escapeHtml(label) + '</span>';
|
|
1273
|
+
html += '<span class="kv-value">' + escapeHtml(String(value)) + '</span>';
|
|
1274
|
+
if (hint) html += '<span class="kv-hint">' + escapeHtml(hint) + '</span>';
|
|
1275
|
+
html += '</div>';
|
|
1276
|
+
return html;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
function renderPackageList(packages, maxShow) {
|
|
1280
|
+
if (!packages || packages.length === 0) return '<span class="kv-value">None</span>';
|
|
1281
|
+
const shown = packages.slice(0, maxShow);
|
|
1282
|
+
const remaining = packages.length - maxShow;
|
|
1283
|
+
let html = '<div class="package-list">';
|
|
1284
|
+
shown.forEach(pkg => {
|
|
1285
|
+
html += '<span class="package-tag">' + escapeHtml(pkg) + '</span>';
|
|
1286
|
+
});
|
|
1287
|
+
if (remaining > 0) {
|
|
1288
|
+
html += '<span class="package-tag">+' + remaining + ' more</span>';
|
|
1289
|
+
}
|
|
1290
|
+
html += '</div>';
|
|
1291
|
+
return html;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
function renderSection(title, desc, bodyHtml) {
|
|
1295
|
+
let html = '<div class="section">';
|
|
1296
|
+
html += '<div class="section-header">';
|
|
1297
|
+
html += '<span class="section-title">' + escapeHtml(title) + '</span>';
|
|
1298
|
+
if (desc) html += '<span class="section-desc">' + escapeHtml(desc) + '</span>';
|
|
1299
|
+
html += '</div>';
|
|
1300
|
+
html += bodyHtml;
|
|
1301
|
+
html += '</div>';
|
|
1302
|
+
return html;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
function renderKvSection(title, desc, items) {
|
|
1306
|
+
return renderSection(title, desc, '<div class="kv-grid">' + items.join('') + '</div>');
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
function renderPackageLinks(links) {
|
|
1310
|
+
const icons = {
|
|
1311
|
+
npm: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M0 7.334v8h6.666v1.332H12v-1.332h12v-8H0zm6.666 6.664H5.334v-4H3.999v4H1.335V8.667h5.331v5.331zm4 0v1.336H8.001V8.667h5.334v5.332h-2.669v-.001zm12.001 0h-1.33v-4h-1.336v4h-1.335v-4h-1.33v4h-2.671V8.667h8.002v5.331zM10.665 10H12v2.667h-1.335V10z"/></svg>',
|
|
1312
|
+
repo: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/></svg>',
|
|
1313
|
+
bugs: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>',
|
|
1314
|
+
homepage: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>'
|
|
1315
|
+
};
|
|
1316
|
+
|
|
1317
|
+
let html = '<div class="package-links">';
|
|
1318
|
+
|
|
1319
|
+
// npm link is always present
|
|
1320
|
+
html += '<a href="' + escapeHtml(links.npm) + '" target="_blank" rel="noopener" class="package-link">' + icons.npm + 'npm</a>';
|
|
1321
|
+
|
|
1322
|
+
if (links.repository) {
|
|
1323
|
+
html += '<a href="' + escapeHtml(links.repository) + '" target="_blank" rel="noopener" class="package-link">' + icons.repo + 'Repository</a>';
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
if (links.homepage) {
|
|
1327
|
+
html += '<a href="' + escapeHtml(links.homepage) + '" target="_blank" rel="noopener" class="package-link">' + icons.homepage + 'Homepage</a>';
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
if (links.bugs) {
|
|
1331
|
+
html += '<a href="' + escapeHtml(links.bugs) + '" target="_blank" rel="noopener" class="package-link">' + icons.bugs + 'Issues</a>';
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
html += '</div>';
|
|
1335
|
+
return html;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
function renderDep(dep) {
|
|
1339
|
+
const licenseText = dep.license.license || 'Unknown';
|
|
1340
|
+
const severity = highestSeverity(dep);
|
|
1341
|
+
const licenseCategory = getLicenseCategory(licenseText);
|
|
1342
|
+
const highestRisk = getHighestRisk(dep);
|
|
1343
|
+
|
|
1344
|
+
const licenseCategoryDisplay = {
|
|
1345
|
+
permissive: { text: 'Permissive', class: 'green' },
|
|
1346
|
+
weakCopyleft: { text: 'Weak Copyleft', class: 'amber' },
|
|
1347
|
+
strongCopyleft: { text: 'Strong Copyleft', class: 'red' },
|
|
1348
|
+
unknown: { text: 'Unknown', class: 'gray' }
|
|
1349
|
+
};
|
|
1350
|
+
|
|
1351
|
+
// Use new terminology: Dependency/Sub-Dependency instead of Direct/Transitive
|
|
1352
|
+
const depTypeText = dep.direct ? 'Dependency' : 'Sub-Dependency';
|
|
1353
|
+
const depTypeClass = dep.direct ? 'green' : 'amber';
|
|
1354
|
+
|
|
1355
|
+
const indicators = [
|
|
1356
|
+
indicator(depTypeText, depTypeClass),
|
|
1357
|
+
indicatorSeparator(),
|
|
1358
|
+
indicator(dep.runtimeClass, dep.runtimeClass === 'runtime' ? 'green' : dep.runtimeClass === 'build-time' ? 'amber' : 'gray'),
|
|
1359
|
+
indicatorSeparator(),
|
|
1360
|
+
indicator(licenseText + ' (' + licenseCategoryDisplay[licenseCategory].text + ')', licenseCategoryDisplay[licenseCategory].class),
|
|
1361
|
+
indicatorSeparator(),
|
|
1362
|
+
indicator('Vulns: ' + severity, dep.vulnRisk),
|
|
1363
|
+
indicatorSeparator(),
|
|
1364
|
+
indicator(dep.usage.status, dep.usage.status === 'unused' ? 'red' : dep.usage.status === 'used' ? 'green' : 'gray')
|
|
1365
|
+
];
|
|
1366
|
+
|
|
1367
|
+
const summary = [
|
|
1368
|
+
'<summary class="dep-summary">',
|
|
1369
|
+
'<svg class="expand-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m9 18 6-6-6-6"/></svg>',
|
|
1370
|
+
'<span class="dep-name">' + escapeHtml(dep.name) + '<span class="dep-version">@' + escapeHtml(dep.version) + '</span></span>',
|
|
1371
|
+
'<div class="dep-indicators">',
|
|
1372
|
+
indicators.join(''),
|
|
1373
|
+
'</div>',
|
|
1374
|
+
'</summary>'
|
|
1375
|
+
].join('');
|
|
1376
|
+
|
|
1377
|
+
const vulnRows = dep.vulnerabilities.items.map((v) =>
|
|
1378
|
+
'<tr data-severity="' + v.severity + '"><td>' + escapeHtml(v.title) + '</td><td>' + escapeHtml(v.severity) + '</td><td>' + escapeHtml(v.vulnerableRange || '') + '</td><td><a href="' + escapeHtml(v.url || '#') + '" target="_blank" rel="noopener">Link</a></td></tr>'
|
|
1379
|
+
).join('');
|
|
1380
|
+
|
|
1381
|
+
const vulnTable = dep.vulnerabilities.items.length
|
|
1382
|
+
? '<table class="vuln-table"><thead><tr><th>Title</th><th>Severity</th><th>Range</th><th>Ref</th></tr></thead><tbody>' + vulnRows + '</tbody></table>'
|
|
1383
|
+
: '<p class="no-vulns">No known vulnerabilities.</p>';
|
|
1384
|
+
|
|
1385
|
+
const rawJson = JSON.stringify(dep, null, 2);
|
|
1386
|
+
|
|
1387
|
+
// Parents as package names, not keys
|
|
1388
|
+
const parentNames = (dep.parents || []).map(key => key.split('@')[0]);
|
|
1389
|
+
const installedBy = (dep.rootCauses || []).join(', ') || (dep.direct ? 'package.json' : 'Unknown');
|
|
1390
|
+
|
|
1391
|
+
const overviewSection = renderKvSection('Overview', 'Dependency position and runtime classification', [
|
|
1392
|
+
renderKvItem('Type', depTypeText, dep.direct ? 'Listed in package.json' : 'Installed as a sub-dependency'),
|
|
1393
|
+
renderKvItem('Depth', dep.depth, 'How deep this package is in the dependency tree'),
|
|
1394
|
+
renderKvItem('Parents', parentNames.join(', ') || 'None (direct dependency)', 'Packages that directly depend on this'),
|
|
1395
|
+
renderKvItem('Installed By', installedBy, 'Root dependency in package.json that causes this to be installed'),
|
|
1396
|
+
renderKvItem('Runtime Class', dep.runtimeClass, dep.runtimeReason)
|
|
1397
|
+
]);
|
|
1398
|
+
|
|
1399
|
+
const licenseSection = renderKvSection('License', 'License information for this package', [
|
|
1400
|
+
renderKvItem('License', licenseText, 'The declared license'),
|
|
1401
|
+
renderKvItem('Category', licenseCategoryDisplay[licenseCategory].text, 'Business-friendliness classification'),
|
|
1402
|
+
renderKvItem('License File', dep.license.licenseFile || 'Not found', 'Path to license file if present')
|
|
1403
|
+
]);
|
|
1404
|
+
|
|
1405
|
+
const vulnSection = renderSection('Vulnerabilities', 'Known security issues from npm audit', vulnTable);
|
|
1406
|
+
|
|
1407
|
+
const maintenanceSection = renderSection(
|
|
1408
|
+
'Maintenance',
|
|
1409
|
+
'Package maintenance status',
|
|
1410
|
+
'<div class="kv-grid">' +
|
|
1411
|
+
renderKvItem('Status', dep.maintenance.status, dep.maintenance.reason) +
|
|
1412
|
+
(dep.maintenance.lastPublished ? renderKvItem('Last Published', dep.maintenance.lastPublished, '') : '') +
|
|
1413
|
+
'</div>'
|
|
1414
|
+
);
|
|
1415
|
+
|
|
1416
|
+
const usageSection = renderSection(
|
|
1417
|
+
'Usage',
|
|
1418
|
+
'Whether this package is actively used in your code',
|
|
1419
|
+
'<div class="kv-grid">' + renderKvItem('Status', dep.usage.status, dep.usage.reason) + '</div>'
|
|
1420
|
+
);
|
|
1421
|
+
|
|
1422
|
+
const identitySection = renderKvSection('Identity & Metadata', 'Package metadata', [
|
|
1423
|
+
renderKvItem('Deprecated', yesNo(dep.identity.deprecated), 'Whether the author has deprecated this package'),
|
|
1424
|
+
renderKvItem('Node Engine', dep.identity.nodeEngine || 'Any', 'Required Node.js version'),
|
|
1425
|
+
renderKvItem('Repository', yesNo(dep.identity.hasRepository), 'Whether source repo is linked'),
|
|
1426
|
+
renderKvItem('Funding', yesNo(dep.identity.hasFunding), 'Whether funding info is provided')
|
|
1427
|
+
]);
|
|
1428
|
+
|
|
1429
|
+
const dependencySurfaceSection = renderKvSection('Dependency Surface', 'What this package depends on', [
|
|
1430
|
+
renderKvItem('Dependencies', dep.dependencySurface.dependencies + ' prod / ' + dep.dependencySurface.devDependencies + ' dev / ' + dep.dependencySurface.peerDependencies + ' peer / ' + dep.dependencySurface.optionalDependencies + ' optional', ''),
|
|
1431
|
+
renderKvItem('Has Peer Dependencies', yesNo(dep.dependencySurface.hasPeerDependencies), 'Peer deps can complicate upgrades')
|
|
1432
|
+
]);
|
|
1433
|
+
|
|
1434
|
+
const sizeSection = renderKvSection('Size & Footprint', 'Disk space usage', [
|
|
1435
|
+
renderKvItem('Installed Size', formatBytes(dep.sizeFootprint.installedSize), 'Total size on disk'),
|
|
1436
|
+
renderKvItem('File Count', dep.sizeFootprint.fileCount, 'Number of files installed')
|
|
1437
|
+
]);
|
|
1438
|
+
|
|
1439
|
+
const buildSection = renderKvSection('Build & Platform', 'Build complexity indicators', [
|
|
1440
|
+
renderKvItem('Native Code', yesNo(dep.buildPlatform.nativeBindings), 'Requires compilation'),
|
|
1441
|
+
renderKvItem('Install Scripts', yesNo(dep.buildPlatform.installScripts), 'Runs code during install')
|
|
1442
|
+
]);
|
|
1443
|
+
|
|
1444
|
+
const moduleSection = renderKvSection('Module System', 'Module format information', [
|
|
1445
|
+
renderKvItem('Format', dep.moduleSystem.format, 'CommonJS, ESM, or dual'),
|
|
1446
|
+
renderKvItem('Conditional Exports', yesNo(dep.moduleSystem.conditionalExports), 'Uses exports field')
|
|
1447
|
+
]);
|
|
1448
|
+
|
|
1449
|
+
const typesSection = renderKvSection('TypeScript', 'Type definition support', [
|
|
1450
|
+
renderKvItem('Types', dep.typescript.types === 'bundled' ? 'Bundled' : 'None', 'Whether types are included')
|
|
1451
|
+
]);
|
|
1452
|
+
|
|
1453
|
+
// Graph section with package lists
|
|
1454
|
+
const dependedOnByList = dep.graph?.dependedOnBy || [];
|
|
1455
|
+
const dependsOnList = dep.graph?.dependsOn || [];
|
|
1456
|
+
|
|
1457
|
+
const graphSection = renderSection('Graph Shape', 'Dependency graph connections',
|
|
1458
|
+
'<div class="kv-grid">' +
|
|
1459
|
+
'<div class="kv-item"><span class="kv-label">Depended On By (' + dep.graph.fanIn + ')</span>' + renderPackageList(dependedOnByList, 8) + '</div>' +
|
|
1460
|
+
'<div class="kv-item"><span class="kv-label">Depends On (' + dep.graph.fanOut + ')</span>' + renderPackageList(dependsOnList, 8) + '</div>' +
|
|
1461
|
+
'</div>'
|
|
1462
|
+
);
|
|
1463
|
+
|
|
1464
|
+
return [
|
|
1465
|
+
'<details class="dep-card" data-risk="' + highestRisk + '">',
|
|
1466
|
+
summary,
|
|
1467
|
+
'<div class="dep-details">',
|
|
1468
|
+
renderPackageLinks(dep.links),
|
|
1469
|
+
overviewSection,
|
|
1470
|
+
licenseSection,
|
|
1471
|
+
vulnSection,
|
|
1472
|
+
maintenanceSection,
|
|
1473
|
+
usageSection,
|
|
1474
|
+
identitySection,
|
|
1475
|
+
dependencySurfaceSection,
|
|
1476
|
+
sizeSection,
|
|
1477
|
+
buildSection,
|
|
1478
|
+
moduleSection,
|
|
1479
|
+
typesSection,
|
|
1480
|
+
graphSection,
|
|
1481
|
+
'<details class="raw-data-toggle"><summary>View raw data</summary><pre>' + escapeHtml(rawJson) + '</pre></details>',
|
|
1482
|
+
'</div>',
|
|
1483
|
+
'</details>'
|
|
1484
|
+
].join('');
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
function renderList() {
|
|
1488
|
+
const filtered = applyFilters();
|
|
1489
|
+
const deps = sortDeps(filtered);
|
|
1490
|
+
|
|
1491
|
+
summaryEl.innerHTML = 'Showing <strong>' + deps.length + '</strong> of <strong>' + report.dependencies.length + '</strong> dependencies';
|
|
1492
|
+
|
|
1493
|
+
if (deps.length === 0) {
|
|
1494
|
+
container.innerHTML = '<div class="empty-state"><div class="empty-state-icon">📦</div><div class="empty-state-text">No dependencies match your filters</div></div>';
|
|
1495
|
+
return;
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
container.innerHTML = deps.map(renderDep).join('');
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
// Event listeners
|
|
1502
|
+
[controls.search, controls.direct, controls.runtime, controls.sort, controls.hasVulns, controls.unusedOnly,
|
|
1503
|
+
controls.licensePermissive, controls.licenseWeakCopyleft, controls.licenseStrongCopyleft, controls.licenseUnknown
|
|
1504
|
+
].forEach((ctrl) => {
|
|
1505
|
+
if (!ctrl) return;
|
|
1506
|
+
ctrl.addEventListener('input', renderList);
|
|
1507
|
+
ctrl.addEventListener('change', renderList);
|
|
1508
|
+
});
|
|
1509
|
+
|
|
1510
|
+
renderList();
|
|
1511
|
+
})();
|
|
1512
|
+
</script>
|
|
1513
|
+
</body>
|
|
1514
|
+
</html>`;
|
|
1515
|
+
}
|
|
1516
|
+
function escapeHtml(str) {
|
|
1517
|
+
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
1518
|
+
}
|
|
1519
|
+
function renderToolErrors(data) {
|
|
1520
|
+
const entries = Object.entries(data.toolErrors || {});
|
|
1521
|
+
if (!entries.length)
|
|
1522
|
+
return '';
|
|
1523
|
+
const list = entries.map(([tool, err]) => `<div><strong>${escapeHtml(tool)}:</strong> ${escapeHtml(err)}</div>`).join('');
|
|
1524
|
+
return `<div class="tool-errors"><strong>Some tools failed:</strong>${list}</div>`;
|
|
1525
|
+
}
|