# tinfoil
Distribution automation for [Burrito](https://github.com/burrito-elixir/burrito)-based Elixir CLIs.
Be to Burrito what [cargo-dist](https://opensource.axo.dev/cargo-dist/) is to Cargo: a
single tool that takes your `mix release` output to platform binaries in a GitHub
Release, with Homebrew and installer support, in under 30 minutes of setup.
> **Status:** v0.1 — generate-and-forget. `mix tinfoil.init` scaffolds a
> self-contained GitHub Actions workflow. Later versions will evolve the workflow
> to call `mix tinfoil.*` tasks directly, the way cargo-dist does.
## The problem
Burrito solves binary packaging. Nobody has solved what comes after:
CI matrix builds, GitHub Releases, checksums, Homebrew formulas, installer
scripts. Every team shipping a Burrito-based CLI (Next LS, lazyasdf, etc.)
hand-rolls the same pipeline. tinfoil is that pipeline, as a Hex package.
## Installation
Add tinfoil to your dev dependencies alongside Burrito:
```elixir
def deps do
[
{:burrito, "~> 1.0"},
{:tinfoil, "~> 0.1", only: :dev, runtime: false}
]
end
```
Then add a `:tinfoil` key to `project/0` in `mix.exs`:
```elixir
def project do
[
app: :my_cli,
version: "0.1.0",
# ... standard project config ...
tinfoil: [
targets: [:darwin_arm64, :darwin_x86_64, :linux_x86_64, :linux_arm64],
homebrew: [
enabled: true,
tap: "owner/homebrew-tap"
],
installer: [
enabled: true
]
]
]
end
```
Then run:
```sh
mix deps.get
mix tinfoil.init
```
That generates `.github/workflows/release.yml` and any enabled extras
(installer, Homebrew formula template, tap update script). Commit the
generated files, push a tag like `v0.1.0`, and watch the workflow build
and publish platform binaries to a GitHub Release.
## What you get
```
your-project/
├── .github/workflows/release.yml ← CI pipeline (always)
├── .tinfoil/formula.rb.eex ← if homebrew enabled
├── scripts/
│ ├── install.sh ← if installer enabled
│ └── update-homebrew.sh ← if homebrew enabled
└── mix.exs
```
The workflow builds for every configured target in parallel, packages
each binary into a `.tar.gz` with a SHA256 sidecar, creates a GitHub
Release, and (optionally) pushes an updated Homebrew formula to your tap.
## Tasks
| Task | Description |
| ------------------------ | ----------- |
| `mix tinfoil.init` | Interactive scaffold — writes config guidance and generates the workflow. |
| `mix tinfoil.generate` | Regenerate the workflow and scripts from the current config. Run after editing `:tinfoil` in mix.exs or upgrading tinfoil. |
| `mix tinfoil.plan` | Show what would be built and released. Supports `--format human` (default), `--format json`, and `--format matrix` for GitHub Actions consumption. |
v0.2+ will add `mix tinfoil.build` and `mix tinfoil.publish`, and evolve the
generated workflow to call these tasks directly.
### `mix tinfoil.plan`
Read-only preview of the release plan:
```sh
$ mix tinfoil.plan
tinfoil plan for my_cli 1.2.3
target runner archive
───────────── ──────────────── ──────────────────────────────────────────────
darwin_arm64 macos-latest my_cli-1.2.3-aarch64-apple-darwin.tar.gz
darwin_x86_64 macos-13 my_cli-1.2.3-x86_64-apple-darwin.tar.gz
linux_x86_64 ubuntu-latest my_cli-1.2.3-x86_64-unknown-linux-musl.tar.gz
linux_arm64 ubuntu-24.04-arm my_cli-1.2.3-aarch64-unknown-linux-musl.tar.gz
format: tar_gz (sha256)
github: owner/my_cli (draft: false)
homebrew: disabled
installer: disabled
```
For CI consumption, `--format matrix` emits a compact GitHub Actions
matrix fragment:
```yaml
- id: plan
run: echo "matrix=$(mix tinfoil.plan --format matrix)" >> "$GITHUB_OUTPUT"
build:
needs: plan
strategy:
matrix: ${{ fromJson(needs.plan.outputs.matrix) }}
```
## Supported targets
| Target | Triple | GitHub runner |
| --------------- | ------------------------------- | ------------------- |
| `:darwin_arm64` | `aarch64-apple-darwin` | `macos-latest` |
| `:darwin_x86_64`| `x86_64-apple-darwin` | `macos-13` |
| `:linux_x86_64` | `x86_64-unknown-linux-musl` | `ubuntu-latest` |
| `:linux_arm64` | `aarch64-unknown-linux-musl` | `ubuntu-24.04-arm` |
Triples follow the standard Rust-style convention since that is what
users expect to see in release asset names.
Windows support is deferred until Burrito's Windows story stabilizes.
## Configuration reference
```elixir
tinfoil: [
# Required. Targets to build.
targets: [:darwin_arm64, :linux_x86_64],
# Archive naming template. Interpolations: {app}, {version}, {target}.
archive_name: "{app}-{version}-{target}",
archive_format: :tar_gz,
# GitHub Release configuration. repo is inferred from `git remote get-url
# origin` if omitted.
github: [
repo: "owner/my_cli",
draft: false
],
# Homebrew formula generation. Requires a HOMEBREW_TAP_TOKEN secret with
# repo access to the tap.
homebrew: [
enabled: true,
tap: "owner/homebrew-tap",
formula_name: "my_cli" # defaults to the app name
],
# Shell installer script.
installer: [
enabled: true,
install_dir: "~/.local/bin"
],
checksums: :sha256,
ci: [
provider: :github_actions,
# elixir_version is auto-detected from the project's :elixir
# requirement if not explicitly set; these are the current fallbacks.
elixir_version: "1.19",
otp_version: "28",
zig_version: "0.15.2"
]
]
```
The only required key is `:targets`. Everything else has a sensible default.
## How it compares
- **Burrito** builds self-contained binaries. tinfoil orchestrates everything
that happens *around* that build. They're peers, not rivals.
- **cargo-dist** is the architectural inspiration. tinfoil borrows the
"generate-and-delegate" model and the idea that the intelligence should
live in the tool, not in hand-rolled YAML.
- **Next LS's release pipeline** is the state of the art for Burrito today —
bespoke, sophisticated, and impossible to reuse. tinfoil is what "the
reusable version of that" would look like.
## License
MIT.