# Publishing to Hex
This guide explains how Releaser publishes monorepo packages to Hex in
the correct order, handling internal dependencies automatically.
## The problem
In a monorepo, packages depend on each other via `path:` references:
```elixir
# apps/api/mix.exs
defp deps do
[{:my_core, path: "../core"}]
end
```
But Hex doesn't understand `path:` — it needs version constraints:
```elixir
{:my_core, "~> 1.1"}
```
So to publish `api`, you need to:
1. First publish `core` to Hex
2. Change `api`'s dep from `path:` to the published version
3. Publish `api`
4. Change everything back to `path:` for local development
With 34 packages and nested dependencies, doing this manually is painful.
Releaser automates the entire process.
## How it works
### Step 1: See the plan
```bash
$ mix releaser.publish --dry-run
=== Releaser Publish ===
Level 0:
my_core v1.1.0
Level 1:
my_api v1.2.4 (deps: my_core)
my_worker v0.5.1 (deps: my_core)
--dry-run: nothing will be published
my_api mix.exs changes:
{:my_core, path: "..."} → {:my_core, "~> 1.1"}
my_worker mix.exs changes:
{:my_core, path: "..."} → {:my_core, "~> 1.1"}
```
This shows:
- **Level 0**: Packages with no internal deps are published first
- **Level 1**: Packages that depend on level 0
- **Path replacement**: What `path:` deps become
### Step 2: Publish
```bash
$ mix releaser.publish
```
For each package (in dependency order), Releaser:
1. Backs up the original `mix.exs`
2. Replaces `{:my_core, path: "../core"}` → `{:my_core, "~> 1.1"}`
3. Injects `package/0` with license and links if missing
4. Runs `mix hex.publish --yes`
5. Restores the original `mix.exs`
If **any** publish fails, all `mix.exs` files are restored immediately.
### Step 3: Verify
After publishing, your `mix.exs` files are back to using `path:` for
local development. Nothing changes in your working tree.
## Publish options
### Bump before publishing
```bash
# Bump all packages by patch before publishing
$ mix releaser.publish --bump patch
```
### Publish specific packages
```bash
# Only publish my_api (automatically includes my_core because it depends on it)
$ mix releaser.publish --only my_api
```
This resolves transitive dependencies: if `api` depends on `core`, and
`core` depends on `openssl`, all three are published in the right order.
### Publish to a Hex organization
```bash
$ mix releaser.publish --org my_company
```
## Real-world example
Here's a 34-package monorepo with 4 levels of dependencies:
```bash
$ mix releaser.publish --dry-run
=== Releaser Publish ===
Level 0:
cfdi_catalogos v4.0.16
cfdi_complementos v4.0.17
cfdi_transform v4.0.14
clir_openssl v0.0.17
saxon_he v12.5.2
... (23 more)
Level 1:
cfdi_csd v4.0.16 (deps: clir_openssl)
cfdi_designs v1.0.0 (deps: cfdi_xml2json, cfdi_utils, cfdi_types, cfdi_complementos)
Level 2:
cfdi_xml v4.0.18 (deps: cfdi_csd, cfdi_transform, cfdi_complementos, cfdi_catalogos, cfdi_xsd, saxon_he)
sat_auth v1.0.1 (deps: cfdi_csd)
Level 3:
cfdi_cancelacion v0.0.1 (deps: sat_auth)
cfdi_descarga v0.0.1 (deps: sat_auth)
cfdi_csd mix.exs changes:
{:clir_openssl, path: "..."} → {:clir_openssl, "~> 0.0"}
cfdi_xml mix.exs changes:
{:cfdi_csd, path: "..."} → {:cfdi_csd, "~> 4.0"}
{:cfdi_transform, path: "..."} → {:cfdi_transform, "~> 4.0"}
{:cfdi_complementos, path: "..."} → {:cfdi_complementos, "~> 4.0"}
{:cfdi_catalogos, path: "..."} → {:cfdi_catalogos, "~> 4.0"}
{:cfdi_xsd, path: "..."} → {:cfdi_xsd, "~> 4.0"}
{:saxon_he, path: "..."} → {:saxon_he, "~> 12.5"}
```
## The `package/0` injection
Many monorepo packages don't have `package/0` defined because they're not
published individually. Releaser automatically injects it during publish:
```elixir
defp package do
[
licenses: ["MIT"],
links: %{"GitHub" => "https://github.com/me/project"},
files: ~w(lib mix.exs README.md LICENSE)
]
end
```
You can customize the defaults in your config:
```elixir
releaser: [
publisher: [
package_defaults: [
licenses: ["Apache-2.0"],
links: %{"GitHub" => "https://github.com/me/project"},
files: ~w(lib priv mix.exs README.md LICENSE)
]
]
]
```
## Prerequisites
- Each app must have a `description` in its `mix.exs`
- The app must compile without errors
- You must be authenticated to Hex (see below)
### Hex authentication
Releaser supports both auth modes that `mix hex.publish` accepts:
**Local interactive auth** (good for personal machines):
```bash
mix hex.user auth
# Prompts for username and password, then persists a token.
```
After that, `mix releaser.publish` just works.
**Environment variable** (good for CI / one-off pushes):
```bash
HEX_API_KEY=<your_key> mix releaser.publish
```
Get a key at <https://hex.pm/dashboard/keys>. The value is an `auth` scoped
key with `write` permission.
If neither method is available, `mix releaser.publish` aborts with a clear
message instead of hanging on the interactive password prompt.
### Live output
`mix releaser.publish` streams the output of `mix hex.publish` to your
terminal in real time — you will see the upload progress, checksum, and
URLs as Hex prints them, rather than a single buffered dump at the end.
## Before publishing: check status
Use `mix releaser.status` to see what needs publishing:
```bash
$ mix releaser.status
=== Release Status ===
Package Local Hex Status
cfdi_xml 4.0.19 4.0.18 ahead
cfdi_csd 4.0.16 4.0.16 published
cfdi_complementos 4.0.18-dev.1 4.0.17 pre-release
my_new_app 0.1.0 — unpublished
2 package(s) need publishing.
Run mix releaser.publish --dry-run to see the plan.
```
## Recommended workflow
```bash
# 1. Develop with pre-release tags
mix releaser.bump cfdi_xml patch --tag dev
# ... make changes ...
mix releaser.bump cfdi_xml patch --tag dev
# 2. When ready, release
mix releaser.bump cfdi_xml release
# 3. Check what's pending
mix releaser.status
# 4. Preview the publish plan
mix releaser.publish --dry-run
# 5. Publish
mix releaser.publish
```