mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-19 17:11:42 -06:00
120 lines
3.3 KiB
JavaScript
120 lines
3.3 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
// Verifies that script TypeScript uses Node's transformation-free syntax subset.
|
|
import fs from "node:fs";
|
|
import { stripTypeScriptTypes } from "node:module";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const SCRIPT_ROOT = path.dirname(fileURLToPath(import.meta.url));
|
|
const TYPESCRIPT_IMPLEMENTATION_RE = /\.(?:cts|mts|ts)$/u;
|
|
const TYPESCRIPT_DECLARATION_RE = /\.d\.(?:cts|mts|ts)$/u;
|
|
const SKIPPED_DIRECTORY_NAMES = new Set([
|
|
".cache",
|
|
".git",
|
|
"build",
|
|
"coverage",
|
|
"dist",
|
|
"generated",
|
|
"node_modules",
|
|
"out",
|
|
"vendor",
|
|
]);
|
|
|
|
function compareNames(left, right) {
|
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
}
|
|
|
|
function listTypeScriptImplementationFiles(rootDir) {
|
|
const files = [];
|
|
|
|
function visit(directory) {
|
|
const entries = fs
|
|
.readdirSync(directory, { withFileTypes: true })
|
|
.toSorted((left, right) => compareNames(left.name, right.name));
|
|
for (const entry of entries) {
|
|
const entryPath = path.join(directory, entry.name);
|
|
if (entry.isDirectory()) {
|
|
if (!SKIPPED_DIRECTORY_NAMES.has(entry.name)) {
|
|
visit(entryPath);
|
|
}
|
|
continue;
|
|
}
|
|
if (
|
|
entry.isFile() &&
|
|
TYPESCRIPT_IMPLEMENTATION_RE.test(entry.name) &&
|
|
!TYPESCRIPT_DECLARATION_RE.test(entry.name)
|
|
) {
|
|
files.push(entryPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
visit(rootDir);
|
|
return files;
|
|
}
|
|
|
|
function diagnosticLine(error, sourceUrl) {
|
|
if (!(error instanceof Error) || !error.stack) {
|
|
return undefined;
|
|
}
|
|
const firstLine = error.stack.split("\n", 1)[0];
|
|
const prefix = `${sourceUrl}:`;
|
|
if (!firstLine.startsWith(prefix)) {
|
|
return undefined;
|
|
}
|
|
const line = Number.parseInt(firstLine.slice(prefix.length), 10);
|
|
return Number.isFinite(line) ? line : undefined;
|
|
}
|
|
|
|
/**
|
|
* Checks each TypeScript implementation file under a scripts tree without resolving imports.
|
|
*/
|
|
export function checkScriptErasability(rootDir = SCRIPT_ROOT) {
|
|
const files = listTypeScriptImplementationFiles(rootDir);
|
|
const rootLabel = path.basename(rootDir);
|
|
const errors = [];
|
|
|
|
for (const filePath of files) {
|
|
const relativePath = path.relative(rootDir, filePath).split(path.sep).join("/");
|
|
const sourceUrl = `${rootLabel}/${relativePath}`;
|
|
try {
|
|
stripTypeScriptTypes(fs.readFileSync(filePath, "utf8"), {
|
|
mode: "strip",
|
|
sourceUrl,
|
|
});
|
|
} catch (error) {
|
|
errors.push({
|
|
file: sourceUrl,
|
|
line: diagnosticLine(error, sourceUrl),
|
|
message: error instanceof Error ? error.message : String(error),
|
|
});
|
|
}
|
|
}
|
|
|
|
return { checkedFiles: files.length, errors };
|
|
}
|
|
|
|
export function main() {
|
|
const result = checkScriptErasability();
|
|
if (result.errors.length === 0) {
|
|
console.log(
|
|
`[script-erasability] checked ${result.checkedFiles} TypeScript implementation files`,
|
|
);
|
|
return;
|
|
}
|
|
|
|
console.error(
|
|
"TypeScript syntax under scripts/ must be erasable by Node without transformation:",
|
|
);
|
|
for (const error of result.errors) {
|
|
const location = error.line === undefined ? error.file : `${error.file}:${error.line}`;
|
|
console.error(`- ${location}: ${error.message}`);
|
|
}
|
|
process.exitCode = 1;
|
|
}
|
|
|
|
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
main();
|
|
}
|