Monorepo management for opam overlays
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

OCaml 89.1%
Perl 10.5%
Dune 0.1%
Shell 0.1%
Other 0.3%
376 2 0

Clone this repository

https://tangled.org/anil.recoil.org/monopam https://tangled.org/did:plc:nhyitepp3u4u6fcfboegzcjw/monopam
git@git.recoil.org:anil.recoil.org/monopam git@git.recoil.org:did:plc:nhyitepp3u4u6fcfboegzcjw/monopam

For self-hosted knots, clone URLs may differ based on your setup.

Download tar.gz
README.md

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#

Install with opam:

$ opam install monopam

If opam cannot find the package, it may not yet be released in the public opam-repository. Add the overlay repository, then install it:

$ opam repo add samoht https://tangled.org/gazagnaire.org/opam-overlay.git
$ opam update
$ 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
monopam fetch [names...] Fetch updates without merging
monopam clean Remove build artifacts and stale state
monopam test [names...] Run tests for subtrees
monopam lint [names...] Run merlint across subtrees
monopam verse ... Collaborate with verse members

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 with source override
  • Subtrees not listed use origin as both source and push target
  • push always goes to the top-level origin
  • pull uses per-subtree source if set, otherwise origin

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's dev-repo field
  • 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 overlay
  • product/ → 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#

  1. Push is always safe. Push goes to your monorepo remote. Never to someone else's repo.
  2. Pull and push are the only sync verbs. No sync command. Pull first, build and test, then push. These steps should never be combined.
  3. A monorepo is a subtree. Layers emerge from nesting. Same tool at every level.
  4. One manifest, mostly overrides. sources.toml defines your remote and overrides per-subtree sources. Your own projects need no entry.
  5. Name resolution. add crowbar should work, not just URLs.

Licence#

ISC