Implement interactive execution plan and strict HTML validation
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.
This commit is contained in:
99
tools/validate_html.ts
Normal file
99
tools/validate_html.ts
Normal file
@ -0,0 +1,99 @@
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user