Plan — @forinda/kickjs-cli-kit + @forinda/kickjs-db/cli
Status: proposal for review Goal: ship the
kick dbcommand tree from@forinda/kickjs-db/clias a mountable CLI plugin (+ optional standalone bin), so adopters can use the DB tooling without pulling all of@forinda/kickjs-cli, and it plugs into the same plugin ecosystem. Breaks thedb ↔ clidependency cycle via a shared contract package (decision: option A).
The cycle to break
@forinda/kickjs-cli already depends on @forinda/kickjs-db (it mounts the db commands). If db imported defineCliPlugin / KickCliPlugin from @forinda/kickjs-cli, that's db → cli → a cycle. So the CLI-plugin contract moves to a new dependency-free package both import.
Dependency reality (why the kit can't be a naïve cut-paste)
KickCliPlugin today references cli internals:
| Field | Type | Source today |
|---|---|---|
register | (program: Command, ctx) => … | commander (peer) |
commands | KickCommandDefinition[] | cli/config (small, self-contained) |
typegens | TypegenPlugin[] | cli/typegen/plugin → KickConfig + scanner (deep) |
generators | GeneratorSpec[] | cli/generator-extension/define (self-contained) |
ctx.config | KickConfig | null | cli/config (795 lines, deep) |
GeneratorSpec/GeneratorContext/GeneratorFile and KickCommandDefinition are self-contained → move wholesale. KickConfig and TypegenPlugin are not → the kit must reference them loosely so it doesn't drag half of cli.
@forinda/kickjs-cli-kit contents
Dependency-free except a commander peer. Exports:
Moved wholesale (no edits):
define.ts→defineGenerator,GeneratorSpec,GeneratorContext,GeneratorFile,GeneratorArg,GeneratorFlag. AndKickCommandDefinition(name/description/steps/aliases).The plugin contract, with the deep types loosened:
tsexport interface KickCliPluginContext<TConfig = unknown> { cwd: string projectRoot: string config: TConfig | null // cli passes its KickConfig; db reads ctx.config.db log: (msg: string) => void generators?: DiscoveredGeneratorLike[] } export interface KickCliPlugin<TConfig = unknown> { name: string commands?: KickCommandDefinition[] register?: (program: Command, ctx: KickCliPluginContext<TConfig>) => void | Promise<void> typegens?: CliTypegenLike[] // minimal { id; generate(ctx): … } — TypegenPlugin satisfies it generators?: GeneratorSpec[] } export function defineCliPlugin<T = unknown>(p: KickCliPlugin<T>): KickCliPlugin<T> export class KickPluginConflictError extends Error { … }TConfiggeneric defaults tounknown— cli instantiates asKickCliPlugin<KickConfig>; db usesKickCliPluginand readsctx.configas{ db?: KickDbConfigBlock }via a local cast.CliTypegenLikeis the minimal shape cli'sTypegenPluginstructurally satisfies (so the kit never imports the scanner).
@forinda/kickjs-cli changes
- Depend on
@forinda/kickjs-cli-kit. plugin/types.ts→ re-export the contract from the kit (back-compat:export { defineCliPlugin, KickCliPlugin, … } from '@forinda/kickjs-cli-kit'), and narrowKickCliPluginContext<KickConfig>where it consumes it.generator-extension/define.ts→ re-export from the kit (the file moved).merge.ts/cli.tskeep working — they already operate on the contract.- Mount the db plugin instead of hardcoding
registerDbCommands:cli.tsadds the db CLI plugin tobuiltinCliPlugins(sokick dbworks out of the box).commands/db.tsis deleted (logic moved to db).
@forinda/kickjs-db/cli
- New subpath entry
src/cli.ts(+ tsdown entry +./cliexport). - Depends on
@forinda/kickjs-cli-kit+commander(peer). - Exports
dbCliPlugin(adefineCliPlugin) — itsregister(program, ctx)builds thedbcommand tree (generate / migrate latest|up|down|rollback|status|review / introspect), reading config fromctx.config.dbandctx.projectRoot. - The command bodies move verbatim from
cli/src/commands/db.ts, with two swaps: config comes fromctx.config(notloadKickConfig), and the pg adapter import already points at@forinda/kickjs-db/pg(in-package). - Standalone bin
bin/kickjs-db.mjs: a ~15-line commander program that loadskick.config.{ts,js}(jiti, optional dep) and calls the plugin'sregister(). Letsnpx kickjs-db migrate latestwork without kickjs-cli.
Open micro-decisions (defaults if unanswered)
- Standalone bin — ship it? Default: yes (the whole point of "use db without kickjs-cli").
- kickjs-cli mounts db plugin — by default, or only when in
kick.config.ts plugins[]? Default: by default (sokick dbkeeps working with zero config; still available as a plugin for other CLIs).
Changeset
@forinda/kickjs-cli-kit— new package,0.1.0(or1.0.0).@forinda/kickjs-cli— minor (contract re-exported from kit; db commands now via plugin). Non-breaking for adopters importingdefineCliPluginfrom@forinda/kickjs-cli(re-exported).@forinda/kickjs-db— minor (/clisubpath + bin + cli-kit dep).
Risks
- commander version skew — kit, cli, db must agree on
commander(peer-pin a single range). ctx.configloose typing — db castsctx.configto read.db; a typo wouldn't be caught by the kit. Mitigation: db defines a local{ db?: KickDbConfigBlock }view + asserts shape at runtime (it already defaults each field).- Back-compat — keep
cli'sdefineCliPlugin/defineGeneratorre-exports so existing adopterkick.config.tsimports don't break. - Bin config loading — the standalone bin needs a jiti (optional) to read a
.tsconfig; document that.js/.mjs/.jsonconfigs work without it.