From 3d933e9abe3af56e5451b25cf1b0ff808a4959ca Mon Sep 17 00:00:00 2001 From: Ruslan Farkhutdinov <23283554+r-farkhutdinov@users.noreply.github.com> Date: Thu, 23 Jul 2026 17:26:47 +0300 Subject: [PATCH 1/2] CI: Remove changesSegregation job from pr-filter-stubs WF (#34360) --- .github/workflows/pr-filter-stubs.yml | 130 +++++++++++++------------- 1 file changed, 66 insertions(+), 64 deletions(-) diff --git a/.github/workflows/pr-filter-stubs.yml b/.github/workflows/pr-filter-stubs.yml index 3d047ffa54a6..0a8d8e89c199 100644 --- a/.github/workflows/pr-filter-stubs.yml +++ b/.github/workflows/pr-filter-stubs.yml @@ -7,35 +7,37 @@ on: jobs: - changesSegregation: - name: Changes Segregation Check + mdOnlyFilter: + name: 'MD-only filter' runs-on: ubuntu-latest - env: - GITHUB_TOKEN: ${{ secrets.MONOREPO_ALERTS_FGPAT }} + outputs: + hasOtherFiles: ${{ steps.otherFilesFilter.outputs.result }} steps: - - name: Checkout uses: actions/checkout@v6 with: fetch-depth: 2 - - name: .md files filter - id: mdFilesFilter + - name: Other files filter + id: otherFilesFilter uses: DevExpress/github-actions/pr-filter@v1 with: paths: '**;!apps/**/*.md' gh-token: ${{ secrets.GITHUB_TOKEN }} - - name: Show warning for .md files - if: steps.mdFilesFilter.outputs.result == 'true' - uses: actions/github-script@v9 - with: - script: core.setFailed('Changes in apps/**/*.md shouldn\'t be mixed with changes in other files') + stubChecksReady: + name: 'Stub checks ready' + needs: [ mdOnlyFilter ] + if: needs.mdOnlyFilter.outputs.hasOtherFiles != 'true' + runs-on: ubuntu-latest + steps: + - name: Prepare required check stubs + run: echo "Required check stubs are available for apps/**/*.md PRs." # build_all.yml, demos_visual_tests.yml, packages_publishing.yml, playgrounds_tests.yml, qunit_tests.yml, testcafe_tests.yml, wrapper_tests.yml build1: name: 'build' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: @@ -43,28 +45,28 @@ jobs: # wrapper_tests.yml wrapper_tests1: name: 'check-regenerate' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 wrapper_tests2: name: 'test (angular)' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 wrapper_tests3: name: 'test (react)' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 wrapper_tests4: name: 'test (vue)' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: @@ -72,7 +74,7 @@ jobs: #default_workflow.yml default_workflow1: name: 'main' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: @@ -80,28 +82,28 @@ jobs: # lint.yml lint1: name: 'Renovation' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 lint2: name: 'TS' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 lint3: name: 'JS' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 lint4: name: 'CSS' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: @@ -109,7 +111,7 @@ jobs: # styles.yml styles1: name: 'Tests' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: @@ -117,7 +119,7 @@ jobs: # renovation.yml renovation1: name: 'jest-tests' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: @@ -125,98 +127,98 @@ jobs: # qunit_tests qunit_tests1: name: 'Constel export' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 qunit_tests2: name: 'Constel misc' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 qunit_tests3: name: 'Constel ui' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 qunit_tests4: name: 'Constel ui.scheduler-US/Pacific' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 qunit_tests5: name: 'Constel misc-no-csp' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 qunit_tests6: name: 'Constel ui.widgets-no-csp' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 qunit_tests7: name: 'Constel ui.editors' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 qunit_tests8: name: 'Constel ui.grid' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 qunit_tests9: name: 'Constel ui.scheduler' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 qunit_tests10: name: 'ui-shadow-dom' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 qunit_tests11: name: 'ui.widgets-shadow-dom' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 qunit_tests12: name: 'ui.editors-shadow-dom' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 qunit_tests13: name: 'ui.grid-shadow-dom' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 qunit_tests14: name: 'ui.scheduler-shadow-dom' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: @@ -224,49 +226,49 @@ jobs: # demos_visual_tests.yml demos_visual_tests1: name: 'jquery(1/3)-screenshots-material.blue.light' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 demos_visual_tests2: name: 'jquery(2/3)-screenshots-material.blue.light' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 demos_visual_tests3: name: 'jquery(3/3)-screenshots-material.blue.light' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 demos_visual_tests4: name: 'jquery(1/3)-screenshots-fluent.blue.light' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 demos_visual_tests5: name: 'jquery(2/3)-screenshots-fluent.blue.light' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 demos_visual_tests6: name: 'jquery(3/3)-screenshots-fluent.blue.light' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 demos_visual_tests7: name: 'jquery-accessibility-fluent.blue.light' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: @@ -274,133 +276,133 @@ jobs: # testcafe_tests.yml testcafe_tests1: name: 'accessibility - fluent' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 testcafe_tests2: name: 'common - fluent' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 testcafe_tests3: name: 'generic' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 testcafe_tests4: name: 'material' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 testcafe_tests5: name: 'material - compact' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 testcafe_tests6: name: 'cardView - fluent' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 testcafe_tests7: name: 'dataGrid (1/4) - fluent' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 testcafe_tests8: name: 'dataGrid (2/4) - fluent' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 testcafe_tests9: name: 'dataGrid (3/4) - fluent' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 testcafe_tests10: name: 'dataGrid (4/4) - fluent' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 testcafe_test11: name: 'editors - fluent' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 testcafe_tests12: name: 'navigation - fluent' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 testcafe_tests13: name: 'scheduler / common (1/3) - fluent' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 testcafe_tests14: name: 'scheduler / common (2/3) - fluent' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 testcafe_tests15: name: 'scheduler / common (3/3) - fluent' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 testcafe_tests16: name: 'scheduler / viewOffset (1/2) - fluent' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 testcafe_tests17: name: 'scheduler / viewOffset (2/2) - fluent' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 testcafe_tests18: name: 'scheduler / timezones (Europe/Berlin) - fluent' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: - run: exit 1 testcafe_tests19: name: 'scheduler / timezones (America/Los_Angeles) - fluent' - needs: [ changesSegregation ] + needs: [ stubChecksReady ] if: false runs-on: ubuntu-latest steps: From 78838c6027605cfc8489f416503ff5bf2cc4e97d Mon Sep 17 00:00:00 2001 From: Ruslan Farkhutdinov <23283554+r-farkhutdinov@users.noreply.github.com> Date: Thu, 15 Oct 2026 20:55:03 +0000 Subject: [PATCH 2/2] fix(sbom): add hashes for workspace packages (#35714) --- packages/sbom/project.json | 2 +- packages/sbom/scripts/add-workspace-hashes.ts | 62 +++++++++++++++++++ packages/sbom/scripts/make.ts | 9 ++- packages/sbom/scripts/tsconfig.json | 3 +- 4 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 packages/sbom/scripts/add-workspace-hashes.ts 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 } }