Features: - **Interactive Checklist**: 'Review Changes' modal now updates in real-time as save tasks complete. - **Signal Logging**: Backend emits [SIGNAL] logs for deletions, adoptions, uploads, and reorders. - **UI Cleanup**: Removed redundant textual 'Execute Progress' log pane. Build & Quality: - **HTML Validation**: Added ools/validate_html.ts to build pipeline to prevent syntax errors in embedded JS. - **Strict Build**: pm run build now runs alidate:html first.
100 lines
3.3 KiB
TypeScript
100 lines
3.3 KiB
TypeScript
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import * as cheerio from 'cheerio';
|
|
import * as ts from 'typescript';
|
|
import { glob } from 'glob';
|
|
|
|
// Configuration
|
|
const SRC_DIR = 'src';
|
|
|
|
async function validateHtmlFiles() {
|
|
console.log(`[HTML Validator] Scanning ${SRC_DIR} for HTML files...`);
|
|
|
|
// Find all HTML files
|
|
const htmlFiles = glob.sync(`${SRC_DIR}/**/*.html`);
|
|
let hasErrors = false;
|
|
|
|
for (const file of htmlFiles) {
|
|
const absolutPath = path.resolve(file);
|
|
const content = fs.readFileSync(absolutPath, 'utf-8');
|
|
|
|
// Load with source location info enabled
|
|
// Cast options to any to avoid TS version mismatches with cheerio types
|
|
const options: any = { sourceCodeLocationInfo: true };
|
|
const $ = cheerio.load(content, options);
|
|
|
|
const scripts = $('script').toArray();
|
|
|
|
for (const element of scripts) {
|
|
// Cast to any to access startIndex safely
|
|
const node = element as any;
|
|
|
|
// Skip external scripts
|
|
if ($(element).attr('src')) continue;
|
|
|
|
const scriptContent = $(element).html();
|
|
if (!scriptContent) continue;
|
|
|
|
// Determine start line of the script tag in the original file
|
|
// Cheerio (htmlparser2) location info:
|
|
const loc = node.startIndex !== undefined ?
|
|
getLineNumber(content, node.startIndex) : 1;
|
|
|
|
// Validate Syntax using TypeScript Compiler API
|
|
const sourceFile = ts.createSourceFile(
|
|
'virtual.js',
|
|
scriptContent,
|
|
ts.ScriptTarget.ES2020,
|
|
true, // setParentNodes
|
|
ts.ScriptKind.JS
|
|
);
|
|
|
|
// Cast to any because parseDiagnostics might not be in the public interface depending on version
|
|
const sf: any = sourceFile;
|
|
|
|
if (sf.parseDiagnostics && sf.parseDiagnostics.length > 0) {
|
|
hasErrors = true;
|
|
console.error(`\n❌ Syntax Error in ${file}`);
|
|
|
|
sf.parseDiagnostics.forEach((diag: any) => {
|
|
const { line, character } = sourceFile.getLineAndCharacterOfPosition(diag.start!);
|
|
const message = ts.flattenDiagnosticMessageText(diag.messageText, '\n');
|
|
|
|
// Adjust line number: Script Start line + Error line inside script
|
|
// Note: 'line' is 0-indexed relative to script start
|
|
const visualLine = loc + line;
|
|
|
|
console.error(` Line ${visualLine}: ${message}`);
|
|
|
|
// Show snippet
|
|
const lines = scriptContent.split('\n');
|
|
if (lines[line]) {
|
|
console.error(` > ${lines[line].trim()}\n`);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hasErrors) {
|
|
console.error(`\n[HTML Validator] Failed. Syntax errors detected.`);
|
|
process.exit(1);
|
|
} else {
|
|
console.log(`[HTML Validator] Passed. All HTML scripts are valid.`);
|
|
}
|
|
}
|
|
|
|
// Helper to calculate line number from char index
|
|
function getLineNumber(fullText: string, index: number): number {
|
|
return fullText.substring(0, index).split('\n').length;
|
|
}
|
|
|
|
// Check if run directly
|
|
if (require.main === module) {
|
|
validateHtmlFiles().catch(err => {
|
|
console.error("Validator crashed:", err);
|
|
process.exit(1);
|
|
});
|
|
}
|