Current section
Files
Jump to
Current section
Files
README.md
# Plumb
A `mix publish` task that releases a package to Hex.
Its one idea: **every check runs before anything is written.** A run that fails
verification leaves the repository exactly as it was — no bumped `mix.exs`, no
commit to amend, no local tag to delete.
```bash
mix publish patch # 0.1.1 -> 0.1.2
mix publish minor # 0.1.1 -> 0.2.0
mix publish major # 0.1.1 -> 1.0.0
mix publish 0.4.0-rc.1 # an explicit version, which must sort above the current one
```
## Installation
```elixir
def deps do
[
{:plumb, "~> 0.2", only: :dev, runtime: false}
]
end
```
## What it does, in order
Nothing is written until every one of these has passed:
1. The git working tree is clean.
2. The target tag does not exist **locally**.
3. The target tag does not exist **on the remote** — checked with `git ls-remote`
up front, rather than discovered at push time with a commit and a tag already
made.
4. The README install snippet fits the version being released (opt-in, below).
5. `mix format --check-formatted`.
6. `mix test`.
7. `mix docs` builds.
Then, and only then:
7. `mix.exs` is rewritten with the new version.
8. It is committed as `Release vX.Y.Z`.
9. An annotated tag `vX.Y.Z` is created.
10. `HEAD` and the tag are pushed.
11. `mix hex.publish` uploads the package and its documentation.
The tag is pushed **before** the upload, so the `source_ref` in the published
documentation resolves the moment the docs go live rather than 404ing until you
remember to push it.
## Why the docs build is a release gate
`mix docs` failing — or emitting warnings you have learned to scroll past — means
the published page has broken links: a `@doc` pointing at a function that was
renamed, or at a module marked `@moduledoc false`. No test catches that, because
nothing is wrong at runtime. It is only wrong for the person reading your
documentation, and by then it is published.
Running it as a gate turns that into something you fix before the release rather
than after it.
## The README check
A README telling people to depend on `~> 0.1.1` when you have just published
`0.2.0` is wrong in a way nothing else catches: the code compiles, the tests pass,
the docs build, and everyone following the install instructions silently gets the
old release.
It is off unless asked for, since README shapes vary:
```elixir
def project do
[
plumb: [readme: :exact]
]
end
```
- **`:satisfies`** — the version being released must satisfy the requirement the
README names. Imposes no style; catches a release falling outside the range the
README allows at all.
- **`:exact`** — the README must name exactly `~> MAJOR.MINOR` of the version being
released. For a 0.x project, where a minor bump is a breaking change, this is the
honest one: `~> 0.3` tells a reader that `0.4.0` is a safe upgrade, and it is not.
- **`:readme_path`** — defaults to `"README.md"`.
The check reads the version **being released**, not the one in `mix.exs`. Releasing
`0.4.0` from a README that says `~> 0.3` fails before the bump, so you fix the
snippet and re-run rather than discovering it after publishing.
## Where the version comes from
A `@version "..."` module attribute is preferred, and is what you want anyway for
`source_ref` in your docs config:
```elixir
@version "0.3.0"
def project do
[version: @version, docs: [source_ref: "v#{@version}"]]
end
```
A literal `version: "0.3.0"` in the project config works too. Only the first
occurrence is rewritten, and only when the version is a literal string — a
`version: @version` that points at an attribute is left alone, so the attribute is
what gets bumped.
A `mix.exs` that computes its version some other way is refused rather than
guessed at.
## Options
- `--dry-run` — run every check and build the tarball, report what would follow,
write nothing
- `--remote NAME` — the git remote to push to, default `origin`
- `--allow-untracked` — let untracked files through the clean-tree check. Tracked
changes still block it, and untracked files are still not committed
- `--skip CHECKS` — comma-separated checks to leave out: `format`, `test`, `docs`.
An escape hatch for a project with no `ex_doc`, not a habit
```bash
mix publish minor --dry-run
mix publish patch --remote upstream
mix publish patch --skip docs
```
## What it deliberately does not do
**Changelog entries.** Generating one means either parsing commit messages or
templating a stub, and both produce a changelog that reads like a git log. If you
want that, [`expublish`](https://hex.pm/packages/expublish) does it well and covers
much of the same ground.
**Publishing from CI.** The task prompts through `mix hex.publish`, which is the
confirmation step that stops an accidental release. Automating that away is the
opposite of the point.
## License
MIT