Quick Start
Getting started with Fuma CLI.
Getting Started
Define Registry
Specify the base directory for your registry.
import type { Registry } from "fuma-cli/compiler";
// my-repo/src
const dir = path.join(path.dirname(fileURLToPath(import.meta.url)), "./src");
export const registry: Registry = {
name: "acme-ui",
dir,
tsconfigPath: "../tsconfig.json",
packageJson: "../package.json",
components: [
{
name: "button",
files: [
{
// `type` specifies how the file should be installed
type: "ui",
path: "src/button.tsx",
// optional: change the installed path, <dir> refers to the default destination
target: "<dir>/primitives/button.tsx",
},
],
},
],
};Common pitfalls
The same file cannot belong to two different top-level components.
Now compile & write the output via a script:
import { compile, writeRegistry } from "fuma-cli/compiler";
import { registry } from "../registry";
const out = await compile({ root: registry });
await writeRegistry(out, {
dir: "./public",
// optional: clean previous outputs
cleanDir: true,
});node ./scripts/compile.tsYou need to serve the output files in public folder, here we assume all files under public are already served via https://acme-ui.com.
Implement Installer
Initialize your own CLI package and install dependencies:
npm i fuma-cli @clack/promptsFuma CLI installer is headless, we will use @clack/prompts for creating TUI.
import { ComponentInstaller } from "fuma-cli/registry/installer";
import { HttpRegistryConnector } from "fuma-cli/registry/connector";
import type { LoadedConfig } from "@/config";
import { confirm, isCancel } from "@clack/prompts";
import picocolors from "picocolors";
export class MyComponentInstaller extends ComponentInstaller {
constructor() {
const connector = new HttpRegistryConnector("https://acme-ui.com");
super(connector, {
// optional: where to write components
outDir: {
base: "src",
},
// use our own TUI
io: {
onWarn(message) {
console.warn(message);
},
async confirmFileOverride(options) {
const value = await confirm({
message: `Do you want to override ${options.path}?`,
initialValue: false,
});
if (isCancel(value)) {
outro("Installation terminated");
process.exit(0);
}
return value;
},
onFileDownloaded(options) {
console.log(`downloaded ${options.path}`);
},
},
});
}
}Create Command
Create a command that installs your button component.
import { isCancel, confirm, box } from "@clack/prompts";
import { MyComponentInstaller } from "../installer";
export async function install(targets: string[]) {
const installer = new MyComponentInstaller();
for (const target of targets) {
const deps = await installer.install(target).then((res) => res.deps());
if (deps.hasRequired()) {
box([...deps.dependencies, ...deps.devDependencies].join("\n"), "New Dependencies");
const value = await confirm({
message: `Do you want to install dependencies?`,
});
if (isCancel(value)) {
outro("Installation terminated");
process.exit(0);
}
if (value) {
await deps.installRequired();
} else {
await deps.writeRequired();
}
}
}
}The consumer can now execute the command to install button, e.g.
import { install } from "./commands/install";
await install(["button"]);node scripts/test-install.tsExamples
Interactive Install UI
Allow user to select components to install.
import { isCancel, autocompleteMultiselect, outro, spinner } from "@clack/prompts";
import picocolors from "picocolors";
import { RegistryConnector } from "fuma-cli/registry/connector";
import { install } from "@/commands/install";
interface AddOption {
label: string;
value: string;
hint?: string;
}
export async function add() {
// add your connector
const connector: RegistryConnector;
const spin = spinner();
spin.start("fetching registry");
const info = await connector.fetchRegistryInfo();
spin.stop(picocolors.bold(picocolors.greenBright("registry fetched")));
const value = await autocompleteMultiselect({
message: "Select components to install",
options: info.indexes.map<AddOption>((item) => ({
label: item.title ?? item.name,
value: item.name,
hint: item.description,
})),
});
if (isCancel(value)) {
outro("Ended");
return;
}
await install(value);
outro(picocolors.bold(picocolors.greenBright("Successful")));
}