58 lines
2.6 KiB
JavaScript
58 lines
2.6 KiB
JavaScript
#!/usr/bin/env node
|
|||
|
|
// Pre-deploy syntax guard for the served HTML pages.
|
||
|
|
//
|
||
|
|
// WHY: our pages carry large INLINE <script> blocks. A single JavaScript syntax error in one block makes the
|
||
|
|
// WHOLE block fail to parse — every function/handler it defines becomes undefined. That is exactly what caused
|
||
|
|
// an app-wide login outage (home.html's `callConnected` listener was missing one `}`: the boot code never ran,
|
||
|
|
// so after login the splash spun forever). The browser catches it instantly; our deploy did not. This script
|
||
|
|
// turns that class of bug into a FAILED BUILD (exit 1) instead of a production outage.
|
||
|
|
//
|
||
|
|
// It parses — but never executes — every inline <script> in server/public/*.html with Node's `vm`, so it only
|
||
|
|
// checks SYNTAX (browser globals like `document` are irrelevant to parsing). Runs in the Docker build (see
|
||
|
|
// Dockerfile, right after `COPY server/ ./`) and locally from redeploy.sh for fast feedback.
|
||
|
|
const fs = require('fs');
|
||
|
|
const path = require('path');
|
||
|
|
const vm = require('vm');
|
||
|
|
|
||
|
|
const dir = path.join(__dirname, '..', 'public');
|
||
|
|
let files;
|
||
|
|
try {
|
||
|
|
files = fs.readdirSync(dir).filter((f) => f.toLowerCase().endsWith('.html'));
|
||
|
|
} catch (e) {
|
||
|
|
console.error('[check-syntax] cannot read ' + dir + ': ' + e.message);
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Inline <script> … </script> (skip ones with a src= attribute — those are external files, nothing inline).
|
||
|
|
const re = /<script\b([^>]*)>([\s\S]*?)<\/script\s*>/gi;
|
||
|
|
let errors = 0;
|
||
|
|
let blocks = 0;
|
||
|
|
|
||
|
|
for (const f of files) {
|
||
|
|
const html = fs.readFileSync(path.join(dir, f), 'utf8');
|
||
|
|
re.lastIndex = 0;
|
||
|
|
let m;
|
||
|
|
while ((m = re.exec(html))) {
|
||
|
|
if (/\bsrc\s*=/i.test(m[1] || '')) continue; // external script
|
||
|
|
blocks++;
|
||
|
|
const code = m[2];
|
||
|
|
// File line where this block's body starts, so the reported line is file-relative (not block-relative).
|
||
|
|
const openTagEnd = m.index + m[0].indexOf('>') + 1;
|
||
|
|
const startLine = html.slice(0, openTagEnd).split('\n').length - 1; // 0-indexed offset
|
||
|
|
try {
|
||
|
|
new vm.Script(code, { filename: f, lineOffset: startLine });
|
||
|
|
} catch (e) {
|
||
|
|
errors++;
|
||
|
|
const loc = (String(e.stack || '').split('\n')[0] || '').trim(); // e.g. "home.html:4398"
|
||
|
|
console.error('[check-syntax] ' + f + ' — ' + (e.message || 'syntax error') +
|
||
|
|
(loc && /:\d+$/.test(loc) ? (' @ ' + loc) : (' (inline <script> near line ' + (startLine + 1) + ')')));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (errors) {
|
||
|
|
console.error('[check-syntax] FAILED: ' + errors + ' inline <script> block(s) with syntax errors. Aborting deploy.');
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
console.log('[check-syntax] OK: ' + blocks + ' inline <script> block(s) parsed across ' + files.length + ' HTML file(s).');
|
||
|
|
process.exit(0);
|