@bomb.sh/tools
Version:
The internal dev, build, and lint CLI for Bombshell projects
1 lines • 6.17 kB
Source Map (JSON)
{"version":3,"file":"surface.mjs","names":[],"sources":["../src/surface.ts"],"sourcesContent":["import { NodeHfs } from '@humanfs/node';\n\nconst hfs = new NodeHfs();\n\n/**\n * A `package.json` shape covering the publish-surface fields we read.\n * Everything is optional — real-world manifests are messy.\n */\ninterface PackageJson {\n\tmain?: unknown;\n\tmodule?: unknown;\n\tbin?: unknown;\n\texports?: unknown;\n}\n\n/**\n * Compute the public API surface of the package(s) rooted at `root`.\n *\n * The public surface is the set of source files consumers can import —\n * derived from `package.json` `exports`, `bin`, and `main`/`module` fields\n * by mapping published `dist/` paths back to their `src/` origins.\n *\n * Returns oxlint-style glob patterns relative to `root`. Packages without a\n * publish surface (apps, examples, private tooling) yield an empty array —\n * surface-scoped lint rules stay silent there.\n *\n * Monorepos are handled by convention: each `packages/<name>/package.json`\n * contributes its own surface, prefixed with `packages/<name>/`.\n */\nexport async function getPublicSurface(root: URL): Promise<string[]> {\n\tconst patterns = new Set<string>();\n\n\tconst roots = new Map<string, URL>([['', root]]);\n\tconst packagesDir = new URL('packages/', root);\n\tif (await hfs.isDirectory(packagesDir)) {\n\t\tfor await (const entry of hfs.list(packagesDir)) {\n\t\t\tif (!entry.isDirectory) continue;\n\t\t\tconst pkgRoot = new URL(`packages/${entry.name}/`, root);\n\t\t\tif (await hfs.isFile(new URL('package.json', pkgRoot))) {\n\t\t\t\troots.set(`packages/${entry.name}`, pkgRoot);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (const [prefix, pkgRoot] of roots) {\n\t\tconst pkg = (await hfs.json(new URL('package.json', pkgRoot))) as PackageJson | undefined;\n\t\tif (!pkg) continue;\n\t\tfor (const pattern of surfacePatterns(pkg)) {\n\t\t\tpatterns.add(prefix ? `${prefix}/${pattern}` : pattern);\n\t\t}\n\t}\n\n\treturn [...patterns];\n}\n\n/** Map a package.json's publish fields to source glob patterns. */\nfunction surfacePatterns(pkg: PackageJson): string[] {\n\tconst targets = new Set<string>();\n\n\tif (pkg.exports !== undefined) {\n\t\tfor (const value of walkExports(pkg.exports)) targets.add(value);\n\t} else {\n\t\t// Legacy fields only apply when `exports` is absent\n\t\tif (typeof pkg.main === 'string') targets.add(pkg.main);\n\t\tif (typeof pkg.module === 'string') targets.add(pkg.module);\n\t}\n\tif (typeof pkg.bin === 'string') {\n\t\ttargets.add(pkg.bin);\n\t} else if (pkg.bin && typeof pkg.bin === 'object') {\n\t\tfor (const value of Object.values(pkg.bin)) {\n\t\t\tif (typeof value === 'string') targets.add(value);\n\t\t}\n\t}\n\n\tconst patterns = new Set<string>();\n\tfor (const target of targets) {\n\t\tconst pattern = toSourcePattern(target);\n\t\tif (pattern) patterns.add(pattern);\n\t}\n\treturn [...patterns];\n}\n\n/** Collect every string leaf from an `exports` value of any shape. */\nfunction* walkExports(value: unknown): Generator<string> {\n\tif (typeof value === 'string') {\n\t\tyield value;\n\t} else if (Array.isArray(value)) {\n\t\tfor (const item of value) yield* walkExports(item);\n\t} else if (value && typeof value === 'object') {\n\t\tfor (const item of Object.values(value)) yield* walkExports(item);\n\t}\n}\n\n/**\n * Map a published file target back to its source glob pattern.\n *\n * - `./dist/index.mjs` → `src/index.ts`\n * - `./src/index.ts` (source-exporting packages) → `src/index.ts`\n *\n * Wildcard passthroughs (`\"./*\": \"./dist/*\"`) are skipped: they make files\n * importable by convention, but don't designate a curated public API, so\n * surface-scoped rules would fire on internal modules.\n *\n * Returns `undefined` for targets that aren't code entry points\n * (`./package.json`, asset paths, etc.).\n */\nfunction toSourcePattern(target: string): string | undefined {\n\tif (!target.startsWith('./')) return undefined;\n\tlet path = target.slice(2);\n\n\t// Published artifacts map back to src/; anything else must already be source\n\tif (path.startsWith('dist/')) {\n\t\tpath = `src/${path.slice('dist/'.length)}`;\n\t} else if (!path.startsWith('src/')) {\n\t\treturn undefined;\n\t}\n\n\tif (path.includes('*')) return undefined;\n\n\tconst source = path.replace(/\\.(d\\.[cm]ts|[cm]?[jt]s)$/, '.ts');\n\treturn source.endsWith('.ts') ? source : undefined;\n}\n"],"mappings":";;AAEA,MAAM,MAAM,IAAI,QAAQ;;;;;;;;;;;;;;;AA2BxB,eAAsB,iBAAiB,MAA8B;CACpE,MAAM,2BAAW,IAAI,IAAY;CAEjC,MAAM,wBAAQ,IAAI,IAAiB,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC;CAC/C,MAAM,cAAc,IAAI,IAAI,aAAa,IAAI;CAC7C,IAAI,MAAM,IAAI,YAAY,WAAW,GACpC,WAAW,MAAM,SAAS,IAAI,KAAK,WAAW,GAAG;EAChD,IAAI,CAAC,MAAM,aAAa;EACxB,MAAM,UAAU,IAAI,IAAI,YAAY,MAAM,KAAK,IAAI,IAAI;EACvD,IAAI,MAAM,IAAI,OAAO,IAAI,IAAI,gBAAgB,OAAO,CAAC,GACpD,MAAM,IAAI,YAAY,MAAM,QAAQ,OAAO;CAE7C;CAGD,KAAK,MAAM,CAAC,QAAQ,YAAY,OAAO;EACtC,MAAM,MAAO,MAAM,IAAI,KAAK,IAAI,IAAI,gBAAgB,OAAO,CAAC;EAC5D,IAAI,CAAC,KAAK;EACV,KAAK,MAAM,WAAW,gBAAgB,GAAG,GACxC,SAAS,IAAI,SAAS,GAAG,OAAO,GAAG,YAAY,OAAO;CAExD;CAEA,OAAO,CAAC,GAAG,QAAQ;AACpB;;AAGA,SAAS,gBAAgB,KAA4B;CACpD,MAAM,0BAAU,IAAI,IAAY;CAEhC,IAAI,IAAI,YAAY,KAAA,GACnB,KAAK,MAAM,SAAS,YAAY,IAAI,OAAO,GAAG,QAAQ,IAAI,KAAK;MACzD;EAEN,IAAI,OAAO,IAAI,SAAS,UAAU,QAAQ,IAAI,IAAI,IAAI;EACtD,IAAI,OAAO,IAAI,WAAW,UAAU,QAAQ,IAAI,IAAI,MAAM;CAC3D;CACA,IAAI,OAAO,IAAI,QAAQ,UACtB,QAAQ,IAAI,IAAI,GAAG;MACb,IAAI,IAAI,OAAO,OAAO,IAAI,QAAQ;OACnC,MAAM,SAAS,OAAO,OAAO,IAAI,GAAG,GACxC,IAAI,OAAO,UAAU,UAAU,QAAQ,IAAI,KAAK;CAAA;CAIlD,MAAM,2BAAW,IAAI,IAAY;CACjC,KAAK,MAAM,UAAU,SAAS;EAC7B,MAAM,UAAU,gBAAgB,MAAM;EACtC,IAAI,SAAS,SAAS,IAAI,OAAO;CAClC;CACA,OAAO,CAAC,GAAG,QAAQ;AACpB;;AAGA,UAAU,YAAY,OAAmC;CACxD,IAAI,OAAO,UAAU,UACpB,MAAM;MACA,IAAI,MAAM,QAAQ,KAAK,GAC7B,KAAK,MAAM,QAAQ,OAAO,OAAO,YAAY,IAAI;MAC3C,IAAI,SAAS,OAAO,UAAU,UACpC,KAAK,MAAM,QAAQ,OAAO,OAAO,KAAK,GAAG,OAAO,YAAY,IAAI;AAElE;;;;;;;;;;;;;;AAeA,SAAS,gBAAgB,QAAoC;CAC5D,IAAI,CAAC,OAAO,WAAW,IAAI,GAAG,OAAO,KAAA;CACrC,IAAI,OAAO,OAAO,MAAM,CAAC;CAGzB,IAAI,KAAK,WAAW,OAAO,GAC1B,OAAO,OAAO,KAAK,MAAM,CAAc;MACjC,IAAI,CAAC,KAAK,WAAW,MAAM,GACjC;CAGD,IAAI,KAAK,SAAS,GAAG,GAAG,OAAO,KAAA;CAE/B,MAAM,SAAS,KAAK,QAAQ,6BAA6B,KAAK;CAC9D,OAAO,OAAO,SAAS,KAAK,IAAI,SAAS,KAAA;AAC1C"}