@forinda/kickjs-cli 5.11.1 → 6.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/agent-docs-hbOXsAAh.mjs +12 -0
  2. package/dist/agent-docs-hbOXsAAh.mjs.map +1 -0
  3. package/dist/{builtins-BL1BhYEv.mjs → builtins-Dyk9a-mv.mjs} +2 -2
  4. package/dist/cli.mjs +151 -1376
  5. package/dist/config-DSpcRefL.mjs +13 -0
  6. package/dist/config-DSpcRefL.mjs.map +1 -0
  7. package/dist/doctor-559QZlHi.mjs +1221 -0
  8. package/dist/doctor-559QZlHi.mjs.map +1 -0
  9. package/dist/index.d.mts +663 -853
  10. package/dist/index.d.mts.map +1 -1
  11. package/dist/index.mjs +2 -2
  12. package/dist/{plugin-C4hfxiPw.mjs → plugin-DK01q7wy.mjs} +3 -3
  13. package/dist/{plugin-C4hfxiPw.mjs.map → plugin-DK01q7wy.mjs.map} +1 -1
  14. package/dist/{project-docs-CfB-KVN5.mjs → project-docs-CrfNQIZA.mjs} +6 -36
  15. package/dist/project-docs-CrfNQIZA.mjs.map +1 -0
  16. package/dist/{project-root-CDYKLnfG.mjs → project-root-BdTe6EpE.mjs} +3 -3
  17. package/dist/{project-root-CDYKLnfG.mjs.map → project-root-BdTe6EpE.mjs.map} +1 -1
  18. package/dist/{rolldown-runtime-CeWwRE8g.mjs → rolldown-runtime-CoN4EDcd.mjs} +1 -1
  19. package/dist/run-plugins-BAYoDnFI.mjs +636 -0
  20. package/dist/run-plugins-BAYoDnFI.mjs.map +1 -0
  21. package/dist/typegen-CwtvFZ0t.mjs +114 -0
  22. package/dist/typegen-CwtvFZ0t.mjs.map +1 -0
  23. package/dist/types-BKKzf_bU.mjs +11 -0
  24. package/package.json +13 -13
  25. package/dist/agent-docs-CXqrGZLl.mjs +0 -12
  26. package/dist/agent-docs-CXqrGZLl.mjs.map +0 -1
  27. package/dist/config-Cf8GU8CG.mjs +0 -13
  28. package/dist/config-Cf8GU8CG.mjs.map +0 -1
  29. package/dist/doctor-Dl709LzL.mjs +0 -2076
  30. package/dist/doctor-Dl709LzL.mjs.map +0 -1
  31. package/dist/project-docs-CfB-KVN5.mjs.map +0 -1
  32. package/dist/run-plugins-M_WVt-7a.mjs +0 -976
  33. package/dist/run-plugins-M_WVt-7a.mjs.map +0 -1
  34. package/dist/typegen-CezcLjMb.mjs +0 -114
  35. package/dist/typegen-CezcLjMb.mjs.map +0 -1
  36. package/dist/types-DvYczI2m.mjs +0 -12
  37. package/dist/types-DvYczI2m.mjs.map +0 -1
package/dist/index.d.mts CHANGED
@@ -1,6 +1,5 @@
1
1
 
2
- import { Command } from "commander";
3
-
2
+ import { GeneratorArg, GeneratorContext, GeneratorFile, GeneratorFlag, GeneratorSpec, KickCliPlugin as KickCliPlugin$1, KickCliPluginContext as KickCliPluginContext$1, KickPluginConflictError, defineCliPlugin, defineGenerator } from "@forinda/kickjs-cli-kit";
4
3
  //#region src/commands/doctor.d.ts
5
4
  /**
6
5
  * `kick doctor` — pre-flight checks for a KickJS project's dev
@@ -101,911 +100,356 @@ declare function defineDoctorExtension(ext: DoctorExtension): DoctorExtension;
101
100
  */
102
101
  declare function defineDoctorCheck(check: DoctorCheck): DoctorCheck;
103
102
  //#endregion
104
- //#region src/typegen/scanner.d.ts
105
- /**
106
- * Static scanner for KickJS decorated classes and DI tokens.
107
- *
108
- * Walks `src/**\/*.ts` (excluding tests and node_modules) and extracts:
109
- *
110
- * - Decorated classes (`@Service`, `@Controller`, `@Repository`, etc.)
111
- * - `createToken<T>('name')` definitions
112
- * - `@Inject('literal')` calls
113
- *
114
- * The output feeds the type generator, which emits `.kickjs/types/*.d.ts`
115
- * files used by the user's tsc to make `container.resolve()` and module
116
- * discovery type-safe.
117
- *
118
- * This is intentionally regex-based (not AST-based) to avoid the
119
- * ts-morph / typescript compiler dependency. Pattern from
120
- * `packages/vite/src/module-discovery.ts` which already uses regex
121
- * to detect `*.module.ts` exports.
122
- *
123
- * ## Collision detection
124
- *
125
- * Two classes with the same name across different files is a collision.
126
- * The scanner records all collisions in `ScanResult.collisions` so the
127
- * caller (generator) can decide whether to hard-error or auto-namespace.
128
- *
129
- * @module @forinda/kickjs-cli/typegen/scanner
130
- */
131
- /** Decorators that mark a class as DI-managed */
132
- declare const DECORATOR_NAMES: readonly ['Service', 'Controller', 'Repository', 'Injectable', 'Component', 'Module'];
133
- type DecoratorName = (typeof DECORATOR_NAMES)[number];
134
- /** A single discovered decorated class */
135
- interface DiscoveredClass {
136
- /** Class name (e.g., 'UserService') */
137
- className: string;
138
- /** Decorator that marked it (e.g., 'Service') */
139
- decorator: DecoratorName;
140
- /** Absolute file path */
141
- filePath: string;
142
- /** Path relative to scan root, with forward slashes */
143
- relativePath: string;
144
- /** True if exported as `default` */
145
- isDefault: boolean;
146
- }
147
- /** A single route handler discovered on a controller class */
148
- interface DiscoveredRoute {
149
- /** Owning controller class name (e.g. 'UserController') */
150
- controller: string;
151
- /** Handler method name on the controller (e.g. 'getUser') */
152
- method: string;
153
- /** HTTP verb (uppercase) */
154
- httpMethod: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
155
- /** Route path including parameter placeholders (e.g. '/:id/posts/:postId') */
156
- path: string;
157
- /** URL path parameter names extracted from `:placeholder` segments */
158
- pathParams: string[];
159
- /**
160
- * Whitelisted query field names extracted from `@ApiQueryParams({...})`.
161
- * `null` means no `@ApiQueryParams` was found on this method (so the
162
- * generator emits an unconstrained `query` shape). An empty array means
163
- * the decorator existed but no fields could be statically extracted
164
- * (e.g. an opaque imported config).
165
- */
166
- queryFilterable: string[] | null;
167
- querySortable: string[] | null;
168
- querySearchable: string[] | null;
169
- /**
170
- * Schema identifiers referenced from the route decorator's second arg
171
- * (e.g. `@Post('/', { body: createTaskSchema })`). `null` means no
172
- * such reference; the value carries the identifier and the resolved
173
- * import source (relative module path) if known.
174
- */
175
- bodySchema: SchemaRef | null;
176
- querySchema: SchemaRef | null;
177
- paramsSchema: SchemaRef | null;
178
- /** Absolute file path of the controller */
179
- filePath: string;
180
- /** Path relative to scan root, with forward slashes */
181
- relativePath: string;
182
- }
183
- /** A statically-resolved schema identifier reference */
184
- interface SchemaRef {
185
- /** The identifier as written (e.g. `createTaskSchema`) */
186
- identifier: string;
103
+ //#region src/plugin/types.d.ts
104
+ /** CLI plugin context with the host config narrowed to `KickConfig`. */
105
+ type KickCliPluginContext = KickCliPluginContext$1<KickConfig>;
106
+ /** A CLI plugin with the host config narrowed to `KickConfig`. */
107
+ type KickCliPlugin = KickCliPlugin$1<KickConfig>;
108
+ //#endregion
109
+ //#region src/config.d.ts
110
+ /** A custom command that developers can register via kick.config.ts */
111
+ interface KickCommandDefinition {
112
+ /** The command name (e.g. 'db:migrate', 'seed', 'proto:gen') */
113
+ name: string;
114
+ /** Description shown in --help */
115
+ description: string;
187
116
  /**
188
- * Resolved module specifier (relative path or bare module name) where
189
- * the identifier is defined. `null` means the source could not be
190
- * statically determined (the generator falls back to `unknown`).
117
+ * Shell command(s) to run. Can be a single string or an array of
118
+ * sequential steps. Use {args} as a placeholder for CLI arguments.
119
+ *
120
+ * @example
121
+ * 'npx drizzle-kit migrate'
122
+ * ['npx drizzle-kit generate', 'npx drizzle-kit migrate']
191
123
  */
192
- source: string | null;
193
- }
194
- /** A `createToken<T>('name')` call discovered in source */
195
- interface DiscoveredToken {
196
- /** The literal string passed to `createToken()` */
197
- name: string;
198
- /** The const variable name on the LHS, if any */
199
- variable: string | null;
200
- /** Absolute file path */
201
- filePath: string;
202
- /** Path relative to scan root, with forward slashes */
203
- relativePath: string;
204
- }
205
- /** An `@Inject('literal')` call discovered in source */
206
- interface DiscoveredInject {
207
- /** The literal string passed to `@Inject()` */
208
- name: string;
209
- /** Absolute file path */
210
- filePath: string;
211
- /** Path relative to scan root, with forward slashes */
212
- relativePath: string;
213
- }
214
- /** A name collision — same class name in two or more files */
215
- interface ClassCollision {
216
- /** The colliding class name */
217
- className: string;
218
- /** All files declaring the class */
219
- classes: DiscoveredClass[];
124
+ steps: string | string[];
125
+ /** Optional aliases (e.g. ['migrate'] for 'db:migrate') */
126
+ aliases?: string[];
220
127
  }
128
+ /** Project pattern — controls what generators produce and which deps are installed */
129
+ type ProjectPattern = 'rest' | 'minimal';
130
+ /** Package manager used for `kick add` and other dep-installing commands */
131
+ type PackageManager = 'pnpm' | 'npm' | 'yarn' | 'bun';
221
132
  /**
222
- * Information about a discovered env schema file. The typegen
223
- * generator uses this to emit a `KickEnv` + `NodeJS.ProcessEnv`
224
- * augmentation that flows through to `@Value` and `process.env`.
133
+ * Built-in repository type with first-class code generation support.
225
134
  *
226
- * `null` means no env file was found at the configured location.
135
+ * Only `inmemory` remains built-in (zero-dep, framework-owned). The
136
+ * `prisma` and `drizzle` ORM presets are **deprecated** — they now
137
+ * scaffold a generic custom-repository stub like any other name (see
138
+ * {@link DEPRECATED_REPO_TYPES}). Bring your own DB by passing a name:
139
+ * `repo: { name: 'postgres' }`.
227
140
  */
228
- interface DiscoveredEnv {
229
- /** Absolute path to the env schema file */
230
- filePath: string;
231
- /** Path relative to scan root, with forward slashes */
232
- relativePath: string;
141
+ type BuiltinRepoType$1 = 'inmemory';
142
+ /** Custom repository type generates a stub with TODO markers */
143
+ interface CustomRepoType {
144
+ name: string;
233
145
  }
146
+ /** Repository type — built-in string or custom object */
147
+ type RepoTypeConfig = BuiltinRepoType$1 | CustomRepoType;
234
148
  /**
235
- * A plugin or adapter discovered in source — either via `defineAdapter({ name })`
236
- * / `definePlugin({ name })` calls, or via a class that `implements AppAdapter`
237
- * and declares a string-literal `name` field.
149
+ * Supported schema validators for `kick typegen` body/query/params
150
+ * type extraction.
238
151
  *
239
- * The `name` here is the literal string passed to the framework (the value
240
- * `dependsOn` references), NOT the symbol on the LHS. `defineAdapter` lets
241
- * authors choose any name they want; the symbol is irrelevant at runtime.
152
+ * - `'zod'` emits `import('zod').infer<typeof schema>` (default)
153
+ * - `'kickjs-schema'` emits `InferSchemaOutput<typeof schema>` from
154
+ * `@forinda/kickjs-schema`, works with Zod, Valibot, Yup, and any
155
+ * Standard Schema v1 or KickSchema adapter
156
+ * - `false` — disables schema-driven body typing (fields stay `unknown`)
242
157
  */
243
- interface DiscoveredPluginOrAdapter {
244
- /** Whether this is a plugin (`definePlugin`) or adapter (`defineAdapter` / class) */
245
- kind: 'plugin' | 'adapter';
246
- /** The string literal passed as `name` (the value `dependsOn` references) */
247
- name: string;
248
- /** Absolute file path */
249
- filePath: string;
250
- /** Path relative to scan root, with forward slashes */
251
- relativePath: string;
252
- }
158
+ type SchemaValidator = 'zod' | 'kickjs-schema' | false;
253
159
  /**
254
- * A context key discovered from a `defineContextDecorator({ key })` or
255
- * `defineHttpContextDecorator({ key })` call (including the curried
256
- * `.withParams<P>()({ key })` form). Feeds the `kick/context` typegen
257
- * plugin, which emits the `ContextKeys` augmentation so `dependsOn`
258
- * typo-checking is automatic and complete.
160
+ * One entry in the typed `assetMap` config record (`assets-plan.md`).
161
+ * Each entry names a source directory whose files become addressable
162
+ * via the `assets.<name>.*` typed accessor at runtime.
259
163
  */
260
- interface DiscoveredContextKey {
261
- /** The literal `key:` value the contributor writes. */
262
- key: string;
263
- /** Absolute file path. */
264
- filePath: string;
265
- /** Path relative to scan root, with forward slashes. */
266
- relativePath: string;
164
+ interface AssetMapEntry {
165
+ /**
166
+ * Source directory, relative to project root. Required. The directory
167
+ * must exist when `kick build` runs — `loadKickConfig` warns when an
168
+ * entry points at a missing directory but doesn't fail the load
169
+ * (the typegen + build steps surface the error in context instead).
170
+ */
171
+ src: string;
172
+ /**
173
+ * Destination directory inside `dist/`. Defaults to `dist/<name>/`
174
+ * where `<name>` is the assetMap key. Override when the consumer of
175
+ * the assets expects a non-standard layout (e.g. an existing
176
+ * downstream tool reads from `dist/templates/...`).
177
+ */
178
+ dest?: string;
179
+ /**
180
+ * Glob pattern for which files to include. Defaults to `**\/*` (all
181
+ * files). Files that don't match are NOT copied — `assetMap` is
182
+ * selective by design (unlike `copyDirs` which copies everything).
183
+ */
184
+ glob?: string;
185
+ /**
186
+ * How file extensions feed into manifest keys. Default `'auto'`.
187
+ *
188
+ * - `'strip'` — drop the extension. `pages/index.pug` →
189
+ * `'pages/index'`. Two siblings with the same basename collide;
190
+ * last-walk-order wins, others are silently dropped. Opt-in
191
+ * only — kept for backward compatibility with projects that
192
+ * relied on this contract.
193
+ * - `'with-extension'` — keep every extension on every key. Best
194
+ * when the namespace holds extension siblings (`index.pug` +
195
+ * `index.html` + `index.css` in `src/pages/`); every file
196
+ * reaches the manifest under its full path.
197
+ * - `'auto'` — strip when basenames are unique, keep extensions
198
+ * on collision groups. Singleton files keep their short key;
199
+ * `pages/index.{pug,html,css}` becomes
200
+ * `pages/index.pug` / `pages/index.html` / `pages/index.css`.
201
+ * No data loss; non-colliding namespaces stay on the short
202
+ * keys they had before.
203
+ *
204
+ * @default 'auto'
205
+ */
206
+ keys?: 'auto' | 'strip' | 'with-extension';
267
207
  }
268
208
  /**
269
- * A `defineAugmentation('Name', meta)` call discovered in source. Plugins
270
- * call this to advertise an augmentable interface so the typegen can list
271
- * every augmentation surface in one generated file.
209
+ * Database settings consumed by `kick db generate`, `kick db migrate`,
210
+ * and the M4.C composite-type detection gate. Mirrors the runtime
211
+ * `DbConfig` shape from `@forinda/kickjs-db` duplicated here so
212
+ * adopters who only install `@forinda/kickjs-cli` can set the block
213
+ * without pulling `@forinda/kickjs-db` types into their kick.config.ts
214
+ * resolution. The CLI loads kick.config.ts through `loadKickConfig`,
215
+ * then `resolveDbConfig` re-reads this block from the same module and
216
+ * normalises defaults (`schemaPath` / `migrationsDir` / `dialect`).
217
+ *
218
+ * @example
219
+ * ```ts
220
+ * defineConfig({
221
+ * db: {
222
+ * schemaPath: 'src/db/schema.ts',
223
+ * migrationsDir: 'db/migrations',
224
+ * dialect: 'postgres',
225
+ * connectionString: process.env.DATABASE_URL,
226
+ * },
227
+ * })
228
+ * ```
272
229
  */
273
- interface DiscoveredAugmentation {
274
- /** The literal string passed as the first arg to `defineAugmentation` */
275
- name: string;
276
- /** Optional `description` extracted from the second-arg object literal */
277
- description: string | null;
278
- /** Optional `example` extracted from the second-arg object literal */
279
- example: string | null;
280
- /** Absolute file path */
281
- filePath: string;
282
- /** Path relative to scan root, with forward slashes */
283
- relativePath: string;
284
- }
285
- /**
286
- * A decorated class whose file sits inside a module directory but
287
- * isn't picked up by any of the module's `import.meta.glob(...)`
288
- * patterns. Surfaced as a typegen warning per forinda/kick-js#235 §4
289
- * so adopters notice silent registration drift before it bites them
290
- * at runtime with a `MissingContributorError` or wrong code path.
291
- */
292
- interface OrphanedClass {
293
- /** The decorated class name */
294
- className: string;
295
- /** Absolute path of the class file */
296
- filePath: string;
297
- /** Path relative to scan root, with forward slashes */
298
- relativePath: string;
299
- /** Absolute path of the module file whose globs didn't match */
300
- moduleFilePath: string;
301
- /** The decorator name (`Service`, `Controller`, `Repository`, …) */
302
- decorator: DecoratorName;
303
- }
304
- /** Aggregated scanner output */
305
- interface ScanResult {
306
- classes: DiscoveredClass[];
307
- routes: DiscoveredRoute[];
308
- tokens: DiscoveredToken[];
309
- injects: DiscoveredInject[];
310
- collisions: ClassCollision[];
311
- /** Discovered env schema file (or null if none found at the configured path) */
312
- env: DiscoveredEnv | null;
313
- /** Plugins/adapters discovered via `defineAdapter`/`definePlugin`/`implements AppAdapter` */
314
- pluginsAndAdapters: DiscoveredPluginOrAdapter[];
315
- /** Augmentation interfaces declared via `defineAugmentation('Name', meta)` */
316
- augmentations: DiscoveredAugmentation[];
317
- /** Context keys from `define(Http)ContextDecorator({ key })` calls */
318
- contextKeys: DiscoveredContextKey[];
230
+ interface KickDbConfigBlock {
319
231
  /**
320
- * Decorated classes that sit inside a module directory but aren't
321
- * picked up by any of the module's `import.meta.glob(...)` patterns.
322
- * Empty when every decorator file is matched. forinda/kick-js#235 §4.
232
+ * Path to the schema module (`pgEnum` / `table` declarations).
233
+ * Defaults to `'src/db/schema.ts'`.
323
234
  */
324
- orphanedClasses: OrphanedClass[];
325
- }
326
- /** Options for the scanner */
327
- interface ScanOptions {
328
- /** Root directory to scan (e.g., absolute path to `src`) */
329
- root: string;
330
- /** Project root used to compute relative paths (e.g., process.cwd()) */
331
- cwd: string;
332
- /** Glob-like extensions to scan */
333
- extensions?: string[];
334
- /** Substrings that exclude a path (matched against relative path) */
335
- exclude?: string[];
235
+ schemaPath?: string;
336
236
  /**
337
- * Path to the env schema file, relative to `cwd`. Defaults to
338
- * `'src/env.ts'`. The file must contain a `defineEnv(...)` call
339
- * with a default export for the typegen to emit a typed `KickEnv`
340
- * augmentation. If the file does not exist or doesn't match the
341
- * expected shape, env typing is skipped silently.
237
+ * Where `kick db generate` writes migration directories. Defaults
238
+ * to `'db/migrations'`.
342
239
  */
343
- envFile?: string;
344
- }
345
- //#endregion
346
- //#region src/typegen/plugin.d.ts
347
- interface TypegenLogger {
348
- info(msg: string): void;
349
- warn(msg: string): void;
350
- error(msg: string): void;
351
- }
352
- interface TypegenContext {
353
- cwd: string;
354
- config: KickConfig;
355
- /** Dynamic-import a TS module (Node loader). Used by plugins that need to
356
- * read the adopter's schema / route map / asset registry at generate time. */
357
- importTs<T = unknown>(absPath: string): Promise<T>;
358
- /** Write under `cwd`. Caller passes a relPath (e.g. `.kickjs/types/foo.d.ts`). */
359
- writeFile(relPath: string, contents: string): Promise<void>;
240
+ migrationsDir?: string;
241
+ /** SQL dialect. Defaults to `'postgres'`. */
242
+ dialect?: 'postgres' | 'sqlite' | 'mysql';
360
243
  /**
361
- * Run `scanProject` once per typegen pass, memoizing the result so
362
- * multiple plugins (`kick/routes`, `kick/env`, future adopter plugins)
363
- * share a single fs walk + AST extraction.
364
- *
365
- * The runner uses an order-independent cache key derived from the
366
- * resolved options (`root`, `cwd`, `extensions`, `exclude`, `envFile`)
367
- * — semantically equal options hit the cache regardless of how the
368
- * caller built the literal. (We deliberately don't `JSON.stringify`
369
- * the options for caching since that would be sensitive to property
370
- * insertion order.) Plugins that don't need scanner data can ignore
371
- * this method entirely.
372
- *
373
- * Implementation lives in the runner so test harnesses can inject
374
- * a stub scanner; plugins only see the function.
244
+ * Postgres connection string for the built-in pgAdapter path. Read
245
+ * from the `DATABASE_URL` env var when omitted. Used by
246
+ * `kick db migrate*` and the M4.C composite-type gate at
247
+ * `kick db generate`.
375
248
  */
376
- getScanResult(opts: ScanOptions): Promise<ScanResult>;
377
- log: TypegenLogger;
249
+ connectionString?: string;
250
+ /**
251
+ * Escape hatch: a factory returning a fully-constructed
252
+ * MigrationAdapter (typed loosely here so the CLI types don't pull
253
+ * `@forinda/kickjs-db` into adopter projects that don't import it).
254
+ * Takes precedence over `connectionString` when both are set.
255
+ */
256
+ adapter?: () => unknown | Promise<unknown>;
378
257
  }
379
- interface TypegenPlugin {
380
- /** Stable id — used as filename: `.kickjs/types/${id}<outExtension>` (slashes → `__`). */
381
- id: string;
382
- /** Glob patterns the Vite watcher subscribes to; change → re-run this plugin. */
383
- inputs: string[];
258
+ /** Typegen settings — controls .kickjs/types/* generation */
259
+ interface TypegenConfig {
384
260
  /**
385
- * Output filename extension. Default `.d.ts` the right choice for
386
- * pure module-augmentation plugins (kick/db, kick/assets) since
387
- * declaration files don't need the runtime-import dance.
388
- *
389
- * `kick/routes` overrides to `.ts` because it emits hoisted
390
- * `import type {...} from '...'` lines at the top of the file. Inline
391
- * `import('...').X` references inside `.d.ts` silently degrade to
392
- * `unknown` under `moduleResolution: 'bundler'`; emitting `.ts`
393
- * sidesteps that quirk and gets full type resolution.
394
- *
395
- * Adopter-supplied plugins should leave this unset unless they hit
396
- * the same hoisted-import constraint.
261
+ * Source directory to scan for controllers and decorators.
262
+ * Defaults to `'src'`.
397
263
  */
398
- outExtension?: string;
264
+ srcDir?: string;
399
265
  /**
400
- * Return the augmentation source (without banner — runner prepends).
401
- * Return null to skip emission (e.g. no schema file present).
266
+ * Output directory for generated `.d.ts` files.
267
+ * Defaults to `'.kickjs/types'`.
402
268
  */
403
- generate(ctx: TypegenContext): Promise<string | null>;
404
- }
405
- interface TypegenPluginResult {
406
- id: string;
407
- status: 'written' | 'unchanged' | 'skipped' | 'error';
408
- outFile?: string;
409
- }
410
- /**
411
- * Identity factory for {@link TypegenPlugin}. Returns the spec verbatim.
412
- * Exists for type inference and forward-compatibility — future
413
- * fields can be added with defaults without breaking adopters.
414
- *
415
- * Mirrors {@link defineGenerator} ergonomics. Use at the call site so
416
- * the plugin's `generate(ctx)` body gets a fully-typed `ctx` without
417
- * an explicit annotation:
418
- *
419
- * @example
420
- * ```ts
421
- * import { defineTypegen } from '@forinda/kickjs-cli'
422
- *
423
- * export const drizzleTypegen = defineTypegen({
424
- * id: 'drizzle',
425
- * inputs: ['src/db/schema.ts'],
426
- * async generate(ctx) {
427
- * const schema = await ctx.importTs(`${ctx.cwd}/src/db/schema.ts`)
428
- * return `// declare module …`
429
- * },
430
- * })
431
- * ```
432
- */
433
- declare function defineTypegen(spec: TypegenPlugin): TypegenPlugin;
434
- //#endregion
435
- //#region src/generator-extension/define.d.ts
436
- /**
437
- * Plugin generator API per architecture.md §21.2.3 — lets first-party
438
- * AND third-party packages ship `kick g <name>` scaffolders the same
439
- * way the framework's built-in generators do.
440
- *
441
- * Plugins declare a discovery file in their `package.json`:
442
- *
443
- * ```json
444
- * {
445
- * "name": "@my-org/kickjs-cqrs",
446
- * "kickjs": { "generators": "./dist/generators.js" }
447
- * }
448
- * ```
449
- *
450
- * The discovery file exports a typed manifest:
451
- *
452
- * ```ts
453
- * import { defineGenerator } from '@forinda/kickjs-cli'
454
- *
455
- * export default [
456
- * defineGenerator({
457
- * name: 'command',
458
- * description: 'Generate a CQRS command + handler',
459
- * args: [{ name: 'name', required: true }],
460
- * files: (ctx) => [
461
- * {
462
- * path: `src/modules/${ctx.kebab}/commands/create-${ctx.kebab}.command.ts`,
463
- * content: `// generated command for ${ctx.pascal}`,
464
- * },
465
- * ],
466
- * }),
467
- * ]
468
- * ```
469
- *
470
- * `kick g command Order` then dispatches against the registered
471
- * generator and writes the returned files relative to `cwd`.
472
- */
473
- /**
474
- * Resolved naming variants + project context handed to a generator's
475
- * `files()` factory. Keys mirror `TemplateContext` so first-party
476
- * generators that rely on the same shape can be migrated to plugin
477
- * generators without rewriting their templates.
478
- */
479
- interface GeneratorContext {
480
- /** Raw name passed on the command line (`kick g drizzle-typegen UserPost` → `'UserPost'`). */
481
- name: string;
482
- /** PascalCase form (`UserPost`). */
483
- pascal: string;
484
- /** kebab-case form (`user-post`). */
485
- kebab: string;
486
- /** camelCase form (`userPost`). */
487
- camel: string;
488
- /** snake_case form (`user_post`). */
489
- snake: string;
490
- /** Pluralized PascalCase (`UserPosts`) — present when the project enables `pluralize`. */
491
- pluralPascal?: string;
492
- /** Pluralized kebab-case (`user-posts`). */
493
- pluralKebab?: string;
494
- /** Pluralized camelCase (`userPosts`). */
495
- pluralCamel?: string;
496
- /** Modules directory from `kick.config.ts` (default `'src/modules'`). */
497
- modulesDir: string;
269
+ outDir?: string;
498
270
  /**
499
- * Working directory the CLI was invoked from. May be a nested subdirectory
500
- * if the adopter ran `kick g ...` from `src/modules/foo/` — for "write
501
- * files relative to the project" use {@link projectRoot} instead.
271
+ * Schema validator used to derive `body` types from route metadata.
272
+ *
273
+ * - `'zod'` emit `z.infer<typeof <importedSchema>>` for any schema
274
+ * referenced as a named identifier in `@Get/@Post/...({ body, query, params })`.
275
+ * - `false` — disable schema-driven body typing.
276
+ *
277
+ * Future: `'joi' | 'yup' | 'json-schema'` plus a `{ name; module }`
278
+ * escape hatch for custom adapters.
279
+ *
280
+ * @default 'zod'
502
281
  */
503
- cwd: string;
282
+ schemaValidator?: SchemaValidator;
504
283
  /**
505
- * Resolved project root the nearest ancestor directory of {@link cwd}
506
- * containing `kick.config.{ts,js,mjs,json}` (or `package.json` as a
507
- * fallback). Always set; falls back to `cwd` when no marker is found.
284
+ * Path to the project's env schema file (relative to project root).
285
+ * Must default-export a `defineEnv(...)` schema for typegen to emit
286
+ * the typed `KickEnv` global registry.
287
+ *
288
+ * Set to `false` to disable env typing entirely.
508
289
  *
509
- * Use this when writing files that must land at a known location
510
- * regardless of where the adopter typed the command. `kick g ...` from
511
- * inside `src/modules/foo/` will still see `projectRoot` pointing at
512
- * the directory that owns `kick.config.ts`.
290
+ * @default 'src/env.ts'
513
291
  */
514
- projectRoot: string;
515
- /** Positional arguments passed AFTER the name (e.g. `kick g command Order extra1 extra2` → `['extra1', 'extra2']`). */
516
- args: string[];
517
- /** Flag values from the command line — booleans for switches, strings for `--key value`. */
518
- flags: Record<string, string | boolean>;
519
- }
520
- /** A single output file the generator wants written. */
521
- interface GeneratorFile {
292
+ envFile?: string | false;
522
293
  /**
523
- * Output path. Relative paths resolve against `ctx.cwd`; absolute
524
- * paths are used as-is. Parent directories are created automatically.
294
+ * Built-in or user typegen plugin ids to skip during `kick typegen`,
295
+ * `kick dev`, and `kick typegen --watch`.
296
+ *
297
+ * The plugin still loads and merge-time conflict detection still
298
+ * runs — only the `generate()` invocation is skipped — so adopters
299
+ * who want to hand-write `KickDbRegister` (manual typeof-schema
300
+ * augmentation) can disable `'kick/db'` and keep the rest:
301
+ *
302
+ * @example
303
+ * typegen: {
304
+ * disable: ['kick/db'], // hand-written register.ts owns the type
305
+ * }
306
+ *
307
+ * Unrecognised ids are ignored — the list is treated as a wishlist,
308
+ * not a strict registry.
525
309
  */
526
- path: string;
527
- /** File contents written verbatim (UTF-8). */
528
- content: string;
529
- }
530
- /** CLI argument descriptor surfaced in `kick g --list` help. */
531
- interface GeneratorArg {
532
- name: string;
533
- required?: boolean;
534
- description?: string;
535
- }
536
- /** CLI flag descriptor — boolean unless `takesValue: true`. */
537
- interface GeneratorFlag {
538
- name: string;
539
- alias?: string;
540
- description?: string;
541
- takesValue?: boolean;
310
+ disable?: string[];
542
311
  }
543
- /**
544
- * Spec returned by {@link defineGenerator}. Plugin discovery files
545
- * export `GeneratorSpec[]` as their default export.
546
- */
547
- interface GeneratorSpec {
312
+ /** Module generation settings — controls how `kick g module` produces code */
313
+ interface ModuleConfig {
314
+ /** Where modules live (default: 'src/modules') */
315
+ dir?: string;
548
316
  /**
549
- * Dispatch name `kick g <name>` looks for an exact match against
550
- * this string after the built-in generators are checked.
317
+ * Default repository implementation for generators.
318
+ *
319
+ * Built-in types (string): `'drizzle'`, `'inmemory'`, `'prisma'`
320
+ * — generate fully working repository code.
321
+ *
322
+ * Custom types (object): `{ name: 'typeorm' }`
323
+ * — generate a stub repository with TODO markers.
324
+ *
325
+ * @example
326
+ * repo: 'prisma' // built-in
327
+ * repo: { name: 'typeorm' } // custom
551
328
  */
552
- name: string;
553
- /** Description shown in `kick g --list` and `--help`. */
554
- description: string;
555
- /** Optional argument descriptors — informational, surfaced in help output. */
556
- args?: readonly GeneratorArg[];
557
- /** Optional flag descriptors — informational, surfaced in help output. */
558
- flags?: readonly GeneratorFlag[];
559
- /** Build the output files for one invocation. May return a Promise. */
560
- files(ctx: GeneratorContext): GeneratorFile[] | Promise<GeneratorFile[]>;
561
- }
562
- /**
563
- * Identity factory — returns the spec verbatim. Exists for type
564
- * inference and forward-compatibility (future fields can be added with
565
- * defaults).
566
- *
567
- * @example
568
- * ```ts
569
- * import { defineGenerator } from '@forinda/kickjs-cli'
570
- *
571
- * export default [
572
- * defineGenerator({
573
- * name: 'command',
574
- * description: 'Generate a CQRS command + handler',
575
- * files: (ctx) => [
576
- * {
577
- * path: `src/modules/${ctx.kebab}/commands/${ctx.kebab}.command.ts`,
578
- * content: `// command for ${ctx.pascal}`,
579
- * },
580
- * ],
581
- * }),
582
- * ]
583
- * ```
584
- */
585
- declare function defineGenerator(spec: GeneratorSpec): GeneratorSpec;
586
- //#endregion
587
- //#region src/generator-extension/discover.d.ts
588
- /**
589
- * One row in the discovered registry. `source` is the npm package name
590
- * the generator came from — surfaced in error messages so adopters can
591
- * see which plugin owns a given generator.
592
- */
593
- interface DiscoveredGenerator {
594
- source: string;
595
- spec: GeneratorSpec;
596
- }
597
- /**
598
- * Plugin discovery result, kept around even when no generators were
599
- * registered so callers can distinguish "no plugins installed" from
600
- * "no plugins matched the requested name."
601
- */
602
- interface DiscoveryResult {
603
- generators: DiscoveredGenerator[];
604
- /** Packages whose `kickjs.generators` was loaded successfully. */
605
- loaded: string[];
606
- /**
607
- * Packages we tried to load but failed — typically a missing entry
608
- * file or a default export that wasn't an array of GeneratorSpec.
609
- */
610
- failed: Array<{
611
- source: string;
612
- reason: string;
613
- }>;
614
- }
615
- //#endregion
616
- //#region src/plugin/types.d.ts
617
- /**
618
- * Runtime context handed to `register()` — saves callbacks from
619
- * re-loading config or guessing cwd. Forward-compatible: future fields
620
- * land here without changing the callback signature.
621
- */
622
- interface KickCliPluginContext {
329
+ repo?: RepoTypeConfig;
330
+ /** Schema output directory (e.g. 'src/db/schema' for Drizzle, 'prisma/' for Prisma) */
331
+ schemaDir?: string;
623
332
  /**
624
- * Working directory the CLI was invoked from. Plugin authors that need
625
- * a stable "project base" location (e.g. to write artifacts, resolve
626
- * config-relative paths) should prefer {@link projectRoot} over `cwd`
627
- * — `cwd` is whatever the adopter typed the command from, including
628
- * arbitrary subdirectories.
333
+ * Whether to pluralize module names in generated code.
334
+ * When true (default), `kick g module user` creates `src/modules/users/`.
335
+ * When false, it creates `src/modules/user/` and uses singular names throughout.
629
336
  */
630
- cwd: string;
337
+ pluralize?: boolean;
631
338
  /**
632
- * Resolved project root the directory `loadKickConfig` walked up to
633
- * via `findProjectRoot()`. Always set to a usable absolute path: when
634
- * a `kick.config.{ts,js,mjs,json}` is found, that directory; otherwise
635
- * the nearest ancestor with a `package.json`; otherwise `cwd` itself.
339
+ * Import path for the Prisma generated client in `--repo prisma` templates.
340
+ * Must resolve within `src/` for path alias compatibility.
636
341
  *
637
- * Populated by `mergeCliPlugins.register()` from the same resolution
638
- * that `cli.ts` performs at startup, so plugin authors get a coherent
639
- * view of the project root without re-walking the filesystem.
342
+ * @default '@prisma/client' (Prisma 5/6)
343
+ * @example
344
+ * prismaClientPath: '@/generated/prisma/client' // Prisma 7+
345
+ * prismaClientPath: './generated/prisma/client' // relative
640
346
  */
641
- projectRoot: string;
642
- /** Resolved kick.config.ts (null if the project has none). */
643
- config: KickConfig | null;
644
- log: (msg: string) => void;
347
+ prismaClientPath?: string;
645
348
  /**
646
- * Plugin-shipped generators merged from built-ins + adopter
647
- * `kick.config.ts > plugins[]`. Populated by `mergeCliPlugins` and
648
- * threaded through so `register()` callbacks (notably
649
- * `kick/generate`) can register each plugin generator as a real
650
- * Commander subcommand — without that, plugin generators only fire
651
- * via the bare-action dispatch and are invisible to `kick g --help`.
349
+ * Module declaration style emitted by `kick g module` and the
350
+ * project scaffold.
652
351
  *
653
- * Optional so light test harnesses that call `plugin.register(program)`
654
- * directly (no merge step) stay unaffected.
655
- */
656
- generators?: DiscoveredGenerator[];
657
- }
658
- interface KickCliPlugin {
659
- /** Stable identifier — used in error messages on conflict + de-dup. */
660
- name: string;
661
- commands?: KickCommandDefinition[];
662
- /** Programmatic command registration. Called once at CLI startup. */
663
- register?: (program: Command, ctx: KickCliPluginContext) => void | Promise<void>;
664
- typegens?: TypegenPlugin[];
665
- generators?: GeneratorSpec[];
666
- }
667
- /** Identity helper — exists for type inference + parity with definePlugin. */
668
- declare function defineCliPlugin(p: KickCliPlugin): KickCliPlugin;
669
- declare class KickPluginConflictError extends Error {
670
- constructor(kind: 'plugin' | 'command' | 'typegen' | 'generator', id: string, owners: string[]);
671
- }
672
- //#endregion
673
- //#region src/config.d.ts
674
- /** A custom command that developers can register via kick.config.ts */
675
- interface KickCommandDefinition {
676
- /** The command name (e.g. 'db:migrate', 'seed', 'proto:gen') */
677
- name: string;
678
- /** Description shown in --help */
679
- description: string;
680
- /**
681
- * Shell command(s) to run. Can be a single string or an array of
682
- * sequential steps. Use {args} as a placeholder for CLI arguments.
352
+ * - `'define'` (default) `defineModule({ name, build: () => ({...}) })`
353
+ * factory form. Mirrors `defineAdapter` / `definePlugin` /
354
+ * `defineContextDecorator`.
355
+ * - `'class'` — legacy `class FooModule implements AppModule { ... }`
356
+ * form. Still fully supported by the framework loader; pin to this
357
+ * value for projects that prefer the class shape (existing-codebase
358
+ * consistency, class-decorator setups, etc).
683
359
  *
684
- * @example
685
- * 'npx drizzle-kit migrate'
686
- * ['npx drizzle-kit generate', 'npx drizzle-kit migrate']
360
+ * The framework runtime accepts both shapes regardless of this
361
+ * setting the flag controls codegen output only. `kick g module`
362
+ * inserts the matching call form into `src/modules/index.ts`
363
+ * (`Module()` vs `Module`); `kick rm module` matches both.
364
+ *
365
+ * @default 'define'
687
366
  */
688
- steps: string | string[];
689
- /** Optional aliases (e.g. ['migrate'] for 'db:migrate') */
690
- aliases?: string[];
691
- }
692
- /** Project pattern — controls what generators produce and which deps are installed */
693
- type ProjectPattern = 'rest' | 'ddd' | 'cqrs' | 'minimal';
694
- /** Package manager used for `kick add` and other dep-installing commands */
695
- type PackageManager = 'pnpm' | 'npm' | 'yarn' | 'bun';
696
- /** Built-in repository types with first-class code generation support */
697
- type BuiltinRepoType$1 = 'drizzle' | 'inmemory' | 'prisma';
698
- /** Custom repository type — generates a stub with TODO markers */
699
- interface CustomRepoType {
700
- name: string;
367
+ style?: 'define' | 'class';
701
368
  }
702
- /** Repository type built-in string or custom object */
703
- type RepoTypeConfig = BuiltinRepoType$1 | CustomRepoType;
704
- /**
705
- * Supported schema validators for `kick typegen` body/query/params
706
- * type extraction.
707
- *
708
- * - `'zod'` — emits `import('zod').infer<typeof schema>` (default)
709
- * - `'kickjs-schema'` — emits `InferSchemaOutput<typeof schema>` from
710
- * `@forinda/kickjs-schema`, works with Zod, Valibot, Yup, and any
711
- * Standard Schema v1 or KickSchema adapter
712
- * - `false` — disables schema-driven body typing (fields stay `unknown`)
713
- */
714
- type SchemaValidator = 'zod' | 'kickjs-schema' | false;
715
- /**
716
- * One entry in the typed `assetMap` config record (`assets-plan.md`).
717
- * Each entry names a source directory whose files become addressable
718
- * via the `assets.<name>.*` typed accessor at runtime.
719
- */
720
- interface AssetMapEntry {
369
+ /** Configuration for the kick.config.ts file */
370
+ interface KickConfig {
721
371
  /**
722
- * Source directory, relative to project root. Required. The directory
723
- * must exist when `kick build` runs `loadKickConfig` warns when an
724
- * entry points at a missing directory but doesn't fail the load
725
- * (the typegen + build steps surface the error in context instead).
372
+ * Project pattern controls default generator behavior.
373
+ * - 'rest'Express + Swagger (default)
374
+ * - 'ddd' Full DDD modules with use cases, entities, value objects
375
+ * - 'cqrs' CQRS with commands, queries, events, WebSocket + queue
376
+ * - 'minimal' — Bare Express with no scaffolding
726
377
  */
727
- src: string;
378
+ pattern?: ProjectPattern;
728
379
  /**
729
- * Destination directory inside `dist/`. Defaults to `dist/<name>/`
730
- * where `<name>` is the assetMap key. Override when the consumer of
731
- * the assets expects a non-standard layout (e.g. an existing
732
- * downstream tool reads from `dist/templates/...`).
380
+ * Module generation settings — directory, repo type, pluralization, schema dir.
381
+ *
382
+ * @example
383
+ * modules: {
384
+ * dir: 'src/modules',
385
+ * repo: 'prisma',
386
+ * pluralize: false,
387
+ * schemaDir: 'prisma/',
388
+ * }
733
389
  */
734
- dest?: string;
390
+ modules?: ModuleConfig;
735
391
  /**
736
- * Glob pattern for which files to include. Defaults to `**\/*` (all
737
- * files). Files that don't match are NOT copied — `assetMap` is
738
- * selective by design (unlike `copyDirs` which copies everything).
392
+ * Package manager used by `kick add` (and any future dep-installing command)
393
+ * to install dependencies. When set, overrides lockfile auto-detection so
394
+ * commands always use the project's intended package manager.
395
+ *
396
+ * Priority (highest first):
397
+ * 1. `--pm` flag on the CLI
398
+ * 2. `packageManager` in kick.config
399
+ * 3. `packageManager` field in package.json (corepack convention)
400
+ * 4. Lockfile detection (pnpm-lock.yaml → pnpm, yarn.lock → yarn)
401
+ * 5. `'npm'`
402
+ *
403
+ * @example
404
+ * packageManager: 'pnpm'
739
405
  */
740
- glob?: string;
406
+ packageManager?: PackageManager;
741
407
  /**
742
- * How file extensions feed into manifest keys. Default `'auto'`.
408
+ * DI token scope prefix used by code generators. Every scaffolded
409
+ * `createToken<T>('<scope>/<area>/<key>')` substitutes this string
410
+ * for `<scope>`. Generators emit org-scoped tokens out of the box
411
+ * so adopter projects pass `kick-lint`'s `token-reserved-prefix`
412
+ * rule (which forbids the reserved `kick/` prefix on third-party
413
+ * code) without manual rename.
743
414
  *
744
- * - `'strip'` drop the extension. `pages/index.pug` →
745
- * `'pages/index'`. Two siblings with the same basename collide;
746
- * last-walk-order wins, others are silently dropped. Opt-in
747
- * only kept for backward compatibility with projects that
748
- * relied on this contract.
749
- * - `'with-extension'` — keep every extension on every key. Best
750
- * when the namespace holds extension siblings (`index.pug` +
751
- * `index.html` + `index.css` in `src/pages/`); every file
752
- * reaches the manifest under its full path.
753
- * - `'auto'` — strip when basenames are unique, keep extensions
754
- * on collision groups. Singleton files keep their short key;
755
- * `pages/index.{pug,html,css}` becomes
756
- * `pages/index.pug` / `pages/index.html` / `pages/index.css`.
757
- * No data loss; non-colliding namespaces stay on the short
758
- * keys they had before.
415
+ * Resolution order (highest first):
416
+ * 1. This field, when set
417
+ * 2. `package.json` `name` field `@scope/pkg` → `'scope'`,
418
+ * bare `pkg` `'pkg'`
419
+ * 3. Fallback `'app'`
759
420
  *
760
- * @default 'auto'
421
+ * @example
422
+ * tokenScope: 'mycorp'
423
+ * // → createToken<...>('mycorp/users/repository')
761
424
  */
762
- keys?: 'auto' | 'strip' | 'with-extension';
763
- }
764
- /**
765
- * Database settings consumed by `kick db generate`, `kick db migrate`,
766
- * and the M4.C composite-type detection gate. Mirrors the runtime
767
- * `DbConfig` shape from `@forinda/kickjs-db` — duplicated here so
768
- * adopters who only install `@forinda/kickjs-cli` can set the block
769
- * without pulling `@forinda/kickjs-db` types into their kick.config.ts
770
- * resolution. The CLI loads kick.config.ts through `loadKickConfig`,
771
- * then `resolveDbConfig` re-reads this block from the same module and
772
- * normalises defaults (`schemaPath` / `migrationsDir` / `dialect`).
773
- *
774
- * @example
775
- * ```ts
776
- * defineConfig({
777
- * db: {
778
- * schemaPath: 'src/db/schema.ts',
779
- * migrationsDir: 'db/migrations',
780
- * dialect: 'postgres',
781
- * connectionString: process.env.DATABASE_URL,
782
- * },
783
- * })
784
- * ```
785
- */
786
- interface KickDbConfigBlock {
425
+ tokenScope?: string;
787
426
  /**
788
- * Path to the schema module (`pgEnum` / `table` declarations).
789
- * Defaults to `'src/db/schema.ts'`.
427
+ * Directories to copy to dist/ after build.
428
+ * Useful for EJS templates, email templates, static assets, etc.
429
+ *
430
+ * @example
431
+ * ```ts
432
+ * copyDirs: [
433
+ * 'src/views', // copies to dist/src/views
434
+ * { src: 'src/views', dest: 'dist/views' }, // custom dest
435
+ * 'src/emails',
436
+ * ]
437
+ * ```
790
438
  */
791
- schemaPath?: string;
439
+ copyDirs?: Array<string | {
440
+ src: string;
441
+ dest?: string;
442
+ }>;
792
443
  /**
793
- * Where `kick db generate` writes migration directories. Defaults
794
- * to `'db/migrations'`.
795
- */
796
- migrationsDir?: string;
797
- /** SQL dialect. Defaults to `'postgres'`. */
798
- dialect?: 'postgres' | 'sqlite' | 'mysql';
799
- /**
800
- * Postgres connection string for the built-in pgAdapter path. Read
801
- * from the `DATABASE_URL` env var when omitted. Used by
802
- * `kick db migrate*` and the M4.C composite-type gate at
803
- * `kick db generate`.
804
- */
805
- connectionString?: string;
806
- /**
807
- * Escape hatch: a factory returning a fully-constructed
808
- * MigrationAdapter (typed loosely here so the CLI types don't pull
809
- * `@forinda/kickjs-db` into adopter projects that don't import it).
810
- * Takes precedence over `connectionString` when both are set.
811
- */
812
- adapter?: () => unknown | Promise<unknown>;
813
- }
814
- /** Typegen settings — controls .kickjs/types/* generation */
815
- interface TypegenConfig {
816
- /**
817
- * Source directory to scan for controllers and decorators.
818
- * Defaults to `'src'`.
819
- */
820
- srcDir?: string;
821
- /**
822
- * Output directory for generated `.d.ts` files.
823
- * Defaults to `'.kickjs/types'`.
824
- */
825
- outDir?: string;
826
- /**
827
- * Schema validator used to derive `body` types from route metadata.
828
- *
829
- * - `'zod'` — emit `z.infer<typeof <importedSchema>>` for any schema
830
- * referenced as a named identifier in `@Get/@Post/...({ body, query, params })`.
831
- * - `false` — disable schema-driven body typing.
832
- *
833
- * Future: `'joi' | 'yup' | 'json-schema'` plus a `{ name; module }`
834
- * escape hatch for custom adapters.
835
- *
836
- * @default 'zod'
837
- */
838
- schemaValidator?: SchemaValidator;
839
- /**
840
- * Path to the project's env schema file (relative to project root).
841
- * Must default-export a `defineEnv(...)` schema for typegen to emit
842
- * the typed `KickEnv` global registry.
843
- *
844
- * Set to `false` to disable env typing entirely.
845
- *
846
- * @default 'src/env.ts'
847
- */
848
- envFile?: string | false;
849
- /**
850
- * Built-in or user typegen plugin ids to skip during `kick typegen`,
851
- * `kick dev`, and `kick typegen --watch`.
852
- *
853
- * The plugin still loads and merge-time conflict detection still
854
- * runs — only the `generate()` invocation is skipped — so adopters
855
- * who want to hand-write `KickDbRegister` (manual typeof-schema
856
- * augmentation) can disable `'kick/db'` and keep the rest:
857
- *
858
- * @example
859
- * typegen: {
860
- * disable: ['kick/db'], // hand-written register.ts owns the type
861
- * }
862
- *
863
- * Unrecognised ids are ignored — the list is treated as a wishlist,
864
- * not a strict registry.
865
- */
866
- disable?: string[];
867
- }
868
- /** Module generation settings — controls how `kick g module` produces code */
869
- interface ModuleConfig {
870
- /** Where modules live (default: 'src/modules') */
871
- dir?: string;
872
- /**
873
- * Default repository implementation for generators.
874
- *
875
- * Built-in types (string): `'drizzle'`, `'inmemory'`, `'prisma'`
876
- * — generate fully working repository code.
877
- *
878
- * Custom types (object): `{ name: 'typeorm' }`
879
- * — generate a stub repository with TODO markers.
880
- *
881
- * @example
882
- * repo: 'prisma' // built-in
883
- * repo: { name: 'typeorm' } // custom
884
- */
885
- repo?: RepoTypeConfig;
886
- /** Schema output directory (e.g. 'src/db/schema' for Drizzle, 'prisma/' for Prisma) */
887
- schemaDir?: string;
888
- /**
889
- * Whether to pluralize module names in generated code.
890
- * When true (default), `kick g module user` creates `src/modules/users/`.
891
- * When false, it creates `src/modules/user/` and uses singular names throughout.
892
- */
893
- pluralize?: boolean;
894
- /**
895
- * Import path for the Prisma generated client in `--repo prisma` templates.
896
- * Must resolve within `src/` for path alias compatibility.
897
- *
898
- * @default '@prisma/client' (Prisma 5/6)
899
- * @example
900
- * prismaClientPath: '@/generated/prisma/client' // Prisma 7+
901
- * prismaClientPath: './generated/prisma/client' // relative
902
- */
903
- prismaClientPath?: string;
904
- /**
905
- * Module declaration style emitted by `kick g module` and the
906
- * project scaffold.
907
- *
908
- * - `'define'` (default) — `defineModule({ name, build: () => ({...}) })`
909
- * factory form. Mirrors `defineAdapter` / `definePlugin` /
910
- * `defineContextDecorator`.
911
- * - `'class'` — legacy `class FooModule implements AppModule { ... }`
912
- * form. Still fully supported by the framework loader; pin to this
913
- * value for projects that prefer the class shape (existing-codebase
914
- * consistency, class-decorator setups, etc).
915
- *
916
- * The framework runtime accepts both shapes regardless of this
917
- * setting — the flag controls codegen output only. `kick g module`
918
- * inserts the matching call form into `src/modules/index.ts`
919
- * (`Module()` vs `Module`); `kick rm module` matches both.
920
- *
921
- * @default 'define'
922
- */
923
- style?: 'define' | 'class';
924
- }
925
- /** Configuration for the kick.config.ts file */
926
- interface KickConfig {
927
- /**
928
- * Project pattern — controls default generator behavior.
929
- * - 'rest' — Express + Swagger (default)
930
- * - 'ddd' — Full DDD modules with use cases, entities, value objects
931
- * - 'cqrs' — CQRS with commands, queries, events, WebSocket + queue
932
- * - 'minimal' — Bare Express with no scaffolding
933
- */
934
- pattern?: ProjectPattern;
935
- /**
936
- * Module generation settings — directory, repo type, pluralization, schema dir.
937
- *
938
- * @example
939
- * modules: {
940
- * dir: 'src/modules',
941
- * repo: 'prisma',
942
- * pluralize: false,
943
- * schemaDir: 'prisma/',
944
- * }
945
- */
946
- modules?: ModuleConfig;
947
- /**
948
- * Package manager used by `kick add` (and any future dep-installing command)
949
- * to install dependencies. When set, overrides lockfile auto-detection so
950
- * commands always use the project's intended package manager.
951
- *
952
- * Priority (highest first):
953
- * 1. `--pm` flag on the CLI
954
- * 2. `packageManager` in kick.config
955
- * 3. `packageManager` field in package.json (corepack convention)
956
- * 4. Lockfile detection (pnpm-lock.yaml → pnpm, yarn.lock → yarn)
957
- * 5. `'npm'`
958
- *
959
- * @example
960
- * packageManager: 'pnpm'
961
- */
962
- packageManager?: PackageManager;
963
- /**
964
- * DI token scope prefix used by code generators. Every scaffolded
965
- * `createToken<T>('<scope>/<area>/<key>')` substitutes this string
966
- * for `<scope>`. Generators emit org-scoped tokens out of the box
967
- * so adopter projects pass `kick-lint`'s `token-reserved-prefix`
968
- * rule (which forbids the reserved `kick/` prefix on third-party
969
- * code) without manual rename.
970
- *
971
- * Resolution order (highest first):
972
- * 1. This field, when set
973
- * 2. `package.json` `name` field — `@scope/pkg` → `'scope'`,
974
- * bare `pkg` → `'pkg'`
975
- * 3. Fallback `'app'`
976
- *
977
- * @example
978
- * tokenScope: 'mycorp'
979
- * // → createToken<...>('mycorp/users/repository')
980
- */
981
- tokenScope?: string;
982
- /**
983
- * Directories to copy to dist/ after build.
984
- * Useful for EJS templates, email templates, static assets, etc.
985
- *
986
- * @example
987
- * ```ts
988
- * copyDirs: [
989
- * 'src/views', // copies to dist/src/views
990
- * { src: 'src/views', dest: 'dist/views' }, // custom dest
991
- * 'src/emails',
992
- * ]
993
- * ```
994
- */
995
- copyDirs?: Array<string | {
996
- src: string;
997
- dest?: string;
998
- }>;
999
- /**
1000
- * Build output settings. The asset manager + `kick build`'s copy
1001
- * steps honour these — adopters who use Vite's `build.outDir =
1002
- * 'out'` (or any non-default) should mirror the value here so
1003
- * `assets.x.y()` paths line up with where Vite actually wrote.
1004
- *
1005
- * @example
1006
- * ```ts
1007
- * build: { outDir: 'out' }
1008
- * ```
444
+ * Build output settings. The asset manager + `kick build`'s copy
445
+ * steps honour these — adopters who use Vite's `build.outDir =
446
+ * 'out'` (or any non-default) should mirror the value here so
447
+ * `assets.x.y()` paths line up with where Vite actually wrote.
448
+ *
449
+ * @example
450
+ * ```ts
451
+ * build: { outDir: 'out' }
452
+ * ```
1009
453
  */
1010
454
  build?: {
1011
455
  /**
@@ -1158,7 +602,7 @@ declare function loadKickConfig(startDir: string): Promise<KickConfig | null>;
1158
602
  type ModuleStyle = 'define' | 'class';
1159
603
  //#endregion
1160
604
  //#region src/generators/module.d.ts
1161
- type BuiltinRepoType = 'drizzle' | 'inmemory' | 'prisma';
605
+ type BuiltinRepoType = 'inmemory';
1162
606
  type RepoType = BuiltinRepoType | (string & {});
1163
607
  interface GenerateModuleOptions {
1164
608
  name: string;
@@ -1193,9 +637,7 @@ interface GenerateModuleOptions {
1193
637
  * Generate a module — structure depends on the project pattern.
1194
638
  *
1195
639
  * Patterns:
1196
- * rest — flat folder: controller + service + DTOs + repo
1197
- * ddd — nested DDD: presentation/ application/ domain/ infrastructure/
1198
- * cqrs — commands, queries, events with WS/queue integration
640
+ * rest — flat folder: controller + service + DTOs + repo (default)
1199
641
  * minimal — just controller + module index
1200
642
  */
1201
643
  declare function generateModule(options: GenerateModuleOptions): Promise<string[]>;
@@ -1274,7 +716,7 @@ interface GenerateDtoOptions {
1274
716
  declare function generateDto(options: GenerateDtoOptions): Promise<string[]>;
1275
717
  //#endregion
1276
718
  //#region src/generators/project.d.ts
1277
- type ProjectTemplate = 'rest' | 'ddd' | 'cqrs' | 'minimal';
719
+ type ProjectTemplate = 'rest' | 'minimal';
1278
720
  type SchemaLib = 'zod' | 'valibot' | 'yup';
1279
721
  interface InitProjectOptions {
1280
722
  name: string;
@@ -1309,6 +751,374 @@ declare function initProject(options: InitProjectOptions): Promise<void>;
1309
751
  */
1310
752
  declare function findProjectRoot(startDir?: string): string;
1311
753
  //#endregion
754
+ //#region src/typegen/scanner.d.ts
755
+ /**
756
+ * Static scanner for KickJS decorated classes and DI tokens.
757
+ *
758
+ * Walks `src/**\/*.ts` (excluding tests and node_modules) and extracts:
759
+ *
760
+ * - Decorated classes (`@Service`, `@Controller`, `@Repository`, etc.)
761
+ * - `createToken<T>('name')` definitions
762
+ * - `@Inject('literal')` calls
763
+ *
764
+ * The output feeds the type generator, which emits `.kickjs/types/*.d.ts`
765
+ * files used by the user's tsc to make `container.resolve()` and module
766
+ * discovery type-safe.
767
+ *
768
+ * This is intentionally regex-based (not AST-based) to avoid the
769
+ * ts-morph / typescript compiler dependency. Pattern from
770
+ * `packages/vite/src/module-discovery.ts` which already uses regex
771
+ * to detect `*.module.ts` exports.
772
+ *
773
+ * ## Collision detection
774
+ *
775
+ * Two classes with the same name across different files is a collision.
776
+ * The scanner records all collisions in `ScanResult.collisions` so the
777
+ * caller (generator) can decide whether to hard-error or auto-namespace.
778
+ *
779
+ * @module @forinda/kickjs-cli/typegen/scanner
780
+ */
781
+ /** Decorators that mark a class as DI-managed */
782
+ declare const DECORATOR_NAMES: readonly ['Service', 'Controller', 'Repository', 'Injectable', 'Component', 'Module'];
783
+ type DecoratorName = (typeof DECORATOR_NAMES)[number];
784
+ /** A single discovered decorated class */
785
+ interface DiscoveredClass {
786
+ /** Class name (e.g., 'UserService') */
787
+ className: string;
788
+ /** Decorator that marked it (e.g., 'Service') */
789
+ decorator: DecoratorName;
790
+ /** Absolute file path */
791
+ filePath: string;
792
+ /** Path relative to scan root, with forward slashes */
793
+ relativePath: string;
794
+ /** True if exported as `default` */
795
+ isDefault: boolean;
796
+ }
797
+ /** A single route handler discovered on a controller class */
798
+ interface DiscoveredRoute {
799
+ /** Owning controller class name (e.g. 'UserController') */
800
+ controller: string;
801
+ /** Handler method name on the controller (e.g. 'getUser') */
802
+ method: string;
803
+ /** HTTP verb (uppercase) */
804
+ httpMethod: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
805
+ /** Route path including parameter placeholders (e.g. '/:id/posts/:postId') */
806
+ path: string;
807
+ /** URL path parameter names extracted from `:placeholder` segments */
808
+ pathParams: string[];
809
+ /**
810
+ * Whitelisted query field names extracted from `@ApiQueryParams({...})`.
811
+ * `null` means no `@ApiQueryParams` was found on this method (so the
812
+ * generator emits an unconstrained `query` shape). An empty array means
813
+ * the decorator existed but no fields could be statically extracted
814
+ * (e.g. an opaque imported config).
815
+ */
816
+ queryFilterable: string[] | null;
817
+ querySortable: string[] | null;
818
+ querySearchable: string[] | null;
819
+ /**
820
+ * Schema identifiers referenced from the route decorator's second arg
821
+ * (e.g. `@Post('/', { body: createTaskSchema })`). `null` means no
822
+ * such reference; the value carries the identifier and the resolved
823
+ * import source (relative module path) if known.
824
+ */
825
+ bodySchema: SchemaRef | null;
826
+ querySchema: SchemaRef | null;
827
+ paramsSchema: SchemaRef | null;
828
+ /** Absolute file path of the controller */
829
+ filePath: string;
830
+ /** Path relative to scan root, with forward slashes */
831
+ relativePath: string;
832
+ }
833
+ /** A statically-resolved schema identifier reference */
834
+ interface SchemaRef {
835
+ /** The identifier as written (e.g. `createTaskSchema`) */
836
+ identifier: string;
837
+ /**
838
+ * Resolved module specifier (relative path or bare module name) where
839
+ * the identifier is defined. `null` means the source could not be
840
+ * statically determined (the generator falls back to `unknown`).
841
+ */
842
+ source: string | null;
843
+ }
844
+ /** A `createToken<T>('name')` call discovered in source */
845
+ interface DiscoveredToken {
846
+ /** The literal string passed to `createToken()` */
847
+ name: string;
848
+ /** The const variable name on the LHS, if any */
849
+ variable: string | null;
850
+ /** Absolute file path */
851
+ filePath: string;
852
+ /** Path relative to scan root, with forward slashes */
853
+ relativePath: string;
854
+ }
855
+ /** An `@Inject('literal')` call discovered in source */
856
+ interface DiscoveredInject {
857
+ /** The literal string passed to `@Inject()` */
858
+ name: string;
859
+ /** Absolute file path */
860
+ filePath: string;
861
+ /** Path relative to scan root, with forward slashes */
862
+ relativePath: string;
863
+ }
864
+ /** A name collision — same class name in two or more files */
865
+ interface ClassCollision {
866
+ /** The colliding class name */
867
+ className: string;
868
+ /** All files declaring the class */
869
+ classes: DiscoveredClass[];
870
+ }
871
+ /**
872
+ * Information about a discovered env schema file. The typegen
873
+ * generator uses this to emit a `KickEnv` + `NodeJS.ProcessEnv`
874
+ * augmentation that flows through to `@Value` and `process.env`.
875
+ *
876
+ * `null` means no env file was found at the configured location.
877
+ */
878
+ interface DiscoveredEnv {
879
+ /** Absolute path to the env schema file */
880
+ filePath: string;
881
+ /** Path relative to scan root, with forward slashes */
882
+ relativePath: string;
883
+ }
884
+ /**
885
+ * A plugin or adapter discovered in source — either via `defineAdapter({ name })`
886
+ * / `definePlugin({ name })` calls, or via a class that `implements AppAdapter`
887
+ * and declares a string-literal `name` field.
888
+ *
889
+ * The `name` here is the literal string passed to the framework (the value
890
+ * `dependsOn` references), NOT the symbol on the LHS. `defineAdapter` lets
891
+ * authors choose any name they want; the symbol is irrelevant at runtime.
892
+ */
893
+ interface DiscoveredPluginOrAdapter {
894
+ /** Whether this is a plugin (`definePlugin`) or adapter (`defineAdapter` / class) */
895
+ kind: 'plugin' | 'adapter';
896
+ /** The string literal passed as `name` (the value `dependsOn` references) */
897
+ name: string;
898
+ /** Absolute file path */
899
+ filePath: string;
900
+ /** Path relative to scan root, with forward slashes */
901
+ relativePath: string;
902
+ }
903
+ /**
904
+ * A context key discovered from a `defineContextDecorator({ key })` or
905
+ * `defineHttpContextDecorator({ key })` call (including the curried
906
+ * `.withParams<P>()({ key })` form). Feeds the `kick/context` typegen
907
+ * plugin, which emits the `ContextKeys` augmentation so `dependsOn`
908
+ * typo-checking is automatic and complete.
909
+ */
910
+ interface DiscoveredContextKey {
911
+ /** The literal `key:` value the contributor writes. */
912
+ key: string;
913
+ /** Absolute file path. */
914
+ filePath: string;
915
+ /** Path relative to scan root, with forward slashes. */
916
+ relativePath: string;
917
+ }
918
+ /**
919
+ * A `defineAugmentation('Name', meta)` call discovered in source. Plugins
920
+ * call this to advertise an augmentable interface so the typegen can list
921
+ * every augmentation surface in one generated file.
922
+ */
923
+ interface DiscoveredAugmentation {
924
+ /** The literal string passed as the first arg to `defineAugmentation` */
925
+ name: string;
926
+ /** Optional `description` extracted from the second-arg object literal */
927
+ description: string | null;
928
+ /** Optional `example` extracted from the second-arg object literal */
929
+ example: string | null;
930
+ /** Absolute file path */
931
+ filePath: string;
932
+ /** Path relative to scan root, with forward slashes */
933
+ relativePath: string;
934
+ }
935
+ /**
936
+ * A decorated class whose file sits inside a module directory but
937
+ * isn't picked up by any of the module's `import.meta.glob(...)`
938
+ * patterns. Surfaced as a typegen warning per forinda/kick-js#235 §4
939
+ * so adopters notice silent registration drift before it bites them
940
+ * at runtime with a `MissingContributorError` or wrong code path.
941
+ */
942
+ interface OrphanedClass {
943
+ /** The decorated class name */
944
+ className: string;
945
+ /** Absolute path of the class file */
946
+ filePath: string;
947
+ /** Path relative to scan root, with forward slashes */
948
+ relativePath: string;
949
+ /** Absolute path of the module file whose globs didn't match */
950
+ moduleFilePath: string;
951
+ /** The decorator name (`Service`, `Controller`, `Repository`, …) */
952
+ decorator: DecoratorName;
953
+ }
954
+ /** Aggregated scanner output */
955
+ interface ScanResult {
956
+ classes: DiscoveredClass[];
957
+ routes: DiscoveredRoute[];
958
+ tokens: DiscoveredToken[];
959
+ injects: DiscoveredInject[];
960
+ collisions: ClassCollision[];
961
+ /** Discovered env schema file (or null if none found at the configured path) */
962
+ env: DiscoveredEnv | null;
963
+ /** Plugins/adapters discovered via `defineAdapter`/`definePlugin`/`implements AppAdapter` */
964
+ pluginsAndAdapters: DiscoveredPluginOrAdapter[];
965
+ /** Augmentation interfaces declared via `defineAugmentation('Name', meta)` */
966
+ augmentations: DiscoveredAugmentation[];
967
+ /** Context keys from `define(Http)ContextDecorator({ key })` calls */
968
+ contextKeys: DiscoveredContextKey[];
969
+ /**
970
+ * Decorated classes that sit inside a module directory but aren't
971
+ * picked up by any of the module's `import.meta.glob(...)` patterns.
972
+ * Empty when every decorator file is matched. forinda/kick-js#235 §4.
973
+ */
974
+ orphanedClasses: OrphanedClass[];
975
+ }
976
+ /** Options for the scanner */
977
+ interface ScanOptions {
978
+ /** Root directory to scan (e.g., absolute path to `src`) */
979
+ root: string;
980
+ /** Project root used to compute relative paths (e.g., process.cwd()) */
981
+ cwd: string;
982
+ /** Glob-like extensions to scan */
983
+ extensions?: string[];
984
+ /** Substrings that exclude a path (matched against relative path) */
985
+ exclude?: string[];
986
+ /**
987
+ * Path to the env schema file, relative to `cwd`. Defaults to
988
+ * `'src/env.ts'`. The file must contain a `defineEnv(...)` call
989
+ * with a default export for the typegen to emit a typed `KickEnv`
990
+ * augmentation. If the file does not exist or doesn't match the
991
+ * expected shape, env typing is skipped silently.
992
+ */
993
+ envFile?: string;
994
+ /**
995
+ * Directory for the persistent per-file extraction cache. When set,
996
+ * unchanged files (matched by `mtimeMs:size` signature) are served
997
+ * from `<cacheDir>/scan.json` instead of being re-read and re-scanned.
998
+ * Omit to disable caching (every scan is a cold read — the original
999
+ * behaviour). Typically `<cwd>/.kickjs/cache`.
1000
+ */
1001
+ cacheDir?: string;
1002
+ }
1003
+ //#endregion
1004
+ //#region src/typegen/plugin.d.ts
1005
+ interface TypegenLogger {
1006
+ info(msg: string): void;
1007
+ warn(msg: string): void;
1008
+ error(msg: string): void;
1009
+ }
1010
+ interface TypegenContext {
1011
+ cwd: string;
1012
+ config: KickConfig;
1013
+ /** Dynamic-import a TS module (Node loader). Used by plugins that need to
1014
+ * read the adopter's schema / route map / asset registry at generate time. */
1015
+ importTs<T = unknown>(absPath: string): Promise<T>;
1016
+ /** Write under `cwd`. Caller passes a relPath (e.g. `.kickjs/types/foo.d.ts`). */
1017
+ writeFile(relPath: string, contents: string): Promise<void>;
1018
+ /**
1019
+ * Run `scanProject` once per typegen pass, memoizing the result so
1020
+ * multiple plugins (`kick/routes`, `kick/env`, future adopter plugins)
1021
+ * share a single fs walk + AST extraction.
1022
+ *
1023
+ * The runner uses an order-independent cache key derived from the
1024
+ * resolved options (`root`, `cwd`, `extensions`, `exclude`, `envFile`)
1025
+ * — semantically equal options hit the cache regardless of how the
1026
+ * caller built the literal. (We deliberately don't `JSON.stringify`
1027
+ * the options for caching since that would be sensitive to property
1028
+ * insertion order.) Plugins that don't need scanner data can ignore
1029
+ * this method entirely.
1030
+ *
1031
+ * Implementation lives in the runner so test harnesses can inject
1032
+ * a stub scanner; plugins only see the function.
1033
+ */
1034
+ getScanResult(opts: ScanOptions): Promise<ScanResult>;
1035
+ log: TypegenLogger;
1036
+ }
1037
+ interface TypegenPlugin {
1038
+ /** Stable id — used as filename: `.kickjs/types/${id}<outExtension>` (slashes → `__`). */
1039
+ id: string;
1040
+ /** Glob patterns the Vite watcher subscribes to; change → re-run this plugin. */
1041
+ inputs: string[];
1042
+ /**
1043
+ * Output filename extension. Default `.d.ts` — the right choice for
1044
+ * pure module-augmentation plugins (kick/db, kick/assets) since
1045
+ * declaration files don't need the runtime-import dance.
1046
+ *
1047
+ * `kick/routes` overrides to `.ts` because it emits hoisted
1048
+ * `import type {...} from '...'` lines at the top of the file. Inline
1049
+ * `import('...').X` references inside `.d.ts` silently degrade to
1050
+ * `unknown` under `moduleResolution: 'bundler'`; emitting `.ts`
1051
+ * sidesteps that quirk and gets full type resolution.
1052
+ *
1053
+ * Adopter-supplied plugins should leave this unset unless they hit
1054
+ * the same hoisted-import constraint.
1055
+ */
1056
+ outExtension?: string;
1057
+ /**
1058
+ * Return the augmentation source (without banner — runner prepends).
1059
+ * Return null to skip emission (e.g. no schema file present).
1060
+ */
1061
+ generate(ctx: TypegenContext): Promise<string | null>;
1062
+ }
1063
+ interface TypegenPluginResult {
1064
+ id: string;
1065
+ status: 'written' | 'unchanged' | 'skipped' | 'error';
1066
+ outFile?: string;
1067
+ }
1068
+ /**
1069
+ * Identity factory for {@link TypegenPlugin}. Returns the spec verbatim.
1070
+ * Exists for type inference and forward-compatibility — future
1071
+ * fields can be added with defaults without breaking adopters.
1072
+ *
1073
+ * Mirrors {@link defineGenerator} ergonomics. Use at the call site so
1074
+ * the plugin's `generate(ctx)` body gets a fully-typed `ctx` without
1075
+ * an explicit annotation:
1076
+ *
1077
+ * @example
1078
+ * ```ts
1079
+ * import { defineTypegen } from '@forinda/kickjs-cli'
1080
+ *
1081
+ * export const drizzleTypegen = defineTypegen({
1082
+ * id: 'drizzle',
1083
+ * inputs: ['src/db/schema.ts'],
1084
+ * async generate(ctx) {
1085
+ * const schema = await ctx.importTs(`${ctx.cwd}/src/db/schema.ts`)
1086
+ * return `// declare module …`
1087
+ * },
1088
+ * })
1089
+ * ```
1090
+ */
1091
+ declare function defineTypegen(spec: TypegenPlugin): TypegenPlugin;
1092
+ //#endregion
1093
+ //#region src/generator-extension/discover.d.ts
1094
+ /**
1095
+ * One row in the discovered registry. `source` is the npm package name
1096
+ * the generator came from — surfaced in error messages so adopters can
1097
+ * see which plugin owns a given generator.
1098
+ */
1099
+ interface DiscoveredGenerator {
1100
+ source: string;
1101
+ spec: GeneratorSpec;
1102
+ }
1103
+ /**
1104
+ * Plugin discovery result, kept around even when no generators were
1105
+ * registered so callers can distinguish "no plugins installed" from
1106
+ * "no plugins matched the requested name."
1107
+ */
1108
+ interface DiscoveryResult {
1109
+ generators: DiscoveredGenerator[];
1110
+ /** Packages whose `kickjs.generators` was loaded successfully. */
1111
+ loaded: string[];
1112
+ /**
1113
+ * Packages we tried to load but failed — typically a missing entry
1114
+ * file or a default export that wasn't an array of GeneratorSpec.
1115
+ */
1116
+ failed: Array<{
1117
+ source: string;
1118
+ reason: string;
1119
+ }>;
1120
+ }
1121
+ //#endregion
1312
1122
  //#region src/generator-extension/context.d.ts
1313
1123
  /**
1314
1124
  * Build a {@link GeneratorContext} from the raw name + invocation