@cyclonedx/cyclonedx-esbuild
Version:
Creates CycloneDX Software Bill of Materials (SBoM) from esbuild projects
157 lines (152 loc) • 9.06 kB
JavaScript
;
/*!
This file is part of CycloneDX generator for esbuild.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache-2.0
Copyright (c) OWASP Foundation. All Rights Reserved.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.run = run;
const node_fs_1 = require("node:fs");
const node_path_1 = require("node:path");
const CDX = __importStar(require("@cyclonedx/cyclonedx-library"));
const commander_1 = require("commander");
const _helpers_1 = require("./_helpers");
const builders_1 = require("./builders");
const logger_1 = require("./logger");
const OutputStdOut = '-';
function makeCommand(process_) {
return new commander_1.Command().description('Create CycloneDX Software Bill of Materials (SBOM) from esbuild metafile.').addOption(new commander_1.Option('--ewd, --esbuild-working-dir', 'Working dir used in the esbuild process.').default(process_.cwd(), 'current working dir')).addOption(new commander_1.Option('--gather-license-texts', 'Search for license files in components and include them as license evidence.' +
'\nThis feature is experimental.').default(false)).addOption(new commander_1.Option('--sv, --spec-version <version>', 'Which version of CycloneDX spec to use.').choices(Object.keys(CDX.Spec.SpecVersionDict).sort()).default(CDX.Spec.Version.v1dot6)).addOption(new commander_1.Option('--output-reproducible', 'Whether to go the extra mile and make the output reproducible.\n' +
'This requires more resources, and might result in loss of time- and random-based-values.').env('BOM_REPRODUCIBLE')).addOption(new commander_1.Option('-o, --output-file <file>', 'Path to the output file.\n' +
`Set to "${OutputStdOut}" to write to STDOUT.`).default(OutputStdOut, 'write to STDOUT')).addOption(new commander_1.Option('--validate', 'Validate resulting BOM before outputting.\n' +
'Validation is skipped, if requirements not met. See the README.').default(undefined)).addOption(new commander_1.Option('--no-validate', 'Disable validation of resulting BOM.')).addOption(new commander_1.Option('--mc-type <type>', 'Type of the main component.').choices([
CDX.Enums.ComponentType.Application,
CDX.Enums.ComponentType.Firmware,
CDX.Enums.ComponentType.Library
].sort()).default(CDX.Enums.ComponentType.Application)).addOption(new commander_1.Option('-v, --verbose', 'Increase the verbosity of messages.\n' +
'Use multiple times to increase the verbosity even more.').argParser((_, previous) => previous + 1).default(1)).addArgument(new commander_1.Argument('<metafile>', 'Path to esbuild metafile')).version((0, _helpers_1.loadJsonFile)((0, node_path_1.resolve)(__dirname, '..', 'package.json')).version).allowExcessArguments(false);
}
async function run(process_) {
process_.title = 'cyclonedx-esbuild';
const program = makeCommand(process_);
program.parse(process_.argv);
const options = program.opts();
const logger = (0, logger_1.makeConsoleLogger)(process_.stderr, process_.stderr, options.verbose);
logger.debug(`${logger_1.LogPrefixes.DEBUG} options: %j`, options);
logger.debug(`${logger_1.LogPrefixes.DEBUG} args: %j`, program.args);
const serializeSpec = CDX.Spec.SpecVersionDict[options.specVersion];
if (serializeSpec === undefined) {
throw new Error(`Unknown specVersion: ${options.specVersion}`);
}
const metafile = program.args[0];
if (!metafile || !(0, node_fs_1.existsSync)(metafile)) {
throw new Error(`Missing metafile: ${metafile || '<no input>'}`);
}
const cdxComponentBuilder = new CDX.Builders.FromNodePackageJson.ComponentBuilder(new CDX.Factories.FromNodePackageJson.ExternalReferenceFactory(), new CDX.Factories.LicenseFactory());
const bomBuilder = new builders_1.BomBuilder(cdxComponentBuilder, new CDX.Factories.FromNodePackageJson.PackageUrlFactory('npm'), new CDX.Utils.LicenseUtility.LicenseEvidenceGatherer());
const bom = bomBuilder.fromMetafile((0, _helpers_1.loadJsonFile)(metafile), options.esbuildWorkingDir, options.gatherLicenseTexts, logger);
for (const toolC of (0, _helpers_1.makeToolCs)(CDX.Enums.ComponentType.Application, cdxComponentBuilder, logger)) {
bom.metadata.tools.components.add(toolC);
}
bom.serialNumber = options.outputReproducible
? undefined
: CDX.Utils.BomUtility.randomSerialNumber();
bom.metadata.timestamp = options.outputReproducible
? undefined
: new Date();
if (bom.metadata.component !== undefined) {
bom.metadata.component.type = options.mcType;
}
const serializer = new CDX.Serialize.JsonSerializer(new CDX.Serialize.JSON.Normalize.Factory(serializeSpec));
const serializeOptions = {
sortLists: options.outputReproducible,
space: 2
};
const serialized = serializer.serialize(bom, serializeOptions);
if (options.validate !== false) {
const validator = new CDX.Validation.JsonStrictValidator(serializeSpec.version);
try {
const validationErrors = await validator.validate(serialized);
if (validationErrors !== null) {
logger.debug(logger_1.LogPrefixes.DEBUG, 'BOM result invalid. details:', validationErrors);
throw new _helpers_1.ValidationError(`Failed to generate valid BOM"\n` +
'Please report the issue and provide the npm lock file of the current project to:\n' +
'https://github.com/CycloneDX/cyclonedx-esbuild/issues/new?template=ValidationError-report.md&labels=ValidationError&title=%5BValidationError%5D', validationErrors);
}
}
catch (err) {
if (err instanceof CDX.Validation.MissingOptionalDependencyError && !options.validate) {
logger.info(logger_1.LogPrefixes.INFO, 'skipped validate BOM:', err.message);
}
else {
logger.error(logger_1.LogPrefixes.ERROR, 'unexpected error');
throw err;
}
}
}
let outputFD = process_.stdout.fd;
if (options.outputFile !== OutputStdOut) {
const outputFPn = (0, node_path_1.resolve)(process_.cwd(), options.outputFile);
logger.debug(logger_1.LogPrefixes.DEBUG, 'outputFPn:', outputFPn);
const outputFDir = (0, node_path_1.dirname)(outputFPn);
if (!(0, node_fs_1.existsSync)(outputFDir)) {
logger.info(logger_1.LogPrefixes.INFO, 'creating directory', outputFDir);
(0, node_fs_1.mkdirSync)(outputFDir, { recursive: true });
}
outputFD = (0, node_fs_1.openSync)(outputFPn, 'w');
}
logger.log(`${logger_1.LogPrefixes.LOG} writing BOM to: %s`, options.outputFile);
const written = await (0, _helpers_1.writeAllSync)(outputFD, serialized);
logger.info(`${logger_1.LogPrefixes.INFO} wrote %d bytes to: %s`, written, options.outputFile);
return written > 0
? ExitCode.SUCCESS
: ExitCode.FAILURE;
}
var ExitCode;
(function (ExitCode) {
ExitCode[ExitCode["SUCCESS"] = 0] = "SUCCESS";
ExitCode[ExitCode["FAILURE"] = 1] = "FAILURE";
ExitCode[ExitCode["INVALID"] = 2] = "INVALID";
})(ExitCode || (ExitCode = {}));