Putnami
DocsGitHub

Licensed under FSL-1.1-MIT

Getting Started
Concepts
How To
Build A Web App
Build An Api Service
Share Code Between Projects
Configure Your App
Add Persistence
Add Authentication
Add Background Jobs
Principles
Tooling & Workspace
Workspace Overview
Cli
Jobs & Commands
SDK
Error Handling
Extensions
Typescript
Go
Python
Docker
Ci
Frameworks
Typescript
OverviewWebReact RoutingForms And ActionsStatic FilesApiErrors And ResponsesConfigurationLoggingHttp And MiddlewareDependency InjectionPlugins And LifecycleSessionsAuthPersistenceEventsStorageCachingWebsocketsTestingHealth ChecksTelemetryProto GrpcSmart Client
Go
OverviewHttpDependency InjectionPlugins And LifecycleConfigurationSecurityPersistenceErrorsEventsStorageCachingLoggingTelemetryGrpcService ClientsValidationOpenapiTesting
Platform
  1. DocsSeparator
  2. Tooling & WorkspaceSeparator
  3. Typescript

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

  • Bun
  • Biome

Enable the extension

Install the package at the workspace root:

bun add @putnami/typescript

The 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 . --clear

build

Compile TypeScript packages for distribution using Bun's bundler.

Pipeline phases:

  • generate — pre-build hooks + generated artifacts
  • transpile — 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
  • compile is 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.compile mapping in .putnamirc.json
  • bin entries in package metadata

Default executable targets when --compile is enabled:

  • bun-linux-x64
  • bun-linux-arm64
  • bun-darwin-x64
  • bun-darwin-arm64

Version and Metadata:

The build automatically:

  • Reads the workspace version from .putnamirc.json and 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 gitInfo metadata to package.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-x64

test

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 . --coverage

lint

Format and lint using Biome.

Behavior:

  • Runs biome format then biome lint.
  • Config resolution: if --config-path is the default, it prefers biome.json in 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 . --fix

run

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.ts

serve

Start a dev server with hot reload.

Behavior:

  • If --entrypoint is not provided, resolves ./serve export from the project package.json.
  • If ./serve is missing, it runs putnami build <project> --fast and retries.

Options:

  • --entrypoint <path>
  • --watch <boolean> (default: true)
  • --port <number> (default: 3000)
  • --debug — Forward LOG_LEVEL=debug to 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 4000

Multi-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-tags
    • assets — upload compiled binaries to GCS
    • archives — upload extension .tar.gz archives 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) writes tag.<base-version>.json + tag.latest.json
    • pre-release uploads suffixed version path (for example v1.0.0-abc1234)
  • 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 to tag.latest.<artifact>.json

To enable channels for a project:

{
  "publish": ["npm", "assets", "archives"]
}

Options:

  • --stable — Publish stable version (strips pre-release suffix, tags as latest)
  • --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):

  1. CLI flags: --assets-bucket, --assets-prefix
  2. Environment: PUTNAMI_ASSETS_BUCKET, PUTNAMI_ASSETS_PREFIX
  3. Command defaults in .putnamirc.json:
{
  "options": {
    "@putnami/typescript:publish": {
      "assetsBucket": "putnami-registry",
      "assetsPrefix": "releases"
    }
  }
}

Compatibility keys are also accepted in command defaults:

  • PUTNAMI_ASSETS_BUCKET
  • PUTNAMI_ASSETS_PREFIX
  • PUTNAMI_ARCHIVES_BUCKET
  • PUTNAMI_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.json

Caching and dependencies

  • generate depends on ^generate and is cached with: src/**/*.ts, src/**/*.tsx, package.json. This is a quiet job (hidden from recap on success).
  • build depends on generate and ^build, cached with: src/**/*.ts, src/**/*.tsx, tsconfig.json, tsconfig.app.json, tsconfig.lib.json, package.json, doc/**/*.
  • test depends on generate and 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 --types and ^@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)

  1. Create src/pre-build/index.ts that exports preBuild with a command and optional args.
  2. Create the command script (e.g. src/pre-build/command.ts) that writes a JSON result to the --result path.
  3. 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-web

Generates:

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 test

Run immediately:

putnami serve my-app

typescript-server

A ready-to-run HTTP server using @putnami/application with file-based API routing.

putnami projects create my-api --template typescript-server

Generates:

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 test

Run immediately:

putnami serve my-api

typescript-library

A minimal TypeScript library with exports and a test.

putnami projects create my-lib --template typescript-library

Generates:

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 test

Examples

# 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.json

Next steps

  • Previous: Extensions
  • Next: Go

On this page

  • TypeScript Extension
  • External tools
  • Enable the extension
  • Jobs
  • generate
  • build
  • test
  • lint
  • run
  • serve
  • publish
  • Caching and dependencies
  • Pre-build hooks
  • Example hook
  • Create a new hook (quick steps)
  • Project Templates
  • typescript-web
  • typescript-server
  • typescript-library
  • Examples
  • Next steps