@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.
Files changed (38) hide show
  1. package/README.md +6 -0
  2. package/bin/audit.js +7 -0
  3. package/bin/cdxgen.js +48 -2
  4. package/bin/evinse.js +7 -0
  5. package/lib/audit/index.js +165 -2
  6. package/lib/audit/index.poku.js +462 -0
  7. package/lib/cli/index.js +317 -169
  8. package/lib/evinser/evinser.js +31 -9
  9. package/lib/helpers/analyzer.js +890 -0
  10. package/lib/helpers/analyzer.poku.js +341 -0
  11. package/lib/helpers/atomUtils.js +445 -0
  12. package/lib/helpers/atomUtils.poku.js +137 -0
  13. package/lib/helpers/bomUtils.js +71 -0
  14. package/lib/helpers/bomUtils.poku.js +45 -0
  15. package/lib/helpers/depsUtils.js +146 -0
  16. package/lib/helpers/depsUtils.poku.js +183 -0
  17. package/lib/helpers/utils.js +585 -191
  18. package/lib/helpers/utils.poku.js +357 -4
  19. package/lib/managers/binary.js +18 -9
  20. package/lib/stages/postgen/postgen.js +215 -0
  21. package/lib/stages/postgen/postgen.poku.js +218 -3
  22. package/lib/validator/bomValidator.js +11 -2
  23. package/package.json +8 -8
  24. package/types/lib/audit/index.d.ts.map +1 -1
  25. package/types/lib/cli/index.d.ts.map +1 -1
  26. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  27. package/types/lib/helpers/atomUtils.d.ts +18 -0
  28. package/types/lib/helpers/atomUtils.d.ts.map +1 -0
  29. package/types/lib/helpers/bomUtils.d.ts +10 -0
  30. package/types/lib/helpers/bomUtils.d.ts.map +1 -1
  31. package/types/lib/helpers/depsUtils.d.ts +9 -0
  32. package/types/lib/helpers/depsUtils.d.ts.map +1 -1
  33. package/types/lib/helpers/utils.d.ts +19 -0
  34. package/types/lib/helpers/utils.d.ts.map +1 -1
  35. package/types/lib/managers/binary.d.ts +2 -1
  36. package/types/lib/managers/binary.d.ts.map +1 -1
  37. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  38. 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
+ });
@@ -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"