'use strict'; const node_path = require('node:path'); const CDX = require('@cyclonedx/cyclonedx-library'); const node_fs = require('node:fs'); const promises = require('node:fs/promises'); const normalizePackageData = require('normalize-package-data'); const node_module = require('node:module'); var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null; function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; } function _interopNamespaceCompat(e) { if (e && typeof e === 'object' && 'default' in e) return e; const n = Object.create(null); if (e) { for (const k in e) { n[k] = e[k]; } } n.default = e; return n; } const CDX__namespace = /*#__PURE__*/_interopNamespaceCompat(CDX); const normalizePackageData__default = /*#__PURE__*/_interopDefaultCompat(normalizePackageData); async function getPackageJson(dir) { try { const rawPkg = await promises.readFile(node_path.resolve(dir, "package.json"), "utf-8"); const pkg = JSON.parse(rawPkg.toString()); normalizePackageData__default(pkg); return pkg; } catch { return null; } } async function getCorrespondingPackageFromModuleId(modulePath, traversalLimit = 10) { if (traversalLimit === 0) { return Promise.resolve(null); } const folder = node_path.dirname(modulePath); const potentialPackagePath = node_path.join(folder, "./package.json"); let pkgJson = null; if (node_fs.existsSync(potentialPackagePath)) { pkgJson = await getPackageJson(folder); } if (pkgJson !== null) { return pkgJson; } return await getCorrespondingPackageFromModuleId(folder, traversalLimit - 1); } function convertOrganizationalEntityOptionToModel(option) { return new CDX__namespace.Models.OrganizationalEntity({ name: option.name, url: new Set(option.url), contact: new CDX__namespace.Models.OrganizationalContactRepository( option.contact.map((contact) => new CDX__namespace.Models.OrganizationalContact(contact)) ) }); } const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))); const knownTools = ["rollup-plugin-sbom", "vite", "rollup"]; function registerPackageUrlOnComponent(component, factory) { if (component) { component.purl = factory.makeFromComponent(component); component.bomRef.value = component.purl?.toString(); } } async function registerTools(bom, builder) { async function registerTool(packageName) { try { const modulePath = require$1.resolve(packageName); const pkgJson = await getCorrespondingPackageFromModuleId(modulePath); if (pkgJson) { const tool = builder.makeTool(pkgJson); tool && bom.metadata.tools.add(tool); } } catch { } } for (const pkgName of knownTools) { await registerTool(pkgName); } } const DEFAULT_OPTIONS = { specVersion: CDX.Spec.Version.v1dot5, rootComponentType: CDX.Enums.ComponentType.Application, outDir: "cyclonedx", outFilename: "bom", outFormats: ["json", "xml"], saveTimestamp: true, autodetect: true, generateSerial: false, includeWellKnown: true, supplier: void 0, properties: void 0 }; const PLUGIN_ID = "rollup-plugin-sbom"; function rollupPluginSbom(userOptions) { const options = { ...DEFAULT_OPTIONS, ...userOptions }; const cdxExternalReferenceFactory = new CDX__namespace.Factories.FromNodePackageJson.ExternalReferenceFactory(); const cdxLicenseFactory = new CDX__namespace.Factories.LicenseFactory(); const cdxPurlFactory = new CDX__namespace.Factories.FromNodePackageJson.PackageUrlFactory("npm"); const cdxToolBuilder = new CDX__namespace.Builders.FromNodePackageJson.ToolBuilder(cdxExternalReferenceFactory); const cdxComponentBuilder = new CDX__namespace.Builders.FromNodePackageJson.ComponentBuilder( cdxExternalReferenceFactory, cdxLicenseFactory ); const jsonSerializer = new CDX__namespace.Serialize.JsonSerializer( new CDX__namespace.Serialize.JSON.Normalize.Factory(CDX__namespace.Spec.SpecVersionDict[options.specVersion]) ); const xmlSerializer = new CDX__namespace.Serialize.XmlSerializer( new CDX__namespace.Serialize.XML.Normalize.Factory(CDX__namespace.Spec.SpecVersionDict[options.specVersion]) ); const metadata = new CDX__namespace.Models.Metadata({ supplier: options.supplier && convertOrganizationalEntityOptionToModel(options.supplier), properties: options.properties && new CDX__namespace.Models.PropertyRepository( options.properties.map(({ name, value }) => new CDX__namespace.Models.Property(name, value)) ) }); const bom = new CDX__namespace.Models.Bom({ metadata }); const registeredPackageIds = []; return { name: PLUGIN_ID, async buildStart() { if (options.autodetect) { try { const rootPkg = await getPackageJson(process.cwd()); if (rootPkg) { bom.metadata.component = cdxComponentBuilder.makeComponent(rootPkg, options.rootComponentType); bom.metadata.component.version = rootPkg.version; registerPackageUrlOnComponent(bom.metadata.component, cdxPurlFactory); } } catch (err) { this.error("could not autodetect package.json in the current working directory"); } } bom.metadata.lifecycles.add(CDX__namespace.Enums.LifecyclePhase.Build); if (options.saveTimestamp) { bom.metadata.timestamp = /* @__PURE__ */ new Date(); } if (options.generateSerial) { bom.serialNumber = CDX__namespace.Utils.BomUtility.randomSerialNumber(); } await registerTools(bom, cdxToolBuilder); }, /** * Register only the effectively imported third party modules from `node_modules` */ async moduleParsed(moduleInfo) { const nodeModuleImportedIds = moduleInfo.importedIds.filter( (entry) => entry.includes("node_modules") && !entry.startsWith("\0") ); const potentialComponents = await Promise.all( nodeModuleImportedIds.map((moduleId) => { if (!moduleId.includes("node_modules")) { return Promise.resolve(null); } return getCorrespondingPackageFromModuleId(moduleId); }) ); const pkgs = potentialComponents.filter((entry) => !!entry); for (const pkg of pkgs) { const pkgId = `${pkg.name}@${pkg.version}`; if (registeredPackageIds.includes(pkgId)) { continue; } const component = cdxComponentBuilder.makeComponent(pkg, CDX__namespace.Enums.ComponentType.Library); registerPackageUrlOnComponent(component, cdxPurlFactory); component && bom.components.add(component); registeredPackageIds.push(pkgId); } }, /** * Finalize the SBOM and emit files */ generateBundle() { const formatMap = { json: jsonSerializer, xml: xmlSerializer }; options.outFormats.forEach((format) => { if (!formatMap[format]) { throw new Error(`Unsupported format: ${format}`); } this.emitFile({ type: "asset", fileName: node_path.join(options.outDir, `${options.outFilename}.${format}`), needsCodeReference: false, source: formatMap[format].serialize(bom, { sortLists: false, space: " " }) }); }); if (options.includeWellKnown) { this.emitFile({ type: "asset", fileName: ".well-known/sbom", needsCodeReference: false, source: jsonSerializer.serialize(bom, { sortLists: false, space: " " }) }); } } }; } module.exports = rollupPluginSbom;