monopam#
Monorepo manager for OCaml.
Core Idea#
A monorepo is a directory of subtrees. Each subtree mirrors an external git repository. You edit code in the monorepo, build, test, commit — then sync with upstream.
Each subtree has:
- source: where you pull from (any git repo, defaults to your monorepo)
- origin: where you push to (always your monorepo repo)
Push always goes to your monorepo's git remote — a repo you own. You never accidentally push to someone else's repo. Per-subtree source overrides let you pull from upstream repos you don't own.
Installation#
opam install monopam
Quick Start#
# Initialize a workspace
monopam init --handle yourname.bsky.social
# Add packages (pulls from upstream, pushes to your monorepo)
monopam add https://github.com/mirage/eio.git
monopam add crowbar # resolve from opam
# Pull upstream changes
monopam pull
# Make changes, build, test, commit
dune build && dune test
git add -A && git commit -m "Add feature"
# Push to your monorepo remote
monopam push
Commands#
| Command | Description |
|---|---|
monopam add <source> [name] |
Add a subtree |
monopam remove <name> |
Remove a subtree |
monopam pull [names...] |
Pull updates from source |
monopam push [names...] |
Push changes to your monorepo remote |
monopam status [names...] |
Show sync state |
monopam diff [names...] |
Show changes |
monopam publish |
Generate opam overlay |
monopam init |
Initialize workspace |
sources.toml#
Subtree metadata lives in one file. The top-level origin is your
monorepo's git remote — the only place push ever writes to.
Per-subtree entries override where pull reads from.
# Your monorepo remote — push always goes here
origin = "git@github.com:me/mono.git"
# Subtrees that pull from upstream (override source)
[eio]
source = "https://github.com/mirage/eio.git"
[cohttp]
source = "https://github.com/mirage/cohttp.git"
branch = "main"
# Subtrees with no entry pull from origin and push to origin.
# No need to list your own projects.
add <url>adds an entry withsourceoverride- Subtrees not listed use
originas both source and push target pushalways goes to the top-leveloriginpulluses per-subtreesourceif set, otherwiseorigin
A subtree that is itself a monorepo can be marked with mono = true:
[open-mono]
source = "git@github.com:me/mono.git"
mono = true
This tells push and pull to recurse into open-mono/ and process
its own sources.toml first (depth-first). See Layers.
Source Resolution#
The <source> argument to add can be:
- A URL:
https://github.com/mirage/eio.git— used directly - A package name:
crowbar— resolved via opam'sdev-repofield - A URL#ref:
https://github.com/mirage/eio.git#v0.15— pins to a ref
Default branch is the remote's HEAD, not hardcoded.
Layers#
A monorepo is just a subtree. You can nest monorepos to create layers.
product/
sources.toml
origin = "git@private.com:co/product.git"
[open-mono]
source = "git@github.com:me/mono.git"
mono = true
[secret-lib]
source = "git@private.com:co/secret-lib.git"
open-mono/
sources.toml
origin = "git@github.com:me/mono.git"
[eio]
source = "https://github.com/mirage/eio.git"
[cohttp]
source = "https://github.com/mirage/cohttp.git"
eio/
cohttp/
mylib/
secret-lib/
app/
Same tool, same commands, same sources.toml at every level. The only
thing that changes is which directory you're in.
Changes flow one layer at a time:
- Inward (pull): upstream eio → open-mono → product
- Outward (push): product → open-mono → your fork → PR to upstream
monopam push is recursive. From the product directory, it detects that
open-mono/ has its own sources.toml and pushes inner layers first
(depth-first), then the outer layer. One command propagates changes
all the way out.
This gives you open-source + closed-source separation naturally. Each layer is a separate git repo with its own access controls. Dune sees all directories and builds everything together.
Overlays#
Each layer can have its own opam-repo overlay for publishing package metadata:
open-mono/→ public opam-repo overlayproduct/→ private opam-repo overlay (references public as base)
monopam publish generates opam entries for the current monorepo's
packages.
Daily Workflow#
# Get latest from upstream
monopam pull
# Work
dune build && dune test
git add -A && git commit -m "Description"
# Send changes to your repos
monopam push
Diff#
monopam diff # What you would push
monopam diff --incoming # What pull would bring in
monopam diff eio # Specific subtree
Collaboration (Verse)#
Verse lets you browse and pull from collaborators' monorepos.
# See what collaborators have
monopam verse diff
# Pull their changes
monopam verse pull alice.bsky.social
# Cherry-pick a specific commit
monopam verse cherrypick <sha>
# List members
monopam verse members
Collaboration is just "pull from a different source." A collaborator's monorepo is another subtree you can pull from.
Design Principles#
- Push is always safe. Push goes to your monorepo remote. Never to someone else's repo.
- Pull and push are the only sync verbs. No
synccommand. Pull first, build and test, then push. These steps should never be combined. - A monorepo is a subtree. Layers emerge from nesting. Same tool at every level.
- One manifest, mostly overrides.
sources.tomldefines your remote and overrides per-subtree sources. Your own projects need no entry. - Name resolution.
add crowbarshould work, not just URLs.
Licence#
ISC