Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 39 additions & 21 deletions .github/workflows/visual-tests-demos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -435,26 +435,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 }}
Expand All @@ -463,38 +495,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

Expand All @@ -520,7 +539,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
Expand Down Expand Up @@ -1300,4 +1319,3 @@ jobs:
name: csp-violations-report
path: apps/demos/csp-reports/
if-no-files-found: ignore

2 changes: 1 addition & 1 deletion apps/demos/Demos/Diagram/AdvancedDataBinding/React/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<string, unknown>;
}

interface FeatureCollection {
type: 'FeatureCollection';
features: Feature[];
// eslint-disable-next-line spellcheck/spell-checker
bbox?: number[];
}

const usa: FeatureCollection;
export { usa };
export default usa;
}
Original file line number Diff line number Diff line change
@@ -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<string, unknown>;
}

interface FeatureCollection {
type: 'FeatureCollection';
features: Feature[];
// eslint-disable-next-line spellcheck/spell-checker
bbox?: number[];
}

const world: FeatureCollection;
export { world };
export default world;
}
126 changes: 77 additions & 49 deletions apps/demos/utils/ts-to-js-converter/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [];
Expand Down Expand Up @@ -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()];
Expand All @@ -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<ActionConverterEntry>(
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);
});
Loading
Loading