Extending fusion-config
fusion-config is a small Bun CLI built on
Commander: a single entry point at
src/index.ts plus
a templates/ directory. Adding a template needs no code at all; adding a command is one
declarative block — help text, flag parsing, and error messages are generated from it.
Clone the repo and install dev dependencies first:
git clone https://github.com/tikab-interactive/fusion-config.git
cd fusion-config
bun installRun the CLI straight from source while you work — there's no build step:
bun src/index.ts listAdd a template
Templates are just directories under templates/. The CLI discovers them at runtime —
listTemplates() reads every sub-directory of templates/ — so no code change is needed.
-
Create a directory named after your template and add the files you want scaffolded:
mkdir -p templates/my-template echo "export const example = true;" > templates/my-template/index.ts -
Confirm the CLI sees it, then try copying it somewhere:
bun src/index.ts list # my-template now appears bun src/index.ts add my-template --dest /tmp # copies to /tmp/my-template
templates is already in package.json → files, so your new template ships automatically the
next time the package is published.
Optional: pull in companions with template.json
A template can drop a template.json file at its root to copy companion folders alongside
itself, or to print follow-up instructions. The CLI reads it but never copies it into the
consumer:
{
"description": "Vocs documentation site (Fusion-branded).",
"with": ["patches"],
"notes": "Add \"docs\" to workspaces and the vocs patch to patchedDependencies, then run bun install."
}| Field | Purpose |
|---|---|
description | A human summary of the template. |
with | Other template folders copied into the same destination root. They become companions — hidden from list and not addable on their own. |
notes | Printed after a successful copy. Use it for wiring the CLI can't do (workspace entries, patchedDependencies, …). |
So bunx fusion-config add docs copies templates/docs/ and templates/patches/ into the
project, then prints the notes. Companions are resolved by name against templates/, and
template.json is strict JSON — no comments.
Templates can also carry build output safely: node_modules/, dist/, .DS_Store, and
template.json itself are always skipped when copying and excluded from the published
package — so you can install deps or run a build inside a template without that leaking
downstream.
Add a command
Every command is a program.command() block in src/index.ts, registered alongside the
existing list and add blocks. The description, argument, and option declarations are
the single source of truth: Commander generates --help for the program and for each command
from them, so there is no usage string to keep in sync.
-
Register the command next to the existing blocks:
src/index.tsprogram .command("remove") .description("Delete a previously copied template") .argument("<template>", "template to remove") .option("-d, --dest <dir>", "directory the template was copied into", ".") .option("--dry-run", "print what would be removed without removing it", false) .action((template: string, opts: { dest: string; dryRun: boolean }) => removeTemplate(template, opts.dest, opts.dryRun), );Flag names are camelCased on the options object (
--dry-run→opts.dryRun), and defaults passed as the thirdoption()argument are shown in the generated help. -
Implement the logic as a named function (like the existing
addTemplate), keeping the.command()block declarative. Two error-handling conventions:- Expected failures (bad input, missing template): print a one-line
console.errorandprocess.exit(1). - Unexpected failures: just
throw— the top-leveltry/catcharoundprogram.parseAsync()prints the message and sets the exit code.
- Expected failures (bad input, missing template): print a one-line
-
Check the generated help — no extra work needed, it should already be there:
bun src/index.ts --help # `remove` appears in the command list bun src/index.ts remove --help # arguments, flags, and defaults -
Test it from source:
bun src/index.ts remove my-template --dry-run
Misspelled commands and flags get suggestions for free (fusion-config ad → "Did you mean
add?"), and unknown or missing arguments are rejected with proper errors — none of that needs
to be handled in your command.
Change a shared config
The configs documented in Shared configs live in configs/:
| File | Consumed by projects as |
|---|---|
configs/tsconfig.base.json | "extends": "@tikab-interactive/fusion-config/tsconfig.json" |
configs/oxlint.json | import config from "@tikab-interactive/fusion-config/oxlint" |
configs/oxfmt.json | import config from "@tikab-interactive/fusion-config/oxfmt" |
Edit the JSON and every consumer picks the change up with their next install — both import
routes read the same file. (The two-line .mjs files next to them are re-export shims that
make the JSON importable from oxlint.config.ts / oxfmt.config.ts.)
Two constraints to know about:
- Keep
configs/oxlint.jsonandconfigs/oxfmt.jsoncomment-free. The.mjsshims load them with Node JSON imports, which reject comments (oxlint itself would tolerate them; Node won't). Put the why of a rule in the commit message or on this site instead.tsconfig.base.jsonis only read by TypeScript, which accepts JSONC, so comments are fine there. ignorePatternsdoesn't travel. Oxlint only honors it in the project's root config, so adding it toconfigs/oxlint.jsonsilently does nothing for consumers.
This repo consumes its own configs — the root tsconfig.json, .oxlintrc.json, and
oxfmt.config.ts all extend configs/ exactly the way a downstream project does — so a config
change is verified by the local checks:
bun run typecheck && bun run lint && bun run fmt:checkA brand-new config file additionally needs an entry in package.json → exports (and ships
via the existing configs entry in files).
Publish a release
Templates and commands ship together when the package is published to GitHub Packages. You need
a token with the write:packages scope (see Setup for the .npmrc), then:
npm version patch # bump the version
npm publish # publishConfig points at GitHub Packages