@bomb.sh/tools
Version:
The internal dev, build, and lint CLI for Bombshell projects
84 lines (83 loc) • 3.53 kB
JavaScript
import { NodeHfs } from "@humanfs/node";
//#region src/surface.ts
const hfs = new NodeHfs();
/**
* Compute the public API surface of the package(s) rooted at `root`.
*
* The public surface is the set of source files consumers can import —
* derived from `package.json` `exports`, `bin`, and `main`/`module` fields
* by mapping published `dist/` paths back to their `src/` origins.
*
* Returns oxlint-style glob patterns relative to `root`. Packages without a
* publish surface (apps, examples, private tooling) yield an empty array —
* surface-scoped lint rules stay silent there.
*
* Monorepos are handled by convention: each `packages/<name>/package.json`
* contributes its own surface, prefixed with `packages/<name>/`.
*/
async function getPublicSurface(root) {
const patterns = /* @__PURE__ */ new Set();
const roots = /* @__PURE__ */ new Map([["", root]]);
const packagesDir = new URL("packages/", root);
if (await hfs.isDirectory(packagesDir)) for await (const entry of hfs.list(packagesDir)) {
if (!entry.isDirectory) continue;
const pkgRoot = new URL(`packages/${entry.name}/`, root);
if (await hfs.isFile(new URL("package.json", pkgRoot))) roots.set(`packages/${entry.name}`, pkgRoot);
}
for (const [prefix, pkgRoot] of roots) {
const pkg = await hfs.json(new URL("package.json", pkgRoot));
if (!pkg) continue;
for (const pattern of surfacePatterns(pkg)) patterns.add(prefix ? `${prefix}/${pattern}` : pattern);
}
return [...patterns];
}
/** Map a package.json's publish fields to source glob patterns. */
function surfacePatterns(pkg) {
const targets = /* @__PURE__ */ new Set();
if (pkg.exports !== void 0) for (const value of walkExports(pkg.exports)) targets.add(value);
else {
if (typeof pkg.main === "string") targets.add(pkg.main);
if (typeof pkg.module === "string") targets.add(pkg.module);
}
if (typeof pkg.bin === "string") targets.add(pkg.bin);
else if (pkg.bin && typeof pkg.bin === "object") {
for (const value of Object.values(pkg.bin)) if (typeof value === "string") targets.add(value);
}
const patterns = /* @__PURE__ */ new Set();
for (const target of targets) {
const pattern = toSourcePattern(target);
if (pattern) patterns.add(pattern);
}
return [...patterns];
}
/** Collect every string leaf from an `exports` value of any shape. */
function* walkExports(value) {
if (typeof value === "string") yield value;
else if (Array.isArray(value)) for (const item of value) yield* walkExports(item);
else if (value && typeof value === "object") for (const item of Object.values(value)) yield* walkExports(item);
}
/**
* Map a published file target back to its source glob pattern.
*
* - `./dist/index.mjs` → `src/index.ts`
* - `./src/index.ts` (source-exporting packages) → `src/index.ts`
*
* Wildcard passthroughs (`"./*": "./dist/*"`) are skipped: they make files
* importable by convention, but don't designate a curated public API, so
* surface-scoped rules would fire on internal modules.
*
* Returns `undefined` for targets that aren't code entry points
* (`./package.json`, asset paths, etc.).
*/
function toSourcePattern(target) {
if (!target.startsWith("./")) return void 0;
let path = target.slice(2);
if (path.startsWith("dist/")) path = `src/${path.slice(5)}`;
else if (!path.startsWith("src/")) return;
if (path.includes("*")) return void 0;
const source = path.replace(/\.(d\.[cm]ts|[cm]?[jt]s)$/, ".ts");
return source.endsWith(".ts") ? source : void 0;
}
//#endregion
export { getPublicSurface };
//# sourceMappingURL=surface.mjs.map