Files
CapaKraken/scripts/check-workspace-exports.mjs

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.");