Skip to content
PabloZaidenPublic

About

Script and typescript packages to install and update tools

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

18 Commits

Folders and files

Repository files navigation

@pablozaiden/installer

Reusable installer, updater, and release tooling for GitHub-hosted Bun binaries.

Install this package

bun add @pablozaiden/installer

Binary asset contract

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
windows x64, arm64

Windows assets add the executable extension after the target:

<assetPrefix>-<tag>-windows-<arch>.exe
<assetPrefix>-<tag>-windows-<arch>.exe.sha256

Tags may be provided as 1.2.3 or v1.2.3; release assets are always resolved with the v tag form published by GitHub releases.

Reusable build version action

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: true

The 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.

Generic one-line installer

Use the installer directly from this repository:

curl -fsSL https://raw.githubusercontent.com/pablozaiden/installer/main/install.sh | sh -s -- pablozaiden/link

The target repository should publish an installer manifest at either:

  • .github/installer.json
  • .installer.json

On Windows, run the PowerShell installer:

& ([scriptblock]::Create((irm https://raw.githubusercontent.com/pablozaiden/installer/main/install.ps1))) pablozaiden/link

It installs <name>.exe into $HOME/.local/bin by default and adds that directory to the user PATH. Pass -NoModifyPath to print guidance without changing PATH.

The installer:

  1. Detects Linux/macOS and x64/arm64; the PowerShell entrypoint detects Windows x64/arm64.
  2. Loads the target repository's manifest.
  3. Fetches the latest GitHub release.
  4. Downloads each configured binary asset.
  5. Verifies .sha256 checksums by default.
  6. Installs binaries into $HOME/.local/bin unless overridden.
  7. Prints PATH guidance if the install directory is not on PATH.

Manifest schema

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"],
    "windows": ["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.

Installer fallback options

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-cli

Useful 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.

TypeScript updater library

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/Windows 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.

Windows cannot replace a running executable. The updater stages and verifies the complete update, then starts a detached PowerShell helper. The helper waits for the current process to exit, applies all replacements as one rollback-aware operation, and writes <binary>.update-error.log beside the executable if the deferred operation fails.

Exported helpers include:

import {
  buildReleaseAssetName,
  compareReleaseVersions,
  normalizeReleaseTag,
  normalizeReleaseVersion,
  parseInstallerManifestJson,
  resolveReleasePlatform,
  runUpdateCommand,
} from "@pablozaiden/installer";

Reusable binary release workflow

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.

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, darwin-arm64, windows-x64, and windows-arm64,
  • exports TAG, VERSION, RELEASE_TARGET, BUN_TARGET, BINARY_NAME, ASSET_PREFIX, and ASSET_PATH to each build command,
  • stages release assets using the shared naming convention,
  • generates .sha256 files by default,
  • uploads matrix artifacts first, then publishes GitHub release assets only after all matrix builds succeed.

Publishing this package to npm

This repository includes .github/workflows/release-npm-package.yml.

On a published GitHub release, it:

  1. Derives the npm version from the release tag.
  2. Updates package.json.
  3. Verifies the version.
  4. Runs bun install --frozen-lockfile.
  5. Runs bun run build.
  6. Runs bun test.
  7. Publishes @pablozaiden/installer with npm provenance.

Manual workflow_dispatch publishes with the unstable tag.

Development

This package type-checks with TypeScript 7.

bun install
bun run build
bun test

About

Script and typescript packages to install and update tools

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages