Installer
Install compiled registry components into a consumer codebase.
Connectors
A connector tells the installer where to read the registry from.
import { HttpRegistryConnector } from "fuma-cli/registry/connector";
// the base URL you serve the registry output at
const connector = new HttpRegistryConnector("https://acme-ui.com/registry");Implement RegistryConnector for other sources, it has two methods: fetchManifest(registry?) and fetchFile(file, registry?).
Installer
It accepts a config object, it is recommended to implement your own config system, then pass the config to installer.
import { ComponentInstaller } from "fuma-cli/registry/installer";
import { HttpRegistryConnector } from "fuma-cli/registry/connector";
const installer = new ComponentInstaller(
new HttpRegistryConnector("https://registry.example.com"),
{
// options
},
);
const plan = await installer.install("button");
// or from a sub registry:
await installer.install("chart", "acme/charts");
const deps = await plan.deps();
if (deps.hasRequired()) await deps.installRequired(); // runs detected package managerPlan & Apply
install() is a shorthand of two steps:
const plan = await installer.plan([{ name: "button" }, { name: "chart", registry: "acme/charts" }]);
for (const file of plan.files) {
// "create" | "overwrite" | "merge" | "unchanged"
console.log(file.status, file.output);
}
await installer.apply(plan);plan()links the components against the consumer's codebase, and downloads the files to install. Nothing is written.apply()writes the files, askingio.confirmFileOverride()for the ones withoverwritestatus.
| Status | Meaning |
|---|---|
create | The file doesn't exist. |
overwrite | The file exists with different content. |
merge | A tree-shaken file exists, and lacks some declarations in use. |
unchanged | Nothing to write. |
A merge only adds code, so it needs no confirmation:
- Statements are skipped when the file already declares one of their names, along with what only they reference.
- Imports are inserted after the last import, other statements are appended.
- Statements that declare nothing, like
"use client", are only written when the file is created.
Route handlers are installed by a built-in plugin, using the same hooks available to you.
How preserved imports are linked
An import of a preserved file points to the local file when it is part of the plan, or already exists in the consumer's codebase. Otherwise, it stays as a package import.
Options
| Option | Description |
|---|---|
cwd | The consumer's project, defaults to process.cwd(). |
framework | See below. |
outDir | Directory of each file type, base is prepended to all. src is detected. |
io | See below. |
registryAliases | See below. |
plugins | See Plugins. |
framework
Fuma CLI generates code based on consumer's framework (e.g. next or tanstack-start).
Instead of detecting automatically, you can specify it:
const installer = new ComponentInstaller(connector, {
// 'none' = framework-agnostic
framework: "none",
});io
Customise the IO interface, useful for integrating with your own TUI.
const installer = new ComponentInstaller(connector, {
io: {
confirmFileOverride(file) {
return true;
},
onFileWritten(file) {},
},
});registryAliases
Install files of another registry with the same file structure, like the same UI built on a different headless library.
const installer = new ComponentInstaller(connector, {
registryAliases: {
"acme/radix-ui": "acme/base-ui",
},
});Files referencing acme/radix-ui:button.tsx now install acme/base-ui:button.tsx.
Last updated on