Metadata-Version: 2.1
Name: omnidist
Version: 0.1.34
Summary: Binary distribution for omnidist
Description-Content-Type: text/markdown

# omnidist

[![Go Report Card](https://goreportcard.com/badge/github.com/metalagman/omnidist)](https://goreportcard.com/report/github.com/metalagman/omnidist)
[![lint](https://github.com/metalagman/omnidist/actions/workflows/lint.yml/badge.svg)](https://github.com/metalagman/omnidist/actions/workflows/lint.yml)
[![test](https://github.com/metalagman/omnidist/actions/workflows/test.yml/badge.svg)](https://github.com/metalagman/omnidist/actions/workflows/test.yml)
[![codecov](https://codecov.io/github/metalagman/omnidist/graph/badge.svg)](https://codecov.io/github/metalagman/omnidist)
[![version](https://img.shields.io/github/v/release/metalagman/omnidist?sort=semver)](https://github.com/metalagman/omnidist/releases)
[![npm](https://img.shields.io/npm/v/%40omnidist%2Fomnidist)](https://www.npmjs.com/package/@omnidist/omnidist)
[![PyPI](https://img.shields.io/pypi/v/omnidist)](https://pypi.org/project/omnidist/)
[![license](https://img.shields.io/github/license/metalagman/omnidist)](LICENSE)

Package and publish a Go CLI as npm, uv, and RubyGems installable tools with prebuilt
cross-platform binaries.

`omnidist` gives Go CLI maintainers one repeatable release flow:

```text
build -> stage -> verify -> publish
```

The generated npm packages use platform-specific optional dependencies, so users
can run your CLI with `npx` without install-time downloader scripts. The uv
distribution stages wheel artifacts so users can run the same CLI from Python
tooling with `uvx`. The RubyGems distribution stages platform-specific gems so
users can install the same CLI with `gem install`.

## Requirements

- Go 1.25+
- Node.js and npm for npm staging, verification, and publishing
- `uv` for uv staging, verification, and publishing
- Ruby and RubyGems for gem staging, verification, and publishing
- `git` when `version.source: git-tag`
- `NPM_PUBLISH_TOKEN` for npm token publishing, unless using `--dry-run` or trusted publishing
- `UV_PUBLISH_TOKEN` or `omnidist uv publish --token` for uv publishing, unless using `--dry-run`
- `GEM_HOST_API_KEY` / `RUBYGEMS_API_KEY` for gem token publishing, unless using `--dry-run` or trusted publishing

## Install

Run without installing:

```bash
npx -y @omnidist/omnidist@latest --help
uvx omnidist --help
```

Install with npm:

```bash
npm i -g @omnidist/omnidist
omnidist --help
```

Install with Go:

```bash
go install github.com/metalagman/omnidist/cmd/omnidist@latest
omnidist --help
```

Install with RubyGems after publication:

```bash
gem install omnidist
omnidist --help
```

Run from a checkout:

```bash
go run ./cmd/omnidist --help
```

## Quick Start

Initialize an existing Go CLI repository:

```bash
omnidist init
```

This creates `.omnidist/omnidist.yaml` and initial workspace directories under
`.omnidist/default/`. The generated config uses `profiles.default`.

Edit the generated config before building:

```bash
$EDITOR .omnidist/omnidist.yaml
```

At minimum, check these fields:

- `tool.name`: the binary name users will run.
- `tool.main`: the Go main package, for example `./cmd/mytool`.
- `distributions.npm.package`: the npm package, for example `@my-org/mytool`.
- `distributions.uv.package`: the uv/PyPI package, for example `mytool`.
- `distributions.gem.package`: the RubyGems package, for example `mytool`.
- `version.source`: usually `git-tag`, `file`, `env`, or `fixed`.

Then run the local release pipeline:

```bash
omnidist build
omnidist stage
omnidist verify
```

Publish only after verification passes:

```bash
omnidist publish
```

Generate a GitHub Actions release workflow:

```bash
omnidist ci
```

The generated workflow is written to `.github/workflows/omnidist-release.yml`.
It runs on `v*` tag pushes, builds once, stages and verifies artifacts, publishes
npm, uv, and gem distributions, then uploads the built binaries and `checksums.txt`
to the GitHub release.

## Configuration

`omnidist init` writes profiles-mode config by default:

```yaml
profiles:
  default:
    tool:
      name: mytool
      main: ./cmd/mytool

    version:
      source: git-tag # git-tag | file | env | fixed
      file: VERSION # used only when source is file
      fixed: 1.2.3 # required only when source is fixed

    targets:
      - os: darwin
        arch: amd64
      - os: darwin
        arch: arm64
      - os: linux
        arch: amd64
      - os: linux
        arch: arm64
      - os: windows
        arch: amd64

    build:
      ldflags: -s -w
      tags: []
      cgo: false

    distributions:
      npm:
        package: "@my-org/mytool"
        registry: https://registry.npmjs.org
        access: public # public | restricted
        publish-auth: token # token | trusted
        include-readme: true

      uv:
        package: mytool
        index-url: https://upload.pypi.org/legacy/
        linux-tag: manylinux2014 # manylinux2014 | musllinux_1_2
        include-readme: true

      gem:
        package: mytool
        registry: https://rubygems.org
        publish-auth: token # token | trusted
        repository-url: https://github.com/my-org/mytool
        include-readme: true
```

Select a profile with `--profile <name>` or `OMNIDIST_PROFILE`. If `profiles`
is present and no profile is selected, `default` is used. Profiles write
artifacts to `.omnidist/<profile>/`.

Legacy top-level config is still accepted when loading existing config files,
but do not mix top-level runtime fields with a `profiles` map in the same file.

Targets use Go values: `os` is `GOOS`, and `arch` is `GOARCH`. Distribution
workflows map them as needed, for example `windows/amd64` becomes npm
`win32/x64`. For RubyGems, `windows/amd64` defaults to `x64-mingw-ucrt`; set
`targets[].variant: mingw32` if you need `x64-mingw32`, or `targets[].variant: musl`
for Linux musl gem platforms.

## Versioning

`omnidist build` resolves the release version and writes it to
`.omnidist/<profile>/dist/VERSION`. Stage and publish commands use that build
version so npm, uv, and gem artifacts stay in sync.

Supported version sources:

- `git-tag`: `HEAD` must be on an exact SemVer tag, either `vX.Y.Z` or `X.Y.Z`.
- `file`: read the version from `version.file`, defaulting to `VERSION`.
- `env`: read `OMNIDIST_VERSION`.
- `fixed`: read `version.fixed`.

For local prerelease staging, use:

```bash
omnidist stage --dev
```

`--dev` generates prerelease artifact versions from git metadata. For npm
publishing, `-dev` prerelease versions are automatically published with
`--tag dev` when no tag is supplied.

## Environment and Build Variables

`omnidist` loads `.env` at startup when the file exists.

Environment variables:

- `OMNIDIST_VERSION`: used only when `version.source: env`. `VERSION` is not used.
- `OMNIDIST_CONFIG`: config file path, equivalent to `--config`.
- `OMNIDIST_PROFILE`: config profile name, equivalent to `--profile`.
- `OMNIDIST_OMNIDIST_ROOT`: project root directory, equivalent to `--omnidist-root`.
- `NPM_PUBLISH_TOKEN`: npm token for `distributions.npm.publish-auth: token`.
- `UV_PUBLISH_TOKEN`: uv publish token when `--token` is not provided.
- `GEM_HOST_API_KEY`: RubyGems token for `distributions.gem.publish-auth: token`.
- `RUBYGEMS_API_KEY`: alternate RubyGems token env name.
- `GEM_HOST_OTP_CODE`: RubyGems MFA OTP for token-based publish.

Build `ldflags` template variables:

- `OMNIDIST_VERSION`: expanded in `build.ldflags` during `omnidist build`.
- `OMNIDIST_GIT_COMMIT`: populated by `omnidist build` when git metadata is available.
- `OMNIDIST_BUILD_DATE`: populated by `omnidist build` as UTC RFC3339.

Example:

```yaml
build:
  ldflags: >-
    -s -w
    -X github.com/your-org/mytool/internal/version.version=${OMNIDIST_VERSION}
    -X github.com/your-org/mytool/internal/version.gitCommit=${OMNIDIST_GIT_COMMIT}
    -X github.com/your-org/mytool/internal/version.buildDate=${OMNIDIST_BUILD_DATE}
```

`build.ldflags` uses `os.ExpandEnv`, so both `$VAR` and `${VAR}` are supported.
Unset variables expand to empty strings.

## npm Distribution

The npm distribution has two package types:

- Meta package: `distributions.npm.package`, with a small Node shim.
- Platform packages: one package per target, selected by npm `os` and `cpu` constraints.

The meta package lists platform packages as `optionalDependencies` at the same
version. Published packages do not use `postinstall` scripts or network
downloaders.

Common npm commands:

```bash
omnidist npm stage
omnidist npm verify
omnidist npm publish --tag latest
```

Token publishing uses `NPM_PUBLISH_TOKEN`. `omnidist` writes a workspace
`.omnidist/.npmrc` from `distributions.npm.registry` with npm's token
substitution syntax.

Trusted publishing uses npm OIDC instead of `NPM_PUBLISH_TOKEN`:

```yaml
distributions:
  npm:
    publish-auth: trusted
    repository-url: git+https://github.com/your-org/your-repo.git
```

In trusted mode:

- `repository-url` is required and is written to staged package metadata.
- `omnidist npm publish` skips token-only auth preflight.
- GitHub Actions must grant `id-token: write`.
- Each npm package, including platform packages, needs a trusted publisher configured on npm.

Print trusted publisher setup commands:

```bash
omnidist npm trust
```

Apply them directly:

```bash
omnidist npm trust --apply
```

Useful trust options:

- `--workflow-file <name>` for a workflow filename other than `omnidist-release.yml`.
- `--repo <owner/repo>` to override `distributions.npm.repository-url`.
- `--environment <name>` for npm trusted publishers restricted to a GitHub Actions environment.
- `--allow-stage-publish` to allow npm stage publish.

## uv Distribution

The uv distribution stages wheel artifacts under `.omnidist/<profile>/uv/dist`.
During staging, versions are converted to PEP 440 where needed.

Common uv commands:

```bash
omnidist uv stage
omnidist uv verify
omnidist uv publish
```

Publish to a different PyPI-compatible index:

```bash
omnidist uv publish --publish-url https://test.pypi.org/legacy/ --token <token>
```

`omnidist uv verify` rejects versions with local metadata (`+...`) for
PyPI/TestPyPI publishing, because those indexes reject local versions.

## RubyGems Distribution

The RubyGems distribution stages one platform-specific `.gem` per configured
target under `.omnidist/<profile>/gem/pkg`. Each gem contains:

- a wrapper executable under `exe/`
- the prebuilt CLI binary under `libexec/`
- platform metadata so RubyGems installs the matching package

Common gem commands:

```bash
omnidist gem stage
omnidist gem verify
omnidist gem publish
```

Token publishing uses `GEM_HOST_API_KEY` or `RUBYGEMS_API_KEY`. If your
RubyGems account requires MFA for pushes, also pass `--otp` or set
`GEM_HOST_OTP_CODE`.

Trusted publishing uses RubyGems OIDC instead of static secrets:

```yaml
distributions:
  gem:
    publish-auth: trusted
    registry: https://rubygems.org
    repository-url: https://github.com/your-org/your-repo
```

In trusted mode:

- GitHub Actions must grant `id-token: write`.
- The repository/workflow must be registered as a trusted publisher on RubyGems.org.
- `omnidist` can publish a brand-new gem name once the pending trusted publisher is configured on RubyGems.org.

## README and License Staging

README source precedence during staging:

```text
distributions.<name>.readme-path -> readme-path -> README.md
```

If a configured `readme-path` is set and cannot be read, staging fails. Set
`include-readme: false` for a distribution to skip README inclusion.

For npm packages, `license` can be set explicitly under `distributions.npm`.
When it is omitted, omnidist includes the project license file when present and
writes package metadata that points to that file.

## CI Releases

Generate a release workflow:

```bash
omnidist ci
```

Overwrite an existing generated workflow:

```bash
omnidist ci --force
```

The workflow installs omnidist with npm. When generating this repository's own
release workflow, it uses `go run ./cmd/omnidist` instead. The workflow also
sets `FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true` for JavaScript-based GitHub
Actions.

Before the first tag release, configure the required registry credentials:

- npm token mode: `NPM_PUBLISH_TOKEN` GitHub secret.
- npm trusted mode: npm trusted publishers for every staged npm package.
- uv publishing: `UV_PUBLISH_TOKEN` GitHub secret.
- gem token mode: `RUBYGEMS_API_KEY` GitHub secret, and optionally `RUBYGEMS_OTP_CODE`.
- gem trusted mode: RubyGems trusted publisher for the release workflow.

Release by pushing a SemVer tag:

```bash
git tag v1.2.3
git push origin v1.2.3
```

## Command Reference

Top-level commands:

```bash
omnidist init
omnidist quickstart
omnidist build
omnidist stage [--dev] [--only npm|uv|gem|npm,uv,gem]
omnidist verify [--only npm|uv|gem|npm,uv,gem]
omnidist publish [--dry-run] [--only npm|uv|gem|npm,uv,gem]
omnidist ci [--force] [--dry-run]
omnidist version
```

Global flags:

```bash
--config <path>
--profile <name>
--omnidist-root <path>
```

npm commands:

```bash
omnidist npm stage [--dev]
omnidist npm verify
omnidist npm publish [--dry-run] [--tag <tag>] [--registry <url>] [--otp <code>]
omnidist npm trust [--apply] [--workflow-file <name>] [--repo <owner/repo>] [--environment <name>] [--allow-stage-publish]
```

uv commands:

```bash
omnidist uv stage [--dev]
omnidist uv verify
omnidist uv publish [--dry-run] [--publish-url <url>] [--token <token>]
```

gem commands:

```bash
omnidist gem stage [--dev]
omnidist gem verify
omnidist gem publish [--dry-run] [--host <url>] [--api-key <token>] [--otp <code>]
```

Top-level `stage`, `verify`, and `publish` run distributions in deterministic
order: npm first, then uv, then gem. Use `--only` to limit the command to one
or more distributions.

## Troubleshooting

`omnidist build` says no version could be resolved:

Check `version.source`. For `git-tag`, `HEAD` must be exactly on a SemVer tag.
For `env`, set `OMNIDIST_VERSION`. For `file`, create the configured version
file.

`omnidist stage` says the build version file is missing:

Run `omnidist build` first. Stage commands use the build version persisted under
`.omnidist/<profile>/dist/VERSION`.

npm install cannot find a platform package:

Run `omnidist npm verify` before publishing. It checks package versions,
platform `os` and `cpu` fields, required binaries, forbidden `postinstall`
scripts, repository metadata, and optional dependency parity.

Trusted npm publish fails:

Verify that `distributions.npm.repository-url` matches the GitHub repository,
the workflow grants `id-token: write`, and every npm package has a trusted
publisher configured.

uv publish rejects the version:

Restage with a publishable version. PyPI and TestPyPI reject local version
metadata containing `+`.

## Contributing

For repository layout, development workflow, package model details, and migration
notes, see [CONTRIBUTING.md](CONTRIBUTING.md).
