109 lines
3.1 KiB
JavaScript
109 lines
3.1 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 = ["packages", "tooling"];
|
|
const violations = [];
|
|
|
|
async function pathExists(targetPath) {
|
|
try {
|
|
await stat(targetPath);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function listPackageJsonFiles(baseDir) {
|
|
const absoluteBaseDir = path.join(rootDir, baseDir);
|
|
if (!(await pathExists(absoluteBaseDir))) {
|
|
return [];
|
|
}
|
|
|
|
const entries = await readdir(absoluteBaseDir, { withFileTypes: true });
|
|
return entries
|
|
.filter((entry) => entry.isDirectory())
|
|
.map((entry) => path.join(baseDir, entry.name, "package.json"));
|
|
}
|
|
|
|
function collectExportTargets(value, keyPath, targets) {
|
|
if (typeof value === "string") {
|
|
targets.push({ keyPath, target: value });
|
|
return;
|
|
}
|
|
|
|
if (!value || typeof value !== "object") {
|
|
return;
|
|
}
|
|
|
|
for (const [key, nestedValue] of Object.entries(value)) {
|
|
collectExportTargets(nestedValue, `${keyPath}.${key}`, targets);
|
|
}
|
|
}
|
|
|
|
function getWildcardBaseDir(target) {
|
|
const wildcardIndex = target.indexOf("*");
|
|
if (wildcardIndex === -1) {
|
|
return target;
|
|
}
|
|
|
|
const targetPrefix = target.slice(0, wildcardIndex);
|
|
const lastSlashIndex = targetPrefix.lastIndexOf("/");
|
|
return lastSlashIndex === -1 ? "." : targetPrefix.slice(0, lastSlashIndex);
|
|
}
|
|
|
|
const packageJsonFiles = (
|
|
await Promise.all(workspaceDirs.map((workspaceDir) => listPackageJsonFiles(workspaceDir)))
|
|
).flat();
|
|
|
|
for (const packageJsonFile of packageJsonFiles) {
|
|
const packageJsonPath = path.join(rootDir, packageJsonFile);
|
|
if (!(await pathExists(packageJsonPath))) {
|
|
continue;
|
|
}
|
|
|
|
const packageDir = path.dirname(packageJsonPath);
|
|
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
const exportsField = packageJson.exports;
|
|
|
|
if (!exportsField || typeof exportsField !== "object") {
|
|
continue;
|
|
}
|
|
|
|
const exportTargets = [];
|
|
collectExportTargets(exportsField, "exports", exportTargets);
|
|
|
|
for (const exportTarget of exportTargets) {
|
|
const exportPath = exportTarget.target;
|
|
const containsWildcard = exportPath.includes("*");
|
|
const relativeCheckPath = containsWildcard ? getWildcardBaseDir(exportPath) : exportPath;
|
|
const absoluteCheckPath = path.resolve(packageDir, relativeCheckPath);
|
|
const relativeFromRoot = path.relative(rootDir, absoluteCheckPath) || ".";
|
|
|
|
if (!relativeFromRoot || relativeFromRoot.startsWith("..")) {
|
|
violations.push(
|
|
`${packageJsonFile}: ${exportTarget.keyPath} points outside the repository: ${exportPath}`,
|
|
);
|
|
continue;
|
|
}
|
|
|
|
if (!(await pathExists(absoluteCheckPath))) {
|
|
violations.push(
|
|
`${packageJsonFile}: ${exportTarget.keyPath} references a missing path: ${exportPath}`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (violations.length > 0) {
|
|
console.error("Workspace export check failed:");
|
|
for (const violation of violations) {
|
|
console.error(`- ${violation}`);
|
|
}
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log("Workspace exports passed.");
|