This document describes how to distribute the IPB CLI via various package managers and distribution methods.
The IPB CLI can be distributed in several ways:
- npm (current) -
npm install -g investec-ipb - Homebrew (macOS/Linux) -
brew install ipb - Direct Downloads - Standalone binaries for each platform
- Other Package Managers - Scoop (Windows), apt/yum (Linux), etc.
We use esbuild to bundle the application and @yao-pkg/pkg to create standalone executables that don't require Node.js to be installed.
npm installThe build process consists of two steps:
- Bundle with esbuild: Bundles all code and dependencies into a single CommonJS file
- Package with pkg: Creates platform-specific executables from the bundled file
# Bundle only (creates dist-bundle/index.cjs)
npm run bundle
# Build for current platform only
npm run pkg:build
# Build for macOS (Intel + Apple Silicon)
npm run pkg:macos
# Build for Linux (x64 + ARM64)
npm run pkg:linux
# Build for Windows (x64)
npm run pkg:windows
# Build for all platforms
npm run pkg:all
# Clean dist and bundle directories
npm run pkg:cleanBinaries are generated in the dist/ directory with the following naming convention:
ipb-macos-x64(macOS Intel)ipb-macos-arm64(macOS Apple Silicon)ipb-linux-x64(Linux x64)ipb-linux-arm64(Linux ARM64)ipb-win-x64.exe(Windows x64)
Note: The binary names use the ipb prefix for the installed command name.
The project uses esbuild to bundle ESM code into CommonJS format, which is then packaged with @yao-pkg/pkg. This approach:
- Bundles all dependencies - All npm packages are bundled into a single file, eliminating ESM module resolution issues
- Converts to CommonJS - The bundled output is CommonJS, which
pkghandles reliably - Includes assets - Templates, assets, and instructions are copied to the bundle directory
- Produces working executables - The final binaries work without Node.js installed
The bundling step resolves the ESM limitations that pkg has when packaging ESM code directly.
Homebrew is the most popular package manager for macOS and Linux. There are two approaches:
Create a separate repository for your Homebrew formula.
Create a new GitHub repository named homebrew-ipb (or homebrew-tap if you want multiple formulas).
Create Formula/ipb.rb:
class Ipb < Formula
desc "CLI application to manage programmable banking cards"
homepage "https://github.com/devinpearson/ipb"
url "https://github.com/devinpearson/ipb/releases/download/v#{version}/ipb-macos-arm64"
sha256 "<SHA256_CHECKSUM>"
version "<VERSION>"
if Hardware::CPU.intel?
url "https://github.com/devinpearson/ipb/releases/download/v#{version}/ipb-macos-x64"
sha256 "<SHA256_CHECKSUM_X64>"
end
def install
bin.install "ipb-macos-arm64" => "ipb"
# For Intel builds, use:
# bin.install "ipb-macos-x64" => "ipb"
end
test do
system "#{bin}/ipb", "--version"
end
endFor better multi-architecture support, use conditional logic:
class Ipb < Formula
desc "CLI application to manage programmable banking cards"
homepage "https://github.com/devinpearson/ipb"
version "0.8.3"
if Hardware::CPU.arm?
url "https://github.com/devinpearson/ipb/releases/download/v#{version}/ipb-macos-arm64"
sha256 "<SHA256_CHECKSUM_ARM64>"
else
url "https://github.com/devinpearson/ipb/releases/download/v#{version}/ipb-macos-x64"
sha256 "<SHA256_CHECKSUM_X64>"
end
def install
if Hardware::CPU.arm?
bin.install "ipb-macos-arm64" => "ipb"
else
bin.install "ipb-macos-x64" => "ipb"
end
end
test do
system "#{bin}/ipb", "--version"
end
endUsers can install via:
brew tap devinpearson/ipb
brew install ipbOr in one command:
brew install devinpearson/ipb/ipbFor broader distribution, you can submit to Homebrew core. This requires:
- Minimum 30 stars on GitHub
- Active maintenance
- Passing audit tests
- See Homebrew Core Contributing Guide
We use GitHub Actions to automatically build and release binaries when tags are pushed.
See .github/workflows/release.yml for the automated release workflow.
-
Build binaries for all platforms:
npm run pkg:all
-
Calculate SHA256 checksums:
shasum -a 256 dist/ipb-* -
Create a GitHub Release:
- Go to GitHub Releases
- Click "Draft a new release"
- Tag version:
v0.8.3 - Release title:
v0.8.3 - Upload all binaries from
dist/ - Add release notes
-
Update Homebrew Formula:
- Update version in
Formula/ipb.rb - Update SHA256 checksums
- Update URL if needed
- Update version in
Create a Scoop manifest in a separate repository or in the main repo under scoop/ipb.json:
{
"version": "0.8.3",
"description": "CLI application to manage programmable banking cards",
"homepage": "https://github.com/devinpearson/ipb",
"license": "MIT",
"url": "https://github.com/devinpearson/ipb/releases/download/v0.8.3/ipb-win-x64.exe",
"hash": "SHA256_CHECKSUM",
"bin": ["ipb-win-x64.exe", "ipb.exe"],
"autoupdate": {
"url": "https://github.com/devinpearson/ipb/releases/download/v$version/ipb-win-x64.exe"
}
}Install via:
scoop bucket add ipb https://github.com/devinpearson/ipb-scoop
scoop install ipbCreate a Chocolatey package in a separate repository.
# Build binary first
npm run pkg:linux
# Create .deb package
./scripts/create-deb.sh 0.8.3 amd64
# Install
sudo dpkg -i ipb_0.8.3_amd64.deb# Install required tools
sudo apt-get install fakeroot dpkg-dev
# Create package structure
mkdir -p ipb_0.8.3/usr/local/bin
cp dist/ipb-linux-x64 ipb_0.8.3/usr/local/bin/ipb
chmod +x ipb_0.8.3/usr/local/bin/ipb
# Create control file
mkdir -p ipb_0.8.3/DEBIAN
cat > ipb_0.8.3/DEBIAN/control << EOF
Package: ipb
Version: 0.8.3
Section: utils
Priority: optional
Architecture: amd64
Maintainer: Devin Pearson
Description: CLI application to manage programmable banking cards
EOF
# Build package
dpkg-deb --build ipb_0.8.3For easy distribution and automatic updates, set up a PPA on Launchpad:
- Create Launchpad account at https://launchpad.net
- Create PPA at https://launchpad.net/people/+new-ppa
- Upload packages using
dput(seeUBUNTU_DISTRIBUTION.mdfor details)
Users can then install via:
sudo add-apt-repository ppa:your-launchpad-id/ipb
sudo apt update
sudo apt install ipbBuild a universal Snap package:
# Install snapcraft
sudo snap install snapcraft --classic
# Build snap
snapcraft
# Install
sudo snap install ipb_0.8.3_amd64.snap --dangerousSee snap/snapcraft.yaml for configuration.
Create a portable AppImage:
# Build binary
npm run pkg:linux
# Create AppDir and package (requires appimagetool)
# See UBUNTU_DISTRIBUTION.md for full instructionsCreate an .rpm package using rpmbuild or fpm.
For detailed Ubuntu distribution instructions, see UBUNTU_DISTRIBUTION.md.
- Update version in
package.json - Update version in Homebrew formula
- Update version in other package manifests
- Tag releases with
vprefix:v0.8.3 - GitHub releases should match tag names
Before releasing:
-
Test binaries locally:
./dist/ipb-macos-arm64 --version ./dist/ipb-macos-arm64 --help
-
Test Homebrew formula:
brew install --build-from-source Formula/ipb.rb brew test ipb -
Test on clean systems:
- Use Docker for Linux testing
- Use VMs for cross-platform testing
- Test on fresh installs without Node.js
If templates/assets are not found at runtime:
- Ensure
pkg.assetsin package.json includes all necessary files - Check that paths are relative to the binary location
- Verify assets are included in the snapshot
The project uses esbuild to bundle ESM code into CommonJS before packaging, which resolves ESM compatibility issues. If you encounter module-related errors:
- Ensure
npm run bundlecompletes successfully before packaging - Check that
dist-bundle/index.cjsexists and is valid - Verify all dependencies are being bundled (check bundle size)
- The
--no-bytecode --publicflags are included in build scripts for maximum compatibility
Binaries are large (~65-80MB) because they include Node.js runtime:
- This is expected and normal
- Users don't need Node.js installed separately
- Consider compression for downloads
See .github/workflows/release.yml for automated release workflow that:
- Builds binaries on push of version tags
- Creates GitHub releases
- Uploads binaries as release assets
- Can trigger formula updates (requires separate workflow)