feat(npm): expose types and runtime exports from the main shim package#343
feat(npm): expose types and runtime exports from the main shim package#343ayu-exorcist wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Updates the npm packaging script to expose runtime entrypoints and TypeScript types for both platform-specific bundles and the main “shim” package, including a generated proxy module that forwards loading to the correct optional dependency.
Changes:
- Add
main/types/exportsfields to generatedpackage.jsonfiles (platform bundles + main shim). - Copy
.d.tsartifacts into the main shim so types can be imported from@scope/codegraph. - Generate a runtime
index.jsproxy that requires the platform-specific package based onprocess.platform/process.arch.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Before this change, consumers had to hard-code a platform-specific
package path to get types or runtime imports:
import type { CodeGraph } from "@colbymchenry/codegraph-win32-x64/lib/dist/index"
This broke on every platform except Windows because the win32 package
is not installed as an optionalDependency on other OSs.
What changed:
1. Platform packages now declare main/types/exports so they can be
imported directly without deep paths.
2. The main shim package now ships:
- lib/dist/*.d.ts — type declarations copied from the first
platform bundle (all platforms emit identical .d.ts from the
same TS source).
- index.js — a CJS runtime proxy that resolves the
matching platform package at require()-time and re-exports
everything.
3. The main shim package.json now declares:
- "main": "index.js"
- "types": "lib/dist/index.d.ts"
- "exports" with types/require/default conditions
Consumers can now write:
import { CodeGraph, Node } from "@colbymchenry/codegraph";
...and it works on every supported platform (darwin, linux, win32,
arm64, x64) without any tsconfig paths workaround.
|
@copilot-pull-request-reviewer All 5 points addressed in the amended commit. Summary:
All changes are in "scripts/pack-npm.sh" only (amended commit "c338bfa"). |
feat(npm): expose types and runtime exports from the main shim package
Closes #342
Summary
This PR makes
@colbymichenry/codegraphimportable as a library — for both types and runtime — on every supported platform, without requiring consumers to hard-code a platform-specific package path.Before (only works on Windows x64):
After (works everywhere):
Changes
The only file modified is
scripts/pack-npm.sh(the release script that assembles published npm packages from platform bundles). No source code changes — this is purely a packaging fix.1. Platform packages now self-describe
Each
@colbymchenry/codegraph-<target>now declares:{ "main": "lib/dist/index.js", "types": "lib/dist/index.d.ts", "exports": { ".": { "types": "./lib/dist/index.d.ts", "default": "./lib/dist/index.js" } } }This allows deep-path-free imports from the platform package directly:
2. Main shim now ships type declarations
During
pack-npm.sh, all.d.tsand.d.ts.mapfiles from the first platform bundle are copied intomain/lib/dist/. Because every platform bundle is compiled from the same TypeScript source, the declarations are identical across platforms.3. Main shim now ships a runtime proxy
A new
index.jsis generated in the main shim:This re-exports the entire platform package API through the canonical package name.
4. Main shim
package.jsonnow declares proper entry points{ "main": "index.js", "types": "lib/dist/index.d.ts", "exports": { ".": { "types": "./lib/dist/index.d.ts", "require": "./index.js", "default": "./index.js" } }, "files": ["index.js", "npm-shim.js", "README.md", "lib"] }Design Decisions
We evaluated several approaches for both type export and runtime export. Below is the full decision matrix.
Type Export: How do consumers get
import type { ... } from "@colbymchenry/codegraph"?Option A — Copy
.d.tsfrom a platform bundle into the main shim (chosen)How:
pack-npm.shcopieslib/dist/*.d.tsfrom the first extracted bundle intomain/lib/dist/.Pros:
tscbuild that produces the bundle..d.ts).tsc,ts-node, Vite, Next.js, etc.Cons:
src/andtsconfig.json).#ifdef-style code were ever introduced, the copied.d.tscould drift.Option B — Make each platform package declare its own
typesHow: Add
"types": "lib/dist/index.d.ts"to each@colbymchenry/codegraph-<target>package.json.Pros:
Cons:
Verdict: We did this too (it's part of this PR), but it only solves half the problem. It is a prerequisite, not a replacement, for Option A.
Option C — Hand-write a separate
.d.tsfile in the repoHow: Maintain
src/index.d.ts(or similar) by hand, and ship it in the main shim.Pros:
Cons:
.d.tsupdate.CodeGraphclass + ~20 interfaces + re-exports from submodules).Verdict: Rejected — the maintenance cost outweighs the benefit for a package whose entire API is effectively public.
Option D — Use TypeScript
typesVersionsorpeerDependenciestrickeryHow: Declare
@colbymchenry/codegraph-darwin-arm64(or similar) as apeerDependencyor usetypesVersionsto redirect resolution.Pros:
Cons:
typesVersionsis deprecated-ish and poorly supported by modern tooling (Vite, esbuild, pnpm).peerDependencieswould force consumers to install a platform package explicitly, defeating the purpose ofoptionalDependencies.Verdict: Rejected — over-engineered for this use case.
Runtime Export: How do consumers get
import { CodeGraph } from "@colbymchenry/codegraph"?Option A — CJS proxy (
index.js+require) (chosen)How: Generate a small CommonJS file that
requires the matching platform package and re-exports it viamodule.exports.Pros:
awaitneeded for the initial import.exportsmap withrequireanddefaultconditions covers bothrequire()and dynamicimport().Cons:
@rollup/plugin-commonjs, Rollup, some Deno configs) may struggle with CJS re-exports unless they use thedefaultcondition.module.exportsare interpreted as a default export by some tools).Verdict: Chosen — CodeGraph's consumer base is 100% Node.js-based today (CLI tools, MCP servers, Pi extensions). The simplicity and zero-overhead trade-off is correct for this audience.
Option B — ESM + CJS dual proxy
How: Generate both
index.js(CJS) andindex.mjs(ESM) that each delegate to the platform package.Pros:
Cons:
index.mjs(or use a build step to generate it).index.mjsmust be regenerated or it will be missing.Verdict: Deferred — can be added later without breaking changes by expanding the
exportsmap:Option C — No proxy; consumers dynamically import the platform package
How: Do nothing in the main shim. Consumers write:
Pros:
Cons:
await import(...)) are not statically analyzable by TypeScript or bundlers — types and tree-shaking break.Verdict: Rejected — the whole point of this PR is to let consumers use the simple, canonical import.
Option D — Conditional
exportspointing to platform packagesHow: Use Node.js conditional exports with custom conditions per platform:
Pros:
Cons:
"darwin"or"arm64"export condition.node_modulespath is not guaranteed to be flat (pnpm, yarn PnP, etc.).Verdict: Rejected — Node.js's exports conditions are not designed for this.
Why this specific combination?
.d.tsfrom bundleindex.js)exports.defaultconditionimport()in ESM projects; true ESM proxy can be added lateroptionalDependenciesnpm-shim.jsnpx codegraph) is completely unaffectedBackward Compatibility
npx @colbymchenry/codegraph,npm i -g @colbymchenry/codegraph): Unchanged. Thebinfield still points tonpm-shim.js.@colbymchenry/codegraph-win32-x64/lib/dist/index): Still work. Platform packages still contain the same files at the same paths..d.tsfiles (only declarations, no JS runtime). This is negligible compared to the ~150 MB platform bundles.Testing
The release script (
scripts/pack-npm.sh) was validated with a minimal mock bundle:The actual release pipeline (
.github/workflows/release.yml) callsscripts/pack-npm.shunchanged, so the fix will be picked up automatically on the next release.