diff --git a/packages/sbom/project.json b/packages/sbom/project.json index 98dce0aa48ad..66219fa55cb7 100644 --- a/packages/sbom/project.json +++ b/packages/sbom/project.json @@ -11,7 +11,7 @@ }, "make": { "executor": "nx:run-commands", - "dependsOn": ["install-dependencies"], + "dependsOn": ["install-dependencies", "devextreme-metadata:generate"], "options": { "cwd": "{projectRoot}", "forwardAllArgs": false, diff --git a/packages/sbom/scripts/add-workspace-hashes.ts b/packages/sbom/scripts/add-workspace-hashes.ts new file mode 100644 index 000000000000..568e9533d409 --- /dev/null +++ b/packages/sbom/scripts/add-workspace-hashes.ts @@ -0,0 +1,62 @@ +import assert from 'node:assert'; +import { createHash } from 'node:crypto'; +import fs from 'node:fs'; +import path from 'node:path'; + +import { addHashes } from '@devexpress/sbom-toolkit'; +import type { AddHashesEntry, CycloneDXBom, Hash } from '@devexpress/sbom-toolkit'; + +const INTERNAL_PACKAGES: Record = { + 'devextreme-metadata': 'packages/devextreme-metadata/dist', + 'eslint-migration-utils': 'packages/eslint-migration-utils/src', +}; + +let contentHashes: Record | undefined; + +interface AddWorkspaceHashesConfig { + sbomPath: string; + repoRoot: string; +} + +export function addWorkspaceHashes({ sbomPath, repoRoot }: AddWorkspaceHashesConfig): void { + contentHashes ??= Object.entries(INTERNAL_PACKAGES).reduce>( + (acc, [name, dir]) => ({ ...acc, [name]: contentHash(path.join(repoRoot, dir)) }), + {}, + ); + const hashes = contentHashes; + + const { components = [] }: CycloneDXBom = JSON.parse(fs.readFileSync(sbomPath, 'utf-8')); + const entries = components + .filter((component) => Object.hasOwn(INTERNAL_PACKAGES, componentName(component))) + .map((component): AddHashesEntry => { + const name = componentName(component); + assert(!component.hashes?.length, `Unexpected pre-existing hash for internal package "${name}" in ${sbomPath}`); + assert(name in hashes, `Missing computed content hash for internal package "${name}"`); + return { sbomPath, component: component['bom-ref'], hashes: [hashes[name]] }; + }); + + if (entries.length) { + addHashes({ packages: entries }); + } +} + +function componentName({ group, name }: CycloneDXBom['components'][number]): string { + return group ? `${group}/${name}` : name; +} + +function contentHash(dir: string): Hash { + const files = fs.readdirSync(dir, { recursive: true, encoding: 'utf-8' }) + .filter((file) => fs.statSync(path.join(dir, file)).isFile()) + .map((file) => file.split(path.sep).join('/')) + .sort(); + if (!files.length) { + throw new Error(`No files to hash in ${dir}`); + } + + const hash = createHash('sha512'); + files.forEach((file) => { + const content = fs.readFileSync(path.join(dir, file)); + hash.update(`${file}\0${content.length}\0`).update(content); + }); + return { alg: 'SHA-512', content: hash.digest('hex') }; +} diff --git a/packages/sbom/scripts/make.ts b/packages/sbom/scripts/make.ts index 8417e1d10267..f9b550d797ca 100644 --- a/packages/sbom/scripts/make.ts +++ b/packages/sbom/scripts/make.ts @@ -4,6 +4,8 @@ import path from 'node:path'; import { addDependencies, makeSbom } from '@devexpress/sbom-toolkit'; import type { MakeSbomConfig } from '@devexpress/sbom-toolkit'; +import { addWorkspaceHashes } from './add-workspace-hashes.ts'; + const PACKAGE_DIR = path.resolve(import.meta.dirname, '..'); const REPO_ROOT = path.resolve(PACKAGE_DIR, '../..'); const DIST = path.join(PACKAGE_DIR, 'dist'); @@ -40,11 +42,12 @@ await makeSbom({ }); await makeSbom({ targetDir: REPO_ROOT, outputDir: DIST, packages, options }); -for (const name of PACKAGES_WITH_DEVEXTREME) { - const sbomPath = path.join(DIST, `${name}.cdx.json`); - if (fs.existsSync(sbomPath)) { +for (const { fullName } of packages) { + const sbomPath = path.join(DIST, `${fullName}.cdx.json`); + if (PACKAGES_WITH_DEVEXTREME.includes(fullName)) { addDependencies({ sbomPath, extraSbomPath: EXTRA_SBOM, component: 'devextreme' }); } + addWorkspaceHashes({ sbomPath, repoRoot: REPO_ROOT }); } fs.rmSync(EXTRA_SBOM); diff --git a/packages/sbom/scripts/tsconfig.json b/packages/sbom/scripts/tsconfig.json index ea1ca19f6757..4708864bab91 100644 --- a/packages/sbom/scripts/tsconfig.json +++ b/packages/sbom/scripts/tsconfig.json @@ -3,6 +3,7 @@ "module": "preserve", "target": "es2022", "types": ["node"], - "noEmit": true + "noEmit": true, + "allowImportingTsExtensions": true } }