@cyclonedx/cdxgen 8.0.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/.eslintrc.js +15 -0
- package/LICENSE +201 -0
- package/README.md +354 -0
- package/analyzer.js +189 -0
- package/bin/cdxgen +316 -0
- package/binary.js +507 -0
- package/docker.js +769 -0
- package/docker.test.js +72 -0
- package/index.js +4292 -0
- package/jest.config.js +181 -0
- package/known-licenses.json +27 -0
- package/lic-mapping.json +294 -0
- package/package.json +94 -0
- package/queries.json +68 -0
- package/server.js +110 -0
- package/spdx-licenses.json +500 -0
- package/utils.js +4284 -0
- package/utils.test.js +1660 -0
- package/vendor-alias.json +10 -0
package/analyzer.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
const babelParser = require("@babel/parser");
|
|
2
|
+
const babelTraverse = require("@babel/traverse").default;
|
|
3
|
+
const { join } = require("path");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
|
|
7
|
+
const IGNORE_DIRS = [
|
|
8
|
+
"node_modules",
|
|
9
|
+
"venv",
|
|
10
|
+
"docs",
|
|
11
|
+
"test",
|
|
12
|
+
"tests",
|
|
13
|
+
"e2e",
|
|
14
|
+
"examples",
|
|
15
|
+
"cypress",
|
|
16
|
+
"site-packages"
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const IGNORE_FILE_PATTERN = new RegExp("(conf|test|spec|mock)\\.(js|ts)$", "i");
|
|
20
|
+
|
|
21
|
+
const getAllFiles = (dir, extn, files, result, regex) => {
|
|
22
|
+
files = files || fs.readdirSync(dir);
|
|
23
|
+
result = result || [];
|
|
24
|
+
regex = regex || new RegExp(`\\${extn}$`);
|
|
25
|
+
|
|
26
|
+
for (let i = 0; i < files.length; i++) {
|
|
27
|
+
if (IGNORE_FILE_PATTERN.test(files[i])) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
let file = join(dir, files[i]);
|
|
31
|
+
if (fs.statSync(file).isDirectory()) {
|
|
32
|
+
// Ignore directories
|
|
33
|
+
const dirName = path.basename(file);
|
|
34
|
+
if (
|
|
35
|
+
dirName.startsWith(".") ||
|
|
36
|
+
IGNORE_DIRS.includes(dirName.toLowerCase())
|
|
37
|
+
) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
result = getAllFiles(file, extn, fs.readdirSync(file), result, regex);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
if (regex.test(file)) {
|
|
47
|
+
result.push(file);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const babelParserOptions = {
|
|
55
|
+
sourceType: "unambiguous",
|
|
56
|
+
allowImportExportEverywhere: true,
|
|
57
|
+
allowAwaitOutsideFunction: true,
|
|
58
|
+
allowReturnOutsideFunction: true,
|
|
59
|
+
allowSuperOutsideMethod: true,
|
|
60
|
+
errorRecovery: true,
|
|
61
|
+
allowUndeclaredExports: true,
|
|
62
|
+
attachComment: false,
|
|
63
|
+
plugins: [
|
|
64
|
+
"optionalChaining",
|
|
65
|
+
"classProperties",
|
|
66
|
+
"decorators-legacy",
|
|
67
|
+
"exportDefaultFrom",
|
|
68
|
+
"doExpressions",
|
|
69
|
+
"numericSeparator",
|
|
70
|
+
"dynamicImport",
|
|
71
|
+
"jsx",
|
|
72
|
+
"typescript"
|
|
73
|
+
]
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Filter only references to (t|jsx?) or (less|scss) files for now.
|
|
78
|
+
* Opt to use our relative paths.
|
|
79
|
+
*/
|
|
80
|
+
const setFileRef = (allImports, file, pathway) => {
|
|
81
|
+
// remove unexpected extension imports
|
|
82
|
+
if (/\.(svg|png|jpg|d\.ts)/.test(pathway)) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// replace relative imports with full path
|
|
87
|
+
let module = pathway;
|
|
88
|
+
if (/\.\//g.test(pathway) || /\.\.\//g.test(pathway)) {
|
|
89
|
+
module = path.resolve(file, "..", pathway);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// initialise or increase reference count for file
|
|
93
|
+
if (allImports.hasOwnProperty(module)) {
|
|
94
|
+
allImports[module] = allImports[module] + 1;
|
|
95
|
+
} else {
|
|
96
|
+
allImports[module] = 1;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Handle module package name
|
|
100
|
+
// Eg: zone.js/dist/zone will be referred to as zone.js in package.json
|
|
101
|
+
if (!path.isAbsolute(module) && module.includes("/")) {
|
|
102
|
+
const modPkg = module.split("/")[0];
|
|
103
|
+
if (allImports.hasOwnProperty(modPkg)) {
|
|
104
|
+
allImports[modPkg] = allImports[modPkg] + 1;
|
|
105
|
+
} else {
|
|
106
|
+
allImports[modPkg] = 1;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Check AST tree for any (j|tsx?) files and set a file
|
|
113
|
+
* references for any import, require or dynamic import files.
|
|
114
|
+
*/
|
|
115
|
+
const parseFileASTTree = (file, allImports) => {
|
|
116
|
+
const ast = babelParser.parse(
|
|
117
|
+
fs.readFileSync(file, "utf-8"),
|
|
118
|
+
babelParserOptions
|
|
119
|
+
);
|
|
120
|
+
babelTraverse(ast, {
|
|
121
|
+
// Used for all ES6 import statements
|
|
122
|
+
ImportDeclaration: (path) => {
|
|
123
|
+
if (path && path.node) {
|
|
124
|
+
setFileRef(allImports, file, path.node.source.value);
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
// For require('') statements
|
|
128
|
+
Identifier: (path) => {
|
|
129
|
+
if (
|
|
130
|
+
path &&
|
|
131
|
+
path.node &&
|
|
132
|
+
path.node.name === "require" &&
|
|
133
|
+
path.parent.type === "CallExpression"
|
|
134
|
+
) {
|
|
135
|
+
setFileRef(allImports, file, path.parent.arguments[0].value);
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
// Use for dynamic imports like routes.jsx
|
|
139
|
+
CallExpression: (path) => {
|
|
140
|
+
if (path && path.node && path.node.callee.type === "Import") {
|
|
141
|
+
setFileRef(allImports, file, path.node.arguments[0].value);
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
// Use for export barrells
|
|
145
|
+
ExportAllDeclaration: (path) => {
|
|
146
|
+
setFileRef(allImports, file, path.node.source.value);
|
|
147
|
+
},
|
|
148
|
+
ExportNamedDeclaration: (path) => {
|
|
149
|
+
// ensure there is a path export
|
|
150
|
+
if (path && path.node && path.node.source) {
|
|
151
|
+
setFileRef(allImports, file, path.node.source.value);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Return paths to all (j|tsx?) files.
|
|
159
|
+
*/
|
|
160
|
+
const getAllSrcJSAndTSFiles = (src) =>
|
|
161
|
+
Promise.all([
|
|
162
|
+
getAllFiles(src, ".js"),
|
|
163
|
+
getAllFiles(src, ".jsx"),
|
|
164
|
+
getAllFiles(src, ".ts"),
|
|
165
|
+
getAllFiles(src, ".tsx")
|
|
166
|
+
]);
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Where Node CLI runs from.
|
|
170
|
+
*/
|
|
171
|
+
const findJSImports = async (src) => {
|
|
172
|
+
const allImports = {};
|
|
173
|
+
const errFiles = [];
|
|
174
|
+
try {
|
|
175
|
+
const promiseMap = await getAllSrcJSAndTSFiles(src);
|
|
176
|
+
const srcFiles = promiseMap.flatMap((d) => d);
|
|
177
|
+
for (const file of srcFiles) {
|
|
178
|
+
try {
|
|
179
|
+
parseFileASTTree(file, allImports);
|
|
180
|
+
} catch (err) {
|
|
181
|
+
errFiles.push(file);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return allImports;
|
|
185
|
+
} catch (err) {
|
|
186
|
+
return allImports;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
exports.findJSImports = findJSImports;
|
package/bin/cdxgen
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const bom = require("../index.js");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const jws = require("jws");
|
|
7
|
+
const crypto = require("crypto");
|
|
8
|
+
const bomServer = require("../server.js");
|
|
9
|
+
|
|
10
|
+
const args = require("yargs")
|
|
11
|
+
.option("output", {
|
|
12
|
+
alias: "o",
|
|
13
|
+
description: "Output file for bom.xml or bom.json. Default console"
|
|
14
|
+
})
|
|
15
|
+
.option("type", {
|
|
16
|
+
alias: "t",
|
|
17
|
+
description: "Project type"
|
|
18
|
+
})
|
|
19
|
+
.option("recurse", {
|
|
20
|
+
alias: "r",
|
|
21
|
+
type: "boolean",
|
|
22
|
+
description: "Recurse mode suitable for mono-repos"
|
|
23
|
+
})
|
|
24
|
+
.option("print", {
|
|
25
|
+
alias: "p",
|
|
26
|
+
type: "boolean",
|
|
27
|
+
description: "Print the SBoM as a table"
|
|
28
|
+
})
|
|
29
|
+
.option("resolve-class", {
|
|
30
|
+
alias: "c",
|
|
31
|
+
type: "boolean",
|
|
32
|
+
description: "Resolve class names for packages. jars only for now."
|
|
33
|
+
})
|
|
34
|
+
.option("deep", {
|
|
35
|
+
type: "boolean",
|
|
36
|
+
description:
|
|
37
|
+
"Perform deep searches for components. Useful while scanning live OS and oci images."
|
|
38
|
+
})
|
|
39
|
+
.option("server-url", {
|
|
40
|
+
description: "Dependency track url. Eg: https://deptrack.cyclonedx.io"
|
|
41
|
+
})
|
|
42
|
+
.option("api-key", {
|
|
43
|
+
description: "Dependency track api key"
|
|
44
|
+
})
|
|
45
|
+
.option("project-group", {
|
|
46
|
+
description: "Dependency track project group"
|
|
47
|
+
})
|
|
48
|
+
.option("project-name", {
|
|
49
|
+
description: "Dependency track project name. Default use the directory name"
|
|
50
|
+
})
|
|
51
|
+
.option("project-version", {
|
|
52
|
+
description: "Dependency track project version",
|
|
53
|
+
default: ""
|
|
54
|
+
})
|
|
55
|
+
.option("project-id", {
|
|
56
|
+
description:
|
|
57
|
+
"Dependency track project id. Either provide the id or the project name and version together"
|
|
58
|
+
})
|
|
59
|
+
.option("required-only", {
|
|
60
|
+
type: "boolean",
|
|
61
|
+
description: "Include only the packages with required scope on the SBoM."
|
|
62
|
+
})
|
|
63
|
+
.option("fail-on-error", {
|
|
64
|
+
type: "boolean",
|
|
65
|
+
description: "Fail if any dependency extractor fails."
|
|
66
|
+
})
|
|
67
|
+
.option("no-babel", {
|
|
68
|
+
type: "boolean",
|
|
69
|
+
description:
|
|
70
|
+
"Do not use babel to perform usage analysis for JavaScript/TypeScript projects."
|
|
71
|
+
})
|
|
72
|
+
.option("generate-key-and-sign", {
|
|
73
|
+
type: "boolean",
|
|
74
|
+
description:
|
|
75
|
+
"Generate an RSA public/private key pair and then sign the generated SBoM using JSON Web Signatures."
|
|
76
|
+
})
|
|
77
|
+
.option("server", {
|
|
78
|
+
type: "boolean",
|
|
79
|
+
description: "Run cdxgen as a server"
|
|
80
|
+
})
|
|
81
|
+
.option("server-host", {
|
|
82
|
+
description: "Listen address",
|
|
83
|
+
default: "127.0.0.1"
|
|
84
|
+
})
|
|
85
|
+
.option("server-port", {
|
|
86
|
+
description: "Listen port",
|
|
87
|
+
default: "9090"
|
|
88
|
+
})
|
|
89
|
+
.scriptName("cdxgen")
|
|
90
|
+
.version()
|
|
91
|
+
.help("h").argv;
|
|
92
|
+
|
|
93
|
+
if (args.version) {
|
|
94
|
+
const packageJsonAsString = fs.readFileSync(
|
|
95
|
+
path.join(__dirname, "../", "package.json"),
|
|
96
|
+
"utf-8"
|
|
97
|
+
);
|
|
98
|
+
const packageJson = JSON.parse(packageJsonAsString);
|
|
99
|
+
|
|
100
|
+
console.log(packageJson.version);
|
|
101
|
+
process.exit(0);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (process.env.GLOBAL_AGENT_HTTP_PROXY || process.env.HTTP_PROXY) {
|
|
105
|
+
// Support standard HTTP_PROXY variable if the user doesn't override the namespace
|
|
106
|
+
if (!process.env.GLOBAL_AGENT_ENVIRONMENT_VARIABLE_NAMESPACE) {
|
|
107
|
+
process.env.GLOBAL_AGENT_ENVIRONMENT_VARIABLE_NAMESPACE = "";
|
|
108
|
+
}
|
|
109
|
+
const globalAgent = require("global-agent");
|
|
110
|
+
globalAgent.bootstrap();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let filePath = args._[0] || ".";
|
|
114
|
+
if (!args.projectName) {
|
|
115
|
+
if (filePath !== ".") {
|
|
116
|
+
args.projectName = path.basename(filePath);
|
|
117
|
+
} else {
|
|
118
|
+
args.projectName = path.basename(path.resolve(filePath));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* projectType: python, nodejs, java, golang
|
|
124
|
+
* multiProject: Boolean to indicate monorepo or multi-module projects
|
|
125
|
+
*/
|
|
126
|
+
let options = {
|
|
127
|
+
dev: true,
|
|
128
|
+
projectType: args.type,
|
|
129
|
+
multiProject: args.recurse,
|
|
130
|
+
depth: 3,
|
|
131
|
+
output: args.output,
|
|
132
|
+
resolveClass: args.resolveClass,
|
|
133
|
+
installDeps: true,
|
|
134
|
+
requiredOnly: args.requiredOnly,
|
|
135
|
+
failOnError: args.failOnError,
|
|
136
|
+
noBabel: args.noBabel || args.babel === false,
|
|
137
|
+
deep: args.deep,
|
|
138
|
+
generateKeyAndSign: args.generateKeyAndSign,
|
|
139
|
+
project: args.projectId,
|
|
140
|
+
projectName: args.projectName,
|
|
141
|
+
projectGroup: args.projectGroup,
|
|
142
|
+
projectVersion: args.projectVersion,
|
|
143
|
+
server: args.server,
|
|
144
|
+
serverHost: args.serverHost,
|
|
145
|
+
serverPort: args.serverPort
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Method to start the bom creation process
|
|
150
|
+
*/
|
|
151
|
+
(async () => {
|
|
152
|
+
// Start SBoM server
|
|
153
|
+
if (args.server) {
|
|
154
|
+
return await bomServer.start(options);
|
|
155
|
+
}
|
|
156
|
+
const bomNSData = (await bom.createBom(filePath, options)) || {};
|
|
157
|
+
|
|
158
|
+
if (args.output) {
|
|
159
|
+
if (bomNSData.bomXmlFiles) {
|
|
160
|
+
console.log("BOM files produced:", bomNSData.bomXmlFiles);
|
|
161
|
+
} else {
|
|
162
|
+
const jsonFile = args.output.replace(".xml", ".json");
|
|
163
|
+
// Create bom json file
|
|
164
|
+
if (bomNSData.bomJson) {
|
|
165
|
+
let jsonPayload = undefined;
|
|
166
|
+
if (
|
|
167
|
+
typeof bomNSData.bomJson === "string" ||
|
|
168
|
+
bomNSData.bomJson instanceof String
|
|
169
|
+
) {
|
|
170
|
+
fs.writeFileSync(jsonFile, bomNSData.bomJson);
|
|
171
|
+
jsonPayload = bomNSData.bomJson;
|
|
172
|
+
} else {
|
|
173
|
+
jsonPayload = JSON.stringify(bomNSData.bomJson, null, 2);
|
|
174
|
+
fs.writeFileSync(jsonFile, jsonPayload);
|
|
175
|
+
}
|
|
176
|
+
if (
|
|
177
|
+
jsonPayload &&
|
|
178
|
+
(args.generateKeyAndSign ||
|
|
179
|
+
(process.env.SBOM_SIGN_ALGORITHM &&
|
|
180
|
+
process.env.SBOM_SIGN_ALGORITHM !== "none" &&
|
|
181
|
+
process.env.SBOM_SIGN_PRIVATE_KEY &&
|
|
182
|
+
fs.existsSync(process.env.SBOM_SIGN_PRIVATE_KEY)))
|
|
183
|
+
) {
|
|
184
|
+
let alg = process.env.SBOM_SIGN_ALGORITHM || "RS512";
|
|
185
|
+
if (alg.includes("none")) {
|
|
186
|
+
alg = "RS512";
|
|
187
|
+
}
|
|
188
|
+
let privateKeyToUse = undefined;
|
|
189
|
+
let jwkPublicKey = undefined;
|
|
190
|
+
if (args.generateKeyAndSign) {
|
|
191
|
+
const dirName = path.dirname(jsonFile);
|
|
192
|
+
const publicKeyFile = path.join(dirName, "public.key");
|
|
193
|
+
const privateKeyFile = path.join(dirName, "private.key");
|
|
194
|
+
const { privateKey, publicKey } = crypto.generateKeyPairSync(
|
|
195
|
+
"rsa",
|
|
196
|
+
{
|
|
197
|
+
modulusLength: 4096,
|
|
198
|
+
publicKeyEncoding: {
|
|
199
|
+
type: "spki",
|
|
200
|
+
format: "pem"
|
|
201
|
+
},
|
|
202
|
+
privateKeyEncoding: {
|
|
203
|
+
type: "pkcs8",
|
|
204
|
+
format: "pem"
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
);
|
|
208
|
+
fs.writeFileSync(publicKeyFile, publicKey);
|
|
209
|
+
fs.writeFileSync(privateKeyFile, privateKey);
|
|
210
|
+
console.log(
|
|
211
|
+
"Created public/private key pairs for testing purposes",
|
|
212
|
+
publicKeyFile,
|
|
213
|
+
privateKeyFile
|
|
214
|
+
);
|
|
215
|
+
privateKeyToUse = privateKey;
|
|
216
|
+
jwkPublicKey = crypto
|
|
217
|
+
.createPublicKey(publicKey)
|
|
218
|
+
.export({ format: "jwk" });
|
|
219
|
+
} else {
|
|
220
|
+
privateKeyToUse = fs.readFileSync(
|
|
221
|
+
process.env.SBOM_SIGN_PRIVATE_KEY,
|
|
222
|
+
"utf8"
|
|
223
|
+
);
|
|
224
|
+
if (
|
|
225
|
+
process.env.SBOM_SIGN_PUBLIC_KEY &&
|
|
226
|
+
fs.existsSync(process.env.SBOM_SIGN_PUBLIC_KEY)
|
|
227
|
+
) {
|
|
228
|
+
jwkPublicKey = crypto
|
|
229
|
+
.createPublicKey(
|
|
230
|
+
fs.readFileSync(process.env.SBOM_SIGN_PUBLIC_KEY, "utf8")
|
|
231
|
+
)
|
|
232
|
+
.export({ format: "jwk" });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
const signature = jws.sign({
|
|
237
|
+
header: { alg },
|
|
238
|
+
payload: jsonPayload,
|
|
239
|
+
privateKey: privateKeyToUse
|
|
240
|
+
});
|
|
241
|
+
if (signature) {
|
|
242
|
+
const bomJsonUnsignedObj = JSON.parse(jsonPayload);
|
|
243
|
+
const signatureBlock = {
|
|
244
|
+
algorithm: alg,
|
|
245
|
+
value: signature
|
|
246
|
+
};
|
|
247
|
+
if (jwkPublicKey) {
|
|
248
|
+
signatureBlock.publicKey = jwkPublicKey;
|
|
249
|
+
}
|
|
250
|
+
bomJsonUnsignedObj.signature = signatureBlock;
|
|
251
|
+
fs.writeFileSync(
|
|
252
|
+
jsonFile,
|
|
253
|
+
JSON.stringify(bomJsonUnsignedObj, null, 2)
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
} catch (ex) {
|
|
257
|
+
console.log("SBoM signing was unsuccessful", ex);
|
|
258
|
+
console.log("Check if the private key was exported in PEM format");
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// Create bom xml file
|
|
263
|
+
if (bomNSData.bomXml) {
|
|
264
|
+
const xmlFile = args.output.replace(".json", ".xml");
|
|
265
|
+
fs.writeFileSync(xmlFile, bomNSData.bomXml);
|
|
266
|
+
}
|
|
267
|
+
//
|
|
268
|
+
if (bomNSData.nsMapping && Object.keys(bomNSData.nsMapping).length) {
|
|
269
|
+
const nsFile = jsonFile + ".map";
|
|
270
|
+
fs.writeFileSync(nsFile, JSON.stringify(bomNSData.nsMapping));
|
|
271
|
+
console.log("Namespace mapping file written to", nsFile);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
} else if (!args.print) {
|
|
275
|
+
if (bomNSData.bomJson) {
|
|
276
|
+
console.log(JSON.stringify(bomNSData.bomJson, null, 2));
|
|
277
|
+
} else if (bomNSData.bomXml) {
|
|
278
|
+
console.log(Buffer.from(bomNSData.bomXml).toString());
|
|
279
|
+
} else {
|
|
280
|
+
console.log("Unable to produce BOM for", filePath);
|
|
281
|
+
console.log("Try running the command with -t <type> or -r argument");
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Automatically submit the bom data
|
|
286
|
+
if (args.serverUrl && args.apiKey) {
|
|
287
|
+
try {
|
|
288
|
+
const dbody = await bom.submitBom(args, bomNSData.bomXml);
|
|
289
|
+
console.log("Response from server", dbody);
|
|
290
|
+
} catch (err) {
|
|
291
|
+
console.log(err);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (args.print && bomNSData.bomJson && bomNSData.bomJson.components) {
|
|
296
|
+
const { table } = require("table");
|
|
297
|
+
const data = [["Group", "Name", "Version", "Scope"]];
|
|
298
|
+
for (let comp of bomNSData.bomJson.components) {
|
|
299
|
+
data.push([comp.group || "", comp.name, comp.version, comp.scope || ""]);
|
|
300
|
+
}
|
|
301
|
+
const config = {
|
|
302
|
+
header: {
|
|
303
|
+
alignment: "center",
|
|
304
|
+
content: "Software Bill-of-Materials\nGenerated by @cyclonedx/cdxgen"
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
console.log(table(data, config));
|
|
308
|
+
console.log(
|
|
309
|
+
"BOM includes",
|
|
310
|
+
bomNSData.bomJson.components.length,
|
|
311
|
+
"components and",
|
|
312
|
+
bomNSData.bomJson.dependencies.length,
|
|
313
|
+
"dependencies"
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
})();
|