From cfee17f95f8b15c2a2c42303cc7ae7db64641fe2 Mon Sep 17 00:00:00 2001 From: EugeniyKiyashko Date: Tue, 2 Jun 2026 17:46:47 +0400 Subject: [PATCH 1/2] Speed up generated JS demos check (#33782) --- .github/workflows/demos_visual_tests.yml | 277 ++++++++++++++++-- .../Diagram/AdvancedDataBinding/React/App.tsx | 2 +- .../React/devextreme-vectormap-data.d.ts | 27 ++ .../React/devextreme-vectormap-data.d.ts | 27 ++ apps/demos/utils/ts-to-js-converter/cli.ts | 126 ++++---- .../utils/ts-to-js-converter/converter.ts | 128 ++++++-- 6 files changed, 495 insertions(+), 92 deletions(-) create mode 100644 apps/demos/Demos/VectorMap/ImageMarkers/React/devextreme-vectormap-data.d.ts create mode 100644 apps/demos/Demos/VectorMap/MultipleLayers/React/devextreme-vectormap-data.d.ts diff --git a/.github/workflows/demos_visual_tests.yml b/.github/workflows/demos_visual_tests.yml index 4380f05ddb90..93e50a78ac8f 100644 --- a/.github/workflows/demos_visual_tests.yml +++ b/.github/workflows/demos_visual_tests.yml @@ -438,26 +438,58 @@ jobs: path: apps/demos continue-on-error: true + - name: Detect changed React TS demos + id: changed-react-demos + working-directory: apps/demos + run: | + if [ ! -f "changed-files.json" ]; then + echo "changed-files.json not found, skipping generated JS demos check" + echo "has-react-demos=false" >> $GITHUB_OUTPUT + exit 0 + fi + + jq -r '.[].filename' changed-files.json \ + | grep '/React/' \ + | grep -E '\.tsx?$' \ + | sed 's|^apps/demos/||' \ + | sed -E 's|/[^/]*\.tsx?$||' \ + | sort \ + | uniq > changed-react-demos.txt || true + + if [ -s changed-react-demos.txt ]; then + echo "Changed React demos:" + cat changed-react-demos.txt + echo "has-react-demos=true" >> $GITHUB_OUTPUT + else + echo "No React demos found in changed files, skipping conversion" + echo "has-react-demos=false" >> $GITHUB_OUTPUT + fi + - uses: pnpm/action-setup@v6 + if: steps.changed-react-demos.outputs.has-react-demos == 'true' with: run_install: false - name: Use Node.js + if: steps.changed-react-demos.outputs.has-react-demos == 'true' uses: actions/setup-node@v6 with: node-version-file: '.node-version' - name: Download devextreme sources + if: steps.changed-react-demos.outputs.has-react-demos == 'true' uses: actions/download-artifact@v8 with: name: devextreme-sources - name: Get pnpm store directory + if: steps.changed-react-demos.outputs.has-react-demos == 'true' shell: bash run: | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - uses: actions/cache/restore@v5 + if: steps.changed-react-demos.outputs.has-react-demos == 'true' name: Restore pnpm cache with: path: ${{ env.STORE_PATH }} @@ -466,38 +498,25 @@ jobs: ${{ runner.os }}-pnpm-cache - name: Install dependencies + if: steps.changed-react-demos.outputs.has-react-demos == 'true' run: pnpm install --frozen-lockfile - name: Install tgz + if: steps.changed-react-demos.outputs.has-react-demos == 'true' working-directory: apps/demos run: pnpm add ../../devextreme-installer.tgz ../../devextreme-dist-installer.tgz ../../devextreme-react-installer.tgz ../../devextreme-vue-installer.tgz ../../devextreme-angular-installer.tgz - name: Prepare JS + if: steps.changed-react-demos.outputs.has-react-demos == 'true' working-directory: apps/demos run: pnpm run prepare-js - name: Check generated JS demos + if: steps.changed-react-demos.outputs.has-react-demos == 'true' working-directory: apps/demos run: | - if [ -f "changed-files.json" ]; then - echo "Running convert-to-js on changed files only" - - CHANGED_DEMOS=$(jq -r '.[].filename' changed-files.json | grep '/React/' | grep '\.tsx$' | sed 's|^apps/demos/||' | sed 's|/[^/]*\.tsx$||' | sort | uniq) - - if [ -z "$CHANGED_DEMOS" ]; then - echo "No React demos found in changed files, skipping conversion" - else - echo "Changed React demos:" - echo "$CHANGED_DEMOS" - - echo "$CHANGED_DEMOS" | while read -r demo_dir; do - if [ ! -z "$demo_dir" ]; then - echo "Converting: $demo_dir" - pnpm run convert-to-js "$demo_dir" - fi - done - fi - fi + echo "Running convert-to-js on changed files only" + xargs -r pnpm run convert-to-js < changed-react-demos.txt git add ./Demos -N @@ -523,7 +542,7 @@ jobs: strategy: fail-fast: false matrix: - CONSTEL: ['1/5', '2/5', '3/5', '4/5', '5/5'] + CONSTEL: ['1/2', '2/2'] steps: - name: Get sources @@ -1079,3 +1098,221 @@ jobs: name: accessibility-reports-jquery pattern: accessibility-reports-* delete-merged: true + + csp-check-jquery: + name: CSP check (jQuery) + needs: [check-should-run, build-devextreme] + if: | + always() && + needs.check-should-run.outputs.should-run == 'true' && + needs.build-devextreme.result == 'success' + runs-on: devextreme-shr2 + timeout-minutes: 60 + + steps: + - name: Get sources + uses: actions/checkout@v6 + + - name: Download artifacts + uses: actions/download-artifact@v8 + with: + name: devextreme-artifacts-jquery + path: ./packages/devextreme + + - name: Unpack artifacts + working-directory: ./packages/devextreme + run: 7z x artifacts.zip -aoa + + - name: Setup Chrome + uses: ./.github/actions/setup-chrome + with: + chrome-version: '145.0.7632.67' + + - name: Use Node.js + uses: actions/setup-node@v6 + with: + node-version-file: '.node-version' + + - uses: pnpm/action-setup@v6 + with: + run_install: false + + - name: Get pnpm store directory + shell: bash + run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - uses: actions/cache/restore@v5 + name: Restore pnpm cache + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-cache-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-cache + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Start CSP Server + run: node apps/demos/utils/server/csp-server.js 8080 & + + - name: Run CSP Check + working-directory: apps/demos + env: + CSP_FRAMEWORKS: jQuery + CHROME_PATH: google-chrome-stable + run: node utils/server/csp-check.js + + - name: Upload CSP report + if: always() + uses: actions/upload-artifact@v7 + with: + name: csp-violations-jquery + path: apps/demos/csp-reports/ + if-no-files-found: ignore + + csp-check-frameworks: + name: CSP check (${{ matrix.FRAMEWORK }}) + needs: [check-should-run, determine-framework-tests-scope, build-devextreme] + if: | + always() && + needs.check-should-run.outputs.should-run == 'true' && + needs.determine-framework-tests-scope.result == 'success' && + needs.determine-framework-tests-scope.outputs.framework-tests-scope != 'none' && + needs.build-devextreme.result == 'success' + strategy: + fail-fast: false + matrix: + FRAMEWORK: [React, Vue, Angular] + runs-on: devextreme-shr2 + timeout-minutes: 60 + + steps: + - name: Get sources + uses: actions/checkout@v6 + + - name: Download devextreme sources + uses: actions/download-artifact@v8 + with: + name: devextreme-sources + + - name: Setup Chrome + uses: ./.github/actions/setup-chrome + with: + chrome-version: '145.0.7632.67' + + - name: Use Node.js + uses: actions/setup-node@v6 + with: + node-version-file: '.node-version' + + - uses: pnpm/action-setup@v6 + with: + run_install: false + + - name: Get pnpm store directory + shell: bash + run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - uses: actions/cache/restore@v5 + name: Restore pnpm cache + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-cache-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-cache + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Install tgz + working-directory: apps/demos + run: pnpm add ../../devextreme-installer.tgz ../../devextreme-dist-installer.tgz ../../devextreme-react-installer.tgz ../../devextreme-vue-installer.tgz ../../devextreme-angular-installer.tgz + + - name: Start CSP Server + run: node apps/demos/utils/server/csp-server.js 8080 & + + - name: Run CSP Check + working-directory: apps/demos + env: + CSP_FRAMEWORKS: ${{ matrix.FRAMEWORK }} + CHROME_PATH: google-chrome-stable + run: node utils/server/csp-check.js + + - name: Upload CSP report + if: always() + uses: actions/upload-artifact@v7 + with: + name: csp-violations-${{ matrix.FRAMEWORK }} + path: apps/demos/csp-reports/ + if-no-files-found: ignore + + csp-report-summary: + name: CSP Violations Summary + runs-on: devextreme-shr2 + needs: [check-should-run, csp-check-jquery, csp-check-frameworks] + if: always() && needs.check-should-run.outputs.should-run == 'true' + timeout-minutes: 5 + + steps: + - name: Get sources + uses: actions/checkout@v6 + + - name: Use Node.js + uses: actions/setup-node@v6 + with: + node-version-file: '.node-version' + + - name: Download all CSP reports + uses: actions/download-artifact@v8 + with: + pattern: csp-violations-* + path: csp-reports-all + merge-multiple: true + continue-on-error: true + + - name: Summarize CSP violations + run: | + mkdir -p apps/demos/csp-reports + + echo "## CSP Violations Report" >> $GITHUB_STEP_SUMMARY + echo '' >> $GITHUB_STEP_SUMMARY + + GRAND_TOTAL=0 + for report in csp-reports-all/csp-violations-*.jsonl; do + [ -f "$report" ] || continue + FRAMEWORK=$(basename "$report" | sed 's/csp-violations-//;s/\.jsonl//') + cp "$report" "apps/demos/csp-reports/" + + if [ -s "$report" ]; then + COUNT=$(wc -l < "$report" | tr -d ' ') + GRAND_TOTAL=$((GRAND_TOTAL + COUNT)) + echo "### ⚠️ ${FRAMEWORK}: ${COUNT} violation(s)" >> $GITHUB_STEP_SUMMARY + echo '' >> $GITHUB_STEP_SUMMARY + echo '
' >> $GITHUB_STEP_SUMMARY + echo 'Show detailed report' >> $GITHUB_STEP_SUMMARY + echo '' >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + CSP_REPORT_FILE="$report" node apps/demos/utils/server/csp-report-summary.js >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo '' >> $GITHUB_STEP_SUMMARY + echo '
' >> $GITHUB_STEP_SUMMARY + echo '' >> $GITHUB_STEP_SUMMARY + else + echo "### ✅ ${FRAMEWORK}: No violations" >> $GITHUB_STEP_SUMMARY + echo '' >> $GITHUB_STEP_SUMMARY + fi + done + + if [ "$GRAND_TOTAL" -eq 0 ]; then + echo "✅ No CSP violations detected across all frameworks." + else + echo "⚠️ Total: $GRAND_TOTAL CSP violation(s)" + fi + + - name: Upload merged CSP reports + if: always() + uses: actions/upload-artifact@v7 + with: + name: csp-violations-report + path: apps/demos/csp-reports/ + if-no-files-found: ignore diff --git a/apps/demos/Demos/Diagram/AdvancedDataBinding/React/App.tsx b/apps/demos/Demos/Diagram/AdvancedDataBinding/React/App.tsx index c000ee118b3e..0453eabd0ad4 100644 --- a/apps/demos/Demos/Diagram/AdvancedDataBinding/React/App.tsx +++ b/apps/demos/Demos/Diagram/AdvancedDataBinding/React/App.tsx @@ -46,7 +46,7 @@ function itemTextStyleExpr(obj: { level: string; }) { } function itemStyleExpr(obj: { type: string; }) { - const style: React.CSSProperties = { stroke: '#444444' }; + const style: { stroke: string; fill?: string } = { stroke: '#444444' }; if (obj.type === 'group') { style.fill = '#f3f3f3'; } diff --git a/apps/demos/Demos/VectorMap/ImageMarkers/React/devextreme-vectormap-data.d.ts b/apps/demos/Demos/VectorMap/ImageMarkers/React/devextreme-vectormap-data.d.ts new file mode 100644 index 000000000000..e5de5ff1844d --- /dev/null +++ b/apps/demos/Demos/VectorMap/ImageMarkers/React/devextreme-vectormap-data.d.ts @@ -0,0 +1,27 @@ +declare module 'devextreme-dist/js/vectormap-data/usa.js' { + type Position = number[]; + type Geometry = + | { type: 'Point'; coordinates: Position } + | { type: 'MultiPoint'; coordinates: Position[] } + | { type: 'LineString'; coordinates: Position[] } + | { type: 'MultiLineString'; coordinates: Position[][] } + | { type: 'Polygon'; coordinates: Position[][] } + | { type: 'MultiPolygon'; coordinates: Position[][][] }; + + interface Feature { + type: 'Feature'; + geometry: Geometry; + properties: Record; + } + + interface FeatureCollection { + type: 'FeatureCollection'; + features: Feature[]; + // eslint-disable-next-line spellcheck/spell-checker + bbox?: number[]; + } + + const usa: FeatureCollection; + export { usa }; + export default usa; +} diff --git a/apps/demos/Demos/VectorMap/MultipleLayers/React/devextreme-vectormap-data.d.ts b/apps/demos/Demos/VectorMap/MultipleLayers/React/devextreme-vectormap-data.d.ts new file mode 100644 index 000000000000..370ec277e5ea --- /dev/null +++ b/apps/demos/Demos/VectorMap/MultipleLayers/React/devextreme-vectormap-data.d.ts @@ -0,0 +1,27 @@ +declare module 'devextreme-dist/js/vectormap-data/world.js' { + type Position = number[]; + type Geometry = + | { type: 'Point'; coordinates: Position } + | { type: 'MultiPoint'; coordinates: Position[] } + | { type: 'LineString'; coordinates: Position[] } + | { type: 'MultiLineString'; coordinates: Position[][] } + | { type: 'Polygon'; coordinates: Position[][] } + | { type: 'MultiPolygon'; coordinates: Position[][][] }; + + interface Feature { + type: 'Feature'; + geometry: Geometry; + properties: Record; + } + + interface FeatureCollection { + type: 'FeatureCollection'; + features: Feature[]; + // eslint-disable-next-line spellcheck/spell-checker + bbox?: number[]; + } + + const world: FeatureCollection; + export { world }; + export default world; +} diff --git a/apps/demos/utils/ts-to-js-converter/cli.ts b/apps/demos/utils/ts-to-js-converter/cli.ts index a15262238887..fec37f8784e1 100644 --- a/apps/demos/utils/ts-to-js-converter/cli.ts +++ b/apps/demos/utils/ts-to-js-converter/cli.ts @@ -5,7 +5,19 @@ import { glob } from 'glob'; import { consola } from 'consola'; import fs from 'fs'; -import { converter } from './converter'; +import { converter, prettifyOutputs, splitArrayIntoSubarrays } from './converter'; +import { ActionConverterEntry } from './types'; + +const defaultConversionConcurrency = 8; + +const logger = { + warning: consola.warn, + error: consola.error, + debug: consola.debug, + info: consola.info, + start: consola.start, + success: consola.success, +}; function findFoldersWithTsxFiles(directory) { const foldersWithTsxFiles = []; @@ -51,16 +63,7 @@ const getPatterns = () => { return filteredDemos.map((demoName) => demoName.split(path.sep).join(path.posix.sep)); }; -const performConversion = async (patterns) => { - const logger = { - warning: consola.warn, - error: consola.error, - debug: consola.debug, - info: consola.info, - start: consola.start, - success: consola.success, - }; - +const performConversion = async (patterns, conversionConcurrency) => { const args = minimist(patterns); const sourceDirs = args._ || [process.cwd()]; @@ -81,57 +84,82 @@ const performConversion = async (patterns) => { // @ts-ignore )).flat(1); - await Promise.all( - entries.map(async ({ source, out }) => { - logger.start(`converting ${source}`); - await converter(source, out, logger); - logger.success(`${source} complete`); - }), - ) - // eslint-disable-next-line no-void - .then(void 0) - .catch((error) => { - logger.error(error); - process.exit(1); - }); + const outDirs: (string | null)[] = []; + let failedCount = 0; + const entryBatches = splitArrayIntoSubarrays( + entries, + conversionConcurrency, + ); + + for (const entryBatch of entryBatches) { + outDirs.push(...await Promise.all( + entryBatch.map(async ({ source, out }) => { + logger.start(`converting ${source}`); + try { + const converted = await converter(source, out, logger); + if (converted) { + logger.success(`${source} complete`); + } + return converted ? out : null; + } catch { + logger.error(`failed converting ${source}`); + failedCount += 1; + return null; + } + }), + )); + } + + return { + outDirs: outDirs.filter((outDir): outDir is string => outDir != null), + failedCount, + }; }; -function splitArrayIntoSubarrays(array, subarrayLength) { - const result = []; +function getConversionConcurrency() { + const rawValue = process.env.CONVERT_TO_JS_CONCURRENCY; + const parsedValue = rawValue == null ? defaultConversionConcurrency : Number(rawValue); - for (let i = 0; i < array.length; i += subarrayLength) { - result.push(array.slice(i, i + subarrayLength)); + if (!Number.isInteger(parsedValue) || parsedValue < 1) { + throw new Error(`CONVERT_TO_JS_CONCURRENCY must be a positive integer. Received: ${rawValue}`); } - return result; + return parsedValue; } async function startScript() { const userFlags = process.argv.slice(2); - if (userFlags[0] === 'split') { - process.env.CONSTEL = '1/4'; - consola.log('Start converting Part', process.env.CONSTEL); - await batchPatternsAndConvert(); - process.env.CONSTEL = '2/4'; - consola.log('Start converting Part', process.env.CONSTEL); - await batchPatternsAndConvert(); - process.env.CONSTEL = '3/4'; - consola.log('Start converting Part', process.env.CONSTEL); - await batchPatternsAndConvert(); - process.env.CONSTEL = '4/4'; - consola.log('Start converting Part', process.env.CONSTEL); - await batchPatternsAndConvert(); - } else { - await batchPatternsAndConvert(); + const parts = userFlags[0] === 'split' ? ['1/4', '2/4', '3/4', '4/4'] : [null]; + let failedCount = 0; + + for (const part of parts) { + if (part != null) { + process.env.CONSTEL = part; + consola.log('Start converting Part', process.env.CONSTEL); + } + failedCount += await batchPatternsAndConvert(); } + + return failedCount; } async function batchPatternsAndConvert() { const allPatterns = getPatterns(); - const batches = splitArrayIntoSubarrays(allPatterns, 10); - for (const batch of batches) { - await performConversion(batch); - } + const conversionConcurrency = getConversionConcurrency(); + const { outDirs, failedCount } = await performConversion(allPatterns, conversionConcurrency); + + await prettifyOutputs(outDirs, process.cwd(), logger); + + return failedCount; } -startScript(); +startScript() + .then((failedCount) => { + if (failedCount > 0) { + process.exit(1); + } + }) + .catch((error) => { + logger.error(error); + process.exit(1); + }); diff --git a/apps/demos/utils/ts-to-js-converter/converter.ts b/apps/demos/utils/ts-to-js-converter/converter.ts index 4101fe599c6e..c9262934cab6 100644 --- a/apps/demos/utils/ts-to-js-converter/converter.ts +++ b/apps/demos/utils/ts-to-js-converter/converter.ts @@ -9,7 +9,11 @@ import { promisify } from 'util'; import _ from 'lodash'; import os from 'os'; -import { Logger, PathResolver, PathResolvers } from './types'; +import { + Logger, + PathResolver, + PathResolvers, +} from './types'; let platformGlob = glob; const makePathArrayPosix = (pathArray) => pathArray.map( @@ -34,6 +38,20 @@ const bundleAssets = [ const redundantAssets = ['*tsconfig*', 'types.js']; +const quoteShellArg = (value: string) => `"${value.replace(/(["\\$`])/g, '\\$1')}"`; + +const toPosixPath = (value: string) => value.split(path.sep).join(path.posix.sep); + +export function splitArrayIntoSubarrays(array: T[], subarrayLength: number): T[][] { + const result: T[][] = []; + + for (let i = 0; i < array.length; i += subarrayLength) { + result.push(array.slice(i, i + subarrayLength)); + } + + return result; +} + const makeConfig = ( resolve: PathResolvers, include: string[], @@ -59,6 +77,7 @@ const makeConfig = ( skipLibCheck: true, allowSyntheticDefaultImports: true, resolveJsonModule: true, + preserveSymlinks: true, }, }); @@ -78,15 +97,29 @@ const pipeSource = async ( })); }; -const execTsc = async (directory: string, args: string): Promise => new Promise((resolve, reject) => { - cps.exec(`tsc ${args}`, (error, stdout, stderr) => { - if (error != null) { - // eslint-disable-next-line prefer-promise-reject-errors - return reject(`${error}\n${stderr}\n${stdout}`); +const execTsc = async (directory: string, args: string[]): Promise => { + const tscScript = require.resolve('typescript/bin/tsc'); + try { + const { stdout } = await promisify(cps.execFile)( + process.execPath, + [tscScript, ...args], + { cwd: directory }, + ); + return stdout; + } catch (error) { + const { stdout, stderr } = error as { stdout?: string; stderr?: string }; + + if (stdout) { + console.error(stdout); } - return resolve(stdout); - }); -}); + + if (stderr) { + console.error(stderr); + } + + throw error; + } +}; const compile = async (resolve: PathResolvers, log: Logger) => { log.debug('compiling sources and unit tests'); @@ -181,14 +214,68 @@ const patchImports = async (resolve: PathResolvers, log: Logger) => { log.debug('imports patching done'); }; -const prettify = async (resolve: PathResolvers, log: Logger) => { +const PRETTIFY_CHUNK_SIZE = 50; + +const formatOutputs = async ( + outDirs: string[], + demosRootDir: string, + prettierCwd: string, + prettierConfig: string, + eslintConfig: string, +) => { + const prettierPatterns = outDirs + .map((outDir) => quoteShellArg(`${path.resolve(outDir)}${path.sep}!(*.{css,json,md,tsbuildinfo})`)) + .join(' '); + const eslintPatterns = outDirs + .map((outDir) => quoteShellArg( + toPosixPath(path.relative(demosRootDir, outDir)), + )) + .join(' '); + + const prettierCommand = [ + 'prettier', + '--write', + prettierPatterns, + '--config', + prettierConfig, + '--single-attribute-per-line', + '--print-width 100', + ].join(' '); + const eslintCommand = [ + 'eslint', + '--fix', + eslintPatterns, + '--config', + eslintConfig, + '--ignore-pattern "**/config.js"', + '--ignore-pattern "*.tsbuildinfo"', + ].join(' '); + + await exec(prettierCommand, { cwd: prettierCwd }); + await exec(eslintCommand, { cwd: demosRootDir }); +}; + +export const prettifyOutputs = async ( + outDirs: string[], + demosRootDir: string, + log: Logger, +) => { + const uniqueOutDirs = [...new Set(outDirs)]; + + if (uniqueOutDirs.length === 0) { + return; + } + log.debug('running Prettier'); - await exec(`prettier --write "${resolve.out('')}${path.sep}!(*.{css,json,md,tsbuildinfo})" --single-attribute-per-line --print-width 100`, { - cwd: resolve.out(''), - }); - await exec(`eslint --fix "${resolve.out('')}" --ignore-pattern "config.js" --ignore-pattern "*.tsbuildinfo"`, { - cwd: resolve.out(''), - }); + + const prettierConfig = quoteShellArg(path.join(demosRootDir, '.prettierrc.json')); + const eslintConfig = quoteShellArg(path.join(demosRootDir, 'eslint.config.mjs')); + + const prettierCwd = uniqueOutDirs[0]; + + for (const chunk of splitArrayIntoSubarrays(uniqueOutDirs, PRETTIFY_CHUNK_SIZE)) { + await formatOutputs(chunk, demosRootDir, prettierCwd, prettierConfig, eslintConfig); + } }; const hasTypescriptFiles = async (resolve: PathResolver) => { @@ -202,7 +289,7 @@ export const converter = async ( sourceDir: string, outDir: string, log: Logger, -): Promise => { +): Promise => { log.debug('TS to JS example converter starting'); log.debug(`sourceDir: ${sourceDir}`); log.debug(`outDir: ${outDir}`); @@ -217,7 +304,7 @@ export const converter = async ( if (!await hasTypescriptFiles(sourceDirResolver)) { log.info(`No TypeScript files found in ${sourceDir}. Skipping...`); - return; + return false; } log.debug(`touching ${outDir}`); @@ -237,11 +324,8 @@ export const converter = async ( await compile(resolve, log); await copyAssets(resolve, log); await patchImports(resolve, log); - await prettify(resolve, log); await strip(resolve, log); - } catch (error) { - log.error(error); - return; + return true; } finally { log.debug(`removing temp directory: ${tempDir}`); await remove(tempDir); From a297e9ebecb247dc067597a553f8bbee2b4b003f Mon Sep 17 00:00:00 2001 From: EugeniyKiyashko Date: Tue, 2 Jun 2026 19:08:01 +0400 Subject: [PATCH 2/2] Fix generated JS converter tsc args --- .github/workflows/demos_visual_tests.yml | 218 ------------------ apps/demos/utils/create-bundles/index.ts | 2 +- .../utils/ts-to-js-converter/converter.ts | 4 +- .../utils/visual-tests/testcafe-runner.ts | 2 +- 4 files changed, 4 insertions(+), 222 deletions(-) diff --git a/.github/workflows/demos_visual_tests.yml b/.github/workflows/demos_visual_tests.yml index 93e50a78ac8f..27c05ce433ad 100644 --- a/.github/workflows/demos_visual_tests.yml +++ b/.github/workflows/demos_visual_tests.yml @@ -1098,221 +1098,3 @@ jobs: name: accessibility-reports-jquery pattern: accessibility-reports-* delete-merged: true - - csp-check-jquery: - name: CSP check (jQuery) - needs: [check-should-run, build-devextreme] - if: | - always() && - needs.check-should-run.outputs.should-run == 'true' && - needs.build-devextreme.result == 'success' - runs-on: devextreme-shr2 - timeout-minutes: 60 - - steps: - - name: Get sources - uses: actions/checkout@v6 - - - name: Download artifacts - uses: actions/download-artifact@v8 - with: - name: devextreme-artifacts-jquery - path: ./packages/devextreme - - - name: Unpack artifacts - working-directory: ./packages/devextreme - run: 7z x artifacts.zip -aoa - - - name: Setup Chrome - uses: ./.github/actions/setup-chrome - with: - chrome-version: '145.0.7632.67' - - - name: Use Node.js - uses: actions/setup-node@v6 - with: - node-version-file: '.node-version' - - - uses: pnpm/action-setup@v6 - with: - run_install: false - - - name: Get pnpm store directory - shell: bash - run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - - uses: actions/cache/restore@v5 - name: Restore pnpm cache - with: - path: ${{ env.STORE_PATH }} - key: ${{ runner.os }}-pnpm-cache-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-cache - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Start CSP Server - run: node apps/demos/utils/server/csp-server.js 8080 & - - - name: Run CSP Check - working-directory: apps/demos - env: - CSP_FRAMEWORKS: jQuery - CHROME_PATH: google-chrome-stable - run: node utils/server/csp-check.js - - - name: Upload CSP report - if: always() - uses: actions/upload-artifact@v7 - with: - name: csp-violations-jquery - path: apps/demos/csp-reports/ - if-no-files-found: ignore - - csp-check-frameworks: - name: CSP check (${{ matrix.FRAMEWORK }}) - needs: [check-should-run, determine-framework-tests-scope, build-devextreme] - if: | - always() && - needs.check-should-run.outputs.should-run == 'true' && - needs.determine-framework-tests-scope.result == 'success' && - needs.determine-framework-tests-scope.outputs.framework-tests-scope != 'none' && - needs.build-devextreme.result == 'success' - strategy: - fail-fast: false - matrix: - FRAMEWORK: [React, Vue, Angular] - runs-on: devextreme-shr2 - timeout-minutes: 60 - - steps: - - name: Get sources - uses: actions/checkout@v6 - - - name: Download devextreme sources - uses: actions/download-artifact@v8 - with: - name: devextreme-sources - - - name: Setup Chrome - uses: ./.github/actions/setup-chrome - with: - chrome-version: '145.0.7632.67' - - - name: Use Node.js - uses: actions/setup-node@v6 - with: - node-version-file: '.node-version' - - - uses: pnpm/action-setup@v6 - with: - run_install: false - - - name: Get pnpm store directory - shell: bash - run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - - uses: actions/cache/restore@v5 - name: Restore pnpm cache - with: - path: ${{ env.STORE_PATH }} - key: ${{ runner.os }}-pnpm-cache-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-cache - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Install tgz - working-directory: apps/demos - run: pnpm add ../../devextreme-installer.tgz ../../devextreme-dist-installer.tgz ../../devextreme-react-installer.tgz ../../devextreme-vue-installer.tgz ../../devextreme-angular-installer.tgz - - - name: Start CSP Server - run: node apps/demos/utils/server/csp-server.js 8080 & - - - name: Run CSP Check - working-directory: apps/demos - env: - CSP_FRAMEWORKS: ${{ matrix.FRAMEWORK }} - CHROME_PATH: google-chrome-stable - run: node utils/server/csp-check.js - - - name: Upload CSP report - if: always() - uses: actions/upload-artifact@v7 - with: - name: csp-violations-${{ matrix.FRAMEWORK }} - path: apps/demos/csp-reports/ - if-no-files-found: ignore - - csp-report-summary: - name: CSP Violations Summary - runs-on: devextreme-shr2 - needs: [check-should-run, csp-check-jquery, csp-check-frameworks] - if: always() && needs.check-should-run.outputs.should-run == 'true' - timeout-minutes: 5 - - steps: - - name: Get sources - uses: actions/checkout@v6 - - - name: Use Node.js - uses: actions/setup-node@v6 - with: - node-version-file: '.node-version' - - - name: Download all CSP reports - uses: actions/download-artifact@v8 - with: - pattern: csp-violations-* - path: csp-reports-all - merge-multiple: true - continue-on-error: true - - - name: Summarize CSP violations - run: | - mkdir -p apps/demos/csp-reports - - echo "## CSP Violations Report" >> $GITHUB_STEP_SUMMARY - echo '' >> $GITHUB_STEP_SUMMARY - - GRAND_TOTAL=0 - for report in csp-reports-all/csp-violations-*.jsonl; do - [ -f "$report" ] || continue - FRAMEWORK=$(basename "$report" | sed 's/csp-violations-//;s/\.jsonl//') - cp "$report" "apps/demos/csp-reports/" - - if [ -s "$report" ]; then - COUNT=$(wc -l < "$report" | tr -d ' ') - GRAND_TOTAL=$((GRAND_TOTAL + COUNT)) - echo "### ⚠️ ${FRAMEWORK}: ${COUNT} violation(s)" >> $GITHUB_STEP_SUMMARY - echo '' >> $GITHUB_STEP_SUMMARY - echo '
' >> $GITHUB_STEP_SUMMARY - echo 'Show detailed report' >> $GITHUB_STEP_SUMMARY - echo '' >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - CSP_REPORT_FILE="$report" node apps/demos/utils/server/csp-report-summary.js >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo '' >> $GITHUB_STEP_SUMMARY - echo '
' >> $GITHUB_STEP_SUMMARY - echo '' >> $GITHUB_STEP_SUMMARY - else - echo "### ✅ ${FRAMEWORK}: No violations" >> $GITHUB_STEP_SUMMARY - echo '' >> $GITHUB_STEP_SUMMARY - fi - done - - if [ "$GRAND_TOTAL" -eq 0 ]; then - echo "✅ No CSP violations detected across all frameworks." - else - echo "⚠️ Total: $GRAND_TOTAL CSP violation(s)" - fi - - - name: Upload merged CSP reports - if: always() - uses: actions/upload-artifact@v7 - with: - name: csp-violations-report - path: apps/demos/csp-reports/ - if-no-files-found: ignore diff --git a/apps/demos/utils/create-bundles/index.ts b/apps/demos/utils/create-bundles/index.ts index 67b335220bec..202b4b443576 100644 --- a/apps/demos/utils/create-bundles/index.ts +++ b/apps/demos/utils/create-bundles/index.ts @@ -61,7 +61,7 @@ async function processBatch(bundler: ESBundler, demos: Demo[]) { await Promise.all(promises); } -async function processDemo(bundler: ESBundler, demo: Demo) { +function processDemo(bundler: ESBundler, demo: Demo) { return new Promise((res) => { bundler.buildDemo(demo, res); }).then(() => { console.log(`${bundler.framework} Demo: ${demo.Widget} - ${demo.Name}`); }); diff --git a/apps/demos/utils/ts-to-js-converter/converter.ts b/apps/demos/utils/ts-to-js-converter/converter.ts index c9262934cab6..fff176d52003 100644 --- a/apps/demos/utils/ts-to-js-converter/converter.ts +++ b/apps/demos/utils/ts-to-js-converter/converter.ts @@ -142,7 +142,7 @@ const compile = async (resolve: PathResolvers, log: Logger) => { ), ); - await execTsc(resolve.source('./'), `--build ${tsconfigFile}`); + await execTsc(resolve.source('./'), ['--build', tsconfigFile]); }; const copyAssets = async (resolve: PathResolvers, log: Logger) => { @@ -173,7 +173,7 @@ const strip = async (resolve: PathResolvers, log: Logger) => { }); }; -const replaceInFiles = async (filenamePatterns: string[], replacementCallback: (string) => string, resolvePath: (string) => string, log: Logger) => ( +const replaceInFiles = (filenamePatterns: string[], replacementCallback: (string) => string, resolvePath: (string) => string, log: Logger) => ( Promise.all( filenamePatterns.map(async (pattern) => { const files = await platformGlob(resolvePath(pattern)); diff --git a/apps/demos/utils/visual-tests/testcafe-runner.ts b/apps/demos/utils/visual-tests/testcafe-runner.ts index acc415194b90..4b1c5913075d 100644 --- a/apps/demos/utils/visual-tests/testcafe-runner.ts +++ b/apps/demos/utils/visual-tests/testcafe-runner.ts @@ -4,7 +4,7 @@ import fs from 'fs'; const LAUNCH_RETRY_ATTEMPTS = 3; const LAUNCH_RETRY_TIMEOUT = 10000; -const wait = async ( +const wait = ( timeout: number, ): Promise => new Promise((resolve) => setTimeout(resolve, timeout));