Skip to content

Building

The build step transforms TypeScript source code into distributable JavaScript. The raw .ts files in src/ can’t be published or executed directly by Node.js — they need to be compiled to .js, and type declarations (.d.ts) need to be generated so consumers get autocompletion.

tsup handles this. It’s a TypeScript bundler powered by esbuild, which compiles orders of magnitude faster than the standard TypeScript compiler (tsc). tsup also handles ESM output correctly without extra configuration and generates both JavaScript and type declarations in a single step.

The build produces two entry points:

  • dist/cli.js — the CLI binary. This is what the bin field in package.json points to, and what runs when a user types semtest. It contains the Commander command definitions and orchestrates the full pipeline.
  • dist/index.js — the public API. This is what consumers get when they import { defineConfig } from "@thulanek/semtest-runner". It re-exports the config helper and types for programmatic use.

Code splitting is enabled, so shared modules (like the config schema, which both entry points need) are bundled into a separate chunk and imported by both — no duplication.

The config lives at packages/semtest-runner/tsup.config.ts:

import { defineConfig } from "tsup";
export default defineConfig({
entry: ["src/cli.ts", "src/index.ts"],
format: ["esm"],
dts: true,
clean: true,
target: "node20",
splitting: true,
});
OptionValuePurpose
entrycli.ts, index.tsTwo entry points — CLI binary and public API
formatesmESM-only output (no CJS)
dtstrueGenerates .d.ts declaration files
cleantrueClears dist/ before each build
targetnode20Minimum Node version
splittingtrueCode-splits shared modules between entry points

Key compiler options from packages/semtest-runner/tsconfig.json:

OptionValue
targetES2022
moduleESNext
moduleResolutionbundler
stricttrue
declarationtrue
isolatedModulestrue

The root turbo.json defines three tasks:

{
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"test": {
"dependsOn": ["build"]
}
}
}
  • build: Topologically ordered — dependencies build before dependents. Cached by dist/** output.
  • dev: Not cached, runs persistently (watch mode).
  • test: Runs after build completes.
Terminal window
# Build everything
pnpm turbo run build
# Build only the main package
pnpm turbo run build --filter=@thulanek/semtest-runner
# Watch mode (dev)
pnpm turbo run dev --filter=@thulanek/semtest-runner