@ploaness/governance 1.7.0 → 1.9.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/compliance-report-assets.d.ts +20 -0
- package/dist/compliance-report-assets.d.ts.map +1 -0
- package/dist/compliance-report-assets.js +223 -0
- package/dist/compliance-report-assets.js.map +1 -0
- package/dist/compliance-report-html.d.ts +18 -0
- package/dist/compliance-report-html.d.ts.map +1 -0
- package/dist/compliance-report-html.js +350 -0
- package/dist/compliance-report-html.js.map +1 -0
- package/dist/compliance-report-markup.d.ts +63 -0
- package/dist/compliance-report-markup.d.ts.map +1 -0
- package/dist/compliance-report-markup.js +116 -0
- package/dist/compliance-report-markup.js.map +1 -0
- package/dist/compliance-report-outline.d.ts +33 -0
- package/dist/compliance-report-outline.d.ts.map +1 -0
- package/dist/compliance-report-outline.js +109 -0
- package/dist/compliance-report-outline.js.map +1 -0
- package/dist/compliance-report.d.ts +142 -0
- package/dist/compliance-report.d.ts.map +1 -0
- package/dist/compliance-report.js +240 -0
- package/dist/compliance-report.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/license-allowlist.d.ts +14 -0
- package/dist/license-allowlist.d.ts.map +1 -1
- package/dist/license-allowlist.js +28 -9
- package/dist/license-allowlist.js.map +1 -1
- package/dist/license-inventory.d.ts +25 -0
- package/dist/license-inventory.d.ts.map +1 -0
- package/dist/license-inventory.js +41 -0
- package/dist/license-inventory.js.map +1 -0
- package/dist/pdf-outline.d.ts +7 -0
- package/dist/pdf-outline.d.ts.map +1 -0
- package/dist/pdf-outline.js +69 -0
- package/dist/pdf-outline.js.map +1 -0
- package/dist/sbom-policy.d.ts +17 -0
- package/dist/sbom-policy.d.ts.map +1 -1
- package/dist/sbom-policy.js +76 -1
- package/dist/sbom-policy.js.map +1 -1
- package/dist/settings-layering.js +1 -1
- package/dist/settings-layering.js.map +1 -1
- package/dist/settings.d.ts +1 -3
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +5 -5
- package/dist/settings.js.map +1 -1
- package/dist/vulnerability-policy.d.ts +42 -29
- package/dist/vulnerability-policy.d.ts.map +1 -1
- package/dist/vulnerability-policy.js +56 -58
- package/dist/vulnerability-policy.js.map +1 -1
- package/dist/wiring-policy.js +2 -2
- package/dist/wiring-policy.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** One embedded typeface file. */
|
|
2
|
+
export interface EmbeddedFont {
|
|
3
|
+
readonly family: string;
|
|
4
|
+
readonly style: 'normal' | 'italic';
|
|
5
|
+
/** The weight range a variable font covers, such as `200 900`. */
|
|
6
|
+
readonly weight: string;
|
|
7
|
+
/** The WOFF2 file, base64-encoded. */
|
|
8
|
+
readonly data: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The `@font-face` rules for the embedded typefaces, so the document reads the same with no font installed.
|
|
12
|
+
* @param fonts the typefaces.
|
|
13
|
+
* @returns the CSS rules.
|
|
14
|
+
*/
|
|
15
|
+
export declare const fontFaces: (fonts: readonly EmbeddedFont[]) => string;
|
|
16
|
+
/** The report's stylesheet: the screen layout with its contents panel, then the A4 print layout. */
|
|
17
|
+
export declare const REPORT_STYLE: string;
|
|
18
|
+
/** Table filtering, sorting and the embedded downloads. The document reads completely without it. */
|
|
19
|
+
export declare const REPORT_SCRIPT: string;
|
|
20
|
+
//# sourceMappingURL=compliance-report-assets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compliance-report-assets.d.ts","sourceRoot":"","sources":["../src/compliance-report-assets.ts"],"names":[],"mappings":"AAEA,kCAAkC;AAClC,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,QAAQ,CAAA;IACnC,kEAAkE;IAClE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED;;;;GAIG;AACH,eAAO,MAAM,SAAS,GAAI,OAAO,SAAS,YAAY,EAAE,KAAG,MAO9C,CAAA;AAEb,oGAAoG;AACpG,eAAO,MAAM,YAAY,EAAE,MAoL1B,CAAA;AAED,qGAAqG;AACrG,eAAO,MAAM,aAAa,EAAE,MA4B3B,CAAA"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
// The stylesheet, typefaces and script the compliance report inlines, kept apart from the markup that uses them.
|
|
2
|
+
/**
|
|
3
|
+
* The `@font-face` rules for the embedded typefaces, so the document reads the same with no font installed.
|
|
4
|
+
* @param fonts the typefaces.
|
|
5
|
+
* @returns the CSS rules.
|
|
6
|
+
*/
|
|
7
|
+
export const fontFaces = (fonts) => fonts
|
|
8
|
+
.map((font) => `@font-face{font-family:"${font.family}";font-style:${font.style};font-weight:${font.weight};` +
|
|
9
|
+
`font-display:block;src:url(data:font/woff2;base64,${font.data}) format("woff2")}`)
|
|
10
|
+
.join('');
|
|
11
|
+
/** The report's stylesheet: the screen layout with its contents panel, then the A4 print layout. */
|
|
12
|
+
export const REPORT_STYLE = String.raw `
|
|
13
|
+
:root{color-scheme:light;--ink:#1b1f26;--ink-strong:#0a0d12;--muted:#4d5560;--accent:#1b3a6b;--accent-deep:#16315c;
|
|
14
|
+
--rule:#d5dae1;--hair:#e2e6ec;--grid:#c3cfe0;--panel:#f6f8fa;--stripe:#f9fafc;--code-bg:#f3f6fb;--code-edge:#b4c1d4;
|
|
15
|
+
--good:#166a2f;--good-bg:#edf4ef;--bad:#ae2a20;--bad-bg:#f9eeec;--warn:#8a5a00;--warn-bg:#fbf5e6;
|
|
16
|
+
--permissive:#1b3a6b;--weak:#b7791f;--refused:#ae2a20;--other:#9aa6b8;
|
|
17
|
+
--serif:"Source Serif 4",Charter,Georgia,"Times New Roman",serif;
|
|
18
|
+
--display:"Playfair Display","Iowan Old Style",Georgia,serif;
|
|
19
|
+
--mono:"Source Code Pro",SFMono-Regular,Menlo,Consolas,monospace}
|
|
20
|
+
*{box-sizing:border-box}
|
|
21
|
+
html{-webkit-text-size-adjust:100%;background:#fff;font-size:16px}
|
|
22
|
+
body{margin:0;padding:0 0 0 22rem;background:#fff;color:var(--ink);font-family:var(--serif);font-size:17px;
|
|
23
|
+
line-height:1.6;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;font-kerning:normal;
|
|
24
|
+
font-feature-settings:"lnum" 1}
|
|
25
|
+
table,code,.mono,#toc,.count,.adnum{font-feature-settings:"lnum" 1,"tnum" 1}
|
|
26
|
+
#header,#content,#footer{max-width:58.5em;margin-left:auto;margin-right:auto;padding-left:1.6em;padding-right:1.6em}
|
|
27
|
+
#header{padding-top:48px}
|
|
28
|
+
#header>h1{margin:0;padding:1.1rem 0 0;border-top:6px solid var(--accent);font-family:var(--display);font-size:3em;
|
|
29
|
+
font-weight:600;line-height:1.12;letter-spacing:-.015em;color:var(--ink-strong);hyphens:none;text-align:left}
|
|
30
|
+
#header::after{content:"";display:block;margin-top:.69rem;border-top:1px solid var(--rule)}
|
|
31
|
+
#content{margin-top:1.25em;margin-bottom:1.25em}
|
|
32
|
+
p{margin:0 0 .9em;text-align:justify;text-justify:inter-word;hyphens:auto;letter-spacing:-.01em}
|
|
33
|
+
table p,#toc p{text-align:left;hyphens:manual;letter-spacing:normal}
|
|
34
|
+
a{color:var(--accent);text-decoration:none;border-bottom:1px solid rgba(27,58,107,.5);
|
|
35
|
+
transition:color .12s ease,border-color .12s ease,background-color .12s ease}
|
|
36
|
+
a:hover{color:var(--ink-strong);border-bottom-color:var(--ink-strong);background:rgba(27,58,107,.08)}
|
|
37
|
+
code{margin:0 .09em;padding:.06em .36em;font-family:var(--mono);font-size:.86em;color:var(--accent);
|
|
38
|
+
background:var(--code-bg);border:1px solid var(--code-edge);white-space:nowrap}
|
|
39
|
+
td code,.ids code{white-space:normal;overflow-wrap:anywhere}
|
|
40
|
+
.nowrap code,code.nowrap{white-space:nowrap;overflow-wrap:normal}
|
|
41
|
+
.mono{font-family:var(--mono);font-size:.86em}
|
|
42
|
+
h2,h3,#toctitle{font-family:var(--display);color:var(--accent);font-weight:600;line-height:1.22;hyphens:none;
|
|
43
|
+
text-align:left;font-style:normal}
|
|
44
|
+
h2{display:flex;gap:.3em;margin:40px 0 .8rem;padding-top:.8rem;border-top:3px solid var(--accent);font-size:1.9em;
|
|
45
|
+
letter-spacing:-.005em}
|
|
46
|
+
h3{display:flex;gap:.3em;margin:27px 0 .55rem;padding-bottom:.35em;border-bottom:1px solid var(--hair);
|
|
47
|
+
font-size:1.36em}
|
|
48
|
+
h2 .adnum,h3 .adnum{flex:0 0 auto;letter-spacing:.02em}
|
|
49
|
+
h3 .adnum{font-weight:700;font-size:.86em;align-self:center}
|
|
50
|
+
.admonitionblock{margin:0 0 1.25em}
|
|
51
|
+
.admonitionblock>table{width:100%;border:0;border-collapse:separate;background:none}
|
|
52
|
+
.admonitionblock td.icon{width:3.25em;padding:.5625em .7em .5625em .625em;text-align:center;vertical-align:middle}
|
|
53
|
+
.admonitionblock td.icon svg{display:inline-block;width:1.6em;height:1.6em;vertical-align:middle}
|
|
54
|
+
.admonitionblock td.content{padding:.5625em 1.25em .5625em 1em;border-left:2px solid var(--rule);
|
|
55
|
+
vertical-align:middle;
|
|
56
|
+
text-align:justify;hyphens:auto;letter-spacing:-.01em}
|
|
57
|
+
.admonitionblock td.content p{margin:0;letter-spacing:-.01em}
|
|
58
|
+
.admonitionblock td.content ul{margin:.3em 0 0;padding-left:1.2em}
|
|
59
|
+
.admonitionblock .title{font-family:var(--display);font-weight:600;color:var(--accent-deep)}
|
|
60
|
+
.icon-warning{color:var(--bad)}
|
|
61
|
+
.icon-ok{color:var(--good)}
|
|
62
|
+
.icon-fail{color:var(--bad)}
|
|
63
|
+
.table-wrap{overflow-x:auto;margin:0 0 1.5em}
|
|
64
|
+
table.tableblock{width:100%;border-collapse:collapse;border-spacing:0;background:transparent;margin:0;font-size:1em;
|
|
65
|
+
line-height:1.3;border-left:1px solid var(--rule);border-right:1px solid var(--rule);
|
|
66
|
+
border-bottom:2px solid var(--accent)}
|
|
67
|
+
table.tableblock th,table.tableblock td{padding:.55em .75em;border:solid var(--rule);border-width:0 1px;
|
|
68
|
+
vertical-align:middle;text-align:left;overflow-wrap:break-word}
|
|
69
|
+
table.tableblock th{font-family:var(--display);font-weight:700;color:var(--accent);border-color:var(--grid);
|
|
70
|
+
border-top:2px solid var(--accent);border-bottom:1px solid var(--accent);white-space:nowrap}
|
|
71
|
+
table.tableblock tbody tr:first-child td{border-top:2px solid var(--accent)}
|
|
72
|
+
table.tableblock thead+tbody tr:first-child td{border-top:0}
|
|
73
|
+
table.tableblock tbody tr+tr td{border-top:1px solid var(--hair)}
|
|
74
|
+
table.tableblock tbody tr:nth-child(even){background:var(--stripe)}
|
|
75
|
+
table.tableblock.facts td{vertical-align:middle}
|
|
76
|
+
table.tableblock.facts tbody tr:nth-child(even){background:none}
|
|
77
|
+
table.tableblock.facts td:first-child{width:50%;text-align:right}
|
|
78
|
+
table.tableblock.compact{font-size:.88em}
|
|
79
|
+
th[data-sort]{cursor:pointer;user-select:none}
|
|
80
|
+
th[aria-sort=ascending]::after{content:" \25B4"}
|
|
81
|
+
th[aria-sort=descending]::after{content:" \25BE"}
|
|
82
|
+
.nowrap{white-space:nowrap}
|
|
83
|
+
.empty{color:var(--muted);font-style:italic;text-align:left}
|
|
84
|
+
.stack{display:flex;height:12px;background:var(--hair);margin:.5em 0 .6em}
|
|
85
|
+
.stack i{display:block;height:100%}
|
|
86
|
+
.seg-permissive,.fill-permissive{background:var(--permissive)}
|
|
87
|
+
.seg-weak-copyleft,.fill-weak-copyleft{background:var(--weak)}
|
|
88
|
+
.seg-refused,.fill-refused{background:var(--refused)}
|
|
89
|
+
.fill-other{background:var(--other)}
|
|
90
|
+
.legend{display:flex;flex-wrap:wrap;gap:.2em 1.6em;margin:0 0 1em;padding:0;list-style:none;font-size:.9em;
|
|
91
|
+
color:var(--muted)}
|
|
92
|
+
.legend li::before{content:"";display:inline-block;width:.75em;height:.75em;margin-right:.45em;vertical-align:-.05em}
|
|
93
|
+
.key-permissive::before{background:var(--permissive)}
|
|
94
|
+
.key-weak-copyleft::before{background:var(--weak)}
|
|
95
|
+
.key-refused::before{background:var(--refused)}
|
|
96
|
+
.bars{display:grid;grid-template-columns:minmax(9em,max-content) 1fr max-content;gap:.35em 1em;align-items:center;
|
|
97
|
+
margin:.4em 0 1em;font-size:.94em}
|
|
98
|
+
.bars .label{overflow-wrap:anywhere}
|
|
99
|
+
.bars .track{display:block;height:8px;background:var(--hair)}
|
|
100
|
+
.bars .fill{display:block;height:100%}
|
|
101
|
+
.bars .count{color:var(--muted);text-align:right}
|
|
102
|
+
.sev,.chip{display:inline-block;padding:0 .45em;border:1px solid;font-family:var(--serif);font-size:.82em;
|
|
103
|
+
font-weight:600;letter-spacing:.02em;line-height:1.5;white-space:nowrap}
|
|
104
|
+
.sev{text-transform:capitalize;color:#3c434e;border-color:#c3cbd6;background:#f3f5f8}
|
|
105
|
+
.sev-critical{color:#fff;background:#7a1c15;border-color:#7a1c15}
|
|
106
|
+
.sev-high{color:var(--bad);background:var(--bad-bg);border-color:#e3b7b2}
|
|
107
|
+
.sev-moderate{color:var(--warn);background:var(--warn-bg);border-color:#e6cf9a}
|
|
108
|
+
.sev-low{color:var(--accent);background:var(--code-bg);border-color:var(--code-edge)}
|
|
109
|
+
.chip-permissive{color:var(--accent);background:var(--code-bg);border-color:var(--code-edge)}
|
|
110
|
+
.chip-weak-copyleft{color:var(--warn);background:var(--warn-bg);border-color:#e6cf9a}
|
|
111
|
+
.chip-refused{color:var(--bad);background:var(--bad-bg);border-color:#e3b7b2}
|
|
112
|
+
.statements{margin:.3em 0 1em;padding:0;list-style:none}
|
|
113
|
+
.statements li{position:relative;padding:.1em 0 .55em 1.75em;text-align:justify;hyphens:auto;letter-spacing:-.01em}
|
|
114
|
+
.statements li::before{position:absolute;left:.1em;top:.1em;font-weight:700}
|
|
115
|
+
.statements li.pass::before{content:"\2713";color:var(--good)}
|
|
116
|
+
.statements li.fail::before{content:"\2717";color:var(--bad)}
|
|
117
|
+
.statements.verdict{margin:1.2em 0 1.4em;font-size:1.15em;line-height:1.4}
|
|
118
|
+
.statements.verdict li{padding-bottom:.45em;text-align:left;hyphens:manual}
|
|
119
|
+
.status-open{color:var(--bad);background:var(--bad-bg);border-color:#e3b7b2}
|
|
120
|
+
.status-mitigated{color:var(--good);background:var(--good-bg);border-color:#a9cdb4}
|
|
121
|
+
a.plain,a.plain:hover{border:0;background:none}
|
|
122
|
+
.plan{margin:0 0 1.6em}
|
|
123
|
+
.plan>.title{margin:0;font-family:var(--display);font-weight:600;font-size:1.05em;color:var(--accent);text-align:left}
|
|
124
|
+
.plan-subject{margin:.15em 0 .5em;font-style:italic;color:var(--muted);text-align:left}
|
|
125
|
+
table.tableblock.facts.plan td:first-child{width:26%;text-align:left;vertical-align:top}
|
|
126
|
+
table.tableblock.facts.plan td{vertical-align:top;text-align:left}
|
|
127
|
+
.ids{display:flex;flex-wrap:wrap;gap:.45em;margin:.2em 0 1.2em;padding:0;list-style:none}
|
|
128
|
+
.ids li{max-width:100%}
|
|
129
|
+
.downloads{display:flex;flex-wrap:wrap;gap:.6em;margin:0 0 .9em}
|
|
130
|
+
button{font-family:var(--serif);font-size:.9em;font-weight:600;padding:.35em 1em;border:1px solid var(--accent);
|
|
131
|
+
background:var(--accent);color:#fff;cursor:pointer}
|
|
132
|
+
button+button{background:#fff;color:var(--accent)}
|
|
133
|
+
.controls{display:flex;flex-wrap:wrap;gap:.6em;align-items:center;margin:0 0 .9em}
|
|
134
|
+
.controls input,.controls select{font:inherit;font-size:.9em;padding:.35em .6em;border:1px solid #b9c2cf;
|
|
135
|
+
background:#fff;color:var(--ink)}
|
|
136
|
+
.controls input{flex:1 1 15em}
|
|
137
|
+
.controls output{color:var(--muted);font-size:.85em}
|
|
138
|
+
.notice{margin:0 0 1.4em}
|
|
139
|
+
.notice>.title{margin:0;font-family:var(--display);font-weight:600;font-size:1.02em;color:var(--accent);text-align:left;
|
|
140
|
+
overflow-wrap:anywhere}
|
|
141
|
+
.notice-packages{margin:.1em 0 .5em;font-size:.82em;font-style:italic;color:var(--muted);text-align:left;
|
|
142
|
+
overflow-wrap:anywhere}
|
|
143
|
+
.license-text{padding:.7em 1em .2em;background:var(--panel);border-left:2px solid var(--rule);font-size:.84em;
|
|
144
|
+
line-height:1.5;overflow-wrap:anywhere}
|
|
145
|
+
.license-text p{margin:0 0 .65em}
|
|
146
|
+
.license-text p.ragged{text-align:left;hyphens:manual}
|
|
147
|
+
.digest{font-family:var(--mono);font-size:.84em;color:var(--accent);word-break:break-all}
|
|
148
|
+
.license-text hr{margin:1.1em 0;border:0;border-top:1px solid var(--rule)}
|
|
149
|
+
.license-entry p{margin:0;text-align:left;hyphens:manual}
|
|
150
|
+
.license-entry p+p{margin-top:.1em}
|
|
151
|
+
.license-entry{margin:0 0 .65em}
|
|
152
|
+
.license-heading{font-family:var(--display);font-weight:700;font-size:1.08em;color:var(--accent)}
|
|
153
|
+
#toc{position:fixed;top:0;left:0;bottom:0;z-index:1000;width:22rem;overflow-y:auto;padding:16.4px 1.35em 3em 1.5em;
|
|
154
|
+
background:var(--panel);border-right:1px solid var(--hair);scrollbar-width:thin;text-align:left}
|
|
155
|
+
#toctitle{margin:0 0 1.2em;padding-bottom:.7em;border-bottom:1px solid var(--hair);font-size:1em;
|
|
156
|
+
font-variant:all-small-caps;letter-spacing:.12em;color:var(--accent-deep)}
|
|
157
|
+
#toc ul{margin:0 0 .5em .125em;padding:0;list-style:none;font-size:.87rem;line-height:1.6}
|
|
158
|
+
#toc ul ul{margin:0 0 0 .2em;padding-left:.8em;border-left:1px solid #c6cedb}
|
|
159
|
+
#toc li{margin-top:.14em}
|
|
160
|
+
#toc .sectlevel1>li{margin-top:.75em}
|
|
161
|
+
#toc .sectlevel1>li:first-child{margin-top:0}
|
|
162
|
+
#toc a{display:flex;gap:.25em;padding:.06em 0;border:0;color:#3c434e;line-height:1.32}
|
|
163
|
+
#toc a:hover{color:var(--accent-deep);background:none}
|
|
164
|
+
#toc .sectlevel1>li>a{color:var(--accent-deep);font-weight:600}
|
|
165
|
+
#toc .leader,#toc .page{display:none}
|
|
166
|
+
#footer{margin-top:3em;padding-top:1.5em;padding-bottom:3em;border-top:1px solid var(--hair);color:var(--muted);
|
|
167
|
+
font-size:.85em}
|
|
168
|
+
@media (max-width:1100px){body{padding-left:0}#toc{position:static;width:auto;border-right:0;
|
|
169
|
+
border-bottom:1px solid var(--hair);padding:1.2rem 1.4rem;margin:1.2em 0}}
|
|
170
|
+
@media (max-width:640px){body{font-size:16px}#header>h1{font-size:2.2em}h2{font-size:1.55em}h3{font-size:1.2em}
|
|
171
|
+
.bars{grid-template-columns:1fr max-content}.bars .track{grid-column:1 / -1}}
|
|
172
|
+
@page{size:A4;margin:16mm 16mm 18mm;
|
|
173
|
+
@bottom-center{content:counter(page);font:8.5pt "Source Serif 4",Georgia,serif;color:#1b1f26;
|
|
174
|
+
border-top:1px solid #e2e6ec;vertical-align:middle}}
|
|
175
|
+
@media print{html{font-size:12px}body{padding:0;font-size:10pt;line-height:1.45;print-color-adjust:exact;
|
|
176
|
+
-webkit-print-color-adjust:exact}#header,#content,#footer{max-width:none;padding:0}#header{padding-top:0}
|
|
177
|
+
#header>h1{border-top:0;padding:0;text-align:center;font-size:30pt;line-height:1.15}#header::after{display:none}
|
|
178
|
+
#toc{position:static;width:auto;padding:0;margin:14pt 0 0;background:none;border:0}
|
|
179
|
+
#toctitle{font-size:18pt;font-variant:normal;letter-spacing:0;color:var(--accent);border:0;padding:0;margin:0 0 8pt}
|
|
180
|
+
#toc ul{font-size:10pt;margin:0;line-height:1.5}#toc ul ul{border:0;margin-left:1.2em;padding-left:0}
|
|
181
|
+
#toc a{color:var(--ink);font-weight:400}#toc .sectlevel1>li>a{color:var(--ink);font-weight:400}
|
|
182
|
+
#toc .sectlevel1>li{margin-top:.15em}#toc li{margin-top:.15em}
|
|
183
|
+
#toc .leader{display:block;flex:1 1 auto;margin:0 .15em .28em .3em;border-bottom:1px dotted #9aa3b0}
|
|
184
|
+
#toc .page{display:inline;text-align:right}#toc .page:empty,#toc .leader:has(+ .page:empty){display:none}
|
|
185
|
+
#content{margin-top:16pt}h2{border-top:0;padding-top:0;margin-top:20pt;break-after:avoid}h3{border-bottom:0;
|
|
186
|
+
padding-bottom:0;break-after:avoid}.controls,.downloads{display:none}.table-wrap{overflow:visible}
|
|
187
|
+
a{color:inherit;border:0}table.tableblock{font-size:8.6pt}table.tableblock th,table.tableblock td{padding:.3em .5em}
|
|
188
|
+
tr,.admonitionblock,table.facts,.plan{break-inside:avoid}.notice>.title,.notice-packages{break-after:avoid}
|
|
189
|
+
.notice{break-inside:avoid}
|
|
190
|
+
.license-text{columns:2;column-gap:7mm;font-size:7.4pt;line-height:1.35;background:none;border:0;padding:0}
|
|
191
|
+
#footer{margin-top:16pt;padding:6pt 0 0}}
|
|
192
|
+
`;
|
|
193
|
+
/** Table filtering, sorting and the embedded downloads. The document reads completely without it. */
|
|
194
|
+
export const REPORT_SCRIPT = `
|
|
195
|
+
(function(){
|
|
196
|
+
var data=JSON.parse(document.getElementById('report-data').textContent);
|
|
197
|
+
function save(name,type,text){var blob=new Blob([text],{type:type});var link=document.createElement('a');
|
|
198
|
+
link.href=URL.createObjectURL(blob);link.download=name;document.body.appendChild(link);link.click();
|
|
199
|
+
setTimeout(function(){URL.revokeObjectURL(link.href);link.remove();},0);}
|
|
200
|
+
document.querySelectorAll('[data-download]').forEach(function(button){button.hidden=false;
|
|
201
|
+
button.addEventListener('click',function(){var key=button.getAttribute('data-download');
|
|
202
|
+
save(button.getAttribute('data-file'),key==='sbom'?'application/vnd.cyclonedx+json':'text/csv',data[key]);});});
|
|
203
|
+
var table=document.getElementById('inventory-table');if(!table){return;}
|
|
204
|
+
var body=table.tBodies[0];var rows=Array.prototype.slice.call(body.rows);
|
|
205
|
+
var search=document.getElementById('filter-text');var scope=document.getElementById('filter-scope');
|
|
206
|
+
var kind=document.getElementById('filter-class');var shown=document.getElementById('filter-count');
|
|
207
|
+
function apply(){var needle=search.value.trim().toLowerCase();var visible=0;rows.forEach(function(row){
|
|
208
|
+
var match=(!needle||row.textContent.toLowerCase().indexOf(needle)>=0)&&(!scope.value||row.dataset.scope===scope.value)
|
|
209
|
+
&&(!kind.value||row.dataset.class===kind.value);row.hidden=!match;if(match){visible++;}});
|
|
210
|
+
shown.textContent=visible+' of '+rows.length+' package versions';}
|
|
211
|
+
[search,scope,kind].forEach(function(control){control.addEventListener('input',apply);});
|
|
212
|
+
table.querySelectorAll('th[data-sort]').forEach(function(header,index){header.tabIndex=0;
|
|
213
|
+
function sort(){var ascending=header.getAttribute('aria-sort')!=='ascending';
|
|
214
|
+
table.querySelectorAll('th').forEach(function(other){other.removeAttribute('aria-sort');});
|
|
215
|
+
header.setAttribute('aria-sort',ascending?'ascending':'descending');
|
|
216
|
+
rows.sort(function(left,right){var a=left.cells[index].textContent;var b=right.cells[index].textContent;
|
|
217
|
+
return (ascending?1:-1)*a.localeCompare(b,undefined,{numeric:true,sensitivity:'base'});});
|
|
218
|
+
rows.forEach(function(row){body.appendChild(row);});}
|
|
219
|
+
header.addEventListener('click',sort);header.addEventListener('keydown',function(event){
|
|
220
|
+
if(event.key==='Enter'||event.key===' '){event.preventDefault();sort();}});});
|
|
221
|
+
apply();})();
|
|
222
|
+
`;
|
|
223
|
+
//# sourceMappingURL=compliance-report-assets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compliance-report-assets.js","sourceRoot":"","sources":["../src/compliance-report-assets.ts"],"names":[],"mappings":"AAAA,iHAAiH;AAYjH;;;;GAIG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,KAA8B,EAAU,EAAE,CAClE,KAAK;KACF,GAAG,CACF,CAAC,IAAkB,EAAU,EAAE,CAC7B,2BAA2B,IAAI,CAAC,MAAM,gBAAgB,IAAI,CAAC,KAAK,gBAAgB,IAAI,CAAC,MAAM,GAAG;IAC9F,qDAAqD,IAAI,CAAC,IAAI,oBAAoB,CACrF;KACA,IAAI,CAAC,EAAE,CAAC,CAAA;AAEb,oGAAoG;AACpG,MAAM,CAAC,MAAM,YAAY,GAAW,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoL7C,CAAA;AAED,qGAAqG;AACrG,MAAM,CAAC,MAAM,aAAa,GAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BpC,CAAA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ComplianceReport } from './compliance-report.js';
|
|
2
|
+
import { type EmbeddedFont } from './compliance-report-assets.js';
|
|
3
|
+
/** What the report carries besides its own markup. */
|
|
4
|
+
export interface ReportAttachments {
|
|
5
|
+
/** The CycloneDX document as written, so its SHA-256 matches the one the report prints. */
|
|
6
|
+
readonly sbom: string;
|
|
7
|
+
/** The typefaces the report is set in, embedded so it reads the same on every machine. */
|
|
8
|
+
readonly fonts: readonly EmbeddedFont[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Render the compliance report as one self-contained HTML document.
|
|
12
|
+
* @param report the assembled report.
|
|
13
|
+
* @param attachments the exact bytes the report carries, and the typefaces it is set in.
|
|
14
|
+
* @param fileStem the file name, without extension, downloads are offered under.
|
|
15
|
+
* @returns the complete document.
|
|
16
|
+
*/
|
|
17
|
+
export declare const renderComplianceReport: (report: ComplianceReport, attachments: ReportAttachments, fileStem: string) => string;
|
|
18
|
+
//# sourceMappingURL=compliance-report-html.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compliance-report-html.d.ts","sourceRoot":"","sources":["../src/compliance-report-html.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAA4D,MAAM,wBAAwB,CAAA;AACxH,OAAO,EAAE,KAAK,YAAY,EAA0C,MAAM,+BAA+B,CAAA;AA6BzG,sDAAsD;AACtD,MAAM,WAAW,iBAAiB;IAChC,2FAA2F;IAC3F,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,0FAA0F;IAC1F,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAA;CACxC;AAycD;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,GACjC,QAAQ,gBAAgB,EACxB,aAAa,iBAAiB,EAC9B,UAAU,MAAM,KACf,MAqBW,CAAA"}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
// Render the open source software report as one self-contained HTML document: no external asset, so the file is
|
|
2
|
+
// the whole deliverable whether it is opened offline, attached to a message, or printed to PDF.
|
|
3
|
+
import { fontFaces, REPORT_SCRIPT, REPORT_STYLE } from './compliance-report-assets.js';
|
|
4
|
+
import { admonition, CLASS_LABELS, CLASSES, cells, classChip, code, embeddedJson, escapeHtml, facts, formatCount, idList, inventoryCsv, nowrap, packageLink, paragraphs, percentOf, plural, relationLabel, scopeLabel, severityChip, table, versionLabel, } from './compliance-report-markup.js';
|
|
5
|
+
import { section, sectionReference, subheading, tableOfContents } from './compliance-report-outline.js';
|
|
6
|
+
const SHORT_COMMIT = 12;
|
|
7
|
+
const CHART_LICENSES = 10;
|
|
8
|
+
const shortCommit = (commit) => commit.slice(0, SHORT_COMMIT);
|
|
9
|
+
const draftNotice = (report) => report.draftReasons.length === 0
|
|
10
|
+
? ''
|
|
11
|
+
: admonition('warning', `<p>This report is a draft and must not be treated as release evidence. ${report.draftReasons
|
|
12
|
+
.map((reason) => escapeHtml(reason))
|
|
13
|
+
.join(' ')}</p>`);
|
|
14
|
+
const statement = (outcome, text) => `<li class="${outcome}">${text}</li>`;
|
|
15
|
+
const verdictOf = (isPassing) => (isPassing ? 'pass' : 'fail');
|
|
16
|
+
// The verb that agrees with a counted subject: "1 package version has", "2 package versions have".
|
|
17
|
+
const agreeing = (count, one, several) => (count === 1 ? one : several);
|
|
18
|
+
const severityOf = (entry) => entry.advisory.severity.toLowerCase();
|
|
19
|
+
const countAt = (entries, severity) => entries.filter((entry) => severityOf(entry) === severity).length;
|
|
20
|
+
// "1 high, 2 low", most severe first, which is the order the advisories are already listed in.
|
|
21
|
+
const severityBreakdown = (entries) => [...new Set(entries.map((entry) => severityOf(entry)))]
|
|
22
|
+
.map((severity) => `${formatCount(countAt(entries, severity))} ${escapeHtml(severity)}`)
|
|
23
|
+
.join(', ');
|
|
24
|
+
const licenseStatement = (report) => {
|
|
25
|
+
const outside = report.classCounts.refused;
|
|
26
|
+
return statement(verdictOf(outside === 0), outside === 0
|
|
27
|
+
? `All ${plural(report.packages.length, 'package version')} have a license within the policy.`
|
|
28
|
+
: `${plural(outside, 'package version')} ${agreeing(outside, 'has', 'have')} a license outside the policy. ` +
|
|
29
|
+
`Each is listed in ${sectionReference('review-outside')}.`);
|
|
30
|
+
};
|
|
31
|
+
const openStatement = (report, mitigatedCount) => {
|
|
32
|
+
const open = report.advisories.filter((entry) => entry.mitigation === undefined);
|
|
33
|
+
if (open.length > 0) {
|
|
34
|
+
return statement('fail', `${plural(open.length, 'vulnerability', 'vulnerabilities')} (${severityBreakdown(open)}) ` +
|
|
35
|
+
`${agreeing(open.length, 'is', 'are')} open, with no mitigation plan. ` +
|
|
36
|
+
`Each is listed in ${sectionReference('advisories-all')}.`);
|
|
37
|
+
}
|
|
38
|
+
return statement('pass', mitigatedCount === 0 ? 'No known vulnerabilities.' : 'No open vulnerabilities.');
|
|
39
|
+
};
|
|
40
|
+
const vulnerabilityStatements = (report) => {
|
|
41
|
+
const mitigated = report.advisories.filter((entry) => entry.mitigation !== undefined);
|
|
42
|
+
const stale = report.vulnerabilities.deadEntries.length;
|
|
43
|
+
return [
|
|
44
|
+
openStatement(report, mitigated.length),
|
|
45
|
+
...(mitigated.length > 0
|
|
46
|
+
? [
|
|
47
|
+
statement('pass', `${plural(mitigated.length, 'vulnerability', 'vulnerabilities')} (${severityBreakdown(mitigated)}) ` +
|
|
48
|
+
`${agreeing(mitigated.length, 'is mitigated, with a', 'are mitigated, each with a')} ` +
|
|
49
|
+
`mitigation plan in ${sectionReference('advisories-plans')}.`),
|
|
50
|
+
]
|
|
51
|
+
: []),
|
|
52
|
+
...(stale > 0
|
|
53
|
+
? [
|
|
54
|
+
statement('fail', `${plural(stale, 'recorded mitigation')} ${agreeing(stale, 'matches', 'match')} no current ` +
|
|
55
|
+
`vulnerability. Each is listed in ${sectionReference('advisories-stale')}.`),
|
|
56
|
+
]
|
|
57
|
+
: []),
|
|
58
|
+
];
|
|
59
|
+
};
|
|
60
|
+
const verdict = (report) => `<ul class="statements verdict">${[licenseStatement(report), ...vulnerabilityStatements(report)].join('')}</ul>`;
|
|
61
|
+
const preamble = (report) => `<div id="preamble">${draftNotice(report)}${verdict(report)}${facts([
|
|
62
|
+
['Project', escapeHtml(report.projectName)],
|
|
63
|
+
...(report.projectDescription === undefined
|
|
64
|
+
? []
|
|
65
|
+
: [['Description', escapeHtml(report.projectDescription)]]),
|
|
66
|
+
['Source commit', code(report.source.commit)],
|
|
67
|
+
['Report generated', escapeHtml(report.generatedAt)],
|
|
68
|
+
['Vulnerability data as of', escapeHtml(report.auditedAt)],
|
|
69
|
+
])}</div>`;
|
|
70
|
+
// The verdict precedes the contents, so a reader who opens only the first page reads it.
|
|
71
|
+
const header = (report) => `<div id="header"><h1>Open Source Software Report</h1>${preamble(report)}${tableOfContents()}</div>`;
|
|
72
|
+
const advisoryLink = (advisory) => advisory.url?.startsWith('https://') === true
|
|
73
|
+
? `<a href="${escapeHtml(advisory.url)}" rel="noreferrer noopener">${nowrap(code(advisory.id))}</a>`
|
|
74
|
+
: nowrap(code(advisory.id));
|
|
75
|
+
const aliasLine = (advisory) => advisory.aliases.length > 0 ? `<br><span class="mono">${escapeHtml(advisory.aliases.join(', '))}</span>` : '';
|
|
76
|
+
const planId = (index) => `plan-${String(index + 1)}`;
|
|
77
|
+
const statusOf = (entry, planIndex) => entry.mitigation === undefined
|
|
78
|
+
? '<span class="chip status-open">Open</span>'
|
|
79
|
+
: `<a class="plain" href="#${planId(planIndex)}"><span class="chip status-mitigated">Mitigated</span></a>`;
|
|
80
|
+
const ADVISORY_HEADINGS = ['Severity', 'Advisory', 'Package', 'Scope', 'Status'];
|
|
81
|
+
const advisoryRow = (entry, mitigated) => {
|
|
82
|
+
const status = statusOf(entry, mitigated.indexOf(entry));
|
|
83
|
+
return cells([
|
|
84
|
+
severityChip(entry.advisory.severity),
|
|
85
|
+
`${advisoryLink(entry.advisory)}${aliasLine(entry.advisory)}<br>${escapeHtml(entry.advisory.title)}`,
|
|
86
|
+
escapeHtml(entry.advisory.packageName),
|
|
87
|
+
nowrap(entry.isProduction ? 'Production' : 'Development'),
|
|
88
|
+
nowrap(status),
|
|
89
|
+
]);
|
|
90
|
+
};
|
|
91
|
+
const plan = (entry, index) => {
|
|
92
|
+
const addedOn = escapeHtml(entry.mitigation?.addedOn ?? '');
|
|
93
|
+
const recorded = `<span class="mono">${nowrap(addedOn)}</span>` + (entry.isHarnessMitigation ? ' by the build harness' : '');
|
|
94
|
+
return (`<div class="plan" id="${planId(index)}"><p class="title">${severityChip(entry.advisory.severity)} ` +
|
|
95
|
+
`${nowrap(code(entry.advisory.id))} in ${escapeHtml(entry.advisory.packageName)}</p>` +
|
|
96
|
+
`<p class="plan-subject">${escapeHtml(entry.advisory.title)}</p>` +
|
|
97
|
+
facts([
|
|
98
|
+
['Why it can't be exploited here', escapeHtml(entry.mitigation?.reason ?? '')],
|
|
99
|
+
['Mitigation plan', escapeHtml(entry.mitigation?.mitigation ?? '')],
|
|
100
|
+
['Recorded', recorded],
|
|
101
|
+
], 'plan') +
|
|
102
|
+
'</div>');
|
|
103
|
+
};
|
|
104
|
+
const listItem = (text) => `<li>${escapeHtml(text)}</li>`;
|
|
105
|
+
const staleMitigations = (entries) => entries.length > 0
|
|
106
|
+
? `<ul>${entries.map((entry) => listItem(entry)).join('')}</ul>`
|
|
107
|
+
: '<p class="empty">Every recorded mitigation matches a current vulnerability.</p>';
|
|
108
|
+
const vulnerabilities = (report) => {
|
|
109
|
+
const mitigated = report.advisories.filter((entry) => entry.mitigation !== undefined);
|
|
110
|
+
return section('vulnerabilities', paragraphs([
|
|
111
|
+
'Every installed package, development tools included, was checked against the GitHub Advisory Database ' +
|
|
112
|
+
`at ${escapeHtml(report.auditedAt)}.`,
|
|
113
|
+
'Each vulnerability is either open, which fails the build, or mitigated, which means the project recorded ' +
|
|
114
|
+
'why it can't be exploited here and a mitigation plan.',
|
|
115
|
+
]) +
|
|
116
|
+
subheading('advisories-all') +
|
|
117
|
+
table(ADVISORY_HEADINGS, report.advisories.map((entry) => advisoryRow(entry, mitigated)), 'No known vulnerability was reported.') +
|
|
118
|
+
subheading('advisories-plans') +
|
|
119
|
+
(mitigated.length > 0
|
|
120
|
+
? mitigated.map((entry, index) => plan(entry, index)).join('')
|
|
121
|
+
: '<p class="empty">No vulnerability needs a mitigation plan.</p>') +
|
|
122
|
+
subheading('advisories-stale') +
|
|
123
|
+
staleMitigations(report.vulnerabilities.deadEntries));
|
|
124
|
+
};
|
|
125
|
+
const classStack = (report) => {
|
|
126
|
+
const total = report.packages.length;
|
|
127
|
+
const share = (licenseClass) => percentOf(report.classCounts[licenseClass], total);
|
|
128
|
+
const description = CLASSES.map((licenseClass) => `${CLASS_LABELS[licenseClass]} ${share(licenseClass)} percent`).join(', ');
|
|
129
|
+
return (`<div class="stack" role="img" aria-label="${description}">${CLASSES.map((licenseClass) => `<i class="seg-${licenseClass}" style="width:${share(licenseClass)}%"></i>`).join('')}</div>` +
|
|
130
|
+
`<ul class="legend">${CLASSES.map((licenseClass) => `<li class="key-${licenseClass}">${CLASS_LABELS[licenseClass]}: ` +
|
|
131
|
+
`${formatCount(report.classCounts[licenseClass])} (${share(licenseClass)}%)</li>`).join('')}</ul>`);
|
|
132
|
+
};
|
|
133
|
+
const bar = (label, count, largest, tone) => `<span class="label">${escapeHtml(label)}</span><span class="track"><span class="fill fill-${tone}" ` +
|
|
134
|
+
`style="width:${percentOf(count, largest)}%"></span></span><span class="count">${formatCount(count)}</span>`;
|
|
135
|
+
const licenseBars = (report) => {
|
|
136
|
+
const shown = report.licenseCounts.slice(0, CHART_LICENSES);
|
|
137
|
+
const rest = report.licenseCounts.slice(CHART_LICENSES);
|
|
138
|
+
const largest = shown[0]?.count ?? 0;
|
|
139
|
+
const restCount = rest.reduce((sum, entry) => sum + entry.count, 0);
|
|
140
|
+
return `<div class="bars">${[
|
|
141
|
+
...shown.map((entry) => bar(entry.license, entry.count, largest, entry.licenseClass)),
|
|
142
|
+
...(rest.length > 0 ? [bar(plural(rest.length, 'other license'), restCount, largest, 'other')] : []),
|
|
143
|
+
].join('')}</div>`;
|
|
144
|
+
};
|
|
145
|
+
const REVIEW_HEADINGS = ['Package', 'Version', 'License', 'Scope', 'Relation'];
|
|
146
|
+
const reviewRow = (row) => {
|
|
147
|
+
const version = nowrap(escapeHtml(versionLabel(row)));
|
|
148
|
+
return cells([
|
|
149
|
+
packageLink(row),
|
|
150
|
+
`<span class="mono">${version}</span>`,
|
|
151
|
+
code(row.license),
|
|
152
|
+
nowrap(scopeLabel(row)),
|
|
153
|
+
nowrap(relationLabel(row)),
|
|
154
|
+
]);
|
|
155
|
+
};
|
|
156
|
+
const licenses = (report) => {
|
|
157
|
+
const total = report.packages.length;
|
|
158
|
+
return section('licenses', paragraphs([
|
|
159
|
+
`${plural(total, 'package version')} ${agreeing(total, 'is', 'are')} installed, ` +
|
|
160
|
+
`${formatCount(report.productionCount)} in the production dependency graph and ` +
|
|
161
|
+
`${formatCount(total - report.productionCount)} used only for development and build.`,
|
|
162
|
+
`Each license is judged against the policy in ${sectionReference('policy')}.`,
|
|
163
|
+
]) +
|
|
164
|
+
subheading('license-classes') +
|
|
165
|
+
classStack(report) +
|
|
166
|
+
subheading('common-licenses') +
|
|
167
|
+
licenseBars(report) +
|
|
168
|
+
subheading('review-weak-copyleft') +
|
|
169
|
+
paragraphs([
|
|
170
|
+
'The policy admits weak copyleft only for components used unmodified. Development components are build, ' +
|
|
171
|
+
'test and analysis tools that aren't distributed with the application.',
|
|
172
|
+
]) +
|
|
173
|
+
table(REVIEW_HEADINGS, report.copyleft.map((row) => reviewRow(row)), 'No dependency carries a weak copyleft license.') +
|
|
174
|
+
subheading('review-outside') +
|
|
175
|
+
table(REVIEW_HEADINGS, report.refused.map((row) => reviewRow(row)), 'No dependency declares a license outside the policy.'));
|
|
176
|
+
};
|
|
177
|
+
const policy = (report) => section('policy', paragraphs([
|
|
178
|
+
'The build enforces this policy, rendered from the same definition the license check reads. A license is ' +
|
|
179
|
+
'identified by its Software Package Data Exchange (SPDX) identifier. In a compound expression, an OR is ' +
|
|
180
|
+
'satisfied by any admitted alternative, and an AND requires every part.',
|
|
181
|
+
]) +
|
|
182
|
+
subheading('policy-permissive') +
|
|
183
|
+
paragraphs([
|
|
184
|
+
`The policy admits ${plural(report.policy.permissive.length, 'license')} with no copyleft obligation. ` +
|
|
185
|
+
`Attribution, where the license requires it, is satisfied by ${sectionReference('notices')}.`,
|
|
186
|
+
]) +
|
|
187
|
+
idList(report.policy.permissive) +
|
|
188
|
+
subheading('policy-weak-copyleft') +
|
|
189
|
+
paragraphs([
|
|
190
|
+
`The policy admits ${plural(report.policy.weakCopyleft.length, 'license')} only for dependencies used ` +
|
|
191
|
+
'unmodified. Their copyleft, whether file-level or library-level, doesn't extend to the ' +
|
|
192
|
+
'project's own source when the component is used as published.',
|
|
193
|
+
]) +
|
|
194
|
+
idList(report.policy.weakCopyleft) +
|
|
195
|
+
subheading('policy-outside') +
|
|
196
|
+
paragraphs([
|
|
197
|
+
'Every other license is outside the policy, including strong copyleft such as the GNU General ' +
|
|
198
|
+
'Public License, licenses that restrict the field of use, and any package whose license can't be ' +
|
|
199
|
+
'resolved. A single such package fails the build.',
|
|
200
|
+
]));
|
|
201
|
+
const inventoryRow = (row) => `<tr data-scope="${row.isProduction ? 'production' : 'development'}" data-class="${row.licenseClass}">` +
|
|
202
|
+
`<td>${packageLink(row)}</td><td class="mono nowrap">${escapeHtml(versionLabel(row))}</td>` +
|
|
203
|
+
`<td>${code(row.license)}</td><td>${classChip(row.licenseClass)}</td>` +
|
|
204
|
+
`<td class="nowrap">${scopeLabel(row)}</td><td class="nowrap">${relationLabel(row)}</td></tr>`;
|
|
205
|
+
const classOption = (licenseClass) => `<option value="${licenseClass}">${CLASS_LABELS[licenseClass]}</option>`;
|
|
206
|
+
const inventoryControls = (fileStem) => '<div class="downloads">' +
|
|
207
|
+
`<button type="button" data-download="sbom" data-file="${escapeHtml(fileStem)}.cdx.json" hidden>` +
|
|
208
|
+
'Download SBOM (CycloneDX JSON)</button>' +
|
|
209
|
+
`<button type="button" data-download="csv" data-file="${escapeHtml(fileStem)}.csv" hidden>` +
|
|
210
|
+
'Download inventory (CSV)</button></div>' +
|
|
211
|
+
'<div class="controls">' +
|
|
212
|
+
'<input id="filter-text" type="search" placeholder="Filter by package or license" ' +
|
|
213
|
+
'aria-label="Filter by package or license">' +
|
|
214
|
+
'<select id="filter-scope" aria-label="Scope"><option value="">All scopes</option>' +
|
|
215
|
+
'<option value="production">Production</option><option value="development">Development</option></select>' +
|
|
216
|
+
'<select id="filter-class" aria-label="License class"><option value="">All license classes</option>' +
|
|
217
|
+
CLASSES.map((licenseClass) => classOption(licenseClass)).join('') +
|
|
218
|
+
'</select><output id="filter-count" aria-live="polite"></output></div>';
|
|
219
|
+
const INVENTORY_HEADINGS = ['Package', 'Version', 'License', 'Class', 'Scope', 'Relation'];
|
|
220
|
+
const inventory = (report, fileStem) => section('inventory', paragraphs([
|
|
221
|
+
`This section lists every installed package version, ${plural(report.packages.length, 'row')}, with the ` +
|
|
222
|
+
'license its installed manifest declares.',
|
|
223
|
+
'Scope is production when the version is reachable from a production dependency of any workspace package, ' +
|
|
224
|
+
'and development otherwise.',
|
|
225
|
+
'Relation is direct when a workspace manifest declares the package by name.',
|
|
226
|
+
]) +
|
|
227
|
+
inventoryControls(fileStem) +
|
|
228
|
+
'<div class="table-wrap"><table class="tableblock" id="inventory-table"><thead><tr>' +
|
|
229
|
+
INVENTORY_HEADINGS.map((heading) => `<th scope="col" data-sort>${heading}</th>`).join('') +
|
|
230
|
+
`</tr></thead><tbody>${report.packages.map((row) => inventoryRow(row)).join('')}` +
|
|
231
|
+
'</tbody></table></div>');
|
|
232
|
+
const LICENSE_HEADING = /^#{1,6}[ \t]+/u;
|
|
233
|
+
const LICENSE_RULE = /^[-=_*~]{3,}$/u;
|
|
234
|
+
// A web or e-mail address cannot break between words, and a copyright statement is a short line of names,
|
|
235
|
+
// so justifying either opens wide gaps. Such a paragraph is set flush left instead.
|
|
236
|
+
const FLUSH_LEFT = /\b(?:https?:\/\/|www\.)\S{20}|<[^<>\s]{1,80}@[^<>\s]{1,80}>|^(?:\(c\)|copyright\b|\u{A9})/iu;
|
|
237
|
+
// A licence block is plain lines, except that a bundle of licences titles each one with a Markdown heading
|
|
238
|
+
// and separates them with a ruled line. Those two are drawn as what they are, not printed as characters.
|
|
239
|
+
const licenseLine = (line) => {
|
|
240
|
+
if (LICENSE_RULE.test(line)) {
|
|
241
|
+
return '<hr>';
|
|
242
|
+
}
|
|
243
|
+
return LICENSE_HEADING.test(line)
|
|
244
|
+
? `<p class="license-heading">${escapeHtml(line.replace(LICENSE_HEADING, ''))}</p>`
|
|
245
|
+
: `<p>${escapeHtml(line)}</p>`;
|
|
246
|
+
};
|
|
247
|
+
const licenseBlock = (block) => {
|
|
248
|
+
const lines = block.split('\n');
|
|
249
|
+
const isStructured = lines.some((line) => LICENSE_RULE.test(line) || LICENSE_HEADING.test(line));
|
|
250
|
+
const alignment = FLUSH_LEFT.test(block) ? ' class="ragged"' : '';
|
|
251
|
+
return isStructured
|
|
252
|
+
? `<div class="license-entry">${lines.map((line) => licenseLine(line)).join('')}</div>`
|
|
253
|
+
: `<p${alignment}>${lines.map((line) => escapeHtml(line)).join('<br>')}</p>`;
|
|
254
|
+
};
|
|
255
|
+
// The package is what a reader looks for, so it heads the entry. The file names follow, with the number of
|
|
256
|
+
// package versions that share the text when there is more than one.
|
|
257
|
+
const noticeSource = (group) => escapeHtml(group.fileNames.join(', ')) +
|
|
258
|
+
(group.coordinates.length > 1 ? `, shared by ${plural(group.coordinates.length, 'package version')}` : '');
|
|
259
|
+
const noticeGroup = (group, index) => `<div class="notice" id="notice-${String(index + 1)}">` +
|
|
260
|
+
`<p class="title">${escapeHtml(group.coordinates.join(', '))}</p>` +
|
|
261
|
+
`<p class="notice-packages">${noticeSource(group)}</p>` +
|
|
262
|
+
`<div class="license-text">${group.text
|
|
263
|
+
.split('\n\n')
|
|
264
|
+
.map((block) => licenseBlock(block))
|
|
265
|
+
.join('')}</div></div>`;
|
|
266
|
+
const missingNotices = (report) => report.withoutNotice.length > 0
|
|
267
|
+
? paragraphs([
|
|
268
|
+
'These production package versions ship no license file.',
|
|
269
|
+
`Their declared license is listed in ${sectionReference('inventory')}, and no text is invented for them.`,
|
|
270
|
+
]) + idList(report.withoutNotice)
|
|
271
|
+
: '<p class="empty">Every production package version ships a license file.</p>';
|
|
272
|
+
const notices = (report) => section('notices', paragraphs([
|
|
273
|
+
'This section reproduces the license and notice files shipped by each production dependency, from the ' +
|
|
274
|
+
'installed package.',
|
|
275
|
+
'The words are unchanged. Only the fixed-width line breaks and the Markdown markers of the original files ' +
|
|
276
|
+
'are removed so the text reads as paragraphs.',
|
|
277
|
+
'Identical texts are printed once with every package version that ships them.',
|
|
278
|
+
'Development dependencies aren't distributed with the application, so this section doesn't ' +
|
|
279
|
+
'reproduce them.',
|
|
280
|
+
]) +
|
|
281
|
+
subheading('notices-missing') +
|
|
282
|
+
missingNotices(report) +
|
|
283
|
+
subheading('notices-texts') +
|
|
284
|
+
(report.notices.length > 0
|
|
285
|
+
? report.notices.map((group, index) => noticeGroup(group, index)).join('')
|
|
286
|
+
: '<p class="empty">No production dependency ships a license file.</p>'));
|
|
287
|
+
// A digest is read as one value, so it is set in the code face without a chip, which would split in two
|
|
288
|
+
// where the value wraps.
|
|
289
|
+
const digest = (value) => `<span class="digest">${escapeHtml(value)}</span>`;
|
|
290
|
+
const provenance = (report) => section('provenance', paragraphs([
|
|
291
|
+
'This report is generated by the build, not written by hand.',
|
|
292
|
+
'It reads the same installed dependency tree, license policy and advisory data that the license and ' +
|
|
293
|
+
'vulnerability checks of the build use to pass or fail, so the report and the verdict can't disagree.',
|
|
294
|
+
'Anyone with the repository can regenerate it from the commit below.',
|
|
295
|
+
]) +
|
|
296
|
+
subheading('provenance-source') +
|
|
297
|
+
facts([
|
|
298
|
+
['Source commit', code(report.source.commit)],
|
|
299
|
+
['Uncommitted changes', report.source.isDirty ? 'Present, so this report is a draft' : 'None'],
|
|
300
|
+
['Source fingerprint (SHA-256)', digest(report.source.fingerprint)],
|
|
301
|
+
['Lockfile (SHA-256)', digest(report.source.lockfileSha256)],
|
|
302
|
+
['SBOM (SHA-256)', digest(report.sbom.sha256)],
|
|
303
|
+
['SBOM generator', digest(report.sbom.generator)],
|
|
304
|
+
['Report date', escapeHtml(report.generatedAt)],
|
|
305
|
+
['Regenerate', code('pnpm exec ploaness compliance-report')],
|
|
306
|
+
]) +
|
|
307
|
+
subheading('provenance-scope') +
|
|
308
|
+
paragraphs([
|
|
309
|
+
'The report covers every package the pnpm workspace resolves, for every workspace package, including ' +
|
|
310
|
+
'development and build tools.',
|
|
311
|
+
'A license is the one the installed package manifest declares.',
|
|
312
|
+
'The embedded SBOM lists the same resolved set with its dependency graph.',
|
|
313
|
+
]));
|
|
314
|
+
const body = (report, fileStem) => [
|
|
315
|
+
vulnerabilities(report),
|
|
316
|
+
licenses(report),
|
|
317
|
+
policy(report),
|
|
318
|
+
inventory(report, fileStem),
|
|
319
|
+
notices(report),
|
|
320
|
+
provenance(report),
|
|
321
|
+
];
|
|
322
|
+
/**
|
|
323
|
+
* Render the compliance report as one self-contained HTML document.
|
|
324
|
+
* @param report the assembled report.
|
|
325
|
+
* @param attachments the exact bytes the report carries, and the typefaces it is set in.
|
|
326
|
+
* @param fileStem the file name, without extension, downloads are offered under.
|
|
327
|
+
* @returns the complete document.
|
|
328
|
+
*/
|
|
329
|
+
export const renderComplianceReport = (report, attachments, fileStem) => [
|
|
330
|
+
'<!doctype html>',
|
|
331
|
+
'<html lang="en">',
|
|
332
|
+
'<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
333
|
+
`<title>Open source software report - ${escapeHtml(report.projectName)}</title>`,
|
|
334
|
+
`<style>${fontFaces(attachments.fonts)}${REPORT_STYLE}</style></head>`,
|
|
335
|
+
'<body class="article toc2 toc-left">',
|
|
336
|
+
header(report),
|
|
337
|
+
'<div id="content">',
|
|
338
|
+
...body(report, fileStem),
|
|
339
|
+
'</div>',
|
|
340
|
+
`<div id="footer">Report for ${escapeHtml(report.projectName)} at commit ` +
|
|
341
|
+
`${code(shortCommit(report.source.commit))}, ${escapeHtml(report.generatedAt)}.</div>`,
|
|
342
|
+
`<script type="application/json" id="report-data">${embeddedJson({
|
|
343
|
+
sbom: attachments.sbom,
|
|
344
|
+
csv: inventoryCsv(report.packages),
|
|
345
|
+
})}</script>`,
|
|
346
|
+
`<script>${REPORT_SCRIPT}</script>`,
|
|
347
|
+
'</body></html>',
|
|
348
|
+
'',
|
|
349
|
+
].join('\n');
|
|
350
|
+
//# sourceMappingURL=compliance-report-html.js.map
|