Orchestrates publishing multiple apps to Hex in topological order.
For each app (in dependency order):
- Backs up the original
mix.exs - Replaces
path:deps with their published Hex versions (~> X.Y) - Injects
package/0metadata if missing - Runs
mix hex.publish --yes - Restores the original
mix.exs(always, even on failure)
Blocking detection
An app marked releaser: [publish: true] cannot actually be published to Hex
if any of its direct or transitive internal deps is publish: false — the
resulting Hex package would reference a path dep that has no Hex counterpart.
plan/1 detects this and excludes blocked apps from the publish plan,
surfacing them in the :skipped list with reason: :blocked_by_deps and a
:blocked_by field listing the IMMEDIATE non-publishable causes.
Two public helpers are exposed for external consumers (e.g. mix releaser.graph):
blocked_names/1— returns theMapSetof blocked app names.blocked_with_reasons/1— returns%{name => [immediate_blocker, ...]}.
Both run an iterative worklist (fixed-point over the blocked set) so cycles
among publishable apps terminate, and transitive blocking is discovered
level by level. Worst case is O(apps * deps) per iteration with at most
length(apps) iterations — i.e. cubic in app count, which is fine for
monorepos with hundreds of apps.
Summary
Functions
Returns the set of publishable app names that cannot be published because at
least one direct or transitive internal dep is non-publishable (publish: false).
Returns a map %{blocked_app_name => [immediate_blocking_dep_name, ...]}.
Executes the publish flow.
Plans the publish order and returns a list of levels with app info.
Resolves the version to use when rewriting a path: dep to a Hex dep.
Restores backed-up mix.exs files.
Functions
@spec blocked_names([Releaser.App.t()]) :: MapSet.t(String.t())
Returns the set of publishable app names that cannot be published because at
least one direct or transitive internal dep is non-publishable (publish: false).
Apps with publish: false are NEVER members of the returned set — they are
causes, not effects. Cycles among publishable apps terminate via fixed-point
iteration (the blocked set is monotone-growing and bounded by length(apps)).
@spec blocked_with_reasons([Releaser.App.t()]) :: %{ required(String.t()) => [String.t()] }
Returns a map %{blocked_app_name => [immediate_blocking_dep_name, ...]}.
Same algorithm as blocked_names/1, but exposes WHY each blocked app is
blocked. The list contains only deps from the app's own :deps field that
are themselves either publish: false OR already known to be blocked. It is
the IMMEDIATE cause, NOT the transitive root.
Executes the publish flow.
Plans the publish order and returns a list of levels with app info.
Does not modify anything. Filters out apps whose local version is
already on Hex (status == :published), so publish is idempotent.
Returns a map with:
:levels— topological levels after filtering (only apps that need publishing):apps— publishable apps that survived blocking and Hex status checks:graph— dep graph:skipped— apps filtered out, each entry%{app: name, local: v, hex: v, reason: r}whereris one of::already_published/:prerelease— Hex-status driven:blocked_by_deps— at least one direct or transitive internal dep is non-publishable. Carries an extra:blocked_byfield with the IMMEDIATE blocking dep names.
@spec resolve_dep_version(String.t(), %{required(String.t()) => String.t()}, [ Releaser.App.t() ]) :: String.t()
Resolves the version to use when rewriting a path: dep to a Hex dep.
Order of preference:
pub_acc— apps just published in this run (newest known version).all_apps— full workspace, including apps thatPublisher.plan/1filtered out (e.g. already-on-Hex deps) whoseapp.versionmatches what is on Hex.- Fallback
"0.0.0"only when nothing else is known. This is the last-resort sentinel and should never be reached in a healthy workspace.
This helper exists to fix a bug where find_version/2 was called with
the post-filter to_publish list, returning "0.0.0" for any dep that
was filtered out because it was already on Hex.
Restores backed-up mix.exs files.