Skip to content

Commit 29b9cf6

Browse files
parallel frameworks
1 parent c82de20 commit 29b9cf6

File tree

4 files changed

+175
-56
lines changed

4 files changed

+175
-56
lines changed

.github/workflows/visual-tests-demos.yml

Lines changed: 114 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,8 +1069,8 @@ jobs:
10691069
pattern: accessibility-reports-*
10701070
delete-merged: true
10711071

1072-
csp-check:
1073-
name: CSP violations check
1072+
csp-check-jquery:
1073+
name: CSP check (jQuery)
10741074
needs: [check-should-run, build-devextreme]
10751075
if: |
10761076
always() &&
@@ -1126,24 +1126,100 @@ jobs:
11261126
- name: Start CSP Server
11271127
run: node apps/demos/utils/server/csp-server.js 8080 &
11281128

1129-
- name: Run CSP Check (Chrome headless)
1129+
- name: Run CSP Check
11301130
working-directory: apps/demos
11311131
env:
1132-
CSP_FRAMEWORKS: jQuery,React
1132+
CSP_FRAMEWORKS: jQuery
11331133
run: node utils/server/csp-check.js
11341134

1135-
- name: Upload CSP violation reports
1135+
- name: Upload CSP report
11361136
if: always()
11371137
uses: actions/upload-artifact@v4
11381138
with:
1139-
name: csp-violations
1139+
name: csp-violations-jquery
1140+
path: apps/demos/csp-reports/
1141+
if-no-files-found: ignore
1142+
1143+
csp-check-frameworks:
1144+
name: CSP check (${{ matrix.FRAMEWORK }})
1145+
needs: [check-should-run, determine-framework-tests-scope, build-devextreme]
1146+
if: |
1147+
always() &&
1148+
needs.check-should-run.outputs.should-run == 'true' &&
1149+
needs.determine-framework-tests-scope.result == 'success' &&
1150+
needs.determine-framework-tests-scope.outputs.framework-tests-scope != 'none' &&
1151+
needs.build-devextreme.result == 'success'
1152+
strategy:
1153+
fail-fast: false
1154+
matrix:
1155+
FRAMEWORK: [React, Vue, Angular]
1156+
runs-on: devextreme-shr2
1157+
timeout-minutes: 60
1158+
1159+
steps:
1160+
- name: Get sources
1161+
uses: actions/checkout@v4
1162+
1163+
- name: Download devextreme sources
1164+
uses: actions/download-artifact@v4
1165+
with:
1166+
name: devextreme-sources
1167+
1168+
- name: Setup Chrome
1169+
uses: ./.github/actions/setup-chrome
1170+
with:
1171+
chrome-version: '145.0.7632.67'
1172+
runner-type: 'github-hosted'
1173+
1174+
- name: Use Node.js
1175+
uses: actions/setup-node@v4
1176+
with:
1177+
node-version: '20'
1178+
1179+
- uses: pnpm/action-setup@v4
1180+
with:
1181+
run_install: false
1182+
1183+
- name: Get pnpm store directory
1184+
shell: bash
1185+
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
1186+
1187+
- uses: actions/cache/restore@v4
1188+
name: Restore pnpm cache
1189+
with:
1190+
path: ${{ env.STORE_PATH }}
1191+
key: ${{ runner.os }}-pnpm-cache-${{ hashFiles('**/pnpm-lock.yaml') }}
1192+
restore-keys: |
1193+
${{ runner.os }}-pnpm-cache
1194+
1195+
- name: Install dependencies
1196+
run: pnpm install --frozen-lockfile
1197+
1198+
- name: Install tgz
1199+
working-directory: apps/demos
1200+
run: pnpm add ../../devextreme-installer.tgz ../../devextreme-dist-installer.tgz ../../devextreme-react-installer.tgz ../../devextreme-vue-installer.tgz ../../devextreme-angular-installer.tgz
1201+
1202+
- name: Start CSP Server
1203+
run: node apps/demos/utils/server/csp-server.js 8080 &
1204+
1205+
- name: Run CSP Check
1206+
working-directory: apps/demos
1207+
env:
1208+
CSP_FRAMEWORKS: ${{ matrix.FRAMEWORK }}
1209+
run: node utils/server/csp-check.js
1210+
1211+
- name: Upload CSP report
1212+
if: always()
1213+
uses: actions/upload-artifact@v4
1214+
with:
1215+
name: csp-violations-${{ matrix.FRAMEWORK }}
11401216
path: apps/demos/csp-reports/
11411217
if-no-files-found: ignore
11421218

11431219
csp-report-summary:
11441220
name: CSP Violations Summary
11451221
runs-on: devextreme-shr2
1146-
needs: [check-should-run, csp-check]
1222+
needs: [check-should-run, csp-check-jquery, csp-check-frameworks]
11471223
if: always() && needs.check-should-run.outputs.should-run == 'true'
11481224
timeout-minutes: 5
11491225

@@ -1159,33 +1235,46 @@ jobs:
11591235
- name: Download all CSP reports
11601236
uses: actions/download-artifact@v4
11611237
with:
1162-
pattern: csp-*
1238+
pattern: csp-violations-*
11631239
path: csp-reports-all
11641240
merge-multiple: true
11651241
continue-on-error: true
11661242

1167-
- name: Merge and summarize CSP violations
1243+
- name: Summarize CSP violations
11681244
run: |
11691245
mkdir -p apps/demos/csp-reports
1170-
find csp-reports-all -name '*.jsonl' -exec cat {} + > apps/demos/csp-reports/csp-violations.jsonl 2>/dev/null || true
1171-
1172-
if [ -s apps/demos/csp-reports/csp-violations.jsonl ]; then
1173-
TOTAL=$(wc -l < apps/demos/csp-reports/csp-violations.jsonl | tr -d ' ')
1174-
echo "## ⚠️ CSP Violations Found ($TOTAL total)" >> $GITHUB_STEP_SUMMARY
1175-
echo '' >> $GITHUB_STEP_SUMMARY
1176-
echo '```' >> $GITHUB_STEP_SUMMARY
1177-
node apps/demos/utils/server/csp-report-summary.js >> $GITHUB_STEP_SUMMARY
1178-
echo '```' >> $GITHUB_STEP_SUMMARY
1179-
echo ''
1180-
echo "CSP violations found: $TOTAL"
1181-
node apps/demos/utils/server/csp-report-summary.js
1246+
1247+
echo "## CSP Violations Report" >> $GITHUB_STEP_SUMMARY
1248+
echo '' >> $GITHUB_STEP_SUMMARY
1249+
1250+
GRAND_TOTAL=0
1251+
for report in csp-reports-all/csp-violations-*.jsonl; do
1252+
[ -f "$report" ] || continue
1253+
FRAMEWORK=$(basename "$report" | sed 's/csp-violations-//;s/\.jsonl//')
1254+
cp "$report" "apps/demos/csp-reports/"
1255+
1256+
if [ -s "$report" ]; then
1257+
COUNT=$(wc -l < "$report" | tr -d ' ')
1258+
GRAND_TOTAL=$((GRAND_TOTAL + COUNT))
1259+
echo "### ⚠️ ${FRAMEWORK}: ${COUNT} violation(s)" >> $GITHUB_STEP_SUMMARY
1260+
echo '' >> $GITHUB_STEP_SUMMARY
1261+
echo '```' >> $GITHUB_STEP_SUMMARY
1262+
CSP_REPORT_FILE="$report" node apps/demos/utils/server/csp-report-summary.js >> $GITHUB_STEP_SUMMARY
1263+
echo '```' >> $GITHUB_STEP_SUMMARY
1264+
echo '' >> $GITHUB_STEP_SUMMARY
1265+
else
1266+
echo "### ✅ ${FRAMEWORK}: No violations" >> $GITHUB_STEP_SUMMARY
1267+
echo '' >> $GITHUB_STEP_SUMMARY
1268+
fi
1269+
done
1270+
1271+
if [ "$GRAND_TOTAL" -eq 0 ]; then
1272+
echo "✅ No CSP violations detected across all frameworks."
11821273
else
1183-
echo "## ✅ No CSP Violations" >> $GITHUB_STEP_SUMMARY
1184-
echo "No CSP violations detected across all demo tests." >> $GITHUB_STEP_SUMMARY
1185-
echo "No CSP violations detected."
1274+
echo "⚠️ Total: $GRAND_TOTAL CSP violation(s)"
11861275
fi
11871276
1188-
- name: Upload merged CSP report
1277+
- name: Upload merged CSP reports
11891278
if: always()
11901279
uses: actions/upload-artifact@v4
11911280
with:

apps/demos/utils/server/csp-check.js

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/* eslint-disable no-console */
21
const { execFileSync } = require('child_process');
32
const { join } = require('path');
43
const {
@@ -115,12 +114,10 @@ function httpRequest(url, method) {
115114

116115
async function main() {
117116
const chromePath = findChrome();
117+
const framework = FRAMEWORKS[0];
118118
console.log(`Chrome: ${chromePath}`);
119119
console.log(`Server: ${SERVER_URL}`);
120-
console.log(`Frameworks: ${FRAMEWORKS.join(', ')}\n`);
121-
122-
// Clear previous violations on server
123-
await httpRequest(`${SERVER_URL}/csp-violations`, 'DELETE');
120+
console.log(`Framework: ${framework}\n`);
124121

125122
const demos = findDemos();
126123
console.log(`Found ${demos.length} demo page(s) to check\n`);
@@ -130,33 +127,58 @@ async function main() {
130127
return;
131128
}
132129

133-
let checked = 0;
134-
for (const demo of demos) {
135-
checked += 1;
136-
if (checked % 50 === 0 || checked === demos.length || checked === 1) {
137-
console.log(` [${checked}/${demos.length}] ${demo.widget}/${demo.demo}/${demo.framework}`);
138-
}
130+
mkdirSync(REPORT_DIR, { recursive: true });
131+
const reportFile = join(REPORT_DIR, `csp-violations-${framework.toLowerCase()}.jsonl`);
132+
133+
let totalViolations = 0;
134+
let demosWithViolations = 0;
135+
const allViolations = [];
136+
137+
for (let i = 0; i < demos.length; i += 1) {
138+
const demo = demos[i];
139+
140+
await httpRequest(`${SERVER_URL}/csp-violations`, 'DELETE');
141+
139142
visitPage(chromePath, demo.url);
140-
}
141143

142-
// Wait for in-flight CSP reports to arrive at the server
143-
console.log('\nWaiting for remaining CSP reports...');
144-
await new Promise((resolve) => { setTimeout(resolve, 3000); });
144+
await new Promise((resolve) => { setTimeout(resolve, 500); });
145145

146-
// Fetch violations from CSP server
147-
const result = await httpRequest(`${SERVER_URL}/csp-violations`);
148-
const violations = result.violations || [];
146+
const result = await httpRequest(`${SERVER_URL}/csp-violations`);
147+
const violations = result.violations || [];
149148

150-
// Write JSONL report
151-
mkdirSync(REPORT_DIR, { recursive: true });
152-
const reportFile = join(REPORT_DIR, 'csp-violations.jsonl');
149+
if (violations.length > 0) {
150+
demosWithViolations += 1;
151+
totalViolations += violations.length;
153152

154-
if (violations.length > 0) {
155-
const lines = violations.map((v) => JSON.stringify(v)).join('\n');
153+
console.log(` ❌ [${i + 1}/${demos.length}] ${demo.widget}/${demo.demo}/${demo.framework}${violations.length} violation(s)`);
154+
for (const v of violations) {
155+
const blocked = v.blockedUri || 'N/A';
156+
const directive = v.effectiveDirective || v.violatedDirective || '?';
157+
console.log(` ${directive}: ${blocked}`);
158+
allViolations.push({ ...v, framework });
159+
}
160+
} else {
161+
console.log(` ✅ [${i + 1}/${demos.length}] ${demo.widget}/${demo.demo}/${demo.framework}`);
162+
}
163+
}
164+
165+
if (allViolations.length > 0) {
166+
const lines = allViolations.map((v) => JSON.stringify(v)).join('\n');
156167
writeFileSync(reportFile, `${lines}\n`);
157-
console.log(`\n⚠️ ${violations.length} CSP violation(s) detected`);
158168
} else {
159169
writeFileSync(reportFile, '');
170+
}
171+
172+
console.log(`\n${'='.repeat(60)}`);
173+
console.log(`Framework: ${framework}`);
174+
console.log(`Demos checked: ${demos.length}`);
175+
console.log(`Demos with violations: ${demosWithViolations}`);
176+
console.log(`Total violations: ${totalViolations}`);
177+
178+
if (totalViolations > 0) {
179+
console.log(`\n⚠️ ${totalViolations} CSP violation(s) detected in ${demosWithViolations} demo(s)`);
180+
console.log(`Report: ${reportFile}`);
181+
} else {
160182
console.log('\n✅ No CSP violations detected');
161183
}
162184
}

apps/demos/utils/server/csp-report-summary.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
const { readFileSync, existsSync } = require('fs');
33
const { join } = require('path');
44

5-
const reportFile = join(__dirname, '..', '..', 'csp-reports', 'csp-violations.jsonl');
5+
const reportFile = process.env.CSP_REPORT_FILE || join(__dirname, '..', '..', 'csp-reports', 'csp-violations.jsonl');
66

77
if (!existsSync(reportFile)) {
88
console.log('No CSP violations report found.');

apps/demos/utils/server/csp-server.js

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,22 @@ const cspViolations = [];
1414
let cspViolationIdCounter = 0;
1515

1616
const CSP_DIRECTIVES = [
17-
"default-src 'self'",
17+
"default-src 'none'",
18+
1819
"script-src 'self' https://esm.sh https://cdnjs.cloudflare.com https://cdn.jsdelivr.net",
1920
"style-src 'self' https://maxcdn.bootstrapcdn.com",
20-
"img-src 'self' data: blob: https:",
21-
"font-src 'self' data: https:",
22-
"connect-src 'self' https:",
23-
"worker-src 'self' blob:",
24-
"frame-src 'self' blob:",
21+
22+
"img-src 'self'",
23+
"font-src 'self'",
24+
"connect-src 'self'",
25+
"worker-src 'self'",
26+
"frame-src 'self'",
27+
28+
"object-src 'none'",
29+
"base-uri 'none'",
30+
"form-action 'self'",
31+
"frame-ancestors 'none'",
32+
2533
'report-uri /csp-report',
2634
].join('; ');
2735

0 commit comments

Comments
 (0)