branch:
check-exports.ts
3278 bytesRaw
import { existsSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { readFileSync } from "node:fs";
import fg from "fast-glob";
/**
* Recursively extract all file paths from an exports object
*/
function extractFilePaths(
exports: unknown,
paths: Set<string> = new Set()
): Set<string> {
if (typeof exports === "string") {
// Simple string path
paths.add(exports);
} else if (Array.isArray(exports)) {
// Array of paths
for (const item of exports) {
extractFilePaths(item, paths);
}
} else if (exports && typeof exports === "object") {
// Object with conditions (import, require, types, default, etc.)
for (const value of Object.values(exports)) {
extractFilePaths(value, paths);
}
}
return paths;
}
/**
* Check if all files referenced in a package's exports field exist
*/
function checkPackage(packageJsonPath: string): {
packageName: string;
missing: string[];
} {
const packageDir = dirname(packageJsonPath);
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
const packageName = packageJson.name || packageJsonPath;
const missing: string[] = [];
if (!packageJson.exports) {
// No exports field, nothing to check
return { packageName, missing };
}
// Extract all file paths from exports
const filePaths = extractFilePaths(packageJson.exports);
// Check if each file exists
for (const filePath of filePaths) {
// Skip non-file paths (like package names or special conditions)
if (!filePath.startsWith(".")) {
continue;
}
const fullPath = resolve(packageDir, filePath);
if (!existsSync(fullPath)) {
missing.push(filePath);
}
}
return { packageName, missing };
}
// Main execution
async function main() {
console.log("Checking package exports...\n");
// Find all package.json files in packages directory
const packageJsonFiles: string[] = [];
const SKIP_PACKAGES = new Set(["@cloudflare/agents-ui"]);
for await (const file of await fg.glob("packages/*/package.json")) {
if (file.includes("node_modules")) continue;
const pkg = JSON.parse(readFileSync(file, "utf-8"));
if (SKIP_PACKAGES.has(pkg.name)) continue;
packageJsonFiles.push(file);
}
if (packageJsonFiles.length === 0) {
console.error("No packages found!");
process.exit(1);
}
const results: Array<{ packageName: string; missing: string[] }> = [];
// Check each package
for (const packageJsonPath of packageJsonFiles) {
const result = checkPackage(packageJsonPath);
results.push(result);
if (result.missing.length === 0) {
console.log(`✓ ${result.packageName} - all exports valid`);
} else {
console.error(`✗ ${result.packageName} - missing files:`);
for (const file of result.missing) {
console.error(` - ${file}`);
}
}
}
// Summary
const failed = results.filter((r) => r.missing.length > 0);
if (failed.length > 0) {
console.error(
`\n${failed.length} of ${results.length} packages have missing export files!`
);
process.exit(1);
}
console.log(`\nAll ${results.length} packages have valid exports!`);
}
main().catch((error) => {
console.error("Error:", error);
process.exit(1);
});