mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 15:52:21 +00:00
Add a --write mode to the package boundary check
`node scripts/check-package-boundaries.mjs --write` regenerates each group's module list in scripts/package-boundaries.json from what the group's build reaches: entries that own nothing reached are removed, newly reached modules are appended in sorted order, and the order of kept entries stays. The check without the flag is unchanged.
This commit is contained in:
1 parent
60fb8fee07
commit
f437c615a0
3 files changed
+293
-20
No files matched your search
@@ -1,19 +1,28 @@
|
||||
import { readFile, realpath } from 'node:fs/promises';
|
||||
import { readFile, realpath, writeFile } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import { VUE_PLUGIN_OPTIONS } from '../build/vueOptions.js';
|
||||
import { build, normalizePath } from 'vite';
|
||||
import * as prettier from 'prettier';
|
||||
|
||||
/** Build each browser or Node export group and reject imports outside its declared ownership. */
|
||||
export async function checkPackageBoundaries(root) {
|
||||
return buildPackageBoundaries(root, false);
|
||||
}
|
||||
|
||||
/** Regenerate module ownership from each export group's complete build graph. */
|
||||
export async function writePackageBoundaries(root) {
|
||||
return buildPackageBoundaries(root, true);
|
||||
}
|
||||
|
||||
async function buildPackageBoundaries(root, write) {
|
||||
root = await realpath(root);
|
||||
const readJson = async (name) =>
|
||||
JSON.parse(await readFile(path.join(root, name), 'utf8'));
|
||||
const pkg = await readJson('package.json');
|
||||
const { engineImportExceptions, ...groups } = await readJson(
|
||||
'scripts/package-boundaries.json',
|
||||
);
|
||||
const manifest = await readJson('scripts/package-boundaries.json');
|
||||
const { engineImportExceptions, ...groups } = manifest;
|
||||
const declaredExports = Object.keys(pkg.exports || {}).sort();
|
||||
const classifiedExports = Object.values(groups)
|
||||
.flatMap((group) => group.exports)
|
||||
@@ -34,7 +43,7 @@ export async function checkPackageBoundaries(root) {
|
||||
}
|
||||
if (
|
||||
!Array.isArray(group.modules) ||
|
||||
!group.modules.length ||
|
||||
(!write && !group.modules.length) ||
|
||||
!Array.isArray(group.external)
|
||||
) {
|
||||
throw new Error(`Invalid package boundary: ${name}`);
|
||||
@@ -42,6 +51,7 @@ export async function checkPackageBoundaries(root) {
|
||||
// A module entry ending in "/" owns every file under that directory.
|
||||
const allowed = new Set();
|
||||
const allowedDirectories = [];
|
||||
const modulePaths = new Map();
|
||||
const owns = (id) =>
|
||||
allowed.has(id) || allowedDirectories.some((dir) => id.startsWith(dir));
|
||||
for (const module of group.modules) {
|
||||
@@ -53,7 +63,13 @@ export async function checkPackageBoundaries(root) {
|
||||
) {
|
||||
throw new Error(`Boundary modules must be repository paths: ${name}`);
|
||||
}
|
||||
const resolved = await realpath(path.join(root, module));
|
||||
let resolved;
|
||||
try {
|
||||
resolved = await realpath(path.join(root, module));
|
||||
} catch (error) {
|
||||
if (write && error.code === 'ENOENT') continue;
|
||||
throw error;
|
||||
}
|
||||
const relative = path.relative(root, resolved);
|
||||
if (
|
||||
relative === '..' ||
|
||||
@@ -67,6 +83,7 @@ export async function checkPackageBoundaries(root) {
|
||||
} else {
|
||||
allowed.add(normalizePath(resolved));
|
||||
}
|
||||
modulePaths.set(module, normalizePath(resolved));
|
||||
}
|
||||
for (const external of group.external) {
|
||||
if (
|
||||
@@ -92,7 +109,7 @@ export async function checkPackageBoundaries(root) {
|
||||
if (
|
||||
typeof target !== 'string' ||
|
||||
!target.startsWith('./') ||
|
||||
!owns(normalizePath(path.resolve(root, target)))
|
||||
(!write && !owns(normalizePath(path.resolve(root, target))))
|
||||
) {
|
||||
throw new Error(`Export must point to an owned module: ${key}`);
|
||||
}
|
||||
@@ -119,7 +136,7 @@ export async function checkPackageBoundaries(root) {
|
||||
// template blocks belong to the component file itself.
|
||||
if (info.id.startsWith('\0')) return;
|
||||
const id = info.id.split('?')[0];
|
||||
if (!owns(id)) {
|
||||
if (!write && !owns(id)) {
|
||||
throw new Error(
|
||||
`Package boundary ${name} imports an unowned module: ${path.relative(root, id)}`,
|
||||
);
|
||||
@@ -148,7 +165,44 @@ export async function checkPackageBoundaries(root) {
|
||||
},
|
||||
},
|
||||
});
|
||||
reports.push({ name, exports: input.length, modules: seen.size });
|
||||
if (write) {
|
||||
const reached = [...seen];
|
||||
const kept = group.modules.filter((module) => {
|
||||
const resolved = modulePaths.get(module);
|
||||
return module.endsWith('/')
|
||||
? reached.some((id) => id.startsWith(`${resolved}/`))
|
||||
: seen.has(resolved);
|
||||
});
|
||||
const removed = group.modules.filter((module) => !kept.includes(module));
|
||||
const added = reached
|
||||
.filter((id) => !owns(id))
|
||||
.map((id) => {
|
||||
const relative = path.relative(root, id);
|
||||
if (
|
||||
!path.isAbsolute(id) ||
|
||||
relative === '..' ||
|
||||
relative.startsWith(`..${path.sep}`) ||
|
||||
path.isAbsolute(relative)
|
||||
) {
|
||||
throw new Error(`Boundary module escapes repository: ${name}`);
|
||||
}
|
||||
return normalizePath(relative);
|
||||
})
|
||||
.sort();
|
||||
group.modules = [...kept, ...added];
|
||||
if (added.length || removed.length)
|
||||
reports.push({ name, added, removed });
|
||||
} else {
|
||||
reports.push({ name, exports: input.length, modules: seen.size });
|
||||
}
|
||||
}
|
||||
if (write && reports.length) {
|
||||
const file = path.join(root, 'scripts/package-boundaries.json');
|
||||
const formatted = await prettier.format(JSON.stringify(manifest), {
|
||||
...(await prettier.resolveConfig(file)),
|
||||
filepath: file,
|
||||
});
|
||||
await writeFile(file, formatted);
|
||||
}
|
||||
return reports;
|
||||
}
|
||||
@@ -158,13 +212,25 @@ const invoked = process.argv[1]
|
||||
: '';
|
||||
if (import.meta.url === invoked) {
|
||||
try {
|
||||
const reports = await checkPackageBoundaries(
|
||||
fileURLToPath(new URL('../', import.meta.url)),
|
||||
);
|
||||
for (const report of reports)
|
||||
console.log(
|
||||
`Checked ${report.name}: ${report.exports} exports, ${report.modules} owned modules.`,
|
||||
);
|
||||
const root = fileURLToPath(new URL('../', import.meta.url));
|
||||
if (process.argv.includes('--write')) {
|
||||
const reports = await writePackageBoundaries(root);
|
||||
for (const { name, added, removed } of reports) {
|
||||
console.log(
|
||||
`Updated ${name}: ${added.length} added, ${removed.length} removed.`,
|
||||
);
|
||||
if (added.length + removed.length <= 20) {
|
||||
for (const module of added) console.log(` + ${module}`);
|
||||
for (const module of removed) console.log(` - ${module}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const reports = await checkPackageBoundaries(root);
|
||||
for (const report of reports)
|
||||
console.log(
|
||||
`Checked ${report.name}: ${report.exports} exports, ${report.modules} owned modules.`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error.message);
|
||||
process.exitCode = 1;
|
||||
|
||||
Reference in new issue
Block a user