Metadata-Version: 2.1
Name: omnidist
Version: 0.1.29
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)

Run your Go CLI everywhere with `npx` and `uvx`, without requiring Go on end-user machines.

`omnidist` turns one Go project into cross-platform npm and uv distributions with prebuilt binaries, then stages, verifies, and publishes them in a deterministic release flow.

Release flow: `build -> stage -> verify -> publish` so users can run your tool from JavaScript and Python ecosystems out of the box.

For project background, packaging model details, migration notes, and contributor-oriented repo layout, see [CONTRIBUTING.md](CONTRIBUTING.md).

## Requirements

- Go 1.25+
- Node.js + npm (for npm distribution commands)
- `uv` (for uv distribution commands)
- `git` (when `version.source: git-tag`)
- `NPM_PUBLISH_TOKEN` for npm publish when `distributions.npm.publish-auth: token` (default) and not `--dry-run`
- `UV_PUBLISH_TOKEN` (or `--token`) for uv publish (unless `--dry-run`)

## Installation

Run without installation first:

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

Install globally with npm:

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

Install with Go toolchain:

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

Build locally from source:

```bash
go build -o ./bin/omnidist ./cmd/omnidist
./bin/omnidist --help
```

Or run directly:

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

## Quick Start

1. Print repo-tailored onboarding/release commands:

```bash
omnidist quickstart
```

2. Initialize config and distribution folder structure:

```bash
omnidist init
```

This creates:
- `.omnidist/omnidist.yaml`
- `.omnidist/` workspace directories

`omnidist init` writes profiles-mode config with a `default` profile.
It also derives default `distributions.npm.package` / `distributions.uv.package`
from the current directory name (slugified).

3. Edit config and set environment variables (optional):

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

`omnidist` loads `.env` automatically when present, so you can keep values like `OMNIDIST_VERSION`, `NPM_PUBLISH_TOKEN`, and `UV_PUBLISH_TOKEN` there.

4. Build binaries for configured targets:

```bash
omnidist build
```

This also writes the resolved build version to `.omnidist/<profile>/dist/VERSION`
(`.omnidist/default/dist/VERSION` with init defaults).

5. Stage and verify artifacts:

```bash
omnidist stage
omnidist verify
```

`omnidist uv stage` converts the resolved version to PEP 440 and writes
`.omnidist/<profile>/uv/pyproject.toml` with that version.
It also recreates `.omnidist/<profile>/uv/dist` to prevent stale wheel artifacts from previous runs.
On first stage run, omnidist creates `.omnidist/.gitignore` (if missing).

6. Publish when verification passes:

```bash
omnidist publish
```

7. Generate tag-triggered release workflow:

```bash
omnidist ci
```

The generated workflow publishes npm and uv artifacts and also creates a GitHub
release with the built cross-platform binaries plus `checksums.txt`.

## Common Commands

```bash
# Build binaries for configured targets and persist build version
omnidist build

# Print a quickstart command sequence for this repo
omnidist quickstart

# Show runtime version/build metadata
omnidist version

# Stage and verify both distributions (npm -> uv)
omnidist stage
omnidist verify

# Stage dev/pre-release artifacts
omnidist stage --dev

# Publish both distributions (fail-fast, npm -> uv)
omnidist publish

# Generate GitHub Actions workflow for tagged releases
omnidist ci

# Limit orchestration to one distribution
omnidist stage --only npm
omnidist verify --only uv

# Distribution-specific publishing options
omnidist npm publish --tag next --otp <6-digit-code>
omnidist uv publish --publish-url https://test.pypi.org/legacy/ --token <pypi-token>
```

## Environment Variables and .env

`omnidist` loads `.env` automatically at startup (via `godotenv`) if present.

Supported variables:

- `OMNIDIST_VERSION`: used only when `version.source: env`; also expanded in `build.ldflags` templates (for example `${OMNIDIST_VERSION}`).
  `VERSION` is not used.
- `OMNIDIST_CONFIG`: optional global config file path (same as `--config`).
- `OMNIDIST_PROFILE`: optional config profile name (same as `--profile`).
- `OMNIDIST_OMNIDIST_ROOT`: optional project root directory (same as `--omnidist-root`).
- `OMNIDIST_GIT_COMMIT`: optional ldflags template variable for build metadata; populated automatically by `omnidist build` when git metadata is available.
- `OMNIDIST_BUILD_DATE`: optional ldflags template variable for build metadata; populated automatically by `omnidist build` as UTC RFC3339.
- `NPM_PUBLISH_TOKEN`: required for npm publish commands in `token` auth mode when not using `--dry-run`
- `distributions.npm.publish-auth`: npm publish auth mode; `token` uses `NPM_PUBLISH_TOKEN`, `trusted` uses ambient trusted publishing/OIDC
- `distributions.npm.repository-url`: repository URL written to staged package.json `repository.url`; required for trusted npm publishing
- `UV_PUBLISH_TOKEN`: used by uv publish when `--token` is not provided

Example `.env`:

```dotenv
OMNIDIST_VERSION=1.2.3
OMNIDIST_PROFILE=release
NPM_PUBLISH_TOKEN=npm_xxx
UV_PUBLISH_TOKEN=pypi-xxx
```

## Configuration

`.omnidist/omnidist.yaml`:

`omnidist init` now generates the profiles-mode shape (`profiles.default`) by default.
Legacy top-level format remains supported when loading config files.

```yaml
tool:
  name: omnidist
  main: ./cmd/omnidist

version:
  source: git-tag # git-tag | file | env | fixed
  file: VERSION # optional; used when source is file (default VERSION)
  fixed: 1.2.3 # required when source is fixed

readme-path: docs/README.md # optional shared README source for staging

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: "@omnidist/omnidist"
    registry: https://registry.npmjs.org
    access: public # public | restricted
    publish-auth: token # token | trusted
    repository-url: git+https://github.com/your-org/your-repo.git # required for trusted publish
    license: MIT # optional override for package.json license; omit to use SEE LICENSE IN <file>
    keywords: [cli, ai, llm] # optional npm meta-package keywords
    readme-path: docs/npm-readme.md # optional npm-specific README source
    include-readme: true # include project README.md in staged packages when present

  uv:
    package: omnidist
    index-url: https://upload.pypi.org/legacy/
    linux-tag: manylinux2014 # manylinux2014 | musllinux_1_2
    readme-path: docs/uv-readme.md # optional uv-specific README source
    include-readme: true # include project README.md in staged wheels when present
```

Profiles mode:

```yaml
profiles:
  default:
    tool:
      name: omnidist
      main: ./cmd/omnidist
    version:
      source: env
    readme-path: docs/README.md
    targets:
      - os: linux
        arch: amd64
    build:
      ldflags: -s -w
      tags: []
      cgo: false
    distributions:
      npm:
        package: "@scope/mytool"
        keywords: [cli, ai, llm]
        readme-path: docs/npm-readme.md
      uv:
        package: mytool
        readme-path: docs/uv-readme.md

  release:
    tool:
      name: omnidist
      main: ./cmd/omnidist
    version:
      source: fixed
      fixed: 1.0.0
    targets:
      - os: linux
        arch: amd64
    build:
      ldflags: -s -w
      tags: []
      cgo: false
```

Select a profile with `--profile <name>` or `OMNIDIST_PROFILE`.
If `profiles` is present and no profile is provided, `default` is used.
Mixing top-level runtime fields and `profiles` in the same file is not supported.

`targets` use Go values (`GOOS`/`GOARCH`). Distribution workflows map them as needed (for example `windows/amd64` -> npm `win32/x64`).

README source precedence during staging:
`distributions.<name>.readme-path` -> `readme-path` -> `README.md`.
If a configured readme-path is set and cannot be read, staging fails.

When `distributions.npm.keywords` is set, omnidist writes those values to the staged npm meta package `package.json`.

For appkit version injection, configure `build.ldflags` in your project config:

```yaml
build:
  ldflags: -s -w -X github.com/metalagman/appkit/version.version=${OMNIDIST_VERSION} -X github.com/metalagman/appkit/version.gitCommit=${OMNIDIST_GIT_COMMIT} -X github.com/metalagman/appkit/version.buildDate=${OMNIDIST_BUILD_DATE}
```

`build.ldflags` values are expanded with `os.ExpandEnv` during `omnidist build`.
Both `${VAR}` and `$VAR` are supported; unset vars expand to empty strings.

With `version.source: git-tag`, release workflows require `HEAD` to be on an exact SemVer tag (`vX.Y.Z` or `X.Y.Z`).

With `version.source: file`, omnidist reads `./VERSION` from the repository root.

With `version.source: file`, you can override the path via `version.file` (for example `versions/release.txt`).

With `version.source: fixed`, set `version.fixed` to an exact value in config (for example `1.2.3`).

With `version.source: env`, set `OMNIDIST_VERSION` (for example in `.env`) before build/stage/publish.

Use global `--omnidist-root <path>` to set the project root for a command. Omnidist resolves it to an absolute path at startup and changes working directory to it before loading `.env` and config.

Workspace behavior:
- Legacy config writes artifacts to `.omnidist/*`.
- Profiles config writes artifacts to `.omnidist/<profile>/*`.
- Isolation is by profile name. If different config files use the same profile name in the same repo, they share the same `.omnidist/<profile>` workspace.

## Command Reference

Top-level:

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

Global flags:

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

NPM subcommands:

- `omnidist npm stage [--dev]`
- `omnidist npm verify`
- `omnidist npm publish [--dry-run] [--tag <tag>] [--registry <url>] [--otp <code>]`

UV subcommands:

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

## Usage Patterns

### Local development loop

Use this when iterating on the CLI binary and validating artifact generation locally:

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

### Dev pre-release artifacts

Generate prerelease versions from git describe data:

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

### Unified multi-distribution orchestration

Top-level `stage`, `verify`, and `publish` run distributions in deterministic order:
`npm` first, then `uv`, and stop on first failure.

Select a subset with `--only`:

```bash
omnidist stage --only uv
omnidist verify --only npm
omnidist publish --dry-run --only npm,uv
```

### CI bootstrap for tag releases

Generate `.github/workflows/omnidist-release.yml`:

```bash
omnidist ci
```

The generated workflow triggers on `v*` tag pushes and runs:
`build -> stage -> verify -> publish`, then publishes the built binaries and
checksums to the GitHub release.

If workflow already exists:

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

### npm publishing flow with custom options

```bash
omnidist npm publish --dry-run --tag next --registry https://registry.npmjs.org
```

Before npm commands run, omnidist writes `.omnidist/.npmrc` from `distributions.npm.registry` using:
`//<registry>/:_authToken=${NPM_PUBLISH_TOKEN}`.
If staged package version contains a `-dev` prerelease and `--tag` is not provided, omnidist auto-publishes with `--tag dev`.

To publish through npm trusted publishing, set:

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

In trusted mode, omnidist skips token-only auth preflight and does not force a workspace `.npmrc`; `npm publish` uses the ambient CI credentials instead. For GitHub Actions, that means:
- the workflow must grant `id-token: write`
- the job must use a supported Node/npm toolchain for OIDC
- each published npm package must have its own trusted publisher configured on npm
- each staged package must include a `repository.url` that exactly matches the GitHub repository

`omnidist ci` emits the required GitHub Actions OIDC permissions and Node setup when `publish-auth: trusted` is configured.

To configure npm trusted publishers for the meta package and all platform packages:

```bash
omnidist npm trust
```

That prints the exact `npx -y npm@11.16.0 trust github ...` commands derived from your config and target matrix, so you do not have to rely on the host npm version. To apply them directly with an npm account that has write access and 2FA enabled:

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

Useful overrides:
- `--workflow-file publish.yml` when your workflow filename differs from `omnidist-release.yml`
- `--repo your-org/your-repo` when you want to override `distributions.npm.repository-url`
- `--environment production` when your trusted publisher is restricted to a GitHub Actions environment
- `--allow-stage-publish` to also allow `npm stage publish`

If your npm account requires 2FA for publish operations:

```bash
omnidist npm publish --otp <6-digit-code>
```

### uv publishing flow with custom index/auth

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

`omnidist uv publish` uses token authentication.  
Provide token via `--token` or `UV_PUBLISH_TOKEN` (required for non-dry-run).
`omnidist uv verify` and `omnidist uv publish` use the staged version from
`.omnidist/uv/pyproject.toml` when present.
For PyPI/TestPyPI, `omnidist uv verify` fails if the staged version contains local metadata (`+...`), since those indexes reject local versions.

TestPyPI dry-run style validation:

```bash
omnidist uv publish --dry-run --publish-url https://test.pypi.org/legacy/
```

## Usage Examples

### npm release path

```bash
git tag v1.2.0
omnidist build
omnidist npm stage
omnidist npm verify
omnidist npm publish
```

### uv release path

```bash
git tag v1.2.0
omnidist build
omnidist uv stage
omnidist uv verify
omnidist uv publish --publish-url https://upload.pypi.org/legacy/
```

### uv dry-run publish

```bash
omnidist uv publish --dry-run --publish-url https://test.pypi.org/legacy/
```

### version from environment

```yaml
version:
  source: env
```

```bash
export OMNIDIST_VERSION=2.0.0
omnidist npm stage
omnidist uv stage
```
