@forinda/kickjs-cli 6.2.0-alpha.1 → 6.2.0
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.
- package/dist/{agent-docs-DKEQiq25.mjs → agent-docs-Di96qILM.mjs} +3 -3
- package/dist/{agent-docs-DKEQiq25.mjs.map → agent-docs-Di96qILM.mjs.map} +1 -1
- package/dist/{build-I8Yhoqj-.mjs → build-C_RWnIv1.mjs} +3 -3
- package/dist/{build-I8Yhoqj-.mjs.map → build-C_RWnIv1.mjs.map} +1 -1
- package/dist/{builtins-7IQexhj7.mjs → builtins-BdJFdAsP.mjs} +2 -2
- package/dist/cli.mjs +3 -3
- package/dist/{config-DXJWJLD4.mjs → config-G8kmxDyZ.mjs} +3 -3
- package/dist/{config-DXJWJLD4.mjs.map → config-G8kmxDyZ.mjs.map} +1 -1
- package/dist/{doctor-414bnUd8.mjs → doctor-KBy5WFZJ.mjs} +7 -7
- package/dist/doctor-KBy5WFZJ.mjs.map +1 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{plugin-C03BaYpV.mjs → plugin-BH1mZC2X.mjs} +3 -3
- package/dist/{plugin-C03BaYpV.mjs.map → plugin-BH1mZC2X.mjs.map} +1 -1
- package/dist/{project-docs-gnsQyilx.mjs → project-docs-DKOLHfqu.mjs} +2 -2
- package/dist/{project-docs-gnsQyilx.mjs.map → project-docs-DKOLHfqu.mjs.map} +1 -1
- package/dist/{project-root-gmupeTuu.mjs → project-root-DsnEUmMN.mjs} +3 -3
- package/dist/{project-root-gmupeTuu.mjs.map → project-root-DsnEUmMN.mjs.map} +1 -1
- package/dist/{rolldown-runtime-BKjf5NYQ.mjs → rolldown-runtime-BoqBCbMv.mjs} +1 -1
- package/dist/{run-plugins-A7mE5YjC.mjs → run-plugins-Bql3IbaE.mjs} +4 -4
- package/dist/{run-plugins-A7mE5YjC.mjs.map → run-plugins-Bql3IbaE.mjs.map} +1 -1
- package/dist/{typegen-C4nEHdxn.mjs → typegen-C7AgyPIB.mjs} +5 -5
- package/dist/{typegen-C4nEHdxn.mjs.map → typegen-C7AgyPIB.mjs.map} +1 -1
- package/dist/{types-3XZpaE0p.mjs → types-xMa5uCri.mjs} +1 -1
- package/package.json +4 -4
- package/dist/doctor-414bnUd8.mjs.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"doctor-414bnUd8.mjs","names":["colors","colors"],"sources":["../src/utils/naming.ts","../src/utils/regex.ts","../src/generators/templates/module-index.ts","../src/generators/templates/controller.ts","../src/generators/templates/dtos.ts","../src/generators/templates/repository.ts","../src/generators/templates/tests.ts","../src/generators/templates/rest-service.ts","../src/generators/templates/project-app.ts","../src/generators/patterns/minimal.ts","../src/generators/patterns/rest.ts","../src/generators/module.ts","../src/generators/adapter.ts","../src/utils/resolve-out-dir.ts","../src/generators/middleware.ts","../src/generators/guard.ts","../src/generators/service.ts","../src/generators/controller.ts","../src/generators/dto.ts","../src/generators/templates/project-config.ts","../src/generators/project.ts","../src/commands/typegen-error-reporter.ts","../src/typegen/dev-watcher.ts","../src/generator-extension/context.ts","../src/generator-extension/discover.ts","../src/generator-extension/dispatch.ts","../src/commands/add.ts","../src/commands/doctor.ts"],"sourcesContent":["import pkg from 'pluralize'\n\n/** Convert a name to PascalCase */\nexport function toPascalCase(name: string): string {\n return name\n .replace(/[-_\\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''))\n .replace(/^(.)/, (c) => c.toUpperCase())\n}\n\n/** Convert a name to camelCase */\nexport function toCamelCase(name: string): string {\n const pascal = toPascalCase(name)\n return pascal.charAt(0).toLowerCase() + pascal.slice(1)\n}\n\n/** Convert a name to kebab-case */\nexport function toKebabCase(name: string): string {\n return name\n .replace(/([a-z])([A-Z])/g, '$1-$2')\n .replace(/[\\s_]+/g, '-')\n .toLowerCase()\n}\n\n/**\n * Pluralize a kebab-case name for directory/file names.\n * Uses the `pluralize` npm package for correct English pluralization\n * including irregulars (person → people, status → statuses, child → children).\n */\nexport function pluralize(name: string): string {\n return pkg.plural(name)\n}\n\n/**\n * Pluralize a PascalCase name for class identifiers.\n * Used for `List${pluralPascal}UseCase` to avoid `ListUserssUseCase`.\n */\nexport function pluralizePascal(name: string): string {\n return pkg.plural(name)\n}\n","/**\n * Escape a string for safe interpolation into a `new RegExp(...)`\n * source. Each character that has special meaning inside a regex\n * pattern (`.`, `*`, `+`, `?`, `^`, `$`, `{`, `}`, `(`, `)`, `|`,\n * `[`, `]`, `\\`) is prefixed with a backslash so it's matched\n * literally instead of treated as a metacharacter.\n *\n * Used everywhere the codegen / removal flow builds a regex from\n * adopter-provided identifiers (`pascal`, `plural`, etc). Names\n * coming through `toPascalCase` / `toKebabCase` are already\n * alphanumeric in practice, but escaping defensively keeps the\n * code correct if upstream sanitization ever loosens.\n */\nexport function escapeRegex(input: string): string {\n return input.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n","import type { RepoType } from '../module'\nimport type { ModuleStyle, TemplateContext } from './types'\n\nconst repoLabelMap: Record<string, string> = {\n inmemory: 'in-memory',\n drizzle: 'Drizzle',\n prisma: 'Prisma',\n}\n\nfunction toPascalRepoType(repo: string): string {\n return (\n repo.charAt(0).toUpperCase() +\n repo.slice(1).replace(/-([a-z])/g, (_, c: string) => c.toUpperCase())\n )\n}\n\nfunction toKebabRepoType(repo: string): string {\n return repo.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()\n}\n\nfunction repoLabel(repo: RepoType): string {\n return repoLabelMap[repo] ?? toPascalRepoType(repo)\n}\n\nfunction repoMaps(pascal: string, kebab: string, repo: RepoType) {\n const repoClassMap: Record<string, string> = {\n inmemory: `InMemory${pascal}Repository`,\n drizzle: `Drizzle${pascal}Repository`,\n prisma: `Prisma${pascal}Repository`,\n }\n const repoFileMap: Record<string, string> = {\n inmemory: `in-memory-${kebab}`,\n drizzle: `drizzle-${kebab}`,\n prisma: `prisma-${kebab}`,\n }\n return {\n repoClass: repoClassMap[repo] ?? `${toPascalRepoType(repo)}${pascal}Repository`,\n repoFile: repoFileMap[repo] ?? `${toKebabRepoType(repo)}-${kebab}`,\n }\n}\n\n/** Resolve the style flag, defaulting to 'define' for new code. */\nfunction resolveStyle(style?: ModuleStyle): ModuleStyle {\n return style ?? 'define'\n}\n\n/** DDD module index — nested folders, use-cases, domain services */\nexport function generateModuleIndex(ctx: TemplateContext & { repo: RepoType }): string {\n const { pascal, kebab, plural = '', repo, style } = ctx\n const { repoClass, repoFile } = repoMaps(pascal, kebab, repo)\n const resolvedStyle = resolveStyle(style)\n\n const header = `/**\n * ${pascal} Module\n *\n * Self-contained feature module following Domain-Driven Design (DDD).\n * Registers dependencies in the DI container and declares HTTP routes.\n *\n * Structure:\n * presentation/ — HTTP controllers (entry points)\n * application/ — Use cases (orchestration) and DTOs (validation)\n * domain/ — Entities, value objects, repository interfaces, domain services\n * infrastructure/ — Repository implementations (currently ${repoLabel(repo)})\n */`\n\n const repoImports = `import { ${pascal.toUpperCase()}_REPOSITORY } from './domain/repositories/${kebab}.repository'\nimport { ${repoClass} } from './infrastructure/repositories/${repoFile}.repository'\nimport { ${pascal}Controller } from './presentation/${kebab}.controller'\n\n// Eagerly load decorated classes so @Service()/@Repository() decorators register in the DI container\nimport.meta.glob(\n ['./domain/services/**/*.ts', './application/use-cases/**/*.ts', '!./**/*.test.ts'],\n { eager: true },\n)`\n\n const routesDoc = ` /**\n * Declare HTTP routes for this module. Return value shape:\n *\n * - \\`path\\` — URL prefix for this route set, mounted under\n * \\`/{apiPrefix}/v{version}{path}\\`.\n * - \\`controller\\` — Controller class. Used both for the route\n * handler bindings and OpenAPI spec generation.\n * - \\`version\\` — Optional. Overrides the app-wide API version\n * for this route set only.\n *\n * Return an **array** to mount multiple route sets under the\n * same module (e.g. side-by-side v1 + v2 controllers):\n *\n * return [\n * { path: '/${plural}', version: 1, controller: ${pascal}V1Controller },\n * { path: '/${plural}', version: 2, controller: ${pascal}V2Controller },\n * ]\n */`\n\n if (resolvedStyle === 'class') {\n return `${header}\nimport { Container, type AppModule, type ModuleRoutes } from '@forinda/kickjs'\n${repoImports}\n\nexport class ${pascal}Module implements AppModule {\n /**\n * Register module dependencies in the DI container.\n * Bind repository interface tokens to their implementations here.\n * Currently wired to ${repoLabel(repo)}. To swap implementations, change the factory target.\n */\n register(container: Container): void {\n container.registerFactory(${pascal.toUpperCase()}_REPOSITORY, () =>\n container.resolve(${repoClass}),\n )\n }\n\n${routesDoc.replace(/^ {4}/gm, ' ').replace(/^ {6}/gm, ' ')}\n routes(): ModuleRoutes {\n return {\n path: '/${plural}',\n controller: ${pascal}Controller,\n }\n }\n}\n`\n }\n\n return `${header}\nimport { defineModule } from '@forinda/kickjs'\n${repoImports}\n\nexport const ${pascal}Module = defineModule({\n name: '${pascal}Module',\n build: () => ({\n /**\n * Register module dependencies in the DI container.\n * Bind repository interface tokens to their implementations here.\n * Currently wired to ${repoLabel(repo)}. To swap implementations, change the factory target.\n */\n register(container) {\n container.registerFactory(${pascal.toUpperCase()}_REPOSITORY, () =>\n container.resolve(${repoClass}),\n )\n },\n\n${routesDoc}\n routes() {\n return {\n path: '/${plural}',\n controller: ${pascal}Controller,\n }\n },\n }),\n})\n`\n}\n\n/** REST module index — flat folder, service + controller, no use-cases */\nexport function generateRestModuleIndex(ctx: TemplateContext & { repo: RepoType }): string {\n const { pascal, kebab, plural = '', repo, style } = ctx\n const { repoClass, repoFile } = repoMaps(pascal, kebab, repo)\n const resolvedStyle = resolveStyle(style)\n\n const header = `/**\n * ${pascal} Module\n *\n * REST module with a flat folder structure.\n * Controller delegates to service, service wraps the repository.\n *\n * Structure:\n * ${kebab}.controller.ts — HTTP routes (CRUD)\n * ${kebab}.service.ts — Business logic\n * ${kebab}.repository.ts — Repository interface\n * ${repoFile}.repository.ts — Repository implementation\n * dtos/ — Request/response schemas\n */`\n\n const repoImports = `import { ${pascal.toUpperCase()}_REPOSITORY } from './${kebab}.repository'\nimport { ${repoClass} } from './${repoFile}.repository'\nimport { ${pascal}Controller } from './${kebab}.controller'\n\n// Eagerly load decorated classes so @Service()/@Repository() decorators register in the DI container\nimport.meta.glob(['./**/*.service.ts', './**/*.repository.ts', '!./**/*.test.ts'], { eager: true })`\n\n const routesDoc = ` /**\n * Declare HTTP routes for this module. Return value shape:\n *\n * - \\`path\\` — URL prefix for this route set.\n * - \\`controller\\` — Controller class (also drives OpenAPI).\n * - \\`version\\` — Optional. Overrides the app-wide API version.\n *\n * Return an **array** to mount multiple route sets — admin\n * surfaces, side-by-side v1 + v2 controllers, etc:\n *\n * return [\n * { path: '/${plural}', version: 1, controller: ${pascal}V1Controller },\n * { path: '/${plural}', version: 2, controller: ${pascal}V2Controller },\n * ]\n */`\n\n if (resolvedStyle === 'class') {\n return `${header}\nimport { Container, type AppModule, type ModuleRoutes } from '@forinda/kickjs'\n${repoImports}\n\nexport class ${pascal}Module implements AppModule {\n register(container: Container): void {\n container.registerFactory(${pascal.toUpperCase()}_REPOSITORY, () =>\n container.resolve(${repoClass}),\n )\n }\n\n${routesDoc.replace(/^ {4}/gm, ' ').replace(/^ {6}/gm, ' ')}\n routes(): ModuleRoutes {\n return {\n path: '/${plural}',\n controller: ${pascal}Controller,\n }\n }\n}\n`\n }\n\n return `${header}\nimport { defineModule } from '@forinda/kickjs'\n${repoImports}\n\nexport const ${pascal}Module = defineModule({\n name: '${pascal}Module',\n build: () => ({\n register(container) {\n container.registerFactory(${pascal.toUpperCase()}_REPOSITORY, () =>\n container.resolve(${repoClass}),\n )\n },\n\n${routesDoc}\n routes() {\n return {\n path: '/${plural}',\n controller: ${pascal}Controller,\n }\n },\n }),\n})\n`\n}\n\n/** Minimal module index — just controller, no service/repo */\nexport function generateMinimalModuleIndex(ctx: TemplateContext): string {\n const { pascal, kebab, plural = '', style } = ctx\n const resolvedStyle = resolveStyle(style)\n\n const routesDoc = ` /**\n * Declare HTTP routes. Return value shape:\n *\n * - \\`path\\` — URL prefix for this route set.\n * - \\`controller\\` — Controller class (also drives OpenAPI).\n * - \\`version\\` — Optional. Overrides the app-wide API version.\n *\n * Return an array to mount multiple route sets:\n *\n * return [\n * { path: '/${plural}', version: 1, controller: ${pascal}V1Controller },\n * { path: '/${plural}', version: 2, controller: ${pascal}V2Controller },\n * ]\n */`\n\n if (resolvedStyle === 'class') {\n return `import { type AppModule, type ModuleRoutes } from '@forinda/kickjs'\nimport { ${pascal}Controller } from './${kebab}.controller'\n\nexport class ${pascal}Module implements AppModule {\n${routesDoc.replace(/^ {4}/gm, ' ').replace(/^ {6}/gm, ' ')}\n routes(): ModuleRoutes {\n return {\n path: '/${plural}',\n controller: ${pascal}Controller,\n }\n }\n}\n`\n }\n\n return `import { defineModule } from '@forinda/kickjs'\nimport { ${pascal}Controller } from './${kebab}.controller'\n\nexport const ${pascal}Module = defineModule({\n name: '${pascal}Module',\n build: () => ({\n${routesDoc}\n routes() {\n return {\n path: '/${plural}',\n controller: ${pascal}Controller,\n }\n },\n }),\n})\n`\n}\n","import type { TemplateContext } from './types'\n\n/** DDD controller — injects use-cases, nested import paths */\nexport function generateController(ctx: TemplateContext): string {\n const { pascal, kebab, plural = '', pluralPascal = '' } = ctx\n return `import { Controller, Get, Post, Put, Delete, Autowired, ApiQueryParams, type Ctx } from '@forinda/kickjs'\nimport { ApiTags } from '@forinda/kickjs-swagger'\nimport { Create${pascal}UseCase } from '../application/use-cases/create-${kebab}.use-case'\nimport { Get${pascal}UseCase } from '../application/use-cases/get-${kebab}.use-case'\nimport { List${pluralPascal}UseCase } from '../application/use-cases/list-${plural}.use-case'\nimport { Update${pascal}UseCase } from '../application/use-cases/update-${kebab}.use-case'\nimport { Delete${pascal}UseCase } from '../application/use-cases/delete-${kebab}.use-case'\nimport { create${pascal}Schema } from '../application/dtos/create-${kebab}.dto'\nimport { update${pascal}Schema } from '../application/dtos/update-${kebab}.dto'\nimport { ${pascal.toUpperCase()}_QUERY_CONFIG } from '../constants'\n\n// Each handler annotates its \\`ctx\\` with \\`Ctx<KickRoutes.${pascal}Controller['<method>']>\\`\n// so \\`ctx.params\\`, \\`ctx.body\\`, and \\`ctx.query\\` are typed end-to-end.\n// The \\`KickRoutes\\` namespace is generated by \\`kick typegen\\` (auto-run on\n// \\`kick dev\\`) — see https://forinda.github.io/kick-js/guide/typegen.\n\n@Controller()\nexport class ${pascal}Controller {\n @Autowired() private readonly create${pascal}UseCase!: Create${pascal}UseCase\n @Autowired() private readonly get${pascal}UseCase!: Get${pascal}UseCase\n @Autowired() private readonly list${pluralPascal}UseCase!: List${pluralPascal}UseCase\n @Autowired() private readonly update${pascal}UseCase!: Update${pascal}UseCase\n @Autowired() private readonly delete${pascal}UseCase!: Delete${pascal}UseCase\n\n @Get('/')\n @ApiTags('${pascal}')\n @ApiQueryParams(${pascal.toUpperCase()}_QUERY_CONFIG)\n async list(ctx: Ctx<KickRoutes.${pascal}Controller['list']>) {\n return ctx.paginate(\n (parsed) => this.list${pluralPascal}UseCase.execute(parsed),\n ${pascal.toUpperCase()}_QUERY_CONFIG,\n )\n }\n\n @Get('/:id')\n @ApiTags('${pascal}')\n async getById(ctx: Ctx<KickRoutes.${pascal}Controller['getById']>) {\n const result = await this.get${pascal}UseCase.execute(ctx.params.id)\n if (!result) return ctx.notFound('${pascal} not found')\n ctx.json(result)\n }\n\n @Post('/', { body: create${pascal}Schema, name: 'Create${pascal}' })\n @ApiTags('${pascal}')\n async create(ctx: Ctx<KickRoutes.${pascal}Controller['create']>) {\n const result = await this.create${pascal}UseCase.execute(ctx.body)\n ctx.created(result)\n }\n\n @Put('/:id', { body: update${pascal}Schema, name: 'Update${pascal}' })\n @ApiTags('${pascal}')\n async update(ctx: Ctx<KickRoutes.${pascal}Controller['update']>) {\n const result = await this.update${pascal}UseCase.execute(ctx.params.id, ctx.body)\n ctx.json(result)\n }\n\n @Delete('/:id')\n @ApiTags('${pascal}')\n async remove(ctx: Ctx<KickRoutes.${pascal}Controller['remove']>) {\n await this.delete${pascal}UseCase.execute(ctx.params.id)\n ctx.noContent()\n }\n}\n`\n}\n\n/** REST controller — injects service directly, flat import paths */\nexport function generateRestController(ctx: TemplateContext): string {\n const { pascal, kebab } = ctx\n const camel = pascal.charAt(0).toLowerCase() + pascal.slice(1)\n return `import { Controller, Get, Post, Put, Delete, Autowired, ApiQueryParams, type Ctx } from '@forinda/kickjs'\nimport { ApiTags } from '@forinda/kickjs-swagger'\nimport { ${pascal}Service } from './${kebab}.service'\nimport { create${pascal}Schema } from './dtos/create-${kebab}.dto'\nimport { update${pascal}Schema } from './dtos/update-${kebab}.dto'\nimport { ${pascal.toUpperCase()}_QUERY_CONFIG } from './${kebab}.constants'\n\n// Each handler annotates its \\`ctx\\` with \\`Ctx<KickRoutes.${pascal}Controller['<method>']>\\`\n// so \\`ctx.params\\`, \\`ctx.body\\`, and \\`ctx.query\\` are typed end-to-end.\n// The \\`KickRoutes\\` namespace is generated by \\`kick typegen\\` (auto-run on\n// \\`kick dev\\`) — see https://forinda.github.io/kick-js/guide/typegen.\n\n@Controller()\nexport class ${pascal}Controller {\n @Autowired() private readonly ${camel}Service!: ${pascal}Service\n\n @Get('/')\n @ApiTags('${pascal}')\n @ApiQueryParams(${pascal.toUpperCase()}_QUERY_CONFIG)\n async list(ctx: Ctx<KickRoutes.${pascal}Controller['list']>) {\n return ctx.paginate(\n (parsed) => this.${camel}Service.findPaginated(parsed),\n ${pascal.toUpperCase()}_QUERY_CONFIG,\n )\n }\n\n @Get('/:id')\n @ApiTags('${pascal}')\n async getById(ctx: Ctx<KickRoutes.${pascal}Controller['getById']>) {\n const result = await this.${camel}Service.findById(ctx.params.id)\n if (!result) return ctx.notFound('${pascal} not found')\n ctx.json(result)\n }\n\n @Post('/', { body: create${pascal}Schema, name: 'Create${pascal}' })\n @ApiTags('${pascal}')\n async create(ctx: Ctx<KickRoutes.${pascal}Controller['create']>) {\n const result = await this.${camel}Service.create(ctx.body)\n ctx.created(result)\n }\n\n @Put('/:id', { body: update${pascal}Schema, name: 'Update${pascal}' })\n @ApiTags('${pascal}')\n async update(ctx: Ctx<KickRoutes.${pascal}Controller['update']>) {\n const result = await this.${camel}Service.update(ctx.params.id, ctx.body)\n ctx.json(result)\n }\n\n @Delete('/:id')\n @ApiTags('${pascal}')\n async remove(ctx: Ctx<KickRoutes.${pascal}Controller['remove']>) {\n await this.${camel}Service.delete(ctx.params.id)\n ctx.noContent()\n }\n}\n`\n}\n","import type { TemplateContext } from './types'\n\nexport function generateCreateDTO(ctx: TemplateContext): string {\n const { pascal } = ctx\n return `import { z } from 'zod'\n\n/**\n * Create ${pascal} DTO — Zod schema for validating POST request bodies.\n * This schema is passed to @Post('/', { body: create${pascal}Schema }) for automatic validation.\n * It also generates OpenAPI request body docs when SwaggerAdapter is used.\n *\n * Add more fields as needed. Supported Zod types:\n * z.string(), z.number(), z.boolean(), z.enum([...]),\n * z.array(), z.object(), .optional(), .default(), .transform()\n */\nexport const create${pascal}Schema = z.object({\n name: z.string().min(1, 'Name is required').max(200),\n})\n\nexport type Create${pascal}DTO = z.infer<typeof create${pascal}Schema>\n`\n}\n\nexport function generateUpdateDTO(ctx: TemplateContext): string {\n const { pascal } = ctx\n return `import { z } from 'zod'\n\nexport const update${pascal}Schema = z.object({\n name: z.string().min(1).max(200).optional(),\n})\n\nexport type Update${pascal}DTO = z.infer<typeof update${pascal}Schema>\n`\n}\n\nexport function generateResponseDTO(ctx: TemplateContext): string {\n const { pascal } = ctx\n return `export interface ${pascal}ResponseDTO {\n id: string\n name: string\n createdAt: string\n updatedAt: string\n}\n`\n}\n","import type { TemplateContext } from './types'\n\nexport function generateRepositoryInterface(ctx: TemplateContext): string {\n const { pascal, kebab, dtoPrefix = '../../application/dtos', tokenScope = 'app' } = ctx\n return `/**\n * ${pascal} Repository Interface\n *\n * Defines the contract for data access.\n * The interface declares what operations are available;\n * implementations (in-memory, Drizzle, Prisma) fulfill the contract.\n *\n * To swap implementations, change the factory in the module's register() method.\n */\nimport { createToken } from '@forinda/kickjs'\nimport type { ${pascal}ResponseDTO } from '${dtoPrefix}/${kebab}-response.dto'\nimport type { Create${pascal}DTO } from '${dtoPrefix}/create-${kebab}.dto'\nimport type { Update${pascal}DTO } from '${dtoPrefix}/update-${kebab}.dto'\nimport type { ParsedQuery } from '@forinda/kickjs'\n\nexport interface I${pascal}Repository {\n findById(id: string): Promise<${pascal}ResponseDTO | null>\n findAll(): Promise<${pascal}ResponseDTO[]>\n findPaginated(parsed: ParsedQuery): Promise<{ data: ${pascal}ResponseDTO[]; total: number }>\n create(dto: Create${pascal}DTO): Promise<${pascal}ResponseDTO>\n update(id: string, dto: Update${pascal}DTO): Promise<${pascal}ResponseDTO>\n delete(id: string): Promise<void>\n}\n\n/**\n * Collision-safe DI token bound to \\`I${pascal}Repository\\`.\n * \\`container.resolve(${pascal.toUpperCase()}_REPOSITORY)\\` and\n * \\`@Inject(${pascal.toUpperCase()}_REPOSITORY)\\` both return the typed\n * interface — no manual generic, no \\`any\\` cast.\n *\n * The \\`'${tokenScope}/'\\` prefix matches the project scope so\n * \\`kick-lint\\`'s \\`token-reserved-prefix\\` rule never fires —\n * adopters must NOT use the reserved \\`'kick/'\\` namespace.\n */\nexport const ${pascal.toUpperCase()}_REPOSITORY = createToken<I${pascal}Repository>('${tokenScope}/${pascal}/repository')\n`\n}\n\nexport function generateInMemoryRepository(ctx: TemplateContext): string {\n const {\n pascal,\n kebab,\n repoPrefix = '../../domain/repositories',\n dtoPrefix = '../../application/dtos',\n } = ctx\n return `/**\n * In-Memory ${pascal} Repository\n *\n * Implements the repository interface using a Map.\n * Useful for prototyping and testing. Replace with a database implementation\n * (Drizzle, Prisma, etc.) for production use.\n *\n * @Repository() registers this class in the DI container as a singleton.\n */\nimport { randomUUID } from 'node:crypto'\nimport { Repository, HttpException } from '@forinda/kickjs'\nimport type { ParsedQuery } from '@forinda/kickjs'\nimport type { I${pascal}Repository } from '${repoPrefix}/${kebab}.repository'\nimport type { ${pascal}ResponseDTO } from '${dtoPrefix}/${kebab}-response.dto'\nimport type { Create${pascal}DTO } from '${dtoPrefix}/create-${kebab}.dto'\nimport type { Update${pascal}DTO } from '${dtoPrefix}/update-${kebab}.dto'\n\n@Repository()\nexport class InMemory${pascal}Repository implements I${pascal}Repository {\n private store = new Map<string, ${pascal}ResponseDTO>()\n\n async findById(id: string): Promise<${pascal}ResponseDTO | null> {\n return this.store.get(id) ?? null\n }\n\n async findAll(): Promise<${pascal}ResponseDTO[]> {\n return Array.from(this.store.values())\n }\n\n async findPaginated(parsed: ParsedQuery): Promise<{ data: ${pascal}ResponseDTO[]; total: number }> {\n const all = Array.from(this.store.values())\n const data = all.slice(parsed.pagination.offset, parsed.pagination.offset + parsed.pagination.limit)\n return { data, total: all.length }\n }\n\n async create(dto: Create${pascal}DTO): Promise<${pascal}ResponseDTO> {\n const now = new Date().toISOString()\n const entity: ${pascal}ResponseDTO = {\n id: randomUUID(),\n ...dto,\n createdAt: now,\n updatedAt: now,\n }\n this.store.set(entity.id, entity)\n return entity\n }\n\n async update(id: string, dto: Update${pascal}DTO): Promise<${pascal}ResponseDTO> {\n const existing = this.store.get(id)\n if (!existing) throw HttpException.notFound('${pascal} not found')\n const updated = { ...existing, ...dto, updatedAt: new Date().toISOString() }\n this.store.set(id, updated)\n return updated\n }\n\n async delete(id: string): Promise<void> {\n if (!this.store.has(id)) throw HttpException.notFound('${pascal} not found')\n this.store.delete(id)\n }\n}\n`\n}\n\nexport function generateCustomRepository(ctx: TemplateContext): string {\n const {\n pascal,\n kebab,\n repoType = '',\n repoPrefix = '../../domain/repositories',\n dtoPrefix = '../../application/dtos',\n } = ctx\n const repoTypePascal =\n repoType.charAt(0).toUpperCase() +\n repoType.slice(1).replace(/-([a-z])/g, (_, c: string) => c.toUpperCase())\n return `/**\n * ${repoTypePascal} ${pascal} Repository\n *\n * Stub implementation for a custom '${repoType}' repository.\n * Implements the repository interface using an in-memory Map as a placeholder.\n *\n * TODO: Replace the in-memory Map with your ${repoType} data-access logic.\n * See I${pascal}Repository for the interface contract.\n *\n * @Repository() registers this class in the DI container as a singleton.\n */\nimport { randomUUID } from 'node:crypto'\nimport { Repository, HttpException } from '@forinda/kickjs'\nimport type { ParsedQuery } from '@forinda/kickjs'\nimport type { I${pascal}Repository } from '${repoPrefix}/${kebab}.repository'\nimport type { ${pascal}ResponseDTO } from '${dtoPrefix}/${kebab}-response.dto'\nimport type { Create${pascal}DTO } from '${dtoPrefix}/create-${kebab}.dto'\nimport type { Update${pascal}DTO } from '${dtoPrefix}/update-${kebab}.dto'\n\n@Repository()\nexport class ${repoTypePascal}${pascal}Repository implements I${pascal}Repository {\n // TODO: Replace with your ${repoType} client/connection\n private store = new Map<string, ${pascal}ResponseDTO>()\n\n async findById(id: string): Promise<${pascal}ResponseDTO | null> {\n // TODO: Implement with ${repoType}\n return this.store.get(id) ?? null\n }\n\n async findAll(): Promise<${pascal}ResponseDTO[]> {\n // TODO: Implement with ${repoType}\n return Array.from(this.store.values())\n }\n\n async findPaginated(parsed: ParsedQuery): Promise<{ data: ${pascal}ResponseDTO[]; total: number }> {\n // TODO: Implement with ${repoType}\n const all = Array.from(this.store.values())\n const data = all.slice(parsed.pagination.offset, parsed.pagination.offset + parsed.pagination.limit)\n return { data, total: all.length }\n }\n\n async create(dto: Create${pascal}DTO): Promise<${pascal}ResponseDTO> {\n // TODO: Implement with ${repoType}\n const now = new Date().toISOString()\n const entity: ${pascal}ResponseDTO = {\n id: randomUUID(),\n ...dto,\n createdAt: now,\n updatedAt: now,\n }\n this.store.set(entity.id, entity)\n return entity\n }\n\n async update(id: string, dto: Update${pascal}DTO): Promise<${pascal}ResponseDTO> {\n // TODO: Implement with ${repoType}\n const existing = this.store.get(id)\n if (!existing) throw HttpException.notFound('${pascal} not found')\n const updated = { ...existing, ...dto, updatedAt: new Date().toISOString() }\n this.store.set(id, updated)\n return updated\n }\n\n async delete(id: string): Promise<void> {\n // TODO: Implement with ${repoType}\n if (!this.store.has(id)) throw HttpException.notFound('${pascal} not found')\n this.store.delete(id)\n }\n}\n`\n}\n","import type { TemplateContext } from './types'\n\nexport function generateControllerTest(ctx: TemplateContext): string {\n const { pascal, kebab, plural = '' } = ctx\n return `import { describe, it, expect, beforeEach } from 'vitest'\nimport { Container } from '@forinda/kickjs'\n\ndescribe('${pascal}Controller', () => {\n beforeEach(() => {\n Container.reset()\n })\n\n it('should be defined', () => {\n expect(true).toBe(true)\n })\n\n describe('POST /${plural}', () => {\n it('should create a new ${kebab}', async () => {\n // TODO: Set up test module, call create endpoint, assert 201\n expect(true).toBe(true)\n })\n })\n\n describe('GET /${plural}', () => {\n it('should return paginated ${plural}', async () => {\n // TODO: Set up test module, call list endpoint, assert { data, meta }\n expect(true).toBe(true)\n })\n })\n\n describe('GET /${plural}/:id', () => {\n it('should return a ${kebab} by id', async () => {\n // TODO: Create a ${kebab}, then fetch by id, assert match\n expect(true).toBe(true)\n })\n\n it('should return 404 for non-existent ${kebab}', async () => {\n // TODO: Fetch non-existent id, assert 404\n expect(true).toBe(true)\n })\n })\n\n describe('PUT /${plural}/:id', () => {\n it('should update an existing ${kebab}', async () => {\n // TODO: Create, update, assert changes\n expect(true).toBe(true)\n })\n })\n\n describe('DELETE /${plural}/:id', () => {\n it('should delete a ${kebab}', async () => {\n // TODO: Create, delete, assert gone\n expect(true).toBe(true)\n })\n })\n})\n`\n}\n\nexport function generateRepositoryTest(ctx: TemplateContext): string {\n const {\n pascal,\n kebab,\n plural = '',\n repoPrefix = `../infrastructure/repositories/in-memory-${kebab}.repository`,\n } = ctx\n return `import { describe, it, expect, beforeEach } from 'vitest'\nimport { InMemory${pascal}Repository } from '${repoPrefix}'\n\ndescribe('InMemory${pascal}Repository', () => {\n let repo: InMemory${pascal}Repository\n\n beforeEach(() => {\n repo = new InMemory${pascal}Repository()\n })\n\n it('should create and retrieve a ${kebab}', async () => {\n const created = await repo.create({ name: 'Test ${pascal}' })\n expect(created).toBeDefined()\n expect(created.name).toBe('Test ${pascal}')\n expect(created.id).toBeDefined()\n\n const found = await repo.findById(created.id)\n expect(found).toEqual(created)\n })\n\n it('should return null for non-existent id', async () => {\n const found = await repo.findById('non-existent')\n expect(found).toBeNull()\n })\n\n it('should list all ${plural}', async () => {\n await repo.create({ name: '${pascal} 1' })\n await repo.create({ name: '${pascal} 2' })\n\n const all = await repo.findAll()\n expect(all).toHaveLength(2)\n })\n\n it('should return paginated results', async () => {\n await repo.create({ name: '${pascal} 1' })\n await repo.create({ name: '${pascal} 2' })\n await repo.create({ name: '${pascal} 3' })\n\n const result = await repo.findPaginated({\n filters: [],\n sort: [],\n search: '',\n pagination: { page: 1, limit: 2, offset: 0 },\n })\n\n expect(result.data).toHaveLength(2)\n expect(result.total).toBe(3)\n })\n\n it('should update a ${kebab}', async () => {\n const created = await repo.create({ name: 'Original' })\n const updated = await repo.update(created.id, { name: 'Updated' })\n expect(updated.name).toBe('Updated')\n })\n\n it('should delete a ${kebab}', async () => {\n const created = await repo.create({ name: 'To Delete' })\n await repo.delete(created.id)\n const found = await repo.findById(created.id)\n expect(found).toBeNull()\n })\n})\n`\n}\n","import type { TemplateContext } from './types'\n\n/** REST service — wraps repository with CRUD methods, replaces use-cases for flat pattern */\nexport function generateRestService(ctx: TemplateContext): string {\n const { pascal, kebab } = ctx\n return `import { Service, Inject, HttpException } from '@forinda/kickjs'\nimport type { ParsedQuery } from '@forinda/kickjs'\nimport { ${pascal.toUpperCase()}_REPOSITORY, type I${pascal}Repository } from './${kebab}.repository'\nimport type { ${pascal}ResponseDTO } from './dtos/${kebab}-response.dto'\nimport type { Create${pascal}DTO } from './dtos/create-${kebab}.dto'\nimport type { Update${pascal}DTO } from './dtos/update-${kebab}.dto'\n\n@Service()\nexport class ${pascal}Service {\n constructor(\n @Inject(${pascal.toUpperCase()}_REPOSITORY) private readonly repo: I${pascal}Repository,\n ) {}\n\n async findById(id: string): Promise<${pascal}ResponseDTO | null> {\n return this.repo.findById(id)\n }\n\n async findAll(): Promise<${pascal}ResponseDTO[]> {\n return this.repo.findAll()\n }\n\n async findPaginated(parsed: ParsedQuery) {\n return this.repo.findPaginated(parsed)\n }\n\n async create(dto: Create${pascal}DTO): Promise<${pascal}ResponseDTO> {\n return this.repo.create(dto)\n }\n\n async update(id: string, dto: Update${pascal}DTO): Promise<${pascal}ResponseDTO> {\n return this.repo.update(id, dto)\n }\n\n async delete(id: string): Promise<void> {\n await this.repo.delete(id)\n }\n}\n`\n}\n\n/** REST constants — query config for flat pattern */\nexport function generateRestConstants(ctx: TemplateContext): string {\n const { pascal } = ctx\n return `import type { QueryFieldConfig } from '@forinda/kickjs'\n\nexport const ${pascal.toUpperCase()}_QUERY_CONFIG: QueryFieldConfig = {\n filterable: ['name'],\n sortable: ['name', 'createdAt'],\n searchable: ['name'],\n}\n`\n}\n","type ProjectTemplate = 'rest' | 'minimal'\nexport type ProjectRuntime = 'express' | 'fastify' | 'h3'\n\n/** Per-runtime import source + factory name for the scaffolded `runtime:` option. */\nconst RUNTIME_FACTORY: Record<ProjectRuntime, { from: string; name: string }> = {\n express: { from: '@forinda/kickjs', name: 'expressRuntime' },\n fastify: { from: '@forinda/kickjs/fastify', name: 'fastifyRuntime' },\n h3: { from: '@forinda/kickjs/h3', name: 'h3Runtime' },\n}\n\n/**\n * Generate src/index.ts entry file with template-specific bootstrap.\n *\n * The runtime is always emitted explicitly (`runtime: expressRuntime()` etc.)\n * so the entry file is self-documenting and switching engines is a one-line\n * edit. Fastify / h3 parse bodies natively, so the REST template skips the\n * `express.json()` middleware (and the `express` import) under those engines.\n *\n * All templates export the app for the Vite plugin (dev mode).\n */\nexport function generateEntryFile(\n name: string,\n template: ProjectTemplate,\n version: string,\n packages: string[] = [],\n runtime: ProjectRuntime = 'express',\n): string {\n const factory = RUNTIME_FACTORY[runtime]\n const isExpress = runtime === 'express'\n\n switch (template) {\n case 'minimal': {\n const imports: string[] = []\n const adapters: string[] = []\n\n // The runtime factory comes from the core package for Express, or a\n // subpath for Fastify / h3.\n const kickImport = isExpress\n ? `import { bootstrap, ${factory.name} } from '@forinda/kickjs'`\n : `import { bootstrap } from '@forinda/kickjs'\\nimport { ${factory.name} } from '${factory.from}'`\n\n if (packages.includes('swagger')) {\n imports.push(`import { SwaggerAdapter } from '@forinda/kickjs-swagger'`)\n adapters.push(` SwaggerAdapter({ info: { title: '${name}', version: '${version}' } }),`)\n }\n if (packages.includes('devtools')) {\n imports.push(`import { DevToolsAdapter } from '@forinda/kickjs-devtools'`)\n adapters.push(` DevToolsAdapter(),`)\n }\n const importsBlock = imports.length ? imports.join('\\n') + '\\n' : ''\n const adaptersBlock = adapters.length ? `,\\n adapters: [\\n${adapters.join('\\n')}\\n ]` : ''\n\n return `import 'reflect-metadata'\n// Side-effect import — registers the extended env schema with kickjs\n// **before** any controller / service / @Value gets resolved. Without\n// this line ConfigService.get('YOUR_KEY') returns undefined because the\n// cached schema would still be the base shape. See guide/configuration.\nimport './config'\n${kickImport}\n${importsBlock}import { modules } from './modules'\n\n// Export the app for the Vite plugin (dev mode)\nexport const app = await bootstrap({ modules, runtime: ${factory.name}()${adaptersBlock} })\n`\n }\n\n case 'rest':\n default: {\n // Build adapters based on user-selected packages\n const restImports: string[] = []\n const restAdapters: string[] = []\n\n if (packages.includes('devtools')) {\n restImports.push(`import { DevToolsAdapter } from '@forinda/kickjs-devtools'`)\n restAdapters.push(` DevToolsAdapter(),`)\n }\n if (packages.includes('swagger')) {\n restImports.push(`import { SwaggerAdapter } from '@forinda/kickjs-swagger'`)\n restAdapters.push(\n ` SwaggerAdapter({\\n info: { title: '${name}', version: '${version}' },\\n }),`,\n )\n }\n const restImportsBlock = restImports.length ? restImports.join('\\n') + '\\n' : ''\n const restAdaptersBlock = restAdapters.length\n ? `\\n adapters: [\\n${restAdapters.join('\\n')}\\n ],`\n : ''\n\n // Express needs `express.json()` for body parsing; Fastify / h3 parse\n // bodies natively, so adding it would consume the body stream twice.\n const kickNamed = ['bootstrap', 'requestId', 'requestLogger', 'helmet', 'cors']\n if (isExpress) kickNamed.push(factory.name)\n const kickImport = isExpress\n ? `import express from 'express'\\nimport {\\n ${kickNamed.join(',\\n ')},\\n} from '@forinda/kickjs'`\n : `import {\\n ${kickNamed.join(',\\n ')},\\n} from '@forinda/kickjs'\\nimport { ${factory.name} } from '${factory.from}'`\n const bodyParserLine = isExpress ? `\\n express.json(),` : ''\n\n return `import 'reflect-metadata'\n// Side-effect import — registers the extended env schema with kickjs\n// **before** any controller / service / @Value gets resolved. Without\n// this line ConfigService.get('YOUR_KEY') returns undefined because the\n// cached schema would still be the base shape. See guide/configuration.\nimport './config'\n${kickImport}\n${restImportsBlock}import { modules } from './modules'\n\n// Export the app for the Vite plugin (dev mode)\nexport const app = await bootstrap({\n modules,\n runtime: ${factory.name}(),${restAdaptersBlock}\n middleware: [\n helmet(),\n cors({ origin: '*' }),\n requestId(),\n requestLogger(),${bodyParserLine}\n ],\n})\n`\n }\n }\n}\n\n/** Generate src/modules/index.ts module registry */\nexport function generateModulesIndex(): string {\n return `import { defineModules } from '@forinda/kickjs'\nimport { HelloModule } from './hello/hello.module'\n\n// Remove HelloModule and run: kick g module <name>\n// \\`defineModules()\\` returns a chainable list — \\`kick g module\\` appends\n// \\`.mount(NewModule())\\` to the chain on every generation.\nexport const modules = defineModules().mount(HelloModule())\n`\n}\n\n/**\n * Generate `src/config/index.ts` — the project's typed env schema.\n *\n * Default-exports a `defineEnv(...)` schema so `kick typegen` can\n * infer it into the global `KickEnv` registry, and *also* calls\n * `loadEnv(envSchema)` as a module-load side effect so `ConfigService`\n * and `@Value()` see the extended shape from the very first DI\n * resolution. The companion `src/index.ts` template adds\n * `import './config'` immediately after `reflect-metadata` so the\n * registration runs before `bootstrap()` constructs anything.\n *\n * After typegen runs:\n *\n * @Value('DATABASE_URL') private url!: Env<'DATABASE_URL'>\n * process.env.DATABASE_URL // typed as string\n *\n * Both autocomplete and type-check at compile time.\n */\nexport function generateEnvFile(schemaLib: 'zod' | 'valibot' | 'yup' = 'zod'): string {\n if (schemaLib === 'valibot') {\n return `import { loadEnvFromSchema } from '@forinda/kickjs/config'\nimport { fromValibot } from '@forinda/kickjs-schema/valibot'\nimport * as v from 'valibot'\n\n/**\n * Project environment schema (Valibot).\n *\n * \\`fromValibot\\` wraps the Valibot schema as a \\`KickSchema\\` so the\n * env loader, validate middleware, and swagger spec generator all see\n * the same shape. The default export is the contract \\`kick typegen\\`\n * reads to populate \\`KickEnv\\` via \\`InferSchemaOutput<typeof _envSchema>\\`\n * — that's what makes \\`@Value('FOO')\\` autocomplete and\n * \\`process.env.FOO\\` typed.\n *\n * @example\n * DATABASE_URL: v.pipe(v.string(), v.url()),\n * JWT_SECRET: v.pipe(v.string(), v.minLength(32)),\n * REDIS_URL: v.optional(v.pipe(v.string(), v.url())),\n */\nconst envSchema = fromValibot(\n v.object({\n PORT: v.optional(v.pipe(v.string(), v.transform(Number)), '3000'),\n NODE_ENV: v.optional(v.picklist(['development', 'production', 'test']), 'development'),\n LOG_LEVEL: v.optional(v.string(), 'info'),\n // DATABASE_URL: v.pipe(v.string(), v.url()),\n }),\n)\n\n/**\n * IMPORTANT — side effect: register the schema with kickjs's env cache\n * **at module-load time**. \\`ConfigService\\` and \\`@Value()\\` both consume\n * this cache, and they will fall back to the base schema (or undefined)\n * if no extended schema has been registered before they're resolved.\n *\n * As long as \\`src/index.ts\\` imports this file (\\`import './config'\\`) at\n * the top — before \\`bootstrap()\\` runs — every controller and service\n * in the app sees the typed extended values.\n */\nexport const env = loadEnvFromSchema(envSchema)\n\nexport default envSchema\n`\n }\n\n if (schemaLib === 'yup') {\n return `import { loadEnvFromSchema } from '@forinda/kickjs/config'\nimport { fromYup } from '@forinda/kickjs-schema/yup'\nimport * as yup from 'yup'\n\n/**\n * Project environment schema (Yup).\n *\n * \\`fromYup\\` wraps the Yup schema as a \\`KickSchema\\` so the env loader,\n * validate middleware, and swagger spec generator all see the same\n * shape. The default export is the contract \\`kick typegen\\` reads to\n * populate \\`KickEnv\\` via \\`InferSchemaOutput<typeof _envSchema>\\`.\n *\n * Note: Yup's \\`.url()\\` defaults to http/https; database connection\n * strings like \\`postgres://\\` use \\`.matches(/^[a-z]+:\\\\/\\\\/.+/i)\\` or\n * a plain \\`.string().required()\\`.\n *\n * @example\n * DATABASE_URL: yup.string().required(),\n * JWT_SECRET: yup.string().min(32).required(),\n * REDIS_URL: yup.string().url().optional(),\n */\nconst envSchema = fromYup(\n yup.object({\n PORT: yup.number().default(3000),\n NODE_ENV: yup\n .string()\n .oneOf(['development', 'production', 'test'])\n .default('development'),\n LOG_LEVEL: yup.string().default('info'),\n // DATABASE_URL: yup.string().required(),\n }),\n)\n\n/**\n * IMPORTANT — side effect: register the schema with kickjs's env cache\n * **at module-load time**. \\`ConfigService\\` and \\`@Value()\\` both consume\n * this cache, and they will fall back to the base schema (or undefined)\n * if no extended schema has been registered before they're resolved.\n *\n * As long as \\`src/index.ts\\` imports this file (\\`import './config'\\`) at\n * the top — before \\`bootstrap()\\` runs — every controller and service\n * in the app sees the typed extended values.\n */\nexport const env = loadEnvFromSchema(envSchema)\n\nexport default envSchema\n`\n }\n\n // zod (default)\n return `import { loadEnvFromSchema } from '@forinda/kickjs/config'\nimport { fromZod } from '@forinda/kickjs-schema/zod'\nimport { z } from 'zod'\n\n/**\n * Project environment schema (Zod).\n *\n * \\`fromZod\\` wraps the Zod schema as a \\`KickSchema\\` so the env loader,\n * validate middleware, and swagger spec generator all see the same\n * shape. The default export is the contract \\`kick typegen\\` reads to\n * populate \\`KickEnv\\` via \\`InferSchemaOutput<typeof _envSchema>\\` —\n * that's what makes \\`@Value('FOO')\\` autocomplete and\n * \\`process.env.FOO\\` typed.\n *\n * @example\n * DATABASE_URL: z.string().url(),\n * JWT_SECRET: z.string().min(32),\n * REDIS_URL: z.string().url().optional(),\n */\nconst envSchema = fromZod(\n z.object({\n PORT: z.coerce.number().default(3000),\n NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),\n LOG_LEVEL: z.string().default('info'),\n // DATABASE_URL: z.string().url(),\n }),\n)\n\n/**\n * IMPORTANT — side effect: register the schema with kickjs's env cache\n * **at module-load time**. \\`ConfigService\\` and \\`@Value()\\` both consume\n * this cache, and they will fall back to the base schema (or undefined)\n * if no extended schema has been registered before they're resolved.\n *\n * As long as \\`src/index.ts\\` imports this file (\\`import './config'\\`) at\n * the top — before \\`bootstrap()\\` runs — every controller and service\n * in the app sees the typed extended values.\n */\nexport const env = loadEnvFromSchema(envSchema)\n\nexport default envSchema\n`\n}\n\n/** Generate src/modules/hello/hello.service.ts */\nexport function generateHelloService(): string {\n return `import { Service } from '@forinda/kickjs'\n\n@Service()\nexport class HelloService {\n greet(name: string) {\n return { message: \\`Hello \\${name} from KickJS!\\`, timestamp: new Date().toISOString() }\n }\n\n healthCheck() {\n return { status: 'ok', uptime: process.uptime() }\n }\n}\n`\n}\n\n/** Generate src/modules/hello/hello.controller.ts */\nexport function generateHelloController(): string {\n return `import { Controller, Get, Autowired, type Ctx } from '@forinda/kickjs'\nimport { HelloService } from './hello.service'\n\n// \\`Ctx<KickRoutes.HelloController['<method>']>\\` is generated by\n// \\`kick typegen\\` (auto-run on \\`kick dev\\`). The first run after a fresh\n// scaffold creates \\`.kickjs/types/routes.ts\\` so this file typechecks.\n// See https://forinda.github.io/kick-js/guide/typegen.\n\n@Controller()\nexport class HelloController {\n @Autowired() private readonly helloService!: HelloService\n\n @Get('/')\n index(ctx: Ctx<KickRoutes.HelloController['index']>) {\n ctx.json(this.helloService.greet('World'))\n }\n\n @Get('/health')\n health(ctx: Ctx<KickRoutes.HelloController['health']>) {\n ctx.json(this.helloService.healthCheck())\n }\n}\n`\n}\n\n/** Generate src/modules/hello/hello.module.ts */\nexport function generateHelloModule(): string {\n return `import { defineModule } from '@forinda/kickjs'\nimport { HelloController } from './hello.controller'\n\nexport const HelloModule = defineModule({\n name: 'HelloModule',\n build: () => ({\n // \\`register(container)\\` is optional — only implement it when you need\n // to bind a token to a concrete implementation, e.g.\n // register(container) {\n // container.registerFactory(USER_REPOSITORY, () => container.resolve(InMemoryUserRepository))\n // }\n // The HelloService uses @Service() so the decorator handles registration.\n\n routes() {\n return {\n path: '/hello',\n controller: HelloController,\n }\n },\n }),\n})\n`\n}\n\n/** Generate kick.config.ts CLI configuration */\nexport function generateKickConfig(\n template: ProjectTemplate,\n defaultRepo: string = 'inmemory',\n packageManager: 'pnpm' | 'npm' | 'yarn' | 'bun' = 'pnpm',\n runtime: 'express' | 'fastify' | 'h3' = 'express',\n): string {\n // `inmemory` is the only built-in; every other name (incl. the\n // deprecated prisma/drizzle) is emitted as a `{ name }` custom repo.\n const repoValue = defaultRepo === 'inmemory' ? `'inmemory'` : `{ name: '${defaultRepo}' }`\n\n return `import { defineConfig } from '@forinda/kickjs-cli'\n\nexport default defineConfig({\n pattern: '${template}',\n // The HTTP engine this app boots on (matches \\`bootstrap({ runtime })\\` in\n // src/index.ts). Dep-aware commands read it: \\`kick add upload\\` installs the\n // engine's multipart driver, \\`kick doctor\\` checks the engine peers, and\n // \\`kick typegen\\` flips the runtime escape-hatch types to this engine.\n runtime: '${runtime}',\n // Pinned so \\`kick add\\` and other dep-installing commands always use the\n // project's intended package manager, regardless of which lockfile exists.\n packageManager: '${packageManager}',\n modules: {\n dir: 'src/modules',\n repo: ${repoValue},\n pluralize: true,\n },\n\n // \\`kick typegen\\` populates \\`.kickjs/types/\\` so \\`Ctx<KickRoutes.X['method']>\\`\n // resolves to fully-typed params/body/query. Auto-runs on \\`kick dev\\`.\n // \\`'kickjs-schema'\\` routes inference through \\`InferSchemaOutput\\` so the\n // typegen works for any wrapped schema (Zod / Valibot / Yup). Switch\n // to \\`'zod'\\` if you ship Zod schemas without \\`fromZod()\\` wrapping, or\n // set \\`schemaValidator: false\\` to skip schema-driven body typing.\n typegen: {\n schemaValidator: 'kickjs-schema',\n },\n\n commands: [\n {\n name: 'test',\n description: 'Run tests with Vitest',\n steps: 'npx vitest run',\n },\n {\n name: 'format',\n description: 'Format code with Prettier',\n steps: 'npx prettier --write src/',\n },\n {\n name: 'format:check',\n description: 'Check formatting without writing',\n steps: 'npx prettier --check src/',\n },\n {\n name: 'ci:check',\n description: 'Run typecheck + format check',\n steps: ['npx tsc --noEmit', 'npx prettier --check src/'],\n aliases: ['verify'],\n },\n ],\n})\n`\n}\n","import type { ModuleContext } from './types'\nimport { generateMinimalModuleIndex } from '../templates'\n\nexport async function generateMinimalFiles(ctx: ModuleContext): Promise<void> {\n const { pascal, kebab, plural, style, write } = ctx\n\n // Module file (named `<kebab>.module.ts` so Vite's module-discovery plugin picks it up)\n await write(`${kebab}.module.ts`, generateMinimalModuleIndex({ pascal, kebab, plural, style }))\n\n await write(\n `${kebab}.controller.ts`,\n `import { Controller, Get, type Ctx } from '@forinda/kickjs'\n\n// \\`Ctx<KickRoutes.${pascal}Controller['<method>']>\\` is generated by\n// \\`kick typegen\\` (auto-run on \\`kick dev\\`).\n\n@Controller()\nexport class ${pascal}Controller {\n @Get('/')\n async list(ctx: Ctx<KickRoutes.${pascal}Controller['list']>) {\n ctx.json({ message: '${pascal} list' })\n }\n}\n`,\n )\n}\n","import type { ModuleContext } from './types'\nimport { toKebabCase } from '../../utils/naming'\nimport {\n generateRestModuleIndex,\n generateRestController,\n generateRestConstants,\n generateRestService,\n generateCreateDTO,\n generateUpdateDTO,\n generateResponseDTO,\n generateRepositoryInterface,\n generateInMemoryRepository,\n generateCustomRepository,\n generateControllerTest,\n generateRepositoryTest,\n} from '../templates'\n\nexport async function generateRestFiles(ctx: ModuleContext): Promise<void> {\n const { pascal, kebab, plural, pluralPascal, repo, noTests, tokenScope, style, write } = ctx\n\n // Module file (named `<kebab>.module.ts` so Vite's module-discovery plugin picks it up)\n await write(`${kebab}.module.ts`, generateRestModuleIndex({ pascal, kebab, plural, repo, style }))\n\n // Constants\n await write(`${kebab}.constants.ts`, generateRestConstants({ pascal, kebab }))\n\n // Controller (injects service)\n await write(\n `${kebab}.controller.ts`,\n generateRestController({ pascal, kebab, plural, pluralPascal }),\n )\n\n // Service (wraps repository)\n await write(`${kebab}.service.ts`, generateRestService({ pascal, kebab }))\n\n // DTOs\n await write(`dtos/create-${kebab}.dto.ts`, generateCreateDTO({ pascal, kebab }))\n await write(`dtos/update-${kebab}.dto.ts`, generateUpdateDTO({ pascal, kebab }))\n await write(`dtos/${kebab}-response.dto.ts`, generateResponseDTO({ pascal, kebab }))\n\n // Repository interface (flat imports)\n await write(\n `${kebab}.repository.ts`,\n generateRepositoryInterface({ pascal, kebab, dtoPrefix: './dtos', tokenScope }),\n )\n\n // Repository implementation (flat imports). `inmemory` is the only\n // built-in (zero-dep working impl); every other name scaffolds a\n // generic custom stub — prisma/drizzle no longer have dedicated\n // generators (see `warnIfDeprecatedRepo`).\n const isInMemory = repo === 'inmemory'\n const repoFile = isInMemory ? `in-memory-${kebab}` : `${toKebabCase(repo)}-${kebab}`\n const repoContent = isInMemory\n ? generateInMemoryRepository({ pascal, kebab, repoPrefix: '.', dtoPrefix: './dtos' })\n : generateCustomRepository({\n pascal,\n kebab,\n repoType: repo,\n repoPrefix: '.',\n dtoPrefix: './dtos',\n })\n await write(`${repoFile}.repository.ts`, repoContent)\n\n // Tests\n if (!noTests) {\n // Always generate an in-memory repo for testing — even when using drizzle/prisma\n if (repo !== 'inmemory') {\n await write(\n `in-memory-${kebab}.repository.ts`,\n generateInMemoryRepository({ pascal, kebab, repoPrefix: '.', dtoPrefix: './dtos' }),\n )\n }\n await write(\n `__tests__/${kebab}.controller.test.ts`,\n generateControllerTest({ pascal, kebab, plural }),\n )\n await write(\n `__tests__/${kebab}.repository.test.ts`,\n generateRepositoryTest({\n pascal,\n kebab,\n plural,\n repoPrefix: `../in-memory-${kebab}.repository`,\n }),\n )\n }\n}\n","import { join } from 'node:path'\nimport { writeFileSafe, fileExists } from '../utils/fs'\nimport { confirm, log } from '../utils/prompts'\nimport { colors } from '../utils/colors'\nimport { toPascalCase, toKebabCase, pluralize, pluralizePascal } from '../utils/naming'\nimport { escapeRegex } from '../utils/regex'\nimport { readFile, writeFile } from 'node:fs/promises'\nimport type { ProjectPattern, RepoTypeConfig } from '../config'\nimport type { ModuleStyle } from './templates/types'\nimport { generateMinimalFiles, generateRestFiles } from './patterns'\nimport type { ModuleContext } from './patterns'\n\nexport type BuiltinRepoType = 'inmemory'\nexport type RepoType = BuiltinRepoType | (string & {})\n\n/** Resolve a RepoTypeConfig (from kick.config.ts) into a flat repo type string */\nexport function resolveRepoType(config?: RepoTypeConfig): RepoType {\n if (!config) return 'inmemory'\n if (typeof config === 'string') return config\n return config.name\n}\n\ninterface GenerateModuleOptions {\n name: string\n modulesDir: string\n noEntity?: boolean\n noTests?: boolean\n repo?: RepoType\n minimal?: boolean\n force?: boolean\n pattern?: ProjectPattern\n dryRun?: boolean\n /** When false, skip pluralization — use singular names for folders, routes, and classes */\n pluralize?: boolean\n /** Prisma client import path (default: '@prisma/client', Prisma 7+: '@/generated/prisma/client') */\n prismaClientPath?: string\n /**\n * DI-token scope prefix substituted into emitted `createToken<T>()`\n * literals. Resolved by the orchestrating command from\n * `kick.config.ts > tokenScope` or the project's package.json.\n * Falls back to `'app'` when not set so the generator can be called\n * without a config in tests/fixtures.\n */\n tokenScope?: string\n /**\n * Module declaration style — `'define'` (factory, default) or\n * `'class'` (legacy). Resolved by the orchestrating command from\n * `kick.config.ts > modules.style`.\n */\n style?: ModuleStyle\n}\n\n/**\n * Generate a module — structure depends on the project pattern.\n *\n * Patterns:\n * rest — flat folder: controller + service + DTOs + repo (default)\n * minimal — just controller + module index\n */\nexport async function generateModule(options: GenerateModuleOptions): Promise<string[]> {\n const { name, modulesDir, noEntity, noTests, repo = 'inmemory', force, dryRun } = options\n const shouldPluralize = options.pluralize !== false\n\n let pattern = options.pattern ?? 'rest'\n if (options.minimal) pattern = 'minimal'\n\n const kebab = toKebabCase(name)\n const pascal = toPascalCase(name)\n const plural = shouldPluralize ? pluralize(kebab) : kebab\n const pluralPascal = shouldPluralize ? pluralizePascal(pascal) : pascal\n const moduleDir = join(modulesDir, plural)\n\n const files: string[] = []\n let overwriteAll = force ?? false\n\n const write = async (relativePath: string, content: string) => {\n const fullPath = join(moduleDir, relativePath)\n if (dryRun) {\n files.push(fullPath)\n return\n }\n if (!overwriteAll && (await fileExists(fullPath))) {\n const shouldOverwrite = await confirm({\n message: `File exists: ${colors.dim(relativePath)}. Overwrite?`,\n initialValue: false,\n })\n if (!shouldOverwrite) {\n log.warn(`Skipped: ${relativePath}`)\n return\n }\n }\n await writeFileSafe(fullPath, content)\n files.push(fullPath)\n }\n\n const ctx: ModuleContext = {\n kebab,\n pascal,\n plural,\n pluralPascal,\n moduleDir,\n repo,\n noEntity: noEntity ?? false,\n noTests: noTests ?? false,\n prismaClientPath: options.prismaClientPath ?? '@prisma/client',\n tokenScope: options.tokenScope ?? 'app',\n style: options.style ?? 'define',\n write,\n files,\n }\n\n switch (pattern) {\n case 'minimal':\n await generateMinimalFiles(ctx)\n break\n case 'rest':\n default:\n await generateRestFiles(ctx)\n break\n }\n\n // Auto-register in modules index (all patterns need this)\n if (!dryRun) {\n await autoRegisterModule(modulesDir, pascal, plural, kebab, ctx.style)\n }\n\n return files\n}\n\n// ── Auto-register in modules index ──────────────────────────────────────\n\n/**\n * Add a new module to `src/modules/index.ts`. Handles both the\n * fresh-project initial-write path (creates the file with the right\n * shape for the resolved style) and the established-project append\n * path (inserts the import + extends the flat array OR fluent chain\n * via {@link appendModuleEntry}).\n *\n * Exported so `scaffold.ts` can reuse it — both call sites had\n * line-for-line identical bodies before consolidation, including\n * the inline-regex chain-append bug CodeRabbit caught on PR #196.\n * Single source of truth here keeps the orchestrator and scaffold\n * paths from drifting again.\n */\nexport async function autoRegisterModule(\n modulesDir: string,\n pascal: string,\n plural: string,\n kebab: string,\n style: ModuleStyle = 'define',\n): Promise<void> {\n const indexPath = join(modulesDir, 'index.ts')\n const exists = await fileExists(indexPath)\n const importPath = `./${plural}/${kebab}.module`\n // `defineModule` factories are called at the registration site\n // (`${pascal}Module()`); legacy class modules are passed by reference\n // (`${pascal}Module`). Application's loader discriminates class vs\n // instance at boot, so both forms work — `style` only controls what\n // the orchestrator emits.\n const entryToken = style === 'class' ? `${pascal}Module` : `${pascal}Module()`\n\n if (!exists) {\n // For 'class' style we emit the legacy flat-array form because\n // `defineModules` is the factory-form companion. For 'define' (default)\n // we emit the fluent `defineModules().mount(...)` chain so subsequent\n // `kick g module` invocations append cleanly.\n const initialBody =\n style === 'class'\n ? `import type { AppModuleEntry } from '@forinda/kickjs'\nimport { ${pascal}Module } from '${importPath}'\n\nexport const modules: AppModuleEntry[] = [${entryToken}]\n`\n : `import { defineModules } from '@forinda/kickjs'\nimport { ${pascal}Module } from '${importPath}'\n\nexport const modules = defineModules().mount(${entryToken})\n`\n await writeFileSafe(indexPath, initialBody)\n return\n }\n\n let content = await readFile(indexPath, 'utf-8')\n\n // Two independent checks — a stale comment, a doc snippet, or a\n // partially-edited file shouldn't make the gate skip both halves.\n //\n // 1. Import-line check: look for an exact `import { XModule }\n // from '<importPath>'` statement, not just any `XModule`\n // mention. A comment that names XModule doesn't satisfy this.\n // 2. Registry-entry check: look for `XModule` (word-bounded)\n // inside the actual `export const modules` initializer rhs,\n // not anywhere in the file. Recovers a half-edited registry\n // where the import survived but the .mount entry was deleted.\n const importLine = `import { ${pascal}Module } from '${importPath}'`\n const escapedImportPath = escapeRegex(importPath)\n const importPresentRe = new RegExp(\n `^import\\\\s*\\\\{[^}]*\\\\b${escapeRegex(pascal)}Module\\\\b[^}]*\\\\}\\\\s*from\\\\s*['\"]${escapedImportPath}['\"]`,\n 'm',\n )\n if (!importPresentRe.test(content)) {\n // Insert import after last existing import\n const lastImportIdx = content.lastIndexOf('import ')\n if (lastImportIdx !== -1) {\n const lineEnd = content.indexOf('\\n', lastImportIdx)\n content = content.slice(0, lineEnd + 1) + importLine + '\\n' + content.slice(lineEnd + 1)\n } else {\n content = importLine + '\\n' + content\n }\n }\n\n // Independently confirm the registry rhs already names this entry\n // before skipping the append. Scoped to the `export const modules`\n // slice via `findModulesRhsSpan` so an `XModule` mention in an\n // unrelated builder above the registry doesn't mask a missing\n // `.mount(XModule())` further down.\n const span = findModulesRhsSpan(content)\n if (span) {\n const rhsSlice = content.slice(span.rhsStart, span.rhsEnd + 1)\n const entryPresentRe = new RegExp(`\\\\b${escapeRegex(pascal)}Module\\\\b`)\n if (!entryPresentRe.test(rhsSlice)) {\n content = appendModuleEntry(content, entryToken)\n }\n } else {\n // No recognizable registry shape — fall back to the previous\n // best-effort behaviour: try appending; appendModuleEntry returns\n // content unchanged when it can't find a target.\n content = appendModuleEntry(content, entryToken)\n }\n\n await writeFile(indexPath, content, 'utf-8')\n}\n\n/**\n * Append `entryToken` (e.g. `LionModule()` or bare `LionModule`) to\n * the project's modules registry. Handles two shapes:\n *\n * 1. Flat array literal:\n * `export const modules: AppModuleEntry[] = [HelloModule()]`\n * 2. Fluent factory chain:\n * `export const modules = defineModules().mount(HelloModule())`\n *\n * If neither shape is detected, content is returned unchanged — the\n * adopter's registration site is non-standard and they mount the\n * new module themselves.\n *\n * Exported so `scaffold.ts`'s auto-register can reuse the same\n * balanced-paren scanner — duplicating the regex caused\n * `mount(UserModule())` to corrupt on the scaffold path before this\n * was hoisted out.\n */\nexport function appendModuleEntry(content: string, entryToken: string): string {\n const span = findModulesRhsSpan(content)\n if (!span) return content\n\n // Shape 1 — flat array literal at the rhs.\n if (span.shape === 'array') {\n const inside = content.slice(span.rhsStart + 1, span.rhsEnd)\n const trimmed = inside.trim()\n let rewritten: string\n if (!trimmed) {\n rewritten = `[${entryToken}]`\n } else {\n const needsComma = trimmed.endsWith(',') ? '' : ','\n rewritten = `[${inside.trimEnd()}${needsComma} ${entryToken}]`\n }\n return content.slice(0, span.rhsStart) + rewritten + content.slice(span.rhsEnd + 1)\n }\n\n // Shape 2 — `defineModules()` fluent chain at the rhs. Insert\n // `.mount(...)` right at `chainEnd` (the offset just past the\n // last `.mount(...)`'s closing `)` or just past `defineModules()`'s\n // closing `)` when the chain is empty).\n return `${content.slice(0, span.chainEnd)}\\n .mount(${entryToken})${content.slice(span.chainEnd)}`\n}\n\n/**\n * Span returned by {@link findModulesRhsSpan}. Both shapes carry the\n * `rhsStart` (the offset of `[` for arrays or `d` for the chain) and\n * an `end` offset describing where the rhs concludes:\n *\n * - `array`: `rhsEnd` points at the matching `]` (inclusive).\n * - `chain`: `chainEnd` points just past the last `.mount(...)`\n * call (or just past `defineModules()`'s closing `)` when the\n * chain is empty). For consistency with arrays, `rhsEnd =\n * chainEnd - 1` so a `[rhsStart, rhsEnd + 1)` slice covers the\n * whole initializer.\n */\ntype ModulesRhsSpan =\n | { shape: 'array'; rhsStart: number; rhsEnd: number }\n | { shape: 'chain'; rhsStart: number; rhsEnd: number; chainEnd: number }\n\n/**\n * Locate the `export const modules = <rhs>` initializer in `content`\n * and return the rhs's start/end offsets along with which shape it\n * is (flat array vs `defineModules()` chain). Used by both the\n * append path (`appendModuleEntry`) AND the remove path\n * (`stripChainMount` / array-entry rm regex) so neither mutates\n * unrelated text elsewhere in the file.\n *\n * Returns `null` when no `export const modules` declaration is\n * found, or when its rhs doesn't match either supported shape.\n */\nexport function findModulesRhsSpan(content: string): ModulesRhsSpan | null {\n const declMatch = /export\\s+const\\s+modules\\b[^=]*=/.exec(content)\n if (!declMatch) return null\n const eqEnd = declMatch.index + declMatch[0].length\n let rhsStart = eqEnd\n while (rhsStart < content.length && /\\s/.test(content[rhsStart] ?? '')) rhsStart++\n\n if (content[rhsStart] === '[') {\n const close = balancedBracketClose(content, rhsStart)\n if (close === -1) return null\n return { shape: 'array', rhsStart, rhsEnd: close }\n }\n\n if (content.slice(rhsStart, rhsStart + 'defineModules'.length) === 'defineModules') {\n const chainEnd = findChainEnd(content, rhsStart)\n if (chainEnd === -1) return null\n return { shape: 'chain', rhsStart, rhsEnd: chainEnd - 1, chainEnd }\n }\n\n return null\n}\n\n/**\n * Locate the insertion point after the last `.mount(...)` call in a\n * `defineModules()...` chain, or after `defineModules()` itself when\n * the chain is empty. Returns the source-string offset to insert at,\n * or -1 when the chain isn't found.\n *\n * The scanner walks balanced parens so nested factory calls inside\n * `.mount(X())` don't break boundary detection.\n */\nfunction findChainEnd(src: string, fromIdx = 0): number {\n // Match the CALL site, not the import statement. We require an\n // immediately-following `(` (allowing whitespace) to anchor on\n // `defineModules(...)` and skip past `import { defineModules }`.\n // `fromIdx` lets the caller scope the search to the rhs of an\n // `export const modules =` declaration so unrelated calls\n // elsewhere in the file aren't matched.\n const callRegex = /defineModules\\s*\\(/g\n callRegex.lastIndex = fromIdx\n const match = callRegex.exec(src)\n if (!match) return -1\n // `match.index` points at `d`; `(` is at `match.index + match[0].length - 1`.\n let i = match.index + match[0].length - 1\n if (src[i] !== '(') return -1\n // Balance the args of `defineModules(...)`.\n i = balancedClose(src, i)\n if (i === -1) return -1\n i++\n // Now consume zero or more `.mount(...)` calls. After each, `i`\n // points just past its closing `)` so the next iteration sees a\n // potential `.mount(...)` ahead.\n for (;;) {\n let j = i\n while (j < src.length && /\\s/.test(src[j] ?? '')) j++\n if (src[j] !== '.') break\n if (src.slice(j, j + 6) !== '.mount') break\n j += 6\n while (j < src.length && /\\s/.test(src[j] ?? '')) j++\n if (src[j] !== '(') break\n const close = balancedClose(src, j)\n if (close === -1) break\n i = close + 1\n }\n return i\n}\n\n/**\n * Skip past a `//` line comment or `/* … *\\/` block comment that\n * starts at offset `i`. Returns the offset of the first character\n * after the comment, or `i` unchanged when there's no comment at\n * that position. Used by the balanced-paren / bracket scanners so\n * a `]` or `)` inside a comment doesn't terminate the scan early.\n */\nfunction skipComment(src: string, i: number): number {\n const two = src.slice(i, i + 2)\n if (two === '//') {\n i += 2\n while (i < src.length && src[i] !== '\\n') i++\n return i\n }\n if (two === '/*') {\n i += 2\n while (i + 1 < src.length && !(src[i] === '*' && src[i + 1] === '/')) i++\n return i + 2\n }\n return i\n}\n\n/**\n * Given an offset pointing at `[`, return the offset of its matching\n * `]`. Skips brackets inside string literals so `['weird]name']`\n * doesn't break, and inside `//` / `/* *\\/` comments. Returns -1 on\n * imbalance.\n */\nfunction balancedBracketClose(src: string, openIdx: number): number {\n if (src[openIdx] !== '[') return -1\n let depth = 1\n let i = openIdx + 1\n while (i < src.length) {\n const next = src.slice(i, i + 2)\n if (next === '//' || next === '/*') {\n i = skipComment(src, i)\n continue\n }\n const ch = src[i] ?? ''\n if (ch === \"'\" || ch === '\"' || ch === '`') {\n const quote = ch\n i++\n while (i < src.length && src[i] !== quote) {\n if (src[i] === '\\\\') i++\n i++\n }\n if (i < src.length) i++\n continue\n }\n if (ch === '[') depth++\n else if (ch === ']') {\n depth--\n if (depth === 0) return i\n }\n i++\n }\n return -1\n}\n\n/**\n * Given an offset pointing at `(`, return the offset of its matching\n * `)`, or -1 on imbalance. Skips parens inside string literals\n * (single, double, backtick) and inside `//` / `/* *\\/` comments —\n * a `)` in either would terminate the scan early and corrupt the\n * insertion offset.\n */\nfunction balancedClose(src: string, openIdx: number): number {\n if (src[openIdx] !== '(') return -1\n let depth = 1\n let i = openIdx + 1\n while (i < src.length) {\n const next = src.slice(i, i + 2)\n if (next === '//' || next === '/*') {\n i = skipComment(src, i)\n continue\n }\n const ch = src[i] ?? ''\n if (ch === \"'\" || ch === '\"' || ch === '`') {\n // Skip string literal — find matching unescaped quote.\n const quote = ch\n i++\n while (i < src.length && src[i] !== quote) {\n if (src[i] === '\\\\') i++\n i++\n }\n if (i < src.length) i++\n continue\n }\n if (ch === '(') depth++\n else if (ch === ')') {\n depth--\n if (depth === 0) return i\n }\n i++\n }\n return -1\n}\n","import { join } from 'node:path'\nimport { writeFileSafe } from '../utils/fs'\nimport { toPascalCase, toKebabCase } from '../utils/naming'\n\ninterface GenerateAdapterOptions {\n name: string\n outDir: string\n}\n\n/**\n * Scaffold a `defineAdapter()` factory under `src/adapters/<name>.adapter.ts`.\n *\n * v4 dropped the `class implements AppAdapter` pattern in favour of the\n * `defineAdapter()` factory (architecture.md §21.3.4). The generated\n * template uses the new factory shape so adopters get a working\n * adapter with all four lifecycle hooks (beforeMount, beforeStart,\n * afterStart, shutdown), a typed config object with defaults, and the\n * factory's call / `.scoped()` / `.async()` surfaces — without\n * writing a single class.\n */\nexport async function generateAdapter(options: GenerateAdapterOptions): Promise<string[]> {\n const { name, outDir } = options\n const kebab = toKebabCase(name)\n const pascal = toPascalCase(name)\n const files: string[] = []\n\n const filePath = join(outDir, `${kebab}.adapter.ts`)\n await writeFileSafe(\n filePath,\n `import {\n defineAdapter,\n type AdapterContext,\n type AdapterMiddleware,\n type ContributorRegistrations,\n type Constructor,\n} from '@forinda/kickjs'\n\n/**\n * Configuration for the ${pascal} adapter.\n *\n * Adapters typically take a small config object so callers can tune\n * behaviour at bootstrap time. Keep the shape narrow — anything\n * derived from the environment should be read inside the build\n * function via getEnv(), not forced onto the caller.\n */\nexport interface ${pascal}AdapterConfig {\n // Add your adapter configuration here, e.g.:\n // enabled?: boolean\n // apiKey?: string\n}\n\n/**\n * ${pascal} adapter — built via \\`defineAdapter()\\` so callers get the\n * factory's call / \\`.scoped()\\` / \\`.async()\\` surfaces for free.\n *\n * Hooks into the Application lifecycle to add middleware, routes,\n * Context Contributors, or external service connections.\n *\n * Every lifecycle hook below is OPTIONAL. The scaffold emits all of\n * them so adopters can browse what's available and delete what they\n * don't need — \\`build()\\` returning \\`{}\\` is also valid for an adapter\n * that only contributes config defaults.\n *\n * @example\n * \\`\\`\\`ts\n * import { bootstrap } from '@forinda/kickjs'\n * import { ${pascal}Adapter } from './adapters/${kebab}.adapter'\n *\n * bootstrap({\n * modules,\n * adapters: [${pascal}Adapter({ /* config overrides *\\\\/ })],\n * })\n * \\`\\`\\`\n */\nexport const ${pascal}Adapter = defineAdapter<${pascal}AdapterConfig>({\n name: '${pascal}Adapter',\n defaults: {\n // Default config values go here. The adopter's overrides shallow-merge\n // on top of these before \\`build()\\` runs.\n },\n build: (_config, { name: _name }) => {\n // Closures inside \\`build()\\` are how each adapter instance owns its\n // own state (database client, Map, timer handle, …). The same\n // \\`_config\\` is visible to every hook below.\n\n return {\n /**\n * Express middleware entries the Application mounts at named phases.\n *\n * \\`phase\\` controls where each handler sits in the pipeline:\n * 'beforeGlobal' | 'afterGlobal' | 'beforeRoutes' | 'afterRoutes'.\n *\n * \\`path\\` (optional) scopes the entry to a path prefix.\n *\n * Delete this hook entirely if you don't add middleware.\n */\n middleware(): AdapterMiddleware[] {\n return [\n // Example: add a custom header to all responses\n // {\n // phase: 'beforeGlobal',\n // handler: (_req, res, next) => {\n // res.setHeader('X-${pascal}', 'true')\n // next()\n // },\n // },\n // Example: scope a rate limiter to one path prefix\n // {\n // phase: 'beforeRoutes',\n // path: '/api/v1/auth',\n // handler: rateLimit({ max: 10 }),\n // },\n ]\n },\n\n /**\n * Runs BEFORE global middleware. Mount routes that should bypass the\n * middleware stack — health checks, docs UI, static assets, OAuth\n * callbacks. Anything you want reachable even if a global middleware\n * later in the chain rejects requests.\n *\n * Delete this hook if you have no early routes.\n */\n beforeMount(_ctx: AdapterContext): void {\n // Example:\n // _ctx.app.get('/${kebab}/status', (_req, res) => res.json({ status: 'ok' }))\n },\n\n /**\n * Fires once per controller class as the router mounts. Use this to\n * collect route metadata for OpenAPI specs, dependency graphs, route\n * inventories, devtools dashboards.\n *\n * Delete this hook unless your adapter introspects the route registry.\n */\n onRouteMount(_controllerClass: Constructor, _mountPath: string): void {\n // Example (Swagger-style): collect routes for the spec.\n // openApiSpec.addController(_controllerClass, _mountPath)\n },\n\n /**\n * Runs AFTER modules + routes are wired, BEFORE the server starts.\n * Right place for late-stage DI registrations or final config validation.\n *\n * Delete this hook if there's nothing to wire post-modules.\n */\n beforeStart(_ctx: AdapterContext): void {\n // Example: _ctx.container.registerInstance(MY_TOKEN, new MyService(_config))\n },\n\n /**\n * Runs AFTER the HTTP server is listening. The raw \\`http.Server\\` is\n * available on \\`ctx.server\\` — attach upgrade handlers (Socket.IO,\n * gRPC, GraphQL subscriptions), warm caches, log a banner.\n *\n * Delete this hook if you don't need the running server reference.\n */\n afterStart(_ctx: AdapterContext): void {\n // Example: const io = new Server(_ctx.server)\n },\n\n /**\n * Returns Context Contributors to merge into every route's pipeline\n * at the \\`'adapter'\\` precedence level. Per-route handlers can\n * override the value at the method / class / module level.\n *\n * Delete this hook unless your adapter ships typed per-request values\n * (auth user, tenant, locale, feature flags, geo, etc).\n */\n contributors(): ContributorRegistrations {\n return [\n // Example:\n // import { defineHttpContextDecorator } from '@forinda/kickjs'\n // declare module '@forinda/kickjs' { interface ContextMeta { ${kebab}: { id: string } } }\n // const Load${pascal} = defineHttpContextDecorator({\n // key: '${kebab}',\n // resolve: (ctx) => ({ id: ctx.req.headers['x-${kebab}-id'] as string }),\n // })\n // return [Load${pascal}.registration]\n ]\n },\n\n /**\n * Runs on graceful shutdown (SIGINT/SIGTERM). Clean up long-lived\n * resources the adapter owns: close connections, flush buffers,\n * cancel timers. The framework runs every adapter's \\`shutdown\\`\n * concurrently via \\`Promise.allSettled\\` — one failure won't block\n * sibling adapters.\n *\n * Delete this hook if your adapter holds no resources.\n */\n async shutdown(): Promise<void> {\n // Example: await this.pool.end()\n // Example: clearInterval(this.heartbeatTimer)\n },\n }\n },\n})\n`,\n )\n files.push(filePath)\n\n return files\n}\n","import { resolve, join } from 'node:path'\nimport { pluralize, toKebabCase } from './naming'\nimport type { ProjectPattern } from '../config'\n\n/**\n * Flat folder mapping — the layout for both `rest` and `minimal`.\n * Files live at the module root or in shallow subdirectories.\n */\nconst FLAT_FOLDER_MAP: Record<string, string> = {\n controller: '',\n service: '',\n dto: 'dtos',\n guard: 'guards',\n middleware: 'middleware',\n contributor: 'contributors',\n}\n\nexport interface ResolveOutDirOptions {\n /** The artifact type (controller, service, dto, guard, middleware) */\n type: string\n /** Explicit -o / --out dir from CLI flag (takes highest priority) */\n outDir?: string\n /** Module name from --module flag */\n moduleName?: string\n /** Modules directory (from config or default) */\n modulesDir?: string\n /** Standalone default directory when no --module is used (e.g. 'src/controllers') */\n defaultDir: string\n /** Project pattern — determines folder structure inside modules */\n pattern?: ProjectPattern\n /** Whether to pluralize the module folder name (default: true) */\n shouldPluralize?: boolean\n}\n\n/**\n * Resolve the output directory for a generator artifact.\n *\n * Priority:\n * 1. Explicit --out flag (always wins)\n * 2. --module flag → maps into module's folder (DDD or flat based on pattern)\n * 3. Standalone default directory\n */\nexport function resolveOutDir(options: ResolveOutDirOptions): string {\n const {\n type,\n outDir,\n moduleName,\n modulesDir = 'src/modules',\n defaultDir,\n shouldPluralize = true,\n } = options\n\n // Explicit --out always wins\n if (outDir) return resolve(outDir)\n\n // Module-scoped: place inside the module's folder. All patterns\n // (rest/minimal) share the flat folder layout.\n if (moduleName) {\n const folderMap = FLAT_FOLDER_MAP\n const kebab = toKebabCase(moduleName)\n const folder = shouldPluralize ? pluralize(kebab) : kebab\n const subfolder = folderMap[type] ?? ''\n const base = join(modulesDir, folder)\n return resolve(subfolder ? join(base, subfolder) : base)\n }\n\n // Standalone default\n return resolve(defaultDir)\n}\n","import { join } from 'node:path'\nimport { writeFileSafe } from '../utils/fs'\nimport { toPascalCase, toKebabCase, toCamelCase } from '../utils/naming'\nimport { resolveOutDir } from '../utils/resolve-out-dir'\nimport type { ProjectPattern } from '../config'\n\ninterface GenerateMiddlewareOptions {\n name: string\n outDir?: string\n moduleName?: string\n modulesDir?: string\n pattern?: ProjectPattern\n pluralize?: boolean\n}\n\nexport async function generateMiddleware(options: GenerateMiddlewareOptions): Promise<string[]> {\n const { name, moduleName, modulesDir, pattern } = options\n const outDir = resolveOutDir({\n type: 'middleware',\n outDir: options.outDir,\n moduleName,\n modulesDir,\n defaultDir: 'src/middleware',\n pattern,\n shouldPluralize: options.pluralize ?? true,\n })\n const kebab = toKebabCase(name)\n const camel = toCamelCase(name)\n const files: string[] = []\n\n const filePath = join(outDir, `${kebab}.middleware.ts`)\n await writeFileSafe(\n filePath,\n `import type { Request, Response, NextFunction } from 'express'\n\nexport interface ${toPascalCase(name)}Options {\n // Add configuration options here. The factory below closes over the\n // resolved options object; pass them at the call site —\n // \\`${camel}({ foo: 'bar' })\\` — and the closure preserves them across\n // every request.\n}\n\n/**\n * ${toPascalCase(name)} middleware.\n *\n * Usage in bootstrap (fires on every request):\n * middleware: [${camel}()]\n *\n * Usage with adapter — phase controls *when* the handler runs:\n *\n * middleware() {\n * return [{ handler: ${camel}(), phase: 'afterGlobal' }]\n * }\n *\n * Phase semantics (see \\`MiddlewarePhase\\` JSDoc for the full contract):\n * - 'beforeGlobal' / 'afterGlobal' / 'beforeRoutes' — fire on every\n * request, before module routes run.\n * - 'afterRoutes' — fires ONLY when no route matched (404 fall-through)\n * OR a route handler called \\`next()\\` without ending the response.\n * Controllers that call \\`ctx.json(…)\\` end the chain and skip this\n * phase. For per-response work (logging, metrics) attach to\n * \\`res.on('finish', …)\\` from an earlier-phase middleware instead.\n *\n * Optional path scope — string, RegExp, or array of either:\n * middleware() {\n * return [{\n * handler: ${camel}({ region: 'eu' }),\n * phase: 'afterGlobal',\n * path: ['/api', /^\\\\/admin/],\n * }]\n * }\n *\n * Usage with @Middleware decorator:\n * @Middleware(${camel}())\n */\nexport function ${camel}(options: ${toPascalCase(name)}Options = {}) {\n return (req: Request, res: Response, next: NextFunction) => {\n // Implement your middleware logic here. \\`options\\` is captured by\n // closure — log or read it anywhere in this handler body.\n void options\n next()\n }\n}\n`,\n )\n files.push(filePath)\n\n return files\n}\n","import { join } from 'node:path'\nimport { writeFileSafe } from '../utils/fs'\nimport { toPascalCase, toKebabCase, toCamelCase } from '../utils/naming'\nimport { resolveOutDir } from '../utils/resolve-out-dir'\nimport type { ProjectPattern } from '../config'\n\ninterface GenerateGuardOptions {\n name: string\n outDir?: string\n moduleName?: string\n modulesDir?: string\n pattern?: ProjectPattern\n pluralize?: boolean\n}\n\nexport async function generateGuard(options: GenerateGuardOptions): Promise<string[]> {\n const { name, moduleName, modulesDir, pattern } = options\n const outDir = resolveOutDir({\n type: 'guard',\n outDir: options.outDir,\n moduleName,\n modulesDir,\n defaultDir: 'src/guards',\n pattern,\n shouldPluralize: options.pluralize ?? true,\n })\n const kebab = toKebabCase(name)\n const camel = toCamelCase(name)\n const pascal = toPascalCase(name)\n const files: string[] = []\n\n const filePath = join(outDir, `${kebab}.guard.ts`)\n await writeFileSafe(\n filePath,\n `import { Container, HttpException } from '@forinda/kickjs'\nimport type { RequestContext } from '@forinda/kickjs'\n\n/**\n * ${pascal} guard.\n *\n * Guards protect routes by checking conditions before the handler runs.\n * Return early with an error response to block access.\n *\n * Usage:\n * @Middleware(${camel}Guard)\n * @Get('/protected')\n * async handler(ctx: RequestContext) { ... }\n */\nexport async function ${camel}Guard(ctx: RequestContext, next: () => void): Promise<void> {\n // Example: check for an authorization header\n const header = ctx.headers.authorization\n if (!header?.startsWith('Bearer ')) {\n ctx.res.status(401).json({ message: 'Missing or invalid authorization header' })\n return\n }\n\n const token = header.slice(7)\n\n try {\n // Verify the token using a service from the DI container\n // const container = Container.getInstance()\n // const authService = container.resolve(AuthService)\n // const payload = authService.verifyToken(token)\n // ctx.set('auth', payload)\n\n next()\n } catch {\n ctx.res.status(401).json({ message: 'Invalid or expired token' })\n }\n}\n`,\n )\n files.push(filePath)\n\n return files\n}\n","import { join } from 'node:path'\nimport { writeFileSafe } from '../utils/fs'\nimport { toPascalCase, toKebabCase } from '../utils/naming'\nimport { resolveOutDir } from '../utils/resolve-out-dir'\nimport type { ProjectPattern } from '../config'\n\ninterface GenerateServiceOptions {\n name: string\n outDir?: string\n moduleName?: string\n modulesDir?: string\n pattern?: ProjectPattern\n pluralize?: boolean\n}\n\nexport async function generateService(options: GenerateServiceOptions): Promise<string[]> {\n const { name, moduleName, modulesDir, pattern } = options\n const outDir = resolveOutDir({\n type: 'service',\n outDir: options.outDir,\n moduleName,\n modulesDir,\n defaultDir: 'src/services',\n pattern,\n shouldPluralize: options.pluralize ?? true,\n })\n const kebab = toKebabCase(name)\n const pascal = toPascalCase(name)\n const files: string[] = []\n\n const filePath = join(outDir, `${kebab}.service.ts`)\n await writeFileSafe(\n filePath,\n `import { Service } from '@forinda/kickjs'\n\n@Service()\nexport class ${pascal}Service {\n // Inject dependencies via constructor\n // constructor(\n // @Inject(MY_REPO) private readonly repo: IMyRepository,\n // ) {}\n}\n`,\n )\n files.push(filePath)\n\n return files\n}\n","import { join } from 'node:path'\nimport { writeFileSafe } from '../utils/fs'\nimport { toPascalCase, toKebabCase } from '../utils/naming'\nimport { resolveOutDir } from '../utils/resolve-out-dir'\nimport type { ProjectPattern } from '../config'\n\ninterface GenerateControllerOptions {\n name: string\n outDir?: string\n moduleName?: string\n modulesDir?: string\n pattern?: ProjectPattern\n pluralize?: boolean\n}\n\nexport async function generateController(options: GenerateControllerOptions): Promise<string[]> {\n const { name, moduleName, modulesDir, pattern } = options\n const outDir = resolveOutDir({\n type: 'controller',\n outDir: options.outDir,\n moduleName,\n modulesDir,\n defaultDir: 'src/controllers',\n pattern,\n shouldPluralize: options.pluralize ?? true,\n })\n const kebab = toKebabCase(name)\n const pascal = toPascalCase(name)\n const files: string[] = []\n\n const filePath = join(outDir, `${kebab}.controller.ts`)\n await writeFileSafe(\n filePath,\n `import { Controller, Get, Post, type Ctx } from '@forinda/kickjs'\n\n// \\`Ctx<KickRoutes.${pascal}Controller['<method>']>\\` is generated by\n// \\`kick typegen\\` (auto-run on \\`kick dev\\`). After the first run, your IDE\n// will autocomplete \\`ctx.params\\`, \\`ctx.body\\`, and \\`ctx.query\\`.\n// See https://forinda.github.io/kick-js/guide/typegen for details.\n\n@Controller()\nexport class ${pascal}Controller {\n // @Autowired() private readonly myService!: MyService\n\n @Get('/')\n async list(ctx: Ctx<KickRoutes.${pascal}Controller['list']>) {\n ctx.json({ message: '${pascal} list' })\n }\n\n @Post('/')\n async create(ctx: Ctx<KickRoutes.${pascal}Controller['create']>) {\n ctx.created({ message: '${pascal} created', data: ctx.body })\n }\n}\n`,\n )\n files.push(filePath)\n\n return files\n}\n","import { join } from 'node:path'\nimport { writeFileSafe } from '../utils/fs'\nimport { toPascalCase, toKebabCase, toCamelCase } from '../utils/naming'\nimport { resolveOutDir } from '../utils/resolve-out-dir'\nimport type { ProjectPattern } from '../config'\n\ninterface GenerateDtoOptions {\n name: string\n outDir?: string\n moduleName?: string\n modulesDir?: string\n pattern?: ProjectPattern\n pluralize?: boolean\n}\n\nexport async function generateDto(options: GenerateDtoOptions): Promise<string[]> {\n const { name, moduleName, modulesDir, pattern } = options\n const outDir = resolveOutDir({\n type: 'dto',\n outDir: options.outDir,\n moduleName,\n modulesDir,\n defaultDir: 'src/dtos',\n pattern,\n shouldPluralize: options.pluralize ?? true,\n })\n const kebab = toKebabCase(name)\n const pascal = toPascalCase(name)\n const camel = toCamelCase(name)\n const files: string[] = []\n\n const filePath = join(outDir, `${kebab}.dto.ts`)\n await writeFileSafe(\n filePath,\n `import { z } from 'zod'\n\nexport const ${camel}Schema = z.object({\n // Define your schema fields here\n name: z.string().min(1).max(200),\n})\n\nexport type ${pascal}DTO = z.infer<typeof ${camel}Schema>\n`,\n )\n files.push(filePath)\n\n return files\n}\n","type ProjectTemplate = 'rest' | 'minimal'\n\n/**\n * Supported schema libraries — passed through to `fromZod` /\n * `fromValibot` / `fromYup` in the generated env file. `zod` is the\n * default for `--yes` because it has the deepest ecosystem\n * compatibility (OpenAPI generation, Standard Schema brand for\n * `kick typegen`).\n */\nexport type SchemaLib = 'zod' | 'valibot' | 'yup'\n\n/** Map of optional package names to their npm package identifiers */\nconst PACKAGE_DEPS: Record<string, string> = {\n swagger: '@forinda/kickjs-swagger',\n ws: '@forinda/kickjs-ws',\n queue: '@forinda/kickjs-queue',\n devtools: '@forinda/kickjs-devtools',\n}\n\n/** Schema-lib runtime dependency ranges. Pinned to a recent release. */\nconst SCHEMA_LIB_DEPS: Record<SchemaLib, { name: string; range: string }> = {\n zod: { name: 'zod', range: '^4.3.6' },\n valibot: { name: 'valibot', range: '^1.4.1' },\n yup: { name: 'yup', range: '^1.7.1' },\n}\n\n/**\n * Map of package name → semver range string (`^x.y.z`). Resolved\n * from `npm view <name> version` upstream so per-package independent\n * versioning is honoured at scaffold time. Every sibling\n * `@forinda/kickjs-*` package we might add to the new project must\n * appear here; missing keys throw during package.json generation\n * (loud failure beats silently shipping `^undefined`).\n */\nexport type SiblingVersions = Record<string, string>\n\nfunction take(versions: SiblingVersions, name: string): string {\n const v = versions[name]\n if (!v) {\n throw new Error(\n `generatePackageJson: missing resolved version for ${name}. ` +\n `Add it to SIBLING_PACKAGES in generators/project.ts.`,\n )\n }\n return v\n}\n\n/** Generate package.json with template-aware dependencies */\nexport function generatePackageJson(\n name: string,\n template: ProjectTemplate,\n versions: SiblingVersions,\n packages: string[] = [],\n schemaLib: SchemaLib = 'zod',\n runtime: 'express' | 'fastify' | 'h3' = 'express',\n): string {\n const schemaDep = SCHEMA_LIB_DEPS[schemaLib]\n const baseDeps: Record<string, string> = {\n '@forinda/kickjs': take(versions, '@forinda/kickjs'),\n // The schema-agnostic abstraction kickjs-schema wraps zod / valibot\n // / yup behind a single `KickSchema` interface — env validation,\n // body validation, and swagger spec generation all flow through\n // `detectSchema()`. Shipping it as a direct dep (rather than a peer)\n // keeps the new-project install one-step.\n '@forinda/kickjs-schema': take(versions, '@forinda/kickjs-schema'),\n // `dotenv` is an optional peer of @forinda/kickjs — scaffolded apps\n // get it pre-installed so `.env` files Just Work. Apps that load\n // env from the shell or a secret manager can drop this safely.\n dotenv: '^17.3.1',\n // `express` stays installed for every runtime: it's the default engine,\n // and the Fastify / h3 runtimes use `express.static` for `serveStatic`.\n express: '^5.1.0',\n 'reflect-metadata': '^0.2.2',\n [schemaDep.name]: schemaDep.range,\n }\n\n // Engine peers for the chosen runtime (optional peers of @forinda/kickjs).\n if (runtime === 'fastify') {\n baseDeps.fastify = '^5.0.0'\n baseDeps['@fastify/middie'] = '^9.0.0'\n } else if (runtime === 'h3') {\n baseDeps.h3 = '^1.0.0'\n }\n\n // Add user-selected optional packages — each looked up against\n // the resolved version map so they're independently up-to-date.\n for (const pkg of packages) {\n const dep = PACKAGE_DEPS[pkg]\n if (dep && !baseDeps[dep]) {\n baseDeps[dep] = take(versions, dep)\n }\n }\n\n return JSON.stringify(\n {\n name,\n // Project starts at 0.0.0 — adopters bump as they ship. Tying\n // the project version to the CLI version (the previous\n // behaviour) made every scaffolded app `5.4.0` on day one,\n // which broke npm publishing for adopters trying their first\n // release.\n version: '0.0.0',\n type: 'module',\n scripts: {\n // `kick dev` (not bare `vite`): it boots Vite itself AND owns the\n // typegen-on-save watcher. Plain `vite` gives working HMR but\n // frozen `.kickjs/types` — new routes silently lose their typing\n // until a manual `kick typegen`.\n dev: 'kick dev',\n 'dev:debug': 'kick dev:debug',\n build: 'kick build',\n start: 'kick start',\n test: 'vitest run',\n 'test:watch': 'vitest',\n typecheck: 'tsc --noEmit',\n typegen: 'kick typegen',\n lint: 'eslint src/',\n format: 'prettier --write src/',\n },\n dependencies: baseDeps,\n devDependencies: {\n '@forinda/kickjs-cli': take(versions, '@forinda/kickjs-cli'),\n '@forinda/kickjs-vite': take(versions, '@forinda/kickjs-vite'),\n '@swc/core': '^1.15.21',\n '@types/express': '^5.0.6',\n '@types/node': '^25.0.0',\n 'unplugin-swc': '^1.5.9',\n vite: '^8.0.3',\n vitest: '^4.1.2',\n typescript: '^6.0.3',\n prettier: '^3.8.1',\n },\n },\n null,\n 2,\n )\n}\n\n/**\n * Generate vite.config.ts with the KickJS Vite plugin.\n *\n * The plugin handles:\n * - SSR environment setup for backend Node.js code\n * - Virtual module generation (virtual:kickjs/app)\n * - Module auto-discovery (scans *.module.ts files)\n * - HMR with selective container invalidation\n * - Express mounting via configureServer() post-hook\n * - httpServer piping to adapters (WsAdapter, Socket.IO, etc.)\n */\nexport function generateViteConfig(): string {\n return `import { defineConfig } from 'vite'\nimport { resolve } from 'node:path'\nimport swc from 'unplugin-swc'\nimport { kickjsVitePlugin, envWatchPlugin } from '@forinda/kickjs-vite'\n\nexport default defineConfig({\n oxc: false,\n plugins: [\n swc.vite(),\n kickjsVitePlugin({ entry: 'src/index.ts' }),\n // Watches .env files and triggers a full reload on change so the\n // dev server picks up env tweaks without a manual restart.\n envWatchPlugin(),\n ],\n resolve: {\n alias: {\n '@': resolve(__dirname, 'src'),\n },\n },\n build: {\n target: 'node20',\n ssr: true,\n outDir: 'dist',\n sourcemap: true,\n rollupOptions: {\n input: resolve(__dirname, 'src/index.ts'),\n output: { format: 'esm' },\n },\n },\n})\n`\n}\n\n/** Generate tsconfig.json with decorator support */\nexport function generateTsConfig(): string {\n return JSON.stringify(\n {\n compilerOptions: {\n target: 'ES2022',\n module: 'ESNext',\n moduleResolution: 'bundler',\n lib: ['ES2022'],\n types: ['node', 'vite/client'],\n strict: true,\n esModuleInterop: true,\n skipLibCheck: true,\n sourceMap: true,\n declaration: true,\n experimentalDecorators: true,\n emitDecoratorMetadata: true,\n outDir: 'dist',\n // rootDir omitted so .kickjs/types/*.d.ts can sit outside src/\n paths: { '@/*': ['./src/*'] },\n },\n // .kickjs/types is generated by `kick typegen` and refreshed\n // automatically on `kick dev`. Including it here makes\n // `container.resolve()` and module discovery type-safe.\n // Both .d.ts and .ts are matched: registry/services/modules are\n // declarations, but routes.ts holds resolvable imports from your\n // controllers' Zod schemas (TS silently degrades inline `import('...')`\n // inside `.d.ts` files under `moduleResolution: 'bundler'`).\n include: ['src', '.kickjs/types/**/*.d.ts', '.kickjs/types/**/*.ts'],\n },\n null,\n 2,\n )\n}\n\n/** Generate .prettierrc with project formatting rules */\nexport function generatePrettierConfig(): string {\n return JSON.stringify(\n {\n semi: false,\n singleQuote: true,\n trailingComma: 'all',\n printWidth: 100,\n tabWidth: 2,\n },\n null,\n 2,\n )\n}\n\n/** Generate .editorconfig for consistent editor settings */\nexport function generateEditorConfig(): string {\n return `# https://editorconfig.org\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.md]\ntrim_trailing_whitespace = false\n`\n}\n\n/** Generate .gitignore with common Node.js patterns */\nexport function generateGitIgnore(): string {\n return `node_modules/\ndist/\n.env\ncoverage/\n.DS_Store\n*.tsbuildinfo\n.kickjs/\n`\n}\n\n/** Generate .gitattributes for consistent line endings */\nexport function generateGitAttributes(): string {\n return `# Auto-detect text files and normalise line endings to LF\n* text=auto eol=lf\n\n# Explicitly mark generated / binary files\n*.png binary\n*.jpg binary\n*.jpeg binary\n*.gif binary\n*.ico binary\n*.woff binary\n*.woff2 binary\n*.ttf binary\n*.eot binary\n\n# Lock files — treat as generated\npnpm-lock.yaml -diff linguist-generated\nyarn.lock -diff linguist-generated\npackage-lock.json -diff linguist-generated\n`\n}\n\n/** Generate .env file with default environment variables */\nexport function generateEnv(): string {\n return `PORT=3000\nNODE_ENV=development\n`\n}\n\n/** Generate .env.example file as a template */\nexport function generateEnvExample(): string {\n return `PORT=3000\nNODE_ENV=development\n`\n}\n\n/** Generate vitest.config.ts for test configuration */\nexport function generateVitestConfig(): string {\n return `import { defineConfig } from 'vitest/config'\nimport swc from 'unplugin-swc'\n\nexport default defineConfig({\n plugins: [swc.vite()],\n test: {\n globals: true,\n environment: 'node',\n include: ['src/**/*.test.ts'],\n },\n})\n`\n}\n","import { join, dirname } from 'node:path'\nimport { execFileSync, execSync } from 'node:child_process'\nimport { readFileSync } from 'node:fs'\nimport { fileURLToPath } from 'node:url'\nimport { writeFileSafe } from '../utils/fs'\nimport {\n generatePackageJson,\n generateViteConfig,\n generateTsConfig,\n generatePrettierConfig,\n generateEditorConfig,\n generateGitIgnore,\n generateGitAttributes,\n generateEnv,\n generateEnvExample,\n generateVitestConfig,\n} from './templates/project-config'\nimport {\n generateEntryFile,\n generateEnvFile,\n generateModulesIndex,\n generateKickConfig,\n generateHelloService,\n generateHelloController,\n generateHelloModule,\n} from './templates/project-app'\nimport { generateReadme } from './templates/project-docs'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\nconst cliPkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'))\nconst CLI_VERSION_FALLBACK = `^${cliPkg.version}`\n\n/**\n * Sibling `@forinda/kickjs-*` packages whose versions are resolved\n * independently when scaffolding a new project. Each entry is queried\n * via `npm view <name> version`; failure falls back to the CLI's own\n * version (`CLI_VERSION_FALLBACK`).\n *\n * Per-package independent versioning landed with changesets — before\n * that, every sibling shipped in lockstep with the CLI so a single\n * pin was correct. Now `@forinda/kickjs@5.5.0` may pair with\n * `@forinda/kickjs-cli@5.4.2` and `@forinda/kickjs-swagger@5.3.1`;\n * pinning them all to the CLI's version under-installs adopters.\n */\nconst SIBLING_PACKAGES = [\n '@forinda/kickjs',\n '@forinda/kickjs-cli',\n '@forinda/kickjs-schema',\n '@forinda/kickjs-vite',\n '@forinda/kickjs-swagger',\n '@forinda/kickjs-ws',\n '@forinda/kickjs-queue',\n '@forinda/kickjs-devtools',\n '@forinda/kickjs-testing',\n] as const\n\n/**\n * Resolve the latest published version of every sibling package via\n * `npm view <name> version` (via execFileSync — no shell, no\n * injection vector). Each query has a short timeout; failures fall\n * back to the CLI's own version with a `^` prefix so the scaffold\n * stays usable offline.\n */\nasync function resolveSiblingVersions(): Promise<Record<string, string>> {\n const results = await Promise.all(\n SIBLING_PACKAGES.map(async (name) => {\n try {\n const out = execFileSync('npm', ['view', name, 'version'], {\n encoding: 'utf-8',\n timeout: 5000,\n stdio: ['ignore', 'pipe', 'ignore'],\n })\n .toString()\n .trim()\n if (out && /^\\d+\\.\\d+\\.\\d+/.test(out)) {\n return [name, `^${out}`] as const\n }\n } catch {\n // Network failure / package not yet published / npm\n // unavailable. Fall back to CLI version below.\n }\n return [name, CLI_VERSION_FALLBACK] as const\n }),\n )\n return Object.fromEntries(results)\n}\n\n/**\n * Resolve the exact published version of a package at a given dist-tag\n * (`npm view <name>@<tag> version`). Returns `null` on any failure. Used\n * to pin `@forinda/kickjs` to the `alpha` channel when scaffolding a\n * Fastify / h3 app — the engine subpaths (`@forinda/kickjs/fastify`,\n * `/h3`) ship only on the alpha until the runtimes land in a stable\n * release, so the default `latest` resolution would install a kickjs\n * that doesn't export them (→ Vite \"./h3 is not exported\" at boot).\n * Prerelease versions are pinned exactly (no `^`) — a caret range over a\n * prerelease has surprising semver semantics.\n */\nfunction resolveExactVersionAtTag(name: string, tag: string): string | null {\n try {\n const out = execFileSync('npm', ['view', `${name}@${tag}`, 'version'], {\n encoding: 'utf-8',\n timeout: 5000,\n stdio: ['ignore', 'pipe', 'ignore'],\n })\n .toString()\n .trim()\n return out && /^\\d+\\.\\d+\\.\\d+/.test(out) ? out : null\n } catch {\n return null\n }\n}\n\n/**\n * Whether the package at a given dist-tag exports a subpath (e.g. `./h3`).\n * Reads the `exports` map via `npm view <name>@<tag> exports --json`. Used to\n * gate the alpha-pin: if `latest` already ships the engine subpath, the runtime\n * has graduated to stable and we should NOT downgrade to an older alpha.\n * Returns `false` on any failure (missing field / network / unparseable) so the\n * caller treats \"unknown\" as \"not present\" and falls through to the alpha path.\n */\n/** Strip a leading range operator (`^1.2.3` / `~1.2.3` → `1.2.3`). */\nfunction stripRange(range: string | undefined): string {\n return (range ?? '').replace(/^[\\^~>=<\\s]+/, '')\n}\n\n/**\n * Compare the release cores (major.minor.patch, ignoring any `-prerelease`\n * suffix) of two versions: is `a` >= `b`? Used to guard the alpha-pin so a\n * package is never downgraded onto a stale prerelease whose stable line has\n * already moved past it. A coarse compare is enough here — we only need\n * \"is this alpha at least as new as the stable we'd otherwise install\".\n */\nfunction baseVersionGte(a: string, b: string): boolean {\n const core = (v: string): number[] =>\n stripRange(v)\n .split('-')[0]!\n .split('.')\n .map((n) => Number.parseInt(n, 10) || 0)\n const [a0 = 0, a1 = 0, a2 = 0] = core(a)\n const [b0 = 0, b1 = 0, b2 = 0] = core(b)\n if (a0 !== b0) return a0 > b0\n if (a1 !== b1) return a1 > b1\n return a2 >= b2\n}\n\nfunction tagExportsSubpath(name: string, tag: string, subpath: string): boolean {\n try {\n const out = execFileSync('npm', ['view', `${name}@${tag}`, 'exports', '--json'], {\n encoding: 'utf-8',\n timeout: 5000,\n stdio: ['ignore', 'pipe', 'ignore'],\n })\n .toString()\n .trim()\n if (!out) return false\n const exportsMap = JSON.parse(out) as Record<string, unknown>\n return Object.prototype.hasOwnProperty.call(exportsMap, subpath)\n } catch {\n return false\n }\n}\n\ntype ProjectTemplate = 'rest' | 'minimal'\ntype SchemaLib = 'zod' | 'valibot' | 'yup'\n\ninterface InitProjectOptions {\n name: string\n directory: string\n packageManager?: 'pnpm' | 'npm' | 'yarn' | 'bun'\n initGit?: boolean\n installDeps?: boolean\n template?: ProjectTemplate\n defaultRepo?: string\n packages?: string[]\n /** Schema library to scaffold env / DTOs with. Defaults to `zod`. */\n schemaLib?: SchemaLib\n /** HTTP engine to scaffold. Defaults to `express`. */\n runtime?: 'express' | 'fastify' | 'h3'\n}\n\n/** Scaffold a new KickJS project */\nexport async function initProject(options: InitProjectOptions): Promise<void> {\n const {\n name,\n directory,\n packageManager = 'pnpm',\n template = 'rest',\n defaultRepo = 'inmemory',\n packages = [],\n schemaLib = 'zod',\n runtime = 'express',\n } = options\n const dir = directory\n\n const log = (msg: string) => console.log(` ${msg}`)\n\n console.log(`\\n Creating KickJS project: ${name}\\n`)\n\n // Resolve published version of every sibling kickjs package in\n // parallel. Per-package independent versioning means\n // `@forinda/kickjs@5.5.0` may pair with `@forinda/kickjs-cli@5.4.2`\n // and `@forinda/kickjs-swagger@5.3.1`; pinning every dep to the\n // CLI's own version under-installs adopters whenever a sibling\n // bumps independently. `npm view` fallback keeps the scaffold\n // working offline.\n log('Resolving package versions...')\n const versions = await resolveSiblingVersions()\n\n // The pluggable-runtimes work (Fastify / h3 engine subpaths, the\n // `kick/runtime` typegen, `kick add upload`, `kick doctor` runtime checks)\n // ships only on the `alpha` channel until it lands in a stable release. So a\n // non-Express scaffold needs the alpha of every package that carries runtime\n // behavior, not just `@forinda/kickjs`:\n // - `@forinda/kickjs` — the `./fastify` / `./h3` export subpaths the\n // app imports (stable lacks them → Vite boot\n // error `\"./h3\" is not exported`).\n // - `@forinda/kickjs-cli` — `--runtime`, `kick add upload`, `kick doctor`,\n // the `kick/runtime` typegen plugin.\n // - `@forinda/kickjs-vite` — the dev loop co-versioned with the above.\n // Gated on whether `@forinda/kickjs@latest` already exports the chosen engine\n // subpath: once that's true the runtimes are stable and we keep `latest` for\n // everything (self-retiring — no code change needed at graduation). Each pin\n // is guarded so it never DOWNGRADES (an alpha can be older than latest — e.g.\n // a package whose stable moved on past an old prerelease). Express is exempt.\n if (runtime !== 'express') {\n const subpath = `./${runtime}` // './fastify' | './h3'\n if (tagExportsSubpath('@forinda/kickjs', 'latest', subpath)) {\n log(`Using @forinda/kickjs@latest (stable ships the ${runtime} runtime)`)\n } else {\n const RUNTIME_PKGS = ['@forinda/kickjs', '@forinda/kickjs-cli', '@forinda/kickjs-vite']\n const pinned: string[] = []\n let kickjsPinned = false\n for (const pkg of RUNTIME_PKGS) {\n const alpha = resolveExactVersionAtTag(pkg, 'alpha')\n // Only pin when the alpha is newer-or-equal to the stable we'd otherwise\n // install — never downgrade a package onto a stale prerelease.\n if (alpha && baseVersionGte(alpha, stripRange(versions[pkg]))) {\n versions[pkg] = alpha\n pinned.push(`${pkg}@${alpha}`)\n if (pkg === '@forinda/kickjs') kickjsPinned = true\n }\n }\n if (kickjsPinned) {\n log(`Using the alpha channel for the ${runtime} runtime: ${pinned.join(', ')}`)\n } else {\n log(\n `WARNING: could not resolve @forinda/kickjs@alpha — the ${runtime} runtime subpath ` +\n `may be missing. After install, run: ${packageManager} add @forinda/kickjs@alpha`,\n )\n }\n }\n }\n\n // ── package.json — template-aware deps ────────────────────────────\n await writeFileSafe(\n join(dir, 'package.json'),\n generatePackageJson(name, template, versions, packages, schemaLib, runtime),\n )\n\n // ── vite.config.ts — enables HMR + SWC for decorators ──────────────\n await writeFileSafe(join(dir, 'vite.config.ts'), generateViteConfig())\n\n // ── tsconfig.json ───────────────────────────────────────────────────\n await writeFileSafe(join(dir, 'tsconfig.json'), generateTsConfig())\n\n // ── .prettierrc ─────────────────────────────────────────────────────\n await writeFileSafe(join(dir, '.prettierrc'), generatePrettierConfig())\n\n // ── .editorconfig ─────────────────────────────────────────────────────\n await writeFileSafe(join(dir, '.editorconfig'), generateEditorConfig())\n\n // ── .gitignore ──────────────────────────────────────────────────────\n await writeFileSafe(join(dir, '.gitignore'), generateGitIgnore())\n\n // ── .gitattributes ────────────────────────────────────────────────────\n await writeFileSafe(join(dir, '.gitattributes'), generateGitAttributes())\n\n // ── .env ────────────────────────────────────────────────────────────\n await writeFileSafe(join(dir, '.env'), generateEnv())\n\n await writeFileSafe(join(dir, '.env.example'), generateEnvExample())\n\n // ── src/config/index.ts — typed env schema (read by `kick typegen`) ─\n // Lives under `src/config/` so the framework's \"config\" concept has a\n // single, conventional home. Old projects with `src/env.ts` still\n // work — `detectEnvFile()` searches both locations.\n await writeFileSafe(join(dir, 'src/config/index.ts'), generateEnvFile(schemaLib))\n\n // ── src/index.ts — template-aware entry point ─────────────────────\n await writeFileSafe(\n join(dir, 'src/index.ts'),\n generateEntryFile(name, template, cliPkg.version, packages, runtime),\n )\n\n // ── src/modules/index.ts ────────────────────────────────────────────\n await writeFileSafe(join(dir, 'src/modules/index.ts'), generateModulesIndex())\n\n // ── src/modules/hello/ — sample module ─────────────────────────────\n await writeFileSafe(join(dir, 'src/modules/hello/hello.service.ts'), generateHelloService())\n await writeFileSafe(join(dir, 'src/modules/hello/hello.controller.ts'), generateHelloController())\n await writeFileSafe(join(dir, 'src/modules/hello/hello.module.ts'), generateHelloModule())\n\n // ── kick.config.ts — CLI configuration ─────────────────────────────\n await writeFileSafe(\n join(dir, 'kick.config.ts'),\n generateKickConfig(template, defaultRepo, packageManager, runtime),\n )\n\n // ── vitest.config.ts ────────────────────────────────────────────────\n await writeFileSafe(join(dir, 'vitest.config.ts'), generateVitestConfig())\n\n // ── README.md ────────────────────────────────────────────────────────\n await writeFileSafe(join(dir, 'README.md'), generateReadme(name, template, packageManager))\n\n // ── Agent docs ──────────────────────────────────────────────────────\n // Delegate to `generateAgentDocs()` so `kick new` emits the same\n // `.agents/` subfolder layout as `kick g agents -f`. Otherwise the\n // two paths drifted: kick new was writing the legacy flat layout\n // (root-level AGENTS.md + kickjs-skills.md) while kick g agents\n // emits the per-skill SKILL.md format under .agents/. `force: true`\n // because the project directory is fresh — no overwrite prompts\n // make sense during init.\n const { generateAgentDocs } = await import('./agent-docs')\n await generateAgentDocs({\n outDir: dir,\n name,\n pm: packageManager,\n template,\n only: 'all',\n force: true,\n })\n\n // ── Install Dependencies ────────────────────────────────────────────\n // Install BEFORE git init so the lockfile is included in the first commit.\n if (options.installDeps) {\n console.log(`\\n Installing dependencies with ${packageManager}...\\n`)\n try {\n execSync(`${packageManager} install`, { cwd: dir, stdio: 'inherit' })\n console.log('\\n Dependencies installed successfully!')\n } catch {\n console.log(`\\n Warning: ${packageManager} install failed. Run it manually.`)\n }\n }\n\n // ── Initial typegen ────────────────────────────────────────────────\n // Run typegen once so the freshly-scaffolded HelloController's\n // `Ctx<KickRoutes.HelloController['index']>` references resolve in\n // the user's editor immediately. Failures are non-fatal.\n try {\n const { runTypegen } = await import('../typegen')\n await runTypegen({ cwd: dir, allowDuplicates: true, silent: true })\n } catch {\n // First-run typegen errors are non-fatal — `kick dev` will retry.\n }\n\n // ── Git Init ─────────────────────────────────────────────────────────\n // Runs after install + typegen so lockfile and generated types are\n // included in the initial commit.\n if (options.initGit) {\n try {\n execSync('git init', { cwd: dir, stdio: 'pipe' })\n execSync('git branch -M main', { cwd: dir, stdio: 'pipe' })\n execSync('git add -A', { cwd: dir, stdio: 'pipe' })\n execSync('git commit -m \"chore: initial commit from kick new\"', {\n cwd: dir,\n stdio: 'pipe',\n })\n log('Git repository initialized')\n } catch {\n log('Warning: git init failed (git may not be installed)')\n }\n }\n\n console.log('\\n Project scaffolded successfully!')\n console.log()\n\n const needsCd = dir !== process.cwd()\n log('Next steps:')\n if (needsCd) log(` cd ${name}`)\n if (!options.installDeps) log(` ${packageManager} install`)\n\n const genHint: Record<string, string> = {\n rest: 'kick g module user',\n ddd: 'kick g module user --repo drizzle',\n cqrs: 'kick g module user --pattern cqrs',\n minimal: '# add your routes to src/index.ts',\n }\n log(` ${genHint[template] ?? genHint.rest}`)\n log(' kick dev')\n log('')\n log('Commands:')\n log(' kick dev Start dev server with Vite HMR')\n log(' kick build Production build via Vite')\n log(' kick start Run production build')\n log('')\n log('Generators:')\n log(' kick g module <name> Full DDD module (controller, DTOs, use-cases, repo)')\n log(' kick g scaffold <n> <f..> CRUD module from field definitions')\n log(' kick g controller <name> Standalone controller')\n log(' kick g service <name> @Service() class')\n log(' kick g middleware <name> Express middleware')\n log(' kick g guard <name> Route guard (auth, roles, etc.)')\n log(' kick g adapter <name> AppAdapter with lifecycle hooks')\n log(' kick g dto <name> Zod DTO schema')\n log(' kick g config Generate kick.config.ts')\n log('')\n log('Add packages:')\n log(' kick add <pkg> Install a KickJS package + peers')\n log(' kick add --list Show all available packages')\n log('')\n log('Available: auth, swagger, drizzle, prisma, ws, queue, devtools, mcp, testing')\n log('')\n}\n","/**\n * Deduplicating reporter for typegen failures in `kick dev` watch mode.\n *\n * The watch pipeline used to swallow errors entirely (`.catch(() => {})`),\n * which left adopters debugging stale `.kickjs/types` with no signal.\n * This reporter warns on every NEW failure message per source, stays\n * quiet for repeats of the same message (each save in a broken state\n * would otherwise re-print it), and re-arms once the source succeeds\n * again so a recurring failure after a fix re-warns.\n */\nexport interface TypegenErrorReporter {\n /** Report a failure for `source`; emits only if the message changed. */\n report(source: string, err: unknown): void\n /** Mark `source` healthy — the next failure emits even if identical. */\n clear(source: string): void\n}\n\nexport function createTypegenErrorReporter(emit: (message: string) => void): TypegenErrorReporter {\n const lastMessage = new Map<string, string>()\n return {\n report(source, err) {\n const msg = err instanceof Error ? err.message : String(err)\n if (lastMessage.get(source) === msg) return\n lastMessage.set(source, msg)\n emit(` kick typegen: ${source} pass failed (${msg}) — types in .kickjs/types may be stale`)\n },\n clear(source) {\n lastMessage.delete(source)\n },\n }\n}\n","/**\n * Debounced typegen-on-save watcher — the engine behind `kick dev`'s\n * file-change handling, extracted so `@forinda/kickjs-vite` can run the\n * SAME pipeline when the adopter boots plain `vite` (or any other\n * Vite-embedding tool) instead of `kick dev`.\n *\n * Behaviour (verbatim from the original inline implementation):\n * - Watcher events batch into one debounced pass (default 100ms).\n * - `.ts/.tsx/.mts/.cts` changes feed a precise `{changed, removed}`\n * delta to the incremental scanner; `unlinkDir` forces a full walk\n * (a dir removal can't be expressed as a per-file delta).\n * - Files under an `assetMap.<ns>.src` dir mark the pass asset-dirty,\n * driving an incremental `buildAssets` sweep.\n * - `.kickjs/**` and `.d.ts` events are ignored (typegen's own output).\n * - Failures surface through a deduplicating reporter (one warning per\n * NEW failure message per source; quiet on repeats; re-arms after a\n * successful pass).\n *\n * Exactly one watcher should own typegen per dev process. `kick dev`\n * claims ownership via {@link TYPEGEN_OWNER_KEY} on `globalThis` before\n * booting Vite; the vite plugin checks the marker and stands down.\n */\nimport path from 'node:path'\n\nimport type { KickConfig } from '../config'\nimport type { runTypegen, writeTypegenArtifacts } from './index'\nimport type { runAllPluginTypegens } from './run-plugins'\nimport type { buildAssets } from '../asset-manager/build'\nimport { createTypegenErrorReporter } from '../commands/typegen-error-reporter'\nimport type { ScanDelta } from './scanner'\n\n/**\n * `globalThis` key claiming typegen ownership for the current process.\n * Set by `kick dev` (which boots Vite in-process) so the vite plugin's\n * own watcher never double-runs the pipeline.\n */\nexport const TYPEGEN_OWNER_KEY = '__kickjs_typegen_owner'\n\nexport type TypegenWatchEvent = 'add' | 'change' | 'unlink' | 'unlinkDir'\n\n/** Injectable pipeline — production uses the real functions. */\nexport interface TypegenDevPipeline {\n runTypegen: typeof runTypegen\n runAllPluginTypegens: typeof runAllPluginTypegens\n writeTypegenArtifacts: typeof writeTypegenArtifacts\n buildAssets: typeof buildAssets\n}\n\nexport interface TypegenDevWatcherOptions {\n cwd: string\n /** Pre-loaded kick.config.ts (null when the project has none). */\n config: KickConfig | null\n /**\n * Warning sink — wired by the caller to `console.warn` plus whatever\n * HMR broadcast it has (e.g. the `kickjs:typegen-error` custom event).\n */\n emitWarning: (message: string) => void\n /**\n * Invoked after each pass's plugin chain settles (success or failure).\n * `kick dev` uses it to schedule the `--typecheck` worker against\n * fresh `.kickjs/types`.\n */\n onPassComplete?: () => void\n /** Debounce window in ms. @default 100 */\n debounceMs?: number\n /** Test seam — defaults to the real typegen pipeline. */\n pipeline?: TypegenDevPipeline\n}\n\nexport interface TypegenDevWatcher {\n /** Feed a chokidar-style watcher event into the debounce window. */\n handleWatchEvent(event: TypegenWatchEvent, file: string): void\n /**\n * Run one full (non-incremental) pass immediately — startup catch-up\n * for callers that didn't run typegen before the server booted.\n */\n runOnce(): void\n /**\n * Absolute `assetMap.<ns>.src` roots. Vite's default watcher ignores\n * extensions it doesn't compile, so callers should `watcher.add(...)`\n * these to receive template-file events at all.\n */\n assetSrcRoots: readonly string[]\n /** Cancel any pending debounced pass. */\n dispose(): void\n}\n\nexport function createTypegenDevWatcher(opts: TypegenDevWatcherOptions): TypegenDevWatcher {\n const { cwd, config } = opts\n const debounceMs = opts.debounceMs ?? 100\n // Default pipeline resolves lazily — a static import would drag the\n // whole generator/typegen graph (including dist-layout-only module\n // initializers) into every consumer of this module.\n const pipeline: TypegenDevPipeline = opts.pipeline ?? {\n runTypegen: async (o) => (await import('./index')).runTypegen(o),\n runAllPluginTypegens: async (o) => (await import('./run-plugins')).runAllPluginTypegens(o),\n writeTypegenArtifacts: async (dir, results, silent) =>\n (await import('./index')).writeTypegenArtifacts(dir, results, silent),\n buildAssets: async (cfg, o) => (await import('../asset-manager/build')).buildAssets(cfg, o),\n }\n\n const schemaValidator = config?.typegen?.schemaValidator ?? 'zod'\n const envFile = config?.typegen?.envFile\n const typesOutDir = path.resolve(cwd, config?.typegen?.outDir ?? '.kickjs/types')\n\n const assetSrcRoots: readonly string[] = config?.assetMap\n ? Object.values(config.assetMap)\n .map((entry) => entry?.src)\n .filter((src): src is string => typeof src === 'string' && src.length > 0)\n .map((src) => path.resolve(cwd, src))\n : []\n const hasAssetMap = !!config?.assetMap && Object.keys(config.assetMap).length > 0\n // Normalize BOTH sides to forward slashes before comparing — chokidar\n // emits native separators on Windows while `path.resolve` produces\n // backslash roots, so a raw startsWith would miss either direction.\n const toPosix = (p: string): string => p.replaceAll('\\\\', '/')\n const assetRootsPosix = assetSrcRoots.map(toPosix)\n const isAssetFile = (file: string): boolean => {\n const posix = toPosix(file)\n return assetRootsPosix.some((root) => posix === root || posix.startsWith(`${root}/`))\n }\n\n const reporter = createTypegenErrorReporter(opts.emitWarning)\n\n let timer: ReturnType<typeof setTimeout> | null = null\n let disposed = false\n const pendingChanged = new Set<string>()\n const pendingRemoved = new Set<string>()\n let forceFullScan = false\n let assetDirty = false\n\n function firePass(delta: ScanDelta | undefined, rebuildAssets: boolean): void {\n pipeline\n .runTypegen({\n cwd,\n silent: true,\n allowDuplicates: true,\n schemaValidator,\n envFile,\n srcDir: config?.typegen?.srcDir,\n outDir: config?.typegen?.outDir,\n assetMap: config?.assetMap,\n changedFiles: delta,\n // Plugin pipeline runs separately just below; opting out here\n // avoids double-running it on every debounced trigger.\n runPlugins: false,\n })\n .then(() => reporter.clear('scan'))\n .catch((err) => reporter.report('scan', err))\n pipeline\n .runAllPluginTypegens({ cwd, config, silent: true, changedFiles: delta })\n .then((r) => pipeline.writeTypegenArtifacts(typesOutDir, r, true))\n .then(() => reporter.clear('plugins'))\n .catch((err) => reporter.report('plugins', err))\n // Post-pass hook AFTER the plugin chain settles so consumers (the\n // --typecheck worker) see the freshest .kickjs/types this window\n // produced.\n .finally(() => opts.onPassComplete?.())\n if (rebuildAssets && config) {\n pipeline.buildAssets(config, { cwd, silent: true }).catch(() => {})\n }\n }\n\n function flush(): void {\n // `undefined` delta → full scan (the `unlinkDir` correctness path).\n const delta = forceFullScan\n ? undefined\n : { changed: [...pendingChanged], removed: [...pendingRemoved] }\n const rebuildAssets = assetDirty\n pendingChanged.clear()\n pendingRemoved.clear()\n forceFullScan = false\n assetDirty = false\n firePass(delta, rebuildAssets)\n }\n\n return {\n assetSrcRoots,\n\n handleWatchEvent(event, file) {\n if (disposed) return\n // Typegen's own output directory — separator-aware so a file\n // merely NAMED like `my.kickjs.backup.ts` isn't filtered.\n if (toPosix(file).includes('/.kickjs/')) return\n if (event === 'unlinkDir') {\n // Only meaningful if the removed dir could have held scanned\n // sources or watched assets; cheap to just force a full scan.\n forceFullScan = true\n if (hasAssetMap) assetDirty = true\n } else {\n if (file.endsWith('.d.ts')) return\n const isTs = /\\.(ts|tsx|mts|cts)$/.test(file)\n const isAsset = isAssetFile(file)\n if (!isTs && !isAsset) return\n if (isAsset && hasAssetMap) assetDirty = true\n // Only `.ts` files participate in the source scan delta. Asset-\n // only changes still trigger the pass (so the asset plugin\n // re-emits) but contribute nothing to the scan — an empty `.ts`\n // delta makes the incremental scan a near-instant cache replay.\n if (isTs) {\n if (event === 'unlink') {\n pendingRemoved.add(file)\n pendingChanged.delete(file)\n } else {\n pendingChanged.add(file)\n pendingRemoved.delete(file)\n }\n }\n }\n if (timer) clearTimeout(timer)\n timer = setTimeout(flush, debounceMs)\n },\n\n runOnce() {\n if (disposed) return\n firePass(undefined, hasAssetMap)\n },\n\n dispose() {\n disposed = true\n if (timer) clearTimeout(timer)\n timer = null\n },\n }\n}\n","import { pathToFileURL } from 'node:url'\nimport { resolve } from 'node:path'\nimport { toPascalCase, toCamelCase, toKebabCase, pluralize } from '../utils/naming'\nimport { findProjectRoot } from '../utils/project-root'\nimport type { GeneratorContext } from './define'\n\n/** Convert any string to snake_case (`UserPost` / `user-post` → `user_post`). */\nfunction toSnakeCase(name: string): string {\n return toKebabCase(name).replace(/-/g, '_')\n}\n\n/**\n * Build a {@link GeneratorContext} from the raw name + invocation\n * arguments. Centralises the case-transformation logic so every plugin\n * generator sees the same shape regardless of how the name was typed\n * on the command line (`Post` vs `post` vs `user_post`).\n */\nexport function buildGeneratorContext(input: {\n name: string\n args?: string[]\n flags?: Record<string, string | boolean>\n modulesDir?: string\n cwd?: string\n /**\n * Resolved project root. When omitted, derived from {@link cwd} via\n * `findProjectRoot()` so adopter-facing call sites stay zero-config.\n * Callers that already know the root (e.g. `cli.ts` after one upfront\n * resolution) should pass it through to avoid redundant fs walks.\n */\n projectRoot?: string\n pluralize?: boolean\n}): GeneratorContext {\n const cwd = input.cwd ?? process.cwd()\n const projectRoot = input.projectRoot ?? findProjectRoot(cwd)\n const usePlural = input.pluralize ?? true\n\n const pascal = toPascalCase(input.name)\n const camel = toCamelCase(input.name)\n const kebab = toKebabCase(input.name)\n const snake = toSnakeCase(input.name)\n\n const ctx: GeneratorContext = {\n name: input.name,\n pascal,\n camel,\n kebab,\n snake,\n modulesDir: input.modulesDir ?? 'src/modules',\n cwd,\n projectRoot,\n args: input.args ?? [],\n flags: input.flags ?? {},\n }\n\n if (usePlural) {\n const pluralKebab = pluralize(kebab)\n ctx.pluralKebab = pluralKebab\n ctx.pluralPascal = toPascalCase(pluralKebab)\n ctx.pluralCamel = toCamelCase(pluralKebab)\n }\n\n return ctx\n}\n\n/** Resolve a generator output path against the context's cwd. */\nexport function resolveGeneratorPath(ctx: GeneratorContext, path: string): string {\n return resolve(ctx.cwd, path)\n}\n\n/**\n * Dynamic-import a generator manifest file. Wraps `pathToFileURL` so\n * callers don't have to think about Windows/Unix path quirks.\n */\nexport async function importManifest(absPath: string): Promise<unknown> {\n return import(pathToFileURL(absPath).href)\n}\n","import { readFile } from 'node:fs/promises'\nimport { resolve, dirname } from 'node:path'\nimport { existsSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport type { GeneratorSpec } from './define'\nimport { importManifest } from './context'\n\n/**\n * One row in the discovered registry. `source` is the npm package name\n * the generator came from — surfaced in error messages so adopters can\n * see which plugin owns a given generator.\n */\nexport interface DiscoveredGenerator {\n source: string\n spec: GeneratorSpec\n}\n\n/**\n * Plugin discovery result, kept around even when no generators were\n * registered so callers can distinguish \"no plugins installed\" from\n * \"no plugins matched the requested name.\"\n */\nexport interface DiscoveryResult {\n generators: DiscoveredGenerator[]\n /** Packages whose `kickjs.generators` was loaded successfully. */\n loaded: string[]\n /**\n * Packages we tried to load but failed — typically a missing entry\n * file or a default export that wasn't an array of GeneratorSpec.\n */\n failed: Array<{ source: string; reason: string }>\n}\n\n/** Shape of `package.json` we care about during discovery. */\ninterface PluginPackage {\n name?: string\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n peerDependencies?: Record<string, string>\n kickjs?: {\n generators?: string\n }\n keywords?: string[]\n}\n\n/**\n * Discover generator manifests shipped by every kickjs plugin in the\n * project's direct deps. Spec rationale: walking the\n * `node_modules/@scope/kickjs-name/` tree is one option, but reading\n * the project's own `package.json` and resolving each dep through\n * Node's module resolver gives:\n *\n * 1. Predictable scoping — only deps the project actually declared\n * get scanned, no surprises from transitive packages\n * 2. pnpm `.pnpm` store compatibility — `createRequire().resolve()`\n * handles the symlinked layout correctly\n * 3. Clear error attribution — the source package name is always\n * known before the import happens\n *\n * The walk is shallow (direct deps only). Transitive plugins that want\n * to expose generators must be re-exported by a direct dep.\n *\n * Caches per-cwd inside one CLI invocation so a single `kick g` call\n * does the disk + import work exactly once even when multiple\n * generators dispatch through the same registry.\n */\nconst cache = new Map<string, Promise<DiscoveryResult>>()\n\nexport async function discoverPluginGenerators(cwd: string): Promise<DiscoveryResult> {\n const cached = cache.get(cwd)\n if (cached) return cached\n const promise = doDiscover(cwd)\n cache.set(cwd, promise)\n return promise\n}\n\n/** Reset the cache — used by tests so repeated runs see fresh fixtures. */\nexport function resetGeneratorDiscoveryCache(): void {\n cache.clear()\n}\n\nasync function doDiscover(cwd: string): Promise<DiscoveryResult> {\n const projectPkgPath = resolve(cwd, 'package.json')\n if (!existsSync(projectPkgPath)) {\n return { generators: [], loaded: [], failed: [] }\n }\n\n const projectPkg = JSON.parse(await readFile(projectPkgPath, 'utf-8')) as PluginPackage\n const depNames = collectDepNames(projectPkg)\n\n const require = createRequire(resolve(cwd, 'package.json'))\n\n const generators: DiscoveredGenerator[] = []\n const loaded: string[] = []\n const failed: Array<{ source: string; reason: string }> = []\n\n for (const depName of depNames) {\n let depPkgPath: string\n try {\n depPkgPath = require.resolve(`${depName}/package.json`)\n } catch {\n // Dep declared but not installed (or doesn't expose package.json\n // via its export map). Skip silently — this is `kick g`, not\n // `pnpm install`.\n continue\n }\n\n let depPkg: PluginPackage\n try {\n depPkg = JSON.parse(await readFile(depPkgPath, 'utf-8')) as PluginPackage\n } catch (err) {\n failed.push({ source: depName, reason: `failed to parse package.json: ${err}` })\n continue\n }\n\n if (!depPkg.kickjs?.generators) continue\n\n const entryRel = depPkg.kickjs.generators\n const entryAbs = resolve(dirname(depPkgPath), entryRel)\n if (!existsSync(entryAbs)) {\n failed.push({\n source: depName,\n reason: `kickjs.generators points to missing file: ${entryRel}`,\n })\n continue\n }\n\n let mod: unknown\n try {\n mod = await importManifest(entryAbs)\n } catch (err) {\n failed.push({ source: depName, reason: `failed to import manifest: ${err}` })\n continue\n }\n\n const manifest = (mod as { default?: unknown }).default\n if (!Array.isArray(manifest)) {\n failed.push({\n source: depName,\n reason: `manifest's default export is not an array of GeneratorSpec`,\n })\n continue\n }\n\n for (const entry of manifest) {\n if (!isGeneratorSpec(entry)) {\n failed.push({\n source: depName,\n reason: `manifest entry is not a valid GeneratorSpec (missing name/files)`,\n })\n continue\n }\n generators.push({ source: depName, spec: entry })\n }\n loaded.push(depName)\n }\n\n return { generators, loaded, failed }\n}\n\nfunction collectDepNames(pkg: PluginPackage): string[] {\n const set = new Set<string>()\n for (const block of [pkg.dependencies, pkg.devDependencies, pkg.peerDependencies]) {\n if (!block) continue\n for (const name of Object.keys(block)) set.add(name)\n }\n return Array.from(set)\n}\n\nfunction isGeneratorSpec(entry: unknown): entry is GeneratorSpec {\n if (!entry || typeof entry !== 'object') return false\n const e = entry as Record<string, unknown>\n return typeof e.name === 'string' && typeof e.files === 'function'\n}\n","import { writeFileSafe } from '../utils/fs'\nimport { buildGeneratorContext, resolveGeneratorPath } from './context'\nimport {\n discoverPluginGenerators,\n type DiscoveredGenerator,\n type DiscoveryResult,\n} from './discover'\nimport type { GeneratorSpec } from './define'\n\nexport interface DispatchInput {\n /** The generator name typed on the CLI (`kick g command Order` → `'command'`). */\n generatorName: string\n /** The first positional argument after the generator name. */\n itemName: string\n /** Remaining positional arguments. */\n args?: string[]\n /** Parsed flag map (booleans + values). */\n flags?: Record<string, string | boolean>\n /** Project context — usually `process.cwd()`. */\n cwd?: string\n /**\n * Resolved project root. When omitted, `buildGeneratorContext` derives\n * it from `cwd` via `findProjectRoot()`. Pass through when the caller\n * has already resolved it (the CLI entry point does this once at\n * startup) to avoid re-walking the filesystem on every generator call.\n */\n projectRoot?: string\n /** Modules dir from `kick.config.ts`. */\n modulesDir?: string\n /** Whether the project enables auto-pluralization. */\n pluralize?: boolean\n}\n\nexport interface DispatchResult {\n /** Absolute paths of the files that were written. */\n files: string[]\n /** Which plugin owns the generator we ran. */\n source: string\n}\n\n/**\n * Look up a plugin generator by name and run it. Returns `null` when\n * no plugin generator matches — callers can then fall through to the\n * built-in dispatch (module / scaffold / etc.).\n *\n * Resolution order:\n * 1. `additional` — generators sourced from `KickCliPlugin.generators`\n * via `kick.config.ts > plugins[]`. Authoritative; the canonical\n * path going forward.\n * 2. `package.json > kickjs.generators` discovery — first-match-wins\n * in dependency declaration order. Deprecated path retained for\n * packages that haven't migrated yet.\n *\n * Adopters with conflicts should rename the generator on their side or\n * pin one of the plugins to a different version.\n */\nexport async function tryDispatchPluginGenerator(\n input: DispatchInput,\n additional: readonly DiscoveredGenerator[] = [],\n): Promise<DispatchResult | null> {\n const cwd = input.cwd ?? process.cwd()\n\n const fromConfig = additional.find((g) => g.spec.name === input.generatorName)\n if (fromConfig) {\n return runGenerator(fromConfig.spec, fromConfig.source, input, cwd)\n }\n\n const discovery = await discoverPluginGenerators(cwd)\n const match = findGenerator(discovery, input.generatorName)\n if (!match) return null\n\n return runGenerator(match.spec, match.source, input, cwd)\n}\n\n/**\n * Public helper for `kick g --list` — returns every plugin generator\n * the CLI knows about, merging config-supplied entries on top of the\n * package.json discovery result. Config entries always come first.\n */\nexport async function listPluginGenerators(\n cwd: string,\n additional: readonly DiscoveredGenerator[] = [],\n): Promise<DiscoveryResult> {\n const discovered = await discoverPluginGenerators(cwd)\n const configNames = new Set(additional.map((g) => g.spec.name))\n const filteredDiscovered = discovered.generators.filter((g) => !configNames.has(g.spec.name))\n return {\n generators: [...additional, ...filteredDiscovered],\n loaded: discovered.loaded,\n failed: discovered.failed,\n }\n}\n\nfunction findGenerator(discovery: DiscoveryResult, name: string): DiscoveredGenerator | undefined {\n return discovery.generators.find((g) => g.spec.name === name)\n}\n\n/**\n * Invoke a {@link GeneratorSpec.files} factory with a fully-populated\n * {@link GeneratorContext}, write every returned file under the context's\n * cwd, and return the absolute paths along with the source plugin name.\n *\n * Threads `input.projectRoot` through to `buildGeneratorContext` so\n * callers that already resolved the project root (the CLI entry does\n * this once at startup) avoid a redundant filesystem walk. When omitted,\n * `buildGeneratorContext` derives `projectRoot` from `cwd` via\n * `findProjectRoot()` — keeping ad-hoc callers zero-config.\n */\nasync function runGenerator(\n spec: GeneratorSpec,\n source: string,\n input: DispatchInput,\n cwd: string,\n): Promise<DispatchResult> {\n const ctx = buildGeneratorContext({\n name: input.itemName,\n args: input.args,\n flags: input.flags,\n modulesDir: input.modulesDir,\n pluralize: input.pluralize,\n cwd,\n projectRoot: input.projectRoot,\n })\n\n const files = await spec.files(ctx)\n const written: string[] = []\n\n for (const file of files) {\n const absPath = resolveGeneratorPath(ctx, file.path)\n await writeFileSafe(absPath, file.content)\n written.push(absPath)\n }\n\n return { files: written, source }\n}\n","import { execSync } from 'node:child_process'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport type { Command } from 'commander'\nimport { loadKickConfig, PACKAGE_MANAGERS, type PackageManager } from '../config'\n\ninterface PackageEntry {\n pkg: string\n peers: string[]\n description: string\n dev?: boolean\n /**\n * `true` for packages every project needs (framework + Vite plugin +\n * CLI). `kick new` installs these regardless of options chosen, and\n * future package-removal flows refuse to drop them.\n */\n core?: boolean\n /**\n * Set when the package still installs but should no longer be the\n * default choice. The string is the migration hint shown both in\n * `kick add --list --all` and as a warning when the package is added.\n */\n deprecated?: string\n}\n\n/** Registry of KickJS packages and their required peer dependencies */\nexport const PACKAGE_REGISTRY: Record<string, PackageEntry> = {\n // Core (always installed by kick new — required for the framework to run)\n kickjs: {\n pkg: '@forinda/kickjs',\n peers: ['express'],\n description: 'Unified framework: DI, decorators, routing, middleware',\n core: true,\n },\n vite: {\n pkg: '@forinda/kickjs-vite',\n peers: ['vite'],\n description: 'Vite plugin: dev server, HMR, module discovery',\n dev: true,\n core: true,\n },\n cli: {\n pkg: '@forinda/kickjs-cli',\n peers: [],\n description: 'CLI tool and code generators',\n dev: true,\n core: true,\n },\n\n // Schema validation — the validator backing env + DTO + OpenAPI\n // schemas. `@forinda/kickjs-schema` (a core dep) wraps whichever one\n // you pick behind `KickSchema`, but the validator itself is an\n // optional peer of `@forinda/kickjs`, so it must be installed\n // explicitly or the app errors at startup (\"Cannot find module\n // 'zod'\"). `kick new` installs the chosen one; `kick add` lets an\n // existing project add/switch.\n zod: {\n pkg: 'zod',\n peers: [],\n description: 'Zod schema validation (env, DTOs, OpenAPI) — wrap with fromZod()',\n },\n valibot: {\n pkg: 'valibot',\n peers: [],\n description: 'Valibot schema validation — wrap with fromValibot()',\n },\n yup: {\n pkg: 'yup',\n peers: [],\n description: 'Yup schema validation — wrap with fromYup()',\n },\n\n // Auth — deprecated in favour of BYO (bring-your-own) auth composed\n // from context contributors. Still installable for existing projects;\n // JWT is the common path, so it co-installs jsonwebtoken.\n auth: {\n pkg: '@forinda/kickjs-auth',\n peers: ['jsonwebtoken'],\n description: 'JWT, API key, OAuth strategies, @Public, @Roles (+ optional argon2/bcryptjs)',\n deprecated:\n 'auth is moving to BYO — compose @LoadAuthUser/@RequireRole/@Public from defineContextDecorator (see the BYO Auth recipe in the docs)',\n },\n\n // AI — requires zod (^4) for tool/schema definitions.\n ai: {\n pkg: '@forinda/kickjs-ai',\n peers: ['zod'],\n description: 'AI toolkit — LLM providers, tool definitions from controllers',\n },\n\n // API\n swagger: {\n pkg: '@forinda/kickjs-swagger',\n peers: [],\n description: 'OpenAPI spec + Swagger UI + ReDoc',\n },\n // Database — the dialect adapters now ship as subpaths of\n // `@forinda/kickjs-db` (`/pg`, `/sqlite`, `/mysql`), so each `kick add`\n // pulls the core package plus the one driver you need.\n db: {\n pkg: '@forinda/kickjs-db',\n peers: [],\n description: 'kick/db core — schema DSL, migrations, KickDbClient, customType',\n },\n pg: {\n pkg: '@forinda/kickjs-db',\n peers: ['pg'],\n description: 'kick/db + PostgreSQL driver (use @forinda/kickjs-db/pg)',\n },\n sqlite: {\n pkg: '@forinda/kickjs-db',\n peers: ['better-sqlite3'],\n description: 'kick/db + SQLite driver (use @forinda/kickjs-db/sqlite)',\n },\n mysql: {\n pkg: '@forinda/kickjs-db',\n peers: ['mysql2'],\n description: 'kick/db + MySQL driver (use @forinda/kickjs-db/mysql)',\n },\n drizzle: {\n pkg: '@forinda/kickjs-drizzle',\n peers: ['drizzle-orm'],\n description: 'Drizzle ORM adapter + query builder',\n deprecated:\n 'early-adoption adapter, no longer maintained — wire Drizzle directly (BYO), or use @forinda/kickjs-db, the built-in Kick ORM (`kick add db` / pg / sqlite / mysql)',\n },\n prisma: {\n pkg: '@forinda/kickjs-prisma',\n peers: ['@prisma/client'],\n description: 'Prisma adapter + query builder',\n deprecated:\n 'early-adoption adapter, no longer maintained — wire Prisma directly (BYO), or use @forinda/kickjs-db, the built-in Kick ORM (`kick add db` / pg / sqlite / mysql)',\n },\n\n // Real-time\n ws: {\n pkg: '@forinda/kickjs-ws',\n peers: ['ws'],\n description: 'WebSocket with @WsController decorators',\n },\n\n // DevTools\n devtools: {\n pkg: '@forinda/kickjs-devtools',\n peers: [],\n description: 'Development dashboard — routes, DI, metrics, health',\n dev: true,\n },\n\n // Queue\n queue: {\n pkg: '@forinda/kickjs-queue',\n peers: [],\n description: 'Queue adapter (BullMQ/RabbitMQ/Kafka)',\n },\n 'queue:bullmq': {\n pkg: '@forinda/kickjs-queue',\n peers: ['bullmq', 'ioredis'],\n description: 'Queue with BullMQ + Redis',\n },\n 'queue:rabbitmq': {\n pkg: '@forinda/kickjs-queue',\n peers: ['amqplib'],\n description: 'Queue with RabbitMQ',\n },\n 'queue:kafka': {\n pkg: '@forinda/kickjs-queue',\n peers: ['kafkajs'],\n description: 'Queue with Kafka',\n },\n 'queue:redis-pubsub': {\n pkg: '@forinda/kickjs-queue',\n peers: ['ioredis'],\n description: 'Lightweight pub/sub via Redis (no persistence)',\n },\n\n // MCP — Model Context Protocol server\n mcp: {\n pkg: '@forinda/kickjs-mcp',\n peers: ['@modelcontextprotocol/sdk'],\n description: 'Model Context Protocol server — expose @Controller endpoints as AI tools',\n },\n\n // Testing\n testing: {\n pkg: '@forinda/kickjs-testing',\n peers: [],\n description: 'Test utilities and TestModule builder',\n dev: true,\n },\n}\n\n/**\n * The `upload` catalog name is special — file uploads ship inside\n * `@forinda/kickjs` itself, so there's no package to install. What an app\n * needs is the multipart DRIVER for its HTTP runtime, and that differs per\n * engine. `planAddPackages` resolves `upload` against the configured runtime\n * (see {@link KickConfig.runtime}); `kick doctor` validates the same mapping.\n */\nexport const UPLOAD_DRIVERS: Record<\n 'express' | 'fastify' | 'h3',\n { prod?: string; dev?: string; note: string }\n> = {\n express: {\n prod: 'multer',\n dev: '@types/multer',\n note: 'Express uploads use multer (memory/disk storage, ctx.file / ctx.files).',\n },\n fastify: {\n prod: '@fastify/multipart',\n note: 'Fastify uploads use @fastify/multipart (buffered into ctx.file / ctx.files).',\n },\n h3: {\n note: 'h3 parses multipart natively (readMultipartFormData) — no driver to install.',\n },\n}\n\nexport type AppRuntime = 'express' | 'fastify' | 'h3'\n\n/**\n * Resolve the project's HTTP runtime: the `runtime` field in kick.config\n * (authoritative — `kick new` writes it), falling back to sniffing installed\n * deps in the nearest package.json (`fastify` → fastify, `h3` → h3), else\n * `express` (the default engine). Lets `kick add upload` / `kick doctor` pick\n * the engine-correct multipart driver even in projects scaffolded before the\n * `runtime` field existed.\n */\nexport async function resolveAppRuntime(cwd = process.cwd()): Promise<AppRuntime> {\n const config = await loadKickConfig(cwd)\n const fromConfig = (config as { runtime?: AppRuntime } | null)?.runtime\n if (fromConfig === 'express' || fromConfig === 'fastify' || fromConfig === 'h3') {\n return fromConfig\n }\n return detectRuntimeFromDeps(cwd)\n}\n\n/** Sniff the runtime from installed deps when kick.config has no `runtime`. */\nexport function detectRuntimeFromDeps(cwd = process.cwd()): AppRuntime {\n const dir = findUp('package.json', cwd)\n if (dir) {\n try {\n const pkg = JSON.parse(readFileSync(resolve(dir, 'package.json'), 'utf-8'))\n const deps = { ...pkg.dependencies, ...pkg.devDependencies } as Record<string, unknown>\n if ('fastify' in deps) return 'fastify'\n if ('h3' in deps) return 'h3'\n } catch {\n // ignore — fall through to the default engine\n }\n }\n return 'express'\n}\n\n/**\n * Walk up from `fromDir` to filesystem root, returning the first\n * directory that contains `name`. Lets monorepo sub-packages pick up\n * lockfiles and `packageManager` fields living at the workspace root.\n */\nfunction findUp(name: string, fromDir = process.cwd()): string | null {\n let current = fromDir\n while (true) {\n if (existsSync(resolve(current, name))) return current\n const parent = dirname(current)\n if (parent === current) return null\n current = parent\n }\n}\n\nfunction detectFromLockfile(): PackageManager | null {\n if (findUp('pnpm-lock.yaml')) return 'pnpm'\n if (findUp('yarn.lock')) return 'yarn'\n if (findUp('bun.lockb') || findUp('bun.lock')) return 'bun'\n if (findUp('package-lock.json')) return 'npm'\n return null\n}\n\n/**\n * Read `packageManager` from the nearest ancestor `package.json` that\n * declares the field (corepack convention: `\"pnpm@10.0.0\"`). Climbs so\n * monorepo sub-packages inherit the workspace pm even when their own\n * package.json omits the field.\n */\nfunction packageManagerFromPackageJson(): PackageManager | null {\n let dir: string | null = process.cwd()\n while (dir) {\n const pkgPath = resolve(dir, 'package.json')\n if (existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))\n const field: unknown = pkg.packageManager\n if (typeof field === 'string') {\n const name = field.split('@')[0] as PackageManager\n if (PACKAGE_MANAGERS.includes(name)) return name\n }\n } catch {\n // ignore — keep climbing\n }\n }\n const parent = dirname(dir)\n if (parent === dir) return null\n dir = parent\n }\n return null\n}\n\nexport type PackageManagerSource = 'flag' | 'config' | 'package.json' | 'lockfile' | 'default'\n\n/**\n * Resolve which package manager to use, in priority order:\n * 1. `--pm` CLI flag\n * 2. `packageManager` in kick.config\n * 3. `packageManager` in nearest ancestor package.json (corepack)\n * 4. Nearest ancestor lockfile (pnpm-lock.yaml → yarn.lock → bun.lock → package-lock.json)\n * 5. `'npm'` fallback\n *\n * Returns the chosen pm plus the source for callers that want to log\n * the resolution path.\n */\nexport async function resolvePackageManagerWithSource(\n flagPm: string | undefined,\n): Promise<{ pm: PackageManager; source: PackageManagerSource }> {\n if (flagPm && PACKAGE_MANAGERS.includes(flagPm as PackageManager)) {\n return { pm: flagPm as PackageManager, source: 'flag' }\n }\n\n const config = await loadKickConfig(process.cwd())\n if (config?.packageManager && PACKAGE_MANAGERS.includes(config.packageManager)) {\n return { pm: config.packageManager, source: 'config' }\n }\n\n const fromPkg = packageManagerFromPackageJson()\n if (fromPkg) return { pm: fromPkg, source: 'package.json' }\n\n const fromLock = detectFromLockfile()\n if (fromLock) return { pm: fromLock, source: 'lockfile' }\n\n return { pm: 'npm', source: 'default' }\n}\n\n/** Convenience wrapper for callers that don't care about the source. */\nexport async function resolvePackageManager(flagPm: string | undefined): Promise<PackageManager> {\n const { pm } = await resolvePackageManagerWithSource(flagPm)\n return pm\n}\n\n/**\n * Print the package catalog. By default shows just the three core\n * packages every project always has — the optional list churns\n * (packages added, deprecated, removed) and a long enumeration in CLI\n * output / docs goes stale within a release. Pass `all = true` to dump\n * everything; that's what `kick add --list --all` triggers when an\n * adopter genuinely wants the live catalog.\n */\nexport function printPackageList(all = false): void {\n const entries = Object.entries(PACKAGE_REGISTRY)\n const maxName = Math.max(...entries.map(([k]) => k.length))\n const core = entries.filter(([, info]) => info.core)\n const optional = entries.filter(([, info]) => !info.core)\n\n const formatRow = ([name, info]: [string, PackageEntry]): string => {\n const padded = name.padEnd(maxName + 2)\n const peers = info.peers.length ? ` (+ ${info.peers.join(', ')})` : ''\n const deprecated = info.deprecated ? ` [DEPRECATED — ${info.deprecated}]` : ''\n return ` ${padded} ${info.description}${peers}${deprecated}`\n }\n\n console.log('\\n Core packages (always installed by `kick new`):\\n')\n for (const row of core) console.log(formatRow(row))\n\n if (all) {\n console.log('\\n Optional packages (add as needed):\\n')\n for (const row of optional) console.log(formatRow(row))\n } else {\n console.log(`\\n Plus ${optional.length} optional packages (auth, swagger, db, queue, …).`)\n console.log(' Run `kick add --list --all` for the full catalog.')\n }\n\n console.log('\\n Usage: kick add ai db swagger')\n console.log(' kick add queue:bullmq')\n console.log(' kick add upload # installs the multipart driver for your runtime')\n console.log()\n}\n\nexport interface AddPlan {\n prodDeps: string[]\n devDeps: string[]\n unknown: string[]\n /** Deprecation notices for requested entries — print, then install anyway. */\n warnings: string[]\n /** Informational notes (e.g. the upload driver chosen for the runtime). */\n notices: string[]\n}\n\n/**\n * Pure resolution step for `kick add` — maps requested catalog names to\n * the npm packages (plus peers) to install, split prod/dev. Kept free\n * of I/O so the catalog rules (dev defaults, deprecations, unknown\n * handling) are unit-testable without spawning a package manager.\n */\nexport function planAddPackages(\n packages: string[],\n forceDev: boolean,\n runtime: AppRuntime = 'express',\n): AddPlan {\n const prodDeps = new Set<string>()\n const devDeps = new Set<string>()\n const unknown: string[] = []\n const warnings: string[] = []\n const notices: string[] = []\n\n for (const name of packages) {\n // `upload` isn't a package — it's the runtime's multipart driver. File\n // uploads ship in @forinda/kickjs; only the engine backend needs adding.\n if (name === 'upload') {\n const driver = UPLOAD_DRIVERS[runtime]\n notices.push(`upload (${runtime}): ${driver.note}`)\n if (driver.prod) (forceDev ? devDeps : prodDeps).add(driver.prod)\n if (driver.dev) devDeps.add(driver.dev)\n continue\n }\n\n const entry = PACKAGE_REGISTRY[name]\n if (!entry) {\n unknown.push(name)\n continue\n }\n if (entry.deprecated) {\n warnings.push(`'${name}' (${entry.pkg}) is deprecated — ${entry.deprecated}`)\n }\n const target = forceDev || entry.dev ? devDeps : prodDeps\n target.add(entry.pkg)\n for (const peer of entry.peers) {\n target.add(peer)\n }\n }\n\n return { prodDeps: [...prodDeps], devDeps: [...devDeps], unknown, warnings, notices }\n}\n\nexport function registerListCommand(program: Command): void {\n program\n .command('list')\n .alias('ls')\n .description('List KickJS packages (core only; pair with --all for the full catalog)')\n .option('--all', 'Include the full optional catalog')\n .action((opts: { all?: boolean }) => {\n printPackageList(Boolean(opts.all))\n })\n}\n\nexport function registerAddCommand(program: Command): void {\n program\n .command('add [packages...]')\n .description('Add KickJS packages with their required dependencies')\n .option('--pm <manager>', 'Package manager override')\n .option('-D, --dev', 'Install as dev dependency')\n .option('--list', 'List packages (core only by default; pair with --all)')\n .option('--all', 'When listing, include the full optional catalog')\n .action(async (packages: string[], opts: any) => {\n // List mode\n if (opts.list || packages.length === 0) {\n printPackageList(Boolean(opts.all))\n return\n }\n\n const { pm, source } = await resolvePackageManagerWithSource(opts.pm)\n console.log(`\\n Using ${pm} (resolved from ${source})`)\n // Resolve the runtime so `kick add upload` installs the right multipart\n // driver (express → multer, fastify → @fastify/multipart, h3 → none).\n const runtime = await resolveAppRuntime(process.cwd())\n const { prodDeps, devDeps, unknown, warnings, notices } = planAddPackages(\n packages,\n Boolean(opts.dev),\n runtime,\n )\n\n for (const warning of warnings) {\n console.warn(`\\n WARNING: ${warning}`)\n }\n\n for (const notice of notices) {\n console.log(`\\n ${notice}`)\n }\n\n if (unknown.length > 0) {\n console.log(`\\n Unknown packages: ${unknown.join(', ')}`)\n console.log(' Run \"kick add --list\" to see available packages.\\n')\n if (prodDeps.length === 0 && devDeps.length === 0) return\n }\n\n // Install production dependencies\n if (prodDeps.length > 0) {\n const deps = prodDeps\n const cmd = `${pm} add ${deps.join(' ')}`\n console.log(`\\n Installing ${deps.length} dependency(ies):`)\n for (const dep of deps) console.log(` + ${dep}`)\n console.log()\n try {\n execSync(cmd, { stdio: 'inherit' })\n } catch {\n console.log(`\\n Installation failed. Run manually:\\n ${cmd}\\n`)\n }\n }\n\n // Install dev dependencies\n if (devDeps.length > 0) {\n const deps = devDeps\n const cmd = `${pm} add -D ${deps.join(' ')}`\n console.log(`\\n Installing ${deps.length} dev dependency(ies):`)\n for (const dep of deps) console.log(` + ${dep} (dev)`)\n console.log()\n try {\n execSync(cmd, { stdio: 'inherit' })\n } catch {\n console.log(`\\n Installation failed. Run manually:\\n ${cmd}\\n`)\n }\n }\n\n console.log(' Done!\\n')\n })\n}\n","import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'\nimport { dirname, join, relative, resolve } from 'node:path'\nimport type { Command } from 'commander'\nimport { loadKickConfig, type KickConfig } from '../config'\nimport { resolveAppRuntime, UPLOAD_DRIVERS, type AppRuntime } from './add'\nimport { colors } from '../utils/colors'\nimport { intro, outro, log } from '../utils/prompts'\n\n/**\n * `kick doctor` — pre-flight checks for a KickJS project's dev\n * environment. Detects common misconfigs before they bite, with an\n * actionable fix hint for each problem.\n *\n * Sibling command to `kick check --deploy`, which scans for production\n * readiness (JWT, CORS, helmet, etc.). Doctor is the dev-setup\n * counterpart — \"is my environment correctly wired?\"\n *\n * Extending: adopters can ship their own checks by exporting a\n * `doctor.checks` array from `kick.config.ts`. Each {@link DoctorCheck}\n * receives the same {@link DoctorContext} the built-ins use and\n * returns one or more {@link DoctorResult}s. The framework stays\n * ORM-agnostic — Prisma / Drizzle / Mongoose-specific checks belong\n * in their respective adapters or in adopter config, not in core.\n */\n\n// ── Public types ──────────────────────────────────────────────────────\n\nexport interface DoctorContext {\n cwd: string\n pkg: any | null\n tsconfig: any | null\n /**\n * The project's HTTP runtime — from kick.config `runtime`, else sniffed from\n * deps, else `express`. Lets engine-aware checks (engine peers, the upload\n * multipart driver) validate against the right backend.\n */\n runtime: AppRuntime\n}\n\nexport interface DoctorResult {\n /** Short label for the check (printed first). */\n name: string\n status: 'pass' | 'warn' | 'fail'\n /** Optional extra context after the label (e.g. resolved version). */\n message?: string\n /** Multi-line actionable fix shown when status is `warn` or `fail`. */\n fix?: string\n}\n\nexport type DoctorCheck = (\n ctx: DoctorContext,\n) => DoctorResult | DoctorResult[] | null | Promise<DoctorResult | DoctorResult[] | null>\n\n/**\n * Shape of a doctor extension — the `doctor` block on `KickConfig`,\n * also the publishable unit that plugins and shared modules use to\n * ship a bundle of related checks.\n */\nexport interface DoctorExtension {\n /** Extra checks merged after the built-ins. */\n checks?: DoctorCheck[]\n}\n\n/**\n * Identity helper for adopters / plugins authoring a doctor extension.\n *\n * Provides type inference + autocomplete on the `checks` array without\n * requiring an explicit `: DoctorExtension` annotation. Mirrors the\n * `defineConfig` pattern.\n *\n * @example\n * ```ts\n * // doctor-checks/prisma.ts (shared across projects, or shipped as a\n * // standalone package)\n * import { defineDoctorExtension } from '@forinda/kickjs-cli'\n * import { existsSync } from 'node:fs'\n * import { join } from 'node:path'\n *\n * export const prismaDoctor = defineDoctorExtension({\n * checks: [\n * (ctx) => {\n * if (!existsSync(join(ctx.cwd, 'prisma/schema.prisma'))) return null\n * const generated = join(ctx.cwd, 'node_modules/@prisma/client/default.js')\n * return existsSync(generated)\n * ? { name: 'Prisma client generated', status: 'pass' }\n * : { name: 'Prisma client generated', status: 'fail', fix: 'pnpm exec prisma generate' }\n * },\n * ],\n * })\n *\n * // kick.config.ts\n * import { defineConfig } from '@forinda/kickjs-cli'\n * import { prismaDoctor } from './doctor-checks/prisma'\n *\n * export default defineConfig({ doctor: prismaDoctor })\n * ```\n */\nexport function defineDoctorExtension(ext: DoctorExtension): DoctorExtension {\n return ext\n}\n\n/**\n * Identity helper for a single doctor check. Pairs with\n * `defineDoctorExtension` when assembling an extension from separate\n * per-check files, and gives the same type-inference win for one-offs.\n *\n * @example\n * ```ts\n * import { defineDoctorCheck } from '@forinda/kickjs-cli'\n *\n * export const checkJwtSecretLength = defineDoctorCheck((ctx) => {\n * const v = process.env.JWT_SECRET\n * if (!v || v.length < 32) {\n * return {\n * name: 'JWT_SECRET ≥ 32 chars',\n * status: 'warn',\n * fix: 'Generate a strong secret: openssl rand -hex 32',\n * }\n * }\n * return { name: 'JWT_SECRET ≥ 32 chars', status: 'pass' }\n * })\n * ```\n */\nexport function defineDoctorCheck(check: DoctorCheck): DoctorCheck {\n return check\n}\n\n// ── File helpers ──────────────────────────────────────────────────────\n\nfunction safeReadJson(filepath: string): any | null {\n try {\n return JSON.parse(readFileSync(filepath, 'utf-8'))\n } catch {\n return null\n }\n}\n\nfunction safeRead(filepath: string): string | null {\n try {\n return readFileSync(filepath, 'utf-8')\n } catch {\n return null\n }\n}\n\n/**\n * Read tsconfig.json with `extends` followed one level. Most adopters\n * use a single config; for those that extend kickjs-cli's preset, we\n * pick up the inherited `compilerOptions`. Avoids pulling in `tsc` just\n * to resolve the chain.\n */\nfunction loadTsConfig(cwd: string): any | null {\n const tsconfigPath = join(cwd, 'tsconfig.json')\n const raw = safeRead(tsconfigPath)\n if (!raw) return null\n // tsconfig files often contain comments; strip them before parsing.\n const stripped = raw.replace(/\\/\\*[\\s\\S]*?\\*\\//g, '').replace(/\\/\\/.*$/gm, '')\n let cfg: any\n try {\n cfg = JSON.parse(stripped)\n } catch {\n return null\n }\n if (typeof cfg?.extends === 'string') {\n const extendsPath = resolveExtends(cwd, cfg.extends)\n if (extendsPath) {\n const parent = safeReadJson(extendsPath) ?? {}\n cfg.compilerOptions = {\n ...parent.compilerOptions,\n ...cfg.compilerOptions,\n }\n }\n }\n return cfg\n}\n\nfunction resolveExtends(cwd: string, ext: string): string | null {\n if (ext.startsWith('.')) {\n const resolved = resolve(cwd, ext)\n return existsSync(resolved) ? resolved : null\n }\n const mod = join(cwd, 'node_modules', ext)\n if (existsSync(mod)) return mod\n return null\n}\n\nfunction escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n// ── Individual checks ─────────────────────────────────────────────────\n\nconst MIN_NODE_MAJOR = 20\n\nexport function checkNodeVersion(): DoctorResult {\n const v = process.version\n const major = Number.parseInt(v.replace(/^v/, '').split('.')[0]!, 10)\n if (Number.isNaN(major) || major < MIN_NODE_MAJOR) {\n return {\n name: 'Node version',\n status: 'fail',\n message: v,\n fix: `KickJS requires Node ${MIN_NODE_MAJOR} or newer.\\nInstall a supported version via nvm / fnm / volta.`,\n }\n }\n return { name: 'Node version', status: 'pass', message: v }\n}\n\nexport function checkKickJsInstalled(ctx: DoctorContext): DoctorResult {\n if (!ctx.pkg) {\n return { name: '@forinda/kickjs installed', status: 'warn', message: 'no package.json' }\n }\n const deps = {\n ...ctx.pkg.dependencies,\n ...ctx.pkg.peerDependencies,\n }\n if (!deps['@forinda/kickjs']) {\n return {\n name: '@forinda/kickjs installed',\n status: 'fail',\n fix: 'This directory does not look like a KickJS project — `@forinda/kickjs` is not in your package.json. Run `kick doctor` from the project root, or scaffold a fresh project with `kick new <name>`.',\n }\n }\n return { name: '@forinda/kickjs installed', status: 'pass', message: deps['@forinda/kickjs'] }\n}\n\nexport function checkExpressInstalled(ctx: DoctorContext): DoctorResult | null {\n if (!ctx.pkg) return null\n const deps = {\n ...ctx.pkg.dependencies,\n ...ctx.pkg.peerDependencies,\n }\n if (deps['@forinda/kickjs'] && !deps.express) {\n return {\n name: 'express installed',\n status: 'fail',\n fix: '`@forinda/kickjs` declares `express` as a required peer dependency, but your package.json does not include it. Install: pnpm add express',\n }\n }\n return deps.express ? { name: 'express installed', status: 'pass', message: deps.express } : null\n}\n\n/** Engine peers required by the configured non-Express runtime. */\nconst RUNTIME_PEERS: Record<AppRuntime, string[]> = {\n express: [], // covered by checkExpressInstalled\n fastify: ['fastify', '@fastify/middie'],\n h3: ['h3'],\n}\n\n/**\n * The configured runtime's engine peers are installed. Express is checked by\n * {@link checkExpressInstalled}; this covers the Fastify / h3 runtimes, whose\n * peers are optional on `@forinda/kickjs` and so only required when chosen.\n */\nexport function checkRuntimeEngine(ctx: DoctorContext): DoctorResult | null {\n if (!ctx.pkg || ctx.runtime === 'express') return null\n const deps = {\n ...ctx.pkg.dependencies,\n ...ctx.pkg.peerDependencies,\n ...ctx.pkg.devDependencies,\n }\n const missing = RUNTIME_PEERS[ctx.runtime].filter((p) => !deps[p])\n const name = `runtime engine (${ctx.runtime})`\n if (missing.length > 0) {\n return {\n name,\n status: 'fail',\n fix: `Resolved runtime '${ctx.runtime}' is missing engine peer(s): ${missing.join(', ')}.\\nInstall: pnpm add ${missing.join(' ')}`,\n }\n }\n return { name, status: 'pass' }\n}\n\n/**\n * When the project actually uses file uploads (`@FileUpload` / the `upload`\n * middleware), the multipart driver for its runtime must be installed —\n * express → multer, fastify → @fastify/multipart, h3 → built-in (nothing).\n * Skips silently when no upload usage is detected, so non-upload apps aren't\n * nagged about a driver they don't need.\n */\nexport function checkUploadDriver(ctx: DoctorContext): DoctorResult | null {\n if (!ctx.pkg || !srcUsesUpload(ctx.cwd)) return null\n const driver = UPLOAD_DRIVERS[ctx.runtime]\n const name = `upload driver (${ctx.runtime})`\n // h3 parses multipart natively — there is no driver to require.\n if (!driver.prod) return { name, status: 'pass', message: 'native multipart' }\n const deps = {\n ...ctx.pkg.dependencies,\n ...ctx.pkg.peerDependencies,\n ...ctx.pkg.devDependencies,\n }\n if (!deps[driver.prod]) {\n return {\n name,\n status: 'fail',\n fix: `This project uses file uploads on the '${ctx.runtime}' runtime, which needs '${driver.prod}'.\\nInstall it: kick add upload (or pnpm add ${driver.prod})`,\n }\n }\n return { name, status: 'pass', message: driver.prod }\n}\n\nconst MAX_UPLOAD_SCAN_FILES = 2000\n\n/** Bounded walk of src/ looking for @FileUpload / upload.single|array|none usage. */\nfunction srcUsesUpload(cwd: string): boolean {\n const srcDir = join(cwd, 'src')\n if (!existsSync(srcDir)) return false\n const pattern = /@FileUpload\\b|\\bupload\\.(single|array|none)\\s*\\(/\n const stack = [srcDir]\n let visited = 0\n while (stack.length > 0 && visited < MAX_UPLOAD_SCAN_FILES) {\n const current = stack.pop()!\n let entries\n try {\n entries = readdirSync(current, { withFileTypes: true })\n } catch {\n continue\n }\n for (const entry of entries) {\n if (visited >= MAX_UPLOAD_SCAN_FILES) break\n const full = join(current, entry.name)\n if (entry.isDirectory()) {\n if (entry.name !== 'node_modules') stack.push(full)\n continue\n }\n if (!/\\.(ts|tsx|mts|cts)$/.test(entry.name)) continue\n visited++\n if (pattern.test(safeRead(full) ?? '')) return true\n }\n }\n return false\n}\n\nexport function checkReflectMetadata(ctx: DoctorContext): DoctorResult {\n if (!ctx.pkg)\n return { name: 'reflect-metadata installed', status: 'warn', message: 'no package.json' }\n const deps = {\n ...ctx.pkg.dependencies,\n ...ctx.pkg.peerDependencies,\n ...ctx.pkg.devDependencies,\n }\n if (!deps['reflect-metadata']) {\n return {\n name: 'reflect-metadata installed',\n status: 'fail',\n fix: `KickJS decorators require the reflect-metadata polyfill.\\nInstall it: pnpm add reflect-metadata\\nThen import it at the top of src/index.ts:\\n\\n import 'reflect-metadata'\\n // ... rest of bootstrap`,\n }\n }\n return { name: 'reflect-metadata installed', status: 'pass', message: deps['reflect-metadata'] }\n}\n\nexport function checkDecoratorTsConfig(ctx: DoctorContext): DoctorResult[] {\n if (!ctx.tsconfig) {\n return [\n {\n name: 'tsconfig.json present',\n status: 'fail',\n fix: 'Create a tsconfig.json with `experimentalDecorators: true` and `emitDecoratorMetadata: true`. `kick new` scaffolds one automatically.',\n },\n ]\n }\n const co = ctx.tsconfig.compilerOptions ?? {}\n const results: DoctorResult[] = []\n results.push(\n co.experimentalDecorators === true\n ? { name: 'tsconfig: experimentalDecorators', status: 'pass' }\n : {\n name: 'tsconfig: experimentalDecorators',\n status: 'fail',\n fix: 'Add `\"experimentalDecorators\": true` to compilerOptions in tsconfig.json. Without it, @Service / @Controller / @Get etc. don\\'t register any metadata at compile time.',\n },\n )\n results.push(\n co.emitDecoratorMetadata === true\n ? { name: 'tsconfig: emitDecoratorMetadata', status: 'pass' }\n : {\n name: 'tsconfig: emitDecoratorMetadata',\n status: 'fail',\n fix: 'Add `\"emitDecoratorMetadata\": true` to compilerOptions in tsconfig.json. The DI container uses this metadata for constructor-parameter injection.',\n },\n )\n return results\n}\n\n/**\n * The canonical \"you forgot to wire env\" footgun: an env-init file\n * (e.g. `src/env.ts`, `src/config/env.ts`, `src/config/index.ts`)\n * calls `loadEnv(envSchema)` as a side effect, but the app entry\n * (`src/index.ts` / `src/main.ts`) doesn't import it. Result:\n * `ConfigService.get('X')` returns undefined while `@Value('X')`\n * works via process.env fallback, so adopters get half-broken config\n * without seeing an error.\n *\n * Detection walks the common env-file locations, identifies the ones\n * that actually call `loadEnv(`, then verifies the app entry imports\n * one of them (via relative path or `@/` alias) BEFORE `bootstrap(`.\n */\nexport function checkEnvWiring(ctx: DoctorContext): DoctorResult | null {\n const envCandidates = [\n 'src/env.ts',\n 'src/env/index.ts',\n 'src/config/env.ts',\n 'src/config/index.ts',\n ]\n const envFiles = envCandidates\n .map((p) => join(ctx.cwd, p))\n .filter((p) => existsSync(p))\n .filter((p) => /\\bloadEnv\\s*\\(/.test(safeRead(p) ?? ''))\n\n if (envFiles.length === 0) return null\n\n const indexPath = ['src/index.ts', 'src/main.ts']\n .map((p) => join(ctx.cwd, p))\n .find((p) => existsSync(p))\n if (!indexPath) {\n return {\n name: 'env wiring',\n status: 'warn',\n message: 'env-init file exists but no src/index.ts or src/main.ts found',\n }\n }\n const indexContent = safeRead(indexPath) ?? ''\n const indexDir = dirname(indexPath)\n\n // Build the set of import specifiers that would wire any of these\n // env files — relative paths (`./env`, `./config/env`, …) and the\n // `@/` alias variant (`@/env`, `@/config/env`, …). For each file we\n // accept both the with-`/index` suffix and the bare-directory form,\n // since both resolve to the same module under TypeScript.\n const importSpecs: string[] = []\n for (const envFile of envFiles) {\n const relPath = relative(indexDir, envFile).replace(/\\\\/g, '/').replace(/\\.ts$/, '')\n const relImport = relPath.startsWith('.') ? relPath : './' + relPath\n const relImportNoIndex = relImport.replace(/\\/index$/, '')\n importSpecs.push(relImport, relImportNoIndex)\n\n // `@/` is the standard alias the scaffold and most adopters use;\n // it points at `src/`. Derive the @/-prefixed path from the file's\n // location under src/.\n const srcRel = envFile.replace(/\\\\/g, '/').match(/\\/src\\/(.+?)(?:\\.ts)?$/)\n if (srcRel) {\n const aliasImport = '@/' + srcRel[1]\n const aliasNoIndex = aliasImport.replace(/\\/index$/, '')\n importSpecs.push(aliasImport, aliasNoIndex)\n }\n }\n\n let earliestImportIdx = -1\n for (const spec of new Set(importSpecs)) {\n const re = new RegExp(`^import\\\\s+(?:.*?from\\\\s+)?['\"]${escapeRegExp(spec)}['\"]`, 'm')\n const m = indexContent.match(re)\n if (m && m.index !== undefined) {\n if (earliestImportIdx === -1 || m.index < earliestImportIdx) {\n earliestImportIdx = m.index\n }\n }\n }\n\n const bootstrapIdx = indexContent.search(/\\bbootstrap\\s*\\(/)\n const sample = envFiles.map((p) => relative(ctx.cwd, p).replace(/\\\\/g, '/')).join(', ')\n\n if (earliestImportIdx === -1) {\n return {\n name: 'env wiring',\n status: 'fail',\n message: sample,\n fix: `An env-init file (${sample}) calls \\`loadEnv(...)\\` but \\`${relative(ctx.cwd, indexPath).replace(/\\\\/g, '/')}\\` doesn't import it.\\nWithout this, ConfigService.get('X') returns undefined while @Value('X') works via process.env fallback — a half-broken config you won't notice until something is missing.\\n\\nFix: add a side-effect import at the top of ${relative(ctx.cwd, indexPath).replace(/\\\\/g, '/')} (above bootstrap()), pointing at one of the detected files. For example:\\n\\n import './env'\\n // or\\n import './config'\\n // or, with the @/ alias:\\n import '@/config/env'`,\n }\n }\n if (bootstrapIdx !== -1 && earliestImportIdx > bootstrapIdx) {\n return {\n name: 'env wiring',\n status: 'warn',\n message: 'env-init imported AFTER bootstrap() — should be before',\n fix: `Move the env import above the bootstrap() call so the schema runs before any service reads from ConfigService.`,\n }\n }\n return { name: 'env wiring', status: 'pass' }\n}\n\n/**\n * Find the newest file mtime under a directory tree, recursively.\n * Returns `0` when the tree is empty or unreadable. Walks at most\n * {@link MAX_TYPEGEN_FILES} entries to bound the cost on large\n * generated trees.\n *\n * Necessary because the directory's own `mtimeMs` does not update\n * when existing files inside are rewritten — `kick typegen` can run\n * successfully and the directory stat still looks stale. The newest\n * file mtime is the honest measure.\n */\nfunction newestFileMtime(dir: string, budget = MAX_TYPEGEN_FILES): number {\n let newest = 0\n let visited = 0\n const stack = [dir]\n while (stack.length > 0 && visited < budget) {\n const current = stack.pop()!\n let entries\n try {\n entries = readdirSync(current, { withFileTypes: true })\n } catch {\n continue\n }\n for (const entry of entries) {\n if (visited >= budget) break\n visited++\n const full = join(current, entry.name)\n if (entry.isDirectory()) {\n stack.push(full)\n continue\n }\n try {\n const m = statSync(full).mtimeMs\n if (m > newest) newest = m\n } catch {\n // skip unreadable files\n }\n }\n }\n return newest\n}\n\nconst MAX_TYPEGEN_FILES = 2000\n\nexport function checkTypegenFreshness(ctx: DoctorContext): DoctorResult | null {\n const typegenDir = join(ctx.cwd, '.kickjs', 'types')\n if (!existsSync(typegenDir)) return null\n const newest = newestFileMtime(typegenDir)\n if (newest === 0) return null\n const ageMs = Date.now() - newest\n const ageMin = Math.floor(ageMs / 60_000)\n if (ageMin > 60) {\n return {\n name: 'typegen freshness',\n status: 'warn',\n message: `last updated ${ageMin} minutes ago`,\n fix: 'Re-run `kick typegen` (or `kick dev`, which runs it on every reload) so generated types match the current code.',\n }\n }\n return {\n name: 'typegen freshness',\n status: 'pass',\n message: ageMin === 0 ? 'just now' : `${ageMin}m ago`,\n }\n}\n\n// ── Runner ────────────────────────────────────────────────────────────\n\nconst BUILT_IN_CHECKS: DoctorCheck[] = [\n () => checkNodeVersion(),\n checkKickJsInstalled,\n checkExpressInstalled,\n checkRuntimeEngine,\n checkUploadDriver,\n checkReflectMetadata,\n checkDecoratorTsConfig,\n checkEnvWiring,\n checkTypegenFreshness,\n]\n\nexport async function runChecks(\n cwd: string,\n options: { extraChecks?: DoctorCheck[]; runtime?: AppRuntime } = {},\n): Promise<DoctorResult[]> {\n const ctx: DoctorContext = {\n cwd,\n pkg: safeReadJson(join(cwd, 'package.json')),\n tsconfig: loadTsConfig(cwd),\n runtime: options.runtime ?? 'express',\n }\n const checks = [...BUILT_IN_CHECKS, ...(options.extraChecks ?? [])]\n const out: DoctorResult[] = []\n for (const check of checks) {\n // Per-check try/catch so one buggy extension can't abort the whole\n // report. A throwing check produces a synthetic `fail` result with\n // the error message; the loop continues to the next check.\n let r: DoctorResult | DoctorResult[] | null\n try {\n r = await check(ctx)\n } catch (err) {\n out.push({\n name: check.name || 'doctor check',\n status: 'fail',\n message: err instanceof Error ? err.message : String(err),\n })\n continue\n }\n if (r == null) continue\n if (Array.isArray(r)) out.push(...r)\n else out.push(r)\n }\n return out\n}\n\n// ── Output ────────────────────────────────────────────────────────────\n\nfunction statusTag(status: DoctorResult['status']): string {\n switch (status) {\n case 'pass':\n return colors.green('✔')\n case 'warn':\n return colors.yellow('⚠')\n case 'fail':\n return colors.red('✖')\n }\n}\n\nfunction formatResult(r: DoctorResult): string {\n const tag = statusTag(r.status)\n const tail = r.message ? ` ${colors.dim(`(${r.message})`)}` : ''\n return `${tag} ${r.name}${tail}`\n}\n\nfunction formatFix(fix: string): string {\n return fix\n .split('\\n')\n .map((line) => ` ${colors.dim('→')} ${line}`)\n .join('\\n')\n}\n\n// ── Command registration ──────────────────────────────────────────────\n\nfunction extractDoctorChecks(config: KickConfig | null): DoctorCheck[] {\n return config?.doctor?.checks ?? []\n}\n\nexport function registerDoctorCommand(program: Command): void {\n program\n .command('doctor')\n .description('Pre-flight checks for your KickJS project (dev environment health)')\n .action(async () => {\n const cwd = process.cwd()\n const config = await loadKickConfig(cwd)\n const extraChecks = extractDoctorChecks(config)\n const runtime = await resolveAppRuntime(cwd)\n\n intro('KickJS Doctor')\n\n const results = await runChecks(cwd, { extraChecks, runtime })\n\n for (const r of results) {\n log.message(formatResult(r))\n if (r.fix && r.status !== 'pass') {\n log.message(formatFix(r.fix))\n }\n }\n\n const passed = results.filter((r) => r.status === 'pass').length\n const warns = results.filter((r) => r.status === 'warn').length\n const fails = results.filter((r) => r.status === 'fail').length\n\n const passText = colors.green(`${passed} passed`)\n const warnText =\n warns > 0 ? colors.yellow(`${warns} warning${warns === 1 ? '' : 's'}`) : `${warns} warnings`\n const failText =\n fails > 0 ? colors.red(`${fails} error${fails === 1 ? '' : 's'}`) : `${fails} errors`\n const summary = [passText, warnText, failText].join(', ')\n\n if (fails > 0) {\n outro(`${summary} — fix the errors above before running the app`)\n process.exit(1)\n } else if (warns > 0) {\n outro(`${summary} — review the warnings`)\n } else {\n outro(colors.green(`${summary} — your environment looks good`))\n }\n })\n}\n"],"mappings":"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"}
|