Skip to content

Sync โ€‹

Since the Host and Remote live in separate repositories, there is a small helper library called mono-utils that handles syncing between them.

The templates Mono Host and Mono Remote already ship with mono-utils installed.

Sync flows both ways through GitHub โ€” a Remote pulls in shell updates from the Host, and the Host pulls in each Remote's new modules:

Setup Sync โ€‹

Both Mono Host and Mono Remote use these files to handle the syncing process:

mono-vue-remote/
โ”œโ”€โ”€ .mono/
โ”‚   โ”œโ”€โ”€ apps/                # synced remotes land here
โ”‚   โ””โ”€โ”€ tsconfig.json        # generated path aliases (see Prepare)
โ”œโ”€โ”€ mono.config.ts
โ”œโ”€โ”€ package.json
โ”œโ”€โ”€ .env.dev
โ””โ”€โ”€ .env

The .mono/ folder

Everything mono generates lives under one gitignored .mono/ folder: the synced remotes (.mono/apps/), the generated TypeScript aliases (.mono/tsconfig.json, written by mono prepare), and the sync cache/lock. You never edit it by hand โ€” mono sync and mono prepare keep it in sync.

mono.config.ts โ€‹

Points to the apps you want to sync. If you're on a Remote, list your Host here:

ts
import { defineConfig, JWTCompleteTokenTypes } from "mono-utils/runtime";

export default defineConfig({
    name: 'mono-remote',
    apps: [
      {
        name: 'mono-host',
        url: 'https://github.com/EJI-ICT/mono-vue-host',
        envToken: 'MONO_TOKEN'
      }
    ]
});

The url carries the ref (branch / tag / commit) directly, GitHub-style:

  • https://github.com/EJI-ICT/mono-vue-host โ€” defaults to the main branch
  • https://github.com/EJI-ICT/mono-vue-host/tree/v4.0.7 โ€” a tag
  • https://github.com/EJI-ICT/mono-vue-host/tree/feat/layout-program โ€” a branch (slashes are fine)
  • https://github.com/EJI-ICT/mono-vue-host/tree/deac44a955ac63981e6bae705f5df3f2d61dcf0f โ€” a commit SHA

.env and .env.dev โ€‹

The envToken above (MONO_TOKEN) lives in .env or .env.dev โ€” just a normal GitHub token.

MONO_TOKEN=YOUR_GITHUB_TOKEN

envToken is optional. If everyone who syncs this app has been invited to its repo, mono sync never needs a token at all โ€” see Sync without a token below.

package.json โ€‹

The sync script:

json
{
  "name": "mono-vue",
  "private": true,
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "mono:sync": "mono sync && mono prepare",
    "mono:prepare": "mono prepare",
    "postinstall": "mono sync && mono prepare",
    ...
  },
  "dependencies": {
    ...
  },
  "devDependencies": {
    ...
  }
}

Run Sync โ€‹

Then run:

pnpm mono:sync

It will download each app listed in mono.config.ts into your .mono/apps/ folder โ€” preferring your own git credentials and falling back to the GitHub token from .env.dev or .env (see Sync without a token). The && mono prepare then regenerates .mono/tsconfig.json so the new remote's @<name> path alias is available to TypeScript right away (see Prepare).

Sync without a token โ€‹

A shared personal access token is a rate limit everyone draws from at once. If you have been invited as a collaborator on a repo, your own machine can already reach it over plain git โ€” which spends no GitHub API rate limit at all.

mono sync tries that first. For each app it runs one git ls-remote, which both proves you have access and returns the head commit, so on this path it makes zero GitHub API requests:

==> Check mono-host (EJI-ICT/mono-vue-host@main)
๐Ÿ”‘ Git access OK โ€” no token needed.
๐Ÿ†• First time download (2100d49).
๐Ÿ“ฅ Downloading via git (depth 1)...
โœ… Stored in .mono/apps/mono-host

If your machine can't reach the repo, sync says so and falls back to the token:

==> Check secret-app (EJI-ICT/secret-app@main)
โš ๏ธ  You are not invited to "EJI-ICT/secret-app" โ€” no git access to this repo.
   To get access without a token, ask the owner to invite you as a collaborator,
   then you can sync without a token.
   Falling back to the token in MONO_TOKEN.

To stop paying for the token, ask the repo owner to add you under Settings โ†’ Collaborators. Nothing else changes โ€” the next mono sync picks up the new access on its own.

Requirements โ€‹

You need git on your PATH and a saved github.com credential. On Windows, Git Credential Manager handles this after you sign in once; gh auth login also works. Sync never prompts for credentials โ€” if none are saved it just warns and uses the token, so a postinstall can never hang waiting for a login dialog.

Choosing the transport โ€‹

Per app, via transport in mono.config.ts:

ts
apps: [
  { name: 'mono-host', url: 'โ€ฆ', transport: 'auto' },  // default
]
transportBehaviour
autoProbe git first, fall back to the token. The default.
gitGit only โ€” fails instead of falling back. Use in CI, so a missing credential errors loudly rather than silently spending the shared token.
tokenAlways REST + archive ZIP. The old behaviour.

And globally, via environment variables:

VariableEffect
MONO_SYNC_TRANSPORTauto / git / token for every app. token restores the previous behaviour exactly.
MONO_SYNC_RECHECK_ACCESS=1Ignore the cached "no access" result and probe again.
MONO_SYNC_ACCESS_TTL_MSHow long a "no access" result is remembered. Default 6 hours.
MONO_SYNC_PROBE_TIMEOUT_MSProbe timeout in ms. Default 60000.
MONO_SYNC_GIT_METHODclone / fetch โ€” pin how the git transport downloads. See below.

The same flags work on the command line: mono sync --transport=git --git-method=fetch --recheck-access.

How the git transport downloads โ€‹

Once access is proven, sync gets the tree with a shallow git clone, falling back to git init + git fetch if that fails. The summary line tells you which ran:

โ€ข mono-host (EJI-ICT/mono-vue-host@main @ 2100d49) via git (clone)

clone is roughly twice as fast, measured on a 1.8 MB / 55-file app repo:

wall clock
clone --depth=1~3.8 s
init + fetch + reset~7.1 s

Both transfer the identical pack over the identical protocol โ€” the gap is process startup, and init/remote add/fetch/reset is four extra git processes (~0.44 s each on Windows). End to end that took a real one-app sync from 9.2 s to 6.2 s.

You should never need to set MONO_SYNC_GIT_METHOD; it exists for a locked-down git setup where one of the two misbehaves. Note that an app pinned to a commit SHA always uses fetch, because clone --branch cannot name a bare commit โ€” a branch or tag ref gets the fast path.

Why sync doesn't keep a local repo and git pull

Pulling incrementally into a kept-around clone was measured at ~3.2 s โ€” only ~0.6 s better than a cold clone, because these app repos are single-digit MB. That doesn't pay for keeping a live .git (and its shallow grafts, stale worktree metadata and corruption modes) inside .mono/apps/, so sync deletes .git and starts clean every time. Unchanged apps skip the download entirely anyway, on the commit cache.

Why a "no access" answer is remembered

Without the cache, a teammate who hasn't been invited would pay a failing network probe for that repo on every sync. Only failures are cached, never successes, and the moment an invite lands the next probe succeeds and clears it.

The token path still needs unzip

Only the token fallback downloads an archive ZIP, and unpacking it shells out to unzip, which stock Windows does not ship. On Windows the git transport is therefore the reliable one โ€” another reason to get invited rather than to pass a token around.

pnpm i self-heals

postinstall runs mono sync first so a plain pnpm i also recovers an empty or stale .mono/apps/. That ordering exists because pnpm auto-runs install before any script, so a broken .mono/ would otherwise fail every pnpm command โ€” including pnpm mono:sync itself. You still run pnpm mono:sync explicitly after editing apps[] (it uses .env.dev), but a fresh install no longer needs it. To break the deadlock by hand, bypass pnpm:

bash
node node_modules/mono-utils/dist/mono.mjs sync   # reads .env itself via dotenv