@forinda/kickjs-cli 8.10.2 → 8.12.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.
Files changed (90) hide show
  1. package/dist/{agent-docs-As_KjQBC.mjs → agent-docs-CuD2-X3w.mjs} +30 -26
  2. package/dist/{agent-docs-ZgfnGL-j.mjs → agent-docs-ykxoPqXX.mjs} +23 -19
  3. package/dist/agent-docs-ykxoPqXX.mjs.map +1 -0
  4. package/dist/{build-BfC9Kkt-.mjs → build-C-AsmCh1.mjs} +2 -2
  5. package/dist/{build-BuBCsnzr.mjs → build-DQnV0GCD.mjs} +3 -3
  6. package/dist/{build-BuBCsnzr.mjs.map → build-DQnV0GCD.mjs.map} +1 -1
  7. package/dist/{builtins-DXASTS7y.mjs → builtins-BsYCQU80.mjs} +2 -2
  8. package/dist/{builtins-397XZB9_.mjs → builtins-Cjp9FT1m.mjs} +392 -185
  9. package/dist/cli.mjs +2 -2
  10. package/dist/config-DMFwBdWg.mjs +12 -0
  11. package/dist/config-DbnBakh8.mjs +13 -0
  12. package/dist/config-DbnBakh8.mjs.map +1 -0
  13. package/dist/{doctor-DGWvBOvj.mjs → doctor-rEbeoAoO.mjs} +282 -75
  14. package/dist/doctor-rEbeoAoO.mjs.map +1 -0
  15. package/dist/{fullstack-x16HfCRz.mjs → fullstack-DdSScENL.mjs} +7 -7
  16. package/dist/{fullstack-BqYdhTDI.mjs → fullstack-XhuwRQEb.mjs} +8 -8
  17. package/dist/{fullstack-BqYdhTDI.mjs.map → fullstack-XhuwRQEb.mjs.map} +1 -1
  18. package/dist/index.d.mts +29 -7
  19. package/dist/index.d.mts.map +1 -1
  20. package/dist/index.mjs +2 -2
  21. package/dist/package-manager-C8ekNwsa.mjs +11 -0
  22. package/dist/package-manager-DvXi-NGm.mjs +12 -0
  23. package/dist/package-manager-DvXi-NGm.mjs.map +1 -0
  24. package/dist/{plugin-HazJu53p.mjs → plugin--9hxxgsD.mjs} +2 -2
  25. package/dist/{plugin-Dh0LYWl5.mjs → plugin-CvlUt9NG.mjs} +2 -2
  26. package/dist/project-BN1DnrN3.mjs +134 -0
  27. package/dist/project-BN1DnrN3.mjs.map +1 -0
  28. package/dist/project-DBmrFgdd.mjs +133 -0
  29. package/dist/{project-root-DGKTJDH3.mjs → project-root-BXmtd-te.mjs} +2 -2
  30. package/dist/{project-root-DlgCWwc_.mjs → project-root-FMt83NHV.mjs} +3 -3
  31. package/dist/{project-root-DlgCWwc_.mjs.map → project-root-FMt83NHV.mjs.map} +1 -1
  32. package/dist/{prompts-D4nwf5mP.mjs → prompts-DJBDNwHt.mjs} +2 -2
  33. package/dist/{prompts-D4nwf5mP.mjs.map → prompts-DJBDNwHt.mjs.map} +1 -1
  34. package/dist/{rolldown-runtime-7c5UUG6D.mjs → rolldown-runtime-C4d34LEE.mjs} +1 -1
  35. package/dist/{run-plugins-Dx-QENlm.mjs → run-plugins-9tjbMqS6.mjs} +87 -87
  36. package/dist/run-plugins-9tjbMqS6.mjs.map +1 -0
  37. package/dist/typegen-AGdhxyRc.mjs +107 -0
  38. package/dist/typegen-AGdhxyRc.mjs.map +1 -0
  39. package/dist/typegen-Lf0UPhdl.mjs +106 -0
  40. package/dist/{types-CtrnAiNl.mjs → types-B5dY2mTz.mjs} +1 -1
  41. package/package.json +3 -3
  42. package/templates/base/files/README.md +2 -2
  43. package/templates/base/files/kick.config.ts +1 -1
  44. package/templates/base/files/src/modules/hello/hello.controller.ts +0 -5
  45. package/templates/base/files/src/modules/hello/hello.service.ts +0 -4
  46. package/templates/base/files/tsconfig.json +3 -1
  47. package/templates/feature-devtools/feature.json +2 -6
  48. package/templates/feature-devtools/files/src/devtools.d.ts +3 -0
  49. package/templates/feature-queue/feature.json +39 -3
  50. package/templates/feature-queue/files/_dot_env.example.append +4 -2
  51. package/templates/feature-queue/files/src/adapters/queue.ts +16 -0
  52. package/templates/feature-storage/feature.json +1 -1
  53. package/templates/schema-valibot/files/src/config/index.ts +5 -2
  54. package/templates/schema-yup/files/src/config/index.ts +5 -2
  55. package/templates/schema-zod/files/src/config/index.ts +5 -2
  56. package/templates/template-inertia/feature.json +9 -1
  57. package/templates/template-inertia/files/_dot_env.append +2 -0
  58. package/templates/template-inertia/files/_dot_env.example.append +6 -2
  59. package/templates/template-inertia/files/_dot_env.test.append +1 -0
  60. package/templates/template-inertia/files/_dot_env.test.example.append +1 -0
  61. package/templates/template-inertia/files/src/client/Layout.tsx +2 -0
  62. package/templates/template-inertia/files/src/client/Seo.tsx +35 -0
  63. package/templates/template-inertia/files/src/index.ts +11 -5
  64. package/templates/template-inertia/files/src/modules/index.ts +2 -1
  65. package/templates/template-inertia/files/src/modules/pages/pages.controller.ts +18 -6
  66. package/templates/template-inertia/files/src/modules/pages/pages.test.ts +34 -0
  67. package/templates/template-inertia/files/src/modules/seo/seo.controller.ts +19 -0
  68. package/templates/template-inertia/files/src/modules/seo/seo.module.ts +12 -0
  69. package/templates/template-inertia/files/tsconfig.json +3 -1
  70. package/templates/template-inertia/files/vite.config.ts +3 -2
  71. package/templates/web-kick/files/web/_dot_oxfmtrc.json +9 -0
  72. package/templates/web-kick/files/web/src/App.tsx +1 -7
  73. package/dist/agent-docs-ZgfnGL-j.mjs.map +0 -1
  74. package/dist/config-Ck9aQVpE.mjs +0 -12
  75. package/dist/config-CsOfUFfq.mjs +0 -13
  76. package/dist/config-CsOfUFfq.mjs.map +0 -1
  77. package/dist/doctor-DGWvBOvj.mjs.map +0 -1
  78. package/dist/package-manager--IUl91nq.mjs +0 -11
  79. package/dist/package-manager-DBmD5nSr.mjs +0 -12
  80. package/dist/package-manager-DBmD5nSr.mjs.map +0 -1
  81. package/dist/project-C98JovY4.mjs +0 -115
  82. package/dist/project-C98JovY4.mjs.map +0 -1
  83. package/dist/project-TbTVQCnh.mjs +0 -114
  84. package/dist/run-plugins-Dx-QENlm.mjs.map +0 -1
  85. package/dist/typegen-DJQ6WBwr.mjs +0 -106
  86. package/dist/typegen-DSaawXaQ.mjs +0 -107
  87. package/dist/typegen-DSaawXaQ.mjs.map +0 -1
  88. package/templates/feature-queue/files/_dot_env.append +0 -2
  89. package/templates/template-inertia/files/public/robots.txt +0 -4
  90. /package/templates/feature-storage/files/src/{storage.ts → adapters/storage.ts} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor-rEbeoAoO.mjs","names":["colors","colors","fileExists","writeFileSafe","detectPackageManager","outro","intro","log","confirm","pc","loadKickConfig","resolveAppRuntime","UPLOAD_DRIVERS","findProjectRoot","path","pkg","toPascalCase","name","c","toCamelCase","pascal","toKebabCase","pluralize","pluralizePascal","escapeRegex","input","repoLabelMap","toPascalRepoType","repo","repoLabel","repoFactory","resolveStyle","style","generateRestModuleIndex","ctx","route","plural","factory","resolvedStyle","header","kebab","repoImports","routesDoc","generateMinimalModuleIndex","generateRestController","swaggerImport","apiTags","camel","SCHEMA_SHAPES","schema","objectSchema","lib","body","generateCreateDTO","shape","libName","generateUpdateDTO","generateResponseDTO","generateRepositoryFactory","store","repoType","dtoPrefix","tokenScope","generateControllerTest","moduleEntry","testHarness","testImport","generateRepositoryTest","repoPrefix","generateRestService","SEARCHABLE","UNQUERYABLE","generateRestConstants","queryable","fields","f","searchable","list","names","n","generateMinimalFiles","write","generateRestFiles","swagger","schemaLib","noTests","resolveRepoType","config","generateModule","options","shouldPluralize","pattern","pluralPascal","routePath","moduleDir","join","modulesDir","files","overwriteAll","force","noEntity","fullPath","relativePath","dryRun","content","autoRegisterModule","indexPath","exists","importPath","entryToken","initialBody","readFile","importLine","escapedImportPath","lastImportIdx","lineEnd","span","findModulesRhsSpan","rhsSlice","appendModuleEntry","inside","trimmed","rewritten","needsComma","declMatch","rhsStart","close","balancedBracketClose","chainEnd","findChainEnd","callRegex","fromIdx","match","src","balancedClose","j","skipComment","two","i","openIdx","depth","next","ch","quote","devtoolsHooks","kind","indent","line","generateAdapter","filePath","outDir","FLAT_FOLDER_MAP","resolveOutDir","resolve","moduleName","folderMap","folder","subfolder","type","base","defaultDir","generateMiddleware","isExpress","engineClause","generateGuard","generateService","generateController","generateDto","isDebugLog","level","TypegenDriftError","drifted","d","defineTypegen","spec","createTypegenErrorReporter","lastMessage","msg","err","source","emit","TYPEGEN_OWNER_KEY","createTypegenDevWatcher","opts","debounceMs","pipeline","o","dir","results","silent","cfg","schemaValidator","envFile","typesOutDir","cwd","assetSrcRoots","entry","hasAssetMap","toPosix","p","assetRootsPosix","isAssetFile","posix","file","root","reporter","timer","disposed","pendingChanged","pendingRemoved","forceFullScan","assetDirty","firePass","delta","r","rebuildAssets","flush","event","isTs","isAsset","toSnakeCase","buildGeneratorContext","projectRoot","usePlural","snake","pluralKebab","resolveGeneratorPath","importManifest","pathToFileURL","absPath","cache","discoverPluginGenerators","cached","promise","doDiscover","projectPkgPath","existsSync","depNames","collectDepNames","require","createRequire","generators","loaded","failed","depName","depPkgPath","depPkg","entryRel","entryAbs","dirname","mod","manifest","isGeneratorSpec","set","block","e","tryDispatchPluginGenerator","fromConfig","additional","g","runGenerator","findGenerator","listPluginGenerators","discovered","configNames","filteredDiscovered","discovery","written","defineDoctorExtension","ext","defineDoctorCheck","check","safeReadJson","readFileSync","filepath","safeRead","parseJsonc","stripped","raw","loadTsConfig","tsconfigPath","loadTsConfigFrom","seen","parents","inherited","parentPath","resolveExtends","parent","resolveConfigTarget","fromDir","candidate","target","statSync","nested","withJson","escapeRegExp","s","checkNodeVersion","v","major","checkKickJsInstalled","deps","checkExpressInstalled","RUNTIME_PEERS","checkRuntimeEngine","missing","h3Major","version","range","checkUploadDriver","srcUsesUpload","driver","MAX_UPLOAD_SCAN_FILES","srcDir","stack","visited","current","entries","readdirSync","full","checkReflectMetadata","checkDecoratorTsConfig","co","checkEnvWiring","envFiles","indexContent","indexDir","importSpecs","relPath","relative","relImport","relImportNoIndex","srcRel","aliasImport","aliasNoIndex","earliestImportIdx","re","m","bootstrapIdx","sample","newestFileMtime","MAX_TYPEGEN_FILES","newest","budget","checkTypegenFreshness","typegenDir","ageMs","ageMin","TEST_RUNNER_CONFIGS","TEST_ENV_FILES","GENERIC_ENV_FILES","findTestEnvFileOverride","scripts","value","hit","\"KICKJS_ENV_FILE\"","checkTestEnvIsolation","generic","isolating","override","BUILT_IN_CHECKS","runChecks","checks","out","statusTag","status","formatResult","tag","tail","formatFix","fix","extractDoctorChecks","registerDoctorCommand","program","extraChecks","runtime","passed","warns","fails","summary","defineGenerator"],"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/patterns/minimal.ts","../src/generators/patterns/rest.ts","../src/generators/module.ts","../src/generators/devtools-hooks.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/typegen/plugin.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/doctor.ts"],"sourcesContent":["import pkg from 'pluralize'\n\n// Names of a concern, not a countable resource: `kick g module auth` is\n// `auth/`, not `auths/`. (`health`, `news`, `metadata` are uncountable in\n// the library already.)\npkg.addUncountableRule('auth')\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 repoLabel(repo: RepoType): string {\n return repoLabelMap[repo] ?? toPascalRepoType(repo)\n}\n\n/**\n * Class and file basename for the repository implementation.\n *\n * Only `inmemory` is special: it is the one built-in with a real working\n * implementation, and its name describes what it actually is.\n *\n * Everything else — including `drizzle` and `prisma`, which no longer have\n * dedicated generators — scaffolds the same unimplemented stub, so naming it\n * after the store would assert a technology the file does not implement. It\n * takes the module's name instead, which the folder already carries. The\n * chosen store still appears in the stub's prose and its error messages.\n */\n/** The repository factory to call when registering the binding. */\nfunction repoFactory(pascal: string): string {\n return `create${pascal}Repository`\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/** 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 route = ctx.routePath ?? plural\n const factory = repoFactory(pascal)\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: factory, contract, token\n * dtos/ — Request/response schemas\n *\n * The repository is backed by an in-memory Map so this module works as\n * generated. Swap in ${repo === 'inmemory' ? 'a database' : repoLabel(repo)} by replacing the factory body in\n * ${kebab}.repository.ts — the contract is whatever that factory returns, so\n * nothing else has to change.\n */`\n\n const repoImports = `import { ${pascal.toUpperCase()}_REPOSITORY, ${factory} } from './${kebab}.repository'\nimport { ${pascal}Controller } from './${kebab}.controller'\n\n// Eagerly load every module file so decorators (@Controller / @Service /\n// @Repository, and anything you add) register in the DI container. The glob is\n// deliberately broad: a suffix list only covers the names the generator happens\n// to emit, so a hand-written \\`*.usecase.ts\\` or \\`*.policy.ts\\` silently never\n// registered and failed later as \\`No provider for X\\` (#609). Recursive (./**/)\n// so nesting keeps working.\nimport.meta.glob(['./**/*.ts', '!./**/*.test.ts', '!./**/*.d.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: '/${route}', version: 1, controller: ${pascal}V1Controller },\n * { path: '/${route}', 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, () => ${factory}())\n }\n\n${routesDoc.replace(/^ {4}/gm, ' ').replace(/^ {6}/gm, ' ')}\n routes(): ModuleRoutes {\n return {\n path: '/${route}',\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 ${factory}(),\n )\n },\n\n${routesDoc}\n routes() {\n return {\n path: '/${route}',\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 route = ctx.routePath ?? plural\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: '/${route}', version: 1, controller: ${pascal}V1Controller },\n * { path: '/${route}', 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: '/${route}',\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: '/${route}',\n controller: ${pascal}Controller,\n }\n },\n }),\n})\n`\n}\n","import type { TemplateContext } from './types'\n\n/** REST controller — injects service directly, flat import paths */\nexport function generateRestController(ctx: TemplateContext): string {\n const { pascal, kebab } = ctx\n // `@ApiTags` lives in @forinda/kickjs-swagger. Emitting it for a project\n // that does not depend on that package writes a controller which cannot\n // compile — five decorators and an unresolvable import, in a file the\n // adopter did not write. `kick add swagger` turns it on for later modules.\n const swaggerImport = ctx.swagger ? \"import { ApiTags } from '@forinda/kickjs-swagger'\\n\" : ''\n const apiTags = ctx.swagger ? ` @ApiTags('${ctx.pascal}')\\n` : ''\n const camel = pascal.charAt(0).toLowerCase() + pascal.slice(1)\n // Wrapped exactly as oxfmt would at the scaffold's printWidth of 100 —\n // a single-line import is 112 chars, so `kick format` on a fresh project\n // would otherwise rewrite this file on the adopter's first run.\n return `import {\n Controller,\n Get,\n Post,\n Patch,\n Delete,\n Autowired,\n ApiQueryParams,\n reply,\n type Ctx,\n} from '@forinda/kickjs'\n${swaggerImport}import { ${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://kickjs.app/guide/typegen.\n//\n// Handlers RETURN their payload rather than calling \\`ctx.json()\\`: the runtime\n// auto-sends it, and \\`kick typegen\\` reads the return type to fill in\n// \\`KickRoutes[...].response\\` — which is what makes the typed client know what\n// each route gives back. A handler that writes via \\`ctx.json()\\` and returns\n// nothing infers as \\`unknown\\`. Use \\`reply(status, body)\\` (or the\n// \\`reply.created\\` / \\`reply.noContent\\` sugars) when the status isn't 200, and\n// \\`ctx.problem.*\\` for error branches — those send immediately and return\n// nothing, so they stay out of the success type.\n\n// Require a signed-in user for these routes: https://kickjs.app/guide/authentication\n@Controller()\nexport class ${pascal}Controller {\n @Autowired() private readonly ${camel}Service!: ${pascal}Service\n\n // Offset pages (?page=&limit=). For keyset pages (?cursor=&limit=) — stable\n // under concurrent inserts, no deep OFFSET scans — swap the body for:\n // return ctx.paginateCursor(\n // (q) => this.${camel}Service.findAfter({ ...q, limit: q.limit + 1 }),\n // ${pascal.toUpperCase()}_QUERY_CONFIG,\n // )\n // https://kickjs.app/guide/query-parsing#cursor-pagination\n @Get('/')\n${apiTags} @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} async getById(ctx: Ctx<KickRoutes.${pascal}Controller['getById']>) {\n const result = await this.${camel}Service.findById(ctx.params.id)\n if (!result) {\n // Sends RFC 9457 problem+json now; returning nothing keeps the 404 out\n // of this route's inferred response type.\n ctx.problem.notFound({ detail: \\`${pascal} \\${ctx.params.id} not found\\` })\n return\n }\n return result\n }\n\n @Post('/', { body: create${pascal}Schema, name: 'Create${pascal}' })\n${apiTags} async create(ctx: Ctx<KickRoutes.${pascal}Controller['create']>) {\n const created = await this.${camel}Service.create(ctx.body)\n // 201 + Location: where the new ${kebab} lives, built from the getById route.\n return reply.created(created, {\n location: ctx.urlFor('${pascal}Controller.getById', { id: created.id }),\n })\n }\n\n @Patch('/:id', { body: update${pascal}Schema, name: 'Update${pascal}' })\n${apiTags} async update(ctx: Ctx<KickRoutes.${pascal}Controller['update']>) {\n return this.${camel}Service.update(ctx.params.id, ctx.body)\n }\n\n @Delete('/:id')\n${apiTags} async remove(ctx: Ctx<KickRoutes.${pascal}Controller['remove']>) {\n await this.${camel}Service.delete(ctx.params.id)\n return reply.noContent()\n }\n}\n`\n}\n","import type { SchemaLib } from '../../config'\nimport type { TemplateContext } from './types'\n\n/**\n * Body-schema source for one field, per library.\n *\n * The schemas are emitted RAW — no `fromZod` / `fromValibot` wrapper. Both\n * consumers already handle that: `detectSchema()` sniffs the library at\n * runtime, and `InferSchemaOutput<T>` reads Standard Schema / `_output` /\n * `__outputType` for typegen. Wrapping would only add an import.\n */\nexport const SCHEMA_SHAPES: Record<\n SchemaLib,\n {\n import: string\n /** Required field, 1-200 chars. */\n required: string\n /** Same field, optional — the update DTO. */\n optional: string\n infer: (schema: string) => string\n hint: string\n }\n> = {\n zod: {\n import: `import { z } from 'zod'`,\n required: `z.string().min(1, { error: 'Name is required' }).max(200)`,\n optional: `z.string().min(1).max(200).optional()`,\n infer: (schema) => `z.infer<typeof ${schema}>`,\n hint: [\n ' * z.string(), z.number(), z.boolean(), z.enum([...]),',\n ' * z.array(), z.object(), .optional(), .default(), .transform()',\n ].join('\\n'),\n },\n valibot: {\n import: `import * as v from 'valibot'`,\n required: `v.pipe(v.string(), v.minLength(1, 'Name is required'), v.maxLength(200))`,\n optional: `v.optional(v.pipe(v.string(), v.minLength(1), v.maxLength(200)))`,\n infer: (schema) => `v.InferOutput<typeof ${schema}>`,\n hint: [\n ' * v.string(), v.number(), v.boolean(), v.picklist([...]),',\n ' * v.array(), v.object(), v.optional(), and v.pipe(...) to chain checks',\n ].join('\\n'),\n },\n yup: {\n import: `import * as yup from 'yup'`,\n required: `yup.string().required('Name is required').min(1).max(200)`,\n optional: `yup.string().min(1).max(200).optional()`,\n infer: (schema) => `yup.InferType<typeof ${schema}>`,\n hint: [\n ' * yup.string(), yup.number(), yup.boolean(), .oneOf([...]),',\n ' * yup.array(), yup.object(), .required(), .optional(), .default()',\n ].join('\\n'),\n },\n}\n\n/** Object-schema wrapper differs only in the callee. */\nexport function objectSchema(lib: SchemaLib, body: string): string {\n const callee = lib === 'zod' ? 'z.object' : lib === 'valibot' ? 'v.object' : 'yup.object'\n return `${callee}({\\n ${body},\\n})`\n}\n\nexport function generateCreateDTO(ctx: TemplateContext): string {\n const { pascal } = ctx\n const lib = ctx.schemaLib ?? 'zod'\n const shape = SCHEMA_SHAPES[lib]\n const name = `create${pascal}Schema`\n const libName = lib === 'zod' ? 'Zod' : lib === 'valibot' ? 'Valibot' : 'Yup'\n return `${shape.import}\n\n/**\n * Create ${pascal} DTO — ${libName} schema for validating POST request bodies.\n * This schema is passed to @Post('/', { body: ${name} }) for automatic validation.\n * It also generates OpenAPI request body docs when SwaggerAdapter is used.\n *\n * Add more fields as needed. Supported ${libName} types:\n${shape.hint}\n */\nexport const ${name} = ${objectSchema(lib, `name: ${shape.required}`)}\n\nexport type Create${pascal}DTO = ${shape.infer(name)}\n`\n}\n\nexport function generateUpdateDTO(ctx: TemplateContext): string {\n const { pascal } = ctx\n const lib = ctx.schemaLib ?? 'zod'\n const shape = SCHEMA_SHAPES[lib]\n const name = `update${pascal}Schema`\n return `${shape.import}\n\nexport const ${name} = ${objectSchema(lib, `name: ${shape.optional}`)}\n\nexport type Update${pascal}DTO = ${shape.infer(name)}\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\n/**\n * The repository, as ONE file: factory, contract, token.\n *\n * The factory's RETURN TYPE is the contract — `${pascal}Repository` is derived\n * from it with `ReturnType`, so an implementation cannot drift from its own\n * interface, and there is no separate declaration to keep in step. Swapping\n * stores means writing another factory with a compatible return type; the\n * module decides which one is bound.\n *\n * That collapses what used to be three files (interface, in-memory class,\n * store-named class) into one, and removes the class named after a technology\n * it did not implement.\n *\n * @param ctx.repoType a store name makes the body an unimplemented stub whose\n * methods throw; omit it for the working in-memory implementation.\n */\nexport function generateRepositoryFactory(ctx: TemplateContext): string {\n const { pascal, kebab, repoType, dtoPrefix = './dtos', tokenScope = 'app' } = ctx\n const store = repoType && repoType !== 'inmemory' ? repoType : ''\n const factory = `create${pascal}Repository`\n\n // Always a WORKING implementation. The original bug was the NAME — a\n // `PostgresUserRepository` backed by a Map asserts a technology it does not\n // implement, and an app could boot and pass a smoke test on it. With the\n // store out of the name, an in-memory body is honest: this is the\n // repository, currently in memory, and the TODO says what to swap in.\n //\n // A stub that threw instead would be honest too, but it would leave a\n // generated project that cannot serve a single request.\n const body = ` const store = new Map<string, ${pascal}ResponseDTO>()\n\n /**\n * Filtered, searched and sorted. \\`byId\\` breaks sort-key ties by id — what a\n * cursor needs; without it ties (and an unsorted list) keep insertion order.\n */\n function select(parsed: Omit<ParsedQuery, 'pagination'>, byId = false): ${pascal}ResponseDTO[] {\n // The parser already limited fields to ${pascal.toUpperCase()}_QUERY_CONFIG.\n const q = parsed.search.toLowerCase()\n return [...store.values()]\n .filter(\n (row) =>\n parsed.filters.every((f) => matches(field(row, f.field), f)) &&\n (!q || SEARCHABLE.some((name) => String(field(row, name) ?? '').toLowerCase().includes(q))),\n )\n .sort((a, b) => order(a, b, parsed.sort, byId))\n }\n\n return {\n async findById(id: string): Promise<${pascal}ResponseDTO | null> {\n return store.get(id) ?? null\n },\n\n async findAll(): Promise<${pascal}ResponseDTO[]> {\n return [...store.values()]\n },\n\n async findPaginated(\n parsed: ParsedQuery,\n ): Promise<{ data: ${pascal}ResponseDTO[]; total: number }> {\n const rows = select(parsed)\n const { offset, limit } = parsed.pagination\n return { data: rows.slice(offset, offset + limit), total: rows.length }\n },\n\n /**\n * Keyset page for \\`ctx.paginateCursor\\`: the rows strictly after\n * \\`cursor\\` in sort order (id breaks ties), at most \\`limit\\` of them —\n * the caller asks for one more than it shows, to learn whether a next\n * page exists. A database does the same with\n * \\`WHERE (sortField, id) > (cursor.sortField, cursor.id)\\`.\n */\n async findAfter(\n query: Omit<ParsedQuery, 'pagination'> & {\n cursor: Readonly<Record<string, unknown>> | null\n limit: number\n },\n ): Promise<${pascal}ResponseDTO[]> {\n const { cursor, limit, sort } = query\n const rows = select(query, true)\n const after = cursor ? rows.filter((row) => order(row, cursor, sort, true) > 0) : rows\n return after.slice(0, limit)\n },\n\n async create(dto: Create${pascal}DTO): Promise<${pascal}ResponseDTO> {\n const now = new Date().toISOString()\n const entity = { id: randomUUID(), ...dto, createdAt: now, updatedAt: now } as ${pascal}ResponseDTO\n 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 = store.get(id)\n if (!existing) throw HttpException.notFound('${pascal} not found')\n const updated = { ...existing, ...dto, updatedAt: new Date().toISOString() }\n store.set(id, updated)\n return updated\n },\n\n async delete(id: string): Promise<void> {\n if (!store.has(id)) throw HttpException.notFound('${pascal} not found')\n store.delete(id)\n },\n }`\n\n const imports = `import { randomUUID } from 'node:crypto'\nimport { createToken, HttpException } from '@forinda/kickjs'`\n\n const intro = store\n ? `Backed by an in-memory Map so the module works as generated. Replace the\n * body with your ${store} queries — the contract stays whatever this factory\n * returns.`\n : `Backed by an in-memory Map: a working store with no dependencies.`\n\n return `/**\n * ${pascal} repository.\n *\n * ${intro}\n *\n * The factory's return type IS the contract: \\`${pascal}Repository\\` is derived\n * from it, so the implementation and its interface cannot drift apart. To swap\n * stores, write another factory returning a compatible shape and bind that one\n * in the module — nothing else has to change.\n */\n${imports}\nimport type { FilterItem, ParsedQuery, SortItem } from '@forinda/kickjs'\nimport { ${pascal.toUpperCase()}_QUERY_CONFIG } from './${kebab}.constants'\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\nconst SEARCHABLE = (${pascal.toUpperCase()}_QUERY_CONFIG.searchable ?? []) as string[]\n\nfunction field(row: object, name: string): unknown {\n return (row as Record<string, unknown>)[name]\n}\n\n/**\n * A stored number compares by value (the query's value is text); everything\n * else, ISO dates included, as text — so a string field keeps '001' ≠ '1'.\n */\nfunction compare(stored: unknown, other: unknown): number {\n if (typeof stored === 'number') return stored - Number(other)\n return String(stored ?? '').localeCompare(String(other ?? ''))\n}\n\n/**\n * Sort order of two rows (or a row and a cursor position): each sort field in\n * turn, then — with \\`byId\\` — id, in the direction of the last sort field.\n */\nfunction order(a: object, b: object, sort: readonly SortItem[], byId: boolean): number {\n for (const { field: name, direction } of sort) {\n const result = compare(field(a, name), field(b, name))\n if (result) return direction === 'desc' ? -result : result\n }\n if (!byId) return 0\n const result = compare(field(a, 'id'), field(b, 'id'))\n return sort.at(-1)?.direction === 'desc' ? -result : result\n}\n\n/** One \\`?filter=field:op:value\\` — \\`in\\` and \\`between\\` take comma-separated values. */\nfunction matches(value: unknown, { operator, value: raw }: FilterItem): boolean {\n const text = String(value ?? '').toLowerCase()\n const list = raw.split(',')\n switch (operator) {\n case 'eq':\n return compare(value, raw) === 0\n case 'neq':\n return compare(value, raw) !== 0\n case 'gt':\n return compare(value, raw) > 0\n case 'gte':\n return compare(value, raw) >= 0\n case 'lt':\n return compare(value, raw) < 0\n case 'lte':\n return compare(value, raw) <= 0\n case 'between':\n return compare(value, list[0]) >= 0 && compare(value, list[1]) <= 0\n case 'in':\n return list.some((v) => compare(value, v) === 0)\n case 'contains':\n return text.includes(raw.toLowerCase())\n case 'starts':\n return text.startsWith(raw.toLowerCase())\n case 'ends':\n return text.endsWith(raw.toLowerCase())\n }\n}\n\nexport function ${factory}() {\n${body}\n}\n\n/** The contract, derived from the factory rather than declared beside it. */\nexport type ${pascal}Repository = ReturnType<typeof ${factory}>\n\n/**\n * Collision-safe DI token bound to \\`${pascal}Repository\\`.\n * \\`container.resolve(${pascal.toUpperCase()}_REPOSITORY)\\` and\n * \\`@Inject(${pascal.toUpperCase()}_REPOSITORY)\\` both return the typed\n * contract — 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<${pascal}Repository>('${tokenScope}/${pascal}/repository')\n`\n}\n","import type { TemplateContext } from './types'\n\n/**\n * Controller test scaffold.\n *\n * Every case used to be `expect(true).toBe(true)`, so a generated module\n * reported a full green suite while asserting nothing — and kept reporting it\n * after the routes it names were deleted. A suite that passes unconditionally\n * is worse than no suite: it survives review and makes `pnpm test` stop\n * carrying information.\n *\n * Now: one case that genuinely exercises the module end to end, and `it.todo`\n * for the rest. Todos show up in the reporter as outstanding work and can\n * never be mistaken for coverage.\n */\nexport function generateControllerTest(ctx: TemplateContext): string {\n const {\n pascal,\n kebab,\n plural = '',\n style = 'define',\n testHarness = false,\n testImport = 'vitest',\n } = ctx\n const route = ctx.routePath ?? plural\n // `define` modules are factories; `class` modules are passed as the class.\n // Getting this wrong yields `TypeError: entry is not a constructor`.\n const moduleEntry = style === 'define' ? `${pascal}Module()` : `${pascal}Module`\n\n // Without the harness installed, importing it would emit a file that cannot\n // compile — the same rule that gates `@ApiTags` on `swagger`. Those projects\n // get the identical scaffold with every case as a todo and no extra imports.\n const header = testHarness\n ? `import { describe, it, expect, beforeEach } from '${testImport}'\nimport request from 'supertest'\nimport { Container } from '@forinda/kickjs'\nimport { createTestApp } from '@forinda/kickjs-testing'\n\nimport { ${pascal}Module } from '../${kebab}.module'\n\n/**\n * Where this module is mounted.\n *\n * \\`createTestApp\\` builds its own Application with the framework defaults —\n * apiPrefix \\`/api\\` and defaultVersion \\`1\\` — whatever your \\`bootstrap()\\` uses,\n * so this is correct as generated. If your app configures them differently,\n * pass the same values in \\`boot()\\` below and update this to match, so the test\n * exercises the paths production actually serves:\n *\n * defaultVersion: false → '/api/${route}'\n * apiPrefix: '/v1' → '/v1/v1/${route}'\n */\nconst BASE = '/api/v1/${route}'\n`\n : `import { describe, it, beforeEach } from '${testImport}'\nimport { Container } from '@forinda/kickjs'\n\n// Install \\`@forinda/kickjs-testing\\` and \\`supertest\\` to boot this module in a\n// test: \\`kick add @forinda/kickjs-testing\\`. Until then every case below is a\n// todo — the reporter lists them as outstanding rather than counting them as\n// coverage.\n`\n\n const listBlock = testHarness\n ? ` /**\n * Boot the module through the real pipeline.\n *\n * Drive \\`app.handle\\` rather than an Express app: it is the Application's own\n * Node listener, so this test runs on whichever runtime the app is\n * configured with.\n */\n async function boot() {\n const { app, container } = await createTestApp({\n modules: [${moduleEntry}],\n // Mirror your bootstrap() here if it overrides either, and update BASE:\n // apiPrefix: '/api',\n // defaultVersion: 1,\n })\n return { app, container, agent: request(app.handle.bind(app)) }\n }\n\n describe('GET /${route}', () => {\n it('returns an empty page before anything is created', async () => {\n const { agent } = await boot()\n const res = await agent.get(BASE)\n\n expect(res.status).toBe(200)\n expect(res.body).toMatchObject({ data: [], meta: { page: 1 } })\n })\n\n it('filters and sorts through the query string', async () => {\n const { agent } = await boot()\n for (const name of ['Alpha', 'Beta', 'Gamma']) await agent.post(BASE).send({ name })\n const res = await agent.get(\\`\\${BASE}?filter=name:neq:Beta&sort=name:desc\\`)\n\n expect(res.status).toBe(200)\n expect(res.body.data.map((row: { name: string }) => row.name)).toEqual(['Gamma', 'Alpha'])\n expect(res.body.meta.total).toBe(2)\n })\n\n it.todo('returns created ${plural}, paginated')\n })`\n : ` describe('GET /${route}', () => {\n // Boot the module with createTestApp and assert { data, meta }.\n it.todo('returns an empty page before anything is created')\n it.todo('returns created ${plural}, paginated')\n })`\n\n return `${header}\ndescribe('${pascal}Controller', () => {\n beforeEach(() => {\n Container.reset()\n })\n\n${listBlock}\n\n // The cases below are scaffolding, not coverage. Each is \\`it.todo\\` so the\n // reporter lists it as outstanding instead of counting it as a pass. Replace\n // the todo with a real test as you implement each endpoint.\n\n describe('POST /${route}', () => {\n${\n testHarness\n ? ` it('creates a ${kebab} and points Location at it', async () => {\n const { agent } = await boot()\n const res = await agent.post(BASE).send({ name: 'Test ${pascal}' })\n\n expect(res.status).toBe(201)\n expect(res.body).toMatchObject({ name: 'Test ${pascal}' })\n expect(res.headers.location).toBe(\\`\\${BASE}/\\${res.body.id}\\`)\n })`\n : ` // Post a valid body, assert 201, the created ${kebab}, and its Location header.\n it.todo('creates a ${kebab}')`\n}\n // Post an invalid body, assert 422 and the validation detail.\n it.todo('rejects an invalid body')\n })\n\n describe('GET /${route}/:id', () => {\n // Create one, fetch it by id, assert the payload matches.\n it.todo('returns a ${kebab} by id')\n // Fetch an id that does not exist, assert 404.\n it.todo('returns 404 for an unknown id')\n })\n\n describe('PATCH /${route}/:id', () => {\n // Create, update, assert the change is reflected on a subsequent read.\n it.todo('updates an existing ${kebab}')\n })\n\n describe('DELETE /${route}/:id', () => {\n // Create, delete, assert a subsequent read is 404.\n it.todo('deletes a ${kebab}')\n })\n})\n`\n}\n\nexport function generateRepositoryTest(ctx: TemplateContext): string {\n const {\n pascal,\n kebab,\n plural = '',\n repoPrefix = `../${kebab}.repository`,\n testImport = 'vitest',\n } = ctx\n const factory = `create${pascal}Repository`\n\n return `import { describe, it, expect, beforeEach } from '${testImport}'\nimport { ${factory}, type ${pascal}Repository } from '${repoPrefix}'\n\ndescribe('${pascal} repository', () => {\n let repo: ${pascal}Repository\n\n beforeEach(() => {\n repo = ${factory}()\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 filter, search and sort before paginating', async () => {\n await repo.create({ name: 'Banana' })\n await repo.create({ name: 'apple' })\n await repo.create({ name: 'Cherry' })\n const pagination = { page: 1, limit: 10, offset: 0 }\n\n const sorted = await repo.findPaginated({\n filters: [],\n sort: [{ field: 'name', direction: 'desc' }],\n search: '',\n pagination,\n })\n expect(sorted.data.map((row) => row.name)).toEqual(['Cherry', 'Banana', 'apple'])\n\n const filtered = await repo.findPaginated({\n filters: [{ field: 'name', operator: 'neq', value: 'apple' }],\n sort: [],\n search: '',\n pagination: { page: 1, limit: 1, offset: 0 },\n })\n expect(filtered.data).toHaveLength(1)\n expect(filtered.total).toBe(2)\n\n const searched = await repo.findPaginated({ filters: [], sort: [], search: 'AN', pagination })\n expect(searched.data.map((row) => row.name)).toEqual(['Banana'])\n })\n\n it('compares a text field as text, not as a number', async () => {\n await repo.create({ name: '001' })\n await repo.create({ name: '10' })\n await repo.create({ name: '9' })\n const pagination = { page: 1, limit: 10, offset: 0 }\n\n const exact = await repo.findPaginated({\n filters: [{ field: 'name', operator: 'eq', value: '1' }],\n sort: [],\n search: '',\n pagination,\n })\n expect(exact.total).toBe(0)\n\n const sorted = await repo.findPaginated({\n filters: [],\n sort: [{ field: 'name', direction: 'asc' }],\n search: '',\n pagination,\n })\n expect(sorted.data.map((row) => row.name)).toEqual(['001', '10', '9'])\n })\n\n it('pages through rows with findAfter, stable when sort keys tie', async () => {\n // Three rows share 'a', so a page boundary falls inside the tie: only the\n // id tie-breaker keeps the third 'a' from being skipped or repeated.\n for (const name of ['b', 'a', 'b', 'a', 'a']) await repo.create({ name })\n const query = { filters: [], sort: [{ field: 'name', direction: 'asc' as const }], search: '' }\n const limit = 2\n\n const pages: string[][] = []\n const ids: string[] = []\n let cursor: Record<string, unknown> | null = null\n let hasNext = true\n while (hasNext) {\n // Ask for one more than a page, as ctx.paginateCursor's fetcher does.\n const rows = await repo.findAfter({ ...query, cursor, limit: limit + 1 })\n hasNext = rows.length > limit\n const page = rows.slice(0, limit)\n pages.push(page.map((row) => row.name))\n ids.push(...page.map((row) => row.id))\n const last = page.at(-1)\n cursor = last ? { name: last.name, id: last.id } : null\n }\n\n expect(pages.map((page) => page.length)).toEqual([2, 2, 1])\n expect(pages.flat()).toEqual(['a', 'a', 'a', 'b', 'b'])\n expect(new Set(ids).size).toBe(5)\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 the repository with CRUD methods */\nexport function generateRestService(ctx: TemplateContext): string {\n const { pascal, kebab } = ctx\n return `import { Service, Inject } from '@forinda/kickjs'\nimport type { ParsedQuery } from '@forinda/kickjs'\nimport { ${pascal.toUpperCase()}_REPOSITORY, type ${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: ${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 findAfter(query: Parameters<${pascal}Repository['findAfter']>[0]) {\n return this.repo.findAfter(query)\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/** A field the query config is built from — `kick g scaffold`'s `name:type`. */\nexport interface QueryField {\n name: string\n type: string\n}\n\n// Free text worth a `?q=` search; every other scalar is filter + sort only.\nconst SEARCHABLE = new Set(['string', 'text', 'email', 'url'])\n// Not comparable as a scalar — neither filtered nor sorted.\nconst UNQUERYABLE = new Set(['json'])\n\n/**\n * REST constants — the query config `ctx.paginate` whitelists against, built\n * from the module's fields. `kick g module` has one `name: string` field.\n */\nexport function generateRestConstants(ctx: TemplateContext & { fields?: QueryField[] }): string {\n const { pascal, fields = [{ name: 'name', type: 'string' }] } = ctx\n const queryable = fields.filter((f) => !UNQUERYABLE.has(f.type)).map((f) => f.name)\n const searchable = fields.filter((f) => SEARCHABLE.has(f.type)).map((f) => f.name)\n const list = (names: string[]) => `[${names.map((n) => `'${n}'`).join(', ')}]`\n return `import type { QueryFieldConfig } from '@forinda/kickjs'\n\nexport const ${pascal.toUpperCase()}_QUERY_CONFIG: QueryFieldConfig = {\n filterable: ${list(queryable)},\n sortable: ${list([...queryable.filter((n) => n !== 'createdAt'), 'createdAt'])},\n searchable: ${list(searchable)},\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, routePath, style, write } = ctx\n\n // Module file (named `<kebab>.module.ts` so Vite's module-discovery plugin picks it up)\n await write(\n `${kebab}.module.ts`,\n generateMinimalModuleIndex({ pascal, kebab, plural, routePath, style }),\n )\n\n await write(\n `${kebab}.controller.ts`,\n `import { Controller, Get, Post, Patch, Delete, reply, 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// Return the payload instead of calling \\`ctx.json()\\` — the runtime sends it,\n// and typegen reads the return type to fill in \\`KickRoutes[...].response\\`.\n// \\`reply(status, body)\\` carries a non-200 status without losing that type.\n//\n// The bodies below are placeholders: wire them to a service or repository.\n// \"Minimal\" is about the file count, not the route surface — you get the whole\n// CRUD shape here in one file.\n\n// Require a signed-in user for these routes: https://kickjs.app/guide/authentication\n@Controller()\nexport class ${pascal}Controller {\n @Get('/')\n async list(ctx: Ctx<KickRoutes.${pascal}Controller['list']>) {\n return { items: [], total: 0 }\n }\n\n @Get('/:id')\n async getById(ctx: Ctx<KickRoutes.${pascal}Controller['getById']>) {\n return { id: ctx.params.id }\n }\n\n @Post('/')\n async create(ctx: Ctx<KickRoutes.${pascal}Controller['create']>) {\n return reply.created({ data: ctx.body })\n }\n\n @Patch('/:id')\n async update(ctx: Ctx<KickRoutes.${pascal}Controller['update']>) {\n return { id: ctx.params.id, data: ctx.body }\n }\n\n @Delete('/:id')\n async remove(ctx: Ctx<KickRoutes.${pascal}Controller['remove']>) {\n return reply.noContent()\n }\n}\n`,\n )\n}\n","import type { ModuleContext } from './types'\nimport {\n generateRestModuleIndex,\n generateRestController,\n generateRestConstants,\n generateRestService,\n generateCreateDTO,\n generateUpdateDTO,\n generateResponseDTO,\n generateRepositoryFactory,\n generateControllerTest,\n generateRepositoryTest,\n} from '../templates'\n\nexport async function generateRestFiles(ctx: ModuleContext): Promise<void> {\n const {\n pascal,\n kebab,\n plural,\n pluralPascal,\n routePath,\n repo,\n noTests,\n tokenScope,\n style,\n testHarness,\n testImport,\n write,\n } = ctx\n const swagger = ctx.swagger ?? false\n const schemaLib = ctx.schemaLib ?? 'zod'\n\n // Module file (named `<kebab>.module.ts` so Vite's module-discovery plugin picks it up)\n await write(\n `${kebab}.module.ts`,\n generateRestModuleIndex({ pascal, kebab, plural, routePath, repo, style }),\n )\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, swagger }),\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, schemaLib }))\n await write(`dtos/update-${kebab}.dto.ts`, generateUpdateDTO({ pascal, kebab, schemaLib }))\n await write(`dtos/${kebab}-response.dto.ts`, generateResponseDTO({ pascal, kebab }))\n\n // ONE repository file: factory, contract, token. The factory's return type\n // is the interface, so there is nothing to keep in step and no class named\n // after a store it does not implement.\n await write(\n `${kebab}.repository.ts`,\n generateRepositoryFactory({ pascal, kebab, repoType: repo, dtoPrefix: './dtos', tokenScope }),\n )\n\n // Tests\n if (!noTests) {\n await write(\n `__tests__/${kebab}.controller.test.ts`,\n generateControllerTest({\n pascal,\n kebab,\n plural,\n routePath,\n style,\n testHarness,\n testImport,\n }),\n )\n await write(\n `__tests__/${kebab}.repository.test.ts`,\n generateRepositoryTest({\n pascal,\n kebab,\n plural,\n repoPrefix: `../${kebab}.repository`,\n testImport,\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 } from 'node:fs/promises'\nimport type { ProjectPattern, RepoTypeConfig, SchemaLib } 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 true, pluralize the folder, file and class names (`users/`). Default false. */\n pluralize?: boolean\n /** When false, mount at the name as typed (`/user`) instead of its plural. Default true. */\n pluralizeRoutes?: boolean\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 /** Project depends on @forinda/kickjs-swagger — gates @ApiTags emission. */\n swagger?: boolean\n /** Validation library the emitted DTO schemas import. Default `'zod'`. */\n schemaLib?: SchemaLib\n /**\n * Project has `@forinda/kickjs-testing` and `supertest` — gates the\n * generated controller test booting the module for real. Without them the\n * imports would not resolve and the generated file could not compile.\n */\n testHarness?: boolean\n /** Module generated tests import from — see `testImportSource`. */\n testImport?: 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 === true\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 routePath = options.pluralizeRoutes === false ? kebab : pluralize(kebab)\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 routePath,\n moduleDir,\n repo,\n noEntity: noEntity ?? false,\n noTests: noTests ?? false,\n tokenScope: options.tokenScope ?? 'app',\n swagger: options.swagger ?? false,\n schemaLib: options.schemaLib ?? 'zod',\n testHarness: options.testHarness ?? false,\n testImport: options.testImport,\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 case 'inertia':\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 // Through writeFileSafe like every generated file: the project's oxfmt\n // lays the chain out (one line for two mounts, one per line from three).\n await writeFileSafe(indexPath, content)\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","/**\n * The commented-out `introspect()` / `devtoolsTabs()` hooks that\n * `kick g adapter` and `kick g plugin` both write — one copy, so the two\n * examples can't drift from the real types.\n */\nexport function devtoolsHooks(\n kebab: string,\n pascal: string,\n kind: 'adapter' | 'plugin',\n indent: string,\n): string {\n const block = `/**\n * Snapshot for the DevTools topology view (\\`/_debug\\`).\n *\n * Must be CHEAP — the topology endpoint polls on a short interval, so\n * \\`state\\` and \\`metrics\\` should be counters and flags already in memory,\n * never a database round trip.\n *\n * Uncomment with: import type { IntrospectionSnapshot } from '@forinda/kickjs'\n * Delete this hook if the ${kind} has nothing worth showing.\n */\n// introspect(): IntrospectionSnapshot {\n// return {\n// protocolVersion: 1,\n// name: '${kebab}',\n// kind: '${kind}',\n// state: { connected: true },\n// metrics: { handled: 0 },\n// tokens: { provides: [], requires: [] },\n// }\n// },\n\n/**\n * DevTools panels this ${kind} contributes — a sidebar tab showing HTML,\n * an iframe, buttons that run server code, or a browser module.\n *\n * Uncomment with: import { defineDevtoolsTab } from '@forinda/kickjs-devtools-kit'\n * and add \\`@forinda/kickjs-devtools-kit\\` to the dependencies.\n * (\\`@forinda/kickjs\\` deliberately does not depend on it.)\n * Delete this hook unless the ${kind} ships a panel.\n */\n// devtoolsTabs() {\n// return [\n// defineDevtoolsTab({\n// id: '${kebab}',\n// title: '${pascal}',\n// view: { type: 'html', html: '<p>${pascal} ${kind}</p>' },\n// }),\n// ]\n// },`\n return block\n .split('\\n')\n .map((line) => indent + line)\n .join('\\n')\n}\n","import { join } from 'node:path'\nimport { devtoolsHooks } from './devtools-hooks'\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 adapter with\n * EVERY hook on `AppAdapter`, a typed config object with defaults, and the\n * factory's call / `.scoped()` / `.async()` surfaces — without writing a\n * single class.\n *\n * \"Every hook\" is the point, and `adapter-generator.test.ts` pins it: the\n * scaffold is how adopters discover what an adapter can do, so a hook missing\n * here is a feature that effectively does not exist. `onHealthCheck` was\n * absent for several releases, and adopters hand-rolled their own\n * `/health/ready` instead of contributing to the built-in one.\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 * Connect-style middleware entries the Application mounts at named\n * phases. Works on every runtime — Express, Fastify and h3 all accept\n * the connect signature through the runtime seam.\n *\n * \\`phase\\` controls where each handler sits in the pipeline:\n * 'beforeGlobal' | 'afterGlobal' | 'beforeRoutes' | 'afterRoutes'.\n *\n * \\`path\\` (optional) scopes the entry — a string prefix, a RegExp,\n * or an array mixing both. See \\`MiddlewarePath\\`.\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 — \\`ctx.http\\` is the engine-neutral seam, so this route\n // works under Express, Fastify and h3 alike:\n // _ctx.http.route('GET', '/${kebab}/status', (ctx) => ctx.json({ status: 'ok' }))\n //\n // \\`ctx.app\\` is the engine-native instance (an Express app only under\n // the default runtime). Reach for it when what you're wiring is\n // genuinely engine-specific, and accept the lock-in.\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 AND on every HMR reload. Clean up\n * long-lived resources the adapter OWNS: close connections, flush\n * buffers, cancel timers.\n *\n * Close only what you own — \\`ctx.server\\` is shared (in dev it is\n * Vite's listener), and closing it kills the dev server with no\n * rebind. socket.io's \\`io.close()\\` does this; use\n * \\`io.engine.close()\\` to detach instead.\n *\n * The framework runs every adapter's \\`shutdown\\` concurrently via\n * \\`Promise.allSettled\\`, each time-boxed — one failure or hang won't\n * block 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 * Reports this adapter's backing service to \\`GET /health/ready\\`.\n *\n * The Application aggregates every adapter's check through\n * \\`Promise.allSettled\\` and serves the combined result, so contributing\n * one here is what makes your dependency visible to the built-in\n * readiness endpoint — no route of your own required.\n *\n * Keep it cheap and time-boxed: readiness is polled by orchestrators.\n *\n * Delete this hook if your adapter has nothing to report.\n */\n async onHealthCheck(): Promise<{ name: string; status: 'up' | 'down' }> {\n // Example:\n // try {\n // await _config.client.ping()\n // return { name: '${kebab}', status: 'up' }\n // } catch {\n // return { name: '${kebab}', status: 'down' }\n // }\n return { name: '${kebab}', status: 'up' }\n },\n\n${devtoolsHooks(kebab, pascal, 'adapter', ' ')}\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: false) */\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 (layout depends on the 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 = false,\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 { KickConfig, 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 * The engine from `kick.config.ts`. Global middleware is connect-style on\n * every runtime, but only Express hands the handler an `express.Request` —\n * Fastify passes `request.raw` and h3 the node objects, so `node:http` is\n * the honest type there (and those projects have no `express` dependency to\n * import types from).\n */\n runtime?: KickConfig['runtime']\n}\n\nexport async function generateMiddleware(options: GenerateMiddlewareOptions): Promise<string[]> {\n const { name, moduleName, modulesDir, pattern } = options\n // Explicit opt-in, not a fallback: an unset `runtime` means a hand-written or\n // pre-`--runtime` kick.config, which says nothing about the engine. Emitting\n // express types there is how the original bug shipped — `express` isn't a\n // dependency on a Fastify / h3 scaffold, so the import fails to compile.\n // `kick new` always writes the field, so real Express projects still opt in.\n const isExpress = options.runtime === 'express'\n // An unset `runtime` is a hand-written or pre-`--runtime` kick.config, so the\n // comment must not claim the project runs on one.\n const engineClause = options.runtime\n ? `this project runs on ${options.runtime} and has no\n // \\`express\\``\n : `this project declares no \\`runtime\\` in kick.config.ts, so it\n // may have no \\`express\\``\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 ?? false,\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 `${\n isExpress\n ? `import type { Request, Response, NextFunction } from 'express'`\n : `import type { IncomingMessage, ServerResponse } from 'node:http'`\n }\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 * middlewares: [${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 * NOT for \\`@Middleware()\\`. That decorator takes a ctx-style handler —\n * \\`(ctx, next)\\`, see \\`MiddlewareHandler\\` — and the runtime calls it with\n * exactly two arguments. Passing this connect-style factory there binds\n * \\`next\\` to the response slot and passes no third argument at all, so the\n * first \\`next()\\` throws \\`TypeError: next is not a function\\`. Generate a\n * guard instead (\\`kick g guard\\`) for the per-route, ctx-style shape.\n */\nexport function ${camel}(options: ${toPascalCase(name)}Options = {}) {\n${\n isExpress\n ? ` // Typed from \\`express\\` because that is this project's runtime\n // (\\`runtime: 'express'\\` in kick.config.ts). Global middleware is\n // connect-style on every engine, but only Express hands the handler its own\n // request / response objects — so \\`req.originalUrl\\`, \\`req.query\\` and\n // \\`res.json()\\` are all available here. Switching \\`runtime\\` later means\n // re-typing this signature from \\`node:http\\`.\n return (req: Request, res: Response, next: NextFunction) => {`\n : ` // Typed from \\`node:http\\`, not \\`express\\`. Global middleware is connect-style\n // on every engine, but ${engineClause} dependency to import types from. Under Fastify the handler\n // receives \\`request.raw\\` / a reply driver, and under h3 the node objects, so\n // anything Express-only (\\`req.originalUrl\\`, \\`res.json\\`) is absent. Reach\n // for \\`ctx.*\\` helpers when you need a typed response.\n return (req: IncomingMessage, res: ServerResponse, next: () => void) => {`\n}\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 ?? false,\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.problem.*\\` (RFC 9457) rather than \\`ctx.res\\`: \\`ctx.res\\` is the\n // ENGINE-NATIVE response, so \\`ctx.res.status(401).json(...)\\` only works on\n // Express — \\`FastifyReply\\` has no \\`.json()\\`, and h3's event has no\n // \\`.status()\\`. The \\`ctx.*\\` helpers work on every runtime.\n ctx.problem.unauthorized({ detail: '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.problem.unauthorized({ detail: '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 ?? false,\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 ?? false,\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, Patch, Delete, reply, 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://kickjs.app/guide/typegen for details.\n//\n// Return the payload instead of calling \\`ctx.json()\\` — the runtime sends it,\n// and typegen reads the return type to fill in \\`KickRoutes[...].response\\`.\n// \\`reply(status, body)\\` carries a non-200 status without losing that type.\n//\n// The bodies below are placeholders: wire them to a service or repository.\n\n// Require a signed-in user for these routes: https://kickjs.app/guide/authentication\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 return { items: [], total: 0 }\n }\n\n @Get('/:id')\n async getById(ctx: Ctx<KickRoutes.${pascal}Controller['getById']>) {\n return { id: ctx.params.id }\n }\n\n @Post('/')\n async create(ctx: Ctx<KickRoutes.${pascal}Controller['create']>) {\n return reply.created({ data: ctx.body })\n }\n\n @Patch('/:id')\n async update(ctx: Ctx<KickRoutes.${pascal}Controller['update']>) {\n return { id: ctx.params.id, data: ctx.body }\n }\n\n @Delete('/:id')\n async remove(ctx: Ctx<KickRoutes.${pascal}Controller['remove']>) {\n return reply.noContent()\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 { SCHEMA_SHAPES, objectSchema } from './templates/dtos'\nimport type { ProjectPattern, SchemaLib } 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 * Validation library to write the schema against. Defaults to `'zod'`.\n * The command resolves it from the project's dependencies — a scaffold\n * created with `--schema valibot` never installs zod, so emitting a zod\n * import there produces a file that cannot resolve.\n */\n schemaLib?: SchemaLib\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 ?? false,\n })\n const kebab = toKebabCase(name)\n const pascal = toPascalCase(name)\n const camel = toCamelCase(name)\n const lib = options.schemaLib ?? 'zod'\n const shape = SCHEMA_SHAPES[lib]\n const files: string[] = []\n\n const filePath = join(outDir, `${kebab}.dto.ts`)\n await writeFileSafe(\n filePath,\n `${shape.import}\n\nexport const ${camel}Schema = ${objectSchema(lib, `// Define your schema fields here\\n name: ${shape.required}`)}\n\nexport type ${pascal}DTO = ${shape.infer(`${camel}Schema`)}\n`,\n )\n files.push(filePath)\n\n return files\n}\n","// TypegenPlugin contract — M2.B-T7.\n//\n// Each plugin owns a single `.kickjs/types/<id>.d.ts` file. The runner\n// invokes `generate(ctx)` per plugin, prepends a banner, writes only on\n// content change, and surfaces drift in --check mode (CI gate).\n//\n// Built-in plugins (kick/routes, kick/env, kick/assets, kick/db) live under\n// `./builtin`. Adopters register additional plugins via kick.config.ts.\n\nimport type { KickConfig } from '../config'\nimport type { ScanOptions, ScanResult } from './scanner'\n\n/**\n * True when verbose typegen output is requested via `LOG_LEVEL=debug` (the\n * kickjs log-level convention).\n *\n * Lives here rather than in `run-plugins` because plugins need it too, and\n * `run-plugins` → `plugin/builtins` → any builtin plugin is a cycle: importing\n * it the other way left `kickClientTypegen` undefined at module-eval time.\n */\nexport function isDebugLog(): boolean {\n const level = (process.env.LOG_LEVEL ?? process.env.KICKJS_LOG_LEVEL ?? '').toLowerCase()\n return level === 'debug' || level === 'trace'\n}\n\nexport interface TypegenLogger {\n info(msg: string): void\n warn(msg: string): void\n error(msg: string): void\n}\n\nexport interface TypegenContext {\n cwd: string\n config: KickConfig\n /** Dynamic-import a TS module (Node loader). Used by plugins that need to\n * read the adopter's schema / route map / asset registry at generate time. */\n importTs<T = unknown>(absPath: string): Promise<T>\n /** Write under `cwd`. Caller passes a relPath (e.g. `.kickjs/types/foo.d.ts`). */\n writeFile(relPath: string, contents: string): Promise<void>\n /**\n * Run `scanProject` once per typegen pass, memoizing the result so\n * multiple plugins (`kick/routes`, `kick/env`, future adopter plugins)\n * share a single fs walk + AST extraction.\n *\n * The runner uses an order-independent cache key derived from the\n * resolved options (`root`, `cwd`, `extensions`, `exclude`, `envFile`)\n * — semantically equal options hit the cache regardless of how the\n * caller built the literal. (We deliberately don't `JSON.stringify`\n * the options for caching since that would be sensitive to property\n * insertion order.) Plugins that don't need scanner data can ignore\n * this method entirely.\n *\n * Implementation lives in the runner so test harnesses can inject\n * a stub scanner; plugins only see the function.\n */\n getScanResult(opts: ScanOptions): Promise<ScanResult>\n log: TypegenLogger\n /**\n * True under `kick typegen --check`. The gate is read-only — it must not\n * touch the working tree — so a plugin that caches state alongside its\n * output has to skip the write.\n */\n check?: boolean\n /**\n * True when this pass is part of a watch loop (`kick typegen --watch`,\n * `kick dev`). Plugins whose work is a build-step cost rather than a\n * keystroke cost return `null` when it is set — see `kick/client`, which\n * builds a whole TypeScript program.\n */\n watch?: boolean\n}\n\nexport interface TypegenPlugin {\n /** Stable id — used as filename: `.kickjs/types/${id}<outExtension>` (slashes → `__`). */\n id: string\n /** Glob patterns the Vite watcher subscribes to; change → re-run this plugin. */\n inputs: string[]\n /**\n * Output filename extension. Default `.d.ts` — the right choice for\n * pure module-augmentation plugins (kick/db, kick/assets) since\n * declaration files don't need the runtime-import dance.\n *\n * `kick/routes` overrides to `.ts` because it emits hoisted\n * `import type {...} from '...'` lines at the top of the file. Inline\n * `import('...').X` references inside `.d.ts` silently degrade to\n * `unknown` under `moduleResolution: 'bundler'`; emitting `.ts`\n * sidesteps that quirk and gets full type resolution.\n *\n * Adopter-supplied plugins should leave this unset unless they hit\n * the same hoisted-import constraint.\n */\n outExtension?: string\n /**\n * Return the augmentation source (without banner — runner prepends).\n * Return null to skip emission (e.g. no schema file present).\n */\n generate(ctx: TypegenContext): Promise<string | null>\n}\n\nexport interface TypegenPluginResult {\n id: string\n status: 'written' | 'unchanged' | 'skipped' | 'error' | 'drifted'\n outFile?: string\n}\n\n/**\n * Thrown by the runner under `--check` when a plugin's generated output\n * differs from what's on disk. Carries every drifted plugin, not just\n * the first, so one CI run reports the full list.\n *\n * This has its own class for a load-bearing reason: `runAllPluginTypegens`\n * wraps the pass in a catch that downgrades plugin failures to a warning\n * so a transiently-broken plugin can't crash the `kick dev` loop. That\n * catch used to swallow this error too, which made `--check` exit 0 on\n * *every* drift — the gate silently passed while printing \"skipped\".\n * The catch now rethrows this type. Do not make it a plain `Error`.\n */\nexport class TypegenDriftError extends Error {\n /** Plugin ids whose output drifted, in run order. */\n readonly drifted: readonly { id: string; outFile: string }[]\n\n constructor(drifted: readonly { id: string; outFile: string }[]) {\n const list = drifted.map((d) => ` ${d.id} → ${d.outFile}`).join('\\n')\n super(\n `kick typegen --check: ${drifted.length} generated file(s) are out of date:\\n${list}\\n` +\n ` Run \\`kick typegen\\` and commit the result.`,\n )\n this.name = 'TypegenDriftError'\n this.drifted = drifted\n }\n}\n\n/**\n * Identity factory for {@link TypegenPlugin}. Returns the spec verbatim.\n * Exists for type inference and forward-compatibility — future\n * fields can be added with defaults without breaking adopters.\n *\n * Mirrors {@link defineGenerator} ergonomics. Use at the call site so\n * the plugin's `generate(ctx)` body gets a fully-typed `ctx` without\n * an explicit annotation:\n *\n * @example\n * ```ts\n * import { defineTypegen } from '@forinda/kickjs-cli'\n *\n * export const drizzleTypegen = defineTypegen({\n * id: 'drizzle',\n * inputs: ['src/db/schema.ts'],\n * async generate(ctx) {\n * const schema = await ctx.importTs(`${ctx.cwd}/src/db/schema.ts`)\n * return `// declare module …`\n * },\n * })\n * ```\n */\nexport function defineTypegen(spec: TypegenPlugin): TypegenPlugin {\n return spec\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, watch: true })\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 ?? false\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 { 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'\nimport { detectPackageManager } from '../utils/package-manager'\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 /**\n * The project's tsconfig with its full `extends` chain already merged\n * into `compilerOptions`. `undefined` = no tsconfig.json; `null` = one\n * exists but couldn't be parsed. Checks should distinguish the two —\n * they call for different advice.\n */\n tsconfig: any | null | undefined\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/** Depth cap for `extends` chains — well past any real-world config. */\nconst MAX_EXTENDS_DEPTH = 16\n\n/**\n * Parse a tsconfig's JSONC: block comments, line comments, and trailing\n * commas are all legal in tsconfig.json and all illegal in `JSON.parse`.\n *\n * Trailing commas matter more than they look: a plain `JSON.parse` throw\n * used to make `loadTsConfig` return null, which the decorator check\n * reports as \"tsconfig.json present → fail\" for a file that is sitting\n * right there. A formatting nicety became a phantom missing file.\n */\nfunction parseJsonc(raw: string): any | null {\n const stripped = raw\n // Block comments, but not inside strings — approximated by requiring\n // the `/*` to not be preceded by a `:` + quote start. Good enough for\n // config files; a full JSONC parser isn't worth the dependency here.\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n .replace(/(^|[^:\"'\\\\])\\/\\/.*$/gm, '$1')\n // Trailing commas before } or ]\n .replace(/,(\\s*[}\\]])/g, '$1')\n try {\n return JSON.parse(stripped)\n } catch {\n return null\n }\n}\n\n/**\n * Read a tsconfig and resolve its full `extends` chain.\n *\n * Previously this followed exactly one level, only when `extends` was a\n * string, resolved relative paths against the project root rather than\n * the containing file, and parsed parents with a bare `JSON.parse`. Any\n * of those produced the same symptom: a project whose decorator options\n * live in a shared base config got told it was missing\n * `experimentalDecorators` / `emitDecoratorMetadata` that it plainly\n * sets. Lean per-package configs in a monorepo hit every one of them at\n * once.\n *\n * Now: chains of any depth, array `extends` (TS 5.0+), node_modules\n * lookup walking up from the config's own directory (pnpm/monorepo\n * hoisting), directory specifiers resolving to `tsconfig.json`, and\n * JSONC parsing for every file in the chain.\n *\n * Returns `undefined` when no tsconfig.json exists, and `null` when one\n * exists but couldn't be parsed — callers report those differently.\n *\n * Relative paths *inside* inherited options (`baseUrl`, `paths`,\n * `outDir`) are NOT rebased onto the child's directory the way `tsc`\n * does it. Every check that reads this cares only about booleans; revisit\n * if a path-sensitive check is ever added.\n */\nfunction loadTsConfig(cwd: string): any | null | undefined {\n const tsconfigPath = join(cwd, 'tsconfig.json')\n if (!existsSync(tsconfigPath)) return undefined\n return loadTsConfigFrom(tsconfigPath, new Set())\n}\n\nfunction loadTsConfigFrom(filePath: string, seen: Set<string>): any | null {\n if (seen.has(filePath) || seen.size >= MAX_EXTENDS_DEPTH) return null\n seen.add(filePath)\n\n const raw = safeRead(filePath)\n if (raw === null) return null\n const cfg = parseJsonc(raw)\n if (cfg === null || typeof cfg !== 'object') return null\n\n // `extends` is a string or, since TS 5.0, an array applied left to\n // right with later entries overriding earlier ones.\n const parents = Array.isArray(cfg.extends)\n ? cfg.extends\n : typeof cfg.extends === 'string'\n ? [cfg.extends]\n : []\n\n // Mutating accumulator rather than re-spreading per iteration — later\n // entries override earlier ones, which is the same result.\n const inherited: Record<string, any> = {}\n for (const ext of parents) {\n if (typeof ext !== 'string') continue\n const parentPath = resolveExtends(dirname(filePath), ext)\n if (!parentPath) continue\n const parent = loadTsConfigFrom(parentPath, seen)\n if (!parent) continue\n Object.assign(inherited, parent.compilerOptions)\n }\n\n cfg.compilerOptions = { ...inherited, ...cfg.compilerOptions }\n return cfg\n}\n\n/**\n * Resolve one `extends` entry to an absolute file path, mirroring how\n * `tsc` resolves them.\n *\n * `fromDir` is the directory of the config doing the extending — NOT the\n * project root. A chain of relative extends only resolves correctly when\n * each hop is relative to its own file.\n *\n * Handles: relative paths with an implied `.json`, bare package\n * specifiers (`@tsconfig/node20`, `@acme/tsconfig/base.json`), directory\n * specifiers that mean `<dir>/tsconfig.json`, and node_modules\n * directories anywhere up the tree — the last of which is what pnpm\n * workspaces need, since the preset is usually hoisted to the repo root\n * rather than sitting in the package's own node_modules.\n */\nfunction resolveExtends(fromDir: string, ext: string): string | null {\n if (ext.startsWith('.') || ext.startsWith('/')) {\n return resolveConfigTarget(resolve(fromDir, ext))\n }\n\n // Bare specifier — walk up looking for node_modules/<specifier>.\n let dir = fromDir\n for (;;) {\n const candidate = resolveConfigTarget(join(dir, 'node_modules', ext))\n if (candidate) return candidate\n const parent = dirname(dir)\n if (parent === dir) break\n dir = parent\n }\n return null\n}\n\n/**\n * Turn a resolved specifier into an actual config file: the path itself,\n * the path with `.json` appended, or `<path>/tsconfig.json` when it names\n * a directory. `@tsconfig/node20` resolves to a directory, and the old\n * code handed that straight to `JSON.parse` — an EISDIR that silently\n * became \"no inherited options\".\n */\nfunction resolveConfigTarget(target: string): string | null {\n if (existsSync(target)) {\n try {\n if (statSync(target).isDirectory()) {\n const nested = join(target, 'tsconfig.json')\n return existsSync(nested) ? nested : null\n }\n } catch {\n return null\n }\n return target\n }\n const withJson = `${target}.json`\n return existsSync(withJson) ? withJson : 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: ${detectPackageManager(ctx.cwd)} 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: ${detectPackageManager(ctx.cwd)} add ${missing.join(' ')}`,\n }\n }\n if (ctx.runtime === 'h3' && h3Major(ctx.cwd, deps.h3) === 1) {\n return {\n name,\n status: 'fail',\n message: 'h3 v1',\n fix: `This version of @forinda/kickjs runs on h3 v2 only. Upgrade: ${detectPackageManager(ctx.cwd)} add h3@^2 — no code change for apps that only use KickJS APIs.`,\n }\n }\n return { name, status: 'pass' }\n}\n\n/**\n * The installed h3's major, else the one its declared range starts at. Looks\n * up through ancestor `node_modules` as Node does, so an h3 hoisted to a\n * workspace root counts.\n */\nfunction h3Major(cwd: string, range: string): number | undefined {\n let version = range\n for (let dir = resolve(cwd); ; dir = dirname(dir)) {\n try {\n version = JSON.parse(\n readFileSync(join(dir, 'node_modules', 'h3', 'package.json'), 'utf8'),\n ).version\n break\n } catch {\n // not here — try the parent\n }\n if (dirname(dir) === dir) break\n }\n const major = /(\\d+)/.exec(version)?.[1]\n return major === undefined ? undefined : Number(major)\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 ${detectPackageManager(ctx.cwd)} 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: ${detectPackageManager(ctx.cwd)} 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 === undefined) {\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 if (ctx.tsconfig === null) {\n // The file exists but we couldn't read it (or something in its\n // `extends` chain). Reporting the decorator options as missing here\n // would be a guess, and a wrong one more often than not — say what\n // actually happened instead.\n return [\n {\n name: 'tsconfig.json readable',\n status: 'warn',\n message: 'could not parse tsconfig.json (or a config it extends)',\n fix: 'Check tsconfig.json for a syntax error, and that every path in its `extends` chain resolves. Decorator options could not be verified.',\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/** Test-runner configs that mean \"this project has a suite\". */\nconst TEST_RUNNER_CONFIGS = [\n 'vitest.config.ts',\n 'vitest.config.js',\n 'vitest.config.mts',\n 'vite.config.ts',\n 'jest.config.ts',\n 'jest.config.js',\n]\n\n/**\n * Warn when a project has a `.env` and a test suite but no `.env.test`.\n *\n * KickJS loads dotenv at import time with `override: false`, so vars the\n * test runner pins are safe but everything it *doesn't* pin is backfilled\n * from the developer's `.env`. That makes the runner's `env` block a\n * hand-maintained allow-list, and the failure is silent: a suite can pin\n * its database URL and still reach live development services through the\n * vars it forgot.\n *\n * Under a test run, a `.env.test` / `.env.test.local` is read INSTEAD of the\n * generic `.env` and `.env.local` — no layering, no fallback — which closes\n * that hole, but only if one of those files exists. It is opt-in, and an\n * opt-in nobody discovers is worth nothing, hence this check.\n */\nconst TEST_ENV_FILES = ['.env.test', '.env.test.local'] as const\nconst GENERIC_ENV_FILES = ['.env', '.env.local'] as const\n\n/**\n * The value a `KICKJS_ENV_FILE` override would carry into a test run, or\n * `null` if nothing sets it. Checks the package's test scripts first (an\n * override pinned there applies to everyone), then doctor's own env.\n *\n * Deliberately a shallow scan of `scripts.test*`: a var exported from a\n * developer's shell profile or a CI job definition is not visible from\n * here, so this catches the common committed case rather than every case.\n */\nfunction findTestEnvFileOverride(ctx: DoctorContext): string | null {\n const scripts = ctx.pkg?.scripts\n if (scripts && typeof scripts === 'object') {\n for (const [name, value] of Object.entries(scripts)) {\n if (!name.startsWith('test') || typeof value !== 'string') continue\n const hit = /KICKJS_ENV_FILE=(\\S+)/.exec(value)\n if (hit?.[1]) return hit[1]\n }\n }\n return process.env['KICKJS_ENV_FILE'] ?? null\n}\n\nexport function checkTestEnvIsolation(ctx: DoctorContext): DoctorResult | null {\n const hasTestRunner = TEST_RUNNER_CONFIGS.some((f) => existsSync(join(ctx.cwd, f)))\n if (!hasTestRunner) return null\n\n const generic = GENERIC_ENV_FILES.filter((f) => existsSync(join(ctx.cwd, f)))\n if (generic.length === 0) return null\n\n const isolating = TEST_ENV_FILES.filter((f) => existsSync(join(ctx.cwd, f)))\n if (isolating.length > 0) {\n // `KICKJS_ENV_FILE` is consulted BEFORE the test-mode short-circuit, so\n // it wins outright: a script running `KICKJS_ENV_FILE=.env vitest` reads\n // `.env` no matter how complete the `.env.test` sitting next to it is.\n // Reporting a plain pass there would certify isolation that is not in\n // effect. Look for the override in the test scripts (where it would be\n // pinned for everyone) and in doctor's own environment.\n const override = findTestEnvFileOverride(ctx)\n if (override && override !== 'off' && override !== 'none') {\n return {\n name: 'test env isolation',\n status: 'warn',\n message: `${isolating.join(', ')} present, but KICKJS_ENV_FILE=${override} overrides it`,\n fix:\n `KICKJS_ENV_FILE is checked before test-mode isolation, so ${isolating[0]} is\\n` +\n `ignored and \"${override}\" is read instead. Drop the override to use the\\n` +\n 'test files, or set KICKJS_ENV_FILE=off if env comes from the runner.',\n }\n }\n return {\n name: 'test env isolation',\n status: 'pass',\n message: `${isolating.join(', ')} present`,\n }\n }\n\n return {\n name: 'test env isolation',\n status: 'warn',\n message: `${generic.join(', ')} present, no .env.test`,\n fix:\n \"Under NODE_ENV=test (or vitest's VITEST) KickJS reads .env.test / .env.test.local\\n\" +\n `INSTEAD of ${generic.join(' and ')} — no layering, no fallback. Without one of\\n` +\n 'those files, every var your test config does not pin resolves from your\\n' +\n 'development env — including DATABASE_URL and any third-party credentials.\\n' +\n '\\n' +\n 'Create .env.test (cp .env.test.example .env.test, if the project has one)\\n' +\n 'holding the whole environment the suite should run against\\n' +\n '(a var you leave out is then missing, not inherited), or set KICKJS_ENV_FILE=off\\n' +\n 'and supply env from the test runner.',\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 checkTestEnvIsolation,\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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"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs-cli v8.10.2
2
+ * @forinda/kickjs-cli v8.12.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,11 +8,11 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{a as e,d as t,f as n,h as r,l as i,m as a,n as o,p as s,t as c,u as l}from"./project-TbTVQCnh.mjs";import{r as u,u as d}from"./config-Ck9aQVpE.mjs";import{o as f}from"./package-manager--IUl91nq.mjs";import{join as p}from"node:path";import{execFileSync as m}from"node:child_process";const h=`https://kickjs.app/guide/fullstack-frontend.html`,g=[{value:`react-ts`,label:`React`},{value:`react-compiler-ts`,label:`React + Compiler`},{value:`vue-ts`,label:`Vue`},{value:`svelte-ts`,label:`Svelte`},{value:`solid-ts`,label:`Solid`},{value:`preact-ts`,label:`Preact`},{value:`lit-ts`,label:`Lit`},{value:`qwik-ts`,label:`Qwik`},{value:`vanilla-ts`,label:`Vanilla`}],_=`react-ts`;function v(e,t,n=_){let r=[`--template`,n,`--no-interactive`,`--no-immediate`];return e===`yarn`||e===`bun`?[e,`create`,`vite`,t,...r]:e===`npm`?[e,`create`,`vite@latest`,t,`--`,...r]:[e,`create`,`vite@latest`,t,...r]}async function y(d){let{name:g,directory:y,schemaLib:C=`zod`,runtime:w=`express`,packages:T=[],frontend:E=`kick`,viteTemplate:D=_}=d,O=[`pnpm`,`npm`,`yarn`,`bun`].includes(d.packageManager),k=O?d.packageManager:`pnpm`,A=y,j=e=>console.log(` ${e}`);d.packageManager!==void 0&&!O&&j(`Warning: unknown package manager '${String(d.packageManager)}' — falling back to pnpm.`),console.log(`\n Creating fullstack KickJS workspace: ${g}\n`),console.log(` Resolving package versions...`);let M=await o(),N=await c({name:`${g}-server`,directory:p(A,`server`),packageManager:k,template:`minimal`,schemaLib:C,runtime:w,packages:T,extraLayers:[`server-fullstack`],approveInstallScripts:!1,platformConfig:!1,initGit:!1,installDeps:!1,spaClientDir:`../web/dist`,versions:M});if(E===`vite`){console.log(`\n Handing web/ to create-vite (${D})...\n`);let[e,...t]=v(k,`web`,D);r(e,t,{cwd:A})}else await f(p(A,`web/package.json`),b(g,M));let P=t(k);await f(p(A,`package.json`),x(g,k,M[`@forinda/kickjs-cli`],P));let F=n(k,P);F&&await f(p(A,`.yarnrc.yml`),F),k===`pnpm`&&await f(p(A,`pnpm-workspace.yaml`),`packages:
11
+ import{a as e,d as t,f as n,h as r,l as i,m as a,n as o,p as s,t as c,u as l}from"./project-DBmrFgdd.mjs";import{f as u,r as d}from"./config-DMFwBdWg.mjs";import{o as f}from"./package-manager-C8ekNwsa.mjs";import{join as p}from"node:path";import{execFileSync as m}from"node:child_process";const h=`https://kickjs.app/guide/fullstack-frontend.html`,g=[{value:`react-ts`,label:`React`},{value:`react-compiler-ts`,label:`React + Compiler`},{value:`vue-ts`,label:`Vue`},{value:`svelte-ts`,label:`Svelte`},{value:`solid-ts`,label:`Solid`},{value:`preact-ts`,label:`Preact`},{value:`lit-ts`,label:`Lit`},{value:`qwik-ts`,label:`Qwik`},{value:`vanilla-ts`,label:`Vanilla`}],_=`react-ts`;function v(e,t,n=_){let r=[`--template`,n,`--no-interactive`,`--no-immediate`];return e===`yarn`||e===`bun`?[e,`create`,`vite`,t,...r]:e===`npm`?[e,`create`,`vite@latest`,t,`--`,...r]:[e,`create`,`vite@latest`,t,...r]}async function y(u){let{name:g,directory:y,schemaLib:C=`zod`,runtime:w=`express`,packages:T=[],frontend:E=`kick`,viteTemplate:D=_}=u,O=[`pnpm`,`npm`,`yarn`,`bun`].includes(u.packageManager),k=O?u.packageManager:`pnpm`,A=y,j=e=>console.log(` ${e}`);u.packageManager!==void 0&&!O&&j(`Warning: unknown package manager '${String(u.packageManager)}' — falling back to pnpm.`),console.log(`\n Creating fullstack KickJS workspace: ${g}\n`),console.log(` Resolving package versions...`);let M=await o(),N=await c({name:`${g}-server`,directory:p(A,`server`),packageManager:k,template:`minimal`,schemaLib:C,runtime:w,packages:T,extraLayers:[`server-fullstack`],approveInstallScripts:!1,platformConfig:!1,initGit:!1,installDeps:!1,spaClientDir:`../web/dist`,versions:M});if(E===`vite`){console.log(`\n Handing web/ to create-vite (${D})...\n`);let[e,...t]=v(k,`web`,D);r(e,t,{cwd:A})}else await f(p(A,`web/package.json`),b(g,M));let P=t(k);await f(p(A,`package.json`),x(g,k,M[`@forinda/kickjs-cli`],P));let F=n(k,P);F&&await f(p(A,`.yarnrc.yml`),F),k===`pnpm`&&await f(p(A,`pnpm-workspace.yaml`),`packages:
12
12
  - server
13
13
  - web
14
- `),e(k,A,N.builds);let I=[`fullstack-root`,...E===`vite`?[]:[`web-kick`]],L=s(a(I).files,{name:g,packageManager:k});for(let[e,t]of L)await f(p(A,e),t);await f(p(A,`README.md`),S(g,k,E)),await f(p(A,`Dockerfile`),i(`fullstack`,k,g,P)),await f(p(A,`.dockerignore`),l());let{generateAgentDocs:R}=await import(`./agent-docs-As_KjQBC.mjs`).then(e=>e.t);if(await R({outDir:A,name:g,pm:k,template:`fullstack`,only:`all`,force:!0}),d.installDeps){console.log(`\n Installing workspace dependencies with ${k}...\n`);try{r(k,[`install`],{cwd:A})}catch{console.log(`\n Warning: ${k} install failed. Run it manually.`)}}try{let{runTypegen:e}=await import(`./typegen-DJQ6WBwr.mjs`).then(e=>e.n);await e({cwd:p(A,`server`),allowDuplicates:!0,silent:!0})}catch{}if(d.initGit)try{m(`git`,[`init`],{cwd:A,stdio:`pipe`}),m(`git`,[`branch`,`-M`,`main`],{cwd:A,stdio:`pipe`}),m(`git`,[`add`,`-A`],{cwd:A,stdio:`pipe`}),m(`git`,[`commit`,`-m`,`chore: initial commit from kick new (fullstack)`],{cwd:A,stdio:`pipe`}),j(`Git repository initialized`)}catch{j(`Warning: git init failed (git may not be installed)`)}console.log(`
15
- Fullstack workspace scaffolded!`),console.log(),j(`Next steps:`),j(` cd ${g}`),d.installDeps||j(` ${k} install`),k===`pnpm`?j(` pnpm dev # runs server (kick dev) + web (vite) together`):(j(` ${k} run dev:server # terminal 1`),j(` ${k} run dev:web # terminal 2`)),j(``),E===`vite`?(j(`web/ is create-vite output, unwired. To type it against the API:`),j(` ${h}`)):(j(`The web app calls the API through @forinda/kickjs-client —`),j(`edit server/src/modules/hello, run \`${u(k,`kick`)} typegen\` in server/, and`),j(`web/src/App.tsx's types follow.`)),j(``)}function b(e,t){let n=e=>t[e]??`latest`;return`${JSON.stringify({name:`${e}-web`,private:!0,version:`0.0.0`,type:`module`,scripts:{dev:`vite`,build:`tsc --noEmit && vite build`,preview:`vite preview`,typecheck:`tsc --noEmit`},dependencies:{"@forinda/kickjs-client":n(`@forinda/kickjs-client`),react:n(`react`),"react-dom":n(`react-dom`)},devDependencies:{"@types/react":n(`@types/react`),"@types/react-dom":n(`@types/react-dom`),"@vitejs/plugin-react":n(`@vitejs/plugin-react`),typescript:n(`typescript`),vite:n(`vite`)}},null,2)}\n`}function x(e,n,r,i=t(n)){let a=n===`pnpm`?{dev:`pnpm --parallel -r run dev`,start:`pnpm --filter ./server run start`,"dev:server":`pnpm --filter ./server dev`,"dev:web":`pnpm --filter ./web dev`,build:`pnpm --filter ./server exec kick typegen && pnpm -r run build`,"build:netlify":`pnpm build && pnpm --filter ./server exec kick build:netlify`,"build:vercel":`pnpm build && pnpm --filter ./server exec kick build:vercel`,typecheck:`kick typecheck --cwd server && kick typecheck --cwd web`,test:`pnpm --filter ./server test`,format:`cd server && kick format`}:{start:`cd server && ${n} run start`,"dev:server":`cd server && ${n} run dev`,"dev:web":`cd web && ${n} run dev`,build:`cd server && kick typegen && cd .. && ${n} run build:server && ${n} run build:web`,"build:netlify":`${n} run build && cd server && kick build:netlify`,"build:vercel":`${n} run build && cd server && kick build:vercel`,"build:server":`cd server && ${n} run build`,"build:web":`cd web && ${n} run build`,typecheck:`kick typecheck --cwd server && kick typecheck --cwd web`,test:`cd server && ${n} run test`,format:`cd server && kick format`};return`${JSON.stringify({name:e,private:!0,version:`0.0.0`,type:`module`,...i?{packageManager:i}:{},scripts:a,devDependencies:{"@forinda/kickjs-cli":r},workspaces:[`server`,`web`]},null,2)}\n`}function S(e,t,n=`kick`){let r=`# ${e}
14
+ `),e(k,A,N.builds);let I=[`fullstack-root`,...E===`vite`?[]:[`web-kick`]],L=s(a(I).files,{name:g,packageManager:k});for(let[e,t]of L)await f(p(A,e),t);await f(p(A,`README.md`),S(g,k,E)),await f(p(A,`Dockerfile`),i(`fullstack`,k,g,P)),await f(p(A,`.dockerignore`),l());let{generateAgentDocs:R}=await import(`./agent-docs-CuD2-X3w.mjs`).then(e=>e.t);if(await R({outDir:A,name:g,pm:k,template:`fullstack`,only:`all`,force:!0}),u.installDeps){console.log(`\n Installing workspace dependencies with ${k}...\n`);try{r(k,[`install`],{cwd:A})}catch{console.log(`\n Warning: ${k} install failed. Run it manually.`)}}try{let{runTypegen:e}=await import(`./typegen-Lf0UPhdl.mjs`).then(e=>e.n);await e({cwd:p(A,`server`),allowDuplicates:!0,silent:!0})}catch{}if(u.initGit)try{m(`git`,[`init`],{cwd:A,stdio:`pipe`}),m(`git`,[`branch`,`-M`,`main`],{cwd:A,stdio:`pipe`}),m(`git`,[`add`,`-A`],{cwd:A,stdio:`pipe`}),m(`git`,[`commit`,`-m`,`chore: initial commit from kick new (fullstack)`],{cwd:A,stdio:`pipe`}),j(`Git repository initialized`)}catch{j(`Warning: git init failed (git may not be installed)`)}console.log(`
15
+ Fullstack workspace scaffolded!`),console.log(),j(`Next steps:`),j(` cd ${g}`),u.installDeps||j(` ${k} install`),k===`pnpm`?j(` pnpm dev # runs server (kick dev) + web (vite) together`):(j(` ${k} run dev:server # terminal 1`),j(` ${k} run dev:web # terminal 2`)),j(``),E===`vite`?(j(`web/ is create-vite output, unwired. To type it against the API:`),j(` ${h}`)):(j(`The web app calls the API through @forinda/kickjs-client —`),j(`edit server/src/modules/hello, run \`${d(k,`kick`)} typegen\` in server/, and`),j(`web/src/App.tsx's types follow.`)),j(``)}function b(e,t){let n=e=>t[e]??`latest`;return`${JSON.stringify({name:`${e}-web`,private:!0,version:`0.0.0`,type:`module`,scripts:{dev:`vite`,build:`tsc --noEmit && vite build`,preview:`vite preview`,typecheck:`tsc --noEmit`},dependencies:{"@forinda/kickjs-client":n(`@forinda/kickjs-client`),react:n(`react`),"react-dom":n(`react-dom`)},devDependencies:{"@types/react":n(`@types/react`),"@types/react-dom":n(`@types/react-dom`),"@vitejs/plugin-react":n(`@vitejs/plugin-react`),typescript:n(`typescript`),vite:n(`vite`)}},null,2)}\n`}function x(e,n,r,i=t(n)){let a=n===`pnpm`?{dev:`pnpm --parallel -r run dev`,start:`pnpm --filter ./server run start`,"dev:server":`pnpm --filter ./server dev`,"dev:web":`pnpm --filter ./web dev`,build:`pnpm --filter ./server exec kick typegen && pnpm -r run build`,"build:netlify":`pnpm build && pnpm --filter ./server exec kick build:netlify`,"build:vercel":`pnpm build && pnpm --filter ./server exec kick build:vercel`,typecheck:`kick typecheck --cwd server && kick typecheck --cwd web --no-typegen`,test:`pnpm --filter ./server test`,format:`cd server && kick format`}:{start:`cd server && ${n} run start`,"dev:server":`cd server && ${n} run dev`,"dev:web":`cd web && ${n} run dev`,build:`cd server && kick typegen && cd .. && ${n} run build:server && ${n} run build:web`,"build:netlify":`${n} run build && cd server && kick build:netlify`,"build:vercel":`${n} run build && cd server && kick build:vercel`,"build:server":`cd server && ${n} run build`,"build:web":`cd web && ${n} run build`,typecheck:`kick typecheck --cwd server && kick typecheck --cwd web --no-typegen`,test:`cd server && ${n} run test`,format:`cd server && kick format`};return`${JSON.stringify({name:e,private:!0,version:`0.0.0`,type:`module`,...i?{packageManager:i}:{},scripts:a,devDependencies:{"@forinda/kickjs-cli":r},workspaces:[`server`,`web`]},null,2)}\n`}function S(e,t,n=`kick`){let r=`# ${e}
16
16
 
17
17
  Fullstack KickJS workspace — typed end to end.
18
18
 
@@ -30,9 +30,9 @@ ${t===`pnpm`?`pnpm dev # server (kick dev) + web (vite), in parallel`
30
30
 
31
31
  Server: http://localhost:3000 · Web: http://localhost:5173 (Vite proxies \`/api\`).
32
32
 
33
- From this root: \`${d(t,`test`)}\` (the server's tests), \`${d(t,`typecheck`)}\` (both packages),
34
- \`${d(t,`format`)}\` and \`${d(t,`build`)}\`. The \`kick\` CLI itself runs in \`server/\`, which holds
35
- \`kick.config.ts\`: \`cd server && ${u(t,`kick`)} typegen\`.
33
+ From this root: \`${u(t,`test`)}\` (the server's tests), \`${u(t,`typecheck`)}\` (both packages),
34
+ \`${u(t,`format`)}\` and \`${u(t,`build`)}\`. The \`kick\` CLI itself runs in \`server/\`, which holds
35
+ \`kick.config.ts\`: \`cd server && ${d(t,`kick`)} typegen\`.
36
36
 
37
37
  ## The type loop
38
38