Mono CLI โ
mono-cli installs the private mono libraries โ mono-utils, mono-helper, mono-devextreme โ into your app without spending a shared GitHub token on every pnpm install.
Not stable yet โ do not adopt it
This page documents how it works and where it stands. It is not a migration guide. Keep installing the libraries the way Getting started describes: the catalog:internal git specs with a classic token.
The mono-* templates already run mono-cli, which is why you may see workspace:* in their package.json. That is deliberate โ they are where it is being proven. Leave them alone, and do not move your own project across until this warning comes off. Why it is not stable yet has the detail.
The problem it solves โ
Apps pull the private libraries through pnpm git specs with a token baked into the URL:
yaml
# pnpm-workspace.yaml
catalogs:
internal:
mono-utils: git+https://ghp_xxxxxxxxxxxx@github.com/EJI-ICT/libs#path:/packages/mono-utils
mono-helper: git+https://ghp_xxxxxxxxxxxx@github.com/EJI-ICT/libs#path:/packages/mono-helperOne shared classic PAT, committed in plaintext, charged against a single rate limit by every developer on every install. Anyone with read access to the repo can lift the token out of git history, and it grants whatever that token's scopes allow โ which for a classic PAT is usually every repo its owner can reach.
mono-cli replaces the download with a local checkout under .mono/packages/, registered as pnpm workspace projects. A developer who has been invited to the repo reaches it with their own machine credentials (Git Credential Manager, gh auth), which have no REST rate limit. The PAT becomes a fallback most people never touch.
How it works โ
sh
mono-cli igit ls-remoteโ proves access and returns the head SHA in one call.- Stops there if every requested package is already at that SHA. A no-op run is about two seconds and downloads nothing.
- Otherwise takes one shallow checkout of the monorepo, however many packages were asked for.
- Copies each package's publish subset into
.mono/packages/<name>/. - Registers
.mono/packages/*inpnpm-workspace.yaml, repointspackage.jsonatworkspace:*, and deletes the catalog entries it replaced โ tokens included. - Runs
pnpm install.
Commands โ
sh
mono-cli i # all mono packages
mono-cli i -p mono-utils mono-helper # a subset (dependencies pulled in)
mono-cli i --no-install # skip the trailing `pnpm install`
mono-cli i --dry-run # show every change, write nothing
mono-cli list # what is installed, offline
mono-cli remove -p mono-helper # delete a package and forget itTransports โ
Tried in order. The first three cost the token nothing:
| authenticates with | |
|---|---|
git clone --depth=1 | your own machine credentials |
git init + fetch --depth=1 | same; survives a blocked clone, and the only path that accepts a pinned SHA |
| token over git | https://x-access-token:<TOKEN>@โฆ, same shallow clone |
| REST + archive ZIP | only when git is not installed at all |
All four produce a byte-identical tree โ core.autocrlf=false, core.eol=lf and core.symlinks=false are set on every git call so the commit cache cannot silently lie. Force one with --transport=git|token (or MONO_CLI_TRANSPORT). --transport=git disables the token fallback entirely, which is what you want in CI, where a silent fallback would hide a missing invite.
What lands on disk โ
Only the publish subset โ package.json, whatever files lists, and README/LICENSE. mono-helper goes from 47 MB of demo/, src/, tests/ and plan/ down to roughly 4 MB of dist/.
The copied package.json has its devDependencies and scripts stripped. Every mono package ships a prebuilt dist/, so nothing needs building, and leaving the dev deps in would make your app install mono-helper's VitePress/Playwright toolchain on every pnpm install.
State under .mono/ โ
| file | |
|---|---|
packages/<name>/ | the installed packages |
packages-commit-cache.json | the SHA each package came from; drives the skip |
packages-access-cache.json | negative-only "this machine cannot reach the repo", 6 h TTL |
packages.lock | one run at a time |
All of it is disposable โ delete .mono/ and the next run rebuilds it.
Configuration โ
| default | flag | |
|---|---|---|
MONO_CLI_GITHUB_TOKEN | โ | PAT for the fallback tiers |
MONO_CLI_REPO | https://github.com/EJI-ICT/libs | --repo |
MONO_CLI_REF | main | --ref โ branch, tag or 40-hex commit |
MONO_CLI_TRANSPORT | auto | --transport |
MONO_CLI_GIT_METHOD | auto | --git-method |
MONO_CLI_PROBE_TIMEOUT_MS | 60000 | |
MONO_CLI_FETCH_TIMEOUT_MS | 300000 |
Precedence is flag โ env โ mono.config.* โ default.
Why it is not stable yet โ
Not because the idea is wrong โ the token problem is real and this is the right shape of fix. It is that the implementation is days old and the failure modes it has already produced were destructive ones.
It is four commits old, and three of them are bug fixes. The whole package was written and patched inside one evening:
c837f4ca | initial git-method resolution and materialisation |
63ed1472 | fix: survive a directory that cannot be renamed |
768ca260 | fix: never destroy an installed node_modules on update |
2e4bf56a | fix: do not re-ignore .mono when the app tracks its manifests |
Two of those are the kind that eat someone's working tree. An earlier build wiped node_modules during an update; another rewrote your .gitignore back to a bare .mono rule, silently undoing the negation lines that keep the tracked package manifests in git. Both are fixed โ but they are what "four commits old" means in practice, and nobody outside the templates has exercised the fixed builds yet.
A fresh clone cannot bootstrap itself. Once package.json says workspace:*, pnpm install fails before any script can run โ pnpm resolves the workspace first, so it hits ERR_PNPM_WORKSPACE_PKG_NOT_FOUND for packages mono-cli has not fetched yet. The workaround is to commit each package's manifest under .mono/packages/*/package.json, which then needs a four-rule .gitignore and a .gitattributes entry to survive Windows line endings. That is three files of scaffolding a project must get exactly right before its first install works.
It does not actually remove the token. mono-cli itself is still a plain git dependency installed from a tokenized URL, and a fresh clone needs MONO_CLI_GITHUB_TOKEN to pnpm dlx it before anything else. The shared PAT count goes from three specs to one โ better, not gone.
It depends on a pnpm limitation that may move. The packages are written as bare workspace:* rather than a catalog:mono entry only because pnpm 10.7 rejects every local protocol inside a catalog (ERR_PNPM_CATALOG_ENTRY_INVALID_WORKSPACE_SPEC; link: and file: are refused too, as "not yet supported"). If pnpm lifts that, the shape of the generated package.json changes again.
The tests pass, and that is not the reassurance it sounds like. mono-cli has 85 unit tests across transport, package resolution, git and rewiring, and they are green. Every bug in the table above got through them, because they are all integration failures โ a directory Windows would not rename, an update path that deleted node_modules, a .gitignore rewrite. Those live in the seams between the CLI and a real working tree, which is exactly what unit tests do not cover, and the only working trees it has met so far are the three mono-* templates on one developer's machine.
What to do instead, for now โ
Keep the catalog:internal git specs from Getting started. If you want the rate-limit relief today without any of this, the other token-free path is stable and unrelated: mono sync already clones your federated apps with your own git credentials. That covers .mono/apps/; mono-cli is the one still settling, and it covers .mono/packages/.
Track it in the Template Changelog.