Distribution
Package and distribute your CLI to users
Distribution Guide
Learn how to build, package, and distribute your Bunli CLI application to users across different platforms.
Building Your CLI
Development vs Production
During development, you run your CLI directly:
# Development
bunli dev
./src/index.ts --help
For distribution, you need to build optimized binaries:
# Production build
bunli build
Build Options
# Build traditional JS (requires Bun runtime)
bunli build
# Build standalone executable for current platform
bunli build --targets native
# Build for specific platforms
bunli build --targets darwin-arm64,linux-x64
# Build for all platforms
bunli build --targets all
# Build with custom options
bunli build \
--targets darwin-arm64,linux-x64 \
--minify \
--sourcemap \
--outdir ./dist
Platform Targets
Bunli supports building for multiple platforms:
darwin-arm64
- macOS Apple Silicondarwin-x64
- macOS Intellinux-arm64
- Linux ARM64linux-x64
- Linux x64windows-x64
- Windows x64
Cross-Platform Builds
Build for all platforms at once:
// bunli.config.ts
import { defineConfig } from 'bunli'
export default defineConfig({
build: {
targets: [
'darwin-arm64',
'darwin-x64',
'linux-arm64',
'linux-x64',
'windows-x64'
],
compress: true // Create .tar.gz archives
}
})
bunli build --targets all
This creates:
dist/
├── darwin-arm64/
│ └── cli
├── darwin-x64/
│ └── cli
├── linux-arm64/
│ └── cli
├── linux-x64/
│ └── cli
└── windows-x64/
└── cli.exe
With compression enabled, also:
dist/
├── darwin-arm64.tar.gz
├── darwin-x64.tar.gz
├── linux-arm64.tar.gz
├── linux-x64.tar.gz
└── windows-x64.tar.gz
Standalone Executables
Creating Single-File Executables
Bunli uses Bun's --compile
flag to create standalone executables that bundle the runtime:
# Compile for current platform
bunli build --targets native
# Compile for specific platforms
bunli build --targets darwin-arm64,linux-x64
# Compile with optimization
bunli build --targets native --minify --bytecode
Executable Size Optimization
Reduce executable size:
// bunli.config.ts
export default defineConfig({
build: {
targets: ['native'], // or specific platforms
minify: true,
external: [
// Exclude large optional dependencies
'sharp',
'sqlite3',
'@prisma/client'
]
}
})
Code Signing (macOS)
Sign your macOS executables:
# Build the executable
bunli build --targets darwin-arm64
# Sign the executable
codesign --sign "Developer ID Application: Your Name" \
--options runtime \
--entitlements entitlements.plist \
dist/darwin-arm64/my-cli
# Verify signature
codesign --verify --verbose dist/darwin-arm64/my-cli
Distribution Methods
1. Direct Download
Provide pre-built binaries on your website or GitHub releases:
# .github/workflows/release.yml
name: Release
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v1
- name: Install dependencies
run: bun install
- name: Build for all platforms
run: bunli build --targets all
- name: Create Release
uses: softprops/action-gh-release@v1
with:
files: dist/*
generate_release_notes: true
2. npm Distribution
Distribute via npm for easy installation:
// package.json
{
"name": "my-cli",
"version": "1.0.0",
"bin": {
"my-cli": "./dist/index.js"
},
"files": [
"dist/"
],
"scripts": {
"prepublishOnly": "bunli build"
}
}
# Publish to npm
npm publish
# Users install with
npm install -g my-cli
# or
bunx my-cli
3. Homebrew (macOS/Linux)
Create a Homebrew formula:
# Formula/my-cli.rb
class MyCli < Formula
desc "Description of my CLI"
homepage "https://github.com/username/my-cli"
version "1.0.0"
if OS.mac? && Hardware::CPU.arm?
url "https://github.com/username/my-cli/releases/download/v1.0.0/darwin-arm64.tar.gz"
sha256 "abc123..."
elsif OS.mac?
url "https://github.com/username/my-cli/releases/download/v1.0.0/darwin-x64.tar.gz"
sha256 "def456..."
elsif OS.linux? && Hardware::CPU.arm?
url "https://github.com/username/my-cli/releases/download/v1.0.0/linux-arm64.tar.gz"
sha256 "ghi789..."
else
url "https://github.com/username/my-cli/releases/download/v1.0.0/linux-x64.tar.gz"
sha256 "jkl012..."
end
def install
bin.install "my-cli"
end
test do
system "#{bin}/my-cli", "--version"
end
end
4. Docker Distribution
Create a Docker image:
# Dockerfile
FROM oven/bun:1-alpine AS builder
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
COPY . .
RUN bunli build --targets native
FROM alpine:latest
RUN apk add --no-cache libstdc++
COPY --from=builder /app/dist/my-cli /usr/local/bin/my-cli
ENTRYPOINT ["my-cli"]
# Build and push
docker build -t username/my-cli:latest .
docker push username/my-cli:latest
# Users run with
docker run username/my-cli --help
5. Package Managers
Scoop (Windows)
// my-cli.json
{
"version": "1.0.0",
"description": "Description of my CLI",
"homepage": "https://github.com/username/my-cli",
"license": "MIT",
"architecture": {
"64bit": {
"url": "https://github.com/username/my-cli/releases/download/v1.0.0/windows-x64.tar.gz",
"hash": "sha256:abc123..."
}
},
"bin": "my-cli.exe"
}
AUR (Arch Linux)
# PKGBUILD
pkgname=my-cli
pkgver=1.0.0
pkgrel=1
pkgdesc="Description of my CLI"
arch=('x86_64' 'aarch64')
url="https://github.com/username/my-cli"
license=('MIT')
source_x86_64=("$url/releases/download/v$pkgver/linux-x64.tar.gz")
source_aarch64=("$url/releases/download/v$pkgver/linux-arm64.tar.gz")
package() {
install -Dm755 my-cli "$pkgdir/usr/bin/my-cli"
}
Automated Releases
Using bunli release
Bunli provides a release command that automates the release process:
# Create a new release
bunli release
# Release with specific version
bunli release --version 2.0.0
# Release without npm publish
bunli release --no-npm
# Dry run
bunli release --dry-run
Release Configuration
// bunli.config.ts
export default defineConfig({
release: {
npm: true, // Publish to npm
github: true, // Create GitHub release
tagFormat: 'v${version}', // Git tag format
conventionalCommits: true, // Use conventional commits
// Pre-release script
beforeRelease: async (version) => {
// Update changelog, docs, etc.
},
// Post-release script
afterRelease: async (version) => {
// Notify users, update website, etc.
}
}
})
Release Workflow
-
Version Bump
# Automatic version based on commits bunli release # Manual version bunli release --version 2.0.0
-
Build All Platforms
bunli build --targets all
-
Create Git Tag
git tag v2.0.0 git push origin v2.0.0
-
GitHub Release
- Upload built artifacts
- Generate release notes
- Publish release
-
Publish to npm
npm publish
Installation Scripts
Universal Install Script
Create an install script for easy installation:
#!/bin/sh
# install.sh
set -e
VERSION="1.0.0"
REPO="username/my-cli"
# Detect OS and architecture
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m)
case "$OS" in
darwin) OS="darwin" ;;
linux) OS="linux" ;;
*) echo "Unsupported OS: $OS"; exit 1 ;;
esac
case "$ARCH" in
x86_64) ARCH="x64" ;;
aarch64|arm64) ARCH="arm64" ;;
*) echo "Unsupported architecture: $ARCH"; exit 1 ;;
esac
# Download URL
URL="https://github.com/$REPO/releases/download/v$VERSION/$OS-$ARCH.tar.gz"
# Download and install
echo "Downloading my-cli..."
curl -fsSL "$URL" | tar -xz -C /tmp
sudo mv /tmp/my-cli /usr/local/bin/
sudo chmod +x /usr/local/bin/my-cli
echo "my-cli installed successfully!"
my-cli --version
Users install with:
curl -fsSL https://example.com/install.sh | sh
Version Management
Semantic Versioning
Follow semantic versioning (semver):
- Major (1.0.0 → 2.0.0): Breaking changes
- Minor (1.0.0 → 1.1.0): New features
- Patch (1.0.0 → 1.0.1): Bug fixes
Version Display
// src/index.ts
import { createCLI } from '@bunli/core'
import { version } from '../package.json'
const cli = createCLI({
name: 'my-cli',
version, // Automatically shows with --version
description: 'My awesome CLI'
})
Update Notifications
Notify users of new versions:
// src/utils/check-update.ts
import { getLatestVersion } from '@bunli/utils'
export async function checkForUpdates(currentVersion: string) {
try {
const latest = await getLatestVersion('my-cli')
if (latest > currentVersion) {
console.log(`\n Update available: ${currentVersion} → ${latest}`)
console.log(' Run: npm update -g my-cli\n')
}
} catch {
// Ignore update check failures
}
}
Best Practices
- Test Before Release: Run tests on all target platforms
- Include License: Add LICENSE file to distributions
- Document Installation: Provide clear installation instructions
- Version Everything: Tag releases and maintain changelog
- Automate Releases: Use CI/CD for consistent releases
- Sign Binaries: Code sign for security and trust
- Provide Checksums: Include SHA256 checksums for verification
Troubleshooting
Common Issues
Binary not executable:
chmod +x ./my-cli
Missing dependencies:
# Bundle all dependencies
bunli build --targets native --external none
Large binary size:
# Enable optimizations
bunli build --targets native --minify --bytecode
Platform-specific issues:
// Handle platform differences
if (process.platform === 'win32') {
// Windows-specific code
}
Next Steps
- Testing - Test before distribution
- bunli CLI - CLI command reference
- Configuration - Build configuration options