TypeScript Extension
The @putnami/typescript extension provides the standard TypeScript toolchain for Putnami workspaces. It bundles, tests, linting, running, serving, and publishing for JS/TS projects using Bun and Biome.
Scope: All jobs are project-only. They require a project context (no workspace-level runs).
External tools
Enable the extension
Install the package at the workspace root:
bun add @putnami/typescriptThe extension is auto-discovered from workspace dependencies (see workspace configuration).
Jobs
generate
Run code generation hooks before building. This job is automatically invoked by build and test, but can also be run standalone for debugging or development.
Key steps:
- Discover pre-build hooks from plugin dependencies
- Execute hooks in dependency order
- Output generated exports and assets to
.gen/directory
Options:
--verbose— Show detailed generation output--clear— Clear existing generated artifacts before generating
Example:
bunx putnami generate .
bunx putnami generate . --clearbuild
Compile TypeScript packages for distribution using Bun's bundler.
Pipeline phases:
generate— pre-build hooks + generated artifactstranspile— JS package output (output/lib)types— declaration output (output/types)compile— standalone binary output (output/compile)
Default behavior:
- If no phase flags are provided, build runs
generate + transpile + types compileis opt-in via--compile- If any phase flag is provided, only selected phases run
Options:
- Phase control:
--generate--transpile--types--compile
--target <bun|browser|node>(default:bun)--compile-target <bun-*>(single executable target override, e.g.bun-linux-x64)--bundle <standalone|local|none>(default:none)--sourcemap <inline|external|none>(default:external)--minify <boolean>(default:true)--splitting— Enable code splitting for reduced bundle duplication (ESM format required)--assets <path>(multiple allowed)--publish-config-access <public|restricted>(default:public)--version-suffix <suffix>(overrides git-derived suffix)--release(build for stable release - skips version suffix)--fast(skip type generation)--skip-package-json--skip-artifacts--clear(wipe output folder first)
Compile targets:
Executable outputs are discovered from:
build.compilemapping in.putnamirc.jsonbinentries in package metadata
Default executable targets when --compile is enabled:
bun-linux-x64bun-linux-arm64bun-darwin-x64bun-darwin-arm64
Version and Metadata:
The build automatically:
- Reads the workspace version from
.putnamirc.jsonand adds a version suffix for pre-release builds:{baseVersion}-{hash}(e.g.,1.0.0-a1b2c3d) - The suffix is a 7-character hash of source files (from
filePatterns), ensuring deterministic versions - Resolves
workspace:*dependencies to actual versioned dependencies - Adds
gitInfometadata topackage.json(branch, sha, buildTime, isDirty, release)
Example:
# Pre-release build (adds version suffix)
bunx putnami build . --target bun
# Build standalone binaries for all default targets
bunx putnami build . --compile
# Build standalone binaries for a single target
bunx putnami build . --compile --compile-target bun-linux-x64test
Run tests using Bun's test runner.
Behavior:
- Discovers tests with
**/*.{test,spec}.{ts,tsx,js,jsx}. - If no tests are found, the job returns
SKIP(no failure). - Produces JUnit output and optional LCOV coverage in the job output directory.
Options (selected):
--timeout <ms>(default:5000)--coverage--test-name-pattern <regex>--test <filter>(short:-t)--only,--todo,--bail <n>,--concurrent <n>--update-snapshots--pass-with-no-tests <boolean>(default:true)
Example:
bunx putnami test . --coveragelint
Format and lint using Biome.
Behavior:
- Runs
biome formatthenbiome lint. - Config resolution: if
--config-pathis the default, it prefersbiome.jsonin the project, then workspace root, otherwise uses the built-in config.
Options:
--config-path <path>(default:node_modules/@putnami/typescript/config)--fix <boolean>(default:true)--max-diagnostics <number>(default:10)--diagnostic-level <error|warn|info|off>(default:warn)
Example:
bunx putnami lint . --fixrun
Execute a TypeScript entrypoint with Bun.
Options:
entrypoint(default:src/main.ts)--watch <boolean>(default:true)--inspect,--inspect-wait,--inspect-brk
Example:
bunx putnami run . src/scripts/migrate.tsserve
Start a dev server with hot reload.
Behavior:
- If
--entrypointis not provided, resolves./serveexport from the projectpackage.json. - If
./serveis missing, it runsputnami build <project> --fastand retries.
Options:
--entrypoint <path>--watch <boolean>(default:true)--port <number>(default:3000)--debug— ForwardLOG_LEVEL=debugto the application for debug-level logging--no-services— Disable multi-service discovery (serve only the target project)--inspect,--inspect-wait,--inspect-brk
Example:
bunx putnami serve . --port 4000Multi-service mode:
Declare runtime service dependencies in package.json using putnami.runsWith:
{
"putnami": {
"runsWith": ["catalogue-api", "profile-api"]
}
}When putnami serve web-app --watch is run, it automatically discovers, allocates ports, and starts all declared services. If runsWith is not declared, the system falls back to dependency graph traversal (looking for projects with ./serve exports).
Port conflict resolution: Before starting, the system checks whether each allocated port is already in use. If a conflict is found, you are prompted to choose a new port. The chosen port is saved to the project's package.json under options["@putnami/typescript:serve"].port, so future runs remember the assignment. In CI, the next available port is picked automatically.
Persisted port config (written automatically on conflict resolution):
{
"options": {
"serve": { "port": 4000 }
}
}Service auto-update: editing runsWith in package.json during a live watch session automatically adds or removes services without restarting the session.
See --no-services to disable multi-service discovery.
publish
Publish built packages, optional binary assets, and optional extension archives.
Behavior:
- Runs subtasks (when enabled by project publish channels):
npm— publish package(s) and manage dist-tagsassets— upload compiled binaries to GCSarchives— upload extension.tar.gzarchives to GCS
npm:- reads version from build output package metadata
- skips private packages (
"private": true) - updates tags if the version already exists
- pre-release default tag: channel-derived (
main/master -> canary, branches ->dev) - stable (
--stable) tag default:latest
assets:- uploads immutable versioned path on every publish:
.../v<version>/ - writes tag manifests with binary maps:
tag.<version>.json+tag.<dist-tag>.json - stable (
--stable) writestag.<base-version>.json+tag.latest.json - pre-release uploads suffixed version path (for example
v1.0.0-abc1234)
- uploads immutable versioned path on every publish:
archives:- packages extension payloads into target archives (
<artifact>-<platform>-<target>.tar.gz) - uploads immutable versioned path on every publish:
.../<normalized-project-name>/v<version>/ - writes artifact-scoped manifests under the project segment:
.../<normalized-project-name>/tag.<selector>.<artifact>.json - stable (
--stable) defaults totag.latest.<artifact>.json
- packages extension payloads into target archives (
To enable channels for a project:
{
"publish": ["npm", "assets", "archives"]
}Options:
--stable— Publish stable version (strips pre-release suffix, tags aslatest)--dist-tag <tag>— Override auto-detected npm dist-tag--also-branch-tag— Also publish branch-derived tag manifest selector--access <public|restricted>(default:public)--otp <code>--registry <url>— Custom registry URL--assets-bucket <bucket>— GCS bucket for binaries--assets-prefix <prefix>— GCS object prefix (default:releases)--archives-bucket <bucket>— GCS bucket for extension archives--archives-prefix <prefix>— GCS root prefix for extension archives (default:releases)--dry-run
Asset config sources (highest to lowest priority):
- CLI flags:
--assets-bucket,--assets-prefix - Environment:
PUTNAMI_ASSETS_BUCKET,PUTNAMI_ASSETS_PREFIX - Command defaults in
.putnamirc.json:
{
"options": {
"@putnami/typescript:publish": {
"assetsBucket": "putnami-registry",
"assetsPrefix": "releases"
}
}
}Compatibility keys are also accepted in command defaults:
PUTNAMI_ASSETS_BUCKETPUTNAMI_ASSETS_PREFIXPUTNAMI_ARCHIVES_BUCKETPUTNAMI_ARCHIVES_PREFIX
Note: archive publish paths always include a normalized project segment; assetsPrefix is not used for archive pathing.
Example:
# Pre-release publish
bunx putnami build .
bunx putnami publish .
# → @putnami/mypackage@1.0.0-abc1234 with tag canary (main) or dev (branches)
# → binaries uploaded to gs://<bucket>/<prefix>/v1.0.0-abc1234
# → manifests updated: gs://<bucket>/<prefix>/tag.1.0.0-abc1234.json and gs://<bucket>/<prefix>/tag.canary.json (or tag.dev.json)
# Stable publish
bunx putnami build .
bunx putnami publish . --stable
# → @putnami/mypackage@1.0.0 with tag latest
# → binaries uploaded to gs://<bucket>/<prefix>/v1.0.0
# → manifests updated: gs://<bucket>/<prefix>/tag.1.0.0.json and gs://<bucket>/<prefix>/tag.latest.jsonCaching and dependencies
- generate depends on
^generateand is cached with:src/**/*.ts,src/**/*.tsx,package.json. This is a quiet job (hidden from recap on success). - build depends on
generateand^build, cached with:src/**/*.ts,src/**/*.tsx,tsconfig.json,tsconfig.app.json,tsconfig.lib.json,package.json,doc/**/*. - test depends on
generateand is cached with:src/**/*.ts,src/**/*.tsx,test/**/*.ts,test/**/*.tsx,package.json. - lint caches on:
src/**/*.ts,src/**/*.tsx,biome.json,package.json. - serve is never cached and depends on
build --fast. - publish depends on
build --transpile --typesand^@putnami/typescript:publish.
Pre-build hooks
Dependencies can contribute pre-build hooks to generate sources or assets before compilation. Hooks are discovered from plugin packages via the pre-build export and must return a command plus optional args. The hook command receives context and is expected to write a JSON result with exports and assets for the build pipeline.
Hook CLI arguments: the runner passes --context <path> and --result <path> to your command. Use --result to write the JSON output that the build will consume.
Expected JSON shape:
{
"exports": {
"./generated": ""./.gen/main.ts"
},
"assets": [
{ "src": ".gen/schema.json", "dest": "schema.json" }
]
}Example hook
// packages/my-plugin/src/pre-build/index.ts
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const commandPath = join(dirname(fileURLToPath(import.meta.url)), 'command.ts');
export const preBuild = {
command: 'bun',
args: ['run', commandPath],
};// packages/my-plugin/src/pre-build/command.ts
import { writeFileSync } from 'node:fs';
const resultPath = process.argv[process.argv.indexOf('--result') + 1];
writeFileSync(
resultPath,
JSON.stringify({
exports: {
'./generated': './.gen/main.ts',
},
assets: [
{ src: '.gen/schema.json', dest: 'schema.json' },
],
}),
);Create a new hook (quick steps)
- Create
src/pre-build/index.tsthat exportspreBuildwith acommandand optionalargs. - Create the command script (e.g.
src/pre-build/command.ts) that writes a JSON result to the--resultpath. - Add an export in
package.json:
{
"exports": {
"./pre-build": "./src/pre-build/index.ts"
}
}Project Templates
The TypeScript extension ships three project templates for quick scaffolding:
typescript-web
A React SSR web application with file-based routing, layouts, and client-side hydration.
putnami projects create my-app --template typescript-webGenerates:
packages/my-app/
├── package.json # @putnami/application + @putnami/react deps
├── tsconfig.json
├── src/
│ ├── main.ts # Application setup (HTTP, logger, React plugins)
│ ├── serve.ts # Entry point
│ ├── app/
│ │ ├── layout.tsx # Root layout with <Outlet />
│ │ ├── page.tsx # Home page
│ │ └── not-found.tsx # 404 page
│ └── components/
│ └── counter.tsx # Client-side interactive component
└── test/
└── app.test.ts # Integration testRun immediately:
putnami serve my-apptypescript-server
A ready-to-run HTTP server using @putnami/application with file-based API routing.
putnami projects create my-api --template typescript-serverGenerates:
packages/my-api/
├── package.json # @putnami/application + @putnami/runtime deps
├── tsconfig.json
├── src/
│ ├── main.ts # Application setup (HTTP, logger, API plugins)
│ ├── serve.ts # Entry point
│ └── api/
│ └── get.ts # Root GET endpoint returning { Hello: "World" }
└── test/
└── api.test.ts # Integration testRun immediately:
putnami serve my-apitypescript-library
A minimal TypeScript library with exports and a test.
putnami projects create my-lib --template typescript-libraryGenerates:
packages/my-lib/
├── package.json # Library with exports field
├── tsconfig.json
├── src/
│ ├── index.ts # Re-exports
│ └── hello.ts # hello(name) function
└── test/
└── hello.test.ts # Unit testExamples
# Build a package (pre-release with version suffix)
bunx putnami build .
# Fast dev build (skip types)
bunx putnami build . --fast
# Lint and fix
bunx putnami lint . --fix
# Run tests with coverage
bunx putnami test . --coverage
# Run a script
bunx putnami run . src/scripts/migrate.ts
# Start dev server on port 4000
bunx putnami serve . --port 4000
# CI workflow: build and publish pre-release
bunx putnami build .
bunx putnami publish .
# → @putnami/mypackage@1.0.0-abc1234 with tag canary (main) or dev (branches)
# → binaries uploaded to gs://<bucket>/<prefix>/v1.0.0-abc1234
# → manifests updated: gs://<bucket>/<prefix>/tag.1.0.0-abc1234.json and gs://<bucket>/<prefix>/tag.canary.json (or tag.dev.json)
# Stable release workflow
bunx putnami build .
bunx putnami publish . --stable
# → @putnami/mypackage@1.0.0 with tag latest
# → binaries uploaded to gs://<bucket>/<prefix>/v1.0.0
# → manifests updated: gs://<bucket>/<prefix>/tag.1.0.0.json and gs://<bucket>/<prefix>/tag.latest.jsonNext steps
- Previous: Extensions
- Next: Go