@bunnybox/cli
v0.11.1
Published
Command line tool for scaffolding, building, and deploying BunnyBox micro-frontends.
Maintainers
Readme
@bunnybox/cli
The bunnybox command line tool: scaffold a micro-frontend, run it locally, and ship it to the control plane.
Install
npm install --save-dev @bunnybox/cliOr run it without installing:
npx @bunnybox/cli init my-mfeRequires Node 20.19 or newer.
Getting started
bunnybox init my-mfe && cd my-mfe # scaffold a project
npm install
bunnybox dev # run it in a browser
bunnybox login --url https://control-plane.example # save the connection
bunnybox deploy --version 1.0.0 # build and register an immutable version
bunnybox promote 1.0.0 # make it the active oneThe first three commands need no account. login points the CLI at a BunnyBox control plane — the
service that stores your registered versions and serves the manifest your app fetches — and only the
commands that ship something need one.
init generates a working micro-frontend — bunnybox.json, package.json, a tsconfig.json already wired
for JSX, and a src/index.tsx you can deploy as-is. It pins @bunnybox/core to the version matching this
CLI, since the two release together. Pass --minimal for a bare stub instead of the guided example.
init --app generates an app instead: several micro-frontends laid out into the regions of one page, a
shared/ package they can all import, and one lint config covering the tree.
bunnybox init console --app && cd console # an app, with its first micro-frontend in it
npm install
bunnybox dev
bunnybox init mfes/orders # add another; inside an app it wires itself inAdd --skip-mfe to scaffold the app on its own, for one assembled from micro-frontends that already exist.
Shipping it
page writes the page that starts your app in production, and the app commands move every
micro-frontend of an app at one version:
bunnybox page --manifest-url https://control-plane.example/v1/acme/apps/console/manifest
bunnybox app deploy --version 1.4.0 # build and register all of them
bunnybox app promote 1.4.0 # make that version live, everywhereThe repository's docs/shipping-an-app.md covers the page's contents, channels in production, and
what a release does when it stops half-way.
Commands
| Command | What it does |
| ----------------------- | ---------------------------------------------------------------- |
| init [dir] | Scaffold a new micro-frontend, or an app with --app |
| dev | Build, serve, and rebuild on change, locally |
| build | Bundle once to check that it builds |
| page | Write the production host page |
| deploy --version <v> | Build and register a version. Registered versions are immutable |
| promote <version> | Set the active version |
| rollback <version> | Repoint the active version |
| versions | List registered versions |
| app apply <file> | Create or update an App from its spec file |
| app validate | Check that the micro-frontends an app composes fit together |
| app deploy | Build and register every micro-frontend of an app at one version |
| app promote <version> | Set that version live on every one of them |
| app rollback <ver> | Put every one of them back |
| login | Save control-plane credentials |
| config | Inspect and manage saved CLI config |
bunnybox <command> --help prints the flags for any of them.
Running it locally
bunnybox dev builds your micro-frontend, generates the host page and manifest it needs in order to
mount, and serves them along with the import map that points @bunnybox/core at the runtime bundle:
bunnybox dev # starts on 8010, or the next free port
bunnybox dev --port 4000 # honoured exactly; fails if that port is takenEditing a source file rebuilds and swaps the new code into the running page without a reload. Run it
from a directory holding an app.json instead and it composes several micro-frontends — each one read
from mfes/<name>/ — into a single page. An optional bunnybox.dev.json supplies canned channels
responses and extra context, so a micro-frontend that calls host.request(...) gets an answer
locally rather than an error. Its channelsModule key names a module that answers with code instead,
for the values JSON can't hold — an AudioContext, a socket, anything with a lifetime.
build is a different thing and worth not confusing with it: it bundles once and reports the size
without writing a file, answering only "does this compile and bundle". deploy is what uploads a
bundle anywhere.
Checking that a composed app fits together
Each micro-frontend compiles on its own against the channels and events it imports the handles for — there is no map, just files that declare a handle and files that import it. That leaves one question nobody was asking: does the set actually composed work? A part listening for an event no part emits is silent, and silence is the hardest symptom to trace.
bunnybox app validate builds every micro-frontend the app spec names and compares what they publish
against what they listen for:
✔ 6 MicroFrontends, 8 seams, every listener has a speakerapp deploy runs the same check between building and uploading — the last moment at which stopping
costs nothing — and uploads nothing if it finds a fault. --skip-validation releases anyway.
Channels are listed rather than judged unless the grants are actually known:
★ asks the page for hutch:analyser, hutch:clock, hutch:share, hutch:voice-bus — check the page grants eachA page that builds its channels in code — from an AudioContext, a socket, anything with a lifetime
— is not something this command can see, and bunnybox.dev.json is not a stand-in: its channels
are canned answers for local development, so a channel nobody bothered to stub would read as a
channel nobody grants. The list is the half that can be acted on without inventing the other.
It reads built bundles as text, which is what lets it see the set as deployed rather than as written,
and it is a heuristic: it recognises a seam name by its namespace:name shape, so a name assembled at
runtime is invisible to it, and a project that names its events without a colon is not checked. A
finding is evidence that something is wrong; a clean result is not proof that everything is right. An
event nobody listens for is reported as a note rather than a fault, because that is what a broadcast
looks like before its listener is deployed.
It finds a use by first binding a local name to a seam name and a kind — reading the channel() or
event() call that declared it — and then reading uses through that binding. A handle declared in a
file this command builds into the bundle is bound and its uses are seen. A handle declared inside
@bunnybox/core itself, which every bundle keeps external, is not: nothing binds its local name, so a
use of it is not reported at all, not even as a note. A use whose declaration is external to the
bundle is not reported, and widening it would mean parsing what the bundler already left external.
Deploy and promote are separate
deploy registers a version but does not serve it to anyone; promote repoints which version is active.
Splitting them is what makes a release reversible: rollback moves the pointer back, and because the shell
keys its mounts on the resolved entry URL, only the affected region remounts. Sibling micro-frontends are
untouched and the page is never reloaded.
Styles in a file of their own
A micro-frontend is delivered as one ES module, so its styles travel inside that module or they do not
arrive. That constrains the artifact, not the source: build and dev load a stylesheet as text, so a
component can keep its appearance beside it and still ship as one file.
import css from './styles.css';
export default mfe('orders', () => () => (
<section class="orders">
<style>{css}</style>…
</section>
));init writes src/env.d.ts declaring *.css for the compiler, since this is something the bundler
does that TypeScript cannot see. An existing project needs that file added by hand, once.
Project file
bunnybox.json is the source of truth for the resource, written by init and read by dev, build, and
deploy. bunnybox.yaml and bunnybox.yml are read the same way, for a project that would rather
keep a comment beside a setting than not explain it — as are app.yaml and app.yml for an app spec:
{
"tenant": "acme",
"name": "orders",
"displayName": "Orders",
"contractVersion": "^1.0.0",
"element": "orders",
"entry": "src/index.tsx",
"isolation": "inline"
}License
Apache-2.0. See LICENSE.
The Apache License grants no trademark rights. "BunnyBox" and the BunnyBox logo — which this package embeds and prints as its version banner — are trademarks of Wolfware LLC, and the licence to use the code is not permission to use the name or the mark.
