Building and Publishing the npm Package
Explorbot develops on Bun but ships to npm as a Node.js-compatible package. This page covers how the build works and how to publish it.
Prerequisites
Section titled “Prerequisites”- Bun (for development and running the build)
- Node.js >= 24 (for verifying the build output)
- npm account with publish access to
explorbotpackage
How the Build Works
Section titled “How the Build Works”The source is TypeScript with .ts imports (enabled by allowImportingTsExtensions in tsconfig.json). Bun runs these natively, but Node.js needs compiled .js files.
The build runs the TypeScript compiler (tsc) with a dedicated tsconfig.build.json:
- TypeScript compilation - Compiles
src/,bin/, andboat/todist/, preserving the directory structure. - Import rewriting -
rewriteRelativeImportExtensionsrewrites.tsimports to.jsin the output (a TypeScript 5.7+ feature). - Type declarations -
scripts/build-types.tsemits.d.tsfiles for the library API (see Type Declarations). - Asset copying - Copies
rules/andassets/sample-files/intodist/so runtime path resolution works. - Shebang replacement - Replaces
#!/usr/bin/env bunwith#!/usr/bin/env nodein the CLI entry point.
Build Configuration
Section titled “Build Configuration”tsconfig.build.json extends the base tsconfig.json with:
| Option | Value | Purpose |
|---|---|---|
noEmit | false | Enable output (base config has true) |
outDir | dist | Compilation output directory |
rewriteRelativeImportExtensions | true | Rewrite .ts → .js in imports |
declaration | false | The JS build emits no .d.ts; declarations are built separately (see below) |
sourceMap | false | No source maps in the published package |
skipLibCheck | true | Skip type checking of dependencies |
The build skips type checking (--noCheck flag) because Bun is more permissive than tsc strict mode. Bun enforces type safety during development.
Package Structure
Section titled “Package Structure”After the build, the npm package contains:
dist/├── bin/explorbot-cli.js # CLI entry point (#!/usr/bin/env node)├── src/ # Compiled application code (.js) + type declarations (.d.ts)│ ├── index.js # Library entry point│ └── index.d.ts # Library type declarations├── boat/ # Compiled API tester module├── rules/ # Agent rule files (markdown)└── assets/sample-files/ # Sample files for testingType Declarations
Section titled “Type Declarations”declaration: true doesn’t work directly on this codebase: the Researcher agent is built from generic mixin factories (WithDeepAnalysis(Base) etc.) that return anonymous classes with private members, which TypeScript can’t serialize into a .d.ts (TS4094). Rather than refactor those hot-path agents, scripts/build-types.ts generates declarations from a transformed copy of the source:
- Copies
src/into a temporary tree, rewriting everyprivate/protectedclass modifier topublicat its exact AST position (members are preserved; only the visibility keyword changes, which removesTS4094). - Runs
tsc --emitDeclarationOnlyover the copy intodist/src/. - Rewrites
.ts/.tsxmodule specifiers to.jsin the emitted.d.tsso they resolve for consumers. - Deletes the temporary tree.
The transform touches only the intermediate copy — the shipped .js keeps its real private/protected visibility. The .d.ts types are exact (unions, option shapes, and return types are all preserved), so Node.js/TypeScript consumers get full type-checking. Bun consumers resolve the TypeScript source directly via the bun export condition.
Key package.json fields:
{ "bin": { "explorbot": "./dist/bin/explorbot-cli.js" }, "main": "dist/src/index.js", "types": "dist/src/index.d.ts", "exports": { ".": { "types": "./dist/src/index.d.ts", "bun": "./src/index.ts", "import": "./dist/src/index.js" } }, "files": [ "dist/", "src/**/*.ts", "src/**/*.tsx", "bin/**/*.ts", "boat/api-tester/src/**/*.ts", "boat/doc-collector/src/**/*.ts", "boat/doc-collector/bin/**/*.ts", "boat/doc-collector/package.json", "rules/", "assets/sample-files/" ], "engines": { "node": ">=24.0.0" }}Explorbot is both a CLI (bin) and a library (exports). The . entry point is src/index.ts, a side-effect-free barrel that re-exports the public API (ExplorBot, Plan, Test, and their types). The exports conditions are ordered so each consumer gets the right entry: types (the emitted .d.ts) for type-checking, bun (the TypeScript source) under Bun, and import (the compiled JS) under Node.js. This is why the source src/** files ship alongside dist/.
Building Locally
Section titled “Building Locally”# Build the npm packagebun run build:npm
# Verify the CLI works on Node.jsnode dist/bin/explorbot-cli.js --help
# Check what would be publishednpm pack --dry-runPublishing
Section titled “Publishing”GitHub Actions publishes automatically (see below), but you can also publish manually:
# Bump versionnpm version patch # or minor, major
# Build and publish (prepublishOnly runs build:npm automatically)npm publishKnown Limitations
Section titled “Known Limitations”- Type declarations are transform-generated - Declarations come from a transformed copy of the source (see Type Declarations), not from
tsc --declarationdirectly, because the mixin-based agents can’t emit declarations as written. The published.d.tstypes are exact; the workaround only concerns how they’re produced.
The test.yml workflow verifies the npm build on every push. On Node.js 24 it runs bun run build:npm, then the Node smoke tests: node --test tests/node/*.mjs. The publish.yml workflow additionally checks node dist/bin/explorbot-cli.js --help before publishing.
The publish.yml workflow publishes to npm when you push a version tag (v* or a bare 1.2.3-style tag). It overwrites the package version from the tag; tags containing beta, alpha, pre, or rc publish to the beta dist-tag instead of latest.