This document describes the build system, continuous integration, and deployment infrastructure for the Zod library. It covers the build pipeline that transforms TypeScript source code into distributable packages, the pre-commit hooks that enforce code quality, and the automated CI/CD workflows that test and publish releases.
For information about the test infrastructure and test matrix, see Testing Strategy. For details on package structure and exports, see Package Structure and Exports.
Zod uses pnpm as its package manager, specified in package.json4 with version pinning via packageManager: "[email protected]". The project is configured as a private monorepo workspace (package.json2) with the main library located in packages/zod/.
The workspace uses pnpm workspaces to manage dependencies across multiple packages, allowing cross-package development with workspace protocol references like "zod": "workspace:*" (package.json49).
Sources: package.json1-90
The build system transforms TypeScript source code in packages/zod/src/ into multiple output formats. The primary build tool is tshy (TypeScript hybrid build tool), with additional bundlers available for specific use cases.
Build File Flow
The main build process uses supershy (package.json41), which includes the tshy tool that handles dual ESM/CJS output generation with proper TypeScript definitions. The build is triggered via the build script (package.json73):
This command:
-r) across workspace packageszod packagebuild script defined in packages/zod/package.jsonThe tshy build process reads configuration from:
packages/zod/tsconfig.json - TypeScript compiler optionspackages/zod/package.json - Export maps and build targetsAdditional bundlers are available for testing and development scenarios:
| Bundler | Package Version | Script | Purpose |
|---|---|---|---|
esbuild | ^0.25.5 (package.json29) | bundle:esbuild (package.json86) | Fast single-file bundling for scratch builds |
rollup | ^4.39.0 (package.json39) | bundle:rollup (package.json85) | Module bundler with rollup.config.js |
rolldown | 1.0.0-beta.18 (package.json38) | - | Next-generation Rust-based bundler (experimental) |
zshy | ^0.4.2 (package.json51) | - | Specialized hybrid build tool |
The build process generates multiple output formats to support different module systems:
| Format | File Pattern | Target Environment |
|---|---|---|
| ES Modules | packages/zod/**/*.js | Modern JavaScript environments, bundlers |
| CommonJS | packages/zod/**/*.cjs | Node.js, legacy environments |
| ESM Type Definitions | packages/zod/**/*.d.ts | TypeScript with ESM imports |
| CJS Type Definitions | packages/zod/**/*.d.cts | TypeScript with CommonJS requires |
Intermediate build artifacts are stored in temporary directories excluded from version control:
.tshy/ - tshy build cache (.gitignore18).tshy-build/ - tshy intermediate build files (.gitignore19)Final outputs are placed directly in the packages/zod/ directory according to the exports map in packages/zod/package.json.
Key build-related scripts from package.json66-89:
| Script | Command | Execution Context |
|---|---|---|
build | pnpm run -r --filter zod build | Builds zod package via workspace recursion |
prepublishOnly | pnpm run test && pnpm run build | Pre-publish validation gate (package.json76) |
postbuild | biome format --write . | Post-build formatting (package.json83) |
bundle:rollup | rollup -c | Bundle using rollup.config.js (package.json85) |
bundle:esbuild | esbuild --bundle ./scratch/input.ts --outfile=./scratch/out_esbuild.js --bundle --format=esm | Bundle scratch files (package.json86) |
clean | pnpm run -r clean | Clean all package build artifacts (package.json72) |
Sources: package.json29-51 package.json66-89 .gitignore17-19
The complete build pipeline from source to distribution:
Step-by-step Build Process
The postbuild script (package.json83) ensures all generated files conform to the project's formatting standards by running Biome across the entire output.
Sources: package.json66-89
Zod uses Husky (package.json32) to manage Git hooks, with lint-staged (package.json34) to run linters and formatters on staged files before commit. The hooks are initialized via the prepare script (package.json82):
The lint-staged configuration (package.json53-65) defines three file type handlers:
| Tool | Version | Purpose |
|---|---|---|
| Biome | ^1.9.4 (package.json20) | Fast formatter and linter for TS/JS/JSON |
| Prettier | ^3.5.3 (package.json36) | Opinionated code formatter for Markdown |
Biome replaces ESLint and Prettier for TypeScript files, providing faster performance. Prettier is retained specifically for Markdown formatting.
Developers can also run quality checks manually:
| Script | Command | Purpose |
|---|---|---|
format | biome check --write . | Format all files |
format:check | biome check . | Check formatting without changes |
lint | biome lint --write . | Lint and fix issues |
lint:check | biome lint . | Lint without fixes |
fix | pnpm run format && pnpm run lint | Format and lint everything |
Sources: package.json17-90
GitHub Actions Test Pipeline
The test workflow (.github/workflows/test.yml1-61) runs on every push to main or pull request to main/v4 branches (.github/workflows/test.yml3-10). It consists of three parallel jobs:
Tests across a matrix of Node.js and TypeScript versions:
The job executes:
Validates code formatting and linting:
pnpm format:check (.github/workflows/test.yml47)pnpm lint:check (.github/workflows/test.yml48)Detects circular dependencies using madge (v8.0.0, package.json19):
check:circular script (.github/workflows/test.yml60)v4/core, v3, and iso.ts from circular dependency checks (package.json88)madge --circular --extensions ts --exclude '(v4/core|v3|iso\\.ts)' packages/zod/srcThe exclusions allow intentional circular references between:
v4/core - Core type definitions that may reference each otherv3 - Legacy v3 implementation with separate dependency graphiso.ts - Isomorphic utilities that bridge environmentsSources: .github/workflows/test.yml50-60 package.json88
The release workflow (.github/workflows/release.yml1-115) triggers on pushes to main when (.github/workflows/release.yml4-11):
packages/zod/package.json changes (.github/workflows/release.yml9)packages/zod/src/** change (.github/workflows/release.yml10).github/workflows/release.yml itself changes (.github/workflows/release.yml11)Complete Release Pipeline with Code References
The release job requires specific permissions (.github/workflows/release.yml16-19):
| Permission | Access | Purpose |
|---|---|---|
contents | write | Create releases and tags |
pull-requests | read | Access PR information for changelog |
id-token | write | Generate provenance attestations |
Steps 1-8 (.github/workflows/release.yml21-38):
fetch-depth: 0) for version detectionpnpm installpackages/zod/The workflow uses JS-DevTools/npm-publish@v3 (.github/workflows/release.yml40-47) to publish to npm with the following configuration:
Provenance (.github/workflows/release.yml46) generates cryptographically signed attestations that link the published package to its source code and build process, enhancing supply chain security. This requires the id-token: write permission (.github/workflows/release.yml19).
The action outputs are used in subsequent steps:
steps.publish.outputs.type - Version type if published (patch, minor, major, premajor, etc.) or null if no version changesteps.publish.outputs.version - The published version number (e.g., 4.1.0)The decision logic uses this output to determine the release path (.github/workflows/release.yml50 .github/workflows/release.yml54 .github/workflows/release.yml59).
Sources: .github/workflows/release.yml16-47
If a new version was published to npm, the workflow also publishes to JSR (.github/workflows/release.yml49-51):
The --allow-slow-types flag permits types that may take longer to resolve, which is necessary for Zod's complex type system. The JSR CLI is installed as a dev dependency (package.json33).
| Registry | Package Name | Trigger Condition |
|---|---|---|
| npm | zod | Always (on main push) |
| JSR | @zod/zod | Only if version changed |
| npm (canary) | zod | Only if version unchanged |
Sources: .github/workflows/release.yml49-51 package.json33
When no version change is detected (!steps.publish.outputs.type at .github/workflows/release.yml59), the workflow automatically creates a canary release (.github/workflows/release.yml58-79):
Canary Version Generation Process
The canary version is generated in two steps at .github/workflows/release.yml62-63:
The --no-git-tag-version flag prevents Git tag creation, and the timestamp format %Y%m%dT%H%M%S generates versions like 4.1.0-canary.20250115T143052, allowing continuous deployment while maintaining semantic versioning compatibility.
The canary version is published with:
canary (.github/workflows/release.yml72)false (actually publishes, .github/workflows/release.yml70)Users can install canary releases with:
Sources: .github/workflows/release.yml58-79
For version releases (not canaries), the workflow generates a changelog using mikepenz/[email protected] (.github/workflows/release.yml89-99):
The changelog configuration is created dynamically (.github/workflows/release.yml82-87):
Configuration breakdown:
categories: [] - No categorization of commitstemplate - Simple header with uncategorized commits listpr_template - Format: - <merge_sha> <title>Environment variables for changelog generation:
last_tag - Retrieved via git describe --tags --abbrev=0 (.github/workflows/release.yml86)curr_commit - Retrieved via git rev-parse HEAD (.github/workflows/release.yml87)These are used as fromTag and toTag parameters (.github/workflows/release.yml96-97) to generate the commit list between releases.
The workflow creates a GitHub release using actions/create-release@v1 (.github/workflows/release.yml101-113):
Release configuration:
v (e.g., v4.1.0) using steps.publish.outputs.versiongithub.ref contextsteps.github_release.outputs.changelogfalse - Immediately publishedfalse - Marked as stable releaseThe contents: write permission (.github/workflows/release.yml17) is required for creating releases and tags.
Sources: .github/workflows/release.yml16-19 .github/workflows/release.yml81-113
The .gitignore1-25 file excludes build artifacts from version control:
| Pattern | Description |
|---|---|
dist | Distribution output directory |
.tshy | tshy build cache |
.tshy-build | tshy intermediate build files |
lib | Legacy library output |
node_modules | Package dependencies |
*.tgz | Package tarballs |
coverage | Test coverage reports |
scratch | Development scratch space |
After the build completes, the postbuild script (package.json83) automatically formats all generated files:
This ensures that generated code follows the project's style guidelines.
Sources: .gitignore1-25 package.json83
For local development, several convenience scripts are available:
| Script | Command | Purpose |
|---|---|---|
dev | tsx --conditions @zod/source | Run code with source conditions |
dev:watch | tsx --conditions @zod/source --watch | Run with watch mode |
dev:play | pnpm dev play.ts | Quick playground execution |
The @zod/source condition (package.json77-79) allows importing from source files directly without building, useful for rapid iteration.
| Script | Command | Purpose |
|---|---|---|
test | vitest run | Run tests once |
test:watch | vitest | Run tests in watch mode |
| Script | Command | Purpose |
|---|---|---|
check:semver | tsx scripts/check-versions.ts | Validate semantic versioning |
check:circular | madge --circular ... | Detect circular dependencies |
Sources: package.json66-89
The Zod build and CI/CD system provides:
This infrastructure ensures consistent quality, broad compatibility, and reliable distribution across multiple package registries.
Sources: package.json1-90 .github/workflows/test.yml1-61 .github/workflows/release.yml1-115
Refresh this wiki