Files
pikasTech-HWLAB/web/hwlab-cloud-web/scripts/tsc-check.ts
T

82 lines
3.0 KiB
TypeScript

import { spawnSync } from "node:child_process";
import { readdirSync, readFileSync, statSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const sourceFiles = collectSourceFiles(path.join(rootDir, "src"));
let explicitAnyCount = 0;
for (const file of sourceFiles) {
const text = readFileSync(file, "utf8");
const stripped = stripComments(text);
const matches = [
...stripped.matchAll(/:\s*any\b/gu),
...stripped.matchAll(/<\s*any\s*>/gu),
...stripped.matchAll(/as\s+any\b/gu),
...stripped.matchAll(/Array\s*<\s*any\s*>/gu)
];
if (matches.length > 0) {
explicitAnyCount += matches.length;
console.error(` ${path.relative(rootDir, file)}: ${matches.length} explicit any usage(s)`);
}
}
if (explicitAnyCount > 0) {
console.error(`hwlab-cloud-web tsc-check: ${explicitAnyCount} explicit any usage(s) in Vue source`);
process.exit(2);
}
const vueTscBin = resolveVueTscBin(rootDir);
if (!vueTscBin) {
console.error("hwlab-cloud-web tsc-check: vue-tsc not found in ancestor node_modules/.bin or PATH");
process.exit(2);
}
const result = spawnSync(vueTscBin, ["--noEmit", "--project", path.join(rootDir, "tsconfig.json")], { cwd: rootDir, encoding: "utf8" });
if (result.stdout) process.stdout.write(result.stdout);
if (result.stderr) process.stderr.write(result.stderr);
if (result.error) console.error(`hwlab-cloud-web tsc-check: failed to start ${vueTscBin}: ${result.error.message}`);
if (result.status !== 0) {
console.error(`hwlab-cloud-web tsc-check: vue-tsc failed with exit ${result.status ?? "unknown"}`);
process.exit(result.status ?? 2);
}
console.log("hwlab-cloud-web tsc-check: passed (strict Vue TS, 0 explicit any)");
function resolveVueTscBin(startDir: string): string | null {
let current = startDir;
while (true) {
const candidate = path.join(current, "node_modules/.bin/vue-tsc");
if (isRunnableFile(candidate)) return candidate;
const parent = path.dirname(current);
if (parent === current) break;
current = parent;
}
for (const dir of (process.env.PATH ?? "").split(path.delimiter).filter(Boolean)) {
const candidate = path.join(dir, process.platform === "win32" ? "vue-tsc.cmd" : "vue-tsc");
if (isRunnableFile(candidate)) return candidate;
}
return null;
}
function isRunnableFile(candidate: string): boolean {
try {
return statSync(candidate).isFile();
} catch {
return false;
}
}
function collectSourceFiles(dir: string): string[] {
const out: string[] = [];
for (const entry of readdirSync(dir)) {
if (entry === "node_modules" || entry === "dist" || entry.startsWith(".")) continue;
const full = path.join(dir, entry);
const stat = statSync(full);
if (stat.isDirectory()) out.push(...collectSourceFiles(full));
else if (/\.(ts|vue)$/u.test(full)) out.push(full);
}
return out;
}
function stripComments(text: string): string {
return text.replace(/\/\*[\s\S]*?\*\//gu, "").replace(/(^|[^:])\/\/.*$/gmu, "$1");
}