dependency-radar 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/report.js CHANGED
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.renderReport = renderReport;
7
7
  const promises_1 = __importDefault(require("fs/promises"));
8
8
  const path_1 = __importDefault(require("path"));
9
+ const cta_1 = require("./cta");
9
10
  const report_assets_1 = require("./report-assets");
10
11
  async function renderReport(data, outputPath) {
11
12
  const html = buildHtml(data);
@@ -13,8 +14,8 @@ async function renderReport(data, outputPath) {
13
14
  await promises_1.default.writeFile(outputPath, html, 'utf8');
14
15
  }
15
16
  function buildHtml(data) {
16
- var _a, _b, _c;
17
17
  const json = JSON.stringify(data).replace(/</g, '\\u003c');
18
+ const ctaUrl = (0, cta_1.buildCtaUrl)(data.dependencyRadarVersion);
18
19
  // Format the generated date
19
20
  let formattedDate = data.generatedAt;
20
21
  try {
@@ -35,23 +36,55 @@ function buildHtml(data) {
35
36
  catch {
36
37
  // Keep the original if parsing fails
37
38
  }
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;
49
39
  return `<!doctype html>
50
40
  <html lang="en">
51
41
  <head>
52
42
  <meta charset="UTF-8" />
53
43
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
54
44
  <title>Dependency Radar</title>
45
+ <link
46
+ rel="icon"
47
+ type="image/svg+xml"
48
+ href="data:image/svg+xml;utf8,
49
+ <svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' viewBox='0 0 1024 1024'>
50
+ <defs>
51
+ <style>
52
+ .st0, .st1 {fill: %23ff8000;}
53
+ .st2, .st3 {fill: %2340ff40;}
54
+ .st3, .st4, .st1 {opacity: .4;}
55
+ .st5 {fill: url(%23linear-gradient);}
56
+ .st4, .st6 {fill: red;}
57
+ .st7 {stroke-width: 16px;}
58
+ .st7, .st8 {fill: %23171772;}
59
+ .st7, .st8, .st9 {stroke: %2355fffa; stroke-miterlimit: 10;}
60
+ .st8 {stroke-width: 10px;}
61
+ .st10 {fill: %2314145e;}
62
+ .st9 {fill: none; opacity: .3; stroke-width: 6px;}
63
+ .st11 {fill: %2355fffa;}
64
+ </style>
65
+ <linearGradient id='linear-gradient' x1='239.3' y1='298.2' x2='815.3' y2='298.2' gradientTransform='translate(150 -115.1) rotate(15)' gradientUnits='userSpaceOnUse'>
66
+ <stop offset='.4' stop-color='%2355fffa' stop-opacity='0'/>
67
+ <stop offset='1' stop-color='%2355fffa' stop-opacity='.5'/>
68
+ </linearGradient>
69
+ </defs>
70
+ <circle class='st10' cx='512' cy='512' r='512'/>
71
+ <circle class='st7' cx='512' cy='512' r='430'/>
72
+ <circle class='st8' cx='512' cy='512' r='256'/>
73
+ <circle class='st9' cx='512' cy='512' r='160'/>
74
+ <circle class='st9' cx='512' cy='512' r='379.5'/>
75
+ <circle class='st9' cx='512' cy='512' r='339'/>
76
+ <circle class='st9' cx='512' cy='512' r='210'/>
77
+ <rect class='st11' x='690.2' y='193.1' width='15.8' height='427.6' transform='translate(701.4 -401.1) rotate(60)'/>
78
+ <circle class='st11' cx='512' cy='514.4' r='64'/>
79
+ <path class='st5' d='M517.2,513.4l365.8-213.9c-54.6-95.4-145.8-169.8-260.3-200.5-100.1-26.8-201.5-15.8-288.8,24.3l183.4,390Z'/>
80
+ <circle class='st4' cx='512' cy='256' r='96'/>
81
+ <circle class='st6' cx='512' cy='256' r='56'/>
82
+ <circle class='st3' cx='733.7' cy='640' r='96' transform='translate(-237.7 706.3) rotate(-45)'/>
83
+ <circle class='st2' cx='733.7' cy='640' r='56' transform='translate(-237.7 706.3) rotate(-45)'/>
84
+ <ellipse class='st1' cx='290.3' cy='640' rx='96' ry='96' transform='translate(-367.5 392.7) rotate(-45)'/>
85
+ <circle class='st0' cx='290.3' cy='640' r='56'/>
86
+ </svg>"
87
+ >
55
88
  <style>
56
89
  ${report_assets_1.CSS_CONTENT}
57
90
  </style>
@@ -61,79 +94,62 @@ ${report_assets_1.CSS_CONTENT}
61
94
  <header class="top-header">
62
95
  <div class="header-row">
63
96
  <div class="header-content">
64
- <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"
65
- viewBox="0 0 1024 1024" class="logo">
97
+ <svg class="logo" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 1024 1024">
66
98
  <defs>
67
99
  <style>
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; }
100
+ .st0, .st1 {fill: #ff8000;}
101
+ .st2, .st3 {fill: #40ff40;}
102
+ .st3, .st4, .st1 {opacity: .4;}
103
+ .st5 {fill: url(#linear-gradient);}
104
+ .st4, .st6 {fill: red;}
105
+ .st7 {stroke-width: 16px;}
106
+ .st7, .st8 {fill: #171772;}
107
+ .st7, .st8, .st9 {stroke: #55fffa; stroke-miterlimit: 10;}
108
+ .st8 {stroke-width: 10px;}
109
+ .st10 {fill: #14145e;}
110
+ .st9 {fill: none; opacity: .3; stroke-width: 6px;}
111
+ .st11 {fill: #55fffa;}
81
112
  </style>
82
- <linearGradient id="linear-gradient" x1="225" y1="287" x2="831.3" y2="287" gradientUnits="userSpaceOnUse">
83
- <stop offset=".4" stop-color="#55fffa" stop-opacity="0" />
84
- <stop offset="1" stop-color="#55fffa" stop-opacity=".5" />
113
+ <linearGradient id="linear-gradient" x1="239.3" y1="298.2" x2="815.3" y2="298.2" gradientTransform="translate(150 -115.1) rotate(15)" gradientUnits="userSpaceOnUse">
114
+ <stop offset=".4" stop-color="#55fffa" stop-opacity="0"/>
115
+ <stop offset="1" stop-color="#55fffa" stop-opacity=".5"/>
85
116
  </linearGradient>
86
117
  </defs>
87
- <circle class="st6" cx="512" cy="512" r="512" />
88
- <circle class="st5" cx="512" cy="512" r="450" />
89
- <circle class="st3" cx="512" cy="512" r="325" />
90
- <circle class="st2" cx="512" cy="512" r="200" />
91
- <circle class="st4" cx="512" cy="512" r="80" />
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" />
102
- <circle class="st1" cx="400.1" cy="673" r="50" />
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" />
118
+ <circle class="st10" cx="512" cy="512" r="512"/>
119
+ <circle class="st7" cx="512" cy="512" r="430"/>
120
+ <circle class="st8" cx="512" cy="512" r="256"/>
121
+ <circle class="st9" cx="512" cy="512" r="160"/>
122
+ <circle class="st9" cx="512" cy="512" r="379.5"/>
123
+ <circle class="st9" cx="512" cy="512" r="339"/>
124
+ <circle class="st9" cx="512" cy="512" r="210"/>
125
+ <rect class="st11" x="690.2" y="193.1" width="15.8" height="427.6" transform="translate(701.4 -401.1) rotate(60)"/>
126
+ <circle class="st11" cx="512" cy="514.4" r="64"/>
127
+ <path class="st5" d="M517.2,513.4l365.8-213.9c-54.6-95.4-145.8-169.8-260.3-200.5-100.1-26.8-201.5-15.8-288.8,24.3l183.4,390Z"/>
128
+ <circle class="st4" cx="512" cy="256" r="96"/>
129
+ <circle class="st6" cx="512" cy="256" r="56"/>
130
+ <circle class="st3" cx="733.7" cy="640" r="96" transform="translate(-237.7 706.3) rotate(-45)"/>
131
+ <circle class="st2" cx="733.7" cy="640" r="56" transform="translate(-237.7 706.3) rotate(-45)"/>
132
+ <ellipse class="st1" cx="290.3" cy="640" rx="96" ry="96" transform="translate(-367.5 392.7) rotate(-45)"/>
133
+ <circle class="st0" cx="290.3" cy="640" r="56"/>
114
134
  </svg>
115
135
  <div class="header-text">
116
136
  <h1>Dependency Radar</h1>
117
137
  <div class="header-meta">
118
138
  <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>` : ''}
139
+ <span class="meta-item" id="git-branch-item" style="display: none;"><span class="meta-label">Branch</span> <strong id="git-branch"></strong></span>
140
+ <span class="meta-item" id="node-item" style="display: none;"><span class="meta-label">Node</span> <strong id="node-version"></strong></span>
121
141
  <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>` : ''}
142
+ <span class="header-disclaimer" id="node-disclaimer" style="display: none;"></span>
123
143
  </div>
124
144
  </div>
125
145
  </div>
126
146
  <div class="cta-section">
127
- <a href="https://dependency-radar.com" class="cta-link" target="_blank" rel="noopener">
128
- Get full analysis report
147
+ <a href="${escapeHtml(ctaUrl)}" class="cta-link" target="_blank" rel="noopener" id="cta-primary-link">
148
+ Enrich this scan
129
149
  <span class="cta-arrow">→</span>
130
150
  </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>
136
- <div class="cta-text">dependency-radar.com</div>
151
+ <p class="cta-text">Beyond the standalone report</p>
152
+ <a href="${escapeHtml(ctaUrl)}" target="_blank" rel="noopener" class="cta-url" id="cta-secondary-link">dependency-radar.com</a>
137
153
  </div>
138
154
  </div>
139
155
  </header>
@@ -141,90 +157,127 @@ ${report_assets_1.CSS_CONTENT}
141
157
  <!-- Sticky Filter Bar -->
142
158
  <div class="filter-bar">
143
159
  <div class="filter-bar-inner">
144
- <div class="search-wrapper">
145
- <svg class="search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
146
- <circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/>
147
- </svg>
148
- <input type="search" id="search" placeholder="Search packages..." />
149
- </div>
150
-
151
- <div class="filter-group">
152
- <span class="filter-label">Type</span>
153
- <select id="direct-filter">
154
- <option value="all">All</option>
155
- <option value="direct">Dependency</option>
156
- <option value="transitive">Sub-Dependency</option>
157
- </select>
158
- </div>
159
-
160
- <div class="filter-group">
161
- <span class="filter-label">Scope</span>
162
- <select id="runtime-filter">
163
- <option value="all">All</option>
164
- <option value="runtime">Runtime</option>
165
- <option value="dev">Dev</option>
166
- <option value="optional">Optional</option>
167
- <option value="peer">Peer</option>
168
- </select>
169
- </div>
170
-
171
- <div class="filter-group sort-wrapper">
172
- <span class="filter-label">Sort</span>
173
- <select id="sort-by">
174
- <option value="name">Name</option>
175
- <option value="severity">Severity</option>
176
- <option value="depth">Depth</option>
177
- </select>
178
- <button type="button" class="sort-direction-btn" id="sort-direction" title="Toggle sort direction">↑</button>
179
- </div>
180
-
181
- <button type="button" class="license-filter-toggle" id="license-toggle">
182
- License Categories
183
- <span class="chevron">▼</span>
184
- </button>
185
-
186
- <label class="checkbox-filter">
187
- <input type="checkbox" id="has-vulns" />
188
- Has vulnerabilities
189
- </label>
190
-
191
- <div class="theme-toggle">
192
- <span class="theme-toggle-label">Theme</span>
193
- <div class="theme-switch" id="theme-switch" title="Toggle dark/light mode"></div>
194
- </div>
195
- </div>
196
-
197
- <!-- Collapsible License Filter Panel (inside sticky bar) -->
198
- <div class="license-filter-panel" id="license-panel">
199
- <div class="license-filter-inner">
200
- <div class="license-filter-header">
201
- <span class="license-filter-title">Filter by License Type</span>
202
- <div class="license-quick-actions">
203
- <button type="button" class="quick-action-btn" id="license-all">Show All</button>
204
- <button type="button" class="quick-action-btn" id="license-friendly">Business-Friendly Only</button>
160
+ <div class="filter-row">
161
+ <div class="filter-top-row">
162
+ <div class="search-wrapper">
163
+ <svg class="search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
164
+ <circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/>
165
+ </svg>
166
+ <input type="search" id="search" placeholder="Search packages..." />
205
167
  </div>
168
+ <button
169
+ type="button"
170
+ class="filters-toggle"
171
+ id="filters-toggle"
172
+ aria-expanded="false"
173
+ >
174
+ Filters
175
+ <span class="chevron">▼</span>
176
+ </button>
206
177
  </div>
207
- <div class="license-groups">
208
- <label class="license-group-checkbox">
209
- <input type="checkbox" id="license-permissive" checked />
210
- <span class="license-dot permissive"></span>
211
- Permissive (MIT, BSD, Apache, ISC)
212
- </label>
213
- <label class="license-group-checkbox">
214
- <input type="checkbox" id="license-weak-copyleft" checked />
215
- <span class="license-dot weak-copyleft"></span>
216
- Weak Copyleft (LGPL, MPL, EPL)
217
- </label>
218
- <label class="license-group-checkbox">
219
- <input type="checkbox" id="license-strong-copyleft" checked />
220
- <span class="license-dot strong-copyleft"></span>
221
- Strong Copyleft (GPL, AGPL)
222
- </label>
223
- <label class="license-group-checkbox">
224
- <input type="checkbox" id="license-unknown" checked />
225
- <span class="license-dot unknown"></span>
226
- Other / Unknown
227
- </label>
178
+
179
+ <div class="filter-controls" id="filter-controls">
180
+ <div class="filter-controls-row">
181
+ <div class="filter-group">
182
+ <span class="filter-label">Type</span>
183
+ <select id="direct-filter">
184
+ <option value="all">All</option>
185
+ <option value="direct">Dependency</option>
186
+ <option value="transitive">Sub-Dependency</option>
187
+ </select>
188
+ </div>
189
+
190
+ <div class="filter-group">
191
+ <span class="filter-label">Scope</span>
192
+ <select id="runtime-filter">
193
+ <option value="all">All</option>
194
+ <option value="runtime">Runtime</option>
195
+ <option value="dev">Dev</option>
196
+ <option value="optional">Optional</option>
197
+ <option value="peer">Peer</option>
198
+ </select>
199
+ </div>
200
+
201
+ <button type="button" class="license-filter-toggle" id="license-toggle">
202
+ License Categories
203
+ <span class="chevron">▼</span>
204
+ </button>
205
+
206
+ <label class="checkbox-filter">
207
+ <input type="checkbox" id="has-vulns" />
208
+ Has vulnerabilities
209
+ </label>
210
+
211
+ <!-- Sort dropdown - visible on mobile, hidden on desktop (replaced by column headers) -->
212
+ <div class="filter-group sort-wrapper mobile-only" id="mobile-sort">
213
+ <span class="filter-label">SORT</span>
214
+ <select id="sort-by">
215
+ <option value="name">Name</option>
216
+ <option value="type">Type</option>
217
+ <option value="scope">Scope</option>
218
+ <option value="license">License</option>
219
+ <option value="severity">Severity</option>
220
+ <option value="install">Install</option>
221
+ <option value="depth">Depth</option>
222
+ </select>
223
+ <button type="button" class="sort-direction-btn" id="sort-direction" title="Toggle sort direction">↑</button>
224
+ </div>
225
+
226
+ <div class="theme-toggle">
227
+ <span class="theme-toggle-label">Theme</span>
228
+ <div class="theme-switch" id="theme-switch" title="Toggle dark/light mode"></div>
229
+ </div>
230
+ </div>
231
+ </div>
232
+
233
+ <!-- Collapsible License Filter Panel -->
234
+ <div class="license-filter-panel-row">
235
+ <div class="license-filter-panel" id="license-panel">
236
+ <div class="license-filter-inner">
237
+ <div class="license-filter-header">
238
+ <span class="license-filter-title">Filter by License Type</span>
239
+ <div class="license-quick-actions">
240
+ <button type="button" class="quick-action-btn" id="license-all">Show All</button>
241
+ <button type="button" class="quick-action-btn" id="license-friendly">Business-Friendly Only</button>
242
+ </div>
243
+ </div>
244
+ <div class="license-groups">
245
+ <label class="license-group-checkbox">
246
+ <input type="checkbox" id="license-permissive" checked />
247
+ <span class="license-dot permissive"></span>
248
+ Permissive (MIT, BSD, Apache, ISC)
249
+ </label>
250
+ <label class="license-group-checkbox">
251
+ <input type="checkbox" id="license-weak-copyleft" checked />
252
+ <span class="license-dot weak-copyleft"></span>
253
+ Weak Copyleft (LGPL, MPL, EPL)
254
+ </label>
255
+ <label class="license-group-checkbox">
256
+ <input type="checkbox" id="license-strong-copyleft" checked />
257
+ <span class="license-dot strong-copyleft"></span>
258
+ Strong Copyleft (GPL, AGPL)
259
+ </label>
260
+ <label class="license-group-checkbox">
261
+ <input type="checkbox" id="license-unknown" checked />
262
+ <span class="license-dot unknown"></span>
263
+ Other / Unknown
264
+ </label>
265
+ </div>
266
+ </div>
267
+ </div>
268
+ </div>
269
+
270
+ <!-- Results summary and column headers row -->
271
+ <div class="column-headers-section" id="column-headers-section">
272
+ <div class="package-header-wrapper">
273
+ <div class="results-summary" id="results-summary"></div>
274
+ <button type="button" class="column-header package-header column-header-no-border" data-sort="name" id="package-header">
275
+ PACKAGE
276
+ <span class="sort-indicator"></span>
277
+ </button>
278
+ </div>
279
+ <!-- Column headers are dynamically generated by JavaScript from COLUMN_CONFIG -->
280
+ <div id="column-headers-container"></div>
228
281
  </div>
229
282
  </div>
230
283
  </div>
@@ -232,7 +285,6 @@ ${report_assets_1.CSS_CONTENT}
232
285
 
233
286
  <!-- Main Content -->
234
287
  <main class="main-content">
235
- <div class="results-summary" id="results-summary"></div>
236
288
  <div id="dependency-list" class="dependency-grid"></div>
237
289
  </main>
238
290
 
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.runNpmLs = runNpmLs;
7
7
  const path_1 = __importDefault(require("path"));
8
+ const fs_1 = __importDefault(require("fs"));
8
9
  const utils_1 = require("../utils");
9
10
  const PNPM_DEPTH_ATTEMPTS = ['Infinity', '8', '4', '2', '1'];
10
11
  const PNPM_MAX_OLD_SPACE_SIZE_MB = '8192';
@@ -45,7 +46,7 @@ function buildLsCommand(tool) {
45
46
  };
46
47
  }
47
48
  async function runPnpmLsWithFallback(projectPath, targetFile, options) {
48
- const normalize = normalizePnpmTree;
49
+ const installState = createPnpmInstallState(projectPath);
49
50
  const attempts = [];
50
51
  const env = {
51
52
  NODE_OPTIONS: ensureNodeMaxOldSpaceSize(process.env.NODE_OPTIONS, PNPM_MAX_OLD_SPACE_SIZE_MB)
@@ -57,7 +58,7 @@ async function runPnpmLsWithFallback(projectPath, targetFile, options) {
57
58
  env
58
59
  });
59
60
  const parsed = parseJsonOutput(result.stdout);
60
- const normalized = normalize(parsed);
61
+ const normalized = normalizePnpmTree(parsed, installState);
61
62
  const outOfMemory = isOutOfMemoryError(result.stderr);
62
63
  attempts.push({
63
64
  depth,
@@ -212,15 +213,23 @@ function normalizeNpmNode(name, node) {
212
213
  }
213
214
  return out;
214
215
  }
215
- function normalizePnpmTree(data) {
216
- const roots = Array.isArray(data) ? data : [data];
217
- const root = roots.find((entry) => entry && typeof entry === 'object');
216
+ function normalizePnpmTree(data, installState) {
217
+ const roots = (Array.isArray(data) ? data : [data]).filter((entry) => entry && typeof entry === 'object');
218
+ const root = roots.find((entry) => !isPnpmErrorPayload(entry));
218
219
  if (!root || typeof root !== 'object')
219
220
  return undefined;
220
- const dependencies = collectPnpmDependencyMap(root);
221
+ const dependencies = collectPnpmDependencyMap(root, installState);
221
222
  return { dependencies };
222
223
  }
223
- function collectPnpmDependencyMap(node) {
224
+ function isPnpmErrorPayload(node) {
225
+ if (!node || typeof node !== 'object')
226
+ return false;
227
+ if (!('error' in node))
228
+ return false;
229
+ const error = node.error;
230
+ return typeof error === 'string' || (error !== null && typeof error === 'object');
231
+ }
232
+ function collectPnpmDependencyMap(node, installState) {
224
233
  const out = {};
225
234
  const groups = ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'];
226
235
  for (const group of groups) {
@@ -234,7 +243,7 @@ function collectPnpmDependencyMap(node) {
234
243
  const name = typeof entry.name === 'string' ? entry.name : undefined;
235
244
  if (!name)
236
245
  continue;
237
- const normalized = normalizePnpmNode(name, entry);
246
+ const normalized = normalizePnpmNode(name, entry, installState);
238
247
  if (normalized)
239
248
  out[name] = normalized;
240
249
  }
@@ -244,7 +253,7 @@ function collectPnpmDependencyMap(node) {
244
253
  for (const [name, entry] of Object.entries(value)) {
245
254
  if (!entry || typeof entry !== 'object')
246
255
  continue;
247
- const normalized = normalizePnpmNode(name, entry);
256
+ const normalized = normalizePnpmNode(name, entry, installState);
248
257
  if (normalized)
249
258
  out[name] = normalized;
250
259
  }
@@ -254,19 +263,21 @@ function collectPnpmDependencyMap(node) {
254
263
  return out;
255
264
  if ((node === null || node === void 0 ? void 0 : node.dependencies) && typeof node.dependencies === 'object') {
256
265
  for (const [name, entry] of Object.entries(node.dependencies)) {
257
- const normalized = normalizePnpmNode(name, entry);
266
+ const normalized = normalizePnpmNode(name, entry, installState);
258
267
  if (normalized)
259
268
  out[name] = normalized;
260
269
  }
261
270
  }
262
271
  return out;
263
272
  }
264
- function normalizePnpmNode(name, node) {
273
+ function normalizePnpmNode(name, node, installState) {
265
274
  const version = typeof (node === null || node === void 0 ? void 0 : node.version) === 'string' ? node.version.trim() : '';
266
275
  if (!version || version === 'unknown' || version === 'missing' || version === 'invalid')
267
276
  return undefined;
277
+ if (!isPnpmPackageInstalled(name, version, installState))
278
+ return undefined;
268
279
  const out = { name, version, dependencies: {} };
269
- const childMap = collectPnpmDependencyMap(node);
280
+ const childMap = collectPnpmDependencyMap(node, installState);
270
281
  if (Object.keys(childMap).length > 0) {
271
282
  out.dependencies = childMap;
272
283
  }
@@ -277,6 +288,103 @@ function normalizePnpmNode(name, node) {
277
288
  out.dev = Boolean(node.dev);
278
289
  return out;
279
290
  }
291
+ function createPnpmInstallState(projectPath) {
292
+ const nodeModulesRoots = findNodeModulesRoots(projectPath);
293
+ const virtualStoreEntries = new Set();
294
+ for (const root of nodeModulesRoots) {
295
+ const virtualStoreDir = path_1.default.join(root, '.pnpm');
296
+ if (!safePathExists(virtualStoreDir))
297
+ continue;
298
+ for (const entry of safeReadDirNames(virtualStoreDir)) {
299
+ virtualStoreEntries.add(entry);
300
+ }
301
+ }
302
+ return {
303
+ enabled: virtualStoreEntries.size > 0 || nodeModulesRoots.length > 0,
304
+ virtualStoreEntries,
305
+ nodeModulesRoots,
306
+ installedCache: new Map()
307
+ };
308
+ }
309
+ function findNodeModulesRoots(startPath) {
310
+ const roots = [];
311
+ let current = path_1.default.resolve(startPath);
312
+ while (true) {
313
+ const candidate = path_1.default.join(current, 'node_modules');
314
+ if (safePathExists(candidate)) {
315
+ roots.push(candidate);
316
+ }
317
+ const parent = path_1.default.dirname(current);
318
+ if (parent === current)
319
+ break;
320
+ current = parent;
321
+ }
322
+ return roots;
323
+ }
324
+ function isPnpmPackageInstalled(name, version, installState) {
325
+ if (!installState.enabled)
326
+ return true;
327
+ const cacheKey = `${name}@${version}`;
328
+ const cached = installState.installedCache.get(cacheKey);
329
+ if (cached !== undefined)
330
+ return cached;
331
+ const normalizedName = normalizeScopedPackageNameForPnpmStore(name);
332
+ const storePrefix = `${normalizedName}@${version}`;
333
+ for (const entry of installState.virtualStoreEntries) {
334
+ if (entry === storePrefix || entry.startsWith(`${storePrefix}_`) || entry.startsWith(`${storePrefix}(`)) {
335
+ installState.installedCache.set(cacheKey, true);
336
+ return true;
337
+ }
338
+ }
339
+ // Workspace links may resolve outside the virtual store, but still exist in node_modules.
340
+ for (const nodeModulesRoot of installState.nodeModulesRoots) {
341
+ const packageDir = path_1.default.join(nodeModulesRoot, ...name.split('/'));
342
+ if (safePathExists(packageDir) && packageDirectoryMatchesVersion(packageDir, version)) {
343
+ installState.installedCache.set(cacheKey, true);
344
+ return true;
345
+ }
346
+ }
347
+ installState.installedCache.set(cacheKey, false);
348
+ return false;
349
+ }
350
+ function normalizeScopedPackageNameForPnpmStore(name) {
351
+ if (!name.startsWith('@'))
352
+ return name;
353
+ const slashIndex = name.indexOf('/');
354
+ if (slashIndex <= 0)
355
+ return name;
356
+ return `${name.slice(0, slashIndex)}+${name.slice(slashIndex + 1)}`;
357
+ }
358
+ function safePathExists(targetPath) {
359
+ try {
360
+ return fs_1.default.existsSync(targetPath);
361
+ }
362
+ catch {
363
+ return false;
364
+ }
365
+ }
366
+ function safeReadDirNames(dirPath) {
367
+ try {
368
+ return fs_1.default.readdirSync(dirPath);
369
+ }
370
+ catch {
371
+ return [];
372
+ }
373
+ }
374
+ function packageDirectoryMatchesVersion(packageDir, expectedVersion) {
375
+ const pkgJsonPath = path_1.default.join(packageDir, 'package.json');
376
+ if (!safePathExists(pkgJsonPath))
377
+ return true;
378
+ try {
379
+ const raw = fs_1.default.readFileSync(pkgJsonPath, 'utf8');
380
+ const parsed = JSON.parse(raw);
381
+ const version = typeof (parsed === null || parsed === void 0 ? void 0 : parsed.version) === 'string' ? parsed.version.trim() : '';
382
+ return !version || version === expectedVersion;
383
+ }
384
+ catch {
385
+ return true;
386
+ }
387
+ }
280
388
  function normalizeYarnTree(data) {
281
389
  const treePayload = resolveYarnTreePayload(data);
282
390
  if (!treePayload || !Array.isArray(treePayload.trees))