npm-audit-report-cli 1.0.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/index.cjs ADDED
@@ -0,0 +1,613 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ ParseError: () => ParseError,
24
+ filterBySeverity: () => filterBySeverity,
25
+ format: () => format,
26
+ meetsThreshold: () => meetsThreshold,
27
+ parse: () => parse,
28
+ report: () => report,
29
+ shouldFail: () => shouldFail
30
+ });
31
+ module.exports = __toCommonJS(src_exports);
32
+
33
+ // src/parse.ts
34
+ var ParseError = class extends Error {
35
+ constructor(message) {
36
+ super(message);
37
+ this.name = "ParseError";
38
+ }
39
+ };
40
+ var VALID_SEVERITIES = ["critical", "high", "moderate", "low", "info"];
41
+ function normalizeSeverity(value) {
42
+ if (typeof value === "string" && VALID_SEVERITIES.includes(value)) {
43
+ return value;
44
+ }
45
+ return "info";
46
+ }
47
+ function buildSummary(vulns, metaCounts) {
48
+ const summary = {
49
+ critical: 0,
50
+ high: 0,
51
+ moderate: 0,
52
+ low: 0,
53
+ info: 0,
54
+ total: 0
55
+ };
56
+ for (const v of vulns) {
57
+ summary[v.severity] += 1;
58
+ summary.total += 1;
59
+ }
60
+ if (metaCounts && summary.total === 0) {
61
+ return {
62
+ critical: metaCounts.critical ?? 0,
63
+ high: metaCounts.high ?? 0,
64
+ moderate: metaCounts.moderate ?? 0,
65
+ low: metaCounts.low ?? 0,
66
+ info: metaCounts.info ?? 0,
67
+ total: metaCounts.total ?? (metaCounts.critical ?? 0) + (metaCounts.high ?? 0) + (metaCounts.moderate ?? 0) + (metaCounts.low ?? 0) + (metaCounts.info ?? 0)
68
+ };
69
+ }
70
+ return summary;
71
+ }
72
+ function buildMeta(raw) {
73
+ const totalDependencies = raw && typeof raw["totalDependencies"] === "number" ? raw["totalDependencies"] : 0;
74
+ return {
75
+ npmVersion: "",
76
+ nodeVersion: typeof process !== "undefined" && process.version ? process.version : "",
77
+ auditedAt: (/* @__PURE__ */ new Date()).toISOString(),
78
+ totalDependencies
79
+ };
80
+ }
81
+ function parseV1(data) {
82
+ const advisories = data["advisories"] ?? {};
83
+ const vulns = [];
84
+ for (const key of Object.keys(advisories)) {
85
+ const adv = advisories[key];
86
+ if (!adv) continue;
87
+ const cves = Array.isArray(adv["cves"]) ? adv["cves"] : [];
88
+ const firstCve = cves.length > 0 ? cves[0] : void 0;
89
+ const advisoryId = adv["id"];
90
+ const idStr = firstCve ?? (typeof advisoryId === "number" || typeof advisoryId === "string" ? `npm-advisory-${String(advisoryId)}` : `npm-advisory-${key}`);
91
+ const findings = Array.isArray(adv["findings"]) ? adv["findings"] : [];
92
+ const paths = [];
93
+ for (const f of findings) {
94
+ const p = f["paths"];
95
+ if (Array.isArray(p)) {
96
+ for (const path of p) {
97
+ if (typeof path === "string") paths.push(path);
98
+ }
99
+ }
100
+ }
101
+ const fixAvailable = adv["fixAvailable"] === true;
102
+ const name = typeof adv["module_name"] === "string" ? adv["module_name"] : typeof adv["name"] === "string" ? adv["name"] : paths[0] ?? "unknown";
103
+ const range = typeof adv["vulnerable_versions"] === "string" ? adv["vulnerable_versions"] : "*";
104
+ const patched = typeof adv["patched_versions"] === "string" ? adv["patched_versions"] : void 0;
105
+ const vuln = {
106
+ id: idStr,
107
+ name,
108
+ severity: normalizeSeverity(adv["severity"]),
109
+ title: typeof adv["title"] === "string" ? adv["title"] : "Unknown vulnerability",
110
+ url: typeof adv["url"] === "string" ? adv["url"] : "",
111
+ range,
112
+ fixAvailable,
113
+ paths
114
+ };
115
+ if (fixAvailable && patched && patched !== "<0.0.0") {
116
+ vuln.fixCommand = `npm install ${name}@${patched}`;
117
+ } else if (fixAvailable) {
118
+ vuln.fixCommand = `npm audit fix`;
119
+ }
120
+ vulns.push(vuln);
121
+ }
122
+ const metadata = data["metadata"] ?? {};
123
+ const metaCounts = metadata["vulnerabilities"] ?? void 0;
124
+ return {
125
+ meta: buildMeta(metadata),
126
+ summary: buildSummary(vulns, metaCounts),
127
+ vulnerabilities: vulns
128
+ };
129
+ }
130
+ function parseV2(data) {
131
+ const vulnsRaw = data["vulnerabilities"] ?? {};
132
+ const vulns = [];
133
+ for (const name of Object.keys(vulnsRaw)) {
134
+ const entry = vulnsRaw[name];
135
+ if (!entry) continue;
136
+ const via = Array.isArray(entry["via"]) ? entry["via"] : [];
137
+ let title = "Unknown vulnerability";
138
+ let url = "";
139
+ let range = typeof entry["range"] === "string" ? entry["range"] : "*";
140
+ let id = `npm:${name}`;
141
+ for (const v of via) {
142
+ if (typeof v === "object" && v !== null) {
143
+ if (typeof v["title"] === "string") title = v["title"];
144
+ if (typeof v["url"] === "string") url = v["url"];
145
+ if (typeof v["range"] === "string") range = v["range"];
146
+ if (typeof v["source"] === "number" || typeof v["source"] === "string") {
147
+ id = `GHSA-${String(v["source"])}`;
148
+ }
149
+ break;
150
+ }
151
+ }
152
+ const fix = entry["fixAvailable"];
153
+ let fixAvailable = false;
154
+ let fixCommand;
155
+ if (fix === true) {
156
+ fixAvailable = true;
157
+ fixCommand = `npm audit fix`;
158
+ } else if (typeof fix === "object" && fix !== null) {
159
+ fixAvailable = true;
160
+ const fixObj = fix;
161
+ const fixName = typeof fixObj["name"] === "string" ? fixObj["name"] : name;
162
+ const fixVersion = typeof fixObj["version"] === "string" ? fixObj["version"] : void 0;
163
+ if (fixVersion) {
164
+ fixCommand = `npm install ${fixName}@${fixVersion}`;
165
+ } else {
166
+ fixCommand = `npm audit fix`;
167
+ }
168
+ const isSemVerMajor = fixObj["isSemVerMajor"] === true;
169
+ if (isSemVerMajor) {
170
+ fixCommand = `npm audit fix --force`;
171
+ }
172
+ }
173
+ const nodes = Array.isArray(entry["nodes"]) ? entry["nodes"] : [];
174
+ const vuln = {
175
+ id,
176
+ name: typeof entry["name"] === "string" ? entry["name"] : name,
177
+ severity: normalizeSeverity(entry["severity"]),
178
+ title,
179
+ url,
180
+ range,
181
+ fixAvailable,
182
+ paths: nodes.filter((n) => typeof n === "string")
183
+ };
184
+ if (fixCommand) vuln.fixCommand = fixCommand;
185
+ vulns.push(vuln);
186
+ }
187
+ const metadata = data["metadata"] ?? {};
188
+ const metaCounts = metadata["vulnerabilities"] ?? void 0;
189
+ return {
190
+ meta: buildMeta(metadata),
191
+ summary: buildSummary(vulns, metaCounts),
192
+ vulnerabilities: vulns
193
+ };
194
+ }
195
+ function looksLikeV2(data) {
196
+ const v = data["vulnerabilities"];
197
+ if (typeof v !== "object" || v === null) return false;
198
+ return !Array.isArray(v);
199
+ }
200
+ function looksLikeV1(data) {
201
+ return typeof data["advisories"] === "object" && data["advisories"] !== null;
202
+ }
203
+ function parse(input) {
204
+ let data;
205
+ try {
206
+ data = JSON.parse(input);
207
+ } catch {
208
+ throw new ParseError("Invalid JSON input");
209
+ }
210
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
211
+ throw new ParseError("Unrecognized npm audit schema");
212
+ }
213
+ const obj = data;
214
+ const version = obj["auditReportVersion"];
215
+ if (version === 1) return parseV1(obj);
216
+ if (version === 2) return parseV2(obj);
217
+ if (looksLikeV2(obj)) return parseV2(obj);
218
+ if (looksLikeV1(obj)) return parseV1(obj);
219
+ throw new ParseError("Unrecognized npm audit schema");
220
+ }
221
+
222
+ // src/threshold.ts
223
+ var SEVERITY_ORDER = ["info", "low", "moderate", "high", "critical"];
224
+ function meetsThreshold(severity, threshold) {
225
+ return SEVERITY_ORDER.indexOf(severity) >= SEVERITY_ORDER.indexOf(threshold);
226
+ }
227
+ function shouldFail(report2, failOn) {
228
+ if (failOn === "none") return false;
229
+ return report2.vulnerabilities.some((v) => meetsThreshold(v.severity, failOn));
230
+ }
231
+ function filterBySeverity(vulns, minSeverity) {
232
+ return vulns.filter((v) => meetsThreshold(v.severity, minSeverity));
233
+ }
234
+
235
+ // src/formatters/markdown.ts
236
+ var SEVERITY_ORDER2 = ["critical", "high", "moderate", "low", "info"];
237
+ var SEVERITY_EMOJI = {
238
+ critical: "\u{1F534}",
239
+ high: "\u{1F7E0}",
240
+ moderate: "\u{1F7E1}",
241
+ low: "\u{1F535}",
242
+ info: "\u26AA"
243
+ };
244
+ var SEVERITY_LABEL = {
245
+ critical: "Critical",
246
+ high: "High",
247
+ moderate: "Moderate",
248
+ low: "Low",
249
+ info: "Info"
250
+ };
251
+ function countBySeverity(vulns) {
252
+ const result = {
253
+ critical: { count: 0, fixable: 0 },
254
+ high: { count: 0, fixable: 0 },
255
+ moderate: { count: 0, fixable: 0 },
256
+ low: { count: 0, fixable: 0 },
257
+ info: { count: 0, fixable: 0 }
258
+ };
259
+ for (const v of vulns) {
260
+ result[v.severity].count += 1;
261
+ if (v.fixAvailable) result[v.severity].fixable += 1;
262
+ }
263
+ return result;
264
+ }
265
+ function renderVulnerability(v) {
266
+ const lines = [];
267
+ lines.push(`### ${v.name}`);
268
+ lines.push(`- **ID:** ${v.id}`);
269
+ lines.push(`- **Title:** ${v.title}`);
270
+ lines.push(`- **Range:** \`${v.range}\``);
271
+ if (v.fixAvailable && v.fixCommand) {
272
+ lines.push(`- **Fix:** \`${v.fixCommand}\``);
273
+ } else {
274
+ lines.push(`- **Fix:** No fix available`);
275
+ }
276
+ if (v.url) lines.push(`- **Advisory:** ${v.url}`);
277
+ if (v.paths.length > 0) lines.push(`- **Paths:** \`${v.paths.join(", ")}\``);
278
+ return lines.join("\n");
279
+ }
280
+ function markdown(report2, opts = {}) {
281
+ const title = opts.title ?? "npm audit report";
282
+ const minSeverity = opts.severity ?? "info";
283
+ const filtered = filterBySeverity(report2.vulnerabilities, minSeverity);
284
+ const out = [];
285
+ out.push(`## ${title}`);
286
+ out.push("");
287
+ if (filtered.length === 0) {
288
+ out.push(`## \u2705 No vulnerabilities found`);
289
+ out.push("");
290
+ const date2 = report2.meta.auditedAt ? report2.meta.auditedAt.slice(0, 10) : "";
291
+ out.push(
292
+ `> 0 vulnerabilities found \xB7 audited ${date2} \xB7 ${report2.meta.totalDependencies} dependencies`
293
+ );
294
+ out.push("");
295
+ out.push(`---`);
296
+ out.push(`*Generated by [audit-report](https://github.com/dimasdarfi/audit-report)*`);
297
+ return out.join("\n");
298
+ }
299
+ const date = report2.meta.auditedAt ? report2.meta.auditedAt.slice(0, 10) : "";
300
+ out.push(
301
+ `> ${filtered.length} vulnerabilities found \xB7 audited ${date} \xB7 ${report2.meta.totalDependencies} dependencies`
302
+ );
303
+ out.push("");
304
+ const counts = countBySeverity(filtered);
305
+ out.push(`| Severity | Count | Fixable |`);
306
+ out.push(`|----------|-------|---------|`);
307
+ for (const sev of SEVERITY_ORDER2) {
308
+ const c = counts[sev];
309
+ if (c.count === 0) continue;
310
+ out.push(`| ${SEVERITY_EMOJI[sev]} ${SEVERITY_LABEL[sev]} | ${c.count} | ${c.fixable} |`);
311
+ }
312
+ out.push("");
313
+ out.push("---");
314
+ out.push("");
315
+ for (const sev of SEVERITY_ORDER2) {
316
+ const group = filtered.filter((v) => v.severity === sev);
317
+ if (group.length === 0) continue;
318
+ out.push(`<details>`);
319
+ out.push(`<summary>${SEVERITY_EMOJI[sev]} ${SEVERITY_LABEL[sev]} (${group.length})</summary>`);
320
+ out.push("");
321
+ for (const v of group) {
322
+ out.push(renderVulnerability(v));
323
+ out.push("");
324
+ }
325
+ out.push(`</details>`);
326
+ out.push("");
327
+ }
328
+ out.push("---");
329
+ out.push(`*Generated by [audit-report](https://github.com/dimasdarfi/audit-report)*`);
330
+ return out.join("\n");
331
+ }
332
+
333
+ // src/formatters/html.ts
334
+ var SEVERITY_ORDER3 = ["critical", "high", "moderate", "low", "info"];
335
+ var SEVERITY_COLOR = {
336
+ critical: "#fee2e2",
337
+ high: "#ffedd5",
338
+ moderate: "#fef9c3",
339
+ low: "#dbeafe",
340
+ info: "#f0fdf4"
341
+ };
342
+ var SEVERITY_LABEL2 = {
343
+ critical: "Critical",
344
+ high: "High",
345
+ moderate: "Moderate",
346
+ low: "Low",
347
+ info: "Info"
348
+ };
349
+ var SEVERITY_RANK = {
350
+ critical: 5,
351
+ high: 4,
352
+ moderate: 3,
353
+ low: 2,
354
+ info: 1
355
+ };
356
+ function escapeHtml(s) {
357
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
358
+ }
359
+ function countBySeverity2(vulns) {
360
+ const r = { critical: 0, high: 0, moderate: 0, low: 0, info: 0 };
361
+ for (const v of vulns) r[v.severity] += 1;
362
+ return r;
363
+ }
364
+ function html(report2, opts = {}) {
365
+ const title = opts.title ?? "npm audit report";
366
+ const minSeverity = opts.severity ?? "info";
367
+ const filtered = filterBySeverity(report2.vulnerabilities, minSeverity);
368
+ const date = report2.meta.auditedAt ? report2.meta.auditedAt.slice(0, 10) : "";
369
+ const styles = `
370
+ * { box-sizing: border-box; }
371
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 2rem; color: #1f2937; background: #f9fafb; }
372
+ .container { max-width: 1100px; margin: 0 auto; background: #ffffff; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.06); padding: 2rem; }
373
+ h1 { margin: 0 0 0.5rem; font-size: 1.6rem; }
374
+ .meta { color: #6b7280; font-size: 0.9rem; margin-bottom: 1.5rem; }
375
+ table { width: 100%; border-collapse: collapse; margin-bottom: 1.5rem; font-size: 0.95rem; }
376
+ th, td { padding: 0.6rem 0.75rem; text-align: left; border-bottom: 1px solid #e5e7eb; }
377
+ th { background: #f3f4f6; cursor: pointer; user-select: none; font-weight: 600; }
378
+ th[data-sort] .arrow { color: #9ca3af; font-size: 0.75rem; margin-left: 0.25rem; }
379
+ th.sorted-asc .arrow::after { content: " \u25B2"; color: #1f2937; }
380
+ th.sorted-desc .arrow::after { content: " \u25BC"; color: #1f2937; }
381
+ tr.sev-critical { background: ${SEVERITY_COLOR.critical}; }
382
+ tr.sev-high { background: ${SEVERITY_COLOR.high}; }
383
+ tr.sev-moderate { background: ${SEVERITY_COLOR.moderate}; }
384
+ tr.sev-low { background: ${SEVERITY_COLOR.low}; }
385
+ tr.sev-info { background: ${SEVERITY_COLOR.info}; }
386
+ details { margin-bottom: 0.5rem; }
387
+ summary { cursor: pointer; padding: 0.4rem 0; font-weight: 600; }
388
+ .badge { display: inline-block; padding: 0.15rem 0.55rem; border-radius: 999px; font-size: 0.75rem; font-weight: 600; text-transform: uppercase; }
389
+ .badge-critical { background: #dc2626; color: white; }
390
+ .badge-high { background: #ea580c; color: white; }
391
+ .badge-moderate { background: #ca8a04; color: white; }
392
+ .badge-low { background: #2563eb; color: white; }
393
+ .badge-info { background: #16a34a; color: white; }
394
+ code { background: #f3f4f6; padding: 0.1rem 0.3rem; border-radius: 4px; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace; font-size: 0.85rem; }
395
+ .empty { padding: 2rem; text-align: center; color: #6b7280; }
396
+ .empty-icon { font-size: 3rem; }
397
+ .summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 0.75rem; margin-bottom: 1.5rem; }
398
+ .summary-card { padding: 0.85rem; border-radius: 6px; border: 1px solid #e5e7eb; }
399
+ .summary-card .label { font-size: 0.75rem; text-transform: uppercase; color: #6b7280; font-weight: 600; letter-spacing: 0.03em; }
400
+ .summary-card .value { font-size: 1.4rem; font-weight: 700; margin-top: 0.15rem; }
401
+ footer { margin-top: 2rem; padding-top: 1rem; border-top: 1px solid #e5e7eb; color: #6b7280; font-size: 0.85rem; text-align: center; }
402
+ @media print {
403
+ body { background: white; padding: 0; }
404
+ .container { box-shadow: none; padding: 0; }
405
+ th { cursor: default; }
406
+ th .arrow { display: none; }
407
+ details { break-inside: avoid; }
408
+ details[open] summary { display: list-item; }
409
+ }
410
+ `;
411
+ const counts = countBySeverity2(filtered);
412
+ let body = "";
413
+ if (filtered.length === 0) {
414
+ body = `<div class="empty"><div class="empty-icon">\u2705</div><h2>No vulnerabilities found</h2><p>Audited ${report2.meta.totalDependencies} dependencies</p></div>`;
415
+ } else {
416
+ const summaryCards = SEVERITY_ORDER3.map((sev) => {
417
+ const c = counts[sev];
418
+ return `<div class="summary-card" style="background:${SEVERITY_COLOR[sev]}"><div class="label">${SEVERITY_LABEL2[sev]}</div><div class="value">${c}</div></div>`;
419
+ }).join("");
420
+ const rows = filtered.map((v) => {
421
+ const fix = v.fixAvailable && v.fixCommand ? `<code>${escapeHtml(v.fixCommand)}</code>` : "No fix";
422
+ return `<tr class="sev-${v.severity}" data-severity="${SEVERITY_RANK[v.severity]}" data-name="${escapeHtml(v.name)}">
423
+ <td><span class="badge badge-${v.severity}">${SEVERITY_LABEL2[v.severity]}</span></td>
424
+ <td><strong>${escapeHtml(v.name)}</strong></td>
425
+ <td>${escapeHtml(v.title)}</td>
426
+ <td><code>${escapeHtml(v.range)}</code></td>
427
+ <td>${fix}</td>
428
+ </tr>`;
429
+ }).join("\n");
430
+ body = `
431
+ <div class="summary-grid">${summaryCards}</div>
432
+ <table id="vuln-table">
433
+ <thead>
434
+ <tr>
435
+ <th data-sort="severity">Severity<span class="arrow"></span></th>
436
+ <th data-sort="name">Package<span class="arrow"></span></th>
437
+ <th data-sort="title">Title<span class="arrow"></span></th>
438
+ <th data-sort="range">Range<span class="arrow"></span></th>
439
+ <th data-sort="fix">Fix<span class="arrow"></span></th>
440
+ </tr>
441
+ </thead>
442
+ <tbody>
443
+ ${rows}
444
+ </tbody>
445
+ </table>
446
+ `;
447
+ }
448
+ const sortScript = `
449
+ (function(){
450
+ var table = document.getElementById('vuln-table');
451
+ if (!table) return;
452
+ var headers = table.querySelectorAll('th[data-sort]');
453
+ headers.forEach(function(h, idx){
454
+ h.addEventListener('click', function(){
455
+ var asc = !h.classList.contains('sorted-asc');
456
+ headers.forEach(function(other){ other.classList.remove('sorted-asc','sorted-desc'); });
457
+ h.classList.add(asc ? 'sorted-asc' : 'sorted-desc');
458
+ var tbody = table.querySelector('tbody');
459
+ var rows = Array.prototype.slice.call(tbody.querySelectorAll('tr'));
460
+ rows.sort(function(a, b){
461
+ var key = h.getAttribute('data-sort');
462
+ var av, bv;
463
+ if (key === 'severity') {
464
+ av = parseInt(a.getAttribute('data-severity'), 10);
465
+ bv = parseInt(b.getAttribute('data-severity'), 10);
466
+ } else {
467
+ av = a.children[idx].textContent.trim().toLowerCase();
468
+ bv = b.children[idx].textContent.trim().toLowerCase();
469
+ }
470
+ if (av < bv) return asc ? -1 : 1;
471
+ if (av > bv) return asc ? 1 : -1;
472
+ return 0;
473
+ });
474
+ rows.forEach(function(r){ tbody.appendChild(r); });
475
+ });
476
+ });
477
+ })();
478
+ `;
479
+ return `<!DOCTYPE html>
480
+ <html lang="en">
481
+ <head>
482
+ <meta charset="utf-8">
483
+ <meta name="viewport" content="width=device-width, initial-scale=1">
484
+ <title>${escapeHtml(title)} \u2014 ${escapeHtml(date)}</title>
485
+ <style>${styles}</style>
486
+ </head>
487
+ <body>
488
+ <div class="container">
489
+ <h1>${escapeHtml(title)}</h1>
490
+ <div class="meta">Audited ${escapeHtml(date)} \xB7 ${report2.meta.totalDependencies} dependencies \xB7 ${filtered.length} vulnerabilities</div>
491
+ ${body}
492
+ <footer>Generated by audit-report</footer>
493
+ </div>
494
+ <script>${sortScript}</script>
495
+ </body>
496
+ </html>`;
497
+ }
498
+
499
+ // src/formatters/sarif.ts
500
+ function severityToLevel(s) {
501
+ if (s === "critical" || s === "high") return "error";
502
+ if (s === "moderate") return "warning";
503
+ return "note";
504
+ }
505
+ function sarif(report2, opts = {}) {
506
+ const minSeverity = opts.severity ?? "info";
507
+ const filtered = filterBySeverity(report2.vulnerabilities, minSeverity);
508
+ const rulesMap = /* @__PURE__ */ new Map();
509
+ for (const v of filtered) {
510
+ if (!rulesMap.has(v.id)) {
511
+ const rule = {
512
+ id: v.id,
513
+ name: v.name,
514
+ shortDescription: { text: v.title },
515
+ properties: { severity: v.severity }
516
+ };
517
+ if (v.url) rule.helpUri = v.url;
518
+ rulesMap.set(v.id, rule);
519
+ }
520
+ }
521
+ const results = filtered.map((v) => {
522
+ const fixText = v.fixAvailable && v.fixCommand ? `Fix: ${v.fixCommand}` : "No fix available.";
523
+ const messageText = `${v.title}. Affected range: ${v.range}. ${fixText}`;
524
+ return {
525
+ ruleId: v.id,
526
+ level: severityToLevel(v.severity),
527
+ message: { text: messageText },
528
+ locations: [
529
+ {
530
+ physicalLocation: {
531
+ artifactLocation: { uri: "package.json" }
532
+ }
533
+ }
534
+ ]
535
+ };
536
+ });
537
+ const doc = {
538
+ $schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
539
+ version: "2.1.0",
540
+ runs: [
541
+ {
542
+ tool: {
543
+ driver: {
544
+ name: "npm-audit",
545
+ version: report2.meta.npmVersion || "0.0.0",
546
+ informationUri: "https://docs.npmjs.com/cli/commands/npm-audit",
547
+ rules: Array.from(rulesMap.values())
548
+ }
549
+ },
550
+ results
551
+ }
552
+ ]
553
+ };
554
+ return JSON.stringify(doc, null, 2);
555
+ }
556
+
557
+ // src/formatters/annotations.ts
558
+ function severityToAnnotation(s) {
559
+ if (s === "critical" || s === "high") return "error";
560
+ if (s === "moderate") return "warning";
561
+ return "notice";
562
+ }
563
+ function escapeAnnotation(s) {
564
+ return s.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
565
+ }
566
+ function escapeProperty(s) {
567
+ return escapeAnnotation(s).replace(/:/g, "%3A").replace(/,/g, "%2C");
568
+ }
569
+ function annotations(report2, opts = {}) {
570
+ const minSeverity = opts.severity ?? "info";
571
+ const filtered = filterBySeverity(report2.vulnerabilities, minSeverity);
572
+ const lines = filtered.map((v) => {
573
+ const level = severityToAnnotation(v.severity);
574
+ const fixPart = v.fixAvailable && v.fixCommand ? `fix: ${v.fixCommand}` : "no fix";
575
+ const message = `${v.name} \u2014 ${v.title}, affected: ${v.range} (${fixPart})`;
576
+ const titleProp = escapeProperty(v.id);
577
+ return `::${level} file=package.json,title=${titleProp}::${escapeAnnotation(message)}`;
578
+ });
579
+ return lines.join("\n");
580
+ }
581
+
582
+ // src/formatters/index.ts
583
+ function format(report2, opts = {}) {
584
+ const fmt = opts.format ?? "markdown";
585
+ switch (fmt) {
586
+ case "markdown":
587
+ return markdown(report2, opts);
588
+ case "html":
589
+ return html(report2, opts);
590
+ case "sarif":
591
+ return sarif(report2, opts);
592
+ case "annotations":
593
+ return annotations(report2, opts);
594
+ default:
595
+ throw new Error(`Unknown format: ${String(fmt)}`);
596
+ }
597
+ }
598
+
599
+ // src/index.ts
600
+ function report(json, opts = {}) {
601
+ const parsed = parse(json);
602
+ return format(parsed, opts);
603
+ }
604
+ // Annotate the CommonJS export names for ESM import in node:
605
+ 0 && (module.exports = {
606
+ ParseError,
607
+ filterBySeverity,
608
+ format,
609
+ meetsThreshold,
610
+ parse,
611
+ report,
612
+ shouldFail
613
+ });
@@ -0,0 +1,54 @@
1
+ type Severity = 'critical' | 'high' | 'moderate' | 'low' | 'info';
2
+ type Format = 'markdown' | 'html' | 'sarif' | 'annotations';
3
+ interface Vulnerability {
4
+ id: string;
5
+ name: string;
6
+ severity: Severity;
7
+ title: string;
8
+ url: string;
9
+ range: string;
10
+ fixAvailable: boolean;
11
+ fixCommand?: string;
12
+ paths: string[];
13
+ }
14
+ interface AuditMeta {
15
+ npmVersion: string;
16
+ nodeVersion: string;
17
+ auditedAt: string;
18
+ totalDependencies: number;
19
+ }
20
+ interface AuditSummary {
21
+ critical: number;
22
+ high: number;
23
+ moderate: number;
24
+ low: number;
25
+ info: number;
26
+ total: number;
27
+ }
28
+ interface AuditReport {
29
+ meta: AuditMeta;
30
+ summary: AuditSummary;
31
+ vulnerabilities: Vulnerability[];
32
+ }
33
+ interface FormatOptions {
34
+ format?: Format;
35
+ severity?: Severity;
36
+ failOn?: Severity | 'none';
37
+ title?: string;
38
+ template?: string;
39
+ }
40
+
41
+ declare class ParseError extends Error {
42
+ constructor(message: string);
43
+ }
44
+ declare function parse(input: string): AuditReport;
45
+
46
+ declare function format(report: AuditReport, opts?: FormatOptions): string;
47
+
48
+ declare function meetsThreshold(severity: Severity, threshold: Severity): boolean;
49
+ declare function shouldFail(report: AuditReport, failOn: Severity | 'none'): boolean;
50
+ declare function filterBySeverity(vulns: Vulnerability[], minSeverity: Severity): Vulnerability[];
51
+
52
+ declare function report(json: string, opts?: FormatOptions): string;
53
+
54
+ export { type AuditMeta, type AuditReport, type AuditSummary, type Format, type FormatOptions, ParseError, type Severity, type Vulnerability, filterBySeverity, format, meetsThreshold, parse, report, shouldFail };