test validate local frontend imports

This commit is contained in:
Dariusz L
2026-08-05 20:28:37 +02:00
parent 7b1ae82b0b
commit f103b67b98
+109
View File
@@ -0,0 +1,109 @@
import assert from 'node:assert/strict';
import { readdir, stat, readFile } from 'node:fs/promises';
import {
dirname,
extname,
isAbsolute,
relative,
resolve,
sep,
} from 'node:path';
import { fileURLToPath } from 'node:url';
import test from 'node:test';
const WEB_ROOT = fileURLToPath(new URL('../web/', import.meta.url));
const STATIC_IMPORT_PATTERN = /(?:^|[;\r\n])\s*(?:import|export)\s+(?:[\s\S]*?\sfrom\s+)?["']([^"']+)["']/g;
const DYNAMIC_IMPORT_PATTERN = /\bimport\s*\(\s*["']([^"']+)["']\s*\)/g;
async function collectJavaScriptFiles(directory) {
const entries = await readdir(directory, { withFileTypes: true });
const files = [];
for (const entry of entries) {
const entryPath = resolve(directory, entry.name);
if (entry.isDirectory()) {
files.push(...await collectJavaScriptFiles(entryPath));
} else if (entry.isFile() && extname(entry.name).toLowerCase() === '.js') {
files.push(entryPath);
}
}
return files;
}
function extractModuleSpecifiers(source) {
const specifiers = new Set();
for (const pattern of [STATIC_IMPORT_PATTERN, DYNAMIC_IMPORT_PATTERN]) {
pattern.lastIndex = 0;
for (const match of source.matchAll(pattern)) {
specifiers.add(match[1]);
}
}
return [...specifiers];
}
function isInsideWebRoot(targetPath) {
const pathFromWebRoot = relative(WEB_ROOT, targetPath);
return Boolean(
pathFromWebRoot === ''
|| (!pathFromWebRoot.startsWith(`..${sep}`)
&& pathFromWebRoot !== '..'
&& !isAbsolute(pathFromWebRoot))
);
}
async function resolveLocalModule(importerPath, specifier) {
const targetPath = resolve(dirname(importerPath), specifier);
if (!isInsideWebRoot(targetPath)) {
// Imports outside web/ are ComfyUI-provided modules such as scripts/app.js.
return null;
}
const candidates = [
targetPath,
`${targetPath}.js`,
resolve(targetPath, 'index.js'),
];
for (const candidate of candidates) {
try {
if ((await stat(candidate)).isFile()) return candidate;
} catch (error) {
if (error?.code !== 'ENOENT') throw error;
}
}
return targetPath;
}
test('all local frontend module imports resolve to files', async () => {
const sourceFiles = await collectJavaScriptFiles(WEB_ROOT);
const missingImports = [];
for (const importerPath of sourceFiles) {
const source = await readFile(importerPath, 'utf8');
for (const specifier of extractModuleSpecifiers(source)) {
if (!specifier.startsWith('.')) continue;
const resolvedPath = await resolveLocalModule(importerPath, specifier);
if (!resolvedPath) continue;
try {
if ((await stat(resolvedPath)).isFile()) continue;
} catch (error) {
if (error?.code !== 'ENOENT') throw error;
}
missingImports.push({
importer: relative(WEB_ROOT, importerPath),
specifier,
expected: relative(WEB_ROOT, resolvedPath),
});
}
}
assert.deepEqual(missingImports, [], 'local frontend imports must point to existing files');
});