151 lines
3.9 KiB
JavaScript
151 lines
3.9 KiB
JavaScript
import { readdir, readFile, stat } from "node:fs/promises";
|
|
import path from "node:path";
|
|
import process from "node:process";
|
|
import { resolveRealWorkspaceRoot } from "./load-env.mjs";
|
|
|
|
const rootDir = resolveRealWorkspaceRoot();
|
|
const workspaceDirs = ["apps", "packages", "tooling"];
|
|
const sourceFileExtensions = new Set([
|
|
".ts",
|
|
".tsx",
|
|
".mts",
|
|
".cts",
|
|
".js",
|
|
".jsx",
|
|
".mjs",
|
|
".cjs",
|
|
]);
|
|
const resolutionExtensions = [
|
|
"",
|
|
".ts",
|
|
".tsx",
|
|
".mts",
|
|
".cts",
|
|
".js",
|
|
".jsx",
|
|
".mjs",
|
|
".cjs",
|
|
];
|
|
const violations = [];
|
|
|
|
const importPattern =
|
|
/(?:import|export)\s+(?:[^"'`]*?\s+from\s+)?["'`](\.{1,2}\/[^"'`]+)["'`]|import\s*\(\s*["'`](\.{1,2}\/[^"'`]+)["'`]\s*\)/g;
|
|
|
|
async function pathExists(targetPath) {
|
|
try {
|
|
await stat(targetPath);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function collectSourceFiles(directoryPath, result) {
|
|
const entries = await readdir(directoryPath, { withFileTypes: true });
|
|
|
|
for (const entry of entries) {
|
|
const absolutePath = path.join(directoryPath, entry.name);
|
|
|
|
if (entry.isDirectory()) {
|
|
if (
|
|
entry.name === "node_modules"
|
|
|| entry.name.startsWith(".next")
|
|
|| entry.name === ".turbo"
|
|
|| entry.name === "dist"
|
|
|| entry.name === "coverage"
|
|
|| entry.name === "playwright-report"
|
|
|| entry.name === "test-results"
|
|
) {
|
|
continue;
|
|
}
|
|
await collectSourceFiles(absolutePath, result);
|
|
continue;
|
|
}
|
|
|
|
if (!entry.isFile()) {
|
|
continue;
|
|
}
|
|
|
|
const extension = path.extname(entry.name);
|
|
if (sourceFileExtensions.has(extension)) {
|
|
result.push(absolutePath);
|
|
}
|
|
}
|
|
}
|
|
|
|
function getExtensionlessCandidates(basePath) {
|
|
return resolutionExtensions.flatMap((extension) => [
|
|
`${basePath}${extension}`,
|
|
path.join(basePath, `index${extension}`),
|
|
]);
|
|
}
|
|
|
|
function getExplicitExtensionCandidates(basePath, importExtension) {
|
|
const baseWithoutExtension = basePath.slice(0, -importExtension.length);
|
|
const siblingExtensions =
|
|
importExtension === ".js" || importExtension === ".mjs" || importExtension === ".cjs"
|
|
? [importExtension, ".ts", ".tsx", ".mts", ".cts"]
|
|
: [importExtension];
|
|
|
|
return siblingExtensions.flatMap((extension) => [
|
|
`${baseWithoutExtension}${extension}`,
|
|
path.join(baseWithoutExtension, `index${extension}`),
|
|
]);
|
|
}
|
|
|
|
async function resolvesImport(fromFilePath, importPath) {
|
|
const resolvedBasePath = path.resolve(path.dirname(fromFilePath), importPath);
|
|
const rawExtension = path.extname(importPath);
|
|
const importExtension = resolutionExtensions.includes(rawExtension) ? rawExtension : "";
|
|
const candidates = importExtension
|
|
? getExplicitExtensionCandidates(resolvedBasePath, importExtension)
|
|
: getExtensionlessCandidates(resolvedBasePath);
|
|
|
|
for (const candidate of candidates) {
|
|
if (await pathExists(candidate)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
const sourceFiles = [];
|
|
|
|
for (const workspaceDir of workspaceDirs) {
|
|
const absoluteWorkspaceDir = path.join(rootDir, workspaceDir);
|
|
if (!(await pathExists(absoluteWorkspaceDir))) {
|
|
continue;
|
|
}
|
|
await collectSourceFiles(absoluteWorkspaceDir, sourceFiles);
|
|
}
|
|
|
|
for (const sourceFile of sourceFiles) {
|
|
const content = await readFile(sourceFile, "utf8");
|
|
const relativeSourceFile = path.relative(rootDir, sourceFile);
|
|
const imports = new Set();
|
|
|
|
for (const match of content.matchAll(importPattern)) {
|
|
const importPath = match[1] ?? match[2];
|
|
if (importPath) {
|
|
imports.add(importPath.trim());
|
|
}
|
|
}
|
|
|
|
for (const importPath of imports) {
|
|
if (!(await resolvesImport(sourceFile, importPath))) {
|
|
violations.push(`${relativeSourceFile}: unresolved relative import ${importPath}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (violations.length > 0) {
|
|
console.error("Workspace import check failed:");
|
|
for (const violation of violations) {
|
|
console.error(`- ${violation}`);
|
|
}
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log("Workspace imports passed.");
|