mfe-forge generate
Scaffold a remote app, a host, a source package, a buildable library, or a design system.
Synopsis
mfe-forge generate <type> [name] [options]
mfe-forge g <type> [name] [options]Description
generate renders one of the built-in Mustache templates in packages/cli/src/templates/ into the workspace, assigns a dev-server port to apps and hosts, wires new remotes into the scope's host, and adds a dev:<scope>:<name> script to the root package.json.
Run it from the workspace root. The configuration is loaded first, so an invalid configuration aborts with exit code 3 before anything is written.
Arguments
| Argument | Required | Description |
|---|---|---|
type | Yes | One of app, host, package (alias pkg), library, design-system. |
name | No | Item name. scope/name is required for app and host. Prompted for when omitted; the prompt accepts ^[a-z][a-z0-9-/]*$. |
Options
| Flag | Type | Default | Description |
|---|---|---|---|
--port <port> | number | next free port in dev.portRange | Dev/preview port for app and host. Ignored for other types. Not validated or conflict-checked. |
--host <host> | scope/name | <scope>/host | Host into which a new app is registered. |
--scope <scope> | string | default | Scope used when name has no scope/ prefix. Only meaningful for package, library, and design-system, because apps and hosts must be scoped explicitly. |
--features <list> | comma-separated string | "" | Parsed into a list and passed to templates, but no built-in template uses it today. |
--skip-host | boolean | false | For app: do not register the app in a host. |
--dry-run | boolean | false | Print the target directory and template file list, then exit without writing. |
-h, --help | boolean | Show help. |
Naming and scope rules
The name is normalized before use:
- It is lowercased.
- A leading npm scope made of word characters (for example
@acme/) is stripped. Hyphenated npm scopes such as@my-org/are not stripped. - If the result contains
/, the first segment is the scope and the second is the name. Otherwise the scope is--scopeordefault.
Derived identifiers, shown for shop/cart with organization: 'acme':
| Identifier | Rule | Example |
|---|---|---|
| Full name | scope/name | shop/cart |
| camelName | camelCase of the full name | shopCart |
| PascalName | PascalCase of the full name | ShopCart |
| Federation name | <camelName>App | shopCartApp |
| npm package name | @<organization>/<scope>-<name>, or <scope>-<name> without an organization | @acme/shop-cart |
| Root dev script | dev:<scope>:<name> | dev:shop:cart |
TIP
Set organization without a leading @ (acme, not @acme). The CLI adds the @ itself.
Generator types
app
A Vite + React remote that exposes ./App through @originjs/vite-plugin-federation.
- Location:
apps/<scope>/<name>/ - Files:
index.html,package.json,tsconfig.json,vite.config.ts,src/App.tsx,src/bootstrap.tsx,src/main.tsx,src/index.css - Federation:
name: '<camelName>App',filename: 'remoteEntry.js',exposes: { './App': './src/bootstrap.tsx' }, andreact,react-dom,react-router-dom,zustandshared as singletons. - Scripts:
dev(vite --port <port>),build(tsc -b && vite build),preview,test(vitest),test:e2e(playwright test),lint,type-check. - Dependencies:
@mfe-forge/core,@mfe-forge/router,@mfe-forge/store. They resolve toworkspace:*whenpackages/coreexists in the workspace; otherwise they are pinned to the CLI version.
host
A Vite + React shell that consumes the remotes in its scope.
- Location:
apps/<scope>/<name>/. Usehostas the name (shop/host): discovery,sync,dev --scope, anddoctoronly recognize hosts in a directory namedhost. - Files: the same as
app, plussrc/layouts/AppLayout.tsxandsrc/remotes/declarations.d.ts. - Federation: an empty
remotes: {}block thatgenerateandsyncfill in; noexposes. - On creation: every app already present in the same scope is written into
remotes, and a matchingdeclare module '<federationName>/App'is written tosrc/remotes/declarations.d.ts.
package
A source-only TypeScript package (alias pkg).
- Location:
packages/<name>/. Ascope/nameinput becomespackages/<scope>-<name>/. - Files:
package.json(main, types, and exports point at./src/index.ts),tsconfig.json,src/index.ts. - Scripts:
type-check,lint,test. No build step.
library
A buildable TypeScript library.
- Location: same rules as
package. - Files:
package.json,tsconfig.json,src/index.ts. - Scripts:
buildanddevwithtsup(ESM +.d.tsintodist/),test,type-check,lint.
design-system
A Storybook-based component library built on @mfe-forge/design.
- Location:
apps/<scope>/<name>/, for exampleapps/default/ui/forgenerate design-system ui. It has novite.config.ts, so it is not discovered as an app bydev,build, orsync. - Files:
package.json,tsconfig.json,.storybook/main.ts,.storybook/preview.ts,src/index.ts,src/index.css,src/tokens/index.ts,src/components/{index,Button,Card,Input}.tsx,src/utils/cn.ts. - Scripts:
dev(storybook dev -p 6006),build(storybook build),type-check,lint.
See @mfe-forge/design.
Port assignment
Ports apply to app and host only.
- If
--portis given, it is used as-is (parsed withparseInt). - Otherwise the CLI scans every directory under
apps/(recursively, skippingnode_modules) for literal ports, looking forport: <n>invite.config.tsand--port <n>in thedevscript ofpackage.json. - Starting at the first value of
dev.portRange(default3000), it picks the first port that is both unused by the workspace and free on the machine (viaportfinder), up to the end of the range. - If no port is available, generation fails with
No available ports found in the configured range.
The chosen port is written into vite.config.ts (server.port, preview.port) and into the dev and preview scripts.
WARNING
Ports computed at runtime (for example port: Number(process.env.PORT)) are not detected. Hosts do not get a reserved port; the first generated app or host takes the start of the range.
Remote registration
After an app is generated (unless --skip-host is set):
The target host is
--host, or<scope>/host.If
apps/<host>/does not exist, a warning is printed (Host "<host>" not found. Run "mfe-forge generate host <host>" to create it.) and generation still succeeds.If the host's
remotesblock does not already contain<camelName>App, the whole block is rewritten with one entry for every app in the host's scope:tsremotes: { shopCartApp: 'http://localhost:3001/assets/remoteEntry.js', },src/remotes/declarations.d.tsis regenerated with adeclare module '<federationName>/App'for every app in the host's scope, unless it already declares the new app.If no
remotes: {block exists, a warning is printed and the config is left unchanged.
Cross-scope hosts
Registration always lists the apps in the host's scope. --host admin/host for an app in the shop scope therefore does not add that app, and a later sync removes manual cross-scope entries. Keep remotes in the same scope as their host.
Remote URLs always point to http://localhost:<port>/assets/remoteEntry.js. Production URLs must be configured separately.
Root package.json script
For every type, if a root package.json exists and the script is not already defined, generate adds:
"dev:<scope>:<name>": "<pm> --filter <packageName> dev"npm projects get npm run dev --workspace=<packageName> instead. dev relies on these scripts. Packages also receive a script (for example dev:default:utils), even though the package template has no dev script.
Examples
# Host first, then remotes in the same scope
mfe-forge generate host shop/host
mfe-forge generate app shop/cart
mfe-forge g app shop/catalog --port 3105
# Generate without touching a host
mfe-forge g app shop/experiments --skip-host
# Shared code
mfe-forge g pkg utils # packages/utils, package name "default-utils"
mfe-forge g library shared-lib # packages/shared-lib
mfe-forge g design-system ui --scope brand # apps/brand/ui
# Preview
mfe-forge g app shop/cart --dry-run--dry-run output:
📋 Dry run — files that would be generated:
/path/to/workspace/apps/shop/cart/
index.html
package.json
src/App.tsx
...Dry runs do not check whether the target already exists, do not assign a port, and do not report the host or package.json changes.
Failure modes
All failures exit with code 1 unless noted otherwise.
| Situation | Message |
|---|---|
| Unknown type | Invalid type "<type>". Valid: app, host, package, design-system, library, pkg |
| Unscoped app or host | Apps and hosts must be scoped: "<name>" → try ... |
| Target directory exists | Directory already exists: <path> |
| Template rendering error | Template error: ...; the partial target directory is removed. |
| No free port | Generation failed / No available ports found in the configured range |
| Invalid configuration | Exit 3 before any file is written. |
Related
sync— reconcile all hosts deterministicallydoctor— detect port conflicts and scopes without a host- Concepts and Architecture