@cyclonedx/cdxgen 12.4.3 → 12.4.4
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/README.md +6 -0
- package/bin/audit.js +7 -0
- package/bin/cdxgen.js +48 -2
- package/bin/evinse.js +7 -0
- package/lib/audit/index.js +165 -2
- package/lib/audit/index.poku.js +462 -0
- package/lib/cli/index.js +317 -169
- package/lib/evinser/evinser.js +31 -9
- package/lib/helpers/analyzer.js +890 -0
- package/lib/helpers/analyzer.poku.js +341 -0
- package/lib/helpers/atomUtils.js +445 -0
- package/lib/helpers/atomUtils.poku.js +137 -0
- package/lib/helpers/bomUtils.js +71 -0
- package/lib/helpers/bomUtils.poku.js +45 -0
- package/lib/helpers/depsUtils.js +146 -0
- package/lib/helpers/depsUtils.poku.js +183 -0
- package/lib/helpers/utils.js +585 -191
- package/lib/helpers/utils.poku.js +357 -4
- package/lib/managers/binary.js +18 -9
- package/lib/stages/postgen/postgen.js +215 -0
- package/lib/stages/postgen/postgen.poku.js +218 -3
- package/lib/validator/bomValidator.js +11 -2
- package/package.json +8 -8
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/atomUtils.d.ts +18 -0
- package/types/lib/helpers/atomUtils.d.ts.map +1 -0
- package/types/lib/helpers/bomUtils.d.ts +10 -0
- package/types/lib/helpers/bomUtils.d.ts.map +1 -1
- package/types/lib/helpers/depsUtils.d.ts +9 -0
- package/types/lib/helpers/depsUtils.d.ts.map +1 -1
- package/types/lib/helpers/utils.d.ts +19 -0
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts +2 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
|
|
3
|
+
const ASTGEN_DEFAULT_IGNORE_DIRS = [
|
|
4
|
+
"venv",
|
|
5
|
+
"docs",
|
|
6
|
+
"e2e",
|
|
7
|
+
"e2e-beta",
|
|
8
|
+
"examples",
|
|
9
|
+
"cypress",
|
|
10
|
+
"jest-cache",
|
|
11
|
+
"eslint-rules",
|
|
12
|
+
"codemods",
|
|
13
|
+
"flow-typed",
|
|
14
|
+
"i18n",
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const ATOM_JS_LANGUAGES = new Set([
|
|
18
|
+
"javascript",
|
|
19
|
+
"js",
|
|
20
|
+
"jsx",
|
|
21
|
+
"node",
|
|
22
|
+
"nodejs",
|
|
23
|
+
"typescript",
|
|
24
|
+
"ts",
|
|
25
|
+
"tsx",
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
function escapeScalaRegexLiteral(value) {
|
|
29
|
+
return value.replace(/[\\^$*+?.()|[\]{}]/g, "\\$&");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function normalizeGlobPattern(pattern) {
|
|
33
|
+
pattern = `${pattern}`;
|
|
34
|
+
let normalizedPattern = "";
|
|
35
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
36
|
+
const char = pattern[i];
|
|
37
|
+
if (char !== "\\") {
|
|
38
|
+
normalizedPattern += char;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const nextChar = pattern[i + 1];
|
|
42
|
+
if (nextChar && "*?[]{}()!+@,".includes(nextChar)) {
|
|
43
|
+
normalizedPattern += char;
|
|
44
|
+
normalizedPattern += nextChar;
|
|
45
|
+
i++;
|
|
46
|
+
} else {
|
|
47
|
+
normalizedPattern += "/";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return normalizedPattern.replace(/^\.\//, "");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function splitGlobAlternates(value, separator = ",") {
|
|
54
|
+
const alternates = [];
|
|
55
|
+
let current = "";
|
|
56
|
+
let braceDepth = 0;
|
|
57
|
+
let bracketDepth = 0;
|
|
58
|
+
let parenDepth = 0;
|
|
59
|
+
for (let i = 0; i < value.length; i++) {
|
|
60
|
+
const char = value[i];
|
|
61
|
+
if (char === "\\") {
|
|
62
|
+
current += char;
|
|
63
|
+
if (i + 1 < value.length) {
|
|
64
|
+
current += value[++i];
|
|
65
|
+
}
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (char === "[" && bracketDepth === 0) {
|
|
69
|
+
bracketDepth++;
|
|
70
|
+
} else if (char === "]" && bracketDepth > 0) {
|
|
71
|
+
bracketDepth--;
|
|
72
|
+
} else if (!bracketDepth) {
|
|
73
|
+
if (char === "{") {
|
|
74
|
+
braceDepth++;
|
|
75
|
+
} else if (char === "}" && braceDepth > 0) {
|
|
76
|
+
braceDepth--;
|
|
77
|
+
} else if (char === "(") {
|
|
78
|
+
parenDepth++;
|
|
79
|
+
} else if (char === ")" && parenDepth > 0) {
|
|
80
|
+
parenDepth--;
|
|
81
|
+
} else if (char === separator && braceDepth === 0 && parenDepth === 0) {
|
|
82
|
+
alternates.push(current);
|
|
83
|
+
current = "";
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
current += char;
|
|
88
|
+
}
|
|
89
|
+
alternates.push(current);
|
|
90
|
+
return alternates;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function findClosingGlobToken(value, startIndex, openChar, closeChar) {
|
|
94
|
+
if (openChar === "[") {
|
|
95
|
+
for (let i = startIndex + 1; i < value.length; i++) {
|
|
96
|
+
if (value[i] === "\\") {
|
|
97
|
+
i++;
|
|
98
|
+
} else if (value[i] === closeChar) {
|
|
99
|
+
return i;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return -1;
|
|
103
|
+
}
|
|
104
|
+
let depth = 0;
|
|
105
|
+
let inBracket = false;
|
|
106
|
+
for (let i = startIndex; i < value.length; i++) {
|
|
107
|
+
const char = value[i];
|
|
108
|
+
if (char === "\\") {
|
|
109
|
+
i++;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (char === "[" && !inBracket) {
|
|
113
|
+
inBracket = true;
|
|
114
|
+
} else if (char === "]" && inBracket) {
|
|
115
|
+
inBracket = false;
|
|
116
|
+
} else if (!inBracket) {
|
|
117
|
+
if (char === openChar) {
|
|
118
|
+
depth++;
|
|
119
|
+
} else if (char === closeChar) {
|
|
120
|
+
depth--;
|
|
121
|
+
if (depth === 0) {
|
|
122
|
+
return i;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return -1;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function globCharClassToRegex(value) {
|
|
131
|
+
if (!value.length) {
|
|
132
|
+
return "\\[";
|
|
133
|
+
}
|
|
134
|
+
let classValue = value;
|
|
135
|
+
let prefix = "";
|
|
136
|
+
if (classValue[0] === "!" || classValue[0] === "^") {
|
|
137
|
+
prefix = "^";
|
|
138
|
+
classValue = classValue.slice(1);
|
|
139
|
+
}
|
|
140
|
+
if (!classValue.length) {
|
|
141
|
+
return "\\[";
|
|
142
|
+
}
|
|
143
|
+
classValue = classValue.replace(/\\/g, "\\\\").replace(/]/g, "\\]");
|
|
144
|
+
return `[${prefix}${classValue}]`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function globSegmentToScalaRegex(segment) {
|
|
148
|
+
let regex = "";
|
|
149
|
+
for (let i = 0; i < segment.length; i++) {
|
|
150
|
+
const char = segment[i];
|
|
151
|
+
const nextChar = segment[i + 1];
|
|
152
|
+
if (char === "\\") {
|
|
153
|
+
if (i + 1 < segment.length) {
|
|
154
|
+
regex += escapeScalaRegexLiteral(segment[++i]);
|
|
155
|
+
} else {
|
|
156
|
+
regex += "\\\\";
|
|
157
|
+
}
|
|
158
|
+
} else if (char === "*" && nextChar !== "(") {
|
|
159
|
+
regex += "[^/\\\\]*";
|
|
160
|
+
} else if (char === "?" && nextChar !== "(") {
|
|
161
|
+
regex += "[^/\\\\]";
|
|
162
|
+
} else if (char === "[") {
|
|
163
|
+
const bracketEnd = findClosingGlobToken(segment, i, "[", "]");
|
|
164
|
+
if (bracketEnd === -1) {
|
|
165
|
+
regex += "\\[";
|
|
166
|
+
} else {
|
|
167
|
+
regex += globCharClassToRegex(segment.slice(i + 1, bracketEnd));
|
|
168
|
+
i = bracketEnd;
|
|
169
|
+
}
|
|
170
|
+
} else if (char === "{") {
|
|
171
|
+
const braceEnd = findClosingGlobToken(segment, i, "{", "}");
|
|
172
|
+
if (braceEnd === -1) {
|
|
173
|
+
regex += "\\{";
|
|
174
|
+
} else {
|
|
175
|
+
const alternates = splitGlobAlternates(
|
|
176
|
+
segment.slice(i + 1, braceEnd),
|
|
177
|
+
).map((alternate) => globSegmentToScalaRegex(alternate));
|
|
178
|
+
regex += `(?:${alternates.join("|")})`;
|
|
179
|
+
i = braceEnd;
|
|
180
|
+
}
|
|
181
|
+
} else if (["@", "?", "+", "*", "!"].includes(char) && nextChar === "(") {
|
|
182
|
+
const parenEnd = findClosingGlobToken(segment, i + 1, "(", ")");
|
|
183
|
+
if (parenEnd === -1) {
|
|
184
|
+
regex += escapeScalaRegexLiteral(char);
|
|
185
|
+
} else {
|
|
186
|
+
const alternates = splitGlobAlternates(
|
|
187
|
+
segment.slice(i + 2, parenEnd),
|
|
188
|
+
"|",
|
|
189
|
+
).map((alternate) => globSegmentToScalaRegex(alternate));
|
|
190
|
+
const alternateRegex = `(?:${alternates.join("|")})`;
|
|
191
|
+
if (char === "@") {
|
|
192
|
+
regex += alternateRegex;
|
|
193
|
+
} else if (char === "?") {
|
|
194
|
+
regex += `${alternateRegex}?`;
|
|
195
|
+
} else if (char === "+") {
|
|
196
|
+
regex += `${alternateRegex}+`;
|
|
197
|
+
} else if (char === "*") {
|
|
198
|
+
regex += `${alternateRegex}*`;
|
|
199
|
+
} else {
|
|
200
|
+
regex += `(?!(?:${alternates.join("|")})$)[^/\\\\]*`;
|
|
201
|
+
}
|
|
202
|
+
i = parenEnd;
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
regex += escapeScalaRegexLiteral(char);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return regex;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function getExcludePatterns(options = {}) {
|
|
212
|
+
if (!Array.isArray(options.exclude)) {
|
|
213
|
+
return [];
|
|
214
|
+
}
|
|
215
|
+
return options.exclude
|
|
216
|
+
.flatMap((pattern) => {
|
|
217
|
+
pattern = `${pattern}`;
|
|
218
|
+
return pattern.includes(",") && !pattern.includes("{")
|
|
219
|
+
? pattern.split(",")
|
|
220
|
+
: [pattern];
|
|
221
|
+
})
|
|
222
|
+
.map((pattern) => pattern.trim())
|
|
223
|
+
.filter(Boolean)
|
|
224
|
+
.filter((pattern) => !pattern.startsWith("!"));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function extractIgnoreDirsFromExcludePatterns(
|
|
228
|
+
patterns,
|
|
229
|
+
includeExactPathFragments = false,
|
|
230
|
+
) {
|
|
231
|
+
const ignoreDirs = new Set();
|
|
232
|
+
for (const pattern of patterns) {
|
|
233
|
+
const normalizedPattern = normalizeGlobPattern(pattern);
|
|
234
|
+
const isExactPath = !/[!*?{}[\]]/.test(normalizedPattern);
|
|
235
|
+
const segments = normalizedPattern.split("/").filter(Boolean);
|
|
236
|
+
const literalSegments = segments.filter(
|
|
237
|
+
(segment) =>
|
|
238
|
+
!/[!*?{}[\]]/.test(segment) && segment !== "." && segment !== "..",
|
|
239
|
+
);
|
|
240
|
+
if (!literalSegments.length) {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
const dirName = literalSegments.at(-1);
|
|
244
|
+
if (
|
|
245
|
+
dirName &&
|
|
246
|
+
((includeExactPathFragments && isExactPath) ||
|
|
247
|
+
!dirName.includes(".") ||
|
|
248
|
+
segments.at(-1) !== dirName)
|
|
249
|
+
) {
|
|
250
|
+
ignoreDirs.add(dirName);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return Array.from(ignoreDirs);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function globToScalaRegexFragment(pattern) {
|
|
257
|
+
pattern = normalizeGlobPattern(pattern);
|
|
258
|
+
const isAbsolute = pattern.startsWith("/");
|
|
259
|
+
const segments = pattern.split("/").filter(Boolean);
|
|
260
|
+
if (!segments.length) {
|
|
261
|
+
return "$^";
|
|
262
|
+
}
|
|
263
|
+
if (segments.length === 1 && segments[0] === "**") {
|
|
264
|
+
return ".*";
|
|
265
|
+
}
|
|
266
|
+
let regex = isAbsolute ? "^[/\\\\]" : "(?:^|.*[/\\\\])";
|
|
267
|
+
for (let i = 0; i < segments.length; i++) {
|
|
268
|
+
const segment = segments[i];
|
|
269
|
+
const isLast = i === segments.length - 1;
|
|
270
|
+
const nextSegment = segments[i + 1];
|
|
271
|
+
if (segment === "**") {
|
|
272
|
+
if (i === 0) {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if (isLast) {
|
|
276
|
+
regex += "(?:[/\\\\].*)?";
|
|
277
|
+
} else {
|
|
278
|
+
regex += "(?:[/\\\\][^/\\\\]+)*[/\\\\]";
|
|
279
|
+
}
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
regex += globSegmentToScalaRegex(segment);
|
|
283
|
+
if (!isLast && nextSegment !== "**") {
|
|
284
|
+
regex += "[/\\\\]";
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return `${regex}$`;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Convert cdxgen's glob-style exclude patterns to a Scala/Java regex string.
|
|
292
|
+
*
|
|
293
|
+
* @param {string[]} patterns Glob patterns from cdxgen's `--exclude` option
|
|
294
|
+
* @returns {string|undefined} Scala-compatible regex or undefined when empty
|
|
295
|
+
*/
|
|
296
|
+
export function globPatternsToAtomIgnoreRegex(patterns = []) {
|
|
297
|
+
const fragments = getExcludePatterns({ exclude: patterns }).map((pattern) =>
|
|
298
|
+
globToScalaRegexFragment(pattern),
|
|
299
|
+
);
|
|
300
|
+
if (!fragments.length) {
|
|
301
|
+
return undefined;
|
|
302
|
+
}
|
|
303
|
+
return `(?:${fragments.join("|")})`;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export function isPathExcludedByGlobPatterns(filePath, patterns = []) {
|
|
307
|
+
const atomIgnoreRegex = globPatternsToAtomIgnoreRegex(patterns);
|
|
308
|
+
if (!atomIgnoreRegex) {
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
const normalizedPath = `${filePath}`.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
312
|
+
const regex = new RegExp(atomIgnoreRegex);
|
|
313
|
+
return regex.test(normalizedPath) || regex.test(`./${normalizedPath}`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export function filterAtomSlicesByExcludePatterns(sliceData, patterns = []) {
|
|
317
|
+
if (!sliceData || !getExcludePatterns({ exclude: patterns }).length) {
|
|
318
|
+
return sliceData;
|
|
319
|
+
}
|
|
320
|
+
const shouldKeepFile = (fileName) =>
|
|
321
|
+
!fileName || !isPathExcludedByGlobPatterns(fileName, patterns);
|
|
322
|
+
if (Array.isArray(sliceData)) {
|
|
323
|
+
return sliceData.filter((slice) => shouldKeepFile(slice.fileName));
|
|
324
|
+
}
|
|
325
|
+
const filteredSliceData = { ...sliceData };
|
|
326
|
+
if (Array.isArray(filteredSliceData.objectSlices)) {
|
|
327
|
+
filteredSliceData.objectSlices = filteredSliceData.objectSlices.filter(
|
|
328
|
+
(slice) => shouldKeepFile(slice.fileName),
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
if (Array.isArray(filteredSliceData.userDefinedTypes)) {
|
|
332
|
+
filteredSliceData.userDefinedTypes =
|
|
333
|
+
filteredSliceData.userDefinedTypes.filter((slice) =>
|
|
334
|
+
shouldKeepFile(slice.fileName),
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
if (Array.isArray(filteredSliceData.reachables)) {
|
|
338
|
+
filteredSliceData.reachables = filteredSliceData.reachables.filter(
|
|
339
|
+
(reachable) =>
|
|
340
|
+
(reachable.flows || []).every((flow) =>
|
|
341
|
+
shouldKeepFile(flow.parentFileName || flow.fileName),
|
|
342
|
+
),
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
if (
|
|
346
|
+
filteredSliceData.graph?.nodes &&
|
|
347
|
+
Array.isArray(filteredSliceData.paths)
|
|
348
|
+
) {
|
|
349
|
+
const excludedNodeIds = new Set(
|
|
350
|
+
filteredSliceData.graph.nodes
|
|
351
|
+
.filter((node) => !shouldKeepFile(node.parentFileName || node.fileName))
|
|
352
|
+
.map((node) => node.id),
|
|
353
|
+
);
|
|
354
|
+
filteredSliceData.paths = filteredSliceData.paths.filter((path) =>
|
|
355
|
+
path.every((nodeId) => !excludedNodeIds.has(nodeId)),
|
|
356
|
+
);
|
|
357
|
+
const retainedNodeIds = new Set(filteredSliceData.paths.flat());
|
|
358
|
+
filteredSliceData.graph = {
|
|
359
|
+
...filteredSliceData.graph,
|
|
360
|
+
nodes: filteredSliceData.graph.nodes.filter(
|
|
361
|
+
(node) => retainedNodeIds.has(node.id) || !excludedNodeIds.has(node.id),
|
|
362
|
+
),
|
|
363
|
+
edges: (filteredSliceData.graph.edges || []).filter((edge) => {
|
|
364
|
+
const source = edge.src ?? edge.source;
|
|
365
|
+
const destination = edge.dst ?? edge.destination;
|
|
366
|
+
return (
|
|
367
|
+
!excludedNodeIds.has(source) && !excludedNodeIds.has(destination)
|
|
368
|
+
);
|
|
369
|
+
}),
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
return filteredSliceData;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function mergeCsvValues(...valueLists) {
|
|
376
|
+
const values = new Set();
|
|
377
|
+
for (const valueList of valueLists) {
|
|
378
|
+
if (Array.isArray(valueList)) {
|
|
379
|
+
valueList.forEach((value) => {
|
|
380
|
+
values.add(`${value}`.trim());
|
|
381
|
+
});
|
|
382
|
+
} else if (typeof valueList === "string" && valueList.length) {
|
|
383
|
+
valueList.split(",").forEach((value) => {
|
|
384
|
+
values.add(value.trim());
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return Array.from(values).filter(Boolean).join(",");
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function mergeRegexValues(...regexValues) {
|
|
392
|
+
const values = regexValues
|
|
393
|
+
.map((regexValue) => `${regexValue || ""}`.trim())
|
|
394
|
+
.filter(Boolean);
|
|
395
|
+
if (!values.length) {
|
|
396
|
+
return undefined;
|
|
397
|
+
}
|
|
398
|
+
return values.map((regexValue) => `(?:${regexValue})`).join("|");
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Build additional environment variables for Atom from cdxgen CLI options.
|
|
403
|
+
*
|
|
404
|
+
* @param {Object} options CLI options
|
|
405
|
+
* @param {string} language Atom language name
|
|
406
|
+
* @returns {Object} Environment variables to pass to Atom
|
|
407
|
+
*/
|
|
408
|
+
export function buildAtomCommandEnv(options = {}, language = "") {
|
|
409
|
+
const excludePatterns = getExcludePatterns(options);
|
|
410
|
+
if (!excludePatterns.length) {
|
|
411
|
+
return {};
|
|
412
|
+
}
|
|
413
|
+
const chenIgnoreDirs = mergeCsvValues(
|
|
414
|
+
process.env.CHEN_IGNORE_DIRS,
|
|
415
|
+
extractIgnoreDirsFromExcludePatterns(excludePatterns, true),
|
|
416
|
+
);
|
|
417
|
+
const env = {};
|
|
418
|
+
if (chenIgnoreDirs) {
|
|
419
|
+
env.CHEN_IGNORE_DIRS = chenIgnoreDirs;
|
|
420
|
+
}
|
|
421
|
+
const atomIgnoreRegex = globPatternsToAtomIgnoreRegex(excludePatterns);
|
|
422
|
+
const normalizedLanguage = `${language}`.toLowerCase();
|
|
423
|
+
if (ATOM_JS_LANGUAGES.has(normalizedLanguage)) {
|
|
424
|
+
const astgenBaseIgnoreDirs =
|
|
425
|
+
process.env.ASTGEN_IGNORE_DIRS === undefined
|
|
426
|
+
? ASTGEN_DEFAULT_IGNORE_DIRS
|
|
427
|
+
: process.env.ASTGEN_IGNORE_DIRS;
|
|
428
|
+
const astgenIgnoreDirs = mergeCsvValues(
|
|
429
|
+
astgenBaseIgnoreDirs,
|
|
430
|
+
"node_modules",
|
|
431
|
+
extractIgnoreDirsFromExcludePatterns(excludePatterns),
|
|
432
|
+
);
|
|
433
|
+
if (astgenIgnoreDirs) {
|
|
434
|
+
env.ASTGEN_IGNORE_DIRS = astgenIgnoreDirs;
|
|
435
|
+
}
|
|
436
|
+
const astgenIgnoreFilePattern = mergeRegexValues(
|
|
437
|
+
process.env.ASTGEN_IGNORE_FILE_PATTERN,
|
|
438
|
+
atomIgnoreRegex,
|
|
439
|
+
);
|
|
440
|
+
if (astgenIgnoreFilePattern) {
|
|
441
|
+
env.ASTGEN_IGNORE_FILE_PATTERN = astgenIgnoreFilePattern;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return env;
|
|
445
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
|
|
3
|
+
import { assert, it } from "poku";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
buildAtomCommandEnv,
|
|
7
|
+
filterAtomSlicesByExcludePatterns,
|
|
8
|
+
globPatternsToAtomIgnoreRegex,
|
|
9
|
+
isPathExcludedByGlobPatterns,
|
|
10
|
+
} from "./atomUtils.js";
|
|
11
|
+
|
|
12
|
+
it("converts cdxgen exclude globs to Scala-compatible regex", () => {
|
|
13
|
+
const atomRegex = globPatternsToAtomIgnoreRegex([
|
|
14
|
+
"**/*.spec.js",
|
|
15
|
+
"src/**/fixtures/*.{js,ts}",
|
|
16
|
+
"test/[!a-c]?.jsx",
|
|
17
|
+
"packages/@(api|web)/**/*.test.ts",
|
|
18
|
+
]);
|
|
19
|
+
const regex = new RegExp(atomRegex);
|
|
20
|
+
assert.ok(regex.test("example.spec.js"));
|
|
21
|
+
assert.ok(regex.test("src/example.spec.js"));
|
|
22
|
+
assert.ok(regex.test("src\\example.spec.js"));
|
|
23
|
+
assert.ok(regex.test("src/app/fixtures/demo.ts"));
|
|
24
|
+
assert.ok(regex.test("test/z1.jsx"));
|
|
25
|
+
assert.ok(regex.test("packages/api/src/foo.test.ts"));
|
|
26
|
+
assert.ok(regex.test("packages/web/foo.test.ts"));
|
|
27
|
+
assert.ok(!regex.test("src/app/fixtures/demo.py"));
|
|
28
|
+
assert.ok(!regex.test("test/a1.jsx"));
|
|
29
|
+
assert.ok(!regex.test("packages/mobile/foo.test.ts"));
|
|
30
|
+
assert.ok(!regex.test("src/example.spec.jsx"));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("treats escaped glob wildcards as literal characters", () => {
|
|
34
|
+
const atomRegex = globPatternsToAtomIgnoreRegex(["src/escaped/\\*.js"]);
|
|
35
|
+
const regex = new RegExp(atomRegex);
|
|
36
|
+
assert.ok(regex.test("src/escaped/*.js"));
|
|
37
|
+
assert.ok(!regex.test("src/escaped/index.js"));
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("matches paths against cdxgen exclude globs", () => {
|
|
41
|
+
const patterns = ["**/*.spec.js", "src/generated/**"];
|
|
42
|
+
assert.ok(isPathExcludedByGlobPatterns("src/foo.spec.js", patterns));
|
|
43
|
+
assert.ok(isPathExcludedByGlobPatterns("src/generated/client.js", patterns));
|
|
44
|
+
assert.ok(isPathExcludedByGlobPatterns("src\\foo.spec.js", patterns));
|
|
45
|
+
assert.ok(!isPathExcludedByGlobPatterns("src/foo.test.js", patterns));
|
|
46
|
+
assert.ok(!isPathExcludedByGlobPatterns("src/manual/client.js", patterns));
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("builds global Atom and JavaScript astgen exclude environment", () => {
|
|
50
|
+
const originalAstgenIgnoreDirs = process.env.ASTGEN_IGNORE_DIRS;
|
|
51
|
+
const originalAstgenIgnoreFilePattern =
|
|
52
|
+
process.env.ASTGEN_IGNORE_FILE_PATTERN;
|
|
53
|
+
const originalChenIgnoreDirs = process.env.CHEN_IGNORE_DIRS;
|
|
54
|
+
try {
|
|
55
|
+
delete process.env.ASTGEN_IGNORE_DIRS;
|
|
56
|
+
delete process.env.ASTGEN_IGNORE_FILE_PATTERN;
|
|
57
|
+
process.env.CHEN_IGNORE_DIRS = "vendor";
|
|
58
|
+
const options = {
|
|
59
|
+
exclude: [
|
|
60
|
+
"**/ignored/**",
|
|
61
|
+
"src/generated/**",
|
|
62
|
+
"**/*.spec.js",
|
|
63
|
+
"noxfile.py",
|
|
64
|
+
],
|
|
65
|
+
};
|
|
66
|
+
const env = buildAtomCommandEnv(options, "javascript");
|
|
67
|
+
const chenIgnoreDirs = env.CHEN_IGNORE_DIRS.split(",");
|
|
68
|
+
const astgenIgnoreDirs = env.ASTGEN_IGNORE_DIRS.split(",");
|
|
69
|
+
assert.deepStrictEqual(Object.keys(env).sort(), [
|
|
70
|
+
"ASTGEN_IGNORE_DIRS",
|
|
71
|
+
"ASTGEN_IGNORE_FILE_PATTERN",
|
|
72
|
+
"CHEN_IGNORE_DIRS",
|
|
73
|
+
]);
|
|
74
|
+
assert.ok(chenIgnoreDirs.includes("vendor"));
|
|
75
|
+
assert.ok(chenIgnoreDirs.includes("ignored"));
|
|
76
|
+
assert.ok(chenIgnoreDirs.includes("generated"));
|
|
77
|
+
assert.ok(chenIgnoreDirs.includes("noxfile.py"));
|
|
78
|
+
assert.ok(!chenIgnoreDirs.includes("src"));
|
|
79
|
+
assert.ok(astgenIgnoreDirs.includes("node_modules"));
|
|
80
|
+
assert.ok(astgenIgnoreDirs.includes("ignored"));
|
|
81
|
+
assert.ok(astgenIgnoreDirs.includes("generated"));
|
|
82
|
+
assert.ok(!astgenIgnoreDirs.includes("noxfile.py"));
|
|
83
|
+
assert.ok(!astgenIgnoreDirs.includes("src"));
|
|
84
|
+
assert.ok(
|
|
85
|
+
new RegExp(env.ASTGEN_IGNORE_FILE_PATTERN).test("src/foo.spec.js"),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const pythonEnv = buildAtomCommandEnv(options, "python");
|
|
89
|
+
assert.deepStrictEqual(Object.keys(pythonEnv).sort(), ["CHEN_IGNORE_DIRS"]);
|
|
90
|
+
} finally {
|
|
91
|
+
if (originalAstgenIgnoreDirs === undefined) {
|
|
92
|
+
delete process.env.ASTGEN_IGNORE_DIRS;
|
|
93
|
+
} else {
|
|
94
|
+
process.env.ASTGEN_IGNORE_DIRS = originalAstgenIgnoreDirs;
|
|
95
|
+
}
|
|
96
|
+
if (originalAstgenIgnoreFilePattern === undefined) {
|
|
97
|
+
delete process.env.ASTGEN_IGNORE_FILE_PATTERN;
|
|
98
|
+
} else {
|
|
99
|
+
process.env.ASTGEN_IGNORE_FILE_PATTERN = originalAstgenIgnoreFilePattern;
|
|
100
|
+
}
|
|
101
|
+
if (originalChenIgnoreDirs === undefined) {
|
|
102
|
+
delete process.env.CHEN_IGNORE_DIRS;
|
|
103
|
+
} else {
|
|
104
|
+
process.env.CHEN_IGNORE_DIRS = originalChenIgnoreDirs;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("filters Atom slices using exclude globs", () => {
|
|
110
|
+
const sliceData = {
|
|
111
|
+
objectSlices: [
|
|
112
|
+
{ fileName: "src/index.js", fullName: "src/index.js::program" },
|
|
113
|
+
{ fileName: "src/index.spec.js", fullName: "src/index.spec.js::program" },
|
|
114
|
+
],
|
|
115
|
+
userDefinedTypes: [
|
|
116
|
+
{ fileName: "src/generated/client.js", name: "GeneratedClient" },
|
|
117
|
+
{ fileName: "src/model.js", name: "Model" },
|
|
118
|
+
],
|
|
119
|
+
reachables: [
|
|
120
|
+
{ flows: [{ parentFileName: "src/index.js" }] },
|
|
121
|
+
{ flows: [{ parentFileName: "src/index.spec.js" }] },
|
|
122
|
+
],
|
|
123
|
+
};
|
|
124
|
+
const filtered = filterAtomSlicesByExcludePatterns(sliceData, [
|
|
125
|
+
"**/*.spec.js",
|
|
126
|
+
"src/generated/**",
|
|
127
|
+
]);
|
|
128
|
+
assert.deepStrictEqual(
|
|
129
|
+
filtered.objectSlices.map((slice) => slice.fileName),
|
|
130
|
+
["src/index.js"],
|
|
131
|
+
);
|
|
132
|
+
assert.deepStrictEqual(
|
|
133
|
+
filtered.userDefinedTypes.map((slice) => slice.fileName),
|
|
134
|
+
["src/model.js"],
|
|
135
|
+
);
|
|
136
|
+
assert.strictEqual(filtered.reachables.length, 1);
|
|
137
|
+
});
|
package/lib/helpers/bomUtils.js
CHANGED
|
@@ -11,6 +11,39 @@ const CYCLONEDX_FORMAT_KEYS = new Set([
|
|
|
11
11
|
MODERN_CYCLONEDX_ROOT_KEY,
|
|
12
12
|
"specVersion",
|
|
13
13
|
]);
|
|
14
|
+
const COMPONENT_TYPES_1_4 = [
|
|
15
|
+
"application",
|
|
16
|
+
"framework",
|
|
17
|
+
"library",
|
|
18
|
+
"container",
|
|
19
|
+
"operating-system",
|
|
20
|
+
"device",
|
|
21
|
+
"firmware",
|
|
22
|
+
"file",
|
|
23
|
+
];
|
|
24
|
+
const COMPONENT_TYPES_1_5 = [
|
|
25
|
+
"application",
|
|
26
|
+
"framework",
|
|
27
|
+
"library",
|
|
28
|
+
"container",
|
|
29
|
+
"platform",
|
|
30
|
+
"operating-system",
|
|
31
|
+
"device",
|
|
32
|
+
"device-driver",
|
|
33
|
+
"firmware",
|
|
34
|
+
"file",
|
|
35
|
+
"machine-learning-model",
|
|
36
|
+
"data",
|
|
37
|
+
];
|
|
38
|
+
const COMPONENT_TYPES_1_6 = [...COMPONENT_TYPES_1_5, "cryptographic-asset"];
|
|
39
|
+
|
|
40
|
+
export const CYCLONEDX_COMPONENT_TYPES_BY_SPEC_VERSION = Object.freeze({
|
|
41
|
+
1.4: Object.freeze(COMPONENT_TYPES_1_4),
|
|
42
|
+
1.5: Object.freeze(COMPONENT_TYPES_1_5),
|
|
43
|
+
1.6: Object.freeze(COMPONENT_TYPES_1_6),
|
|
44
|
+
1.7: Object.freeze(COMPONENT_TYPES_1_6),
|
|
45
|
+
"2.0": Object.freeze(COMPONENT_TYPES_1_6),
|
|
46
|
+
});
|
|
14
47
|
|
|
15
48
|
export const isSpdxJsonLd = (bomJson) =>
|
|
16
49
|
Boolean(
|
|
@@ -67,6 +100,44 @@ export const isCycloneDxSpecVersionAtLeast = (specVersion, minimumVersion) => {
|
|
|
67
100
|
export const isCycloneDx20SpecVersion = (specVersion) =>
|
|
68
101
|
isCycloneDxSpecVersionAtLeast(specVersion, 2);
|
|
69
102
|
|
|
103
|
+
export const getSupportedCycloneDxComponentTypes = (specVersion = 1.7) => {
|
|
104
|
+
const normalizedSpecVersion = toCycloneDxSpecVersionString(specVersion);
|
|
105
|
+
return [
|
|
106
|
+
...(CYCLONEDX_COMPONENT_TYPES_BY_SPEC_VERSION[normalizedSpecVersion] ||
|
|
107
|
+
CYCLONEDX_COMPONENT_TYPES_BY_SPEC_VERSION["1.7"]),
|
|
108
|
+
];
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export const normalizeCycloneDxComponentTypeFilter = (componentType) => {
|
|
112
|
+
if (!componentType) {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
const componentTypes = Array.isArray(componentType)
|
|
116
|
+
? componentType
|
|
117
|
+
: [componentType];
|
|
118
|
+
return Array.from(
|
|
119
|
+
new Set(componentTypes.map((type) => `${type}`.trim()).filter(Boolean)),
|
|
120
|
+
);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export const isCycloneDxComponentTypeEnabled = (
|
|
124
|
+
componentType,
|
|
125
|
+
options = {},
|
|
126
|
+
) => {
|
|
127
|
+
if (!componentType) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
const requestedComponentTypes = normalizeCycloneDxComponentTypeFilter(
|
|
131
|
+
options.componentType,
|
|
132
|
+
);
|
|
133
|
+
if (requestedComponentTypes.length) {
|
|
134
|
+
return requestedComponentTypes.includes(componentType);
|
|
135
|
+
}
|
|
136
|
+
return getSupportedCycloneDxComponentTypes(options.specVersion).includes(
|
|
137
|
+
componentType,
|
|
138
|
+
);
|
|
139
|
+
};
|
|
140
|
+
|
|
70
141
|
export const getCycloneDxRootFormatKey = (specVersionOrBom) => {
|
|
71
142
|
const specVersion =
|
|
72
143
|
specVersionOrBom && typeof specVersionOrBom === "object"
|