@forinda/kickjs-cli 8.12.1 → 8.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/{agent-docs-DZJSoa_5.mjs → agent-docs-BxuvlGy4.mjs} +18 -14
  2. package/dist/agent-docs-BxuvlGy4.mjs.map +1 -0
  3. package/dist/{agent-docs-GBEi-4ZM.mjs → agent-docs-Dq_azKfQ.mjs} +25 -21
  4. package/dist/{build-DZtpU_3Z.mjs → build-eFEdQsTj.mjs} +3 -3
  5. package/dist/{build-DZtpU_3Z.mjs.map → build-eFEdQsTj.mjs.map} +1 -1
  6. package/dist/{build-DkjrK7QX.mjs → build-t8pGkT7W.mjs} +2 -2
  7. package/dist/{builtins-BMZz5lPx.mjs → builtins-DCvidf69.mjs} +121 -121
  8. package/dist/{builtins-h-f_jNxU.mjs → builtins-ubUekPfw.mjs} +2 -2
  9. package/dist/cli.mjs +2 -2
  10. package/dist/{config-B7d6wm08.mjs → config-BIlu1hs0.mjs} +2 -2
  11. package/dist/{config-aEXDfmSS.mjs → config-D0zuz3Fx.mjs} +3 -3
  12. package/dist/{config-aEXDfmSS.mjs.map → config-D0zuz3Fx.mjs.map} +1 -1
  13. package/dist/{doctor-DKtiOCf2.mjs → doctor-BDlJqXWX.mjs} +20 -20
  14. package/dist/{doctor-DKtiOCf2.mjs.map → doctor-BDlJqXWX.mjs.map} +1 -1
  15. package/dist/fullstack-BWdQFw3R.mjs +111 -0
  16. package/dist/fullstack-BWdQFw3R.mjs.map +1 -0
  17. package/dist/fullstack-C6hCr30Z.mjs +110 -0
  18. package/dist/index.d.mts +8 -2
  19. package/dist/index.d.mts.map +1 -1
  20. package/dist/index.mjs +2 -2
  21. package/dist/package-manager-CSsqVTZ-.mjs +12 -0
  22. package/dist/package-manager-CSsqVTZ-.mjs.map +1 -0
  23. package/dist/package-manager-D5grCG_d.mjs +11 -0
  24. package/dist/{plugin-BqNQkm01.mjs → plugin-BXckYw0A.mjs} +2 -2
  25. package/dist/{plugin-Dc9DwVlE.mjs → plugin-CGlWHBFX.mjs} +2 -2
  26. package/dist/project-BE7J8TpW.mjs +133 -0
  27. package/dist/project-B_yEOLE4.mjs +134 -0
  28. package/dist/project-B_yEOLE4.mjs.map +1 -0
  29. package/dist/{project-root-By-1PuaD.mjs → project-root-B1zLyHn6.mjs} +2 -2
  30. package/dist/{project-root-C_H7sWTH.mjs → project-root-DYJU7ROd.mjs} +3 -3
  31. package/dist/{project-root-C_H7sWTH.mjs.map → project-root-DYJU7ROd.mjs.map} +1 -1
  32. package/dist/{rolldown-runtime-De7_Z3h7.mjs → rolldown-runtime-Bn82Wzut.mjs} +1 -1
  33. package/dist/{run-plugins-DY4qiPTu.mjs → run-plugins-B8vUgyD4.mjs} +108 -108
  34. package/dist/run-plugins-B8vUgyD4.mjs.map +1 -0
  35. package/dist/{typegen-C13eMf8Q.mjs → typegen-B3mw3F07.mjs} +5 -5
  36. package/dist/{typegen-C13eMf8Q.mjs.map → typegen-B3mw3F07.mjs.map} +1 -1
  37. package/dist/{typegen-ajfH3dB8.mjs → typegen-CAni3mL3.mjs} +4 -4
  38. package/dist/{types-m-1iCFmy.mjs → types-DJMtN7wo.mjs} +1 -1
  39. package/package.json +4 -4
  40. package/templates/base/files/README.md +11 -0
  41. package/templates/feature-devtools/feature.json +1 -1
  42. package/templates/template-inertia/feature.json +1 -3
  43. package/templates/template-inertia/files/vite.client.config.ts +4 -1
  44. package/templates/web-kick/files/web/vite.config.ts +4 -1
  45. package/dist/agent-docs-DZJSoa_5.mjs.map +0 -1
  46. package/dist/fullstack-B6ZY4jGe.mjs +0 -100
  47. package/dist/fullstack-B6ZY4jGe.mjs.map +0 -1
  48. package/dist/fullstack-DBLIBX52.mjs +0 -99
  49. package/dist/package-manager-2OKBz8KC.mjs +0 -11
  50. package/dist/package-manager-C9253oKe.mjs +0 -12
  51. package/dist/package-manager-C9253oKe.mjs.map +0 -1
  52. package/dist/project-BktC0yLE.mjs +0 -134
  53. package/dist/project-BktC0yLE.mjs.map +0 -1
  54. package/dist/project-Cr8KrIZg.mjs +0 -133
  55. package/dist/prompts-iLmIrMD4.mjs +0 -12
  56. package/dist/prompts-iLmIrMD4.mjs.map +0 -1
  57. package/dist/run-plugins-DY4qiPTu.mjs.map +0 -1
  58. package/templates/host-config/files/vercel.json +0 -5
  59. /package/templates/{host-config → deploy-netlify}/files/netlify.toml +0 -0
  60. /package/templates/{fullstack-root → deploy-netlify-workspace}/files/netlify.toml +0 -0
  61. /package/templates/{base → deploy-serverless}/files/src/serverless.ts +0 -0
  62. /package/templates/{fullstack-root → deploy-vercel}/files/vercel.json +0 -0
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs-cli v8.12.1
2
+ * @forinda/kickjs-cli v8.13.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,4 +8,4 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- export{t as builtinCliPlugins}from"./run-plugins-DY4qiPTu.mjs";
11
+ export{t as builtinCliPlugins}from"./run-plugins-B8vUgyD4.mjs";
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs-cli v8.12.1
2
+ * @forinda/kickjs-cli v8.13.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,4 +8,4 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{_ as e}from"./project-Cr8KrIZg.mjs";import{o as t}from"./config-B7d6wm08.mjs";import{n}from"./plugin-Dc9DwVlE.mjs";import{t as r}from"./builtins-BMZz5lPx.mjs";import{t as i}from"./project-root-By-1PuaD.mjs";import{Command as a}from"commander";import{readFileSync as o}from"node:fs";import{delimiter as s,dirname as c,join as l}from"node:path";import{fileURLToPath as u}from"node:url";function d(e,t){if(!t?.commands?.length)return;let n=new Set(e.commands.map(e=>e.name()));for(let r of t.commands)n.has(r.name)?console.warn(` Warning: custom command '${r.name}' skipped — conflicts with a built-in command`):f(e,r)}function f(t,n){let r=t.command(n.name).description(n.description);if(n.aliases)for(let e of n.aliases)r.alias(e);r.allowUnknownOption(!0),r.argument(`[args...]`,`Additional arguments passed to the command`);function i(){return{PATH:`${l(process.cwd(),`node_modules`,`.bin`)}${s}${process.env.PATH??``}`}}r.action(t=>{let r=t.join(` `),a=Array.isArray(n.steps)?n.steps:[n.steps];for(let t of a){let a=r?`${t} ${r}`:t;console.log(` $ ${a}`);try{e(a,void 0,i())}catch{console.error(` Command failed: ${n.name}`),process.exitCode=1;return}}})}const p=c(u(import.meta.url)),m=JSON.parse(o(l(p,`..`,`package.json`),`utf-8`));async function h(){let e=new a;e.name(`kick`).description(`KickJS — A production-grade, decorator-driven Node.js framework`).version(m.version);let o=i(process.cwd()),s=o,c=await t(s)??{},l=[...r,...c.plugins??[]],u=n(l,c.commands??[]);await u.register(e,{cwd:s,projectRoot:o,config:c,log:e=>console.log(e)}),d(e,{...c,commands:u.commands}),e.showHelpAfterError();let f=process.argv.map(e=>e===`-v`?`--version`:e);await e.parseAsync(f)}h().catch(e=>{console.error(e instanceof Error?e.message:e),process.exitCode=1});export{};
11
+ import{_ as e}from"./project-BE7J8TpW.mjs";import{o as t}from"./config-BIlu1hs0.mjs";import{n}from"./plugin-CGlWHBFX.mjs";import{t as r}from"./builtins-DCvidf69.mjs";import{t as i}from"./project-root-B1zLyHn6.mjs";import{Command as a}from"commander";import{readFileSync as o}from"node:fs";import{delimiter as s,dirname as c,join as l}from"node:path";import{fileURLToPath as u}from"node:url";function d(e,t){if(!t?.commands?.length)return;let n=new Set(e.commands.map(e=>e.name()));for(let r of t.commands)n.has(r.name)?console.warn(` Warning: custom command '${r.name}' skipped — conflicts with a built-in command`):f(e,r)}function f(t,n){let r=t.command(n.name).description(n.description);if(n.aliases)for(let e of n.aliases)r.alias(e);r.allowUnknownOption(!0),r.argument(`[args...]`,`Additional arguments passed to the command`);function i(){return{PATH:`${l(process.cwd(),`node_modules`,`.bin`)}${s}${process.env.PATH??``}`}}r.action(t=>{let r=t.join(` `),a=Array.isArray(n.steps)?n.steps:[n.steps];for(let t of a){let a=r?`${t} ${r}`:t;console.log(` $ ${a}`);try{e(a,void 0,i())}catch{console.error(` Command failed: ${n.name}`),process.exitCode=1;return}}})}const p=c(u(import.meta.url)),m=JSON.parse(o(l(p,`..`,`package.json`),`utf-8`));async function h(){let e=new a;e.name(`kick`).description(`KickJS — A production-grade, decorator-driven Node.js framework`).version(m.version);let o=i(process.cwd()),s=o,c=await t(s)??{},l=[...r,...c.plugins??[]],u=n(l,c.commands??[]);await u.register(e,{cwd:s,projectRoot:o,config:c,log:e=>console.log(e)}),d(e,{...c,commands:u.commands}),e.showHelpAfterError();let f=process.argv.map(e=>e===`-v`?`--version`:e);await e.parseAsync(f)}h().catch(e=>{console.error(e instanceof Error?e.message:e),process.exitCode=1});export{};
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs-cli v8.12.1
2
+ * @forinda/kickjs-cli v8.13.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,5 +8,5 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{t as e}from"./rolldown-runtime-De7_Z3h7.mjs";import{existsSync as t,mkdirSync as n,readFileSync as r,writeFileSync as i}from"node:fs";import{dirname as a,isAbsolute as o,join as s,relative as c,resolve as l}from"node:path";import{fileURLToPath as u}from"node:url";import{access as d,readFile as f}from"node:fs/promises";var p=/* @__PURE__ */ e({BUILTIN_REPO_TYPES:()=>_,DEPRECATED_REPO_TYPES:()=>v,PACKAGE_MANAGERS:()=>m,PLURALIZE_DEFAULT:()=>!1,defineConfig:()=>b,execBin:()=>g,hasDependency:()=>x,hasSwagger:()=>C,loadKickConfig:()=>M,resolveModuleConfig:()=>k,resolvePluralize:()=>D,resolvePluralizeRoutes:()=>O,resolveSchemaLib:()=>w,resolveTokenScope:()=>T,runScript:()=>h,testImportSource:()=>S,validateAssetMap:()=>P,warnIfDeprecatedRepo:()=>y,writeAssetConfigSnapshot:()=>N});const m=[`pnpm`,`npm`,`yarn`,`bun`];function h(e,t){return e===`npm`||e===`bun`?`${e} run ${t}`:`${e} ${t}`}function g(e,t){return{npm:`npx ${t}`,pnpm:`pnpm exec ${t}`,yarn:`yarn ${t}`,bun:`bunx ${t}`}[e]}const _=[`inmemory`],v=[`prisma`,`drizzle`];function y(e){return v.includes(e)?(console.warn(` Note: '${e}' is no longer a built-in repository preset — a repository shaped to it is that library's interface, not KickJS's.\n Scaffolding a generic custom-repository stub instead; wire it to ${e} yourself.`),!0):!1}function b(e){return e}function x(e,n,i=`runtime`){try{let a=s(e,`package.json`);if(!t(a))return!1;let o=JSON.parse(r(a,`utf-8`));return o.dependencies?.[n]?!0:i===`any`&&!!o.devDependencies?.[n]}catch{return!1}}function S(e){return x(e,`vite-plus`,`any`)&&!x(e,`vitest`,`any`)?`vite-plus/test`:`vitest`}function C(e){return x(e,`@forinda/kickjs-swagger`,`runtime`)}function w(e){return x(e,`valibot`,`runtime`)?`valibot`:x(e,`yup`,`runtime`)?`yup`:`zod`}function T(e,n){if(e?.tokenScope&&typeof e.tokenScope==`string`&&e.tokenScope.length>0){let t=E(e.tokenScope);if(t.length>0)return t}try{let e=s(n,`package.json`);if(t(e)){let t=JSON.parse(r(e,`utf-8`));if(typeof t.name==`string`&&t.name.length>0){let e=t.name.match(/^@([^/]+)\//),n=E(e?e[1]:t.name);if(n.length>0)return n}}}catch{}return`app`}function E(e){return e.toLowerCase().replace(/[^a-z0-9-]/g,`-`).replace(/^-+|-+$/g,``).replace(/-{2,}/g,`-`)}function D(e,t){return e??t?.pluralize??!1}function O(e,t){return e??t?.pluralizeRoutes??!0}function k(e){if(!e)return{};let t={dir:e.modules?.dir,repo:e.modules?.repo,schemaDir:e.modules?.schemaDir,pluralize:e.modules?.pluralize,pluralizeRoutes:e.modules?.pluralizeRoutes,style:e.modules?.style};return t.style!==void 0&&t.style!==`define`&&t.style!==`class`&&(console.warn(` Warning: modules.style '${t.style}' is not a valid value (expected 'define' or 'class'). Falling back to 'define'.`),t.style=`define`),t.repo&&typeof t.repo==`string`&&!_.includes(t.repo)&&(y(t.repo)||console.warn(` Warning: modules.repo '${t.repo}' is not a built-in type (${_.join(`, `)}). It will generate a stub repository. Use { name: '${t.repo}' } to silence this warning.`)),t}const A=[`kick.config.ts`,`kick.config.js`,`kick.config.mjs`,`kick.config.json`];function j(e){for(let n=e;;n=a(n)){if(t(s(n,`node_modules`,`@forinda`,`kickjs-cli`,`package.json`)))return{};if(a(n)===n)break}let n=s(a(u(import.meta.url)),`..`),r=[s(n,`dist`,`index.mjs`),s(n,`src`,`index.ts`)].find(t);return r?{"@forinda/kickjs-cli":r}:{}}async function M(e){let{findProjectRoot:t}=await import(`./project-root-By-1PuaD.mjs`).then(e=>e.n),n=t(e);for(let e of A){let t=s(n,e);try{await d(t)}catch{continue}if(e.endsWith(`.json`)){let e=await f(t,`utf-8`);return JSON.parse(e)}if(e.endsWith(`.ts`)){let r;try{r=await import(`jiti`)}catch(t){let n=t instanceof Error?t.message:String(t);n.includes(`Cannot find package 'jiti'`)||n.includes(`ERR_MODULE_NOT_FOUND`)?console.warn(`Warning: Failed to load ${e} — 'jiti' is required for TypeScript configs. Install jiti as a dev dependency, or rename the file to kick.config.js / kick.config.mjs / kick.config.json.`):console.warn(`Warning: Failed to initialize jiti for ${e}: ${n}`);continue}try{let e=await r.createJiti(n,{interopDefault:!0,fsCache:!1,alias:j(n),tsconfigPaths:!0}).import(t,{default:!0}),i=P(e,n);for(let e of i)console.warn(` Warning: ${e}`);return N(n,e),e}catch(t){let n=t instanceof Error?t.message:String(t);console.warn(`Warning: Failed to load ${e}: ${n}`);continue}}try{let{pathToFileURL:e}=await import(`node:url`),r=await import(e(t).href),i=r.default??r,a=P(i,n);for(let e of a)console.warn(` Warning: ${e}`);return N(n,i),i}catch(t){let n=t instanceof Error?t.message:String(t);console.warn(`Warning: Failed to load ${e}: ${n}`);continue}}return null}function N(e,t){if(t?.assetMap&&Object.keys(t.assetMap).length!==0)try{let r=s(e,`.kickjs`);n(r,{recursive:!0});let a={version:1,assetMap:t.assetMap,...t.build?.outDir?{build:{outDir:t.build.outDir}}:{}};i(s(r,`kick.config.json`),JSON.stringify(a,null,2)+`
11
+ import{t as e}from"./rolldown-runtime-Bn82Wzut.mjs";import{existsSync as t,mkdirSync as n,readFileSync as r,writeFileSync as i}from"node:fs";import{dirname as a,isAbsolute as o,join as s,relative as c,resolve as l}from"node:path";import{fileURLToPath as u}from"node:url";import{access as d,readFile as f}from"node:fs/promises";var p=/* @__PURE__ */ e({BUILTIN_REPO_TYPES:()=>_,DEPRECATED_REPO_TYPES:()=>v,PACKAGE_MANAGERS:()=>m,PLURALIZE_DEFAULT:()=>!1,defineConfig:()=>b,execBin:()=>g,hasDependency:()=>x,hasSwagger:()=>C,loadKickConfig:()=>M,resolveModuleConfig:()=>k,resolvePluralize:()=>D,resolvePluralizeRoutes:()=>O,resolveSchemaLib:()=>w,resolveTokenScope:()=>T,runScript:()=>h,testImportSource:()=>S,validateAssetMap:()=>P,warnIfDeprecatedRepo:()=>y,writeAssetConfigSnapshot:()=>N});const m=[`pnpm`,`npm`,`yarn`,`bun`];function h(e,t){return e===`npm`||e===`bun`?`${e} run ${t}`:`${e} ${t}`}function g(e,t){return{npm:`npx ${t}`,pnpm:`pnpm exec ${t}`,yarn:`yarn ${t}`,bun:`bunx ${t}`}[e]}const _=[`inmemory`],v=[`prisma`,`drizzle`];function y(e){return v.includes(e)?(console.warn(` Note: '${e}' is no longer a built-in repository preset — a repository shaped to it is that library's interface, not KickJS's.\n Scaffolding a generic custom-repository stub instead; wire it to ${e} yourself.`),!0):!1}function b(e){return e}function x(e,n,i=`runtime`){try{let a=s(e,`package.json`);if(!t(a))return!1;let o=JSON.parse(r(a,`utf-8`));return o.dependencies?.[n]?!0:i===`any`&&!!o.devDependencies?.[n]}catch{return!1}}function S(e){return x(e,`vite-plus`,`any`)&&!x(e,`vitest`,`any`)?`vite-plus/test`:`vitest`}function C(e){return x(e,`@forinda/kickjs-swagger`,`runtime`)}function w(e){return x(e,`valibot`,`runtime`)?`valibot`:x(e,`yup`,`runtime`)?`yup`:`zod`}function T(e,n){if(e?.tokenScope&&typeof e.tokenScope==`string`&&e.tokenScope.length>0){let t=E(e.tokenScope);if(t.length>0)return t}try{let e=s(n,`package.json`);if(t(e)){let t=JSON.parse(r(e,`utf-8`));if(typeof t.name==`string`&&t.name.length>0){let e=t.name.match(/^@([^/]+)\//),n=E(e?e[1]:t.name);if(n.length>0)return n}}}catch{}return`app`}function E(e){return e.toLowerCase().replace(/[^a-z0-9-]/g,`-`).replace(/^-+|-+$/g,``).replace(/-{2,}/g,`-`)}function D(e,t){return e??t?.pluralize??!1}function O(e,t){return e??t?.pluralizeRoutes??!0}function k(e){if(!e)return{};let t={dir:e.modules?.dir,repo:e.modules?.repo,schemaDir:e.modules?.schemaDir,pluralize:e.modules?.pluralize,pluralizeRoutes:e.modules?.pluralizeRoutes,style:e.modules?.style};return t.style!==void 0&&t.style!==`define`&&t.style!==`class`&&(console.warn(` Warning: modules.style '${t.style}' is not a valid value (expected 'define' or 'class'). Falling back to 'define'.`),t.style=`define`),t.repo&&typeof t.repo==`string`&&!_.includes(t.repo)&&(y(t.repo)||console.warn(` Warning: modules.repo '${t.repo}' is not a built-in type (${_.join(`, `)}). It will generate a stub repository. Use { name: '${t.repo}' } to silence this warning.`)),t}const A=[`kick.config.ts`,`kick.config.js`,`kick.config.mjs`,`kick.config.json`];function j(e){for(let n=e;;n=a(n)){if(t(s(n,`node_modules`,`@forinda`,`kickjs-cli`,`package.json`)))return{};if(a(n)===n)break}let n=s(a(u(import.meta.url)),`..`),r=[s(n,`dist`,`index.mjs`),s(n,`src`,`index.ts`)].find(t);return r?{"@forinda/kickjs-cli":r}:{}}async function M(e){let{findProjectRoot:t}=await import(`./project-root-B1zLyHn6.mjs`).then(e=>e.n),n=t(e);for(let e of A){let t=s(n,e);try{await d(t)}catch{continue}if(e.endsWith(`.json`)){let e=await f(t,`utf-8`);return JSON.parse(e)}if(e.endsWith(`.ts`)){let r;try{r=await import(`jiti`)}catch(t){let n=t instanceof Error?t.message:String(t);n.includes(`Cannot find package 'jiti'`)||n.includes(`ERR_MODULE_NOT_FOUND`)?console.warn(`Warning: Failed to load ${e} — 'jiti' is required for TypeScript configs. Install jiti as a dev dependency, or rename the file to kick.config.js / kick.config.mjs / kick.config.json.`):console.warn(`Warning: Failed to initialize jiti for ${e}: ${n}`);continue}try{let e=await r.createJiti(n,{interopDefault:!0,fsCache:!1,alias:j(n),tsconfigPaths:!0}).import(t,{default:!0}),i=P(e,n);for(let e of i)console.warn(` Warning: ${e}`);return N(n,e),e}catch(t){let n=t instanceof Error?t.message:String(t);console.warn(`Warning: Failed to load ${e}: ${n}`);continue}}try{let{pathToFileURL:e}=await import(`node:url`),r=await import(e(t).href),i=r.default??r,a=P(i,n);for(let e of a)console.warn(` Warning: ${e}`);return N(n,i),i}catch(t){let n=t instanceof Error?t.message:String(t);console.warn(`Warning: Failed to load ${e}: ${n}`);continue}}return null}function N(e,t){if(t?.assetMap&&Object.keys(t.assetMap).length!==0)try{let r=s(e,`.kickjs`);n(r,{recursive:!0});let a={version:1,assetMap:t.assetMap,...t.build?.outDir?{build:{outDir:t.build.outDir}}:{}};i(s(r,`kick.config.json`),JSON.stringify(a,null,2)+`
12
12
  `,`utf-8`)}catch{}}function P(e,n){let r=[];if(!e?.assetMap)return r;let i=l(n);for(let[a,o]of Object.entries(e.assetMap)){if(!a||a.includes(`/`)){r.push(`assetMap key '${a}' is invalid — must be a non-empty string without '/'`);continue}if(typeof o?.src!=`string`||o.src.length===0){r.push(`assetMap.${a} is missing a non-empty 'src' field`);continue}let e=l(n,o.src);t(e)||r.push(`assetMap.${a}.src ('${o.src}') does not exist — typegen + build will fail`),o.dest&&F(l(n,o.dest),i)&&r.push(`assetMap.${a}.dest ('${o.dest}') resolves outside the project root — refusing to copy`)}return r}function F(e,t){let n=c(t,e);return n===``?!1:n.startsWith(`..`)||o(n)}export{C as a,D as c,T as d,h as f,x as i,O as l,y as m,p as n,M as o,S as p,g as r,k as s,m as t,w as u};
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs-cli v8.12.1
2
+ * @forinda/kickjs-cli v8.13.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,6 +8,6 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{t as e}from"./rolldown-runtime-De7_Z3h7.mjs";import{dirname as t,isAbsolute as n,join as r,relative as i,resolve as a}from"node:path";import{existsSync as o,mkdirSync as s,readFileSync as c,writeFileSync as l}from"node:fs";import{access as u,readFile as d}from"node:fs/promises";import{fileURLToPath as f}from"node:url";var p=/* @__PURE__ */ e({BUILTIN_REPO_TYPES:()=>_,DEPRECATED_REPO_TYPES:()=>v,PACKAGE_MANAGERS:()=>m,PLURALIZE_DEFAULT:()=>!1,defineConfig:()=>b,execBin:()=>g,hasDependency:()=>x,hasSwagger:()=>C,loadKickConfig:()=>M,resolveModuleConfig:()=>k,resolvePluralize:()=>D,resolvePluralizeRoutes:()=>O,resolveSchemaLib:()=>w,resolveTokenScope:()=>T,runScript:()=>h,testImportSource:()=>S,validateAssetMap:()=>P,warnIfDeprecatedRepo:()=>y,writeAssetConfigSnapshot:()=>N});const m=[`pnpm`,`npm`,`yarn`,`bun`];function h(e,t){return e===`npm`||e===`bun`?`${e} run ${t}`:`${e} ${t}`}function g(e,t){return{npm:`npx ${t}`,pnpm:`pnpm exec ${t}`,yarn:`yarn ${t}`,bun:`bunx ${t}`}[e]}const _=[`inmemory`],v=[`prisma`,`drizzle`];function y(e){return v.includes(e)?(console.warn(` Note: '${e}' is no longer a built-in repository preset — a repository shaped to it is that library's interface, not KickJS's.\n Scaffolding a generic custom-repository stub instead; wire it to ${e} yourself.`),!0):!1}function b(e){return e}function x(e,t,n=`runtime`){try{let i=r(e,`package.json`);if(!o(i))return!1;let a=JSON.parse(c(i,`utf-8`));return a.dependencies?.[t]?!0:n===`any`&&!!a.devDependencies?.[t]}catch{return!1}}function S(e){return x(e,`vite-plus`,`any`)&&!x(e,`vitest`,`any`)?`vite-plus/test`:`vitest`}function C(e){return x(e,`@forinda/kickjs-swagger`,`runtime`)}function w(e){return x(e,`valibot`,`runtime`)?`valibot`:x(e,`yup`,`runtime`)?`yup`:`zod`}function T(e,t){if(e?.tokenScope&&typeof e.tokenScope==`string`&&e.tokenScope.length>0){let t=E(e.tokenScope);if(t.length>0)return t}try{let e=r(t,`package.json`);if(o(e)){let t=JSON.parse(c(e,`utf-8`));if(typeof t.name==`string`&&t.name.length>0){let e=t.name.match(/^@([^/]+)\//),n=E(e?e[1]:t.name);if(n.length>0)return n}}}catch{}return`app`}function E(e){return e.toLowerCase().replace(/[^a-z0-9-]/g,`-`).replace(/^-+|-+$/g,``).replace(/-{2,}/g,`-`)}function D(e,t){return e??t?.pluralize??!1}function O(e,t){return e??t?.pluralizeRoutes??!0}function k(e){if(!e)return{};let t={dir:e.modules?.dir,repo:e.modules?.repo,schemaDir:e.modules?.schemaDir,pluralize:e.modules?.pluralize,pluralizeRoutes:e.modules?.pluralizeRoutes,style:e.modules?.style};return t.style!==void 0&&t.style!==`define`&&t.style!==`class`&&(console.warn(` Warning: modules.style '${t.style}' is not a valid value (expected 'define' or 'class'). Falling back to 'define'.`),t.style=`define`),t.repo&&typeof t.repo==`string`&&!_.includes(t.repo)&&(y(t.repo)||console.warn(` Warning: modules.repo '${t.repo}' is not a built-in type (${_.join(`, `)}). It will generate a stub repository. Use { name: '${t.repo}' } to silence this warning.`)),t}const A=[`kick.config.ts`,`kick.config.js`,`kick.config.mjs`,`kick.config.json`];function j(e){for(let n=e;;n=t(n)){if(o(r(n,`node_modules`,`@forinda`,`kickjs-cli`,`package.json`)))return{};if(t(n)===n)break}let n=r(t(f(import.meta.url)),`..`),i=[r(n,`dist`,`index.mjs`),r(n,`src`,`index.ts`)].find(o);return i?{"@forinda/kickjs-cli":i}:{}}async function M(e){let{findProjectRoot:t}=await import(`./project-root-C_H7sWTH.mjs`).then(e=>e.n),n=t(e);for(let e of A){let t=r(n,e);try{await u(t)}catch{continue}if(e.endsWith(`.json`)){let e=await d(t,`utf-8`);return JSON.parse(e)}if(e.endsWith(`.ts`)){let r;try{r=await import(`jiti`)}catch(t){let n=t instanceof Error?t.message:String(t);n.includes(`Cannot find package 'jiti'`)||n.includes(`ERR_MODULE_NOT_FOUND`)?console.warn(`Warning: Failed to load ${e} — 'jiti' is required for TypeScript configs. Install jiti as a dev dependency, or rename the file to kick.config.js / kick.config.mjs / kick.config.json.`):console.warn(`Warning: Failed to initialize jiti for ${e}: ${n}`);continue}try{let e=await r.createJiti(n,{interopDefault:!0,fsCache:!1,alias:j(n),tsconfigPaths:!0}).import(t,{default:!0}),i=P(e,n);for(let e of i)console.warn(` Warning: ${e}`);return N(n,e),e}catch(t){let n=t instanceof Error?t.message:String(t);console.warn(`Warning: Failed to load ${e}: ${n}`);continue}}try{let{pathToFileURL:e}=await import(`node:url`),r=await import(e(t).href),i=r.default??r,a=P(i,n);for(let e of a)console.warn(` Warning: ${e}`);return N(n,i),i}catch(t){let n=t instanceof Error?t.message:String(t);console.warn(`Warning: Failed to load ${e}: ${n}`);continue}}return null}function N(e,t){if(t?.assetMap&&Object.keys(t.assetMap).length!==0)try{let n=r(e,`.kickjs`);s(n,{recursive:!0});let i={version:1,assetMap:t.assetMap,...t.build?.outDir?{build:{outDir:t.build.outDir}}:{}};l(r(n,`kick.config.json`),JSON.stringify(i,null,2)+`
11
+ import{t as e}from"./rolldown-runtime-Bn82Wzut.mjs";import{dirname as t,isAbsolute as n,join as r,relative as i,resolve as a}from"node:path";import{existsSync as o,mkdirSync as s,readFileSync as c,writeFileSync as l}from"node:fs";import{access as u,readFile as d}from"node:fs/promises";import{fileURLToPath as f}from"node:url";var p=/* @__PURE__ */ e({BUILTIN_REPO_TYPES:()=>_,DEPRECATED_REPO_TYPES:()=>v,PACKAGE_MANAGERS:()=>m,PLURALIZE_DEFAULT:()=>!1,defineConfig:()=>b,execBin:()=>g,hasDependency:()=>x,hasSwagger:()=>C,loadKickConfig:()=>M,resolveModuleConfig:()=>k,resolvePluralize:()=>D,resolvePluralizeRoutes:()=>O,resolveSchemaLib:()=>w,resolveTokenScope:()=>T,runScript:()=>h,testImportSource:()=>S,validateAssetMap:()=>P,warnIfDeprecatedRepo:()=>y,writeAssetConfigSnapshot:()=>N});const m=[`pnpm`,`npm`,`yarn`,`bun`];function h(e,t){return e===`npm`||e===`bun`?`${e} run ${t}`:`${e} ${t}`}function g(e,t){return{npm:`npx ${t}`,pnpm:`pnpm exec ${t}`,yarn:`yarn ${t}`,bun:`bunx ${t}`}[e]}const _=[`inmemory`],v=[`prisma`,`drizzle`];function y(e){return v.includes(e)?(console.warn(` Note: '${e}' is no longer a built-in repository preset — a repository shaped to it is that library's interface, not KickJS's.\n Scaffolding a generic custom-repository stub instead; wire it to ${e} yourself.`),!0):!1}function b(e){return e}function x(e,t,n=`runtime`){try{let i=r(e,`package.json`);if(!o(i))return!1;let a=JSON.parse(c(i,`utf-8`));return a.dependencies?.[t]?!0:n===`any`&&!!a.devDependencies?.[t]}catch{return!1}}function S(e){return x(e,`vite-plus`,`any`)&&!x(e,`vitest`,`any`)?`vite-plus/test`:`vitest`}function C(e){return x(e,`@forinda/kickjs-swagger`,`runtime`)}function w(e){return x(e,`valibot`,`runtime`)?`valibot`:x(e,`yup`,`runtime`)?`yup`:`zod`}function T(e,t){if(e?.tokenScope&&typeof e.tokenScope==`string`&&e.tokenScope.length>0){let t=E(e.tokenScope);if(t.length>0)return t}try{let e=r(t,`package.json`);if(o(e)){let t=JSON.parse(c(e,`utf-8`));if(typeof t.name==`string`&&t.name.length>0){let e=t.name.match(/^@([^/]+)\//),n=E(e?e[1]:t.name);if(n.length>0)return n}}}catch{}return`app`}function E(e){return e.toLowerCase().replace(/[^a-z0-9-]/g,`-`).replace(/^-+|-+$/g,``).replace(/-{2,}/g,`-`)}function D(e,t){return e??t?.pluralize??!1}function O(e,t){return e??t?.pluralizeRoutes??!0}function k(e){if(!e)return{};let t={dir:e.modules?.dir,repo:e.modules?.repo,schemaDir:e.modules?.schemaDir,pluralize:e.modules?.pluralize,pluralizeRoutes:e.modules?.pluralizeRoutes,style:e.modules?.style};return t.style!==void 0&&t.style!==`define`&&t.style!==`class`&&(console.warn(` Warning: modules.style '${t.style}' is not a valid value (expected 'define' or 'class'). Falling back to 'define'.`),t.style=`define`),t.repo&&typeof t.repo==`string`&&!_.includes(t.repo)&&(y(t.repo)||console.warn(` Warning: modules.repo '${t.repo}' is not a built-in type (${_.join(`, `)}). It will generate a stub repository. Use { name: '${t.repo}' } to silence this warning.`)),t}const A=[`kick.config.ts`,`kick.config.js`,`kick.config.mjs`,`kick.config.json`];function j(e){for(let n=e;;n=t(n)){if(o(r(n,`node_modules`,`@forinda`,`kickjs-cli`,`package.json`)))return{};if(t(n)===n)break}let n=r(t(f(import.meta.url)),`..`),i=[r(n,`dist`,`index.mjs`),r(n,`src`,`index.ts`)].find(o);return i?{"@forinda/kickjs-cli":i}:{}}async function M(e){let{findProjectRoot:t}=await import(`./project-root-DYJU7ROd.mjs`).then(e=>e.n),n=t(e);for(let e of A){let t=r(n,e);try{await u(t)}catch{continue}if(e.endsWith(`.json`)){let e=await d(t,`utf-8`);return JSON.parse(e)}if(e.endsWith(`.ts`)){let r;try{r=await import(`jiti`)}catch(t){let n=t instanceof Error?t.message:String(t);n.includes(`Cannot find package 'jiti'`)||n.includes(`ERR_MODULE_NOT_FOUND`)?console.warn(`Warning: Failed to load ${e} — 'jiti' is required for TypeScript configs. Install jiti as a dev dependency, or rename the file to kick.config.js / kick.config.mjs / kick.config.json.`):console.warn(`Warning: Failed to initialize jiti for ${e}: ${n}`);continue}try{let e=await r.createJiti(n,{interopDefault:!0,fsCache:!1,alias:j(n),tsconfigPaths:!0}).import(t,{default:!0}),i=P(e,n);for(let e of i)console.warn(` Warning: ${e}`);return N(n,e),e}catch(t){let n=t instanceof Error?t.message:String(t);console.warn(`Warning: Failed to load ${e}: ${n}`);continue}}try{let{pathToFileURL:e}=await import(`node:url`),r=await import(e(t).href),i=r.default??r,a=P(i,n);for(let e of a)console.warn(` Warning: ${e}`);return N(n,i),i}catch(t){let n=t instanceof Error?t.message:String(t);console.warn(`Warning: Failed to load ${e}: ${n}`);continue}}return null}function N(e,t){if(t?.assetMap&&Object.keys(t.assetMap).length!==0)try{let n=r(e,`.kickjs`);s(n,{recursive:!0});let i={version:1,assetMap:t.assetMap,...t.build?.outDir?{build:{outDir:t.build.outDir}}:{}};l(r(n,`kick.config.json`),JSON.stringify(i,null,2)+`
12
12
  `,`utf-8`)}catch{}}function P(e,t){let n=[];if(!e?.assetMap)return n;let r=a(t);for(let[i,s]of Object.entries(e.assetMap)){if(!i||i.includes(`/`)){n.push(`assetMap key '${i}' is invalid — must be a non-empty string without '/'`);continue}if(typeof s?.src!=`string`||s.src.length===0){n.push(`assetMap.${i} is missing a non-empty 'src' field`);continue}let e=a(t,s.src);o(e)||n.push(`assetMap.${i}.src ('${s.src}') does not exist — typegen + build will fail`),s.dest&&F(a(t,s.dest),r)&&n.push(`assetMap.${i}.dest ('${s.dest}') resolves outside the project root — refusing to copy`)}return n}function F(e,t){let r=i(t,e);return r===``?!1:r.startsWith(`..`)||n(r)}export{x as a,k as c,w as d,T as f,y as h,g as i,D as l,S as m,p as n,C as o,h as p,b as r,M as s,m as t,O as u};
13
- //# sourceMappingURL=config-aEXDfmSS.mjs.map
13
+ //# sourceMappingURL=config-D0zuz3Fx.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"config-aEXDfmSS.mjs","names":["__exportAll","config_exports","BUILTIN_REPO_TYPES","DEPRECATED_REPO_TYPES","PACKAGE_MANAGERS","defineConfig","execBin","hasDependency","hasSwagger","loadKickConfig","resolveModuleConfig","resolvePluralize","resolvePluralizeRoutes","resolveSchemaLib","resolveTokenScope","runScript","testImportSource","validateAssetMap","warnIfDeprecatedRepo","writeAssetConfigSnapshot","pm","script","bin","repo","config","pkgPath","join","cwd","existsSync","pkg","readFileSync","name","scope","sanitised","sanitizeScope","scoped","candidate","raw","flag","mc","CONFIG_FILES","selfAlias","dir","root","dirname","pkgRoot","fileURLToPath","entry","n","findProjectRoot","startDir","filename","filepath","access","content","readFile","jitiModule","err","msg","warnings","warning","mod","pathToFileURL","mkdirSync","snapshot","writeFileSync","resolve","namespace","srcAbs","escapesRoot","rel","relative","path","isAbsolute"],"sources":["../src/config.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { readFile, access } from 'node:fs/promises'\nimport { dirname, isAbsolute, join, relative, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nimport type { KickCliPlugin } from './plugin/types'\n\n/** A custom command that developers can register via kick.config.ts */\nexport interface KickCommandDefinition {\n /** The command name (e.g. 'db:migrate', 'seed', 'proto:gen') */\n name: string\n /** Description shown in --help */\n description: string\n /**\n * Shell command(s) to run. Can be a single string or an array of\n * sequential steps. Use {args} as a placeholder for CLI arguments.\n *\n * @example\n * 'npx drizzle-kit migrate'\n * ['npx drizzle-kit generate', 'npx drizzle-kit migrate']\n */\n steps: string | string[]\n /** Optional aliases (e.g. ['migrate'] for 'db:migrate') */\n aliases?: string[]\n}\n\n/**\n * Project pattern — controls what generators produce and which deps are installed.\n * `kick new` writes the template name here; an `inertia` app generates `rest` modules.\n */\nexport type ProjectPattern = 'rest' | 'minimal' | 'inertia'\n\n/** Package manager used for `kick add` and other dep-installing commands */\nexport type PackageManager = 'pnpm' | 'npm' | 'yarn' | 'bun'\n\nexport const PACKAGE_MANAGERS: readonly PackageManager[] = ['pnpm', 'npm', 'yarn', 'bun']\n\n/**\n * A package.json script as `pm` runs it. npm needs `run` for anything but\n * `test`/`start`, and `bun test` / `bun build` are Bun's own tools, not the script.\n */\nexport function runScript(pm: PackageManager, script: string): string {\n return pm === 'npm' || pm === 'bun' ? `${pm} run ${script}` : `${pm} ${script}`\n}\n\n/** A binary installed in the project, as `pm` runs it — a bare `kick` needs a global install. */\nexport function execBin(pm: PackageManager, bin: string): string {\n return { npm: `npx ${bin}`, pnpm: `pnpm exec ${bin}`, yarn: `yarn ${bin}`, bun: `bunx ${bin}` }[\n pm\n ]\n}\n\n/**\n * Built-in repository type with first-class code generation support.\n *\n * Only `inmemory` is built in — zero-dependency and framework-owned.\n *\n * There are deliberately no ORM presets. A repository shaped to Prisma or\n * Drizzle is that library's interface, not KickJS's, and a generator for it is\n * a promise to track someone else's API across versions. Any other name\n * scaffolds a generic custom-repository stub you own, passed as an object\n * because `RepoTypeConfig` is `BuiltinRepoType | CustomRepoType` and the\n * builtin side is `'inmemory'` alone: `repo: { name: 'postgres' }`.\n */\nexport type BuiltinRepoType = 'inmemory'\n\nexport const BUILTIN_REPO_TYPES: readonly string[] = ['inmemory']\n\n/**\n * Repo names that once had dedicated ORM generators. They still scaffold — as\n * the same generic stub any other custom name produces — and the CLI says so\n * once, because silently changing what a flag generates is worse than a note.\n */\nexport const DEPRECATED_REPO_TYPES: readonly string[] = ['prisma', 'drizzle']\n\n/**\n * Print a note when a generator is asked for a repo that used to be a preset.\n * No-op for every other name. Returns whether a note was printed.\n */\nexport function warnIfDeprecatedRepo(repo: string): boolean {\n if (!DEPRECATED_REPO_TYPES.includes(repo)) return false\n console.warn(\n ` Note: '${repo}' is no longer a built-in repository preset — a repository ` +\n `shaped to it is that library's interface, not KickJS's.\\n` +\n ` Scaffolding a generic custom-repository stub instead; wire it to ${repo} yourself.`,\n )\n return true\n}\n\n/** Custom repository type — generates a stub with TODO markers */\nexport interface CustomRepoType {\n name: string\n}\n\n/** Repository type — built-in string or custom object */\nexport type RepoTypeConfig = BuiltinRepoType | CustomRepoType\n\n/**\n * Supported schema validators for `kick typegen` body/query/params\n * type extraction.\n *\n * - `'zod'` — emits `import('zod').infer<typeof schema>` (default)\n * - `'kickjs-schema'` — emits `InferSchemaOutput<typeof schema>` from\n * `@forinda/kickjs-schema`, works with Zod, Valibot, Yup, and any\n * Standard Schema v1 or KickSchema adapter\n * - `false` — disables schema-driven body typing (fields stay `unknown`)\n */\nexport type SchemaValidator = 'zod' | 'kickjs-schema' | false\n\n/**\n * One entry in the typed `assetMap` config record.\n * Each entry names a source directory whose files become addressable\n * via the `assets.<name>.*` typed accessor at runtime.\n */\nexport interface AssetMapEntry {\n /**\n * Source directory, relative to project root. Required. The directory\n * must exist when `kick build` runs — `loadKickConfig` warns when an\n * entry points at a missing directory but doesn't fail the load\n * (the typegen + build steps surface the error in context instead).\n */\n src: string\n /**\n * Destination directory inside `dist/`. Defaults to `dist/<name>/`\n * where `<name>` is the assetMap key. Override when the consumer of\n * the assets expects a non-standard layout (e.g. an existing\n * downstream tool reads from `dist/templates/...`).\n */\n dest?: string\n /**\n * Glob pattern for which files to include. Defaults to `**\\/*` (all\n * files). Files that don't match are NOT copied — `assetMap` is\n * selective by design (unlike `copyDirs` which copies everything).\n */\n glob?: string\n /**\n * How file extensions feed into manifest keys. Default `'auto'`.\n *\n * - `'strip'` — drop the extension. `pages/index.pug` →\n * `'pages/index'`. Two siblings with the same basename collide;\n * last-walk-order wins, others are silently dropped. Opt-in\n * only — kept for backward compatibility with projects that\n * relied on this contract.\n * - `'with-extension'` — keep every extension on every key. Best\n * when the namespace holds extension siblings (`index.pug` +\n * `index.html` + `index.css` in `src/pages/`); every file\n * reaches the manifest under its full path.\n * - `'auto'` — strip when basenames are unique, keep extensions\n * on collision groups. Singleton files keep their short key;\n * `pages/index.{pug,html,css}` becomes\n * `pages/index.pug` / `pages/index.html` / `pages/index.css`.\n * No data loss; non-colliding namespaces stay on the short\n * keys they had before.\n *\n * @default 'auto'\n */\n keys?: 'auto' | 'strip' | 'with-extension'\n}\n\n/**\n * Database settings consumed by `kick db generate`, `kick db migrate`,\n * and the M4.C composite-type detection gate. Mirrors the runtime\n * `DbConfig` shape from `@forinda/kickjs-db` — duplicated here so\n * adopters who only install `@forinda/kickjs-cli` can set the block\n * without pulling `@forinda/kickjs-db` types into their kick.config.ts\n * resolution. The CLI loads kick.config.ts through `loadKickConfig`,\n * then `resolveDbConfig` re-reads this block from the same module and\n * normalises defaults (`schemaPath` / `migrationsDir` / `dialect`).\n *\n * @example\n * ```ts\n * defineConfig({\n * db: {\n * schemaPath: 'src/db/schema.ts',\n * migrationsDir: 'db/migrations',\n * dialect: 'postgres',\n * connectionString: process.env.DATABASE_URL,\n * },\n * })\n * ```\n */\nexport interface KickDbConfigBlock {\n /**\n * Path to the schema module (`pgEnum` / `table` declarations).\n * Defaults to `'src/db/schema.ts'`.\n */\n schemaPath?: string\n /**\n * Where `kick db generate` writes migration directories. Defaults\n * to `'db/migrations'`.\n */\n migrationsDir?: string\n /** Where `kick db seed` finds seed files. Defaults to `'db/seeds'`. */\n seedsDir?: string\n /** SQL dialect. Defaults to `'postgres'`. */\n dialect?: 'postgres' | 'sqlite' | 'mysql'\n /**\n * Postgres connection string for the built-in pgAdapter path. Read\n * from the `DATABASE_URL` env var when omitted. Used by\n * `kick db migrate*` and the M4.C composite-type gate at\n * `kick db generate`.\n */\n connectionString?: string\n /**\n * Escape hatch: a factory returning a fully-constructed\n * MigrationAdapter (typed loosely here so the CLI types don't pull\n * `@forinda/kickjs-db` into adopter projects that don't import it).\n * Takes precedence over `connectionString` when both are set.\n */\n adapter?: () => unknown | Promise<unknown>\n}\n\n/** Typegen settings — controls .kickjs/types/* generation */\nexport interface TypegenConfig {\n /**\n * Source directory to scan for controllers and decorators.\n * Defaults to `'src'`.\n */\n srcDir?: string\n /**\n * Output directory for generated `.d.ts` files.\n * Defaults to `'.kickjs/types'`.\n */\n outDir?: string\n /**\n * Schema validator used to derive `body` types from route metadata.\n *\n * - `'zod'` — emit `z.infer<typeof <importedSchema>>` for any schema\n * referenced as a named identifier in `@Get/@Post/...({ body, query, params })`.\n * - `false` — disable schema-driven body typing.\n *\n * Future: `'joi' | 'yup' | 'json-schema'` plus a `{ name; module }`\n * escape hatch for custom adapters.\n *\n * @default 'zod'\n */\n schemaValidator?: SchemaValidator\n /**\n * Path to the project's env schema file (relative to project root).\n * Must default-export a `defineEnv(...)` schema for typegen to emit\n * the typed `KickEnv` global registry.\n *\n * Set to `false` to disable env typing entirely.\n *\n * @default 'src/env.ts'\n */\n envFile?: string | false\n /**\n * Built-in or user typegen plugin ids to skip during `kick typegen`,\n * `kick dev`, and `kick typegen --watch`.\n *\n * The plugin still loads and merge-time conflict detection still\n * runs — only the `generate()` invocation is skipped — so adopters\n * who want to hand-write `KickDbRegister` (manual typeof-schema\n * augmentation) can disable `'kick/db'` and keep the rest:\n *\n * @example\n * typegen: {\n * disable: ['kick/db'], // hand-written register.ts owns the type\n * }\n *\n * Unrecognised ids are ignored — the list is treated as a wishlist,\n * not a strict registry.\n */\n disable?: string[]\n\n /**\n * Generate `.kickjs/types/kick__client.d.ts`, the self-contained route map\n * a frontend consumes without compiling the server.\n *\n * Producing it means building a whole TypeScript program over the server —\n * measured at 1.1 GB and ~7.6s on a 1,940-route app, against ~130ms for\n * every other typegen plugin combined. An API with no frontend should not\n * pay that for a file nothing reads, so this is not on for everyone.\n *\n * Off unless set. `kick new --template fullstack` writes `true`, since its\n * web app reads the map; anything else opts in here.\n *\n * A map already on disk does NOT turn this on — that would put the switch\n * somewhere nobody looks. It does get a warning, since a map left to rot is\n * how a frontend ends up type-checking against routes the server no longer\n * serves.\n *\n * Pass an object to tune the expansion. `{ maxDepth }` sets how deeply a\n * response type nests inline before the type at that point is hoisted to a\n * declaration of its own. Hoists spend a per-route budget of 1,000; a route\n * that exhausts it — an endlessly growing generic, or a very large response\n * — is emitted as `unknown` with a warning:\n *\n * ```ts\n * typegen: { client: { maxDepth: 24 } }\n * ```\n */\n client?: boolean | ClientTypegenConfig\n}\n\n/** Tuning for the client route map — see `TypegenConfig.client`. */\nexport interface ClientTypegenConfig {\n /** On, unless explicitly false. Lets `{ maxDepth }` alone mean \"on\". */\n enabled?: boolean\n /**\n * Levels of inline nesting before a type is hoisted to its own declaration\n * (`interface __Tn` / `type __Tn`). Each hoist spends from a per-route budget\n * of 1,000, so a higher value hoists fewer types and lets a larger response\n * through. Default 12.\n */\n maxDepth?: number\n}\n\n/** Module generation settings — controls how `kick g module` produces code */\nexport interface ModuleConfig {\n /** Where modules live (default: 'src/modules') */\n dir?: string\n /**\n * Default repository implementation for generators.\n *\n * @deprecated The name no longer changes the generated code. Only\n * `'inmemory'` produces a distinct file — the one built-in with a real\n * working implementation. Every other value scaffolds the SAME\n * unimplemented stub, named for the module rather than the store, since a\n * `PostgresUserRepository` that is not yet Postgres asserts something the\n * file does not honour.\n *\n * The value survives only as prose: the store appears in the stub's comments\n * and in its \"not implemented\" errors, as a note to the person writing the\n * query. Set it for that hint, or leave it unset and get the in-memory\n * implementation.\n *\n * Kept working, and there is no plan to remove it without a replacement.\n *\n * @example\n * // unset → InMemoryUserRepository, a working store\n * repo: { name: 'postgres' } // → UserRepository, unimplemented, \"write the postgres query\"\n */\n repo?: RepoTypeConfig\n /** Schema output directory (e.g. 'src/db/schema') */\n schemaDir?: string\n /**\n * Whether to pluralize module folder and file names.\n * When false (default), `kick g module user` creates `src/modules/user/` and\n * uses the name as typed throughout. When true, it creates `src/modules/users/`.\n * The route path is controlled separately by {@link ModuleConfig.pluralizeRoutes}.\n */\n pluralize?: boolean\n /**\n * Whether the generated route path is the plural of the name. Default `true`:\n * `kick g module task` mounts at `/tasks`, `user-profile` at `/user-profiles`;\n * uncountables stay (`auth` → `/auth`). Set `false` to mount at the name as\n * typed (`/task`). `--pluralize-routes` / `--no-pluralize-routes` override it.\n */\n pluralizeRoutes?: boolean\n /**\n * Module declaration style emitted by `kick g module` and the\n * project scaffold.\n *\n * - `'define'` (default) — `defineModule({ name, build: () => ({...}) })`\n * factory form. Mirrors `defineAdapter` / `definePlugin` /\n * `defineContextDecorator`.\n * - `'class'` — legacy `class FooModule implements AppModule { ... }`\n * form. Still fully supported by the framework loader; pin to this\n * value for projects that prefer the class shape (existing-codebase\n * consistency, class-decorator setups, etc).\n *\n * The framework runtime accepts both shapes regardless of this\n * setting — the flag controls codegen output only. `kick g module`\n * inserts the matching call form into `src/modules/index.ts`\n * (`Module()` vs `Module`); `kick rm module` matches both.\n *\n * @default 'define'\n */\n style?: 'define' | 'class'\n}\n\n/** Configuration for the kick.config.ts file */\n/**\n * `deploy` block — overrides for the detected serverless layout.\n *\n * @example\n * ```ts\n * defineConfig({\n * // API-only project that should keep its routes under /api\n * deploy: { apiPath: '/api' },\n * })\n * ```\n */\nexport interface DeployConfig {\n /** Serverless entry exporting `handler = createHandler(...)`. Default `src/serverless.ts`. */\n entry?: string\n /** Where the bundle is written, under the project. Default `dist/serverless`. */\n outDir?: string\n /**\n * Built frontend to publish next to the API. Detected as `../web/dist` in a\n * fullstack workspace; `false` forces an API-only deploy.\n */\n staticDir?: string | false\n /**\n * Directory the platform deploys from, relative to the project — where\n * `.netlify/` and `.vercel/` are written. Detected: `..` for a fullstack\n * workspace member, `.` otherwise.\n */\n siteRoot?: string\n /**\n * Empty directory for Netlify's `publish` when there is no frontend; the\n * build creates it. Default `dist/public`.\n */\n publishDir?: string\n /**\n * URL prefix routed to the function. Detected: `/api` alongside a frontend,\n * `''` (every path) for an API-only project.\n */\n apiPath?: string\n /** Optional peers to leave out of the bundle when not installed. Default `['valibot', 'yup']`. */\n external?: string[]\n /** Vercel Node runtime. Default `nodejs22.x`. */\n vercelRuntime?: string\n /**\n * Files and directories the app reads at runtime, relative to the project —\n * packaged with the function at the same paths. Detected for an Inertia\n * app: the client build manifest and the SSR bundle.\n */\n include?: string[]\n}\n\nexport interface KickConfig {\n /**\n * Project pattern — the module shape `kick g module` produces by default.\n * `--pattern` overrides it per call.\n * - 'rest' — flat module: controller, service, DTOs, repository (default)\n * - 'minimal' — controller + module only\n * - 'inertia' — written by `kick new -t inertia`; modules generate as 'rest'\n */\n pattern?: ProjectPattern\n /**\n * Module generation settings — directory, repo type, pluralization, schema dir.\n *\n * @example\n * modules: {\n * dir: 'src/modules',\n * repo: 'postgres',\n * pluralize: false,\n * schemaDir: 'src/db/schema',\n * }\n */\n modules?: ModuleConfig\n /**\n * Package manager used by `kick add` (and any future dep-installing command)\n * to install dependencies. When set, overrides lockfile auto-detection so\n * commands always use the project's intended package manager.\n *\n * Priority (highest first):\n * 1. `--pm` flag on the CLI\n * 2. `packageManager` in kick.config\n * 3. `packageManager` field in package.json (corepack convention)\n * 4. Lockfile detection (pnpm-lock.yaml → pnpm, yarn.lock → yarn)\n * 5. `'npm'`\n *\n * @example\n * packageManager: 'pnpm'\n */\n packageManager?: PackageManager\n\n /**\n * The HTTP runtime the app boots on — the engine passed to\n * `bootstrap({ runtime })`. Written by `kick new --runtime`, and read by\n * dep-aware commands so they install / validate the engine-correct peers:\n * - `kick add upload` picks the multipart driver (express → `multer`,\n * fastify → `@fastify/multipart`, h3 → built-in, no driver)\n * - `kick doctor` checks the engine peers + upload driver are present\n * - the `kick/runtime` typegen emits the `KickRuntimeRegister` augmentation\n * that flips the runtime-typed escape hatches (`AdapterContext.app`,\n * `getRuntimeApp()`) to the engine's native types\n *\n * Defaults to `'express'` when unset (the default engine).\n *\n * @example\n * runtime: 'fastify'\n */\n runtime?: 'express' | 'fastify' | 'h3'\n\n /**\n * DI token scope prefix used by code generators. Every scaffolded\n * `createToken<T>('<scope>/<area>/<key>')` substitutes this string\n * for `<scope>`. Generators emit org-scoped tokens out of the box\n * so adopter projects pass `kick-lint`'s `token-reserved-prefix`\n * rule (which forbids the reserved `kick/` prefix on third-party\n * code) without manual rename.\n *\n * Resolution order (highest first):\n * 1. This field, when set\n * 2. `package.json` `name` field — `@scope/pkg` → `'scope'`,\n * bare `pkg` → `'pkg'`\n * 3. Fallback `'app'`\n *\n * @example\n * tokenScope: 'mycorp'\n * // → createToken<...>('mycorp/users/repository')\n */\n tokenScope?: string\n\n /**\n * Directories to copy to dist/ after build.\n * Useful for EJS templates, email templates, static assets, etc.\n *\n * @example\n * ```ts\n * copyDirs: [\n * 'src/views', // copies to dist/src/views\n * { src: 'src/views', dest: 'dist/views' }, // custom dest\n * 'src/emails',\n * ]\n * ```\n */\n copyDirs?: Array<string | { src: string; dest?: string }>\n /**\n * Build output settings. The asset manager + `kick build`'s copy\n * steps honour these — adopters who use Vite's `build.outDir =\n * 'out'` (or any non-default) should mirror the value here so\n * `assets.x.y()` paths line up with where Vite actually wrote.\n *\n * @example\n * ```ts\n * build: { outDir: 'out' }\n * ```\n */\n build?: {\n /**\n * Output directory, relative to project root. Defaults to\n * `'dist'`. The asset manager emits its manifest + copies\n * assetMap entries into this directory (under a per-namespace\n * subdirectory by default; override per-entry via `dest`).\n */\n outDir?: string\n }\n /**\n * Typed, addressable assets — see https://kickjs.app/guide/asset-manager. Each entry maps\n * a logical namespace name to a source directory. The build pipeline\n * auto-derives the necessary copy step + emits a manifest at\n * `dist/.kickjs-assets.json`; the runtime exposes\n * `import { assets } from '@forinda/kickjs'` so adopters can resolve\n * paths without dev/prod branching.\n *\n * `copyDirs` is unchanged — `assetMap` is a separate, opt-in surface.\n * Adopters who want raw directory copies keep using `copyDirs`; those\n * who want typed addressable assets add `assetMap` entries.\n *\n * @example\n * ```ts\n * assetMap: {\n * mails: { src: 'src/templates/mails' },\n * reports: { src: 'src/templates/reports', glob: '**\\/*.{ejs,html}' },\n * schemas: { src: 'src/schemas', glob: '**\\/*.json' },\n * }\n * ```\n */\n assetMap?: Record<string, AssetMapEntry>\n /**\n * Typegen settings — controls `.kickjs/types/*` generation including\n * the schema validator used for body type extraction.\n *\n * @example\n * ```ts\n * typegen: {\n * schemaValidator: 'zod',\n * }\n * ```\n */\n typegen?: TypegenConfig\n /**\n * Dev-server (`kick dev`) settings.\n */\n dev?: {\n /**\n * Run the project's TypeScript checker (`tsgo --noEmit`, falling\n * back to `tsc --noEmit`) after each debounced change and surface\n * diagnostics in the dev console + a `kickjs:typecheck` HMR event.\n * Equivalent to the `kick dev --typecheck` flag.\n *\n * @default false\n */\n typecheck?: boolean\n }\n /**\n * Database settings — schema path, migrations dir, dialect,\n * connection string, optional adapter factory. Consumed by\n * `kick db generate` / `kick db migrate*`. See {@link KickDbConfigBlock}.\n */\n db?: KickDbConfigBlock\n /** Custom commands that extend the CLI */\n commands?: KickCommandDefinition[]\n /**\n * CLI plugins — bundled commands + typegens contributed by external\n * packages (e.g. `@forinda/kickjs-cli-drizzle`). Plugin commands\n * appear first; adopter `commands` overrides plugin commands of the\n * same name. Duplicate commands or typegen ids across two plugins\n * fail-fast at CLI startup.\n *\n * @example\n * import { drizzlePlugin } from '@forinda/kickjs-cli-drizzle'\n * export default defineConfig({\n * plugins: [drizzlePlugin({ schemaPath: 'src/db/schema' })],\n * })\n */\n plugins?: KickCliPlugin[]\n /**\n * Serverless build targets for `kick build:netlify` / `kick build:vercel`.\n * Every field is optional: the layout is detected from the project (a\n * workspace member with a sibling `web` package is fullstack, anything\n * else is API-only). See {@link DeployConfig}.\n */\n deploy?: DeployConfig\n /** Code style overrides (auto-detected from prettier when possible) */\n style?: {\n semicolons?: boolean\n quotes?: 'single' | 'double'\n trailingComma?: 'all' | 'es5' | 'none'\n indent?: number\n }\n\n /**\n * Extensibility hook for `kick doctor`. Adopters add their own\n * environment / project-shape checks here; each function receives\n * the same context the built-in checks see and returns a result (or\n * `null` to skip).\n *\n * The framework stays ORM- and stack-agnostic — Prisma-specific,\n * Drizzle-specific, deploy-target-specific checks belong in adopter\n * config (or in adapter packages that ship doctor extensions),\n * never in core.\n *\n * @example\n * ```ts\n * import { existsSync } from 'node:fs'\n * import { join } from 'node:path'\n * import { defineConfig } from '@forinda/kickjs-cli'\n *\n * export default defineConfig({\n * doctor: {\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 * : {\n * name: 'Prisma client generated',\n * status: 'fail',\n * fix: 'Run: pnpm exec prisma generate',\n * }\n * },\n * ],\n * },\n * })\n * ```\n */\n doctor?: import('./commands/doctor').DoctorExtension\n}\n\n/** Helper to define a type-safe kick.config.ts */\nexport function defineConfig(config: KickConfig): KickConfig {\n return config\n}\n\n/** Resolve module config from `modules.*` block. */\n/**\n * Resolve the project's DI token scope for code generators.\n * Falls back through kick.config.ts → package.json → `'app'`.\n *\n * @param config Loaded `kick.config.ts` (null when not present)\n * @param cwd Project root — used to read package.json\n */\n/**\n * Whether this project actually depends on `@forinda/kickjs-swagger`.\n *\n * The REST generator decorates controllers with `@ApiTags`, which comes from\n * that package. Emitting it unconditionally put an import of a package the\n * project does not have into every generated controller — five decorators and\n * a broken build, in a file the adopter did not write.\n *\n * Reading package.json rather than resolving the module: a generator should\n * emit what the project DECLARES it uses. A transitively-present copy in\n * node_modules is not a dependency this code may import.\n */\nexport function hasDependency(\n cwd: string,\n name: string,\n /**\n * Where the dependency must be declared. Default `'runtime'`: generated\n * code that *imports a value* — `@ApiTags` is a decorator, evaluated at\n * boot — needs the package present in a production install, and\n * `npm install --omit=dev` drops devDependencies. Pass `'any'` only for\n * something used at build or test time.\n */\n scope: 'runtime' | 'any' = 'runtime',\n): boolean {\n try {\n const pkgPath = join(cwd, 'package.json')\n if (!existsSync(pkgPath)) return false\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as {\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n }\n if (pkg.dependencies?.[name]) return true\n return scope === 'any' ? Boolean(pkg.devDependencies?.[name]) : false\n } catch {\n // An unreadable package.json means we cannot prove the dependency —\n // and emitting an import we cannot justify is the failure being fixed.\n return false\n }\n}\n\n/**\n * Where generated tests import `describe`/`it`/`expect` from. `vp migrate`\n * (Vite+) drops the `vitest` dependency and serves the same API from\n * `vite-plus/test`, so an app on vite-plus without vitest gets that.\n */\nexport function testImportSource(cwd: string): 'vitest' | 'vite-plus/test' {\n return hasDependency(cwd, 'vite-plus', 'any') && !hasDependency(cwd, 'vitest', 'any')\n ? 'vite-plus/test'\n : 'vitest'\n}\n\n/**\n * Whether the project declares the swagger package `@ApiTags` comes from.\n *\n * Runtime scope: the decorator is evaluated when the controller class is\n * defined, so a devDependency-only install would emit controllers that import\n * a package a production install does not have.\n */\nexport function hasSwagger(cwd: string): boolean {\n return hasDependency(cwd, '@forinda/kickjs-swagger', 'runtime')\n}\n\n/** Schema library a generated schema file is written against. */\nexport type SchemaLib = 'zod' | 'valibot' | 'yup'\n\n/**\n * Which validation library the project actually depends on, so generated\n * schema files import something that is installed.\n *\n * `kick new --schema valibot|yup` installs exactly one of these — the chosen\n * library and no other — so an emitted `import { z } from 'zod'` is a file that\n * cannot resolve. Read from package.json rather than `kick.config.ts` because\n * the dependency IS the fact; nothing has to be declared twice, and projects\n * scaffolded before this existed are covered without a config migration.\n *\n * Valibot and Yup are checked first: `kick new` installs one deliberately,\n * whereas `zod` is common enough to appear alongside it for unrelated reasons.\n * A project that genuinely needs to override the pick can gain a config field\n * when someone hits that; until then the dependency answers it.\n *\n * Runtime scope, same reasoning as {@link hasSwagger}: a DTO schema is\n * evaluated when the module loads, so a devDependency-only install would break\n * in production.\n */\nexport function resolveSchemaLib(cwd: string): SchemaLib {\n if (hasDependency(cwd, 'valibot', 'runtime')) return 'valibot'\n if (hasDependency(cwd, 'yup', 'runtime')) return 'yup'\n return 'zod'\n}\n\nexport function resolveTokenScope(config: KickConfig | null, cwd: string): string {\n if (config?.tokenScope && typeof config.tokenScope === 'string' && config.tokenScope.length > 0) {\n const sanitised = sanitizeScope(config.tokenScope)\n // Configured tokenScope can sanitise down to an empty string (e.g.\n // '___' or '!!') — falling through to the package.json chain is\n // safer than emitting an invalid `'/users/repository'` token.\n if (sanitised.length > 0) return sanitised\n }\n\n // Read package.json synchronously — this runs once per generator\n // invocation, so a sync read is cheaper than the async dance + lets\n // the call sites (template builders) stay synchronous.\n try {\n const pkgPath = join(cwd, 'package.json')\n if (existsSync(pkgPath)) {\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as { name?: unknown }\n if (typeof pkg.name === 'string' && pkg.name.length > 0) {\n const scoped = pkg.name.match(/^@([^/]+)\\//)\n const candidate = scoped ? sanitizeScope(scoped[1]) : sanitizeScope(pkg.name)\n if (candidate.length > 0) return candidate\n // Same empty-after-sanitize guard.\n }\n }\n } catch {\n // package.json missing or malformed — fall through to default\n }\n\n return 'app'\n}\n\n/** Lowercase + strip characters that would break a token literal. */\nfunction sanitizeScope(raw: string): string {\n return raw\n .toLowerCase()\n .replace(/[^a-z0-9-]/g, '-') // collapse anything weird to a hyphen\n .replace(/^-+|-+$/g, '') // trim leading/trailing hyphens\n .replace(/-{2,}/g, '-') // collapse runs of hyphens\n}\n\n/** Module names are used as typed unless a project or a flag asks for plurals. */\nexport const PLURALIZE_DEFAULT = false\n\n/** `--pluralize` / `--no-pluralize`, else `modules.pluralize` in kick.config.ts, else `false`. */\nexport function resolvePluralize(flag: boolean | undefined, mc?: ModuleConfig): boolean {\n return flag ?? mc?.pluralize ?? PLURALIZE_DEFAULT\n}\n\n/**\n * `--pluralize-routes` / `--no-pluralize-routes`, else `modules.pluralizeRoutes`,\n * else `true` — routes name a collection (`/tasks`) whatever the folder is called.\n */\nexport function resolvePluralizeRoutes(flag: boolean | undefined, mc?: ModuleConfig): boolean {\n return flag ?? mc?.pluralizeRoutes ?? true\n}\n\nexport function resolveModuleConfig(config: KickConfig | null): ModuleConfig {\n if (!config) return {}\n const mc: ModuleConfig = {\n dir: config.modules?.dir,\n repo: config.modules?.repo,\n schemaDir: config.modules?.schemaDir,\n pluralize: config.modules?.pluralize,\n pluralizeRoutes: config.modules?.pluralizeRoutes,\n style: config.modules?.style,\n }\n // Validate `style` — silently coerce unknown values to the default.\n // The CLI surface only documents 'define' and 'class'; anything else\n // is a typo (e.g. 'defineModule') we'd rather see than silently\n // emit class form.\n if (mc.style !== undefined && mc.style !== 'define' && mc.style !== 'class') {\n console.warn(\n ` Warning: modules.style '${mc.style as string}' is not a valid value ` +\n `(expected 'define' or 'class'). Falling back to 'define'.`,\n )\n mc.style = 'define'\n }\n\n // Warn if a string repo value isn't a known built-in. Deprecated ORM\n // presets get their own note; any other bare string suggests the\n // `{ name }` form to silence the stub-repo warning.\n if (mc.repo && typeof mc.repo === 'string' && !BUILTIN_REPO_TYPES.includes(mc.repo)) {\n if (!warnIfDeprecatedRepo(mc.repo)) {\n console.warn(\n ` Warning: modules.repo '${mc.repo}' is not a built-in type (${BUILTIN_REPO_TYPES.join(', ')}).` +\n ` It will generate a stub repository. Use { name: '${mc.repo}' } to silence this warning.`,\n )\n }\n }\n\n return mc\n}\n\nconst CONFIG_FILES = ['kick.config.ts', 'kick.config.js', 'kick.config.mjs', 'kick.config.json']\n\n/**\n * Load `kick.config.*` starting from `startDir` and walking up toward\n * the filesystem root until a config file is found. Returns `null`\n * when no config exists anywhere on the way up.\n *\n * Walking up means adopters can run `kick <cmd>` from any subdirectory\n * (e.g. `src/modules/users/`) and still pick up the project's config —\n * before this change a nested-cwd invocation silently saw `null` and\n * fell back to framework defaults.\n *\n * TypeScript configs (`.ts`) are loaded via `jiti` when available;\n * `.js` / `.mjs` use native `import()`; `.json` uses `JSON.parse`. The\n * jiti import is dynamic + best-effort: if the dep is missing we\n * surface a warning telling the adopter how to install it, instead of\n * silently dropping the config (which is what the previous bare-catch\n * did).\n */\n/**\n * When the project can't resolve `@forinda/kickjs-cli` itself — a fresh\n * `kick new` before install, or a global `kick` in a project whose deps\n * aren't installed — map it to this CLI, so `import { defineConfig } from\n * '@forinda/kickjs-cli'` in kick.config.ts still loads. The config is read by\n * this CLI either way; an installed copy is left to normal resolution.\n */\nfunction selfAlias(root: string): Record<string, string> {\n for (let dir = root; ; dir = dirname(dir)) {\n if (existsSync(join(dir, 'node_modules', '@forinda', 'kickjs-cli', 'package.json'))) return {}\n if (dirname(dir) === dir) break\n }\n // One level below the package root in source (src/) and in the bundle (dist/).\n const pkgRoot = join(dirname(fileURLToPath(import.meta.url)), '..')\n const entry = [join(pkgRoot, 'dist', 'index.mjs'), join(pkgRoot, 'src', 'index.ts')].find(\n existsSync,\n )\n return entry ? { '@forinda/kickjs-cli': entry } : {}\n}\n\nexport async function loadKickConfig(startDir: string): Promise<KickConfig | null> {\n const { findProjectRoot } = await import('./utils/project-root')\n const root = findProjectRoot(startDir)\n\n for (const filename of CONFIG_FILES) {\n const filepath = join(root, filename)\n try {\n await access(filepath)\n } catch {\n continue\n }\n\n if (filename.endsWith('.json')) {\n const content = await readFile(filepath, 'utf-8')\n return JSON.parse(content)\n }\n\n const isTs = filename.endsWith('.ts')\n\n if (isTs) {\n // Split the import-jiti step from the load-user-config step so the\n // diagnostic blames the right thing. Both surface as Node module-\n // resolution errors with similar shapes (`Cannot find package …`,\n // `ERR_MODULE_NOT_FOUND`), but the remedies differ:\n //\n // - Missing jiti → adopter needs to install jiti.\n // - Missing dep referenced from kick.config.ts → adopter needs to\n // install THAT package; telling them to install jiti would send\n // them to the wrong fix and bury the real missing module.\n let jitiModule: typeof import('jiti')\n try {\n jitiModule = await import('jiti')\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n if (msg.includes(\"Cannot find package 'jiti'\") || msg.includes('ERR_MODULE_NOT_FOUND')) {\n console.warn(\n `Warning: Failed to load ${filename} — 'jiti' is required for TypeScript configs. ` +\n 'Install jiti as a dev dependency, or rename the file ' +\n 'to kick.config.js / kick.config.mjs / kick.config.json.',\n )\n } else {\n console.warn(`Warning: Failed to initialize jiti for ${filename}: ${msg}`)\n }\n continue\n }\n\n try {\n const jiti = jitiModule.createJiti(root, {\n interopDefault: true,\n fsCache: false,\n alias: selfAlias(root),\n // The project's tsconfig `paths` (`@/*` → `src/*`), for what the\n // config imports — eagerly or lazily, like a `db.adapter()` that\n // loads `src/db/client.ts`, which imports `@/config`. Outside Vite\n // nothing else resolves the alias.\n tsconfigPaths: true,\n })\n const config = (await jiti.import(filepath, { default: true })) as KickConfig\n const warnings = validateAssetMap(config, root)\n for (const warning of warnings) console.warn(` Warning: ${warning}`)\n writeAssetConfigSnapshot(root, config)\n return config\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n console.warn(`Warning: Failed to load ${filename}: ${msg}`)\n continue\n }\n }\n\n try {\n const { pathToFileURL } = await import('node:url')\n const mod = await import(pathToFileURL(filepath).href)\n const config = (mod.default ?? mod) as KickConfig\n const warnings = validateAssetMap(config, root)\n for (const warning of warnings) console.warn(` Warning: ${warning}`)\n writeAssetConfigSnapshot(root, config)\n return config\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n console.warn(`Warning: Failed to load ${filename}: ${msg}`)\n continue\n }\n }\n return null\n}\n\n/**\n * Mirror the JSON-serialisable slice of a TS/JS `kick.config` that the\n * runtime asset resolver needs (`assetMap` + `build.outDir`) into\n * `.kickjs/kick.config.json`.\n *\n * Why: the runtime resolver in `@forinda/kickjs` reads its config\n * *synchronously* (it sits on the hot path of `assets.x.y()` /\n * `resolveAsset()`), so it can only parse `kick.config.{json,js,cjs}` —\n * it deliberately can't transpile `kick.config.ts`. For a `.ts`-config\n * project in dev (no built `dist/.kickjs-assets.json` yet), that left\n * the resolver with no config to synthesise a manifest from, so\n * `assets.x.y()` threw `UnknownAssetError` until the first build.\n *\n * The CLI already transpiles the `.ts` config here, so it drops this\n * tiny snapshot the runtime can read with a plain `JSON.parse`. Best\n * effort: any failure (read-only fs, etc.) is swallowed — a missing\n * snapshot just falls back to the previous behaviour.\n */\nexport function writeAssetConfigSnapshot(root: string, config: KickConfig | null): void {\n // Only meaningful for asset-manager users — don't litter `.kickjs/`\n // for projects that never declare an assetMap.\n if (!config?.assetMap || Object.keys(config.assetMap).length === 0) return\n try {\n const dir = join(root, '.kickjs')\n mkdirSync(dir, { recursive: true })\n const snapshot = {\n // Marker so a future shape change can be detected/migrated.\n version: 1 as const,\n assetMap: config.assetMap,\n ...(config.build?.outDir ? { build: { outDir: config.build.outDir } } : {}),\n }\n writeFileSync(join(dir, 'kick.config.json'), JSON.stringify(snapshot, null, 2) + '\\n', 'utf-8')\n } catch {\n // Best effort — snapshot is an optimisation, not a correctness\n // requirement. Production reads the real dist manifest.\n }\n}\n\n/**\n * Validate `assetMap` entries on a loaded config. Returns a list of\n * human-readable warnings; the caller decides how to surface them\n * (typically `console.warn`). Never throws — `kick g` and other\n * unrelated commands should keep working even when the assetMap is\n * misconfigured.\n *\n * Checks:\n *\n * - Each entry's `src` is a non-empty string.\n * - The `src` directory exists on disk (otherwise the typegen + build\n * steps will fail later with cryptic errors).\n * - `dest` doesn't escape the project root (defensive — a `dest:\n * '../../etc'` typo could write files outside the workspace).\n * - The namespace key is a non-empty string and doesn't include a\n * `/` (would conflict with the `<namespace>/<key>` manifest format).\n */\nexport function validateAssetMap(config: KickConfig | null, cwd: string): string[] {\n const warnings: string[] = []\n if (!config?.assetMap) return warnings\n\n const root = resolve(cwd)\n for (const [namespace, entry] of Object.entries(config.assetMap)) {\n if (!namespace || namespace.includes('/')) {\n warnings.push(\n `assetMap key '${namespace}' is invalid — must be a non-empty string without '/'`,\n )\n continue\n }\n if (typeof entry?.src !== 'string' || entry.src.length === 0) {\n warnings.push(`assetMap.${namespace} is missing a non-empty 'src' field`)\n continue\n }\n const srcAbs = resolve(cwd, entry.src)\n if (!existsSync(srcAbs)) {\n warnings.push(\n `assetMap.${namespace}.src ('${entry.src}') does not exist — typegen + build will fail`,\n )\n }\n if (entry.dest) {\n const destAbs = resolve(cwd, entry.dest)\n // path.relative is the right primitive for \"is X inside Y?\" —\n // a raw startsWith() prefix match has two failure modes the\n // earlier version hit: (a) `/app` is a prefix of `/app2/...`\n // even though they're different directories, and (b) it's\n // case-sensitive on filesystems that aren't (macOS default,\n // Windows). path.relative handles both correctly + accounts\n // for `..` traversal in the destination.\n if (escapesRoot(destAbs, root)) {\n warnings.push(\n `assetMap.${namespace}.dest ('${entry.dest}') resolves outside the project root — refusing to copy`,\n )\n }\n }\n }\n return warnings\n}\n\n/**\n * Returns true when `path` (absolute) resolves outside of `root`\n * (also absolute). Uses `path.relative` for accuracy:\n *\n * - The result is empty when paths are identical (inside).\n * - It starts with `..` when the path traverses outside the root.\n * - It's absolute (Windows: cross-drive) when there's no relative\n * path between them.\n *\n * Avoids the prefix-match pitfalls of `startsWith` (e.g. `/app`\n * matching `/app2/...`, or case-mismatches on macOS / Windows).\n */\nfunction escapesRoot(path: string, root: string): boolean {\n const rel = relative(root, path)\n return rel === '' ? false : rel.startsWith('..') || isAbsolute(rel)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"config-D0zuz3Fx.mjs","names":["__exportAll","config_exports","BUILTIN_REPO_TYPES","DEPRECATED_REPO_TYPES","PACKAGE_MANAGERS","defineConfig","execBin","hasDependency","hasSwagger","loadKickConfig","resolveModuleConfig","resolvePluralize","resolvePluralizeRoutes","resolveSchemaLib","resolveTokenScope","runScript","testImportSource","validateAssetMap","warnIfDeprecatedRepo","writeAssetConfigSnapshot","pm","script","bin","repo","config","pkgPath","join","cwd","existsSync","pkg","readFileSync","name","scope","sanitised","sanitizeScope","scoped","candidate","raw","flag","mc","CONFIG_FILES","selfAlias","dir","root","dirname","pkgRoot","fileURLToPath","entry","n","findProjectRoot","startDir","filename","filepath","access","content","readFile","jitiModule","err","msg","warnings","warning","mod","pathToFileURL","mkdirSync","snapshot","writeFileSync","resolve","namespace","srcAbs","escapesRoot","rel","relative","path","isAbsolute"],"sources":["../src/config.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { readFile, access } from 'node:fs/promises'\nimport { dirname, isAbsolute, join, relative, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nimport type { KickCliPlugin } from './plugin/types'\n\n/** A custom command that developers can register via kick.config.ts */\nexport interface KickCommandDefinition {\n /** The command name (e.g. 'db:migrate', 'seed', 'proto:gen') */\n name: string\n /** Description shown in --help */\n description: string\n /**\n * Shell command(s) to run. Can be a single string or an array of\n * sequential steps. Use {args} as a placeholder for CLI arguments.\n *\n * @example\n * 'npx drizzle-kit migrate'\n * ['npx drizzle-kit generate', 'npx drizzle-kit migrate']\n */\n steps: string | string[]\n /** Optional aliases (e.g. ['migrate'] for 'db:migrate') */\n aliases?: string[]\n}\n\n/**\n * Project pattern — controls what generators produce and which deps are installed.\n * `kick new` writes the template name here; an `inertia` app generates `rest` modules.\n */\nexport type ProjectPattern = 'rest' | 'minimal' | 'inertia'\n\n/** Package manager used for `kick add` and other dep-installing commands */\nexport type PackageManager = 'pnpm' | 'npm' | 'yarn' | 'bun'\n\nexport const PACKAGE_MANAGERS: readonly PackageManager[] = ['pnpm', 'npm', 'yarn', 'bun']\n\n/**\n * A package.json script as `pm` runs it. npm needs `run` for anything but\n * `test`/`start`, and `bun test` / `bun build` are Bun's own tools, not the script.\n */\nexport function runScript(pm: PackageManager, script: string): string {\n return pm === 'npm' || pm === 'bun' ? `${pm} run ${script}` : `${pm} ${script}`\n}\n\n/** A binary installed in the project, as `pm` runs it — a bare `kick` needs a global install. */\nexport function execBin(pm: PackageManager, bin: string): string {\n return { npm: `npx ${bin}`, pnpm: `pnpm exec ${bin}`, yarn: `yarn ${bin}`, bun: `bunx ${bin}` }[\n pm\n ]\n}\n\n/**\n * Built-in repository type with first-class code generation support.\n *\n * Only `inmemory` is built in — zero-dependency and framework-owned.\n *\n * There are deliberately no ORM presets. A repository shaped to Prisma or\n * Drizzle is that library's interface, not KickJS's, and a generator for it is\n * a promise to track someone else's API across versions. Any other name\n * scaffolds a generic custom-repository stub you own, passed as an object\n * because `RepoTypeConfig` is `BuiltinRepoType | CustomRepoType` and the\n * builtin side is `'inmemory'` alone: `repo: { name: 'postgres' }`.\n */\nexport type BuiltinRepoType = 'inmemory'\n\nexport const BUILTIN_REPO_TYPES: readonly string[] = ['inmemory']\n\n/**\n * Repo names that once had dedicated ORM generators. They still scaffold — as\n * the same generic stub any other custom name produces — and the CLI says so\n * once, because silently changing what a flag generates is worse than a note.\n */\nexport const DEPRECATED_REPO_TYPES: readonly string[] = ['prisma', 'drizzle']\n\n/**\n * Print a note when a generator is asked for a repo that used to be a preset.\n * No-op for every other name. Returns whether a note was printed.\n */\nexport function warnIfDeprecatedRepo(repo: string): boolean {\n if (!DEPRECATED_REPO_TYPES.includes(repo)) return false\n console.warn(\n ` Note: '${repo}' is no longer a built-in repository preset — a repository ` +\n `shaped to it is that library's interface, not KickJS's.\\n` +\n ` Scaffolding a generic custom-repository stub instead; wire it to ${repo} yourself.`,\n )\n return true\n}\n\n/** Custom repository type — generates a stub with TODO markers */\nexport interface CustomRepoType {\n name: string\n}\n\n/** Repository type — built-in string or custom object */\nexport type RepoTypeConfig = BuiltinRepoType | CustomRepoType\n\n/**\n * Supported schema validators for `kick typegen` body/query/params\n * type extraction.\n *\n * - `'zod'` — emits `import('zod').infer<typeof schema>` (default)\n * - `'kickjs-schema'` — emits `InferSchemaOutput<typeof schema>` from\n * `@forinda/kickjs-schema`, works with Zod, Valibot, Yup, and any\n * Standard Schema v1 or KickSchema adapter\n * - `false` — disables schema-driven body typing (fields stay `unknown`)\n */\nexport type SchemaValidator = 'zod' | 'kickjs-schema' | false\n\n/**\n * One entry in the typed `assetMap` config record.\n * Each entry names a source directory whose files become addressable\n * via the `assets.<name>.*` typed accessor at runtime.\n */\nexport interface AssetMapEntry {\n /**\n * Source directory, relative to project root. Required. The directory\n * must exist when `kick build` runs — `loadKickConfig` warns when an\n * entry points at a missing directory but doesn't fail the load\n * (the typegen + build steps surface the error in context instead).\n */\n src: string\n /**\n * Destination directory inside `dist/`. Defaults to `dist/<name>/`\n * where `<name>` is the assetMap key. Override when the consumer of\n * the assets expects a non-standard layout (e.g. an existing\n * downstream tool reads from `dist/templates/...`).\n */\n dest?: string\n /**\n * Glob pattern for which files to include. Defaults to `**\\/*` (all\n * files). Files that don't match are NOT copied — `assetMap` is\n * selective by design (unlike `copyDirs` which copies everything).\n */\n glob?: string\n /**\n * How file extensions feed into manifest keys. Default `'auto'`.\n *\n * - `'strip'` — drop the extension. `pages/index.pug` →\n * `'pages/index'`. Two siblings with the same basename collide;\n * last-walk-order wins, others are silently dropped. Opt-in\n * only — kept for backward compatibility with projects that\n * relied on this contract.\n * - `'with-extension'` — keep every extension on every key. Best\n * when the namespace holds extension siblings (`index.pug` +\n * `index.html` + `index.css` in `src/pages/`); every file\n * reaches the manifest under its full path.\n * - `'auto'` — strip when basenames are unique, keep extensions\n * on collision groups. Singleton files keep their short key;\n * `pages/index.{pug,html,css}` becomes\n * `pages/index.pug` / `pages/index.html` / `pages/index.css`.\n * No data loss; non-colliding namespaces stay on the short\n * keys they had before.\n *\n * @default 'auto'\n */\n keys?: 'auto' | 'strip' | 'with-extension'\n}\n\n/**\n * Database settings consumed by `kick db generate`, `kick db migrate`,\n * and the M4.C composite-type detection gate. Mirrors the runtime\n * `DbConfig` shape from `@forinda/kickjs-db` — duplicated here so\n * adopters who only install `@forinda/kickjs-cli` can set the block\n * without pulling `@forinda/kickjs-db` types into their kick.config.ts\n * resolution. The CLI loads kick.config.ts through `loadKickConfig`,\n * then `resolveDbConfig` re-reads this block from the same module and\n * normalises defaults (`schemaPath` / `migrationsDir` / `dialect`).\n *\n * @example\n * ```ts\n * defineConfig({\n * db: {\n * schemaPath: 'src/db/schema.ts',\n * migrationsDir: 'db/migrations',\n * dialect: 'postgres',\n * connectionString: process.env.DATABASE_URL,\n * },\n * })\n * ```\n */\nexport interface KickDbConfigBlock {\n /**\n * Path to the schema module (`pgEnum` / `table` declarations).\n * Defaults to `'src/db/schema.ts'`.\n */\n schemaPath?: string\n /**\n * Where `kick db generate` writes migration directories. Defaults\n * to `'db/migrations'`.\n */\n migrationsDir?: string\n /** Where `kick db seed` finds seed files. Defaults to `'db/seeds'`. */\n seedsDir?: string\n /** SQL dialect. Defaults to `'postgres'`. */\n dialect?: 'postgres' | 'sqlite' | 'mysql'\n /**\n * Postgres connection string for the built-in pgAdapter path. Read\n * from the `DATABASE_URL` env var when omitted. Used by\n * `kick db migrate*` and the M4.C composite-type gate at\n * `kick db generate`.\n */\n connectionString?: string\n /**\n * Escape hatch: a factory returning a fully-constructed\n * MigrationAdapter (typed loosely here so the CLI types don't pull\n * `@forinda/kickjs-db` into adopter projects that don't import it).\n * Takes precedence over `connectionString` when both are set.\n */\n adapter?: () => unknown | Promise<unknown>\n}\n\n/** Typegen settings — controls .kickjs/types/* generation */\nexport interface TypegenConfig {\n /**\n * Source directory to scan for controllers and decorators.\n * Defaults to `'src'`.\n */\n srcDir?: string\n /**\n * Output directory for generated `.d.ts` files.\n * Defaults to `'.kickjs/types'`.\n */\n outDir?: string\n /**\n * Schema validator used to derive `body` types from route metadata.\n *\n * - `'zod'` — emit `z.infer<typeof <importedSchema>>` for any schema\n * referenced as a named identifier in `@Get/@Post/...({ body, query, params })`.\n * - `false` — disable schema-driven body typing.\n *\n * Future: `'joi' | 'yup' | 'json-schema'` plus a `{ name; module }`\n * escape hatch for custom adapters.\n *\n * @default 'zod'\n */\n schemaValidator?: SchemaValidator\n /**\n * Path to the project's env schema file (relative to project root).\n * Must default-export a `defineEnv(...)` schema for typegen to emit\n * the typed `KickEnv` global registry.\n *\n * Set to `false` to disable env typing entirely.\n *\n * @default 'src/env.ts'\n */\n envFile?: string | false\n /**\n * Built-in or user typegen plugin ids to skip during `kick typegen`,\n * `kick dev`, and `kick typegen --watch`.\n *\n * The plugin still loads and merge-time conflict detection still\n * runs — only the `generate()` invocation is skipped — so adopters\n * who want to hand-write `KickDbRegister` (manual typeof-schema\n * augmentation) can disable `'kick/db'` and keep the rest:\n *\n * @example\n * typegen: {\n * disable: ['kick/db'], // hand-written register.ts owns the type\n * }\n *\n * Unrecognised ids are ignored — the list is treated as a wishlist,\n * not a strict registry.\n */\n disable?: string[]\n\n /**\n * Generate `.kickjs/types/kick__client.d.ts`, the self-contained route map\n * a frontend consumes without compiling the server.\n *\n * Producing it means building a whole TypeScript program over the server —\n * measured at 1.1 GB and ~7.6s on a 1,940-route app, against ~130ms for\n * every other typegen plugin combined. An API with no frontend should not\n * pay that for a file nothing reads, so this is not on for everyone.\n *\n * Off unless set. `kick new --template fullstack` writes `true`, since its\n * web app reads the map; anything else opts in here.\n *\n * A map already on disk does NOT turn this on — that would put the switch\n * somewhere nobody looks. It does get a warning, since a map left to rot is\n * how a frontend ends up type-checking against routes the server no longer\n * serves.\n *\n * Pass an object to tune the expansion. `{ maxDepth }` sets how deeply a\n * response type nests inline before the type at that point is hoisted to a\n * declaration of its own. Hoists spend a per-route budget of 1,000; a route\n * that exhausts it — an endlessly growing generic, or a very large response\n * — is emitted as `unknown` with a warning:\n *\n * ```ts\n * typegen: { client: { maxDepth: 24 } }\n * ```\n */\n client?: boolean | ClientTypegenConfig\n}\n\n/** Tuning for the client route map — see `TypegenConfig.client`. */\nexport interface ClientTypegenConfig {\n /** On, unless explicitly false. Lets `{ maxDepth }` alone mean \"on\". */\n enabled?: boolean\n /**\n * Levels of inline nesting before a type is hoisted to its own declaration\n * (`interface __Tn` / `type __Tn`). Each hoist spends from a per-route budget\n * of 1,000, so a higher value hoists fewer types and lets a larger response\n * through. Default 12.\n */\n maxDepth?: number\n}\n\n/** Module generation settings — controls how `kick g module` produces code */\nexport interface ModuleConfig {\n /** Where modules live (default: 'src/modules') */\n dir?: string\n /**\n * Default repository implementation for generators.\n *\n * @deprecated The name no longer changes the generated code. Only\n * `'inmemory'` produces a distinct file — the one built-in with a real\n * working implementation. Every other value scaffolds the SAME\n * unimplemented stub, named for the module rather than the store, since a\n * `PostgresUserRepository` that is not yet Postgres asserts something the\n * file does not honour.\n *\n * The value survives only as prose: the store appears in the stub's comments\n * and in its \"not implemented\" errors, as a note to the person writing the\n * query. Set it for that hint, or leave it unset and get the in-memory\n * implementation.\n *\n * Kept working, and there is no plan to remove it without a replacement.\n *\n * @example\n * // unset → InMemoryUserRepository, a working store\n * repo: { name: 'postgres' } // → UserRepository, unimplemented, \"write the postgres query\"\n */\n repo?: RepoTypeConfig\n /** Schema output directory (e.g. 'src/db/schema') */\n schemaDir?: string\n /**\n * Whether to pluralize module folder and file names.\n * When false (default), `kick g module user` creates `src/modules/user/` and\n * uses the name as typed throughout. When true, it creates `src/modules/users/`.\n * The route path is controlled separately by {@link ModuleConfig.pluralizeRoutes}.\n */\n pluralize?: boolean\n /**\n * Whether the generated route path is the plural of the name. Default `true`:\n * `kick g module task` mounts at `/tasks`, `user-profile` at `/user-profiles`;\n * uncountables stay (`auth` → `/auth`). Set `false` to mount at the name as\n * typed (`/task`). `--pluralize-routes` / `--no-pluralize-routes` override it.\n */\n pluralizeRoutes?: boolean\n /**\n * Module declaration style emitted by `kick g module` and the\n * project scaffold.\n *\n * - `'define'` (default) — `defineModule({ name, build: () => ({...}) })`\n * factory form. Mirrors `defineAdapter` / `definePlugin` /\n * `defineContextDecorator`.\n * - `'class'` — legacy `class FooModule implements AppModule { ... }`\n * form. Still fully supported by the framework loader; pin to this\n * value for projects that prefer the class shape (existing-codebase\n * consistency, class-decorator setups, etc).\n *\n * The framework runtime accepts both shapes regardless of this\n * setting — the flag controls codegen output only. `kick g module`\n * inserts the matching call form into `src/modules/index.ts`\n * (`Module()` vs `Module`); `kick rm module` matches both.\n *\n * @default 'define'\n */\n style?: 'define' | 'class'\n}\n\n/** Configuration for the kick.config.ts file */\n/**\n * `deploy` block — overrides for the detected serverless layout.\n *\n * @example\n * ```ts\n * defineConfig({\n * // API-only project that should keep its routes under /api\n * deploy: { apiPath: '/api' },\n * })\n * ```\n */\nexport interface DeployConfig {\n /** Serverless entry exporting `handler = createHandler(...)`. Default `src/serverless.ts`. */\n entry?: string\n /** Where the bundle is written, under the project. Default `dist/serverless`. */\n outDir?: string\n /**\n * Built frontend to publish next to the API. Detected as `../web/dist` in a\n * fullstack workspace; `false` forces an API-only deploy.\n */\n staticDir?: string | false\n /**\n * Directory the platform deploys from, relative to the project — where\n * `.netlify/` and `.vercel/` are written. Detected: `..` for a fullstack\n * workspace member, `.` otherwise.\n */\n siteRoot?: string\n /**\n * Empty directory for Netlify's `publish` when there is no frontend; the\n * build creates it. Default `dist/public`.\n */\n publishDir?: string\n /**\n * URL prefix routed to the function. Detected: `/api` alongside a frontend,\n * `''` (every path) for an API-only project.\n */\n apiPath?: string\n /** Optional peers to leave out of the bundle when not installed. Default `['valibot', 'yup']`. */\n external?: string[]\n /** Vercel Node runtime. Default `nodejs22.x`. */\n vercelRuntime?: string\n /**\n * Files and directories the app reads at runtime, relative to the project —\n * packaged with the function at the same paths. Detected for an Inertia\n * app: the client build manifest and the SSR bundle.\n */\n include?: string[]\n}\n\nexport interface KickConfig {\n /**\n * Project pattern — the module shape `kick g module` produces by default.\n * `--pattern` overrides it per call.\n * - 'rest' — flat module: controller, service, DTOs, repository (default)\n * - 'minimal' — controller + module only\n * - 'inertia' — written by `kick new -t inertia`; modules generate as 'rest'\n */\n pattern?: ProjectPattern\n /**\n * Module generation settings — directory, repo type, pluralization, schema dir.\n *\n * @example\n * modules: {\n * dir: 'src/modules',\n * repo: 'postgres',\n * pluralize: false,\n * schemaDir: 'src/db/schema',\n * }\n */\n modules?: ModuleConfig\n /**\n * Package manager used by `kick add` (and any future dep-installing command)\n * to install dependencies. When set, overrides lockfile auto-detection so\n * commands always use the project's intended package manager.\n *\n * Priority (highest first):\n * 1. `--pm` flag on the CLI\n * 2. `packageManager` in kick.config\n * 3. `packageManager` field in package.json (corepack convention)\n * 4. Lockfile detection (pnpm-lock.yaml → pnpm, yarn.lock → yarn)\n * 5. `'npm'`\n *\n * @example\n * packageManager: 'pnpm'\n */\n packageManager?: PackageManager\n\n /**\n * The HTTP runtime the app boots on — the engine passed to\n * `bootstrap({ runtime })`. Written by `kick new --runtime`, and read by\n * dep-aware commands so they install / validate the engine-correct peers:\n * - `kick add upload` picks the multipart driver (express → `multer`,\n * fastify → `@fastify/multipart`, h3 → built-in, no driver)\n * - `kick doctor` checks the engine peers + upload driver are present\n * - the `kick/runtime` typegen emits the `KickRuntimeRegister` augmentation\n * that flips the runtime-typed escape hatches (`AdapterContext.app`,\n * `getRuntimeApp()`) to the engine's native types\n *\n * Defaults to `'express'` when unset (the default engine).\n *\n * @example\n * runtime: 'fastify'\n */\n runtime?: 'express' | 'fastify' | 'h3'\n\n /**\n * DI token scope prefix used by code generators. Every scaffolded\n * `createToken<T>('<scope>/<area>/<key>')` substitutes this string\n * for `<scope>`. Generators emit org-scoped tokens out of the box\n * so adopter projects pass `kick-lint`'s `token-reserved-prefix`\n * rule (which forbids the reserved `kick/` prefix on third-party\n * code) without manual rename.\n *\n * Resolution order (highest first):\n * 1. This field, when set\n * 2. `package.json` `name` field — `@scope/pkg` → `'scope'`,\n * bare `pkg` → `'pkg'`\n * 3. Fallback `'app'`\n *\n * @example\n * tokenScope: 'mycorp'\n * // → createToken<...>('mycorp/users/repository')\n */\n tokenScope?: string\n\n /**\n * Directories to copy to dist/ after build.\n * Useful for EJS templates, email templates, static assets, etc.\n *\n * @example\n * ```ts\n * copyDirs: [\n * 'src/views', // copies to dist/src/views\n * { src: 'src/views', dest: 'dist/views' }, // custom dest\n * 'src/emails',\n * ]\n * ```\n */\n copyDirs?: Array<string | { src: string; dest?: string }>\n /**\n * Build output settings. The asset manager + `kick build`'s copy\n * steps honour these — adopters who use Vite's `build.outDir =\n * 'out'` (or any non-default) should mirror the value here so\n * `assets.x.y()` paths line up with where Vite actually wrote.\n *\n * @example\n * ```ts\n * build: { outDir: 'out' }\n * ```\n */\n build?: {\n /**\n * Output directory, relative to project root. Defaults to\n * `'dist'`. The asset manager emits its manifest + copies\n * assetMap entries into this directory (under a per-namespace\n * subdirectory by default; override per-entry via `dest`).\n */\n outDir?: string\n }\n /**\n * Typed, addressable assets — see https://kickjs.app/guide/asset-manager. Each entry maps\n * a logical namespace name to a source directory. The build pipeline\n * auto-derives the necessary copy step + emits a manifest at\n * `dist/.kickjs-assets.json`; the runtime exposes\n * `import { assets } from '@forinda/kickjs'` so adopters can resolve\n * paths without dev/prod branching.\n *\n * `copyDirs` is unchanged — `assetMap` is a separate, opt-in surface.\n * Adopters who want raw directory copies keep using `copyDirs`; those\n * who want typed addressable assets add `assetMap` entries.\n *\n * @example\n * ```ts\n * assetMap: {\n * mails: { src: 'src/templates/mails' },\n * reports: { src: 'src/templates/reports', glob: '**\\/*.{ejs,html}' },\n * schemas: { src: 'src/schemas', glob: '**\\/*.json' },\n * }\n * ```\n */\n assetMap?: Record<string, AssetMapEntry>\n /**\n * Typegen settings — controls `.kickjs/types/*` generation including\n * the schema validator used for body type extraction.\n *\n * @example\n * ```ts\n * typegen: {\n * schemaValidator: 'zod',\n * }\n * ```\n */\n typegen?: TypegenConfig\n /**\n * Dev-server (`kick dev`) settings.\n */\n dev?: {\n /**\n * Run the project's TypeScript checker (`tsgo --noEmit`, falling\n * back to `tsc --noEmit`) after each debounced change and surface\n * diagnostics in the dev console + a `kickjs:typecheck` HMR event.\n * Equivalent to the `kick dev --typecheck` flag.\n *\n * @default false\n */\n typecheck?: boolean\n }\n /**\n * Database settings — schema path, migrations dir, dialect,\n * connection string, optional adapter factory. Consumed by\n * `kick db generate` / `kick db migrate*`. See {@link KickDbConfigBlock}.\n */\n db?: KickDbConfigBlock\n /** Custom commands that extend the CLI */\n commands?: KickCommandDefinition[]\n /**\n * CLI plugins — bundled commands + typegens contributed by external\n * packages (e.g. `@forinda/kickjs-cli-drizzle`). Plugin commands\n * appear first; adopter `commands` overrides plugin commands of the\n * same name. Duplicate commands or typegen ids across two plugins\n * fail-fast at CLI startup.\n *\n * @example\n * import { drizzlePlugin } from '@forinda/kickjs-cli-drizzle'\n * export default defineConfig({\n * plugins: [drizzlePlugin({ schemaPath: 'src/db/schema' })],\n * })\n */\n plugins?: KickCliPlugin[]\n /**\n * Serverless build targets for `kick build:netlify` / `kick build:vercel`.\n * Every field is optional: the layout is detected from the project (a\n * workspace member with a sibling `web` package is fullstack, anything\n * else is API-only). See {@link DeployConfig}.\n */\n deploy?: DeployConfig\n /** Code style overrides (auto-detected from prettier when possible) */\n style?: {\n semicolons?: boolean\n quotes?: 'single' | 'double'\n trailingComma?: 'all' | 'es5' | 'none'\n indent?: number\n }\n\n /**\n * Extensibility hook for `kick doctor`. Adopters add their own\n * environment / project-shape checks here; each function receives\n * the same context the built-in checks see and returns a result (or\n * `null` to skip).\n *\n * The framework stays ORM- and stack-agnostic — Prisma-specific,\n * Drizzle-specific, deploy-target-specific checks belong in adopter\n * config (or in adapter packages that ship doctor extensions),\n * never in core.\n *\n * @example\n * ```ts\n * import { existsSync } from 'node:fs'\n * import { join } from 'node:path'\n * import { defineConfig } from '@forinda/kickjs-cli'\n *\n * export default defineConfig({\n * doctor: {\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 * : {\n * name: 'Prisma client generated',\n * status: 'fail',\n * fix: 'Run: pnpm exec prisma generate',\n * }\n * },\n * ],\n * },\n * })\n * ```\n */\n doctor?: import('./commands/doctor').DoctorExtension\n}\n\n/** Helper to define a type-safe kick.config.ts */\nexport function defineConfig(config: KickConfig): KickConfig {\n return config\n}\n\n/** Resolve module config from `modules.*` block. */\n/**\n * Resolve the project's DI token scope for code generators.\n * Falls back through kick.config.ts → package.json → `'app'`.\n *\n * @param config Loaded `kick.config.ts` (null when not present)\n * @param cwd Project root — used to read package.json\n */\n/**\n * Whether this project actually depends on `@forinda/kickjs-swagger`.\n *\n * The REST generator decorates controllers with `@ApiTags`, which comes from\n * that package. Emitting it unconditionally put an import of a package the\n * project does not have into every generated controller — five decorators and\n * a broken build, in a file the adopter did not write.\n *\n * Reading package.json rather than resolving the module: a generator should\n * emit what the project DECLARES it uses. A transitively-present copy in\n * node_modules is not a dependency this code may import.\n */\nexport function hasDependency(\n cwd: string,\n name: string,\n /**\n * Where the dependency must be declared. Default `'runtime'`: generated\n * code that *imports a value* — `@ApiTags` is a decorator, evaluated at\n * boot — needs the package present in a production install, and\n * `npm install --omit=dev` drops devDependencies. Pass `'any'` only for\n * something used at build or test time.\n */\n scope: 'runtime' | 'any' = 'runtime',\n): boolean {\n try {\n const pkgPath = join(cwd, 'package.json')\n if (!existsSync(pkgPath)) return false\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as {\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n }\n if (pkg.dependencies?.[name]) return true\n return scope === 'any' ? Boolean(pkg.devDependencies?.[name]) : false\n } catch {\n // An unreadable package.json means we cannot prove the dependency —\n // and emitting an import we cannot justify is the failure being fixed.\n return false\n }\n}\n\n/**\n * Where generated tests import `describe`/`it`/`expect` from. `vp migrate`\n * (Vite+) drops the `vitest` dependency and serves the same API from\n * `vite-plus/test`, so an app on vite-plus without vitest gets that.\n */\nexport function testImportSource(cwd: string): 'vitest' | 'vite-plus/test' {\n return hasDependency(cwd, 'vite-plus', 'any') && !hasDependency(cwd, 'vitest', 'any')\n ? 'vite-plus/test'\n : 'vitest'\n}\n\n/**\n * Whether the project declares the swagger package `@ApiTags` comes from.\n *\n * Runtime scope: the decorator is evaluated when the controller class is\n * defined, so a devDependency-only install would emit controllers that import\n * a package a production install does not have.\n */\nexport function hasSwagger(cwd: string): boolean {\n return hasDependency(cwd, '@forinda/kickjs-swagger', 'runtime')\n}\n\n/** Schema library a generated schema file is written against. */\nexport type SchemaLib = 'zod' | 'valibot' | 'yup'\n\n/**\n * Which validation library the project actually depends on, so generated\n * schema files import something that is installed.\n *\n * `kick new --schema valibot|yup` installs exactly one of these — the chosen\n * library and no other — so an emitted `import { z } from 'zod'` is a file that\n * cannot resolve. Read from package.json rather than `kick.config.ts` because\n * the dependency IS the fact; nothing has to be declared twice, and projects\n * scaffolded before this existed are covered without a config migration.\n *\n * Valibot and Yup are checked first: `kick new` installs one deliberately,\n * whereas `zod` is common enough to appear alongside it for unrelated reasons.\n * A project that genuinely needs to override the pick can gain a config field\n * when someone hits that; until then the dependency answers it.\n *\n * Runtime scope, same reasoning as {@link hasSwagger}: a DTO schema is\n * evaluated when the module loads, so a devDependency-only install would break\n * in production.\n */\nexport function resolveSchemaLib(cwd: string): SchemaLib {\n if (hasDependency(cwd, 'valibot', 'runtime')) return 'valibot'\n if (hasDependency(cwd, 'yup', 'runtime')) return 'yup'\n return 'zod'\n}\n\nexport function resolveTokenScope(config: KickConfig | null, cwd: string): string {\n if (config?.tokenScope && typeof config.tokenScope === 'string' && config.tokenScope.length > 0) {\n const sanitised = sanitizeScope(config.tokenScope)\n // Configured tokenScope can sanitise down to an empty string (e.g.\n // '___' or '!!') — falling through to the package.json chain is\n // safer than emitting an invalid `'/users/repository'` token.\n if (sanitised.length > 0) return sanitised\n }\n\n // Read package.json synchronously — this runs once per generator\n // invocation, so a sync read is cheaper than the async dance + lets\n // the call sites (template builders) stay synchronous.\n try {\n const pkgPath = join(cwd, 'package.json')\n if (existsSync(pkgPath)) {\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as { name?: unknown }\n if (typeof pkg.name === 'string' && pkg.name.length > 0) {\n const scoped = pkg.name.match(/^@([^/]+)\\//)\n const candidate = scoped ? sanitizeScope(scoped[1]) : sanitizeScope(pkg.name)\n if (candidate.length > 0) return candidate\n // Same empty-after-sanitize guard.\n }\n }\n } catch {\n // package.json missing or malformed — fall through to default\n }\n\n return 'app'\n}\n\n/** Lowercase + strip characters that would break a token literal. */\nfunction sanitizeScope(raw: string): string {\n return raw\n .toLowerCase()\n .replace(/[^a-z0-9-]/g, '-') // collapse anything weird to a hyphen\n .replace(/^-+|-+$/g, '') // trim leading/trailing hyphens\n .replace(/-{2,}/g, '-') // collapse runs of hyphens\n}\n\n/** Module names are used as typed unless a project or a flag asks for plurals. */\nexport const PLURALIZE_DEFAULT = false\n\n/** `--pluralize` / `--no-pluralize`, else `modules.pluralize` in kick.config.ts, else `false`. */\nexport function resolvePluralize(flag: boolean | undefined, mc?: ModuleConfig): boolean {\n return flag ?? mc?.pluralize ?? PLURALIZE_DEFAULT\n}\n\n/**\n * `--pluralize-routes` / `--no-pluralize-routes`, else `modules.pluralizeRoutes`,\n * else `true` — routes name a collection (`/tasks`) whatever the folder is called.\n */\nexport function resolvePluralizeRoutes(flag: boolean | undefined, mc?: ModuleConfig): boolean {\n return flag ?? mc?.pluralizeRoutes ?? true\n}\n\nexport function resolveModuleConfig(config: KickConfig | null): ModuleConfig {\n if (!config) return {}\n const mc: ModuleConfig = {\n dir: config.modules?.dir,\n repo: config.modules?.repo,\n schemaDir: config.modules?.schemaDir,\n pluralize: config.modules?.pluralize,\n pluralizeRoutes: config.modules?.pluralizeRoutes,\n style: config.modules?.style,\n }\n // Validate `style` — silently coerce unknown values to the default.\n // The CLI surface only documents 'define' and 'class'; anything else\n // is a typo (e.g. 'defineModule') we'd rather see than silently\n // emit class form.\n if (mc.style !== undefined && mc.style !== 'define' && mc.style !== 'class') {\n console.warn(\n ` Warning: modules.style '${mc.style as string}' is not a valid value ` +\n `(expected 'define' or 'class'). Falling back to 'define'.`,\n )\n mc.style = 'define'\n }\n\n // Warn if a string repo value isn't a known built-in. Deprecated ORM\n // presets get their own note; any other bare string suggests the\n // `{ name }` form to silence the stub-repo warning.\n if (mc.repo && typeof mc.repo === 'string' && !BUILTIN_REPO_TYPES.includes(mc.repo)) {\n if (!warnIfDeprecatedRepo(mc.repo)) {\n console.warn(\n ` Warning: modules.repo '${mc.repo}' is not a built-in type (${BUILTIN_REPO_TYPES.join(', ')}).` +\n ` It will generate a stub repository. Use { name: '${mc.repo}' } to silence this warning.`,\n )\n }\n }\n\n return mc\n}\n\nconst CONFIG_FILES = ['kick.config.ts', 'kick.config.js', 'kick.config.mjs', 'kick.config.json']\n\n/**\n * Load `kick.config.*` starting from `startDir` and walking up toward\n * the filesystem root until a config file is found. Returns `null`\n * when no config exists anywhere on the way up.\n *\n * Walking up means adopters can run `kick <cmd>` from any subdirectory\n * (e.g. `src/modules/users/`) and still pick up the project's config —\n * before this change a nested-cwd invocation silently saw `null` and\n * fell back to framework defaults.\n *\n * TypeScript configs (`.ts`) are loaded via `jiti` when available;\n * `.js` / `.mjs` use native `import()`; `.json` uses `JSON.parse`. The\n * jiti import is dynamic + best-effort: if the dep is missing we\n * surface a warning telling the adopter how to install it, instead of\n * silently dropping the config (which is what the previous bare-catch\n * did).\n */\n/**\n * When the project can't resolve `@forinda/kickjs-cli` itself — a fresh\n * `kick new` before install, or a global `kick` in a project whose deps\n * aren't installed — map it to this CLI, so `import { defineConfig } from\n * '@forinda/kickjs-cli'` in kick.config.ts still loads. The config is read by\n * this CLI either way; an installed copy is left to normal resolution.\n */\nfunction selfAlias(root: string): Record<string, string> {\n for (let dir = root; ; dir = dirname(dir)) {\n if (existsSync(join(dir, 'node_modules', '@forinda', 'kickjs-cli', 'package.json'))) return {}\n if (dirname(dir) === dir) break\n }\n // One level below the package root in source (src/) and in the bundle (dist/).\n const pkgRoot = join(dirname(fileURLToPath(import.meta.url)), '..')\n const entry = [join(pkgRoot, 'dist', 'index.mjs'), join(pkgRoot, 'src', 'index.ts')].find(\n existsSync,\n )\n return entry ? { '@forinda/kickjs-cli': entry } : {}\n}\n\nexport async function loadKickConfig(startDir: string): Promise<KickConfig | null> {\n const { findProjectRoot } = await import('./utils/project-root')\n const root = findProjectRoot(startDir)\n\n for (const filename of CONFIG_FILES) {\n const filepath = join(root, filename)\n try {\n await access(filepath)\n } catch {\n continue\n }\n\n if (filename.endsWith('.json')) {\n const content = await readFile(filepath, 'utf-8')\n return JSON.parse(content)\n }\n\n const isTs = filename.endsWith('.ts')\n\n if (isTs) {\n // Split the import-jiti step from the load-user-config step so the\n // diagnostic blames the right thing. Both surface as Node module-\n // resolution errors with similar shapes (`Cannot find package …`,\n // `ERR_MODULE_NOT_FOUND`), but the remedies differ:\n //\n // - Missing jiti → adopter needs to install jiti.\n // - Missing dep referenced from kick.config.ts → adopter needs to\n // install THAT package; telling them to install jiti would send\n // them to the wrong fix and bury the real missing module.\n let jitiModule: typeof import('jiti')\n try {\n jitiModule = await import('jiti')\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n if (msg.includes(\"Cannot find package 'jiti'\") || msg.includes('ERR_MODULE_NOT_FOUND')) {\n console.warn(\n `Warning: Failed to load ${filename} — 'jiti' is required for TypeScript configs. ` +\n 'Install jiti as a dev dependency, or rename the file ' +\n 'to kick.config.js / kick.config.mjs / kick.config.json.',\n )\n } else {\n console.warn(`Warning: Failed to initialize jiti for ${filename}: ${msg}`)\n }\n continue\n }\n\n try {\n const jiti = jitiModule.createJiti(root, {\n interopDefault: true,\n fsCache: false,\n alias: selfAlias(root),\n // The project's tsconfig `paths` (`@/*` → `src/*`), for what the\n // config imports — eagerly or lazily, like a `db.adapter()` that\n // loads `src/db/client.ts`, which imports `@/config`. Outside Vite\n // nothing else resolves the alias.\n tsconfigPaths: true,\n })\n const config = (await jiti.import(filepath, { default: true })) as KickConfig\n const warnings = validateAssetMap(config, root)\n for (const warning of warnings) console.warn(` Warning: ${warning}`)\n writeAssetConfigSnapshot(root, config)\n return config\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n console.warn(`Warning: Failed to load ${filename}: ${msg}`)\n continue\n }\n }\n\n try {\n const { pathToFileURL } = await import('node:url')\n const mod = await import(pathToFileURL(filepath).href)\n const config = (mod.default ?? mod) as KickConfig\n const warnings = validateAssetMap(config, root)\n for (const warning of warnings) console.warn(` Warning: ${warning}`)\n writeAssetConfigSnapshot(root, config)\n return config\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n console.warn(`Warning: Failed to load ${filename}: ${msg}`)\n continue\n }\n }\n return null\n}\n\n/**\n * Mirror the JSON-serialisable slice of a TS/JS `kick.config` that the\n * runtime asset resolver needs (`assetMap` + `build.outDir`) into\n * `.kickjs/kick.config.json`.\n *\n * Why: the runtime resolver in `@forinda/kickjs` reads its config\n * *synchronously* (it sits on the hot path of `assets.x.y()` /\n * `resolveAsset()`), so it can only parse `kick.config.{json,js,cjs}` —\n * it deliberately can't transpile `kick.config.ts`. For a `.ts`-config\n * project in dev (no built `dist/.kickjs-assets.json` yet), that left\n * the resolver with no config to synthesise a manifest from, so\n * `assets.x.y()` threw `UnknownAssetError` until the first build.\n *\n * The CLI already transpiles the `.ts` config here, so it drops this\n * tiny snapshot the runtime can read with a plain `JSON.parse`. Best\n * effort: any failure (read-only fs, etc.) is swallowed — a missing\n * snapshot just falls back to the previous behaviour.\n */\nexport function writeAssetConfigSnapshot(root: string, config: KickConfig | null): void {\n // Only meaningful for asset-manager users — don't litter `.kickjs/`\n // for projects that never declare an assetMap.\n if (!config?.assetMap || Object.keys(config.assetMap).length === 0) return\n try {\n const dir = join(root, '.kickjs')\n mkdirSync(dir, { recursive: true })\n const snapshot = {\n // Marker so a future shape change can be detected/migrated.\n version: 1 as const,\n assetMap: config.assetMap,\n ...(config.build?.outDir ? { build: { outDir: config.build.outDir } } : {}),\n }\n writeFileSync(join(dir, 'kick.config.json'), JSON.stringify(snapshot, null, 2) + '\\n', 'utf-8')\n } catch {\n // Best effort — snapshot is an optimisation, not a correctness\n // requirement. Production reads the real dist manifest.\n }\n}\n\n/**\n * Validate `assetMap` entries on a loaded config. Returns a list of\n * human-readable warnings; the caller decides how to surface them\n * (typically `console.warn`). Never throws — `kick g` and other\n * unrelated commands should keep working even when the assetMap is\n * misconfigured.\n *\n * Checks:\n *\n * - Each entry's `src` is a non-empty string.\n * - The `src` directory exists on disk (otherwise the typegen + build\n * steps will fail later with cryptic errors).\n * - `dest` doesn't escape the project root (defensive — a `dest:\n * '../../etc'` typo could write files outside the workspace).\n * - The namespace key is a non-empty string and doesn't include a\n * `/` (would conflict with the `<namespace>/<key>` manifest format).\n */\nexport function validateAssetMap(config: KickConfig | null, cwd: string): string[] {\n const warnings: string[] = []\n if (!config?.assetMap) return warnings\n\n const root = resolve(cwd)\n for (const [namespace, entry] of Object.entries(config.assetMap)) {\n if (!namespace || namespace.includes('/')) {\n warnings.push(\n `assetMap key '${namespace}' is invalid — must be a non-empty string without '/'`,\n )\n continue\n }\n if (typeof entry?.src !== 'string' || entry.src.length === 0) {\n warnings.push(`assetMap.${namespace} is missing a non-empty 'src' field`)\n continue\n }\n const srcAbs = resolve(cwd, entry.src)\n if (!existsSync(srcAbs)) {\n warnings.push(\n `assetMap.${namespace}.src ('${entry.src}') does not exist — typegen + build will fail`,\n )\n }\n if (entry.dest) {\n const destAbs = resolve(cwd, entry.dest)\n // path.relative is the right primitive for \"is X inside Y?\" —\n // a raw startsWith() prefix match has two failure modes the\n // earlier version hit: (a) `/app` is a prefix of `/app2/...`\n // even though they're different directories, and (b) it's\n // case-sensitive on filesystems that aren't (macOS default,\n // Windows). path.relative handles both correctly + accounts\n // for `..` traversal in the destination.\n if (escapesRoot(destAbs, root)) {\n warnings.push(\n `assetMap.${namespace}.dest ('${entry.dest}') resolves outside the project root — refusing to copy`,\n )\n }\n }\n }\n return warnings\n}\n\n/**\n * Returns true when `path` (absolute) resolves outside of `root`\n * (also absolute). Uses `path.relative` for accuracy:\n *\n * - The result is empty when paths are identical (inside).\n * - It starts with `..` when the path traverses outside the root.\n * - It's absolute (Windows: cross-drive) when there's no relative\n * path between them.\n *\n * Avoids the prefix-match pitfalls of `startsWith` (e.g. `/app`\n * matching `/app2/...`, or case-mismatches on macOS / Windows).\n */\nfunction escapesRoot(path: string, root: string): boolean {\n const rel = relative(root, path)\n return rel === '' ? false : rel.startsWith('..') || isAbsolute(rel)\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs-cli v8.12.1
2
+ * @forinda/kickjs-cli v8.13.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{i as e,o as t,t as n}from"./package-manager-C9253oKe.mjs";import{a as r,n as i,r as a,t as o,u as s}from"./prompts-iLmIrMD4.mjs";import{s as c}from"./config-aEXDfmSS.mjs";import{c as l,i as u}from"./project-BktC0yLE.mjs";import{t as d}from"./project-root-C_H7sWTH.mjs";import{createRequire as f}from"node:module";import p,{dirname as m,join as h,relative as g,resolve as _}from"node:path";import{existsSync as v,readFileSync as y,readdirSync as b,statSync as x}from"node:fs";import{readFile as S}from"node:fs/promises";import C from"pluralize";import{pathToFileURL as w}from"node:url";import{defineGenerator as ee}from"@forinda/kickjs-cli-kit";C.addUncountableRule(`auth`);function T(e){return e.replace(/[-_\s]+(.)?/g,(e,t)=>t?t.toUpperCase():``).replace(/^(.)/,e=>e.toUpperCase())}function E(e){let t=T(e);return t.charAt(0).toLowerCase()+t.slice(1)}function D(e){return e.replace(/([a-z])([A-Z])/g,`$1-$2`).replace(/[\s_]+/g,`-`).toLowerCase()}function O(e){return C.plural(e)}function k(e){return C.plural(e)}function A(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}const te={inmemory:`in-memory`,drizzle:`Drizzle`,prisma:`Prisma`};function ne(e){return e.charAt(0).toUpperCase()+e.slice(1).replace(/-([a-z])/g,(e,t)=>t.toUpperCase())}function re(e){return te[e]??ne(e)}function ie(e){return`create${e}Repository`}function j(e){return e??`define`}function M(e){let{pascal:t,kebab:n,plural:r=``,repo:i,style:a}=e,o=e.routePath??r,s=ie(t),c=j(a),l=`/**
11
+ import{_ as e,a as t,c as n,h as r,i,o as a,p as o,t as s}from"./package-manager-CSsqVTZ-.mjs";import{s as c}from"./config-D0zuz3Fx.mjs";import{c as l,i as u}from"./project-B_yEOLE4.mjs";import{t as d}from"./project-root-DYJU7ROd.mjs";import{createRequire as f}from"node:module";import p,{dirname as m,join as h,relative as g,resolve as _}from"node:path";import{existsSync as v,readFileSync as y,readdirSync as b,statSync as x}from"node:fs";import{readFile as S}from"node:fs/promises";import C from"pluralize";import{pathToFileURL as w}from"node:url";import{defineGenerator as ee}from"@forinda/kickjs-cli-kit";C.addUncountableRule(`auth`);function T(e){return e.replace(/[-_\s]+(.)?/g,(e,t)=>t?t.toUpperCase():``).replace(/^(.)/,e=>e.toUpperCase())}function E(e){let t=T(e);return t.charAt(0).toLowerCase()+t.slice(1)}function D(e){return e.replace(/([a-z])([A-Z])/g,`$1-$2`).replace(/[\s_]+/g,`-`).toLowerCase()}function O(e){return C.plural(e)}function k(e){return C.plural(e)}function A(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}const te={inmemory:`in-memory`,drizzle:`Drizzle`,prisma:`Prisma`};function ne(e){return e.charAt(0).toUpperCase()+e.slice(1).replace(/-([a-z])/g,(e,t)=>t.toUpperCase())}function re(e){return te[e]??ne(e)}function ie(e){return`create${e}Repository`}function j(e){return e??`define`}function M(e){let{pascal:t,kebab:n,plural:r=``,repo:i,style:a}=e,o=e.routePath??r,s=ie(t),c=j(a),l=`/**
12
12
  * ${t} Module
13
13
  *
14
14
  * REST module with a flat folder structure.
@@ -757,18 +757,18 @@ export class ${t}Controller {
757
757
  return reply.noContent()
758
758
  }
759
759
  }
760
- `)}async function me(e){let{pascal:t,kebab:n,plural:r,pluralPascal:i,routePath:a,repo:o,noTests:s,tokenScope:c,style:l,testHarness:u,testImport:d,write:f}=e,p=e.swagger??!1,m=e.schemaLib??`zod`;await f(`${n}.module.ts`,M({pascal:t,kebab:n,plural:r,routePath:a,repo:o,style:l})),await f(`${n}.constants.ts`,R({pascal:t,kebab:n})),await f(`${n}.controller.ts`,N({pascal:t,kebab:n,plural:r,pluralPascal:i,swagger:p})),await f(`${n}.service.ts`,L({pascal:t,kebab:n})),await f(`dtos/create-${n}.dto.ts`,oe({pascal:t,kebab:n,schemaLib:m})),await f(`dtos/update-${n}.dto.ts`,se({pascal:t,kebab:n,schemaLib:m})),await f(`dtos/${n}-response.dto.ts`,ce({pascal:t,kebab:n})),await f(`${n}.repository.ts`,I({pascal:t,kebab:n,repoType:o,dtoPrefix:`./dtos`,tokenScope:c})),s||(await f(`__tests__/${n}.controller.test.ts`,le({pascal:t,kebab:n,plural:r,routePath:a,style:l,testHarness:u,testImport:d})),await f(`__tests__/${n}.repository.test.ts`,ue({pascal:t,kebab:n,plural:r,repoPrefix:`../${n}.repository`,testImport:d})))}function he(e){return e?typeof e==`string`?e:e.name:`inmemory`}async function ge(n){let{name:r,modulesDir:i,noEntity:c,noTests:l,repo:u=`inmemory`,force:d,dryRun:f}=n,p=n.pluralize===!0,m=n.pattern??`rest`;n.minimal&&(m=`minimal`);let g=D(r),_=T(r),v=p?O(g):g,y=p?k(_):_,b=n.pluralizeRoutes===!1?g:O(g),x=h(i,v),S=[],C=d??!1,w={kebab:g,pascal:_,plural:v,pluralPascal:y,routePath:b,moduleDir:x,repo:u,noEntity:c??!1,noTests:l??!1,tokenScope:n.tokenScope??`app`,swagger:n.swagger??!1,schemaLib:n.schemaLib??`zod`,testHarness:n.testHarness??!1,testImport:n.testImport,style:n.style??`define`,write:async(n,r)=>{let i=h(x,n);f?S.push(i):!C&&await e(i)&&!await o({message:`File exists: ${s.dim(n)}. Overwrite?`,initialValue:!1})?a.warn(`Skipped: ${n}`):(await t(i,r),S.push(i))},files:S};switch(m){case`minimal`:await pe(w);break;default:await me(w)}return f||await z(i,_,v,g,w.style),S}async function z(n,r,i,a,o=`define`){let s=h(n,`index.ts`),c=await e(s),l=`./${i}/${a}.module`,u=o===`class`?`${r}Module`:`${r}Module()`;if(!c){let e=o===`class`?`import type { AppModuleEntry } from '@forinda/kickjs'
761
- import { ${r}Module } from '${l}'
760
+ `)}async function me(e){let{pascal:t,kebab:n,plural:r,pluralPascal:i,routePath:a,repo:o,noTests:s,tokenScope:c,style:l,testHarness:u,testImport:d,write:f}=e,p=e.swagger??!1,m=e.schemaLib??`zod`;await f(`${n}.module.ts`,M({pascal:t,kebab:n,plural:r,routePath:a,repo:o,style:l})),await f(`${n}.constants.ts`,R({pascal:t,kebab:n})),await f(`${n}.controller.ts`,N({pascal:t,kebab:n,plural:r,pluralPascal:i,swagger:p})),await f(`${n}.service.ts`,L({pascal:t,kebab:n})),await f(`dtos/create-${n}.dto.ts`,oe({pascal:t,kebab:n,schemaLib:m})),await f(`dtos/update-${n}.dto.ts`,se({pascal:t,kebab:n,schemaLib:m})),await f(`dtos/${n}-response.dto.ts`,ce({pascal:t,kebab:n})),await f(`${n}.repository.ts`,I({pascal:t,kebab:n,repoType:o,dtoPrefix:`./dtos`,tokenScope:c})),s||(await f(`__tests__/${n}.controller.test.ts`,le({pascal:t,kebab:n,plural:r,routePath:a,style:l,testHarness:u,testImport:d})),await f(`__tests__/${n}.repository.test.ts`,ue({pascal:t,kebab:n,plural:r,repoPrefix:`../${n}.repository`,testImport:d})))}function he(e){return e?typeof e==`string`?e:e.name:`inmemory`}async function ge(t){let{name:n,modulesDir:s,noEntity:c,noTests:l,repo:u=`inmemory`,force:d,dryRun:f}=t,p=t.pluralize===!0,m=t.pattern??`rest`;t.minimal&&(m=`minimal`);let g=D(n),_=T(n),v=p?O(g):g,y=p?k(_):_,b=t.pluralizeRoutes===!1?g:O(g),x=h(s,v),S=[],C=d??!1,w={kebab:g,pascal:_,plural:v,pluralPascal:y,routePath:b,moduleDir:x,repo:u,noEntity:c??!1,noTests:l??!1,tokenScope:t.tokenScope??`app`,swagger:t.swagger??!1,schemaLib:t.schemaLib??`zod`,testHarness:t.testHarness??!1,testImport:t.testImport,style:t.style??`define`,write:async(t,n)=>{let s=h(x,t);f?S.push(s):!C&&await r(s)&&!await i({message:`File exists: ${o.dim(t)}. Overwrite?`,initialValue:!1})?a.warn(`Skipped: ${t}`):(await e(s,n),S.push(s))},files:S};switch(m){case`minimal`:await pe(w);break;default:await me(w)}return f||await z(s,_,v,g,w.style),S}async function z(t,n,i,a,o=`define`){let s=h(t,`index.ts`),c=await r(s),l=`./${i}/${a}.module`,u=o===`class`?`${n}Module`:`${n}Module()`;if(!c){let t=o===`class`?`import type { AppModuleEntry } from '@forinda/kickjs'
761
+ import { ${n}Module } from '${l}'
762
762
 
763
763
  export const modules: AppModuleEntry[] = [${u}]
764
764
  `:`import { defineModules } from '@forinda/kickjs'
765
- import { ${r}Module } from '${l}'
765
+ import { ${n}Module } from '${l}'
766
766
 
767
767
  export const modules = defineModules().mount(${u})
768
- `;await t(s,e);return}let d=await S(s,`utf-8`),f=`import { ${r}Module } from '${l}'`,p=A(l);if(!RegExp(`^import\\s*\\{[^}]*\\b${A(r)}Module\\b[^}]*\\}\\s*from\\s*['"]${p}['"]`,`m`).test(d)){let e=d.lastIndexOf(`import `);if(e!==-1){let t=d.indexOf(`
768
+ `;await e(s,t);return}let d=await S(s,`utf-8`),f=`import { ${n}Module } from '${l}'`,p=A(l);if(!RegExp(`^import\\s*\\{[^}]*\\b${A(n)}Module\\b[^}]*\\}\\s*from\\s*['"]${p}['"]`,`m`).test(d)){let e=d.lastIndexOf(`import `);if(e!==-1){let t=d.indexOf(`
769
769
  `,e);d=d.slice(0,t+1)+f+`
770
770
  `+d.slice(t+1)}else d=f+`
771
- `+d}let m=V(d);if(m){let e=d.slice(m.rhsStart,m.rhsEnd+1);RegExp(`\\b${A(r)}Module\\b`).test(e)||(d=B(d,u))}else d=B(d,u);await t(s,d)}function B(e,t){let n=V(e);if(!n)return e;if(n.shape===`array`){let r=e.slice(n.rhsStart+1,n.rhsEnd),i=r.trim(),a;if(!i)a=`[${t}]`;else{let e=i.endsWith(`,`)?``:`,`;a=`[${r.trimEnd()}${e} ${t}]`}return e.slice(0,n.rhsStart)+a+e.slice(n.rhsEnd+1)}return`${e.slice(0,n.chainEnd)}\n .mount(${t})${e.slice(n.chainEnd)}`}function V(e){let t=/export\s+const\s+modules\b[^=]*=/.exec(e);if(!t)return null;let n=t.index+t[0].length;for(;n<e.length&&/\s/.test(e[n]??``);)n++;if(e[n]===`[`){let t=ve(e,n);return t===-1?null:{shape:`array`,rhsStart:n,rhsEnd:t}}if(e.slice(n,n+13)===`defineModules`){let t=_e(e,n);return t===-1?null:{shape:`chain`,rhsStart:n,rhsEnd:t-1,chainEnd:t}}return null}function _e(e,t=0){let n=/defineModules\s*\(/g;n.lastIndex=t;let r=n.exec(e);if(!r)return-1;let i=r.index+r[0].length-1;if(e[i]!==`(`||(i=U(e,i),i===-1))return-1;for(i++;;){let t=i;for(;t<e.length&&/\s/.test(e[t]??``);)t++;if(e[t]!==`.`||e.slice(t,t+6)!==`.mount`)break;for(t+=6;t<e.length&&/\s/.test(e[t]??``);)t++;if(e[t]!==`(`)break;let n=U(e,t);if(n===-1)break;i=n+1}return i}function H(e,t){let n=e.slice(t,t+2);if(n===`//`){for(t+=2;t<e.length&&e[t]!==`
771
+ `+d}let m=V(d);if(m){let e=d.slice(m.rhsStart,m.rhsEnd+1);RegExp(`\\b${A(n)}Module\\b`).test(e)||(d=B(d,u))}else d=B(d,u);await e(s,d)}function B(e,t){let n=V(e);if(!n)return e;if(n.shape===`array`){let r=e.slice(n.rhsStart+1,n.rhsEnd),i=r.trim(),a;if(!i)a=`[${t}]`;else{let e=i.endsWith(`,`)?``:`,`;a=`[${r.trimEnd()}${e} ${t}]`}return e.slice(0,n.rhsStart)+a+e.slice(n.rhsEnd+1)}return`${e.slice(0,n.chainEnd)}\n .mount(${t})${e.slice(n.chainEnd)}`}function V(e){let t=/export\s+const\s+modules\b[^=]*=/.exec(e);if(!t)return null;let n=t.index+t[0].length;for(;n<e.length&&/\s/.test(e[n]??``);)n++;if(e[n]===`[`){let t=ve(e,n);return t===-1?null:{shape:`array`,rhsStart:n,rhsEnd:t}}if(e.slice(n,n+13)===`defineModules`){let t=_e(e,n);return t===-1?null:{shape:`chain`,rhsStart:n,rhsEnd:t-1,chainEnd:t}}return null}function _e(e,t=0){let n=/defineModules\s*\(/g;n.lastIndex=t;let r=n.exec(e);if(!r)return-1;let i=r.index+r[0].length-1;if(e[i]!==`(`||(i=U(e,i),i===-1))return-1;for(i++;;){let t=i;for(;t<e.length&&/\s/.test(e[t]??``);)t++;if(e[t]!==`.`||e.slice(t,t+6)!==`.mount`)break;for(t+=6;t<e.length&&/\s/.test(e[t]??``);)t++;if(e[t]!==`(`)break;let n=U(e,t);if(n===-1)break;i=n+1}return i}function H(e,t){let n=e.slice(t,t+2);if(n===`//`){for(t+=2;t<e.length&&e[t]!==`
772
772
  `;)t++;return t}if(n===`/*`){for(t+=2;t+1<e.length&&(e[t]!==`*`||e[t+1]!==`/`);)t++;return t+2}return t}function ve(e,t){if(e[t]!==`[`)return-1;let n=1,r=t+1;for(;r<e.length;){let t=e.slice(r,r+2);if(t===`//`||t===`/*`){r=H(e,r);continue}let i=e[r]??``;if(i===`'`||i===`"`||i==="`"){let t=i;for(r++;r<e.length&&e[r]!==t;)e[r]===`\\`&&r++,r++;r<e.length&&r++}else{if(i===`[`)n++;else if(i===`]`&&(n--,n===0))return r;r++}}return-1}function U(e,t){if(e[t]!==`(`)return-1;let n=1,r=t+1;for(;r<e.length;){let t=e.slice(r,r+2);if(t===`//`||t===`/*`){r=H(e,r);continue}let i=e[r]??``;if(i===`'`||i===`"`||i==="`"){let t=i;for(r++;r<e.length&&e[r]!==t;)e[r]===`\\`&&r++,r++;r<e.length&&r++}else{if(i===`(`)n++;else if(i===`)`&&(n--,n===0))return r;r++}}return-1}function W(e,t,n,r){return`/**
773
773
  * Snapshot for the DevTools topology view (\`/_debug\`).
774
774
  *
@@ -809,7 +809,7 @@ export const modules = defineModules().mount(${u})
809
809
  // ]
810
810
  // },`.split(`
811
811
  `).map(e=>r+e).join(`
812
- `)}async function ye(e){let{name:n,outDir:r}=e,i=D(n),a=T(n),o=[],s=h(r,`${i}.adapter.ts`);return await t(s,`import {
812
+ `)}async function ye(t){let{name:n,outDir:r}=t,i=D(n),a=T(n),o=[],s=h(r,`${i}.adapter.ts`);return await e(s,`import {
813
813
  defineAdapter,
814
814
  type AdapterContext,
815
815
  type AdapterMiddleware,
@@ -1018,8 +1018,8 @@ ${W(i,a,`adapter`,` `)}
1018
1018
  }
1019
1019
  },
1020
1020
  })
1021
- `),o.push(s),o}const be={controller:``,service:``,dto:`dtos`,guard:`guards`,middleware:`middleware`,contributor:`contributors`};function G(e){let{type:t,outDir:n,moduleName:r,modulesDir:i=`src/modules`,defaultDir:a,shouldPluralize:o=!1}=e;if(n)return _(n);if(r){let e=be,n=D(r),a=o?O(n):n,s=e[t]??``,c=h(i,a);return _(s?h(c,s):c)}return _(a)}async function xe(e){let{name:n,moduleName:r,modulesDir:i,pattern:a}=e,o=e.runtime===`express`,s=e.runtime?`this project runs on ${e.runtime} and has no
1022
- // \`express\``:"this project declares no `runtime` in kick.config.ts, so it\n // may have no `express`",c=G({type:`middleware`,outDir:e.outDir,moduleName:r,modulesDir:i,defaultDir:`src/middleware`,pattern:a,shouldPluralize:e.pluralize??!1}),l=D(n),u=E(n),d=[],f=h(c,`${l}.middleware.ts`);return await t(f,`${o?`import type { Request, Response, NextFunction } from 'express'`:`import type { IncomingMessage, ServerResponse } from 'node:http'`}
1021
+ `),o.push(s),o}const be={controller:``,service:``,dto:`dtos`,guard:`guards`,middleware:`middleware`,contributor:`contributors`};function G(e){let{type:t,outDir:n,moduleName:r,modulesDir:i=`src/modules`,defaultDir:a,shouldPluralize:o=!1}=e;if(n)return _(n);if(r){let e=be,n=D(r),a=o?O(n):n,s=e[t]??``,c=h(i,a);return _(s?h(c,s):c)}return _(a)}async function xe(t){let{name:n,moduleName:r,modulesDir:i,pattern:a}=t,o=t.runtime===`express`,s=t.runtime?`this project runs on ${t.runtime} and has no
1022
+ // \`express\``:"this project declares no `runtime` in kick.config.ts, so it\n // may have no `express`",c=G({type:`middleware`,outDir:t.outDir,moduleName:r,modulesDir:i,defaultDir:`src/middleware`,pattern:a,shouldPluralize:t.pluralize??!1}),l=D(n),u=E(n),d=[],f=h(c,`${l}.middleware.ts`);return await e(f,`${o?`import type { Request, Response, NextFunction } from 'express'`:`import type { IncomingMessage, ServerResponse } from 'node:http'`}
1023
1023
 
1024
1024
  export interface ${T(n)}Options {
1025
1025
  // Add configuration options here. The factory below closes over the
@@ -1078,7 +1078,7 @@ ${o?" // Typed from `express` because that is this project's runtime\n // (`ru
1078
1078
  next()
1079
1079
  }
1080
1080
  }
1081
- `),d.push(f),d}async function Se(e){let{name:n,moduleName:r,modulesDir:i,pattern:a}=e,o=G({type:`guard`,outDir:e.outDir,moduleName:r,modulesDir:i,defaultDir:`src/guards`,pattern:a,shouldPluralize:e.pluralize??!1}),s=D(n),c=E(n),l=T(n),u=[],d=h(o,`${s}.guard.ts`);return await t(d,`import { Container, HttpException } from '@forinda/kickjs'
1081
+ `),d.push(f),d}async function Se(t){let{name:n,moduleName:r,modulesDir:i,pattern:a}=t,o=G({type:`guard`,outDir:t.outDir,moduleName:r,modulesDir:i,defaultDir:`src/guards`,pattern:a,shouldPluralize:t.pluralize??!1}),s=D(n),c=E(n),l=T(n),u=[],d=h(o,`${s}.guard.ts`);return await e(d,`import { Container, HttpException } from '@forinda/kickjs'
1082
1082
  import type { RequestContext } from '@forinda/kickjs'
1083
1083
 
1084
1084
  /**
@@ -1118,7 +1118,7 @@ export async function ${c}Guard(ctx: RequestContext, next: () => void): Promise<
1118
1118
  ctx.problem.unauthorized({ detail: 'Invalid or expired token' })
1119
1119
  }
1120
1120
  }
1121
- `),u.push(d),u}async function Ce(e){let{name:n,moduleName:r,modulesDir:i,pattern:a}=e,o=G({type:`service`,outDir:e.outDir,moduleName:r,modulesDir:i,defaultDir:`src/services`,pattern:a,shouldPluralize:e.pluralize??!1}),s=D(n),c=T(n),l=[],u=h(o,`${s}.service.ts`);return await t(u,`import { Service } from '@forinda/kickjs'
1121
+ `),u.push(d),u}async function Ce(t){let{name:n,moduleName:r,modulesDir:i,pattern:a}=t,o=G({type:`service`,outDir:t.outDir,moduleName:r,modulesDir:i,defaultDir:`src/services`,pattern:a,shouldPluralize:t.pluralize??!1}),s=D(n),c=T(n),l=[],u=h(o,`${s}.service.ts`);return await e(u,`import { Service } from '@forinda/kickjs'
1122
1122
 
1123
1123
  @Service()
1124
1124
  export class ${c}Service {
@@ -1127,7 +1127,7 @@ export class ${c}Service {
1127
1127
  // @Inject(MY_REPO) private readonly repo: IMyRepository,
1128
1128
  // ) {}
1129
1129
  }
1130
- `),l.push(u),l}async function we(e){let{name:n,moduleName:r,modulesDir:i,pattern:a}=e,o=G({type:`controller`,outDir:e.outDir,moduleName:r,modulesDir:i,defaultDir:`src/controllers`,pattern:a,shouldPluralize:e.pluralize??!1}),s=D(n),c=T(n),l=[],u=h(o,`${s}.controller.ts`);return await t(u,`import { Controller, Get, Post, Patch, Delete, reply, type Ctx } from '@forinda/kickjs'
1130
+ `),l.push(u),l}async function we(t){let{name:n,moduleName:r,modulesDir:i,pattern:a}=t,o=G({type:`controller`,outDir:t.outDir,moduleName:r,modulesDir:i,defaultDir:`src/controllers`,pattern:a,shouldPluralize:t.pluralize??!1}),s=D(n),c=T(n),l=[],u=h(o,`${s}.controller.ts`);return await e(u,`import { Controller, Get, Post, Patch, Delete, reply, type Ctx } from '@forinda/kickjs'
1131
1131
 
1132
1132
  // \`Ctx<KickRoutes.${c}Controller['<method>']>\` is generated by
1133
1133
  // \`kick typegen\` (auto-run on \`kick dev\`). After the first run, your IDE
@@ -1170,21 +1170,21 @@ export class ${c}Controller {
1170
1170
  return reply.noContent()
1171
1171
  }
1172
1172
  }
1173
- `),l.push(u),l}async function Te(e){let{name:n,moduleName:r,modulesDir:i,pattern:a}=e,o=G({type:`dto`,outDir:e.outDir,moduleName:r,modulesDir:i,defaultDir:`src/dtos`,pattern:a,shouldPluralize:e.pluralize??!1}),s=D(n),c=T(n),l=E(n),u=e.schemaLib??`zod`,d=P[u],f=[],p=h(o,`${s}.dto.ts`);return await t(p,`${d.import}
1173
+ `),l.push(u),l}async function Te(t){let{name:n,moduleName:r,modulesDir:i,pattern:a}=t,o=G({type:`dto`,outDir:t.outDir,moduleName:r,modulesDir:i,defaultDir:`src/dtos`,pattern:a,shouldPluralize:t.pluralize??!1}),s=D(n),c=T(n),l=E(n),u=t.schemaLib??`zod`,d=P[u],f=[],p=h(o,`${s}.dto.ts`);return await e(p,`${d.import}
1174
1174
 
1175
1175
  export const ${l}Schema = ${F(u,`// Define your schema fields here\n name: ${d.required}`)}
1176
1176
 
1177
1177
  export type ${c}DTO = ${d.infer(`${l}Schema`)}
1178
1178
  `),f.push(p),f}function Ee(){let e=(process.env.LOG_LEVEL??process.env.KICKJS_LOG_LEVEL??``).toLowerCase();return e===`debug`||e===`trace`}var De=class extends Error{drifted;constructor(e){let t=e.map(e=>` ${e.id} → ${e.outFile}`).join(`
1179
- `);super(`kick typegen --check: ${e.length} generated file(s) are out of date:\n${t}\n Run \`kick typegen\` and commit the result.`),this.name=`TypegenDriftError`,this.drifted=e}};function Oe(e){return e}function ke(e){let t=/* @__PURE__ */ new Map;return{report(n,r){let i=r instanceof Error?r.message:String(r);t.get(n)!==i&&(t.set(n,i),e(` kick typegen: ${n} pass failed (${i}) — types in .kickjs/types may be stale`))},clear(e){t.delete(e)}}}const Ae=`__kickjs_typegen_owner`;function je(e){let{cwd:t,config:n}=e,r=e.debounceMs??100,i=e.pipeline??{runTypegen:async e=>(await import(`./typegen-C13eMf8Q.mjs`).then(e=>e.n)).runTypegen(e),runAllPluginTypegens:async e=>(await import(`./run-plugins-DY4qiPTu.mjs`).then(e=>e.n)).runAllPluginTypegens(e),writeTypegenArtifacts:async(e,t,n)=>(await import(`./typegen-C13eMf8Q.mjs`).then(e=>e.n)).writeTypegenArtifacts(e,t,n),buildAssets:async(e,t)=>(await import(`./build-DZtpU_3Z.mjs`).then(e=>e.n)).buildAssets(e,t)},a=n?.typegen?.schemaValidator??`zod`,o=n?.typegen?.envFile,s=p.resolve(t,n?.typegen?.outDir??`.kickjs/types`),c=n?.assetMap?Object.values(n.assetMap).map(e=>e?.src).filter(e=>typeof e==`string`&&e.length>0).map(e=>p.resolve(t,e)):[],l=!!n?.assetMap&&Object.keys(n.assetMap).length>0,u=e=>e.replaceAll(`\\`,`/`),d=c.map(u),f=e=>{let t=u(e);return d.some(e=>t===e||t.startsWith(`${e}/`))},m=ke(e.emitWarning),h=null,g=!1,_=/* @__PURE__ */ new Set,v=/* @__PURE__ */ new Set,y=!1,b=!1;function x(r,c){i.runTypegen({cwd:t,silent:!0,allowDuplicates:!0,schemaValidator:a,envFile:o,srcDir:n?.typegen?.srcDir,outDir:n?.typegen?.outDir,assetMap:n?.assetMap,changedFiles:r,runPlugins:!1}).then(()=>m.clear(`scan`)).catch(e=>m.report(`scan`,e)),i.runAllPluginTypegens({cwd:t,config:n,silent:!0,changedFiles:r,watch:!0}).then(e=>i.writeTypegenArtifacts(s,e,!0)).then(()=>m.clear(`plugins`)).catch(e=>m.report(`plugins`,e)).finally(()=>e.onPassComplete?.()),c&&n&&i.buildAssets(n,{cwd:t,silent:!0}).catch(()=>{})}function S(){let e=y?void 0:{changed:[..._],removed:[...v]},t=b;_.clear(),v.clear(),y=!1,b=!1,x(e,t)}return{assetSrcRoots:c,handleWatchEvent(e,t){if(!g&&!u(t).includes(`/.kickjs/`)){if(e===`unlinkDir`)y=!0,l&&(b=!0);else{if(t.endsWith(`.d.ts`))return;let n=/\.(ts|tsx|mts|cts)$/.test(t),r=f(t);if(!n&&!r)return;r&&l&&(b=!0),n&&(e===`unlink`?(v.add(t),_.delete(t)):(_.add(t),v.delete(t)))}h&&clearTimeout(h),h=setTimeout(S,r)}},runOnce(){g||x(void 0,l)},dispose(){g=!0,h&&clearTimeout(h),h=null}}}function Me(e){return D(e).replace(/-/g,`_`)}function K(e){let t=e.cwd??process.cwd(),n=e.projectRoot??d(t),r=e.pluralize??!1,i=T(e.name),a=E(e.name),o=D(e.name),s=Me(e.name),c={name:e.name,pascal:i,camel:a,kebab:o,snake:s,modulesDir:e.modulesDir??`src/modules`,cwd:t,projectRoot:n,args:e.args??[],flags:e.flags??{}};if(r){let e=O(o);c.pluralKebab=e,c.pluralPascal=T(e),c.pluralCamel=E(e)}return c}function Ne(e,t){return _(e.cwd,t)}async function Pe(e){return import(w(e).href)}const q=/* @__PURE__ */ new Map;async function J(e){let t=q.get(e);if(t)return t;let n=Fe(e);return q.set(e,n),n}async function Fe(e){let t=_(e,`package.json`);if(!v(t))return{generators:[],loaded:[],failed:[]};let n=Ie(JSON.parse(await S(t,`utf-8`))),r=f(_(e,`package.json`)),i=[],a=[],o=[];for(let e of n){let t;try{t=r.resolve(`${e}/package.json`)}catch{continue}let n;try{n=JSON.parse(await S(t,`utf-8`))}catch(t){o.push({source:e,reason:`failed to parse package.json: ${t}`});continue}if(!n.kickjs?.generators)continue;let s=n.kickjs.generators,c=_(m(t),s);if(!v(c)){o.push({source:e,reason:`kickjs.generators points to missing file: ${s}`});continue}let l;try{l=await Pe(c)}catch(t){o.push({source:e,reason:`failed to import manifest: ${t}`});continue}let u=l.default;if(!Array.isArray(u))o.push({source:e,reason:`manifest's default export is not an array of GeneratorSpec`});else{for(let t of u)Le(t)?i.push({source:e,spec:t}):o.push({source:e,reason:`manifest entry is not a valid GeneratorSpec (missing name/files)`});a.push(e)}}return{generators:i,loaded:a,failed:o}}function Ie(e){let t=/* @__PURE__ */ new Set;for(let n of[e.dependencies,e.devDependencies,e.peerDependencies])if(n)for(let e of Object.keys(n))t.add(e);return Array.from(t)}function Le(e){if(!e||typeof e!=`object`)return!1;let t=e;return typeof t.name==`string`&&typeof t.files==`function`}async function Re(e,t=[]){let n=e.cwd??process.cwd(),r=t.find(t=>t.spec.name===e.generatorName);if(r)return Y(r.spec,r.source,e,n);let i=Be(await J(n),e.generatorName);return i?Y(i.spec,i.source,e,n):null}async function ze(e,t=[]){let n=await J(e),r=new Set(t.map(e=>e.spec.name)),i=n.generators.filter(e=>!r.has(e.spec.name));return{generators:[...t,...i],loaded:n.loaded,failed:n.failed}}function Be(e,t){return e.generators.find(e=>e.spec.name===t)}async function Y(e,n,r,i){let a=K({name:r.itemName,args:r.args,flags:r.flags,modulesDir:r.modulesDir,pluralize:r.pluralize,cwd:i,projectRoot:r.projectRoot}),o=await e.files(a),s=[];for(let e of o){let n=Ne(a,e.path);await t(n,e.content),s.push(n)}return{files:s,source:n}}function Ve(e){return e}function He(e){return e}function Ue(e){try{return JSON.parse(y(e,`utf-8`))}catch{return null}}function X(e){try{return y(e,`utf-8`)}catch{return null}}function We(e){let t=e.replace(/\/\*[\s\S]*?\*\//g,``).replace(/(^|[^:"'\\])\/\/.*$/gm,`$1`).replace(/,(\s*[}\]])/g,`$1`);try{return JSON.parse(t)}catch{return null}}function Ge(e){let t=h(e,`tsconfig.json`);if(v(t))return Z(t,/* @__PURE__ */ new Set)}function Z(e,t){if(t.has(e)||t.size>=16)return null;t.add(e);let n=X(e);if(n===null)return null;let r=We(n);if(typeof r!=`object`||!r)return null;let i=Array.isArray(r.extends)?r.extends:typeof r.extends==`string`?[r.extends]:[],a={};for(let n of i){if(typeof n!=`string`)continue;let r=Ke(m(e),n);if(!r)continue;let i=Z(r,t);i&&Object.assign(a,i.compilerOptions)}return r.compilerOptions={...a,...r.compilerOptions},r}function Ke(e,t){if(t.startsWith(`.`)||t.startsWith(`/`))return Q(_(e,t));let n=e;for(;;){let e=Q(h(n,`node_modules`,t));if(e)return e;let r=m(n);if(r===n)break;n=r}return null}function Q(e){if(v(e)){try{if(x(e).isDirectory()){let t=h(e,`tsconfig.json`);return v(t)?t:null}}catch{return null}return e}let t=`${e}.json`;return v(t)?t:null}function qe(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function Je(){let e=process.version,t=Number.parseInt(e.replace(/^v/,``).split(`.`)[0],10);return Number.isNaN(t)||t<20?{name:`Node version`,status:`fail`,message:e,fix:`KickJS requires Node 20 or newer.
1180
- Install a supported version via nvm / fnm / volta.`}:{name:`Node version`,status:`pass`,message:e}}function Ye(e){if(!e.pkg)return{name:`@forinda/kickjs installed`,status:`warn`,message:`no package.json`};let t={...e.pkg.dependencies,...e.pkg.peerDependencies};return t[`@forinda/kickjs`]?{name:`@forinda/kickjs installed`,status:`pass`,message:t[`@forinda/kickjs`]}:{name:`@forinda/kickjs installed`,status:`fail`,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>`."}}function Xe(e){if(!e.pkg)return null;let t={...e.pkg.dependencies,...e.pkg.peerDependencies};return t[`@forinda/kickjs`]&&!t.express?{name:`express installed`,status:`fail`,fix:`\`@forinda/kickjs\` declares \`express\` as a required peer dependency, but your package.json does not include it. Install: ${n(e.cwd)} add express`}:t.express?{name:`express installed`,status:`pass`,message:t.express}:null}const Ze={express:[],fastify:[`fastify`,`@fastify/middie`],h3:[`h3`]};function Qe(e){if(!e.pkg||e.runtime===`express`)return null;let t={...e.pkg.dependencies,...e.pkg.peerDependencies,...e.pkg.devDependencies},r=Ze[e.runtime].filter(e=>!t[e]),i=`runtime engine (${e.runtime})`;return r.length>0?{name:i,status:`fail`,fix:`Resolved runtime '${e.runtime}' is missing engine peer(s): ${r.join(`, `)}.\nInstall: ${n(e.cwd)} add ${r.join(` `)}`}:e.runtime===`h3`&&$e(e.cwd,t.h3)===1?{name:i,status:`fail`,message:`h3 v1`,fix:`This version of @forinda/kickjs runs on h3 v2 only. Upgrade: ${n(e.cwd)} add h3@^2 — no code change for apps that only use KickJS APIs.`}:{name:i,status:`pass`}}function $e(e,t){let n=t;for(let t=_(e);;t=m(t)){try{n=JSON.parse(y(h(t,`node_modules`,`h3`,`package.json`),`utf8`)).version;break}catch{}if(m(t)===t)break}let r=/(\d+)/.exec(n)?.[1];return r===void 0?void 0:Number(r)}function et(e){if(!e.pkg||!tt(e.cwd))return null;let t=u[e.runtime],r=`upload driver (${e.runtime})`;return t.prod?{...e.pkg.dependencies,...e.pkg.peerDependencies,...e.pkg.devDependencies}[t.prod]?{name:r,status:`pass`,message:t.prod}:{name:r,status:`fail`,fix:`This project uses file uploads on the '${e.runtime}' runtime, which needs '${t.prod}'.\nInstall it: kick add upload (or ${n(e.cwd)} add ${t.prod})`}:{name:r,status:`pass`,message:`native multipart`}}const $=2e3;function tt(e){let t=h(e,`src`);if(!v(t))return!1;let n=/@FileUpload\b|\bupload\.(single|array|none)\s*\(/,r=[t],i=0;for(;r.length>0&&i<$;){let e=r.pop(),t;try{t=b(e,{withFileTypes:!0})}catch{continue}for(let a of t){if(i>=$)break;let t=h(e,a.name);if(a.isDirectory())a.name!==`node_modules`&&r.push(t);else if(/\.(ts|tsx|mts|cts)$/.test(a.name)&&(i++,n.test(X(t)??``)))return!0}}return!1}function nt(e){if(!e.pkg)return{name:`reflect-metadata installed`,status:`warn`,message:`no package.json`};let t={...e.pkg.dependencies,...e.pkg.peerDependencies,...e.pkg.devDependencies};return t[`reflect-metadata`]?{name:`reflect-metadata installed`,status:`pass`,message:t[`reflect-metadata`]}:{name:`reflect-metadata installed`,status:`fail`,fix:`KickJS decorators require the reflect-metadata polyfill.\nInstall it: ${n(e.cwd)} add reflect-metadata\nThen import it at the top of src/index.ts:\n\n import 'reflect-metadata'\n // ... rest of bootstrap`}}function rt(e){if(e.tsconfig===void 0)return[{name:`tsconfig.json present`,status:`fail`,fix:"Create a tsconfig.json with `experimentalDecorators: true` and `emitDecoratorMetadata: true`. `kick new` scaffolds one automatically."}];if(e.tsconfig===null)return[{name:`tsconfig.json readable`,status:`warn`,message:`could not parse tsconfig.json (or a config it extends)`,fix:"Check tsconfig.json for a syntax error, and that every path in its `extends` chain resolves. Decorator options could not be verified."}];let t=e.tsconfig.compilerOptions??{},n=[];return n.push(t.experimentalDecorators===!0?{name:`tsconfig: experimentalDecorators`,status:`pass`}:{name:`tsconfig: experimentalDecorators`,status:`fail`,fix:'Add `"experimentalDecorators": true` to compilerOptions in tsconfig.json. Without it, @Service / @Controller / @Get etc. don\'t register any metadata at compile time.'}),n.push(t.emitDecoratorMetadata===!0?{name:`tsconfig: emitDecoratorMetadata`,status:`pass`}:{name:`tsconfig: emitDecoratorMetadata`,status:`fail`,fix:'Add `"emitDecoratorMetadata": true` to compilerOptions in tsconfig.json. The DI container uses this metadata for constructor-parameter injection.'}),n}function it(e){let t=[`src/env.ts`,`src/env/index.ts`,`src/config/env.ts`,`src/config/index.ts`].map(t=>h(e.cwd,t)).filter(e=>v(e)).filter(e=>/\bloadEnv\s*\(/.test(X(e)??``));if(t.length===0)return null;let n=[`src/index.ts`,`src/main.ts`].map(t=>h(e.cwd,t)).find(e=>v(e));if(!n)return{name:`env wiring`,status:`warn`,message:`env-init file exists but no src/index.ts or src/main.ts found`};let r=X(n)??``,i=m(n),a=[];for(let e of t){let t=g(i,e).replace(/\\/g,`/`).replace(/\.ts$/,``),n=t.startsWith(`.`)?t:`./`+t,r=n.replace(/\/index$/,``);a.push(n,r);let o=e.replace(/\\/g,`/`).match(/\/src\/(.+?)(?:\.ts)?$/);if(o){let e=`@/`+o[1],t=e.replace(/\/index$/,``);a.push(e,t)}}let o=-1;for(let e of new Set(a)){let t=RegExp(`^import\\s+(?:.*?from\\s+)?['"]${qe(e)}['"]`,`m`),n=r.match(t);n&&n.index!==void 0&&(o===-1||n.index<o)&&(o=n.index)}let s=r.search(/\bbootstrap\s*\(/),c=t.map(t=>g(e.cwd,t).replace(/\\/g,`/`)).join(`, `);return o===-1?{name:`env wiring`,status:`fail`,message:c,fix:`An env-init file (${c}) calls \`loadEnv(...)\` but \`${g(e.cwd,n).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 ${g(e.cwd,n).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'`}:s!==-1&&o>s?{name:`env wiring`,status:`warn`,message:`env-init imported AFTER bootstrap() — should be before`,fix:`Move the env import above the bootstrap() call so the schema runs before any service reads from ConfigService.`}:{name:`env wiring`,status:`pass`}}function at(e,t=ot){let n=0,r=0,i=[e];for(;i.length>0&&r<t;){let e=i.pop(),a;try{a=b(e,{withFileTypes:!0})}catch{continue}for(let o of a){if(r>=t)break;r++;let a=h(e,o.name);if(o.isDirectory())i.push(a);else try{let e=x(a).mtimeMs;e>n&&(n=e)}catch{}}}return n}const ot=2e3;function st(e){let t=h(e.cwd,`.kickjs`,`types`);if(!v(t))return null;let n=at(t);if(n===0)return null;let r=Date.now()-n,i=Math.floor(r/6e4);return i>60?{name:`typegen freshness`,status:`warn`,message:`last updated ${i} minutes ago`,fix:"Re-run `kick typegen` (or `kick dev`, which runs it on every reload) so generated types match the current code."}:{name:`typegen freshness`,status:`pass`,message:i===0?`just now`:`${i}m ago`}}const ct=[`vitest.config.ts`,`vitest.config.js`,`vitest.config.mts`,`vite.config.ts`,`jest.config.ts`,`jest.config.js`],lt=[`.env.test`,`.env.test.local`],ut=[`.env`,`.env.local`];function dt(e){let t=e.pkg?.scripts;if(t&&typeof t==`object`)for(let[e,n]of Object.entries(t)){if(!e.startsWith(`test`)||typeof n!=`string`)continue;let t=/KICKJS_ENV_FILE=(\S+)/.exec(n);if(t?.[1])return t[1]}return process.env.KICKJS_ENV_FILE??null}function ft(e){if(!ct.some(t=>v(h(e.cwd,t))))return null;let t=ut.filter(t=>v(h(e.cwd,t)));if(t.length===0)return null;let n=lt.filter(t=>v(h(e.cwd,t)));if(n.length>0){let t=dt(e);return t&&t!==`off`&&t!==`none`?{name:`test env isolation`,status:`warn`,message:`${n.join(`, `)} present, but KICKJS_ENV_FILE=${t} overrides it`,fix:`KICKJS_ENV_FILE is checked before test-mode isolation, so ${n[0]} is\nignored and "${t}" is read instead. Drop the override to use the\ntest files, or set KICKJS_ENV_FILE=off if env comes from the runner.`}:{name:`test env isolation`,status:`pass`,message:`${n.join(`, `)} present`}}return{name:`test env isolation`,status:`warn`,message:`${t.join(`, `)} present, no .env.test`,fix:`Under NODE_ENV=test (or vitest's VITEST) KickJS reads .env.test / .env.test.local
1179
+ `);super(`kick typegen --check: ${e.length} generated file(s) are out of date:\n${t}\n Run \`kick typegen\` and commit the result.`),this.name=`TypegenDriftError`,this.drifted=e}};function Oe(e){return e}function ke(e){let t=/* @__PURE__ */ new Map;return{report(n,r){let i=r instanceof Error?r.message:String(r);t.get(n)!==i&&(t.set(n,i),e(` kick typegen: ${n} pass failed (${i}) — types in .kickjs/types may be stale`))},clear(e){t.delete(e)}}}const Ae=`__kickjs_typegen_owner`;function je(e){let{cwd:t,config:n}=e,r=e.debounceMs??100,i=e.pipeline??{runTypegen:async e=>(await import(`./typegen-B3mw3F07.mjs`).then(e=>e.n)).runTypegen(e),runAllPluginTypegens:async e=>(await import(`./run-plugins-B8vUgyD4.mjs`).then(e=>e.n)).runAllPluginTypegens(e),writeTypegenArtifacts:async(e,t,n)=>(await import(`./typegen-B3mw3F07.mjs`).then(e=>e.n)).writeTypegenArtifacts(e,t,n),buildAssets:async(e,t)=>(await import(`./build-eFEdQsTj.mjs`).then(e=>e.n)).buildAssets(e,t)},a=n?.typegen?.schemaValidator??`zod`,o=n?.typegen?.envFile,s=p.resolve(t,n?.typegen?.outDir??`.kickjs/types`),c=n?.assetMap?Object.values(n.assetMap).map(e=>e?.src).filter(e=>typeof e==`string`&&e.length>0).map(e=>p.resolve(t,e)):[],l=!!n?.assetMap&&Object.keys(n.assetMap).length>0,u=e=>e.replaceAll(`\\`,`/`),d=c.map(u),f=e=>{let t=u(e);return d.some(e=>t===e||t.startsWith(`${e}/`))},m=ke(e.emitWarning),h=null,g=!1,_=/* @__PURE__ */ new Set,v=/* @__PURE__ */ new Set,y=!1,b=!1;function x(r,c){i.runTypegen({cwd:t,silent:!0,allowDuplicates:!0,schemaValidator:a,envFile:o,srcDir:n?.typegen?.srcDir,outDir:n?.typegen?.outDir,assetMap:n?.assetMap,changedFiles:r,runPlugins:!1}).then(()=>m.clear(`scan`)).catch(e=>m.report(`scan`,e)),i.runAllPluginTypegens({cwd:t,config:n,silent:!0,changedFiles:r,watch:!0}).then(e=>i.writeTypegenArtifacts(s,e,!0)).then(()=>m.clear(`plugins`)).catch(e=>m.report(`plugins`,e)).finally(()=>e.onPassComplete?.()),c&&n&&i.buildAssets(n,{cwd:t,silent:!0}).catch(()=>{})}function S(){let e=y?void 0:{changed:[..._],removed:[...v]},t=b;_.clear(),v.clear(),y=!1,b=!1,x(e,t)}return{assetSrcRoots:c,handleWatchEvent(e,t){if(!g&&!u(t).includes(`/.kickjs/`)){if(e===`unlinkDir`)y=!0,l&&(b=!0);else{if(t.endsWith(`.d.ts`))return;let n=/\.(ts|tsx|mts|cts)$/.test(t),r=f(t);if(!n&&!r)return;r&&l&&(b=!0),n&&(e===`unlink`?(v.add(t),_.delete(t)):(_.add(t),v.delete(t)))}h&&clearTimeout(h),h=setTimeout(S,r)}},runOnce(){g||x(void 0,l)},dispose(){g=!0,h&&clearTimeout(h),h=null}}}function Me(e){return D(e).replace(/-/g,`_`)}function K(e){let t=e.cwd??process.cwd(),n=e.projectRoot??d(t),r=e.pluralize??!1,i=T(e.name),a=E(e.name),o=D(e.name),s=Me(e.name),c={name:e.name,pascal:i,camel:a,kebab:o,snake:s,modulesDir:e.modulesDir??`src/modules`,cwd:t,projectRoot:n,args:e.args??[],flags:e.flags??{}};if(r){let e=O(o);c.pluralKebab=e,c.pluralPascal=T(e),c.pluralCamel=E(e)}return c}function Ne(e,t){return _(e.cwd,t)}async function Pe(e){return import(w(e).href)}const q=/* @__PURE__ */ new Map;async function J(e){let t=q.get(e);if(t)return t;let n=Fe(e);return q.set(e,n),n}async function Fe(e){let t=_(e,`package.json`);if(!v(t))return{generators:[],loaded:[],failed:[]};let n=Ie(JSON.parse(await S(t,`utf-8`))),r=f(_(e,`package.json`)),i=[],a=[],o=[];for(let e of n){let t;try{t=r.resolve(`${e}/package.json`)}catch{continue}let n;try{n=JSON.parse(await S(t,`utf-8`))}catch(t){o.push({source:e,reason:`failed to parse package.json: ${t}`});continue}if(!n.kickjs?.generators)continue;let s=n.kickjs.generators,c=_(m(t),s);if(!v(c)){o.push({source:e,reason:`kickjs.generators points to missing file: ${s}`});continue}let l;try{l=await Pe(c)}catch(t){o.push({source:e,reason:`failed to import manifest: ${t}`});continue}let u=l.default;if(!Array.isArray(u))o.push({source:e,reason:`manifest's default export is not an array of GeneratorSpec`});else{for(let t of u)Le(t)?i.push({source:e,spec:t}):o.push({source:e,reason:`manifest entry is not a valid GeneratorSpec (missing name/files)`});a.push(e)}}return{generators:i,loaded:a,failed:o}}function Ie(e){let t=/* @__PURE__ */ new Set;for(let n of[e.dependencies,e.devDependencies,e.peerDependencies])if(n)for(let e of Object.keys(n))t.add(e);return Array.from(t)}function Le(e){if(!e||typeof e!=`object`)return!1;let t=e;return typeof t.name==`string`&&typeof t.files==`function`}async function Re(e,t=[]){let n=e.cwd??process.cwd(),r=t.find(t=>t.spec.name===e.generatorName);if(r)return Y(r.spec,r.source,e,n);let i=Be(await J(n),e.generatorName);return i?Y(i.spec,i.source,e,n):null}async function ze(e,t=[]){let n=await J(e),r=new Set(t.map(e=>e.spec.name)),i=n.generators.filter(e=>!r.has(e.spec.name));return{generators:[...t,...i],loaded:n.loaded,failed:n.failed}}function Be(e,t){return e.generators.find(e=>e.spec.name===t)}async function Y(t,n,r,i){let a=K({name:r.itemName,args:r.args,flags:r.flags,modulesDir:r.modulesDir,pluralize:r.pluralize,cwd:i,projectRoot:r.projectRoot}),o=await t.files(a),s=[];for(let t of o){let n=Ne(a,t.path);await e(n,t.content),s.push(n)}return{files:s,source:n}}function Ve(e){return e}function He(e){return e}function Ue(e){try{return JSON.parse(y(e,`utf-8`))}catch{return null}}function X(e){try{return y(e,`utf-8`)}catch{return null}}function We(e){let t=e.replace(/\/\*[\s\S]*?\*\//g,``).replace(/(^|[^:"'\\])\/\/.*$/gm,`$1`).replace(/,(\s*[}\]])/g,`$1`);try{return JSON.parse(t)}catch{return null}}function Ge(e){let t=h(e,`tsconfig.json`);if(v(t))return Z(t,/* @__PURE__ */ new Set)}function Z(e,t){if(t.has(e)||t.size>=16)return null;t.add(e);let n=X(e);if(n===null)return null;let r=We(n);if(typeof r!=`object`||!r)return null;let i=Array.isArray(r.extends)?r.extends:typeof r.extends==`string`?[r.extends]:[],a={};for(let n of i){if(typeof n!=`string`)continue;let r=Ke(m(e),n);if(!r)continue;let i=Z(r,t);i&&Object.assign(a,i.compilerOptions)}return r.compilerOptions={...a,...r.compilerOptions},r}function Ke(e,t){if(t.startsWith(`.`)||t.startsWith(`/`))return Q(_(e,t));let n=e;for(;;){let e=Q(h(n,`node_modules`,t));if(e)return e;let r=m(n);if(r===n)break;n=r}return null}function Q(e){if(v(e)){try{if(x(e).isDirectory()){let t=h(e,`tsconfig.json`);return v(t)?t:null}}catch{return null}return e}let t=`${e}.json`;return v(t)?t:null}function qe(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function Je(){let e=process.version,t=Number.parseInt(e.replace(/^v/,``).split(`.`)[0],10);return Number.isNaN(t)||t<20?{name:`Node version`,status:`fail`,message:e,fix:`KickJS requires Node 20 or newer.
1180
+ Install a supported version via nvm / fnm / volta.`}:{name:`Node version`,status:`pass`,message:e}}function Ye(e){if(!e.pkg)return{name:`@forinda/kickjs installed`,status:`warn`,message:`no package.json`};let t={...e.pkg.dependencies,...e.pkg.peerDependencies};return t[`@forinda/kickjs`]?{name:`@forinda/kickjs installed`,status:`pass`,message:t[`@forinda/kickjs`]}:{name:`@forinda/kickjs installed`,status:`fail`,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>`."}}function Xe(e){if(!e.pkg)return null;let t={...e.pkg.dependencies,...e.pkg.peerDependencies};return t[`@forinda/kickjs`]&&!t.express?{name:`express installed`,status:`fail`,fix:`\`@forinda/kickjs\` declares \`express\` as a required peer dependency, but your package.json does not include it. Install: ${s(e.cwd)} add express`}:t.express?{name:`express installed`,status:`pass`,message:t.express}:null}const Ze={express:[],fastify:[`fastify`,`@fastify/middie`],h3:[`h3`]};function Qe(e){if(!e.pkg||e.runtime===`express`)return null;let t={...e.pkg.dependencies,...e.pkg.peerDependencies,...e.pkg.devDependencies},n=Ze[e.runtime].filter(e=>!t[e]),r=`runtime engine (${e.runtime})`;return n.length>0?{name:r,status:`fail`,fix:`Resolved runtime '${e.runtime}' is missing engine peer(s): ${n.join(`, `)}.\nInstall: ${s(e.cwd)} add ${n.join(` `)}`}:e.runtime===`h3`&&$e(e.cwd,t.h3)===1?{name:r,status:`fail`,message:`h3 v1`,fix:`This version of @forinda/kickjs runs on h3 v2 only. Upgrade: ${s(e.cwd)} add h3@^2 — no code change for apps that only use KickJS APIs.`}:{name:r,status:`pass`}}function $e(e,t){let n=t;for(let t=_(e);;t=m(t)){try{n=JSON.parse(y(h(t,`node_modules`,`h3`,`package.json`),`utf8`)).version;break}catch{}if(m(t)===t)break}let r=/(\d+)/.exec(n)?.[1];return r===void 0?void 0:Number(r)}function et(e){if(!e.pkg||!tt(e.cwd))return null;let t=u[e.runtime],n=`upload driver (${e.runtime})`;return t.prod?{...e.pkg.dependencies,...e.pkg.peerDependencies,...e.pkg.devDependencies}[t.prod]?{name:n,status:`pass`,message:t.prod}:{name:n,status:`fail`,fix:`This project uses file uploads on the '${e.runtime}' runtime, which needs '${t.prod}'.\nInstall it: kick add upload (or ${s(e.cwd)} add ${t.prod})`}:{name:n,status:`pass`,message:`native multipart`}}const $=2e3;function tt(e){let t=h(e,`src`);if(!v(t))return!1;let n=/@FileUpload\b|\bupload\.(single|array|none)\s*\(/,r=[t],i=0;for(;r.length>0&&i<$;){let e=r.pop(),t;try{t=b(e,{withFileTypes:!0})}catch{continue}for(let a of t){if(i>=$)break;let t=h(e,a.name);if(a.isDirectory())a.name!==`node_modules`&&r.push(t);else if(/\.(ts|tsx|mts|cts)$/.test(a.name)&&(i++,n.test(X(t)??``)))return!0}}return!1}function nt(e){if(!e.pkg)return{name:`reflect-metadata installed`,status:`warn`,message:`no package.json`};let t={...e.pkg.dependencies,...e.pkg.peerDependencies,...e.pkg.devDependencies};return t[`reflect-metadata`]?{name:`reflect-metadata installed`,status:`pass`,message:t[`reflect-metadata`]}:{name:`reflect-metadata installed`,status:`fail`,fix:`KickJS decorators require the reflect-metadata polyfill.\nInstall it: ${s(e.cwd)} add reflect-metadata\nThen import it at the top of src/index.ts:\n\n import 'reflect-metadata'\n // ... rest of bootstrap`}}function rt(e){if(e.tsconfig===void 0)return[{name:`tsconfig.json present`,status:`fail`,fix:"Create a tsconfig.json with `experimentalDecorators: true` and `emitDecoratorMetadata: true`. `kick new` scaffolds one automatically."}];if(e.tsconfig===null)return[{name:`tsconfig.json readable`,status:`warn`,message:`could not parse tsconfig.json (or a config it extends)`,fix:"Check tsconfig.json for a syntax error, and that every path in its `extends` chain resolves. Decorator options could not be verified."}];let t=e.tsconfig.compilerOptions??{},n=[];return n.push(t.experimentalDecorators===!0?{name:`tsconfig: experimentalDecorators`,status:`pass`}:{name:`tsconfig: experimentalDecorators`,status:`fail`,fix:'Add `"experimentalDecorators": true` to compilerOptions in tsconfig.json. Without it, @Service / @Controller / @Get etc. don\'t register any metadata at compile time.'}),n.push(t.emitDecoratorMetadata===!0?{name:`tsconfig: emitDecoratorMetadata`,status:`pass`}:{name:`tsconfig: emitDecoratorMetadata`,status:`fail`,fix:'Add `"emitDecoratorMetadata": true` to compilerOptions in tsconfig.json. The DI container uses this metadata for constructor-parameter injection.'}),n}function it(e){let t=[`src/env.ts`,`src/env/index.ts`,`src/config/env.ts`,`src/config/index.ts`].map(t=>h(e.cwd,t)).filter(e=>v(e)).filter(e=>/\bloadEnv\s*\(/.test(X(e)??``));if(t.length===0)return null;let n=[`src/index.ts`,`src/main.ts`].map(t=>h(e.cwd,t)).find(e=>v(e));if(!n)return{name:`env wiring`,status:`warn`,message:`env-init file exists but no src/index.ts or src/main.ts found`};let r=X(n)??``,i=m(n),a=[];for(let e of t){let t=g(i,e).replace(/\\/g,`/`).replace(/\.ts$/,``),n=t.startsWith(`.`)?t:`./`+t,r=n.replace(/\/index$/,``);a.push(n,r);let o=e.replace(/\\/g,`/`).match(/\/src\/(.+?)(?:\.ts)?$/);if(o){let e=`@/`+o[1],t=e.replace(/\/index$/,``);a.push(e,t)}}let o=-1;for(let e of new Set(a)){let t=RegExp(`^import\\s+(?:.*?from\\s+)?['"]${qe(e)}['"]`,`m`),n=r.match(t);n&&n.index!==void 0&&(o===-1||n.index<o)&&(o=n.index)}let s=r.search(/\bbootstrap\s*\(/),c=t.map(t=>g(e.cwd,t).replace(/\\/g,`/`)).join(`, `);return o===-1?{name:`env wiring`,status:`fail`,message:c,fix:`An env-init file (${c}) calls \`loadEnv(...)\` but \`${g(e.cwd,n).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 ${g(e.cwd,n).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'`}:s!==-1&&o>s?{name:`env wiring`,status:`warn`,message:`env-init imported AFTER bootstrap() — should be before`,fix:`Move the env import above the bootstrap() call so the schema runs before any service reads from ConfigService.`}:{name:`env wiring`,status:`pass`}}function at(e,t=ot){let n=0,r=0,i=[e];for(;i.length>0&&r<t;){let e=i.pop(),a;try{a=b(e,{withFileTypes:!0})}catch{continue}for(let o of a){if(r>=t)break;r++;let a=h(e,o.name);if(o.isDirectory())i.push(a);else try{let e=x(a).mtimeMs;e>n&&(n=e)}catch{}}}return n}const ot=2e3;function st(e){let t=h(e.cwd,`.kickjs`,`types`);if(!v(t))return null;let n=at(t);if(n===0)return null;let r=Date.now()-n,i=Math.floor(r/6e4);return i>60?{name:`typegen freshness`,status:`warn`,message:`last updated ${i} minutes ago`,fix:"Re-run `kick typegen` (or `kick dev`, which runs it on every reload) so generated types match the current code."}:{name:`typegen freshness`,status:`pass`,message:i===0?`just now`:`${i}m ago`}}const ct=[`vitest.config.ts`,`vitest.config.js`,`vitest.config.mts`,`vite.config.ts`,`jest.config.ts`,`jest.config.js`],lt=[`.env.test`,`.env.test.local`],ut=[`.env`,`.env.local`];function dt(e){let t=e.pkg?.scripts;if(t&&typeof t==`object`)for(let[e,n]of Object.entries(t)){if(!e.startsWith(`test`)||typeof n!=`string`)continue;let t=/KICKJS_ENV_FILE=(\S+)/.exec(n);if(t?.[1])return t[1]}return process.env.KICKJS_ENV_FILE??null}function ft(e){if(!ct.some(t=>v(h(e.cwd,t))))return null;let t=ut.filter(t=>v(h(e.cwd,t)));if(t.length===0)return null;let n=lt.filter(t=>v(h(e.cwd,t)));if(n.length>0){let t=dt(e);return t&&t!==`off`&&t!==`none`?{name:`test env isolation`,status:`warn`,message:`${n.join(`, `)} present, but KICKJS_ENV_FILE=${t} overrides it`,fix:`KICKJS_ENV_FILE is checked before test-mode isolation, so ${n[0]} is\nignored and "${t}" is read instead. Drop the override to use the\ntest files, or set KICKJS_ENV_FILE=off if env comes from the runner.`}:{name:`test env isolation`,status:`pass`,message:`${n.join(`, `)} present`}}return{name:`test env isolation`,status:`warn`,message:`${t.join(`, `)} present, no .env.test`,fix:`Under NODE_ENV=test (or vitest's VITEST) KickJS reads .env.test / .env.test.local
1181
1181
  INSTEAD of ${t.join(` and `)} — no layering, no fallback. Without one of\nthose files, every var your test config does not pin resolves from your
1182
1182
  development env — including DATABASE_URL and any third-party credentials.
1183
1183
 
1184
1184
  Create .env.test (cp .env.test.example .env.test, if the project has one)
1185
1185
  holding the whole environment the suite should run against
1186
1186
  (a var you leave out is then missing, not inherited), or set KICKJS_ENV_FILE=off
1187
- and supply env from the test runner.`}}const pt=[()=>Je(),Ye,Xe,Qe,et,nt,rt,it,ft,st];async function mt(e,t={}){let n={cwd:e,pkg:Ue(h(e,`package.json`)),tsconfig:Ge(e),runtime:t.runtime??`express`},r=[...pt,...t.extraChecks??[]],i=[];for(let e of r){let t;try{t=await e(n)}catch(t){i.push({name:e.name||`doctor check`,status:`fail`,message:t instanceof Error?t.message:String(t)});continue}t!=null&&(Array.isArray(t)?i.push(...t):i.push(t))}return i}function ht(e){switch(e){case`pass`:return s.green(`✔`);case`warn`:return s.yellow(`⚠`);case`fail`:return s.red(`✖`)}}function gt(e){let t=ht(e.status),n=e.message?` ${s.dim(`(${e.message})`)}`:``;return`${t} ${e.name}${n}`}function _t(e){return e.split(`
1188
- `).map(e=>` ${s.dim(`→`)} ${e}`).join(`
1189
- `)}function vt(e){return e?.doctor?.checks??[]}function yt(e){e.command(`doctor`).description(`Pre-flight checks for your KickJS project (dev environment health)`).action(async()=>{let e=process.cwd(),t=vt(await c(e)),n=await l(e);i(`KickJS Doctor`);let o=await mt(e,{extraChecks:t,runtime:n});for(let e of o)a.message(gt(e)),e.fix&&e.status!==`pass`&&a.message(_t(e.fix));let u=o.filter(e=>e.status===`pass`).length,d=o.filter(e=>e.status===`warn`).length,f=o.filter(e=>e.status===`fail`).length,p=[s.green(`${u} passed`),d>0?s.yellow(`${d} warning${d===1?``:`s`}`):`${d} warnings`,f>0?s.red(`${f} error${f===1?``:`s`}`):`${f} errors`].join(`, `);f>0?(r(`${p} — fix the errors above before running the app`),process.exit(1)):r(d>0?`${p} — review the warnings`:s.green(`${p} — your environment looks good`))})}export{A,ge as C,I as D,L as E,T as F,k as M,E as N,N as O,D as P,V as S,R as T,xe as _,Re as a,W as b,Ae as c,Oe as d,Ee as f,Se as g,Ce as h,ze as i,O as j,M as k,je as l,we as m,Ve as n,K as o,Te as p,yt as r,ee as s,He as t,De as u,G as v,he as w,z as x,ye as y};
1190
- //# sourceMappingURL=doctor-DKtiOCf2.mjs.map
1187
+ and supply env from the test runner.`}}const pt=[()=>Je(),Ye,Xe,Qe,et,nt,rt,it,ft,st];async function mt(e,t={}){let n={cwd:e,pkg:Ue(h(e,`package.json`)),tsconfig:Ge(e),runtime:t.runtime??`express`},r=[...pt,...t.extraChecks??[]],i=[];for(let e of r){let t;try{t=await e(n)}catch(t){i.push({name:e.name||`doctor check`,status:`fail`,message:t instanceof Error?t.message:String(t)});continue}t!=null&&(Array.isArray(t)?i.push(...t):i.push(t))}return i}function ht(e){switch(e){case`pass`:return o.green(`✔`);case`warn`:return o.yellow(`⚠`);case`fail`:return o.red(`✖`)}}function gt(e){let t=ht(e.status),n=e.message?` ${o.dim(`(${e.message})`)}`:``;return`${t} ${e.name}${n}`}function _t(e){return e.split(`
1188
+ `).map(e=>` ${o.dim(`→`)} ${e}`).join(`
1189
+ `)}function vt(e){return e?.doctor?.checks??[]}function yt(e){e.command(`doctor`).description(`Pre-flight checks for your KickJS project (dev environment health)`).action(async()=>{let e=process.cwd(),r=vt(await c(e)),i=await l(e);t(`KickJS Doctor`);let s=await mt(e,{extraChecks:r,runtime:i});for(let e of s)a.message(gt(e)),e.fix&&e.status!==`pass`&&a.message(_t(e.fix));let u=s.filter(e=>e.status===`pass`).length,d=s.filter(e=>e.status===`warn`).length,f=s.filter(e=>e.status===`fail`).length,p=[o.green(`${u} passed`),d>0?o.yellow(`${d} warning${d===1?``:`s`}`):`${d} warnings`,f>0?o.red(`${f} error${f===1?``:`s`}`):`${f} errors`].join(`, `);f>0?(n(`${p} — fix the errors above before running the app`),process.exit(1)):n(d>0?`${p} — review the warnings`:o.green(`${p} — your environment looks good`))})}export{A,ge as C,I as D,L as E,T as F,k as M,E as N,N as O,D as P,V as S,R as T,xe as _,Re as a,W as b,Ae as c,Oe as d,Ee as f,Se as g,Ce as h,ze as i,O as j,M as k,je as l,we as m,Ve as n,K as o,Te as p,yt as r,ee as s,He as t,De as u,G as v,he as w,z as x,ye as y};
1190
+ //# sourceMappingURL=doctor-BDlJqXWX.mjs.map