Skip to content

Commit 32660ff

Browse files
committed
report: html report feature
1 parent 83c2a81 commit 32660ff

File tree

5 files changed

+323
-33
lines changed

5 files changed

+323
-33
lines changed

commands/report.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const {
1414
} = require('../lib/report/util')
1515
const longReport = require('../lib/report/long')
1616
const shortReport = require('../lib/report/short')
17+
const htmlReport = require('../lib/report/html')
1718
const { helpHeader } = require('../lib/help')
1819
const {
1920
COLORS,
@@ -30,7 +31,8 @@ module.exports.optionsList = optionsList
3031

3132
async function report (argv, _dir) {
3233
const {
33-
long
34+
long,
35+
html
3436
} = argv
3537
let { dir = _dir } = argv
3638
if (!dir) dir = process.cwd()
@@ -162,6 +164,7 @@ async function report (argv, _dir) {
162164

163165
if (!long) shortReport(pkgScores, whitelisted, dir, argv)
164166
if (long) longReport(pkgScores, whitelisted, dir, argv)
167+
if (html) htmlReport(pkgScores, whitelisted, dir, html, argv)
165168
if (hasFailures) process.exitCode = 1
166169
}
167170

lib/report/html-template.js

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
'use strict'
2+
3+
const {
4+
filterVulns,
5+
SEVERITY_RMAP
6+
} = require('./util')
7+
8+
module.exports = renderHTML
9+
10+
function renderHTML (title, summary, report, whitelist) {
11+
const { riskCount, securityCount, insecureModules, complianceCount } = summary
12+
13+
let alternate = false
14+
let whitelistInfo = ''
15+
for (const pkg of whitelist) {
16+
whitelistInfo += segment(pkg)
17+
}
18+
19+
alternate = false
20+
let pkgInfo = ''
21+
for (const pkg of report) {
22+
pkgInfo += segment(pkg)
23+
}
24+
25+
function segment (pkg) {
26+
const { name, version, maxSeverity, failures, license } = pkg
27+
const pkgVulns = filterVulns(failures).map((v, i) => v !== 0
28+
? `<p class="${['light1', 'yellow', 'orange', 'red'][i]}">
29+
${v}&nbsp;${['Low', 'Medium', 'High', 'Critical'][i]}
30+
</p>`
31+
: ''
32+
)
33+
const pkgLicense = license && license.data && license.data.spdx ? license.data.spdx : 'UNKNOWN'
34+
const pkgLicensePass = license && license.pass === true
35+
const pkgSeverity = maxSeverity === 0
36+
? `<p>${SEVERITY_RMAP[maxSeverity]}</p>`
37+
: `<p class="${['light1', 'yellow', 'orange', 'red'][maxSeverity - 1]}">
38+
${SEVERITY_RMAP[maxSeverity]}
39+
</p>`
40+
41+
alternate = !alternate
42+
return `
43+
<tr class="module-element" ${alternate ? 'style="background:#2e3535"' : ''}>
44+
<td>
45+
<p><span class="white">${name}</span>@${version}</p>
46+
</td>
47+
<td style="text-align:center;">
48+
<p>${pkgSeverity}</p>
49+
<td>
50+
<p>
51+
<span class="${pkgLicensePass ? 'green">✓' : 'red">X'}</span>&nbsp;${pkgLicense}
52+
</p>
53+
</td>
54+
<td>
55+
<p>
56+
${pkgVulns.join('').length === 0
57+
? 'None'
58+
: pkgVulns.reverse().join(' ')}
59+
</p>
60+
</td>
61+
</tr>
62+
`
63+
}
64+
65+
const template = `
66+
<!DOCTYPE html>
67+
<html lang="en">
68+
<meta charset="utf-8">
69+
<head>
70+
<title>NCM Report > ${title}</title>
71+
<style>
72+
body {
73+
padding: 2%;
74+
color: #89a19d;
75+
font-family: Helvetica;
76+
font-weight: 200;
77+
}
78+
a {
79+
color: #5ac878;
80+
text-decoration: none;
81+
}
82+
p {
83+
margin: 0;
84+
}
85+
td {
86+
padding-top: 3px;
87+
padding-bottom: 3px;
88+
padding-left: 10px;
89+
padding-right: 10px;
90+
}
91+
table {
92+
width: 100%;
93+
}
94+
.title {
95+
font-size: 28pt;
96+
color:#e1e7e6;
97+
}
98+
.subtitle {
99+
font-size: 20pt;
100+
color: #e1e7e6;
101+
}
102+
.module-element {
103+
margin: 4px;
104+
}
105+
.bottom-20 {
106+
margin-bottom: 20px;
107+
}
108+
.left-20 {
109+
margin-left: 20px;
110+
}
111+
.white {
112+
color: #e1e7e6;
113+
}
114+
.light1 {
115+
color: #89a19d;
116+
}
117+
.yellow {
118+
color: #ffb726;
119+
}
120+
.orange {
121+
color: #ff8b40;
122+
}
123+
.red {
124+
color: #ff6040;
125+
}
126+
.green {
127+
color: #5ac878;
128+
}
129+
</style>
130+
</head>
131+
<body style="background: #202525">
132+
133+
<div id="ns-branding" class="bottom-20">
134+
<svg xmlns="http://www.w3.org/2000/svg" height="50" width="auto" viewBox="0 0 100 100">
135+
<path d="M89.243 20.864L55.642 1.5a11.21 11.21 0 0 0-11.2 0L10.778 20.937a11.208 11.208 0 0 0-5.6 9.7l-.032 38.781a11.2 11.2 0 0 0 5.611 9.718L44.359 98.5a11.208 11.208 0 0 0 11.2 0l33.663-19.437a11.206 11.206 0 0 0 5.6-9.7l.028-38.784a11.2 11.2 0 0 0-5.607-9.715zM46.36 4.823a7.338 7.338 0 0 1 1.723-.718v16.552a1.917 1.917 0 0 0 3.834 0V4.079a7.369 7.369 0 0 1 1.81.741l33.6 19.366a7.322 7.322 0 0 1 1.522 1.169L68.486 37.113a22.518 22.518 0 0 0-36.983.014L11.186 25.418a7.329 7.329 0 0 1 1.51-1.16zM68.713 50a18.74 18.74 0 0 1-16.8 18.616V51.107l15.149-8.746A18.579 18.579 0 0 1 68.713 50zm-35.785-7.626l15.155 8.734v17.508a18.679 18.679 0 0 1-15.155-26.242zM50 47.786l-15.149-8.729a18.651 18.651 0 0 1 30.288-.012zM12.672 75.813a7.284 7.284 0 0 1-1.522-1.168l13.117-7.574a1.918 1.918 0 0 0-1.918-3.321L9.232 71.323a7.316 7.316 0 0 1-.251-1.9l.029-38.786a7.274 7.274 0 0 1 .254-1.9l20.318 11.71a22.53 22.53 0 0 0 18.5 32.015v22.945a1.9 1.9 0 0 0 .083.53 7.351 7.351 0 0 1-1.892-.757zm40.969 19.364a7.362 7.362 0 0 1-1.8.741 1.919 1.919 0 0 0 .077-.511V72.461A22.533 22.533 0 0 0 70.411 40.43l20.357-11.754a7.329 7.329 0 0 1 .251 1.9l-.028 38.784a7.33 7.33 0 0 1-.264 1.937L77.651 63.75a1.918 1.918 0 0 0-1.918 3.321l13.053 7.536a7.3 7.3 0 0 1-1.481 1.135z" fill="#89a19d"></path>
136+
</svg>
137+
<svg height="30" width="auto" viewBox="0 0 346.17 33.7" style="display:inline-block;fill:#fff;">
138+
<path d="M24.54,33.07a1.88,1.88,0,0,1-1.47-.71L3.75,7.9V31.2A1.88,1.88,0,1,1,0,31.2V2.5A1.88,1.88,0,0,1,3.35,1.34L22.67,25.8V2.5a1.88,1.88,0,0,1,3.75,0V31.2a1.88,1.88,0,0,1-1.87,1.88Z"></path><path d="M48.92,33.7c-8.81,0-16-7.56-16-16.85S40.11,0,48.92,0s16,7.56,16,16.85S57.72,33.7,48.92,33.7Zm0-29.95c-6.74,0-12.22,5.88-12.22,13.1s5.48,13.1,12.22,13.1,12.22-5.88,12.22-13.1S55.66,3.75,48.92,3.75Z"></path><path d="M177.24,33.7c-8.81,0-16-7.56-16-16.85S168.43,0,177.24,0s16,7.56,16,16.85S186,33.7,177.24,33.7Zm0-29.95C170.5,3.75,165,9.63,165,16.85s5.48,13.1,12.22,13.1,12.22-5.88,12.22-13.1S184,3.75,177.24,3.75Z"></path><path d="M234.8,33.07a1.88,1.88,0,0,1-1.87-1.88V2.5A1.88,1.88,0,0,1,234.8.62h13.31c5.67,0,9.78,3.79,9.78,9s-4.11,9-9.78,9H236.67V31.2A1.88,1.88,0,0,1,234.8,33.07Zm1.88-18.18h11.43c3.55,0,6-2.16,6-5.26s-2.48-5.26-6-5.26H236.67Z"></path><path d="M212.47,33.7A13.51,13.51,0,0,1,199.16,20V2.5a1.88,1.88,0,0,1,3.75,0V20A9.57,9.57,0,1,0,222,20V2.5a1.88,1.88,0,0,1,3.75,0V20A13.51,13.51,0,0,1,212.47,33.7Z"></path><path d="M152.72,6.59l-.18-.12c-.12-.08-.31-.21-.51-.33a18.63,18.63,0,0,0-1.91-1A14.31,14.31,0,0,0,147,4a13,13,0,0,0-4.25-.17,8.14,8.14,0,0,0-4.29,1.67A4.4,4.4,0,0,0,136.9,9.7a3.81,3.81,0,0,0,.3,1,3.68,3.68,0,0,0,.61,1A6.93,6.93,0,0,0,140,13.22a17.68,17.68,0,0,0,3,1c.54.14,1.09.26,1.64.37s1.15.23,1.82.41a22.48,22.48,0,0,1,3.7,1.25,13.74,13.74,0,0,1,3.49,2.14,8.41,8.41,0,0,1,2.52,3.66c.11.36.2.74.28,1.11s.09.75.1,1.12v.89l0,.17,0,.1-.06.42,0,.21-.07.29a8.86,8.86,0,0,1-5.21,6,15.75,15.75,0,0,1-6.39,1.31,12.44,12.44,0,0,1-1.47-.07,14.11,14.11,0,0,1-1.46-.22,17.07,17.07,0,0,1-2.49-.72,20.61,20.61,0,0,1-3.68-1.84,21.46,21.46,0,0,1-2.13-1.54c-.47-.39-.72-.62-.72-.62l0,0a1.69,1.69,0,0,1,2.26-2.5s.2.17.61.47a19.79,19.79,0,0,0,1.86,1.2,17.77,17.77,0,0,0,3.1,1.43,13.37,13.37,0,0,0,2,.54c.35.06.71.12,1,.14a10.25,10.25,0,0,0,1.15,0,12.07,12.07,0,0,0,4.86-1,6.41,6.41,0,0,0,2-1.38,3.17,3.17,0,0,0,.38-.45,2.58,2.58,0,0,0,.31-.48,4.25,4.25,0,0,0-1.06-5.41,14,14,0,0,0-5.61-2.57c-.48-.14-1.17-.26-1.67-.38s-1.23-.26-1.84-.42a21.32,21.32,0,0,1-3.68-1.27A10.58,10.58,0,0,1,135,14.17,7.53,7.53,0,0,1,133.17,10a3.77,3.77,0,0,1,0-.48l0-.42v-.6l0-.53c0-.35.11-.7.18-1s.19-.68.32-1A8.25,8.25,0,0,1,136,2.67a11.8,11.8,0,0,1,6.3-2.54,15.79,15.79,0,0,1,5.47.29,16.09,16.09,0,0,1,3.94,1.48A17.75,17.75,0,0,1,154,3.31l.71.53a1.69,1.69,0,0,1-2,2.75Z"></path><path d="M79.8,33.07H73.15a1.88,1.88,0,0,1-1.87-1.88V2.5A1.88,1.88,0,0,1,73.15.62H79.8c12.62,0,18.27,8.15,18.27,16.22C98.07,27.16,91.41,33.07,79.8,33.07ZM75,29.32H79.8c12,0,14.52-6.78,14.52-12.47,0-7.58-5.7-12.47-14.52-12.47H75Z"></path><path d="M125.54,33.07H106.37a1.88,1.88,0,0,1-1.87-1.88V2.5A1.88,1.88,0,0,1,106.37.62h19.17a1.87,1.87,0,1,1,0,3.75H108.24V29.32h17.29a1.88,1.88,0,0,1,0,3.75Z"></path><path d="M120.39,18.66h-14a1.88,1.88,0,0,1,0-3.75h14a1.87,1.87,0,0,1,0,3.75Z"></path><path d="M319.58,33.07H300.41a1.88,1.88,0,0,1-1.87-1.88V2.5A1.88,1.88,0,0,1,300.41.62h19.17a1.87,1.87,0,1,1,0,3.75H302.29V29.32h17.29a1.88,1.88,0,0,1,0,3.75Z"></path><path d="M314.44,18.66H300.8a1.88,1.88,0,0,1,0-3.75h13.63a1.88,1.88,0,0,1,0,3.75Z"></path><path d="M256.16,33.07a1.87,1.87,0,0,1-1.53-.79L244.5,17.93a1.88,1.88,0,0,1,3.06-2.16l10.13,14.35a1.88,1.88,0,0,1-1.53,3Z"></path><path d="M291.05,28.93l-.58.55a17.13,17.13,0,0,1-2,1.54,16.17,16.17,0,0,1-8.36,2.66h-.78l-.63,0a10.23,10.23,0,0,1-1.39-.13,16.51,16.51,0,0,1-3.05-.7,16.71,16.71,0,0,1-5.73-3.27,16.91,16.91,0,0,1-5.82-12.72l0-.91c0-.28,0-.53.05-.84.1-.63.17-1.3.31-1.9a16.8,16.8,0,0,1,1.18-3.45,17,17,0,0,1,4.24-5.62A16.71,16.71,0,0,1,274.28.86,17.45,17.45,0,0,1,280.12,0a16.27,16.27,0,0,1,8.38,2.66,17.06,17.06,0,0,1,2,1.52l.63.6a1.69,1.69,0,0,1-2.24,2.51l-.09-.07-.53-.45a14,14,0,0,0-1.61-1.11A13.58,13.58,0,0,0,280,3.76a14,14,0,0,0-4.57.66A13,13,0,0,0,271,7a13.24,13.24,0,0,0-3.3,4.37A13,13,0,0,0,266.8,14c-.11.46-.14.88-.23,1.32,0,.23,0,.53-.05.8l0,.72A13.16,13.16,0,0,0,271,26.74a12.9,12.9,0,0,0,6.71,3.06,7.27,7.27,0,0,0,1.19.11l.63,0H280A13.5,13.5,0,0,0,286.61,28a14.42,14.42,0,0,0,1.59-1.09l.59-.5h0a1.69,1.69,0,0,1,2.26,2.51Z"></path><path d="M337.88.2a8.29,8.29,0,1,0,8.29,8.29A8.3,8.3,0,0,0,337.88.2Zm0,15.12a6.83,6.83,0,1,1,6.83-6.83A6.84,6.84,0,0,1,337.88,15.32Z"></path><path d="M335.87,12.12a.61.61,0,0,1-.61-.61v-6a.61.61,0,0,1,.61-.61h2.81a2.12,2.12,0,1,1,0,4.23h-2.2v2.43A.61.61,0,0,1,335.87,12.12Zm.61-4.26h2.2a.91.91,0,1,0,0-1.79h-2.2Z"></path><path d="M340.38,12.12a.61.61,0,0,1-.5-.26l-2.13-3a.61.61,0,0,1,1-.7l2.13,3a.61.61,0,0,1-.5,1Z"></path>
139+
</svg>
140+
</div>
141+
142+
<div id="title" class="bottom-20">
143+
<div class="title">
144+
<p>
145+
NCM&nbsp;Project&nbsp;Report
146+
<span class="light1">&nbsp;>&nbsp;</span>
147+
${title}
148+
</p>
149+
</div>
150+
<div class="left-20">
151+
<p>
152+
<b class="green">></b>
153+
Powered by
154+
<a href="https://docs.nodesource.com/ncmv2/docs">NodeSource Certified Modules v2</a>
155+
</p>
156+
</div>
157+
</div>
158+
159+
<div id="summary" class="bottom-20 left-20">
160+
<span class="subtitle">Summary</span>
161+
<p><b class="white">${report.length}</b> packages checked</p>
162+
<br>
163+
<p><span class="red"><b>${riskCount[4]}</b> Critical Risk</span></p>
164+
<p><span class="orange"><b>${riskCount[3]}</b> High Risk</span></p>
165+
<p><span class="yellow"><b>${riskCount[2]}</b> Medium Risk</span></p>
166+
<p><span class-"light1"><b>${riskCount[1]}</b> Low Risk</span></p>
167+
<br>
168+
<p>
169+
<b class="white">${securityCount}</b>
170+
security vulnerabilities found
171+
${(securityCount > 0
172+
? `across <b class="white">${insecureModules}</b> modules`
173+
: '')}
174+
</p>
175+
<p><b class="white">${complianceCount}</b> noncompliant modules found</p>
176+
${(whitelist.length > 0
177+
? `<p><b class="white">${whitelist.length}</b> used modules whitelisted</p>`
178+
: '')}
179+
</div>
180+
181+
${(whitelist.length > 0
182+
? `
183+
<div id="whitelist-list">
184+
<span class="subtitle">Whitelisted Modules</span>
185+
<hr>
186+
<table id="whitelist-table" class="bottom-20">
187+
<tr style="margin-bottom:5px;">
188+
<th>Module Name</th>
189+
<th>Risk</th>
190+
<th>License</th>
191+
<th>Security</th>
192+
</tr>
193+
${whitelistInfo}
194+
</table>
195+
</div>
196+
`
197+
: '')}
198+
199+
<div id="module-list">
200+
<p class="subtitle">${(whitelist.length > 0 ? 'Non-Whitelisted ' : '')}Modules</p>
201+
<hr>
202+
<table id="module-table" class="bottom-20">
203+
<tr style="margin-bottom:5px;">
204+
<th>Module Name</th>
205+
<th>Risk</th>
206+
<th>License</th>
207+
<th>Security</th>
208+
</tr>
209+
${pkgInfo}
210+
</table>
211+
</div>
212+
</body>
213+
</html>
214+
`
215+
216+
return template
217+
}

lib/report/html.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
'use strict'
2+
3+
const path = require('path')
4+
const { promisify } = require('util')
5+
const writeFile = promisify(require('fs').writeFile)
6+
const {
7+
success,
8+
formatError
9+
} = require('../ncm-style')
10+
const {
11+
summaryInfo
12+
} = require('./util')
13+
const renderTemplate = require('./html-template')
14+
const L = console.log
15+
16+
module.exports = htmlReport
17+
18+
async function htmlReport (report, whitelist, dir, output) {
19+
/* Output may only use the `.html` file format */
20+
if (output !== true && !(/^.*\.html$/.test(path.basename(output)))) {
21+
L()
22+
L(formatError('Invalid file extension to write the HTML report. Please use `*.html`.'))
23+
L()
24+
process.exitCode = 1
25+
return
26+
}
27+
28+
const title = `${path.basename(dir) || 'NCM'}`
29+
const summary = summaryInfo(report) // { riskCount, insecureModules, complianceCount, securityCount }
30+
31+
const htmlData = renderTemplate(
32+
title,
33+
summary,
34+
report,
35+
whitelist
36+
)
37+
38+
/*
39+
No write location was specified.
40+
Setting output location to current working directory with generated report name.
41+
*/
42+
if (output === true) output = path.join(process.cwd(), `${title.toLowerCase()}-report-${Date.now()}.html`)
43+
44+
/* Write report to file */
45+
try {
46+
await writeFile(output, htmlData)
47+
L()
48+
L(success(`Wrote HTML report to: ${output}`))
49+
L()
50+
} catch (error) {
51+
L()
52+
L(formatError(`Unable to write HTML report to: ${output}`, error))
53+
L()
54+
process.exitCode = 1
55+
}
56+
}

lib/report/summary.js

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const {
77
tooltip
88
} = require('../ncm-style')
99
const {
10-
SEVERITY_RMAP
10+
summaryInfo
1111
} = require('./util')
1212
const L = console.log
1313
const chalk = require('chalk')
@@ -19,27 +19,7 @@ function summary (report, dir, filterOptions) {
1919
L(chalk`${report.length} {${COLORS.light1} packages checked}`)
2020
L()
2121

22-
const riskCount = [0, 0, 0, 0, 0]
23-
let insecureModules = 0
24-
let complianceCount = 0
25-
let securityCount = 0
26-
27-
for (const pkg of report) {
28-
let insecure = false
29-
let pkgMaxSeverity = 0
30-
for (const score of pkg.scores) {
31-
if (score.group === 'quality') continue
32-
if (score.group === 'compliance' && !score.pass) complianceCount++
33-
if (score.group === 'security' && !score.pass) {
34-
securityCount++
35-
insecure = true
36-
}
37-
const scoreIndex = SEVERITY_RMAP.indexOf(score.severity)
38-
pkgMaxSeverity = scoreIndex > pkgMaxSeverity ? scoreIndex : pkgMaxSeverity
39-
}
40-
riskCount[pkgMaxSeverity]++
41-
if (insecure) insecureModules++
42-
}
22+
const { riskCount, insecureModules, complianceCount, securityCount } = summaryInfo(report)
4323

4424
L(chalk` {${COLORS.red} ! ${riskCount[4]}} critical risk`)
4525
L(chalk` {${COLORS.orange} ${riskCount[3]}} high risk`)

0 commit comments

Comments
 (0)