Reusable installer, updater, and release tooling for GitHub-hosted Bun binaries.
bun add github:PabloZaiden/installer#<commit-or-tag>All tools use the same release asset naming convention:
<assetPrefix>-<tag>-<os>-<arch>
<assetPrefix>-<tag>-<os>-<arch>.sha256
Supported targets are:
| OS | Architectures |
|---|---|
linux |
x64, arm64 |
darwin |
x64, arm64 |
Use WSL for a Linux environment.
Versions may be provided as 1.2.3 or v1.2.3; asset names use the v form.
The rolling latest release uses the embedded build version for asset names,
not its fixed release tag. See release channels.
Use the shared action to resolve the version embedded in a build:
- name: Resolve build version
id: version
uses: pablozaiden/installer/.github/actions/resolve-version@main
with:
mode: main
github_token: ${{ github.token }}
update_package_version: trueThe action exposes steps.version.outputs.version and
steps.version.outputs.base_version. In release mode, it removes the leading
v from release_tag (or GITHUB_REF_NAME). In main mode, it reads the
latest published release, increments its patch component, and appends the UTC
timestamp and seven-character commit SHA:
8.5.9 -> 8.5.10-main-2026-07-11-14-48-abcdef1
If the repository has no published release, the base version is 0.0.0, so
the first main build is 0.0.1-main-<timestamp>-<short-sha>. Set
latest_release_tag when the release lookup must be supplied explicitly.
Pin the action to an immutable commit in production workflows instead of
using main.
Use the installer directly from this repository:
curl -fsSL https://raw.githubusercontent.com/pablozaiden/installer/main/install.sh | sh -s -- pablozaiden/linkThe target repository should publish an installer manifest at either:
.github/installer.json.installer.json
The installer:
- Detects Linux/macOS and x64/arm64.
- Loads the target repository's manifest.
- Fetches the latest GitHub release.
- Downloads each configured binary asset.
- Verifies
.sha256checksums by default. - Installs binaries into
$HOME/.local/binunless overridden. - Prints PATH guidance if the install directory is not on
PATH.
Single-binary example:
{
"schemaVersion": 1,
"repo": "pablozaiden/myproject",
"installDir": "$HOME/.local/bin",
"binaries": [
{
"name": "myproject-cli",
"assetPrefix": "myproject-cli",
"postInstallMessage": "Run 'myproject-cli' to start MyProject."
}
],
"checksums": {
"required": true,
"extension": ".sha256"
},
"platforms": {
"linux": ["x64", "arm64"],
"darwin": ["x64", "arm64"]
}
}Multi-binary example:
{
"schemaVersion": 1,
"repo": "pablozaiden/myproject",
"installDir": "$HOME/.local/bin",
"binaries": [
{
"name": "myproject",
"assetPrefix": "myproject",
"postInstallMessage": "Run 'myproject' to start the local server."
},
{
"name": "myproject-cli",
"assetPrefix": "myproject-cli",
"postInstallMessage": "Run 'myproject-cli --help' to use the API client."
}
],
"checksums": {
"required": true,
"extension": ".sha256"
}
}checksums.required should be true for new projects. Use false only while migrating existing projects that do not yet publish checksum assets.
The shell installer supports manifest schemaVersion: 1 and fails before reading other manifest fields if a future schema version is provided.
For projects without a manifest, pass binaries explicitly:
curl -fsSL https://raw.githubusercontent.com/pablozaiden/installer/main/install.sh \
| sh -s -- pablozaiden/myproject --binary myproject-cliUseful options:
--ref <ref> Repository ref to read manifests from
--binary <name> Binary name when no manifest is available
--asset-prefix <prefix> Asset prefix for the most recent --binary
--install-dir <dir> Install directory
--checksum required|optional|none
--checksum none disables checksum downloads and verification entirely. optional attempts checksum verification when the checksum asset exists, while required fails if the checksum cannot be downloaded or verified.
Use runUpdateCommand from an installed binary's update command.
import { runUpdateCommand } from "@pablozaiden/installer";
import { MYPROJECT_VERSION } from "./version";
export async function runCliCommand(command: {
kind: string;
checkOnly?: boolean;
version?: string;
preRelease?: boolean;
}) {
if (command.kind === "update") {
return await runUpdateCommand(
{
checkOnly: command.checkOnly ?? false,
version: command.version,
preRelease: command.preRelease,
},
{
repository: "pablozaiden/myproject",
binaryName: "myproject-cli",
currentVersion: MYPROJECT_VERSION,
productName: "MyProject",
checksum: { required: true },
},
);
}
}Set preRelease: true to consider published prereleases. The updater selects
the prerelease with the highest semantic version only when it is newer than
the latest stable release; otherwise, it uses the stable release. The same
selection applies to checkOnly. An explicit version continues to install
that exact release.
For a CLI with a companion binary installed beside it:
import { runUpdateCommand } from "@pablozaiden/installer";
import { MYPROJECT_VERSION } from "./version";
await runUpdateCommand(
{
checkOnly: false,
version: undefined,
},
{
repository: "pablozaiden/myproject",
binaryName: "myproject-cli",
currentVersion: MYPROJECT_VERSION,
productName: "MyProject",
checksum: { required: false },
companionBinaries: [
{
binaryName: "myproject",
assetPrefix: "myproject",
required: false
}
],
},
);The updater supports:
- latest release checks,
- opt-in prerelease selection when a prerelease is newer than the latest stable release,
- explicit version installs,
- SemVer comparison including prereleases, with build metadata ignored for precedence,
- GitHub release metadata validation,
- Linux/macOS x64/arm64 target resolution,
- checksum verification before replacement,
- source-mode rejection when running from
bun, - staged temp-file replacement with executable permission preservation,
- companion binary updates that are committed together with the primary binary and rolled back on replacement failure.
Exported helpers include:
import {
buildReleaseAssetName,
compareReleaseVersions,
normalizeReleaseTag,
normalizeReleaseVersion,
parseInstallerManifestJson,
resolveReleasePlatform,
runUpdateCommand,
} from "@pablozaiden/installer";In a consuming repository, add a workflow like:
name: Build and Release Binaries
on:
release:
types: [published]
jobs:
binaries:
uses: pablozaiden/installer/.github/workflows/reusable-binary-release.yml@main
permissions:
contents: write
secrets:
macos_signing_certificate_base64: ${{ secrets.MACOS_SIGNING_CERTIFICATE_BASE64 }}
macos_signing_certificate_password: ${{ secrets.MACOS_SIGNING_CERTIFICATE_PASSWORD }}
with:
prebuild_command: bun run build
binaries: |
[
{
"name": "myproject-cli",
"asset_prefix": "myproject-cli",
"build_command": "bun run build-binary.ts --target=$BUN_TARGET --outfile=$ASSET_PATH",
"output_path": "$ASSET_PATH"
}
]When both macOS signing secrets are provided, the workflow imports the
Base64-encoded PKCS#12 certificate into a temporary keychain on each macOS
runner and signs every macOS binary before staging it. The password is never
written to the workflow environment after keychain setup. If neither secret is
provided, macOS artifacts remain unsigned; providing only one secret fails the
macOS job. The PKCS#12 must contain the code-signing certificate and its private
key. The workflow grants private-key access only to /usr/bin/codesign and
selects the imported certificate by fingerprint. It does not change trust
settings, so self-signed certificates remain usable for internal signing
without requiring a trust-root workaround.
Projects requiring distribution signing or notarization can opt in with
macos_finalize_command. It runs once per Darwin build group after all
compilation and staging, before artifact upload; failure blocks publication.
The command runs with strict Bash error, unset-variable and pipeline handling.
It receives MACOS_ARTIFACT_PATHS_JSON, a JSON array of the staged
Darwin paths, and MACOS_SIGNING_KEYCHAIN/MACOS_SIGNING_IDENTITY from the
temporary signing keychain. Required signing credentials must be present and
the keychain must contain exactly one valid code-signing identity. Finalization
can re-sign with the project's runtime entitlements and secure timestamps;
the workflow regenerates enabled checksums afterward.
Optional macos_notarization_key_p8, macos_notarization_key_id and
macos_notarization_issuer_id secrets are exposed only to finalization as
MACOS_NOTARIZATION_KEY_P8, MACOS_NOTARIZATION_KEY_ID and
MACOS_NOTARIZATION_ISSUER_ID, not to installation or compilation. The caller
owns submission, acceptance and signature verification, and cleanup of any
private files it creates. The workflow removes its signing credentials and
keychain; cleanup failure blocks the opted-in job. With no finalizer, existing
optional signing behavior is unchanged.
For a project with multiple binaries:
jobs:
binaries:
uses: pablozaiden/installer/.github/workflows/reusable-binary-release.yml@main
permissions:
contents: write
with:
prebuild_command: bun run build
binaries: |
[
{
"name": "myproject",
"asset_prefix": "myproject",
"build_command": "cd apps/server && bun src/build.ts --target=$BUN_TARGET",
"output_path": "apps/server/dist/myproject-$RELEASE_TARGET"
},
{
"name": "myproject-cli",
"asset_prefix": "myproject-cli",
"build_command": "cd apps/cli && bun src/build.ts --target=$BUN_TARGET",
"output_path": "apps/cli/dist/myproject-cli-$RELEASE_TARGET"
}
]The workflow:
- runs on GitHub release publication,
- builds
linux-x64,linux-arm64,darwin-x64, anddarwin-arm64, - exports
TAG,VERSION,RELEASE_TARGET,BUN_TARGET,BINARY_NAME,ASSET_PREFIX, andASSET_PATHto each build command, - stages release assets using the shared naming convention,
- generates
.sha256files by default, - uploads matrix artifacts first, then publishes GitHub release assets only after all matrix builds succeed.
Install directly from GitHub using a commit SHA or release tag. This package is not published to npm. Commit references are supported for development and final integrations; publishing an intermediate release is not required.
This package type-checks with TypeScript 7.
bun install
bun run build
bun test