@@ -21,7 +21,18 @@ glob(path.resolve(__dirname, csaf20DocumentGlob), async (err, matches) => {
21
21
` L Validating: ${ path . relative ( process . cwd ( ) , filePath ) } ...` ,
22
22
) ;
23
23
const fileContents = require ( filePath ) ;
24
- const { isValid, errors} = await document . validate ( { document : fileContents } ) ;
24
+ const validationResults : Record < string , ValidationResult > = {
25
+ secvisogram : await document . validate ( { document : fileContents } ) ,
26
+ revisionDate : validateTracking ( fileContents ) ,
27
+ distribution : validateDistribution ( fileContents ) ,
28
+ productTree : validateProductTree ( fileContents ) ,
29
+ publisher : validatePublisher ( fileContents ) ,
30
+ } ;
31
+
32
+ const validationResultsValues = Object . values ( validationResults ) ;
33
+
34
+ const errors = validationResultsValues . flatMap ( x => x . errors ) ;
35
+ const isValid = errors . length < 1 ;
25
36
26
37
if ( isValid ) console . log ( 'Done!' ) ;
27
38
else {
@@ -44,3 +55,163 @@ glob(path.resolve(__dirname, csaf20DocumentGlob), async (err, matches) => {
44
55
45
56
console . log ( 'CSAF 2.0 validation done.' ) ;
46
57
} ) ;
58
+
59
+ interface ValidationResult {
60
+ isValid : boolean ;
61
+ errors : {
62
+ instancePath : string ;
63
+ message ?: string ;
64
+ } [ ] ;
65
+ }
66
+
67
+ function validateTracking ( fileContents : any ) : ValidationResult {
68
+ const tracking = fileContents . document . tracking ;
69
+ let errors : ValidationResult [ 'errors' ] = [ ] ;
70
+
71
+ if ( ! / ^ ( l b s a - [ 1 - 9 ] [ 0 - 9 ] * ) $ / . test ( tracking . id ) ) {
72
+ errors . push ( {
73
+ instancePath : 'document/tracking/id' ,
74
+ message : 'id must match `/^(lbsa-[1-9][0-9]*)$/`.' ,
75
+ } ) ;
76
+ }
77
+
78
+ if ( tracking . status !== 'final' ) {
79
+ errors . push ( {
80
+ instancePath : 'document/tracking/status' ,
81
+ message : 'status must equal `final`.' ,
82
+ } ) ;
83
+ }
84
+
85
+ if ( tracking . revision_history [ 0 ] . date != tracking . current_release_date ) {
86
+ errors . push ( {
87
+ instancePath : 'document/tracking/current_release_date' ,
88
+ message : 'current_release_date does not match latest revision history.' ,
89
+ } ) ;
90
+ }
91
+
92
+ if (
93
+ tracking . revision_history [ tracking . revision_history . length - 1 ] . date !=
94
+ tracking . initial_release_date
95
+ ) {
96
+ errors . push ( {
97
+ instancePath : 'document/tracking/initial_release_date' ,
98
+ message : 'initial_release_date does not match first revision history.' ,
99
+ } ) ;
100
+ }
101
+
102
+ function getVersioningSystem ( str : string ) : 'integer' | 'semver' {
103
+ const intVersioningRegex = / ^ ( 0 | [ 1 - 9 ] [ 0 - 9 ] * ) $ / ;
104
+ return intVersioningRegex . test ( str ) ? 'integer' : 'semver' ;
105
+ }
106
+
107
+ if ( tracking . revision_history . length > 1 ) {
108
+ const versioningSystem = getVersioningSystem (
109
+ tracking . revision_history [ 0 ] . number ,
110
+ ) ;
111
+
112
+ for ( let i = 1 ; i < tracking . revision_history . length ; i ++ ) {
113
+ if (
114
+ getVersioningSystem ( tracking . revision_history [ i ] . number ) !==
115
+ versioningSystem
116
+ ) {
117
+ errors . push ( {
118
+ instancePath : `document/revision_history/${ i } /number` ,
119
+ message : 'number version system inconsistent.' ,
120
+ } ) ;
121
+ }
122
+ }
123
+ }
124
+
125
+ return {
126
+ isValid : errors . length < 1 ,
127
+ errors,
128
+ } ;
129
+ }
130
+
131
+ function validateDistribution ( fileContents : any ) : ValidationResult {
132
+ const distribution = fileContents . document . distribution ;
133
+ const errors : ValidationResult [ 'errors' ] = [ ] ;
134
+ const standardisedDistributionInfo =
135
+ 'Disclosure is not limited.\n' +
136
+ 'SPDX-FileCopyrightText: LoopBack Contributors\n' +
137
+ 'SPDX-License-Identifier: MIT' ;
138
+
139
+ if ( ( distribution . text as string ) !== standardisedDistributionInfo ) {
140
+ errors . push ( {
141
+ instancePath : 'document/distribution/text' ,
142
+ message : `text must be \`${ standardisedDistributionInfo } \`` ,
143
+ } ) ;
144
+ }
145
+
146
+ if ( distribution . tlp ?. label !== 'WHITE' ) {
147
+ errors . push ( {
148
+ instancePath : 'document/distribution/tlp/label' ,
149
+ message : 'label must be `WHITE`' ,
150
+ } ) ;
151
+ }
152
+
153
+ return {
154
+ isValid : errors . length < 1 ,
155
+ errors,
156
+ } ;
157
+ }
158
+
159
+ function validateProductTree ( fileContents : any ) : ValidationResult {
160
+ const productTree = fileContents . product_tree . branches ;
161
+ const errors : ValidationResult [ 'errors' ] = [ ] ;
162
+ const lbRootBranchIndex = ( productTree as any [ ] ) ?. findIndex (
163
+ v => v . name === 'The LoopBack Maintainers' ,
164
+ ) ;
165
+
166
+ if ( lbRootBranchIndex > - 1 ) {
167
+ const lbRootBranch = productTree [ lbRootBranchIndex ] ;
168
+ if ( lbRootBranch . category !== 'vendor' ) {
169
+ errors . push ( {
170
+ instancePath : `product_tree/branches/${ lbRootBranchIndex } /category` ,
171
+ message :
172
+ 'category must be `vendor` for `The LoopBack Maintainers` vendor root branch.' ,
173
+ } ) ;
174
+ }
175
+ } else {
176
+ errors . push ( {
177
+ instancePath : 'product_tree/branches' ,
178
+ message : '`The LoopBack Maintainers` vendor root branch must exist.' ,
179
+ } ) ;
180
+ }
181
+
182
+ return {
183
+ isValid : errors . length < 1 ,
184
+ errors,
185
+ } ;
186
+ }
187
+
188
+ function validatePublisher ( fileContents : any ) : ValidationResult {
189
+ const publisher = fileContents . document . publisher ;
190
+ const errors : ValidationResult [ 'errors' ] = [ ] ;
191
+
192
+ if ( publisher . category !== 'vendor' ) {
193
+ errors . push ( {
194
+ instancePath : 'document/publisher/category' ,
195
+ message : 'category must equal `vendor`' ,
196
+ } ) ;
197
+ }
198
+
199
+ if ( publisher . name !== 'The LoopBack Maintainers' ) {
200
+ errors . push ( {
201
+ instancePath : 'document/publisher/name' ,
202
+ message : 'name must equal `The LoopBack Maintainers`' ,
203
+ } ) ;
204
+ }
205
+
206
+ if ( publisher . namespace !== 'https://loopback.io' ) {
207
+ errors . push ( {
208
+ instancePath : 'document/publisher/namespace' ,
209
+ message : 'namespace must equal `https://loopback.io`' ,
210
+ } ) ;
211
+ }
212
+
213
+ return {
214
+ isValid : errors . length < 1 ,
215
+ errors,
216
+ } ;
217
+ }
0 commit comments