Gleam has no workspaces.Trellis adds them.
gleam build, gleam test, and gleam publish each operate on a single package directory. Trellis is one binary that fans them across every package in the repository, then adds changelogs, versioning, and publishing on top. Everything it knows comes from the gleam.toml files you already have.
$curl --proto '=https' --tlsv1.2 -LsSf https://github.com/tylerbutler/trellis/releases/latest/download/trellis-gleam-installer.sh | shlat_core (1.2.0)
lat_mid (0.5.1)
└─ lat_core
lat_cli (0.4.3)
├─ lat_core
└─ lat_mid
lat_example (0.0.0)
└─ lat_cliOpinionated, but modular.
Trellis has one way of doing each job — TOML change fragments, a tag per package, bumps derived from fragment kinds. But the jobs are independent: nothing requires the piece above it, so you adopt a layer at a time and keep whatever already works.
Just the task runner
run,exec,list, andgraphneed no configuration at all — members are auto-discovered from git. Your changelog tool and CI stay untouched.Add changelogs and versioning
Fragments,
version plan, andversion applymanage bumps and changelogs without trellis owning your release workflow — or any CI at all.Add publishing
tagandpublishpush to Hex in dependency order, from your own scripts or workflows.The full pipeline
ci,changelog check, andrelease prturn GitHub Actions workflows into thin triggers around trellis commands.
The glue this replaces.
Without a workspace concept, a multi-package repository builds one by hand — bash loops, YAML blocks, duplicated config. Every row below is something a Gleam repository has to maintain itself, and every row can go quietly wrong:
| Where it lives | What's maintained by hand | What can drift |
|---|---|---|
justfile | The package list, in topological order, as a space-separated string | A new package is silently excluded from every recipe |
justfile | ~15 near-identical for pkg in … bash loops | Copy-paste drift between recipes; strictly serial execution |
.changie.yaml | One config block per package: label, key, changelog path, version regex | A forgotten block means a package can't be versioned or released |
publish.yml | A hand-written mirror of the path_dependency graph | Nothing verifies it when a package gains a dependency |
release.yml | 25 lines of inline sed/grep patching locked versions | Untestable regex logic living inside CI YAML |
Trellis derives the information behind each row from gleam.toml, a file format the ecosystem already uses.
Configure nothing that can be derived.
Verify anything that must be duplicated.
# The table's presence marks the workspace root.# Every key is optional — members auto-discover from git.[tools.trellis]members = ["packages/*", "examples/*"]# Exclusions are scoped by task. `@release` covers changelog,# versioning, tagging, publishing, and release CI.exclude = { docs = ["examples/*"], "@release" = ["examples/*"] }
[tools.trellis.tasks.lint]command = "gleam run -m glinter"
[tools.trellis.publish]tag_format = "{name}-v{version}"From that, trellis computes:
- The member set and its topological order — new packages join every command the moment their directory matches a glob.
- Change impact —
--since origin/mainmaps a diff to owning packages, plus the reverse-dependency closure. Only test what a PR touched. - Publish order and path-dep rewrites — dependencies publish first, path references become Hex versions automatically.
- A tag per releasable package — and a
doctorthat fails loudly when anything drifts.
One workspace. Different task sets.
Examples, benchmarks, and internal tools can remain workspace members for builds, tests, and graphing without needing generated docs or a release on Hex.
exclude gives each built-in or custom task its own member-path globs. The reserved @release key covers changelogs, versioning, tags, publishing, and release CI.
| Member | build / test | docs | release |
|---|---|---|---|
packages/* | included | included | included |
examples/* | included | excluded | excluded |
lat_core hex
lat_mid hex
lat_cli hexlat_example — the examples/ member — still appears in trellis list, graph, exec, and tasks that do not exclude it. Only the configured task sets change.One binary across the lifecycle.
The same tool runs locally and in CI. Commands work from anywhere in the workspace — the root is found by walking up, like git or cargo.
Every day
init- Write the workspace table; only needed to configure something
run- Run a task across packages, graph-parallel by default
list- Packages in topological order — dependencies first
graph- The dependency graph, as text, DOT, Mermaid, or JSON
exec- Any command, in each package directory
doctor- Validate workspace invariants; non-zero exit on any error
Cut a release
changelog- Native fragment engine — no second tool to install
version- Plan and apply bumps from unreleased fragments
tag- Compare versions against git tags; create what's missing
publish- To Hex, in dependency order, path deps rewritten
lockfile- Patch locked versions without touching the network
In CI
ci- Structured JSON for GitHub Actions matrices and outputs
changelog check- Fail a PR that changes a package without logging it
release pr- Open the release pull request, versions applied
Checks for drift.
Some things can't be derived — locked versions, changelogs, tags, toolchain pins. For those, trellis doctor checks the invariants that remain explicit and exits non-zero when one breaks.
checked: member globs resolve and every package has a parseable gleam.toml
checked: path dependencies stay inside the workspace; graph is acyclic
checked: task exclusion globs match members; no package depends on one unavailable at its release lifecycle
checked: tag format produces a unique tag per releasable package
checked: manifest.toml locked versions match workspace-internal gleam.toml versions
checked: each releasable package's version is not behind its CHANGELOG
checked: unreleased changelog fragments parse and reference valid packages, kinds, and categories
checked: [tools.trellis] carries no unrecognized or deprecated keys
checked: packages agree on the external dependencies they share
checked: gleam on PATH matches the .tool-versions pin (advisory)
ok: 4 package(s) (1 workspace, 0 git_only, 3 hex), 0 warning(s)Install a prebuilt binary.
Trellis has no runtime dependencies and includes SLSA build provenance. Its shell installer typically completes in about a second in CI.
- Shell (Linux, macOS)
$curl --proto '=https' --tlsv1.2 -LsSf https://github.com/tylerbutler/trellis/releases/latest/download/trellis-gleam-installer.sh | sh- Homebrew
$brew install tylerbutler/tap/trellis- mise / asdf (pin it in .tool-versions)
$mise use "github:tylerbutler/trellis@0.10.3"- From source
$cargo install --git https://github.com/tylerbutler/trellis
Windows installer and pinned-version URLs are on thereleases page. From there, trellis doctor works immediately — no setup. Run trellis init only when you want to configure something; the configuration guide covers what it writes.