Selecting publishers
Run a tailored subset of publishers with the --publishers allowlist and the unified --skip denylist
Anodizer runs every configured publisher by default. Two flags narrow that set when
you want a targeted release — --publishers (an allowlist) and --skip (a denylist).
Both are available on release, publish, continue, and check config, and both
accept the same publisher vocabulary, so a selection that works on one command works on
all of them. continue resumes a stalled release through the same publish pipeline, so
it validates and honors the selectors identically — a typo there is rejected before any
publisher runs, the same one-way-door guard the other commands carry.
Allowlist vs denylist
| Flag | Role | Effect when set |
|---|---|---|
--publishers <a,b,…> | allowlist | Only the named publishers run. Every other publisher — including the irreversible publish stages blob, snapcraft-publish, docker, docker-sign, and announce (the last broadcasts to webhooks/Slack/Twitter/Mastodon/Bluesky) — is deselected. (The release stage that creates the GitHub release is the substrate the others depend on and is governed by --skip=release only; see the valid-names note below.) |
--skip <a,b,…> | denylist | The named stages and publishers are removed; everything else runs. |
Both flags are comma-separated and may be repeated:
anodizer release --publishers cargo,homebrew
anodizer release --publishers cargo --publishers homebrew # equivalent
anodizer release --skip npm,dockerhubPrecedence: --skip always wins
When a publisher appears in both lists, --skip removes it. This makes the denylist a
hard veto you can layer on top of an allowlist:
# Allow cargo + homebrew, but veto homebrew anyway → only cargo runs.
anodizer release --publishers cargo,homebrew --skip homebrewEmpty means "all configured"
Omitting both flags (or passing empty values) runs every configured publisher — the default release behavior is unchanged:
anodizer release # every configured publisher runsThe valid publisher names
--publishers and --skip accept these publisher names (the canonical token for each
publisher is its own name — anodizer derives the set from the publisher registry, so it
never drifts):
| Publisher name | Also a stage name? |
|---|---|
cargo | yes |
homebrew | yes |
scoop | yes |
nix | yes |
aur | yes |
krew | yes |
winget | yes |
chocolatey | yes |
github-release | the stage token is release |
npm | publisher only |
dockerhub | publisher only |
uploads | publisher only |
artifactory | publisher only |
cloudsmith | publisher only |
gemfury | publisher only |
schemastore | publisher only |
mcp | publisher only |
upstream-aur | publisher only |
blob | the publish stage blob (object-store upload) |
snapcraft-publish | the publish stage snapcraft-publish (Snap Store upload) |
docker | the publish stage docker (image-registry push) |
docker-sign | the publish stage docker-sign (cosign signatures to the registry) |
announce | the publish stage announce (irreversible external broadcasts — webhooks, Slack, Twitter, Mastodon, Bluesky) |
A name in the yes rows resolves the same whether you reach it through --skip (which
also accepts stage tokens) or --publishers. The publisher only names have no stage
of their own, so before this selection surface the only way to gate them was per-block
config — now they honor --skip/--publishers uniformly at dispatch time.
The last five names — blob, snapcraft-publish, docker, docker-sign, announce —
are publish stages rather than dispatch publishers, but each performs an external,
irreversible push (an object store, the Snap Store, an image registry, registry
signatures, or — for announce — broadcasts to webhooks/Slack/Twitter/Mastodon/Bluesky).
They are therefore governed by the same selectors: --publishers cargo deselects them, and
--publishers blob allow-lists a blob-only upload. Like homebrew, announce depends on
the release substrate (it reads the release URL) yet is itself a leaf, so the allowlist
governs it just like the others. The one exception is the release stage that creates the
GitHub/GitLab/Gitea release: it is the substrate every other publisher depends on
(manifests reference its assets; announce needs the release URL), so it is governed by
--skip=release only and is never dropped by a --publishers allowlist.
Canonical names: homebrew, not brew; chocolatey, not choco
The selection vocabulary uses each publisher's full name. The short forms are rejected with a loud error so a typo can never silently widen or narrow a release:
$ anodizer release --skip brew
Error: invalid --skip value(s): brew. Valid options: …, homebrew, …, chocolatey, …
$ anodizer release --publishers crates
Error: invalid --publishers value(s): crates. Valid publishers: cargo, …
Use homebrew, chocolatey, and cargo (the canonical names from the table above).
Worked examples
Publish a single channel from a finished build
anodizer publish reuses artifacts already in dist/ and runs only the publish
pipeline. Pair it with --publishers to release one channel without re-running the rest:
$ anodizer publish --publishers npm
• publishing npm
• skipped cargo — not in --publishers allowlist
• skipped homebrew — not in --publishers allowlist
…Exclude one publisher from a full release
$ anodizer release --skip npm
• skipped npm — excluded via --skip
… # every other configured publisher runsTailor a release to two publishers
$ anodizer release --publishers cargo,homebrew
• publishing cargo
• publishing homebrew
• skipped npm — not in --publishers allowlist
• skipped dockerhub — not in --publishers allowlist
…Two deselect outputs: the dispatch line and the summary line
A deselected publisher is never silent. anodizer emits the deselect line at
two points in the run — in-flight (as the publish pipeline reaches the
publisher) and again in the publish summary — with the same
distinguished wording at both. The two outputs differ only in phase, not in
text: both come from the one deselected_reason source, so they always name
the same cause.
| Wording (both phases) | When it applies |
|---|---|
skipped <name> — excluded via --skip | The publisher was named in the --skip denylist. |
skipped <name> — not in --publishers allowlist | A non-empty --publishers allowlist was given and this publisher was not in it. |
The line is distinguished: it names the exact cause so you can confirm a
publisher was turned off the way you intended. When both selectors apply to one
publisher, --skip wins and the line reports the denylist cause.
The five publish stages (blob, snapcraft-publish, docker,
docker-sign, announce) run outside the dispatch loop, so a deselected one
emits a single line at its stage — skipped blob — not in --publishers allowlist / skipped docker — excluded via --skip — rather than the
in-flight-plus-summary pair. The wording is identical, so you still see exactly
which selector turned it off.
$ anodizer release --skip npm
• skipped npm — excluded via --skip # in-flight
… # every other configured publisher runs
• skipped npm — excluded via --skip # summary (same wording)$ anodizer release --publishers cargo
• publishing cargo
• skipped npm — not in --publishers allowlist # in-flight
…
• skipped npm — not in --publishers allowlist # summary (same wording)Validating a selection ahead of release
check config --publishers <names> validates the selection against the configured
publishers without running anything. Naming a publisher you forgot to configure is an
error, so you catch a selection mistake before tag time rather than at release time:
$ anodizer check config --publishers cargo
• validating configuration
• Config is valid.
$ anodizer check config --publishers npm
Error: publisher 'npm' named in --publishers is not configured (no npm publish block)
The same loud error guards a typo here as on release/publish:
$ anodizer check config --publishers crates
Error: invalid --publishers value(s): crates. Valid publishers: cargo, …Typos fail loud — one-way-door safety
Several publishers push to one-way-door registries (crates.io, Cloudsmith, Chocolatey, winget, AUR). A misread selection must never quietly publish nothing or everything, so anodizer rejects any unknown token with a nonzero exit before dispatch begins:
$ anodizer release --publishers homebrwe # typo
Error: invalid --publishers value(s): homebrwe. Valid publishers: cargo, …
$ echo $?
1
Because the error fires before the publish pipeline runs, a typo can never reach a registry — you fix the name and re-run.