For AI agents: the complete documentation index is available at https://mfeflow.github.io/mfeflow/llms.txt, the full documentation bundle is available at https://mfeflow.github.io/mfeflow/llms-full.txt, and this page is available as Markdown at https://mfeflow.github.io/mfeflow/reference/cli.md.

CLI reference

This page lists every command, flag and exit code of the mfeflow command line. It matches mfeflow --help for version 0.4.0. For task-based help, use the guides.

In a generated project, run commands as pnpm mfeflow <command>. To create a project, use pnpm create mfeflow, which runs init.

Run the command with --help for the options and examples of one command.

Commands

CommandDoes
init [dir]Create a project: a shell and remotes, in one repo or one repo per team
adoptSet up mfeflow in an existing Module Federation project; never edits build config
add remote <name>Add a remote, its registry entries and a shell route
add shell <name>Add a shell that routes to every remote
add team-repo <name>In a polyrepo shell repo: add a remote in a new repo next to the others
add <feature...>Add features, or @namespace/feature from a registry
add env-key <KEY>Add a typed env key
upgradeMove the project to the version of mfeflow you run, keeping your edits
features [search]List and search features from mfeflow and your registries
registry add, list, remove, buildConfigure or build third-party registries
doctorCheck singletons, compatibility, config and drift
validateCheck mfeflow.json, every mfeflow.app.json and every registry entry
check-compatCheck each remote's shared ranges against its shells
budgetMeasure built JavaScript against the cost model
devRun the shell and every remote with a dev registry
deploy <remote>Upload a remote's build to a static registry host and point an environment at it
promote <remote>Point one environment at the version another serves; no upload
rollback <remote>Point an environment back at an earlier version; no rebuild
statusWhat each environment serves, the version before it, and drift
rollout step|complete|abort|status <remote>Move or read a percentage rollout (registry server only)
kill <remote>, restore <remote>Take a remote out of the served index, or put it back (registry server only)

This release has no command to remove a feature.

An unknown command exits with code 2 (E107).

Global options

Every command accepts these.

FlagMeaning
-y, --yesTake the defaults and never prompt
--preset <file|url>Replay options from a JSON file or an https URL. Flags win over the preset
--jsonPrint one JSON document on stdout and nothing else
--dry-runPrint the summary and diff, write nothing
--cwd <dir>Run as if started in this folder
--install, --no-installRun the package manager after writing. On by default
--verboseShow package manager output and stack traces
--local <path>Link @mfeflow/* from a built mfeflow checkout. Environment variable MFEFLOW_LOCAL
-h, --helpShow help

init

Creates a project.

mfeflow init [dir] [options]
FlagTypeDefaultMeaning
--scope <@scope>textfrom the folder namenpm scope for the project packages
--remotes <a,b>listcatalog,cartRemotes to create
--features <a,b>listlint-oxlint, url-factory, env-t3, testingOptional features to turn on
--package-manager <pm>textpnpmOnly pnpm in this release
--topology <name>textmonorepomonorepo or polyrepo
--org <owner>textthe scope without @polyrepo: GitHub owner the CI and Renovate files point at
--option <f.k=v>repeatablenoneA feature option, as feature.key=value

Feature names accepted by --features: lint-oxlint, url-factory, env-t3, testing, analytics-gtm, telemetry-otel, event-bus, route-factory, http, theming, i18n, tailwind, ui-shadcn, forms, data-react-query, analytics-clarity, storybook, ci-github, git-hooks, commit-prompt.

mfeflow init my-app --scope @acme --remotes catalog,cart,checkout --features lint-oxlint,git-hooks

With --topology polyrepo, dir is a folder that holds one git repo per platform, shell and remote. --features goes to the shell and the remote repos. ci-github and env-t3 are refused, and no feature is on by default. The result lists each repo under repos. See One repo per team.

mfeflow init my-org --topology polyrepo --remotes catalog,cart

adopt

Sets up mfeflow in a project that already uses Module Federation. The tutorial is in Bring an existing Module Federation app.

mfeflow adopt [--dry-run] [--json] [--yes] [--no-install]

It reads the build config as source text and writes mfeflow.json, one mfeflow.app.json per app, registry/envs/<env>/<remote>.json for dev, staging and prod, and adds @mfeflow/runtime to each shell's package.json. It never edits a build config file. It prints a line to paste into your shared list instead.

FlagMeaning
--dry-runPrint the summary and every diff, write nothing
--yesWrite without asking. Required when there is no terminal
--jsonPrint one JSON document. Needs --yes unless it is a dry run

In a terminal the command asks once before it writes. Without a terminal, and without --yes or --dry-run, it exits with code 2 and writes nothing. The files are written all or nothing.

If mfeflow.json already exists the command stops with E123. A folder with no shell stops with E124. Parts of the config that could not be read, or have no home in mfeflow, are printed as not carried over: with a reason. They do not change the exit code: a partial adoption exits 0 and the plan carries warnings.

--json prints { ok, command: "adopt", dryRun, root, scope, apps[], notAdopted[] (dir, file, field, reason), buildConfig[], snippet, next, plan, written, installed, diff }.

add

Adds an app, a team repo, features or an env key. Five forms:

mfeflow add remote <name>
mfeflow add shell <name>
mfeflow add team-repo <name>
mfeflow add <feature...>
mfeflow add env-key <KEY> --scope server|runtime --schema <expr>
FlagApplies toMeaning
--port <n>remote, shell, team-repoDev server port. Default: the next free one
--option <f.k=v>featuresA feature option, repeatable
--scope <server|runtime>env-keyserver writes env.ts and .env.example. runtime writes the runtime config files
--schema <expr>env-keyThe validator expression, such as z.url()
--value <text>env-keyPlaceholder written to the example or config files
--app <name>env-keyOnly this app. Default: every app, or every shell for runtime
--group <root|shell|remotes>env-keyRoot, every shell or every remote, instead of --app

add team-repo runs in the shell repo of a polyrepo. It creates ../<name> as a git repo with the first-party features the shell has, then adds a route, staging and prod registry entries and a dev server entry to the shell. It writes the new repo first, so a failure leaves no folder. Run it elsewhere and it exits with code 2 (E107).

A feature from a registry is written only after a yes at the prompt, or with --yes. Without a terminal, --yes is required.

mfeflow add remote checkout --port 4320
mfeflow add env-t3 --option env-t3.validator=valibot
mfeflow add env-key GTM_ID --scope runtime --schema "z.string().min(1)" --app shell

features

Lists and searches features from mfeflow and every configured registry.

mfeflow features [search]

The search matches name, description or keywords. A registry that cannot be read is reported on stderr and the others are still listed. The exit code stays 0.

registry

Manages third-party feature registries in mfeflow.json, or builds one to host.

mfeflow registry add @namespace <url-template>
mfeflow registry list
mfeflow registry remove @namespace
mfeflow registry build <dir> [--out dir]
ActionDoes
addWrites the namespace and URL template to mfeflow.json. The template needs {name}; {version} is optional
listPrints each namespace with its template
removeRemoves it. Refused while a feature from that namespace is installed
buildChecks every feature folder in <dir> and writes the files to host. Default output <dir>/dist

The URL must be https. Plain http is accepted for loopback hosts only: localhost, 127.0.0.1 and [::1]. The namespace @mfeflow is reserved.

The build action lists every problem and writes nothing if there is one (E404).

upgrade

Moves the project to the version of mfeflow you run. It never asks npm for a newer one. The tutorial is in Versions and upgrades. It does not work in the repos of a polyrepo yet and exits with code 2 (E107) without writing.

mfeflow upgrade [--to <version>] [--dry-run] [--json] [--yes] [--accept <path|all>] [--keep <path|all>] [--accept-changed-content [features]]
FlagMeaning
--dry-runPrint the summary and every diff, write nothing
--accept <path|all>Replace a file you changed with the new version. A path, a comma list, or all. Symlinks are never replaced
--keep <path|all>Keep your file. It is raised again only when the new version of it changes
--yesNever ask. A changed file still needs --accept or --keep, and a registry feature is applied
--accept-changed-content [features]Take registry content that differs from the recorded hash. Alone it takes every feature; with a comma list (@acme/a,@acme/b) only those
--to <version>Must be the version that runs, because the files come from it. Anything else is E107

A generated file you did not edit is replaced and a new file is created. package.json, pnpm-workspace.yaml and turbo.json only get small edits. Files the new version no longer makes, and files you deleted, are listed and left alone. A file you changed is offered only when the new version generates something different from the old one. It is never written unless you accept it.

Without a terminal there is nobody to ask. A changed file with no decision stops the run before anything is written, with E604 and exit code 8, even with --yes. A --dry-run never fails that way: it prints everything and exits 0. A symlink is reported and never written over, with or without --accept all. So is a file under a symlinked folder: every part of the path from the project root down is checked. A symlink needs no decision and never blocks a run.

A feature from a registry is applied only after you confirm in a terminal, or pass --yes without one. The content of each registry feature is compared with the hash in mfeflow.json. If the registry now serves other bytes for the same version, the run stops with E406. Pass --accept-changed-content to take them, or name the features, as in --accept-changed-content @acme/hello. Repeat the flag to add names. A name that is not an installed feature, or an empty =, is E107. The new hash is saved even when no file changes. A feature with no recorded hash is shown as first seen and its hash is recorded. The files are written all or nothing, and the install runs in the same step.

integrity maps each registry feature to verified, first-seen or accepted-changed. changedValues is the part of lostKeys that the new version has with another value.

--json prints { ok, command: "upgrade", dryRun, root, from, fromSource, to, upToDate, replaced, created, edited, diverged: [{ path, reason, decision, lostKeys, changedValues, diff }], integrity, missing, removed, migrations, plan, written, installed, diff }.

doctor, validate and check-compat

CommandDoes
doctorRuns all checks: apps on disk, mfeflow.app.json, ports, routes, registry, singleton versions, workspace versions, compatibility, drift, runtime plugins and env files
validateApps on disk, mfeflow.app.json, routes and registry entries
check-compatRemote ranges against their shells

doctor --fix repairs a missing workspace version (E111) and missing registry entry files (E114). Add --dry-run to see the diff first.

doctor --against <url|file> runs every check and takes the shell for the compatibility check from a deployed shell, as check-compat --against does (the list below says what it accepts). Use it in a remote repo whose shell lives elsewhere. With --against, the "shell is not in this repo" warning does not appear. Without it, a repo that has remotes but no shell prints shell is not in this repo, run \mfeflow doctor --against <url|file>`. A source that cannot be read is E307 and exits 5, as in check-compat. A skewed shared version is E302 and also exits 5. The code follows the errors in the report, so a project error (E1xx) in the same run exits 3 instead. With --json, against` holds the source you passed, with any token in a URL hidden.

check-compat

Checks each remote's shared ranges against the shell it runs in. By default it reads the shell's versions from node_modules in this repo.

mfeflow check-compat [--against <url|file>] [--json]
FlagMeaning
--against <url|file>Read the shell's versions from a deployed shell instead of this repo. See the list below

--against takes one of three things:

  • A registry index for an environment. The shell's mf-manifest.json is read from shell/mf-manifest.json beside it.
  • A deployed shell mf-manifest.json.
  • A local file of either kind.

Every remote in this repo is then checked against the versions the shell shares. A remote that needs a React the shell does not provide is an error (E302) and exits with code 5.

A URL must be https. Plain http is allowed for localhost only. The answer may not be larger than 1 MB, and a redirect to a private address is refused. A source that cannot be read is E307.

Some versions cannot be compared before install, such as workspace:* or catalog:. Each one is a warning, not an error.

If the shell is not in this repo and you pass no --against, the check cannot compare anything. It does not pass silently. It prints this warning:

warning shell is not in this repo, pass --against <url|file>

A warning does not change the exit code, as with every other check. The command exits 0 unless there is an error. With --json, against holds the source you passed, with any token in a URL hidden.

budget

Measures the built JavaScript of the shell and each remote against the cost model. Build first.

FlagDefaultMeaning
--dist <dir>distBuild output folder inside each app

See CI and budgets for what it checks and The cost model.

dev

Runs the shell and every remote with a dev registry that follows the project. In the shell repo of a polyrepo it runs the shell only, and the registry lists the remotes in devRemotes of public/config/runtime-config.dev.json.

FlagMeaning
--only <a,b>Run only these apps. Default: the shell and every remote
--remote <name=url>Use a server you already run for a remote. Repeatable. A bare origin means /mf-manifest.json
--allow-origin <origin>Allow --remote to point at this origin. Localhost is always allowed. Repeatable

Ctrl+C stops everything and exits with code 130. See Develop locally.

deploy, promote, rollback and status

mfeflow deploy catalog --env prod --version 1.4.2
mfeflow promote catalog --from staging --to prod
mfeflow rollback catalog --env prod --to 1.3.0
mfeflow status --env prod

A walk-through with output is in Deploy and roll back a remote.

FlagCommandsDoes
--env <env>deploy, rollback, statusThe environment. status defaults to every env on the host and every folder in registry/envs but dev
--from <env>, --to <env>promoteCopy the pointer from one environment to another
--to <version>rollbackGo back to this version, which must be in the history. Default: the previous one
--forcerollbackRoll back although the schema, route or compat checks fail. The files must still exist
--version <v>deployDefault: the remote's package.json version
--dist <dir>deployBuild folder inside the remote. Default dist
--no-publishdeployUpload the files, leave the index where it is. status then shows drift
--host <spec>allfile:<dir> or s3://<bucket>[/prefix]. Default registry.host in mfeflow.json
--base-url <url>deploy, promote, rollbackhttps URL (http only on localhost) the host serves from. Default registry.baseUrl
--shell-manifest <file>deploy, promote, rollbackThe shell's deployed mf-manifest.json. Default envs/<env>/shell/ on the host

S3 settings come from MFEFLOW_S3_ENDPOINT, MFEFLOW_S3_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN, and are never printed. --dry-run prints the plan as data and writes nothing. Without --yes, a terminal asks before anything moves. A failed gate exits with 7 and one of E501 to E505. The other deploy errors are E506 to E512.

--json prints { ok, command, dryRun, env, fromEnv, host, remote, version, servedBefore, published, previous, rolledBackFrom, uploads, uploaded, skipped, localEntry, warnings, findings } for the first three, with index added for a dry run. status prints { ok, command: "status", host, envs: [{ env, published, generatedAt, remotes }], findings }. A successful deploy, promote or rollback also writes registry/envs/<env>/<remote>.json, so git records what is served. Not for dev: mfeflow dev reads that folder.

Server mode

Set MFEFLOW_REGISTRY_URL, or pass --registry-url, and deploy, promote, rollback and status talk to a registry server instead of writing a static index. Without it nothing changes.

export MFEFLOW_REGISTRY_URL=https://registry.example.com
export MFEFLOW_REGISTRY_TOKEN=...
mfeflow deploy catalog --env prod --percent 10 --steps 50,100
mfeflow rollout status catalog --env prod
mfeflow rollout complete catalog --env prod
mfeflow kill catalog --env prod --reason "payments error"
Flag or variableCommandsDoes
MFEFLOW_REGISTRY_URLall fiveThe server address. https, or http on localhost only. No login, query or fragment in it
--registry-url <url>all fiveThe same, and it wins over the variable
MFEFLOW_REGISTRY_TOKENall but statusThe token. Read only from the environment; there is no flag. Never printed
--percent <n>, --steps <a,b>deploy, promoteStart a rollout at n percent (1 to 99), then the rising steps. A static host refuses (E505)
--reason <text>kill, rollout abortWritten to the audit log

In server mode deploy still uploads the build to --host first, because the server never stores files. Then it asks the server to move the pointer. promote --percent sends the rollout with the promote, so it needs promote on the target and read on the source, and it is audited as a promote. status needs --env outside a project, and shows kills and rollouts when a token is set.

--json and --dry-run work as in static mode. A dry run reads the index and sends nothing, so the server's own checks do not run. Server answers map to the same codes: a missing version or rollout is E508, a changed published version is E511, a missing build file is E509, a lost race is E510, and an unreachable server, a bad token or a missing token is E506. All exit with 7.

Exit codes

CodeMeaning
0Success
1Unexpected error
2Bad command line (E107, E215)
3Project or config problem (E1xx)
4Feature problem (E2xx)
5Build or runtime problem (E3xx)
6Feature registry problem (E4xx)
7Deploy problem (E5xx)
8Upgrade problem, or files that need a decision (E6xx)
130Cancelled at a prompt, or Ctrl+C in dev

For the four check commands, the exit code is the lowest code among the errors found. Warnings do not change it. Every error has a code and a page. See Error codes.

Output and exit codes

With --json, a command prints one JSON document and nothing else. An error prints a failure document:

{
  "ok": false,
  "error": {
    "code": "MFEFLOW_E102",
    "message": "...",
    "fix": "...",
    "docs": "https://mfeflow.github.io/mfeflow/errors/E102"
  },
  "exitCode": 3
}

The success documents have these shapes. Each has ok and command.

CommandMain fields
initdryRun, root, plan, written, installed, scope, apps, next, diff on a dry run
adoptThe same as init, plus notAdopted[], buildConfig[] and snippet
addThe same as init, plus kind, added and skipped
featuressearch, features[] (source, name, version, description, always, default, installed), registries[]
registryaction, dryRun, root, changed, registries[], build
doctor, validate, check-compatroot, summary (errors, warnings, skipped, fixed), checks[], findings[]
budgetroot, model, shells[], remotes[], findings[]
devroot, registry, apps[], overrides, dryRun, interrupted

The plan in the init and add output lists features, changes (path and action), dependencies, scripts and warnings. Output shapes are part of the public API. See Versions and upgrades.