Package contract
How a package plugs into kitr add, the CLI and the build: questions, codemods, commands, generators.
A kitr package is an npm package with two additions:
- a
kitrblock inpackage.jsonthat describes it, and - a setup module exported as
<package>/kitrthatkitr add,kitr remove,kitr doctorandkitr make:*run.
Official and third-party packages work the same way: kitr add @acme/anything does what kitr add jobs does.
The setup module
// src/kitr.ts, published as the "./kitr" export
import { definePackage } from '@kitr/core/package'
export default definePackage({
name: 'jobs',
requires: ['server'],
// Asked by `kitr add jobs`. Every question is also a flag (`--worker` / `--no-worker`),
// so CI and agents never see a prompt; `when` makes a question depend on earlier answers.
questions: [
{ name: 'worker', type: 'confirm', message: 'Run the job worker inside the server process?', default: true },
],
// Codemods. Every call is idempotent and queued: the CLI writes once, installs once, formats once.
add(ctx) {
ctx.provider.add('@kitr/jobs/provider')
ctx.config('jobs', jobsConfig(ctx.answers.worker !== false)) // writes config/jobs.ts
ctx.write('server/jobs/example.ts', exampleJob) // skipped when it exists
if (ctx.answers.worker === false) ctx.scripts.add('worker', 'kitr jobs:work')
ctx.agents.add('jobs', '- Background work is a job in server/jobs/*.ts ...')
},
remove(ctx) {
ctx.provider.remove('@kitr/jobs/provider')
ctx.scripts.remove('worker', 'kitr jobs:work')
ctx.agents.remove('jobs')
},
commands: () => import('./commands'), // `kitr jobs:list`, `kitr jobs:work`
generators: { job: { description: 'A job with a schema, retries and a schedule', target, stub } },
doctor: (project) => [], // extra `kitr doctor` findings
vite: { serverOnlyConfig: true }, // config/jobs.ts never reaches a browser bundle
})
The CLI installs the package itself and lists it in kitrrc.ts packages; add only does what is specific to the package. The service a package exposes is a container binding, declared the way every kitr package already does it: augment ContainerBindings in @kitr/core and bind it in the provider's register.
| Field | Type | What the CLI does with it |
|---|---|---|
name |
string | The short name: kitr add jobs, config/jobs.ts, the jobs: command prefix |
kind |
'package' (default), 'platform', 'kit' |
A platform is added as kitr add web site; a kit is used by kitr new --kit |
requires |
string[] | Added first, in order, each with its own questions. Short names or full package names |
client |
boolean | Refused in a project with no React |
dev |
boolean | Installed as a devDependency (tooling the app never imports at run time, such as testing) |
questions |
Question[] or (project) => Question[] |
Asked on add; --yes, CI or no TTY takes the defaults |
add(ctx) / remove(ctx) |
codemods | Run with the answers; --dry-run prints the plan |
commands |
() => import(...) |
Commands mounted as kitr <name>, such as kitr jobs:work |
generators |
Record<string, Generator> |
Mounted as kitr make:<kind>; the project's stubs/<kind>.ts overrides the stub |
doctor(project) |
Finding[] |
Appended to kitr doctor. Pure: read, never write |
types |
() => import(...) |
A module exporting a defineTypes() generator that kitr types runs into .kitr/ |
wraps |
Record<string, string> |
Upstream libraries this package re-exports, by the kitr entry to import instead; kitr doctor flags direct imports |
vite |
{ serverOnlyConfig, optimizeDeps, plugin, start, provider } |
What the package adds to the Vite build, such as a plugin: @kitr/tailwind/vite |
platform |
{ factory, export, from, defaultName } |
For kind: 'platform': the definePlatform factory kitrrc.ts calls, how it imports it, and the name kitr add <type> suggests |
kit |
{ description, platforms, packages, components, files } |
For kind: 'kit': what kitr new --kit sets up. See Writing a kit |
routes |
string | The module exporting this package's route-table extension |
eject |
Record<string, string> |
Import rewrites kitr eject all prints |
Questions
| Type | Flag | Answer |
|---|---|---|
select |
--driver redis |
one of options |
multiselect |
--targets ios,android |
a list of options |
confirm |
--worker / --no-worker |
boolean |
text |
--port 4000 |
string |
when(answers, project) skips a question (and its answer) when it returns false; validate(value) rejects a prompt or a flag with a message. kitr add <pkg> --help prints the questions as flags, read from the module.
What ctx can do
ctx edits the wiring files. Every call is idempotent.
ctx.deps.add('ioredis', { name: 'vite', dev: true }), ctx.deps.remove(...) |
package.json dependencies, installed once at the end |
ctx.packageJson.set('msw', { workerDirectory: ['public'] }), ctx.packageJson.remove(...) |
A top-level package.json field; remove only when it still holds that value |
ctx.provider.add('@kitr/jobs/provider'), .remove(...) |
the providers list in kitrrc.ts |
ctx.config('jobs', source) |
config/jobs.ts, skipped when it exists |
ctx.write(path, content, { overwrite }), ctx.unlink(path) |
any new file; delete a file kitr wrote |
ctx.edit(path, (mod, src) => ...) |
an existing file through magicast, or return replacement text |
ctx.scripts.add('worker', 'kitr jobs:work'), .remove(name, command?) |
package.json scripts |
ctx.env.add({ REDIS_URL: '...' }), ctx.env.schema('server', 'REDIS_URL', "e.url()") |
.env.example and app/env.ts |
ctx.agents.add('jobs', body), .remove('jobs') |
a ## kitr: jobs section in AGENTS.md |
ctx.tsconfig.addTypes(...), .addLib(...), .addInclude(...), .addPaths({...}), .set(option, value) |
tsconfig.json |
ctx.vite.addPlugin('tailwind', '@tailwindcss/vite') |
plugins in vite.config.ts, when the file exists |
ctx.routes.extend('@kitr/api/routes'), .remove(...) |
the extensions in the project's defineRoutes([...]) call |
ctx.server.attach({ marker, import, line, first }) |
one line and its import after createServer() in the server entry. first puts it before the others |
ctx.replaceImport(file, from, to) |
swap one side-effect import for another (@kitr/theme/styles.css for the Tailwind preset) |
ctx.types.enable() |
generated types in .kitr/, wired into tsconfig.json and .gitignore |
ctx.app.option('operations', '#kitr/actions'), .removeOption(...) |
pass a named value to createApp({ ... }) in app/app.ts, imported from the given module |
The project facts it may read: ctx.root, ctx.pm, ctx.pkg, ctx.rc (packages, providers, directories, platforms), ctx.isServerOnly, ctx.has('react') (a dependency, present or queued), ctx.hasPackage('server') (in kitrrc.ts, or added earlier in this run), ctx.exists(path), ctx.read(path), and ctx.answers.
The static block
{
"name": "@kitr/jobs",
"exports": { ".": "./dist/index.js", "./provider": "./dist/provider.js", "./kitr": "./dist/kitr.js" },
"kitr": {
"name": "jobs",
"title": "Jobs",
"summary": "Run work in the background or on a schedule, with retries when it fails.",
"category": "backend",
"runsOn": "server",
"requires": ["server"],
"docs": "docs",
"setup": "./kitr"
}
}
setup says the package ships a setup module; the CLI looks for the ./kitr export either way. kitr package:check validates the block, and the generated manifest table in the CLI (pnpm manifests) is what the site lists.
How kitr add resolves a name
jobs means @kitr/jobs; a name with a scope is used as written. If the package is not installed, the CLI installs it, then runs its kitr module. A package without a setup module is installed and listed in kitrrc.ts, and nothing else happens.
Commands
A commands module exports command objects in citty's shape (meta, args, run). Prefix them with the package name (jobs:work) so they never clash. A name that clashes with a core command or another package is an error.
Platforms and kits
A platform package (kind: 'platform') adds a platform block: the definePlatform factory kitrrc.ts calls, the export and module it is imported from, and the name kitr add <type> suggests. See Platforms.
A kit package (kind: 'kit') adds a kit block and runs on kitr new --kit <name>. See Kits.
Where the official setups are
Every official package keeps its setup in packages/<name>/src/kitr.ts, and every kit in packages/kit-<name>/src/kitr.ts.