Deployment Build Process - aku11i/phantom GitHub Wiki
Build Process
This document describes how to build Phantom for distribution and the build system architecture.
Build Overview
Phantom uses a custom TypeScript build script with esbuild for fast, efficient builds.
Build Stack
- Build Tool: esbuild (extremely fast JavaScript bundler)
- Language: TypeScript
- Target: Node.js v22+
- Module Format: ES Modules (ESM)
- Output: Self-contained executable
Build Script
Source: build.ts
The build process is defined in a TypeScript file:
import { build } from "esbuild";
import { chmod, mkdir } from "node:fs/promises";
import { dirname } from "node:path";
async function main() {
const entryPoint = "src/bin/phantom.ts";
const outfile = "dist/phantom.js";
// Ensure output directory exists
await mkdir(dirname(outfile), { recursive: true });
// Build with esbuild
await build({
entryPoints: [entryPoint],
outfile,
bundle: true,
platform: "node",
target: "node22",
format: "esm",
sourcemap: true,
minify: false,
banner: {
js: "#!/usr/bin/env node",
},
});
// Make executable
await chmod(outfile, 0o755);
}
Build Configuration
esbuild Options
Option | Value | Purpose |
---|---|---|
bundle |
true |
Bundle all dependencies into single file |
platform |
"node" |
Target Node.js environment |
target |
"node22" |
Use Node.js v22 features |
format |
"esm" |
Output ES modules |
sourcemap |
true |
Generate source maps for debugging |
minify |
false |
Keep code readable |
banner |
#!/usr/bin/env node |
Make file executable |
Output Structure
dist/
├── phantom.js # Main executable (bundled)
└── phantom.js.map # Source map for debugging
Running the Build
Basic Build
# Run build
pnpm build
# Or directly
npx tsx build.ts
Build Output
$ pnpm build
Building Phantom...
✓ Built dist/phantom.js (150KB)
✓ Made executable
Build completed in 0.2s
Build Process Details
1. Dependency Resolution
esbuild automatically:
- Resolves all imports
- Bundles TypeScript files
- Handles node_modules (development only)
- Respects package.json exports
2. TypeScript Compilation
- TypeScript is compiled by esbuild
- No separate tsc step needed
- Type checking happens separately
- Fast incremental builds
3. Module Bundling
Input: Output:
src/ dist/phantom.js
├── bin/phantom.ts → (single bundled file)
├── cli/handlers/*
├── core/worktree/*
└── core/git/*
4. Executable Creation
The build script:
- Adds shebang (
#!/usr/bin/env node
) - Sets executable permissions (
chmod 755
) - Creates ready-to-run CLI tool
Build Optimizations
Why No Minification?
minify: false // Keep readable for:
- Debugging - Stack traces remain useful
- Size - Already small (~150KB)
- Performance - Negligible runtime difference
- Security - No sensitive code to hide
Bundle Size Analysis
Check what's included:
# Analyze bundle
npx esbuild src/bin/phantom.ts --bundle --analyze
# Output shows:
# - File sizes
# - Included modules
# - Tree shaking results
Development vs Production Builds
Development Build
Current build configuration is optimized for development:
{
sourcemap: true, // Debugging support
minify: false, // Readable output
}
Production Build (if needed)
For production optimization:
{
sourcemap: false, // Smaller size
minify: true, // Compressed output
treeShaking: true, // Remove dead code
}
Build Integration
package.json Scripts
Source: package.json#L13-L14
{
"scripts": {
"build": "tsx build.ts",
"prepublishOnly": "pnpm run build"
}
}
Automatic Builds
- On publish:
prepublishOnly
runs build - CI/CD: Build runs in GitHub Actions
- Local dev: Manual
pnpm build
Cross-Platform Considerations
File Paths
The build handles paths correctly:
// Use Node.js path module
import { dirname, join } from "node:path";
// Not hard-coded paths
const outfile = join("dist", "phantom.js");
Line Endings
- Build outputs LF line endings
- Git configuration ensures consistency
- Works on Windows, macOS, Linux
Executable Permissions
// Unix-style permissions
await chmod(outfile, 0o755);
// Equivalent to: -rwxr-xr-x
// Owner: read, write, execute
// Group: read, execute
// Others: read, execute
Build Artifacts
What's Included
The build includes:
- All source code from
src/
- Type definitions (compiled away)
- No runtime dependencies
- No development dependencies
What's Excluded
Not bundled:
- Test files (
*.test.ts
) - Development dependencies
- TypeScript source files
- Documentation
Build Performance
Metrics
Typical build times:
- Full build: ~200ms
- Incremental: ~50ms
- Watch mode: Instant
Why esbuild?
Compared to other bundlers:
Tool | Build Time | Features |
---|---|---|
esbuild | 0.2s | Fast, simple |
Webpack | 3-5s | Complex, plugins |
Rollup | 2-3s | Tree shaking |
tsc | 1-2s | Type checking only |
Troubleshooting Builds
Common Issues
1. Build Fails
# Check Node version
node --version # Must be v22+
# Clear and rebuild
rm -rf dist
pnpm build
2. Import Errors
// ✓ Correct - with extension
import { something } from "./module.js";
// ✗ Wrong - missing extension
import { something } from "./module";
3. Module Not Found
# Ensure dependencies installed
pnpm install
# Check import paths
# Must be relative or from node_modules
Debug Build Issues
# Verbose esbuild output
npx esbuild src/bin/phantom.ts \
--bundle \
--platform=node \
--log-level=debug
# Check bundled output
cat dist/phantom.js | less
Custom Build Tasks
Adding Pre-build Steps
// In build.ts
async function prebuild() {
// Clean dist directory
await rm("dist", { recursive: true, force: true });
// Generate version info
await generateVersion();
}
async function main() {
await prebuild();
await build({ /* ... */ });
}
Post-build Steps
async function postbuild() {
// Copy additional files
await copyFile("LICENSE", "dist/LICENSE");
// Generate metadata
await writeFile("dist/metadata.json", JSON.stringify({
version,
buildTime: new Date().toISOString(),
}));
}
CI/CD Build Process
GitHub Actions Build
The CI pipeline runs:
- name: Build
run: pnpm build
- name: Verify build
run: |
test -f dist/phantom.js
test -x dist/phantom.js
Release Builds
For npm releases:
# Version bump
npm version patch
# Build and publish
npm publish
# prepublishOnly automatically runs build
Build Best Practices
1. Keep Builds Fast
- Use esbuild for speed
- Avoid unnecessary transformations
- Cache when possible
2. Reproducible Builds
- Lock dependencies (
pnpm-lock.yaml
) - Specify exact Node version
- Use consistent environment
3. Build Validation
Always verify builds:
# Test the built executable
./dist/phantom.js --version
./dist/phantom.js list
# Check file size
ls -lh dist/phantom.js
4. Source Maps
Include in development:
- Debugging support
- Error stack traces
- Performance profiling
Advanced Build Topics
Custom esbuild Plugins
// Example: Add build info
const buildInfoPlugin = {
name: "build-info",
setup(build) {
build.onResolve({ filter: /^BUILD_INFO$/ }, () => ({
path: "BUILD_INFO",
namespace: "build-info",
}));
build.onLoad({ filter: /.*/, namespace: "build-info" }, () => ({
contents: JSON.stringify({
version: process.env.npm_package_version,
buildTime: new Date().toISOString(),
}),
loader: "json",
}));
},
};
// Use in build
await build({
plugins: [buildInfoPlugin],
// ... other options
});
Watch Mode
For development:
// Add to build.ts
if (process.argv.includes("--watch")) {
const ctx = await context({
// ... build options
});
await ctx.watch();
console.log("Watching for changes...");
}
Summary
Phantom's build process is:
- Fast - Sub-second builds with esbuild
- Simple - Single TypeScript file configuration
- Reliable - Consistent, reproducible outputs
- Efficient - Small, self-contained executables
- Developer-friendly - Source maps and readable output
The build system prioritizes developer experience while producing efficient, distributable CLI tools.