npm package: @opndev/rzilla
Description: Release zilla for npm packages
Author: Wesley Schwengle
Latest version: 0.0.4

<!--
SPDX-FileCopyrightText: 2026 Wesley Schwengle <wesleys@opperschaap.net>

SPDX-License-Identifier: MIT
-->

# @opndev/rzilla

Release zilla for npm packages.

`rzil` lets you replace manual `package.json` maintenance with a
declarative `dist.toml` file.\
Inspired by Dist::Zilla, but for npm.

## Philosophy

- `dist.toml` is the source of truth.
- `package.json` is a generated artifact.
- Strict where ambiguity is dangerous.
- Ergonomic where it does not matter.
- No automatic pushing.
- Deterministic releases.

## Commands

The following commands are listed, but maybe incomplete. Please run the actual
script for an update to date listing.

``` bash
rzil pkg       # Generate package.json from dist.toml
rzil release   # Run release workflow
rzil test      # npm test
rzil build     # Run build workflow
rzil clean     # Clean the .build dir
rzil prereqs   # List all the dependencies
```

# dist.toml Example

``` toml
name = "@opndev/rzilla"
description = "Release zilla for npm packages"
type = "module"
keywords = ["release","npm","changes","git"]

[license]
spdx = "MIT"
file = "LICENSES/MIT.txt"

[author]
name = "Your Name"
email = "you@example.com"

[repository]
remote = "origin"

[prereqs]
node = ">= 18 < 22"
npm  = ">= 9"
lodash = "^4.17.0"

[prereqs.dev]
tap = "latest"
jsdoc = "0"

[prereqs.peer]
left-pad = "latest"

[gather]
main = "lib/index.mjs"
bin = "bin"
files = "lib/**/*.mjs"
include = ["README.md"]

[exports]
__DOT__ = "lib/index.mjs"
foo = "lib/foo.mjs"

[release]
changes = "Changes"
tagPrefix = "v"
bump = "patch"
access = "public"

[release.preflight]
dirty = "dist.toml"
airplane = false

[release.after]
commit = ["Changes", "dist.toml", "package.json"]
bump = true
```

# Key Concepts

## package.json is generated

Run:

``` bash
rzil pkg
```

This creates:

- `package.json`
- `bin` entries from `gather.bin`
- `exports` map
- dependencies from `[prereqs]`
- engines from `node` and `npm`
- repository + homepage derived from git remote

Do not edit `package.json` manually.


## Prereqs

Prereqs or dependencies can be set via `[prereqs]` and friends:

``` toml
[prereqs]
node = ">= 18 < 22"
npm  = ">= 9"
foo  = "^1.2.3"
bar  = 0
baz  = "latest"
```

Mapping:

- `node` → `engines.node`
- `npm` → `engines.npm`
- others → `dependencies`
- `0` → `"*"`
- Spaces in ranges are normalized (`"> 18 < 22"` → `">18 <22"`)

```toml
[prereqs.test]
tap = "latest"

[prereqs.peer]
left-pad = "latest"
```

## Autoprereqs

When enabled, rzil scans your source tree and fills in missing dependencies:

``` toml
[autoprereqs]
# enabled when the table exists
# enabled = true
ignore = ["node:fs"]
```

- Runtime imports (uses the `[gather]` paths/globs) are added to
  `dependencies`.
- Test imports (from `t/`, `test/`, `tests/`, `__tests__/`) are added to
  `devDependencies`.
- Inferred versions default to `"latest"`.
- Explicit entries in `[prereqs]` always win.
- Relative imports and Node builtins are ignored.

## Gather

``` toml
[gather]
main = "lib/index.mjs"
bin  = ["bin", "cli"]
files = "lib/**/*.mjs"
include = ["README.md"]
```

- `main` defines the root export `"."`
- `bin` auto-discovers CLI files
- `files` and `include` populate `package.json.files`
- `bin` supports string or array
- You don't need to add your license file, it is taken from `license.file`.


## License

``` toml
[license]
spdx = "MIT"
file = "LICENSES/MIT.txt"
```

- `spdx` becomes `package.json.license`.
- `file` is written as `LICENSE` in the build artifact.


## Exports

``` toml
[exports]
__DOT__ = "lib/index.mjs"
foo = "lib/foo.mjs"

[exports.deny]
testing = true
```

Rules:

- `__DOT__` → `"."`
- other keys → `"./key"`
- `exports.deny` removes subpaths
- `exports.deny.__DOT__` is forbidden

If `[exports]` exists, you fully control the export surface.


## Repository

``` toml
[repository]
remote = "origin"
provider = "github"   # optional
```

rzil:

- reads `git remote`
- derives `repository.url`
- derives `repository.homepage`
- derives `bugs.url` if `[bugtracker]` exists
- supports github, gitlab, codeberg, bitbucket
- supports private hosts with `provider`

rzil never pushes automatically.


## Release Workflow

``` bash
rzil release
```

Steps:

1.  Check `{{ NEXT }}` in Changes has entries
2.  Run tests
3.  Check git dirty state (with allowlist)
4.  Create a build directory (`.build/<id>/` and `.build/current`)
5.  Copy only publishable files into the build directory
6.  Generate `package.json` (and other build artifacts) into the build
    directory
7.  Finalize Changes
8.  Commit release
9.  Tag release
10. `npm publish` from `.build/current` (unless airplane mode)
11. Restore `{{ NEXT }}`
12. Bump version (optional)
13. Commit post-release files


You push manually.

Airplane mode disables network actions.

## Status

Early-stage but functional.

## Code of Conduct

Be human.

## Developer notes about this package

### Semver

This project does not adhere to semver and one should not rely on the version
x.y.z notation to infer stability or reliability. Read the Changes file to see
any updates a version may bring. The fact that this module sits currently at
0.x.z ranges does not indicate alpha or beta or even unstable associations. It
is just a number and we started at 0.0.1.

In general the following hard guarantee will be given: We will not break your
code. In case we do happen to cause breakage: we will fix it accordingly.

In case we foresee breaking changes we'll add deprecation warnings. Giving you
time to fix things before a breaking change will be introduced. When a change
will be introduced is communicated in the Changes file. Security fixes may
cause breakage at any given time without notice.

This package is released by itself, so obviously changes to `package.json` will
be overridden. Versioning for humans yo, not machines. Rawr.