@forinda/kickjs-cli 5.11.1 → 6.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-docs-hbOXsAAh.mjs +12 -0
- package/dist/agent-docs-hbOXsAAh.mjs.map +1 -0
- package/dist/{builtins-BL1BhYEv.mjs → builtins-Dyk9a-mv.mjs} +2 -2
- package/dist/cli.mjs +151 -1376
- package/dist/config-DSpcRefL.mjs +13 -0
- package/dist/config-DSpcRefL.mjs.map +1 -0
- package/dist/doctor-559QZlHi.mjs +1221 -0
- package/dist/doctor-559QZlHi.mjs.map +1 -0
- package/dist/index.d.mts +663 -853
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{plugin-C4hfxiPw.mjs → plugin-DK01q7wy.mjs} +3 -3
- package/dist/{plugin-C4hfxiPw.mjs.map → plugin-DK01q7wy.mjs.map} +1 -1
- package/dist/{project-docs-CfB-KVN5.mjs → project-docs-CrfNQIZA.mjs} +6 -36
- package/dist/project-docs-CrfNQIZA.mjs.map +1 -0
- package/dist/{project-root-CDYKLnfG.mjs → project-root-BdTe6EpE.mjs} +3 -3
- package/dist/{project-root-CDYKLnfG.mjs.map → project-root-BdTe6EpE.mjs.map} +1 -1
- package/dist/{rolldown-runtime-CeWwRE8g.mjs → rolldown-runtime-CoN4EDcd.mjs} +1 -1
- package/dist/run-plugins-BAYoDnFI.mjs +636 -0
- package/dist/run-plugins-BAYoDnFI.mjs.map +1 -0
- package/dist/typegen-CwtvFZ0t.mjs +114 -0
- package/dist/typegen-CwtvFZ0t.mjs.map +1 -0
- package/dist/types-BKKzf_bU.mjs +11 -0
- package/package.json +13 -13
- package/dist/agent-docs-CXqrGZLl.mjs +0 -12
- package/dist/agent-docs-CXqrGZLl.mjs.map +0 -1
- package/dist/config-Cf8GU8CG.mjs +0 -13
- package/dist/config-Cf8GU8CG.mjs.map +0 -1
- package/dist/doctor-Dl709LzL.mjs +0 -2076
- package/dist/doctor-Dl709LzL.mjs.map +0 -1
- package/dist/project-docs-CfB-KVN5.mjs.map +0 -1
- package/dist/run-plugins-M_WVt-7a.mjs +0 -976
- package/dist/run-plugins-M_WVt-7a.mjs.map +0 -1
- package/dist/typegen-CezcLjMb.mjs +0 -114
- package/dist/typegen-CezcLjMb.mjs.map +0 -1
- package/dist/types-DvYczI2m.mjs +0 -12
- package/dist/types-DvYczI2m.mjs.map +0 -1
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @forinda/kickjs-cli v5.11.1
|
|
3
|
-
*
|
|
4
|
-
* Copyright (c) Felix Orinda
|
|
5
|
-
*
|
|
6
|
-
* This source code is licensed under the MIT license found in the
|
|
7
|
-
* LICENSE file in the root directory of this source tree.
|
|
8
|
-
*
|
|
9
|
-
* @license MIT
|
|
10
|
-
*/
|
|
11
|
-
import{t as e}from"./rolldown-runtime-CeWwRE8g.mjs";import{basename as t,dirname as n,join as r,relative as i,resolve as a,sep as o}from"node:path";import{statSync as s}from"node:fs";import{mkdir as c,readFile as l,readdir as u,stat as d,unlink as f,writeFile as p}from"node:fs/promises";import{globSync as m}from"glob";import{groupAssetKeys as h}from"@forinda/kickjs";const g=[`Service`,`Controller`,`Repository`,`Injectable`,`Component`,`Module`],_=[`.ts`,`.tsx`,`.mts`,`.cts`],v=[`node_modules`,`.kickjs`,`dist`,`build`,`.test.`,`.spec.`,`.d.ts`],y=new RegExp(String.raw`@(${g.join(`|`)})\s*\([^)]*\)`+String.raw`(?:\s*@[A-Z]\w*(?:\s*\([^)]*\))?)*`+String.raw`\s*export\s+(default\s+)?(?:abstract\s+)?class\s+(\w+)`,`g`),b=new RegExp(String.raw`export\s+(default\s+)?(?:abstract\s+)?class\s+(\w+)`+String.raw`(?:\s+extends\s+\w+(?:<[^>]*>)?)?`+String.raw`\s+implements\s+[^{]*\bAppModule\b`,`g`),x=/(?:export\s+)?const\s+(\w+)\s*(?::\s*[^=]+)?=\s*createToken\s*(?:<[^>]*>)?\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g,S=/createToken\s*(?:<[^>]*>)?\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g,C=/@Inject\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g,w=/\b(defineAdapter|definePlugin)\s*(?:<[^>]*>)?\s*\(/g,T=/\b(?:defineContextDecorator|defineHttpContextDecorator)\s*(?:\.withParams\s*<(?:[^<>]|<[^<>]*>)*>\s*\(\s*\))?\s*(?:<(?:[^<>]|<[^<>]*>)*>)?\s*\(/g,ee=new RegExp(String.raw`export\s+(?:default\s+)?(?:abstract\s+)?class\s+(\w+)`+String.raw`(?:\s+extends\s+\w+(?:<[^>]*>)?)?`+String.raw`\s+implements\s+[^{]*\bAppAdapter\b`,`g`),te=/\bname\s*(?::\s*[^=]+)?=\s*['"`]([^'"`]+)['"`]/,E=/\bdefineAugmentation\s*\(\s*['"`]([^'"`]+)['"`]\s*(,\s*\{)?/g,D=new RegExp(String.raw`@(${[`Get`,`Post`,`Put`,`Delete`,`Patch`].join(`|`)})\s*\(`,`g`);function O(e,t){let n=1;for(let r=t+1;r<e.length;r++){let t=e[r];if(t===`(`)n++;else if(t===`)`&&(n--,n===0))return r}return-1}function ne(e,t){let n=t;for(;n<e.length;){for(;n<e.length&&/\s/.test(e[n]);)n++;if(e[n]!==`@`)break;let t=e.slice(n).match(/^@([A-Z]\w*)/);if(!t)break;for(n+=t[0].length;n<e.length&&/\s/.test(e[n]);)n++;if(e[n]===`(`){let t=O(e,n);if(t<0)return null;n=t+1}}for(;n<e.length&&/\s/.test(e[n]);)n++;for(let t of[`public`,`private`,`protected`])if(e.slice(n,n+t.length)===t&&/\s/.test(e.charAt(n+t.length))){for(n+=t.length;n<e.length&&/\s/.test(e[n]);)n++;break}if(e.slice(n,n+5)===`async`&&/\s/.test(e.charAt(n+5)))for(n+=5;n<e.length&&/\s/.test(e[n]);)n++;let r=e.slice(n).match(/^([a-zA-Z_]\w*)\s*\(/);return r?{methodName:r[1],endPos:n+r[0].length}:null}function re(e){return(e.match(/:([a-zA-Z_]\w*)/g)??[]).map(e=>e.slice(1))}function ie(e,t){let n=e.endsWith(`/`)?e.slice(0,-1):e;return!t||t===`/`?n||`/`:n+(t.startsWith(`/`)?t:`/`+t)||`/`}const k=/\b(?:public\s+|private\s+|protected\s+)?routes\s*\([^)]*\)\s*(?::\s*[A-Za-z_][\w<>[\]\s,|]*\s*)?\{/g,A=/\bpath\s*:\s*['"`]([^'"`]*)['"`]/g,ae=/\bcontroller\s*:\s*([A-Z]\w*)\b/g,j=/\bimport\.meta\.glob\s*\(/g;function oe(e){let t=[];for(j.lastIndex=0;j.exec(e)!==null;){let n=j.lastIndex-1,r=O(e,n);if(r<0)continue;let i=e.slice(n+1,r),a=/['"`]([^'"`]+)['"`]/g,o;for(;(o=a.exec(i))!==null;)t.push(o[1])}return t}function se(e){let t=e.replace(/[.+^$()|[\]\\]/g,`\\$&`).replace(/\?/g,`.`).replace(/\*\*\//g,`___DOUBLESTAR_SLASH___`).replace(/\*\*/g,`___DOUBLESTAR___`).replace(/\*/g,`[^/]*`).replace(/___DOUBLESTAR_SLASH___/g,`(?:.+/)?`).replace(/___DOUBLESTAR___/g,`.*`);return RegExp(`^`+t+`$`)}function ce(e,t){let n=e.startsWith(`./`)?e:`./`+e,r=!1;for(let e of t){let t=e.startsWith(`!`);se(t?e.slice(1):e).test(n)&&(r=!t)}return r}function le(e){let t=[];k.lastIndex=0;let n;for(;(n=k.exec(e))!==null;){let r=e.indexOf(`{`,n.index+n[0].length-1);if(r<0)continue;let i=z(e,r);if(i<0)continue;let a=e.slice(r+1,i),o=[];A.lastIndex=0;let s;for(;(s=A.exec(a))!==null;)o.push(s[1]??``);let c=[];ae.lastIndex=0;let l;for(;(l=ae.exec(a))!==null;)c.push(l[1]);let u=Math.min(o.length,c.length);for(let e=0;e<u;e++)t.push({controller:c[e],mountPath:o[e]})}return t}function M(e,t){let n=new RegExp(String.raw`\b${t}\s*:\s*([A-Za-z_$][\w$]*)`,`g`).exec(e);return n?n[1]:null}function N(e,t){let n=new RegExp(String.raw`import\s*(?:type\s+)?\{[^}]*\b${t}\b[^}]*\}\s*from\s*['"\`]([^'"\`]+)['"\`]`).exec(e);if(n)return n[1];let r=new RegExp(String.raw`import\s+(?:type\s+)?${t}\s+from\s*['"\`]([^'"\`]+)['"\`]`).exec(e);if(r)return r[1];let i=new RegExp(String.raw`import\s*\*\s*as\s+${t}\s+from\s*['"\`]([^'"\`]+)['"\`]`).exec(e);return i?i[1]:new RegExp(String.raw`(?:^|\n)\s*(?:export\s+)?const\s+${t}\b`).test(e)?``:null}function ue(e,t){let n=/@ApiQueryParams\s*\(\s*([\s\S]*?)\s*\)\s*$/.exec(e);if(!n){let n=/@ApiQueryParams\s*\(([\s\S]*?)\)/.exec(e);return n?P(n[1].trim(),t):null}return P(n[1].trim(),t)}function P(e,t){if(e.startsWith(`{`))return I(e);let n=/^([A-Za-z_]\w*)/.exec(e);if(n){let e=n[1],r=new RegExp(String.raw`const\s+${e}\s*(?::\s*[^=]+)?=\s*(\{[\s\S]*?\n\})`,`m`).exec(t);if(r)return I(r[1])}return{filterable:[],sortable:[],searchable:[]}}function F(e,t){let n=new RegExp(String.raw`${t}\s*:\s*\[([\s\S]*?)\]`).exec(e);return n?Array.from(n[1].matchAll(/['"`]([^'"`]+)['"`]/g)).map(e=>e[1]):[]}function I(e){return{filterable:F(e,`filterable`),sortable:F(e,`sortable`),searchable:F(e,`searchable`)}}async function L(e,t){let n=t.extensions??_,a=t.exclude??v,o=[],s;try{s=await u(e,{withFileTypes:!0,encoding:`utf-8`})}catch{return o}for(let c of s){let s=r(e,c.name),l=i(t.cwd,s);a.some(e=>l.includes(e))||(c.isDirectory()?o.push(...await L(s,t)):c.isFile()&&n.some(e=>c.name.endsWith(e))&&o.push(s))}return o}function R(e,t){return i(t,e).split(o).join(`/`)}function de(e,t,n){let r=[],i=R(t,n);y.lastIndex=0;let a;for(;(a=y.exec(e))!==null;){let[,e,n,o]=a;r.push({className:o,decorator:e,filePath:t,relativePath:i,isDefault:!!n})}b.lastIndex=0;let o;for(;(o=b.exec(e))!==null;){let[,e,n]=o;r.some(e=>e.className===n&&e.filePath===t)||r.push({className:n,decorator:`Module`,filePath:t,relativePath:i,isDefault:!!e})}return r}function fe(e,t,n){let r=[],i=R(t,n),a=new Set;x.lastIndex=0;let o;for(;(o=x.exec(e))!==null;){let[e,n,s]=o;a.add(e),r.push({name:s,variable:n,filePath:t,relativePath:i})}for(S.lastIndex=0;(o=S.exec(e))!==null;)a.has(o[0])||r.push({name:o[1],variable:null,filePath:t,relativePath:i});return r}function pe(e,t,n,r,i=new Map){let a=[];if(r.length===0)return a;let o=R(t,n),s=[];for(let t of r){let n=new RegExp(String.raw`class\s+${t.className}\b`).exec(e);n?.index!==void 0&&s.push({cls:t,start:n.index})}s.sort((e,t)=>e.start-t.start);for(let n=0;n<s.length;n++){let{cls:r,start:c}=s[n],l=n+1<s.length?s[n+1].start:e.length,u=e.slice(c,l);D.lastIndex=0;let d;for(;(d=D.exec(u))!==null;){let n=d[1],s=d.index,c=D.lastIndex-1,l=O(u,c);if(l<0)continue;let f=u.slice(c+1,l),p=f.match(/^\s*['"`]([^'"`]*)['"`]/),m=p&&p[1].length>0?p[1]:`/`,h=ne(u,l+1);if(!h)continue;let{methodName:g,endPos:_}=h;D.lastIndex=_;let v=ue(u.slice(s,_),e),y=M(f,`body`),b=M(f,`query`),x=M(f,`params`),S=i.get(r.className)??``,C=S?ie(S,m):m;a.push({controller:r.className,method:g,httpMethod:n.toUpperCase(),path:m,pathParams:re(C),queryFilterable:v?.filterable??null,querySortable:v?.sortable??null,querySearchable:v?.searchable??null,bodySchema:y?{identifier:y,source:N(e,y)}:null,querySchema:b?{identifier:b,source:N(e,b)}:null,paramsSchema:x?{identifier:x,source:N(e,x)}:null,filePath:t,relativePath:o})}}return a}function me(e,t,n){let r=[],i=R(t,n);C.lastIndex=0;let a;for(;(a=C.exec(e))!==null;)r.push({name:a[1],filePath:t,relativePath:i});return r}function z(e,t){let n=1;for(let r=t+1;r<e.length;r++){let t=e[r];if(t===`{`)n++;else if(t===`}`&&(n--,n===0))return r}return-1}function he(e,t,n){let r=[],i=R(t,n),a=new Set;w.lastIndex=0;let o;for(;(o=w.exec(e))!==null;){let n=o[1],s=w.lastIndex-1,c=O(e,s);if(c<0)continue;let l=e.slice(s+1,c),u=/\bname\s*:\s*['"`]([^'"`]+)['"`]/.exec(l);if(!u)continue;let d=u[1],f=`${n}::${d}::${t}`;a.has(f)||(a.add(f),r.push({kind:n===`definePlugin`?`plugin`:`adapter`,name:d,filePath:t,relativePath:i}))}ee.lastIndex=0;let s;for(;(s=ee.exec(e))!==null;){let n=s.index,o=e.indexOf(`{`,n);if(o<0)continue;let c=z(e,o);if(c<0)continue;let l=e.slice(o+1,c),u=te.exec(l);if(!u)continue;let d=u[1],f=`class::${d}::${t}`;a.has(f)||(a.add(f),r.push({kind:`adapter`,name:d,filePath:t,relativePath:i}))}return r}function ge(e,t,n){let r=[],i=R(t,n),a=new Set;for(T.lastIndex=0;T.exec(e)!==null;){let n=T.lastIndex-1,o=O(e,n);if(o<0)continue;let s=e.slice(n+1,o),c=/\bkey\s*:\s*['"`]([^'"`]+)['"`]/.exec(s);if(!c)continue;let l=c[1];a.has(l)||(a.add(l),r.push({key:l,filePath:t,relativePath:i}))}return r}function _e(e,t,n){let r=[],i=R(t,n);E.lastIndex=0;let a;for(;(a=E.exec(e))!==null;){let n=a[1],o=null,s=null;if(a[2]){let t=e.indexOf(`{`,a.index+a[0].length-1);if(t>=0){let n=z(e,t);if(n>=0){let r=e.slice(t+1,n);o=B(r,`description`),s=B(r,`example`)}}}r.push({name:n,description:o,example:s,filePath:t,relativePath:i})}return r}function B(e,t){let n=RegExp(`\\b${t}\\s*:\\s*(['"\`])`,`g`).exec(e);if(!n)return null;let r=n[1],i=n.index+n[0].length,a=i,o=null;for(;a<e.length;){let t=e[a];if(t===`\\`){a+=2;continue}if(t===r){o=e.slice(i,a);break}a++}return o===null?null:o.replace(/\\(.)/g,(e,t)=>t===`n`?`
|
|
12
|
-
`:t===`t`?` `:t===`r`?`\r`:t)}const ve=[`src/config/index.ts`,`src/config/env.ts`,`src/config.ts`,`src/env.ts`];async function ye(e,t){let n=t===`src/env.ts`?ve:[t];for(let t of n){let n=a(e,t),r;try{r=await l(n,`utf-8`)}catch{continue}if(!(!/\bdefineEnv\s*\(/.test(r)&&!/\bfrom(Zod|Valibot|Yup)\s*\(/.test(r))&&/export\s+default\b/.test(r)&&!/export\s+default\s+loadEnvFromSchema\s*\(/.test(r))return{filePath:n,relativePath:R(n,e)}}return null}function be(e){let t=new Map;for(let n of e){let e=t.get(n.className)??[];e.push(n),t.set(n.className,e)}let n=[];for(let[e,r]of t)new Set(r.map(e=>e.filePath)).size>1&&n.push({className:e,classes:r});return n.sort((e,t)=>e.className.localeCompare(t.className)),n}async function V(e){let t=await L(a(e.root),e),n=[],r=[],i=[],s=[],c=[],u=[],d=[],f=new Map;for(let r of t){let t;try{t=await l(r,`utf-8`)}catch{continue}f.set(r,t),n.push(...de(t,r,e.cwd)),i.push(...fe(t,r,e.cwd)),s.push(...me(t,r,e.cwd)),c.push(...he(t,r,e.cwd)),u.push(..._e(t,r,e.cwd)),d.push(...ge(t,r,e.cwd))}let p=new Map;for(let[,e]of f)for(let{controller:t,mountPath:n}of le(e))p.has(t)||p.set(t,n);for(let[t,i]of f){let a=n.filter(e=>e.filePath===t);r.push(...pe(i,t,e.cwd,a,p))}let m=[];for(let[e,t]of f){if(!/\.module\.[mc]?[tj]sx?$/.test(e))continue;let r=oe(t);if(r.length===0)continue;let i=e.replaceAll(o,`/`),a=i.slice(0,i.lastIndexOf(`/`));for(let t of n){if(t.decorator===`Module`)continue;let n=t.filePath.replaceAll(o,`/`);n.startsWith(a+`/`)&&n!==i&&(ce(n.slice(a.length+1),r)||m.push({className:t.className,filePath:t.filePath,relativePath:t.relativePath,moduleFilePath:e,decorator:t.decorator}))}}n.sort((e,t)=>e.className===t.className?e.relativePath.localeCompare(t.relativePath):e.className.localeCompare(t.className)),i.sort((e,t)=>e.name.localeCompare(t.name)||e.relativePath.localeCompare(t.relativePath)),s.sort((e,t)=>e.name.localeCompare(t.name)||e.relativePath.localeCompare(t.relativePath)),r.sort((e,t)=>e.controller.localeCompare(t.controller)||e.method.localeCompare(t.method)),c.sort((e,t)=>e.name.localeCompare(t.name)||e.relativePath.localeCompare(t.relativePath)),u.sort((e,t)=>e.name.localeCompare(t.name)||e.relativePath.localeCompare(t.relativePath)),d.sort((e,t)=>e.key.localeCompare(t.key)||e.relativePath.localeCompare(t.relativePath));let h=be(n),g=await ye(e.cwd,e.envFile??`src/env.ts`);return m.sort((e,t)=>e.relativePath.localeCompare(t.relativePath)||e.className.localeCompare(t.className)),{classes:n,routes:r,tokens:i,injects:s,collisions:h,env:g,pluginsAndAdapters:c,augmentations:u,contextKeys:d,orphanedClasses:m}}const H="/* eslint-disable */\n// AUTO-GENERATED by `kick typegen`. DO NOT EDIT.\n// Re-run with `kick typegen` or rely on `kick dev` to refresh.\n",U=new Set([`Service`,`Repository`,`Injectable`,`Component`]);var W=class extends Error{collisions;constructor(e){super(xe(e)),this.name=`TokenCollisionError`,this.collisions=e}};function xe(e){let t=[`kick typegen: token collision detected`];for(let n of e){t.push(``),t.push(` ${n.classes.length} classes named '${n.className}':`);for(let e of n.classes)t.push(` - ${e.relativePath}`)}return t.push(``),t.push(`Resolutions:`),t.push(` (a) Rename one of the classes`),t.push(` (b) Use createToken<T>('namespaced/Name') and import the token explicitly — see @forinda/kickjs`),t.push(` (c) Pass --allow-duplicates to namespace the registry keys automatically`),t.push(` (e.g. 'modules/users/UserService' instead of 'UserService')`),t.join(`
|
|
13
|
-
`)}function Se(e,t){let r=i(n(t),e).split(o).join(`/`);return r=r.replace(/\.(ts|tsx|mts|cts)$/i,``),r.startsWith(`.`)||(r=`./`+r),r}function G(e){let t=e.relativePath.replace(/^src\//,``).replace(/\.(ts|tsx|mts|cts)$/i,``).split(`/`);t.pop();let n=t.join(`/`);return n?`${n}/${e.className}`:e.className}function Ce(e,t,n){let r=new Set,i=[];for(let a of e){if(!U.has(a.decorator))continue;let e=n.has(a.className)?G(a):a.className;if(r.has(e))continue;r.add(e);let o=Se(a.filePath,t),s=a.isDefault?`import('${o}').default`:`import('${o}').${a.className}`;i.push(` '${e}': ${s}`)}return`${H}
|
|
14
|
-
declare module '@forinda/kickjs' {
|
|
15
|
-
interface KickJsRegistry {
|
|
16
|
-
${i.length?i.join(`
|
|
17
|
-
`):" // (no services discovered yet — run `kick g service <name>` to add one)"}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export {}
|
|
22
|
-
`}function we(e){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e)}function Te(e){return`${H}
|
|
23
|
-
declare module '@forinda/kickjs' {
|
|
24
|
-
/**
|
|
25
|
-
* Key-only registry of every context key produced by a
|
|
26
|
-
* \`defineContextDecorator\` / \`defineHttpContextDecorator\` in the
|
|
27
|
-
* project. Feeds \`dependsOn\` typo-checking. Value types live in
|
|
28
|
-
* \`ContextMeta\`; this only records that the key exists.
|
|
29
|
-
*/
|
|
30
|
-
interface ContextKeys {
|
|
31
|
-
${[...new Set(e.map(e=>e.key))].toSorted().map(e=>` ${we(e)?e:JSON.stringify(e)}: true`).join(`
|
|
32
|
-
`)}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export {}
|
|
37
|
-
`}function Ee(e,t,n){return t.length===0?`${H}
|
|
38
|
-
// ${n}
|
|
39
|
-
export type ${e} = never
|
|
40
|
-
`:`${H}
|
|
41
|
-
export type ${e} =
|
|
42
|
-
${[...new Set(t)].toSorted().map(e=>` | '${e}'`).join(`
|
|
43
|
-
`)}
|
|
44
|
-
`}function K(e,t,n,r){return[...e.filter(e=>U.has(e.decorator)).map(e=>r.has(e.className)?G(e):e.className),...t.map(e=>e.name),...n.map(e=>e.name)]}function q(e){return e.filter(e=>e.decorator===`Module`).map(e=>e.className)}function De(e){let t=new Map;for(let n of e)t.has(n.name)||t.set(n.name,n);return`${H}
|
|
45
|
-
declare module '@forinda/kickjs' {
|
|
46
|
-
/**
|
|
47
|
-
* Map of every plugin/adapter \`name\` discovered in the project. The
|
|
48
|
-
* value type is the kind tag (\`'plugin'\` or \`'adapter'\`); the
|
|
49
|
-
* \`keyof\` of this interface narrows \`dependsOn\` so misspelled deps
|
|
50
|
-
* become compile errors instead of boot-time \`MissingMountDepError\`.
|
|
51
|
-
*/
|
|
52
|
-
interface KickJsPluginRegistry {
|
|
53
|
-
${[...t.values()].toSorted((e,t)=>e.name.localeCompare(t.name)).map(e=>` '${e.name}': '${e.kind}'`).join(`
|
|
54
|
-
`)||" // (no plugins/adapters discovered yet — `defineAdapter`/`definePlugin` calls feed this)"}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export {}
|
|
59
|
-
`}function Oe(e){if(e.length===0)return`${H}
|
|
60
|
-
// No augmentations discovered.
|
|
61
|
-
//
|
|
62
|
-
// Plugins advertise augmentable interfaces via:
|
|
63
|
-
//
|
|
64
|
-
// import { defineAugmentation } from '@forinda/kickjs'
|
|
65
|
-
// defineAugmentation('FeatureFlags', {
|
|
66
|
-
// description: 'Feature flag shape consumed by FlagsPlugin',
|
|
67
|
-
// example: '{ beta: boolean; rolloutPercentage: number }',
|
|
68
|
-
// })
|
|
69
|
-
//
|
|
70
|
-
// See \`docs/guide/typegen.md#augmentations\` for the full pattern.
|
|
71
|
-
export {}
|
|
72
|
-
`;let t=new Map;for(let n of e)t.has(n.name)||t.set(n.name,n);let n=[];for(let e of[...t.values()].toSorted((e,t)=>e.name.localeCompare(t.name))){let t=[];if(e.description)for(let n of e.description.split(`
|
|
73
|
-
`))t.push(` * ${n}`);if(e.example){t.push(` * @example`," * ```ts");for(let n of e.example.split(`
|
|
74
|
-
`))t.push(` * ${n}`);t.push(" * ```")}t.push(` * @see ${e.relativePath}`),n.push([`/**`,...t,` */`,`export interface ${e.name}Augmentation {}`].join(`
|
|
75
|
-
`))}return`${H}
|
|
76
|
-
// Catalogue of augmentable interfaces in this project. The interfaces
|
|
77
|
-
// below are documentation only — augment the source-of-truth interfaces
|
|
78
|
-
// in your own \`d.ts\` files (the framework declares the actual types).
|
|
79
|
-
|
|
80
|
-
${n.join(`
|
|
81
|
-
|
|
82
|
-
`)}
|
|
83
|
-
`}const ke=/^(kick\/)?([a-z][\w-]*\/[A-Z]\w*)(\/.+)?(:[a-z][\w-]+(:[a-z][\w-]+)*)?$/;function Ae(e){let t=[];for(let n of e){let e=n.name;e.startsWith(`kickjs.`)||ke.test(e)||t.push({token:e,variable:n.variable,filePath:n.relativePath,reason:"does not match `<scope>/<PascalKey>[/<suffix>][:<instance>]`",suggestion:je(e)})}return t}function je(e){if(/^[A-Z]\w*$/.test(e))return`'<scope>/${e}' (e.g. 'mycorp/${e}')`;if(e.includes(`.`))return`consider '<scope>/PascalKey' instead of dotted form`;let t=/^([a-z][\w-]*)\/([a-z]\w*)$/.exec(e);if(t){let[,e,n]=t;return`'${e}/${n.charAt(0).toUpperCase()}${n.slice(1)}'`}}function J(e,t){if(!e)return{entries:[],count:0};let n=new Map;for(let[r,i]of Object.entries(e)){if(!i||typeof i.src!=`string`)continue;let e=a(t,i.src);if(!Pe(e))continue;let o=m(i.glob??`**/*`,{cwd:e,nodir:!0,dot:!1,posix:!0});o.sort();let{pairs:s}=h(r,o,{strategy:i.keys??`auto`});for(let{key:e}of s){let t=e.slice(r.length+1);n.set(e,{namespace:r,key:t})}}return{entries:[...n.values()],count:n.size}}function Me(e){let t="/* eslint-disable */\n// AUTO-GENERATED by `kick typegen`. DO NOT EDIT.\n// Re-run with `kick typegen` or rely on `kick dev` to refresh.\n";if(e.entries.length===0)return`${t}
|
|
84
|
-
declare module '@forinda/kickjs' {
|
|
85
|
-
/**
|
|
86
|
-
* Map of every typed asset discovered in the project's assetMap.
|
|
87
|
-
* (No assetMap entries discovered yet — declare with
|
|
88
|
-
* \`assetMap: { name: { src: 'src/...' } }\` in kick.config.ts.)
|
|
89
|
-
*/
|
|
90
|
-
interface KickAssets {}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export {}
|
|
94
|
-
`;let n={};for(let t of e.entries){let e=`${t.namespace}/${t.key}`.split(`/`),r=n;for(let t=0;t<e.length-1;t++){let n=e[t],i=r[n];if(i===Y){let e={};r[n]=e,r=e}else i||(r[n]={}),r=r[n]}let i=e[e.length-1];typeof r[i]!=`object`&&(r[i]=Y)}return`${t}
|
|
95
|
-
declare module '@forinda/kickjs' {
|
|
96
|
-
/**
|
|
97
|
-
* Map of every typed asset discovered in the project's assetMap.
|
|
98
|
-
* Each leaf is a \`() => string\` thunk that returns the resolved
|
|
99
|
-
* absolute path for the file in the current run mode (dev → src,
|
|
100
|
-
* prod → dist).
|
|
101
|
-
*/
|
|
102
|
-
interface KickAssets {
|
|
103
|
-
${X(n,` `)}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export {}
|
|
108
|
-
`}const Y=Symbol(`asset-leaf`);function X(e,t){let n=Object.keys(e).toSorted(),r=[];for(let i of n){let n=e[i],a=Ne(i)?i:JSON.stringify(i);n===Y?r.push(`${t}${a}: () => string`):(r.push(`${t}${a}: {`),r.push(X(n,`${t} `)),r.push(`${t}}`))}return r.join(`
|
|
109
|
-
`)}function Ne(e){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e)}function Pe(e){try{return s(e).isDirectory()}catch{return!1}}var Fe=e({runTypegen:()=>Q,sweepStaleTypegen:()=>Re,watchTypegen:()=>Le,writeTypegenArtifacts:()=>$});function Z(e){let t=e.cwd??process.cwd();return{cwd:t,srcDir:a(t,e.srcDir??`src`),outDir:a(t,e.outDir??`.kickjs/types`),silent:e.silent??!1,allowDuplicates:e.allowDuplicates??!1,schemaValidator:e.schemaValidator??!1,envFile:e.envFile??`src/env.ts`}}async function Q(e={}){let{cwd:t,srcDir:n,outDir:r,silent:i,allowDuplicates:a,envFile:o}=Z(e),s=Date.now(),c=await V({root:n,cwd:t,envFile:o===!1?void 0:o});if(c.collisions.length>0&&!a)throw new W(c.collisions);let l=J(e.assetMap,t),u=[],d=[];if(e.runPlugins!==!1){try{let{runAllPluginTypegens:e}=await import(`./run-plugins-M_WVt-7a.mjs`),{loadKickConfig:n}=await import(`./config-Cf8GU8CG.mjs`).then(e=>e.n);u=await e({cwd:t,config:await n(t),silent:!0})}catch(e){if(!i){let t=e instanceof Error?e.message:String(e);console.warn(` kick typegen: plugin pipeline failed (${t}) — continuing`)}}d.push(...await $(r,u,i))}let f=Ae(c.tokens),p=Ie(c,l.count,d),m=Date.now()-s;if(!i){let e=r.replace(t+`/`,``),n=p.resolvedCollisions>0?`, ${p.resolvedCollisions} collisions namespaced`:``,i=p.envWritten?`, env typed`:``,a=p.pluginEntries>0?`, ${p.pluginEntries} plugins/adapters`:``,o=p.augmentationEntries>0?`, ${p.augmentationEntries} augmentations`:``,s=p.assetEntries>0?`, ${p.assetEntries} assets`:``;if(console.log(` kick typegen → ${p.serviceTokens} services, ${p.routeEntries} routes, ${p.moduleTokens} modules${a}${o}${s}${i}${n} → ${e} (${m}ms)`),f.length>0){console.warn(` kick typegen: ${f.length} token(s) don't match the §22.2 convention:`);for(let e of f){let t=e.variable?` [${e.variable}]`:``;console.warn(` '${e.token}' (${e.filePath})${t} — ${e.reason}`),e.suggestion&&console.warn(` → suggestion: ${e.suggestion}`)}}if(c.orphanedClasses.length>0){console.warn(` kick typegen: ${c.orphanedClasses.length} decorated class(es) not matched by any module's import.meta.glob():`);for(let e of c.orphanedClasses)console.warn(` @${e.decorator} ${e.className} (${e.relativePath})`),console.warn(` → not picked up by any glob in ${e.moduleFilePath}`)}}return{scan:c,result:p,tokenWarnings:f}}function Ie(e,t,n){let r=new Set(e.collisions.map(e=>e.className)),i=e.classes.filter(e=>U.has(e.decorator)),a=K(e.classes,e.tokens,e.injects,r);return{registryEntries:i.length,serviceTokens:new Set(a).size,moduleTokens:q(e.classes).length,routeEntries:e.routes.length,pluginEntries:new Set(e.pluginsAndAdapters.map(e=>e.name)).size,augmentationEntries:new Set(e.augmentations.map(e=>e.name)).size,assetEntries:t,envWritten:e.env!==null,written:n,resolvedCollisions:e.collisions.length}}async function $(e,t,i){await c(e,{recursive:!0}),await p(r(n(e),`.gitignore`),`# Auto-generated by kick typegen
|
|
110
|
-
*
|
|
111
|
-
`,`utf-8`);let a=t.filter(e=>e.outFile).map(e=>e.outFile);return await Re(e,a,t,i),a}async function Le(e={}){let t=Z(e),{srcDir:n,silent:r,cwd:i}=t,a={...t,allowDuplicates:!0,runPlugins:!1},o=process.env.KICKJS_WATCH_POLLING===`1`||process.env.KICKJS_WATCH_POLLING===`true`,[{runAllPluginTypegens:s},{loadKickConfig:c}]=await Promise.all([import(`./run-plugins-M_WVt-7a.mjs`),import(`./config-Cf8GU8CG.mjs`).then(e=>e.n)]),l=await c(i),u=async()=>{try{await Q({...a})}catch(e){if(r)return;if(e instanceof W)console.error(`
|
|
112
|
-
`+e.message+`
|
|
113
|
-
`);else{let t=e instanceof Error?e.message:String(e);console.error(` kick typegen failed: ${t}`)}}},d=async()=>{try{let e=await s({cwd:i,config:l,silent:!0});await $(t.outDir,e,!0)}catch{}};await u(),await d();let{watch:f}=await import(`node:fs`),p=null,m=e=>{e&&/\.(ts|tsx|mts|cts)$/.test(e)&&(e.includes(`.kickjs`)||e.endsWith(`.d.ts`)||(p&&clearTimeout(p),p=setTimeout(()=>{u().then(d)},100)))};if(o){r||console.log(` kick typegen: polling mode (KICKJS_WATCH_POLLING)`);let e=setInterval(()=>{u().then(d)},2e3);return()=>clearInterval(e)}let h;try{h=f(n,{recursive:!0},(e,t)=>{m(t)})}catch(e){r||console.warn(` kick typegen: watch mode unavailable (${e?.message??e}). Falling back to polling.`);let t=setInterval(()=>{u().then(d)},2e3);return()=>clearInterval(t)}return()=>{p&&clearTimeout(p),h.close()}}async function Re(e,n,r,i){let o=new Set;for(let e of n)o.add(t(e));for(let e of r)e.outFile&&o.add(t(e.outFile));let s;try{s=await u(e)}catch{return[]}let c=[];for(let t of s){if(!ze.has(t)||o.has(t))continue;let n=a(e,t);try{if(!(await d(n)).isFile())continue;await f(n),c.push(t)}catch{}}return c.length>0&&!i&&console.log(` kick typegen: swept ${c.length} stale file(s): ${c.join(`, `)}`),c}const ze=new Set([`assets.d.ts`,`env.ts`,`routes.ts`,`registry.d.ts`,`services.d.ts`,`modules.d.ts`,`plugins.d.ts`,`augmentations.d.ts`,`index.d.ts`]);export{J as a,q as c,Te as d,De as f,V as h,$ as i,K as l,Ee as m,Fe as n,Me as o,Ce as p,Le as r,W as s,Q as t,Oe as u};
|
|
114
|
-
//# sourceMappingURL=typegen-CezcLjMb.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"typegen-CezcLjMb.mjs","names":[],"sources":["../src/typegen/scanner.ts","../src/typegen/render/manifest.ts","../src/typegen/token-conventions.ts","../src/typegen/asset-types.ts","../src/typegen/index.ts"],"sourcesContent":["/**\n * Static scanner for KickJS decorated classes and DI tokens.\n *\n * Walks `src/**\\/*.ts` (excluding tests and node_modules) and extracts:\n *\n * - Decorated classes (`@Service`, `@Controller`, `@Repository`, etc.)\n * - `createToken<T>('name')` definitions\n * - `@Inject('literal')` calls\n *\n * The output feeds the type generator, which emits `.kickjs/types/*.d.ts`\n * files used by the user's tsc to make `container.resolve()` and module\n * discovery type-safe.\n *\n * This is intentionally regex-based (not AST-based) to avoid the\n * ts-morph / typescript compiler dependency. Pattern from\n * `packages/vite/src/module-discovery.ts` which already uses regex\n * to detect `*.module.ts` exports.\n *\n * ## Collision detection\n *\n * Two classes with the same name across different files is a collision.\n * The scanner records all collisions in `ScanResult.collisions` so the\n * caller (generator) can decide whether to hard-error or auto-namespace.\n *\n * @module @forinda/kickjs-cli/typegen/scanner\n */\n\nimport type { Dirent } from 'node:fs'\nimport { readdir, readFile } from 'node:fs/promises'\nimport { join, relative, resolve, sep } from 'node:path'\n\n/** Decorators that mark a class as DI-managed */\nexport const DECORATOR_NAMES = [\n 'Service',\n 'Controller',\n 'Repository',\n 'Injectable',\n 'Component',\n 'Module',\n] as const\n\nexport type DecoratorName = (typeof DECORATOR_NAMES)[number]\n\n/** A single discovered decorated class */\nexport interface DiscoveredClass {\n /** Class name (e.g., 'UserService') */\n className: string\n /** Decorator that marked it (e.g., 'Service') */\n decorator: DecoratorName\n /** Absolute file path */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n /** True if exported as `default` */\n isDefault: boolean\n}\n\n/** A single route handler discovered on a controller class */\nexport interface DiscoveredRoute {\n /** Owning controller class name (e.g. 'UserController') */\n controller: string\n /** Handler method name on the controller (e.g. 'getUser') */\n method: string\n /** HTTP verb (uppercase) */\n httpMethod: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'\n /** Route path including parameter placeholders (e.g. '/:id/posts/:postId') */\n path: string\n /** URL path parameter names extracted from `:placeholder` segments */\n pathParams: string[]\n /**\n * Whitelisted query field names extracted from `@ApiQueryParams({...})`.\n * `null` means no `@ApiQueryParams` was found on this method (so the\n * generator emits an unconstrained `query` shape). An empty array means\n * the decorator existed but no fields could be statically extracted\n * (e.g. an opaque imported config).\n */\n queryFilterable: string[] | null\n querySortable: string[] | null\n querySearchable: string[] | null\n /**\n * Schema identifiers referenced from the route decorator's second arg\n * (e.g. `@Post('/', { body: createTaskSchema })`). `null` means no\n * such reference; the value carries the identifier and the resolved\n * import source (relative module path) if known.\n */\n bodySchema: SchemaRef | null\n querySchema: SchemaRef | null\n paramsSchema: SchemaRef | null\n /** Absolute file path of the controller */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/** A statically-resolved schema identifier reference */\nexport interface SchemaRef {\n /** The identifier as written (e.g. `createTaskSchema`) */\n identifier: string\n /**\n * Resolved module specifier (relative path or bare module name) where\n * the identifier is defined. `null` means the source could not be\n * statically determined (the generator falls back to `unknown`).\n */\n source: string | null\n}\n\n/** A `createToken<T>('name')` call discovered in source */\nexport interface DiscoveredToken {\n /** The literal string passed to `createToken()` */\n name: string\n /** The const variable name on the LHS, if any */\n variable: string | null\n /** Absolute file path */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/** An `@Inject('literal')` call discovered in source */\nexport interface DiscoveredInject {\n /** The literal string passed to `@Inject()` */\n name: string\n /** Absolute file path */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/** A name collision — same class name in two or more files */\nexport interface ClassCollision {\n /** The colliding class name */\n className: string\n /** All files declaring the class */\n classes: DiscoveredClass[]\n}\n\n/**\n * Information about a discovered env schema file. The typegen\n * generator uses this to emit a `KickEnv` + `NodeJS.ProcessEnv`\n * augmentation that flows through to `@Value` and `process.env`.\n *\n * `null` means no env file was found at the configured location.\n */\nexport interface DiscoveredEnv {\n /** Absolute path to the env schema file */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/**\n * A plugin or adapter discovered in source — either via `defineAdapter({ name })`\n * / `definePlugin({ name })` calls, or via a class that `implements AppAdapter`\n * and declares a string-literal `name` field.\n *\n * The `name` here is the literal string passed to the framework (the value\n * `dependsOn` references), NOT the symbol on the LHS. `defineAdapter` lets\n * authors choose any name they want; the symbol is irrelevant at runtime.\n */\nexport interface DiscoveredPluginOrAdapter {\n /** Whether this is a plugin (`definePlugin`) or adapter (`defineAdapter` / class) */\n kind: 'plugin' | 'adapter'\n /** The string literal passed as `name` (the value `dependsOn` references) */\n name: string\n /** Absolute file path */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/**\n * A context key discovered from a `defineContextDecorator({ key })` or\n * `defineHttpContextDecorator({ key })` call (including the curried\n * `.withParams<P>()({ key })` form). Feeds the `kick/context` typegen\n * plugin, which emits the `ContextKeys` augmentation so `dependsOn`\n * typo-checking is automatic and complete.\n */\nexport interface DiscoveredContextKey {\n /** The literal `key:` value the contributor writes. */\n key: string\n /** Absolute file path. */\n filePath: string\n /** Path relative to scan root, with forward slashes. */\n relativePath: string\n}\n\n/**\n * A `defineAugmentation('Name', meta)` call discovered in source. Plugins\n * call this to advertise an augmentable interface so the typegen can list\n * every augmentation surface in one generated file.\n */\nexport interface DiscoveredAugmentation {\n /** The literal string passed as the first arg to `defineAugmentation` */\n name: string\n /** Optional `description` extracted from the second-arg object literal */\n description: string | null\n /** Optional `example` extracted from the second-arg object literal */\n example: string | null\n /** Absolute file path */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/**\n * A decorated class whose file sits inside a module directory but\n * isn't picked up by any of the module's `import.meta.glob(...)`\n * patterns. Surfaced as a typegen warning per forinda/kick-js#235 §4\n * so adopters notice silent registration drift before it bites them\n * at runtime with a `MissingContributorError` or wrong code path.\n */\nexport interface OrphanedClass {\n /** The decorated class name */\n className: string\n /** Absolute path of the class file */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n /** Absolute path of the module file whose globs didn't match */\n moduleFilePath: string\n /** The decorator name (`Service`, `Controller`, `Repository`, …) */\n decorator: DecoratorName\n}\n\n/** Aggregated scanner output */\nexport interface ScanResult {\n classes: DiscoveredClass[]\n routes: DiscoveredRoute[]\n tokens: DiscoveredToken[]\n injects: DiscoveredInject[]\n collisions: ClassCollision[]\n /** Discovered env schema file (or null if none found at the configured path) */\n env: DiscoveredEnv | null\n /** Plugins/adapters discovered via `defineAdapter`/`definePlugin`/`implements AppAdapter` */\n pluginsAndAdapters: DiscoveredPluginOrAdapter[]\n /** Augmentation interfaces declared via `defineAugmentation('Name', meta)` */\n augmentations: DiscoveredAugmentation[]\n /** Context keys from `define(Http)ContextDecorator({ key })` calls */\n contextKeys: DiscoveredContextKey[]\n /**\n * Decorated classes that sit inside a module directory but aren't\n * picked up by any of the module's `import.meta.glob(...)` patterns.\n * Empty when every decorator file is matched. forinda/kick-js#235 §4.\n */\n orphanedClasses: OrphanedClass[]\n}\n\n/** Options for the scanner */\nexport interface ScanOptions {\n /** Root directory to scan (e.g., absolute path to `src`) */\n root: string\n /** Project root used to compute relative paths (e.g., process.cwd()) */\n cwd: string\n /** Glob-like extensions to scan */\n extensions?: string[]\n /** Substrings that exclude a path (matched against relative path) */\n exclude?: string[]\n /**\n * Path to the env schema file, relative to `cwd`. Defaults to\n * `'src/env.ts'`. The file must contain a `defineEnv(...)` call\n * with a default export for the typegen to emit a typed `KickEnv`\n * augmentation. If the file does not exist or doesn't match the\n * expected shape, env typing is skipped silently.\n */\n envFile?: string\n}\n\nconst DEFAULT_EXTENSIONS = ['.ts', '.tsx', '.mts', '.cts']\nconst DEFAULT_EXCLUDES = ['node_modules', '.kickjs', 'dist', 'build', '.test.', '.spec.', '.d.ts']\n\n/**\n * Match a class-level decorator immediately followed by an exported\n * class declaration. Captures decorator name and class name.\n */\nconst DECORATED_CLASS_REGEX = new RegExp(\n String.raw`@(${DECORATOR_NAMES.join('|')})\\s*\\([^)]*\\)` +\n String.raw`(?:\\s*@[A-Z]\\w*(?:\\s*\\([^)]*\\))?)*` +\n String.raw`\\s*export\\s+(default\\s+)?(?:abstract\\s+)?class\\s+(\\w+)`,\n 'g',\n)\n\n/**\n * Match an exported class declaration that implements `AppModule`.\n * KickJS modules are not decorated — they implement the `AppModule`\n * interface — so the decorated-class scanner never picks them up. This\n * regex captures them by name so `ModuleToken` can be populated.\n *\n * Tolerates an `extends BaseClass` clause before `implements`, multiple\n * implements clauses (`implements Foo, AppModule`), and `default` exports.\n */\nconst APP_MODULE_CLASS_REGEX = new RegExp(\n String.raw`export\\s+(default\\s+)?(?:abstract\\s+)?class\\s+(\\w+)` +\n String.raw`(?:\\s+extends\\s+\\w+(?:<[^>]*>)?)?` +\n String.raw`\\s+implements\\s+[^{]*\\bAppModule\\b`,\n 'g',\n)\n\n/**\n * Match a `createToken<T>('name')` call with optional `export const X =`\n * or `const X =` prefix. Tolerates whitespace and the type parameter\n * being absent (`createToken('name')`).\n */\nconst CREATE_TOKEN_REGEX =\n /(?:export\\s+)?const\\s+(\\w+)\\s*(?::\\s*[^=]+)?=\\s*createToken\\s*(?:<[^>]*>)?\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]\\s*\\)/g\n\n/**\n * Match a bare `createToken<T>('name')` call (no const assignment) so\n * we still pick up dynamically-used tokens.\n */\nconst BARE_CREATE_TOKEN_REGEX = /createToken\\s*(?:<[^>]*>)?\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]\\s*\\)/g\n\n/** Match `@Inject('literal')` — only literals; computed args are skipped */\nconst INJECT_LITERAL_REGEX = /@Inject\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]\\s*\\)/g\n\n/**\n * Match the start of a `defineAdapter(...)` or `definePlugin(...)` call,\n * tolerating optional `<TConfig, TExtra>` generics. Captures the helper\n * name. The callsite's first-arg object is parsed forward via\n * `findBalancedClose` so nested objects/parens don't confuse us.\n */\nconst DEFINE_HELPER_START = /\\b(defineAdapter|definePlugin)\\s*(?:<[^>]*>)?\\s*\\(/g\n\n/**\n * Match the start of a `defineContextDecorator(...)` /\n * `defineHttpContextDecorator(...)` call up to the `(` that opens the\n * spec object — tolerating optional `<...>` generics AND the curried\n * `.withParams<P>()(...)` form (the empty `()` is consumed so the next\n * `(` is the spec). The spec's `key:` literal is then read forward via\n * `findBalancedClose`, mirroring `DEFINE_HELPER_START`.\n */\n// `(?:<(?:[^<>]|<[^<>]*>)*>)?` tolerates one level of nested generics\n// (e.g. `defineHttpContextDecorator<'tenant', Record<string, never>>`),\n// which a flat `<[^>]*>` would truncate at the inner `>`.\nconst CONTEXT_DECORATOR_START =\n /\\b(?:defineContextDecorator|defineHttpContextDecorator)\\s*(?:\\.withParams\\s*<(?:[^<>]|<[^<>]*>)*>\\s*\\(\\s*\\))?\\s*(?:<(?:[^<>]|<[^<>]*>)*>)?\\s*\\(/g\n\n/**\n * Match a class declaration whose `implements` clause includes `AppAdapter`.\n * Captures the class name. Used to pick up the (rare, post-defineAdapter)\n * legacy class-style adapters so their literal `name = '...'` field can\n * still feed `KickJsPluginRegistry`.\n */\nconst APP_ADAPTER_CLASS_REGEX = new RegExp(\n String.raw`export\\s+(?:default\\s+)?(?:abstract\\s+)?class\\s+(\\w+)` +\n String.raw`(?:\\s+extends\\s+\\w+(?:<[^>]*>)?)?` +\n String.raw`\\s+implements\\s+[^{]*\\bAppAdapter\\b`,\n 'g',\n)\n\n/** Match a string-literal `name = '...'` field on a class body. */\nconst CLASS_NAME_FIELD_REGEX = /\\bname\\s*(?::\\s*[^=]+)?=\\s*['\"`]([^'\"`]+)['\"`]/\n\n/**\n * Match the start of a `defineAugmentation('Name', ...)` call. Captures\n * the literal name. The optional second-arg object is parsed forward so\n * `description` / `example` can be pulled out.\n */\nconst DEFINE_AUGMENTATION_START = /\\bdefineAugmentation\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]\\s*(,\\s*\\{)?/g\n\n/** HTTP route decorator names recognised by the scanner */\nconst HTTP_DECORATORS = ['Get', 'Post', 'Put', 'Delete', 'Patch'] as const\n\n/**\n * Locate the start of a route decorator: `@Get(`, `@Post(`, etc.\n * Used by `extractRoutesFromSource`; the rest of the route declaration\n * (balanced parens, stacked decorators, method name) is parsed by walking\n * the source forward from this match. The previous all-in-one regex\n * couldn't handle nested parens in stacked decorator args (e.g.\n * `@ApiResponse(201, { schema: z.object({ id: z.string() }) })`) — see\n * forinda/kick-js#108.\n */\nconst ROUTE_DECORATOR_START = new RegExp(String.raw`@(${HTTP_DECORATORS.join('|')})\\s*\\(`, 'g')\n\n/**\n * Find the index of the `)` that balances the `(` at `openPos`.\n * Returns -1 if no matching `)` exists. Counts balanced parens only;\n * does not understand string literals, so a `(` or `)` inside a string\n * inside the args will skew the depth counter (matches the limitation\n * of `extractRouteOptionsArg`).\n */\nfunction findBalancedClose(text: string, openPos: number): number {\n let depth = 1\n for (let i = openPos + 1; i < text.length; i++) {\n const ch = text[i]\n if (ch === '(') depth++\n else if (ch === ')') {\n depth--\n if (depth === 0) return i\n }\n }\n return -1\n}\n\n/**\n * Walk forward from the end of a route decorator past any stacked\n * decorators (`@ApiOperation(...)`, `@ApiResponse(...)`, `@Middleware(fn)`,\n * etc.), then past optional `public`/`private`/`protected` and `async`,\n * and capture the method name + opening `(`.\n *\n * Returns the method name and the position immediately after the method's\n * opening `(`, or `null` if the source between the route decorator and\n * the method body doesn't fit the expected shape.\n */\nfunction readMethodAfterDecorators(\n block: string,\n startPos: number,\n): { methodName: string; endPos: number } | null {\n let pos = startPos\n // Stacked decorators: @PascalCase optionally followed by balanced (...)\n while (pos < block.length) {\n while (pos < block.length && /\\s/.test(block[pos])) pos++\n if (block[pos] !== '@') break\n const decMatch = block.slice(pos).match(/^@([A-Z]\\w*)/)\n if (!decMatch) break\n pos += decMatch[0].length\n while (pos < block.length && /\\s/.test(block[pos])) pos++\n if (block[pos] === '(') {\n const close = findBalancedClose(block, pos)\n if (close < 0) return null\n pos = close + 1\n }\n }\n // Modifiers + async\n while (pos < block.length && /\\s/.test(block[pos])) pos++\n for (const mod of ['public', 'private', 'protected'] as const) {\n if (block.slice(pos, pos + mod.length) === mod && /\\s/.test(block.charAt(pos + mod.length))) {\n pos += mod.length\n while (pos < block.length && /\\s/.test(block[pos])) pos++\n break\n }\n }\n if (block.slice(pos, pos + 5) === 'async' && /\\s/.test(block.charAt(pos + 5))) {\n pos += 5\n while (pos < block.length && /\\s/.test(block[pos])) pos++\n }\n // Method name + `(`\n const methodMatch = block.slice(pos).match(/^([a-zA-Z_]\\w*)\\s*\\(/)\n if (!methodMatch) return null\n return { methodName: methodMatch[1], endPos: pos + methodMatch[0].length }\n}\n\n/** Extract `:placeholder` segments from an Express route path */\nfunction extractPathParams(path: string): string[] {\n const matches = path.match(/:([a-zA-Z_]\\w*)/g) ?? []\n return matches.map((m) => m.slice(1))\n}\n\n/**\n * Join a controller's mount-prefix path with a per-route path.\n * Handles the slash edge cases so `'/orgs/:id'` + `'/'` becomes\n * `'/orgs/:id'` (no trailing slash) and `'/orgs/:id'` + `'/:code'`\n * becomes `'/orgs/:id/:code'`. forinda/kick-js#235 §3.\n */\nfunction joinMountPath(mountPath: string, routePath: string): string {\n const prefix = mountPath.endsWith('/') ? mountPath.slice(0, -1) : mountPath\n if (!routePath || routePath === '/') return prefix || '/'\n const suffix = routePath.startsWith('/') ? routePath : '/' + routePath\n return prefix + suffix || '/'\n}\n\n/**\n * Match the `routes()` method body on a class implementing `AppModule`.\n * Captures the body region so we can scan it for `path:` + `controller:`\n * pairs. Tolerates `routes(): ModuleRoutes` and stripped return type.\n */\nconst ROUTES_METHOD_START =\n /\\b(?:public\\s+|private\\s+|protected\\s+)?routes\\s*\\([^)]*\\)\\s*(?::\\s*[A-Za-z_][\\w<>[\\]\\s,|]*\\s*)?\\{/g\n\n/**\n * Match `path: '/...'` inside a routes-method body. Picks up both\n * single-quoted and double-quoted / template literal forms.\n */\nconst PATH_FIELD_REGEX = /\\bpath\\s*:\\s*['\"`]([^'\"`]*)['\"`]/g\n\n/** Match `controller: SomeController` (bare identifier only). */\nconst CONTROLLER_FIELD_REGEX = /\\bcontroller\\s*:\\s*([A-Z]\\w*)\\b/g\n\n/**\n * Match the start of an `import.meta.glob(...)` call. The first arg\n * (string or string array) gets parsed forward via balanced-paren\n * walking to handle whitespace + line breaks inside the array.\n * forinda/kick-js#235 §4.\n */\nconst IMPORT_META_GLOB_START = /\\bimport\\.meta\\.glob\\s*\\(/g\n\n/**\n * Extract every glob pattern from `import.meta.glob([...patterns], ...)` calls\n * in a module file. Single-string form (`import.meta.glob('./**\\\\/*.ts')`) and\n * array form both supported. Negation patterns (`!./**\\\\/*.test.ts`)\n * are returned with the leading `!` preserved so the caller can apply\n * exclusion logic.\n */\nexport function extractGlobPatterns(source: string): string[] {\n const patterns: string[] = []\n IMPORT_META_GLOB_START.lastIndex = 0\n while (IMPORT_META_GLOB_START.exec(source) !== null) {\n const openParen = IMPORT_META_GLOB_START.lastIndex - 1\n const closeParen = findBalancedClose(source, openParen)\n if (closeParen < 0) continue\n const args = source.slice(openParen + 1, closeParen)\n // Pull out every string literal inside the first-arg region —\n // we don't bother distinguishing the array form from the bare\n // string; both end up as flat patterns.\n const literalRe = /['\"`]([^'\"`]+)['\"`]/g\n let lit: RegExpExecArray | null\n while ((lit = literalRe.exec(args)) !== null) {\n patterns.push(lit[1] as string)\n }\n }\n return patterns\n}\n\n/**\n * Convert a Vite-style glob (e.g. `./**\\\\/*.controller.ts`) to a\n * RegExp. Supports `**` (any path segments including `/`), `*` (any\n * chars within one segment), `?` (single char). Brace alternation is\n * intentionally not handled — none of the templated globs use it,\n * and a false-negative on an unusual pattern is safer than a false-\n * positive (better to skip the warning than to wrongly silence one).\n */\nfunction globToRegex(pattern: string): RegExp {\n // Process `?` before any of the substitutions that insert `?` into\n // the output (e.g. the `(?:.+/)?` non-capture group for `**/`).\n // If we left it for last, the `?` → `.` pass would mangle those\n // groups into `(.:.+\\/).` — broken regex.\n const escaped = pattern\n .replace(/[.+^$()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\?/g, '.')\n .replace(/\\*\\*\\//g, '___DOUBLESTAR_SLASH___')\n .replace(/\\*\\*/g, '___DOUBLESTAR___')\n .replace(/\\*/g, '[^/]*')\n .replace(/___DOUBLESTAR_SLASH___/g, '(?:.+/)?')\n .replace(/___DOUBLESTAR___/g, '.*')\n return new RegExp('^' + escaped + '$')\n}\n\n/**\n * Decide whether a file (relative to the module file's directory)\n * matches any of the module's positive glob patterns. Negation\n * patterns (`!./**\\\\/*.test.ts`) subtract; a file matched by both a\n * positive and a negation is excluded.\n */\nexport function fileMatchesAnyGlob(\n moduleRelativePath: string,\n patterns: readonly string[],\n): boolean {\n const normalised = moduleRelativePath.startsWith('./')\n ? moduleRelativePath\n : './' + moduleRelativePath\n let matched = false\n for (const pattern of patterns) {\n const isNegation = pattern.startsWith('!')\n const body = isNegation ? pattern.slice(1) : pattern\n if (globToRegex(body).test(normalised)) {\n matched = !isNegation\n }\n }\n return matched\n}\n\n/**\n * A `{ controller, mountPath }` pair extracted from a module's\n * `routes()` body. Multiple entries appear when a module returns an\n * array (multi-mount). forinda/kick-js#235 §3.\n */\nexport interface ModuleMount {\n controller: string\n mountPath: string\n}\n\n/**\n * Scan a module file's `routes()` body for `{ path, controller }` pairs.\n * A single return value or an array of return values both work — we\n * regex out every `path: '...'` and every `controller: Ident` and\n * zip them in order. Adopter writing wildly creative bodies won't be\n * matched; that's fine — the scanner falls back to no-mount behaviour\n * (per-route path only) which is the pre-fix behaviour.\n *\n * Returns a list of `{ controller, mountPath }` entries. A controller\n * that appears multiple times in `routes()` (rare; multi-mount\n * version-bundled controllers) gets multiple entries; the route\n * scanner uses the first one for path-param extraction since the\n * pattern usually shares the prefix.\n */\nexport function extractModuleMounts(source: string): ModuleMount[] {\n const out: ModuleMount[] = []\n ROUTES_METHOD_START.lastIndex = 0\n let m: RegExpExecArray | null\n while ((m = ROUTES_METHOD_START.exec(source)) !== null) {\n const openBrace = source.indexOf('{', m.index + m[0].length - 1)\n if (openBrace < 0) continue\n const closeBrace = findBalancedBrace(source, openBrace)\n if (closeBrace < 0) continue\n const body = source.slice(openBrace + 1, closeBrace)\n\n const paths: string[] = []\n PATH_FIELD_REGEX.lastIndex = 0\n let p: RegExpExecArray | null\n while ((p = PATH_FIELD_REGEX.exec(body)) !== null) {\n paths.push(p[1] ?? '')\n }\n\n const controllers: string[] = []\n CONTROLLER_FIELD_REGEX.lastIndex = 0\n let c: RegExpExecArray | null\n while ((c = CONTROLLER_FIELD_REGEX.exec(body)) !== null) {\n controllers.push(c[1] as string)\n }\n\n // Zip — if counts mismatch, take the shorter of the two so we\n // never assign a wrong controller to a path.\n const n = Math.min(paths.length, controllers.length)\n for (let i = 0; i < n; i++) {\n out.push({ controller: controllers[i] as string, mountPath: paths[i] as string })\n }\n }\n return out\n}\n\n/**\n * Extract a bare identifier value from a single field in an object literal\n * embedded in a string. Returns `null` if the field is missing or its value\n * isn't a bare identifier (e.g. an inline object, function call, etc.).\n *\n * Example: `extractObjectFieldIdentifier(\"'/' , { body: createTaskSchema }\", 'body')`\n * returns `'createTaskSchema'`.\n */\nfunction extractObjectFieldIdentifier(text: string, field: string): string | null {\n // Look for `field: <identifier>` not followed by `(` (function call) or `{` (inline object)\n const re = new RegExp(String.raw`\\b${field}\\s*:\\s*([A-Za-z_$][\\w$]*)`, 'g')\n const m = re.exec(text)\n if (!m) return null\n return m[1]\n}\n\n/**\n * Resolve a bare identifier to its module source by inspecting the file's\n * top-level imports and same-file `const` declarations.\n *\n * - `import { X } from './path'` → returns `'./path'`\n * - `import X from './path'` (default import) → returns `'./path'`\n * - `import * as X from './path'` → returns `'./path'`\n * - `const X = z.object(...)` (same file) → returns `null` (caller emits a self-import)\n *\n * Returns `null` when the identifier cannot be resolved.\n */\nfunction resolveImportSource(source: string, identifier: string): string | null {\n // Named import: `import { X, Y as Z } from './path'`\n const namedRe = new RegExp(\n String.raw`import\\s*(?:type\\s+)?\\{[^}]*\\b${identifier}\\b[^}]*\\}\\s*from\\s*['\"\\`]([^'\"\\`]+)['\"\\`]`,\n )\n const named = namedRe.exec(source)\n if (named) return named[1]\n\n // Default import: `import X from './path'`\n const defaultRe = new RegExp(\n String.raw`import\\s+(?:type\\s+)?${identifier}\\s+from\\s*['\"\\`]([^'\"\\`]+)['\"\\`]`,\n )\n const def = defaultRe.exec(source)\n if (def) return def[1]\n\n // Namespace import: `import * as X from './path'`\n const nsRe = new RegExp(\n String.raw`import\\s*\\*\\s*as\\s+${identifier}\\s+from\\s*['\"\\`]([^'\"\\`]+)['\"\\`]`,\n )\n const ns = nsRe.exec(source)\n if (ns) return ns[1]\n\n // Same-file const declaration — return empty string as a sentinel meaning\n // \"current file\". The generator turns this into a self-relative reference.\n const constRe = new RegExp(String.raw`(?:^|\\n)\\s*(?:export\\s+)?const\\s+${identifier}\\b`)\n if (constRe.test(source)) return ''\n\n return null\n}\n\n/**\n * Extract whitelist arrays from an `@ApiQueryParams(...)` decorator\n * within `decoratorBlock`. Handles two forms:\n *\n * - Inline literal: `@ApiQueryParams({ filterable: ['a', 'b'], ... })`\n * - Const reference: `@ApiQueryParams(SOME_CONFIG)` — looks up\n * `const SOME_CONFIG = { ... }` in the same file (`fullSource`).\n *\n * Returns `null` if no `@ApiQueryParams` is present. Returns\n * `{ filterable: [], sortable: [], searchable: [] }` if the decorator\n * is present but no fields could be statically extracted (opaque\n * imports, column-object configs, function calls, etc.).\n */\nfunction extractApiQueryParams(\n decoratorBlock: string,\n fullSource: string,\n): { filterable: string[]; sortable: string[]; searchable: string[] } | null {\n const apiMatch = /@ApiQueryParams\\s*\\(\\s*([\\s\\S]*?)\\s*\\)\\s*$/.exec(decoratorBlock)\n if (!apiMatch) {\n // Try without anchoring to the end (decorator may not be the last in the block)\n const loose = /@ApiQueryParams\\s*\\(([\\s\\S]*?)\\)/.exec(decoratorBlock)\n if (!loose) return null\n return parseApiQueryParamsArg(loose[1].trim(), fullSource)\n }\n return parseApiQueryParamsArg(apiMatch[1].trim(), fullSource)\n}\n\nfunction parseApiQueryParamsArg(\n arg: string,\n fullSource: string,\n): { filterable: string[]; sortable: string[]; searchable: string[] } {\n // Inline literal — starts with `{`\n if (arg.startsWith('{')) {\n return parseInlineConfigLiteral(arg)\n }\n // Const reference — bare identifier (possibly with type assertion)\n const idMatch = /^([A-Za-z_]\\w*)/.exec(arg)\n if (idMatch) {\n const ident = idMatch[1]\n // Look for `const IDENT = { ... }` in the same source file\n const constRe = new RegExp(\n String.raw`const\\s+${ident}\\s*(?::\\s*[^=]+)?=\\s*(\\{[\\s\\S]*?\\n\\})`,\n 'm',\n )\n const constMatch = constRe.exec(fullSource)\n if (constMatch) {\n return parseInlineConfigLiteral(constMatch[1])\n }\n }\n // Fallback: decorator present but extraction failed\n return { filterable: [], sortable: [], searchable: [] }\n}\n\n/** Extract a string array literal for one config key from an inline object literal */\nfunction extractStringArray(literal: string, key: string): string[] {\n const re = new RegExp(String.raw`${key}\\s*:\\s*\\[([\\s\\S]*?)\\]`)\n const m = re.exec(literal)\n if (!m) return []\n return Array.from(m[1].matchAll(/['\"`]([^'\"`]+)['\"`]/g)).map((x) => x[1])\n}\n\n/** Parse an inline `{ filterable: [...], sortable: [...], searchable: [...] }` literal */\nfunction parseInlineConfigLiteral(literal: string): {\n filterable: string[]\n sortable: string[]\n searchable: string[]\n} {\n return {\n filterable: extractStringArray(literal, 'filterable'),\n sortable: extractStringArray(literal, 'sortable'),\n searchable: extractStringArray(literal, 'searchable'),\n }\n}\n\n/** Recursively walk a directory and yield matching file paths */\nasync function walk(dir: string, opts: ScanOptions): Promise<string[]> {\n const exts = opts.extensions ?? DEFAULT_EXTENSIONS\n const excludes = opts.exclude ?? DEFAULT_EXCLUDES\n const out: string[] = []\n\n let entries: Dirent[]\n try {\n entries = (await readdir(dir, { withFileTypes: true, encoding: 'utf-8' })) as Dirent[]\n } catch {\n return out\n }\n\n for (const entry of entries) {\n const full = join(dir, entry.name)\n const rel = relative(opts.cwd, full)\n\n if (excludes.some((ex) => rel.includes(ex))) continue\n\n if (entry.isDirectory()) {\n out.push(...(await walk(full, opts)))\n } else if (entry.isFile()) {\n if (exts.some((ext) => entry.name.endsWith(ext))) {\n out.push(full)\n }\n }\n }\n\n return out\n}\n\n/** Compute the forward-slash relative path used in scanner output */\nfunction toRelative(filePath: string, cwd: string): string {\n return relative(cwd, filePath).split(sep).join('/')\n}\n\n/** Extract decorated classes from a single source file */\nexport function extractClassesFromSource(\n source: string,\n filePath: string,\n cwd: string,\n): DiscoveredClass[] {\n const out: DiscoveredClass[] = []\n const relPath = toRelative(filePath, cwd)\n\n DECORATED_CLASS_REGEX.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = DECORATED_CLASS_REGEX.exec(source)) !== null) {\n const [, decorator, defaultMarker, className] = match\n out.push({\n className,\n decorator: decorator as DecoratorName,\n filePath,\n relativePath: relPath,\n isDefault: Boolean(defaultMarker),\n })\n }\n\n // KickJS modules are undecorated classes that `implements AppModule`.\n // Tag them with the synthetic `Module` decorator so downstream code that\n // already filters by `c.decorator === 'Module'` keeps working.\n APP_MODULE_CLASS_REGEX.lastIndex = 0\n let modMatch: RegExpExecArray | null\n while ((modMatch = APP_MODULE_CLASS_REGEX.exec(source)) !== null) {\n const [, defaultMarker, className] = modMatch\n if (out.some((c) => c.className === className && c.filePath === filePath)) continue\n out.push({\n className,\n decorator: 'Module',\n filePath,\n relativePath: relPath,\n isDefault: Boolean(defaultMarker),\n })\n }\n\n return out\n}\n\n/** Extract `createToken('name')` definitions from a single source file */\nexport function extractTokensFromSource(\n source: string,\n filePath: string,\n cwd: string,\n): DiscoveredToken[] {\n const out: DiscoveredToken[] = []\n const relPath = toRelative(filePath, cwd)\n const seen = new Set<string>()\n\n // First pass: const-bound tokens (preferred — we get the variable name)\n CREATE_TOKEN_REGEX.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = CREATE_TOKEN_REGEX.exec(source)) !== null) {\n const [full, variable, name] = match\n seen.add(full)\n out.push({ name, variable, filePath, relativePath: relPath })\n }\n\n // Second pass: bare calls not captured above (rare but possible)\n BARE_CREATE_TOKEN_REGEX.lastIndex = 0\n while ((match = BARE_CREATE_TOKEN_REGEX.exec(source)) !== null) {\n if (seen.has(match[0])) continue\n out.push({\n name: match[1],\n variable: null,\n filePath,\n relativePath: relPath,\n })\n }\n\n return out\n}\n\n/**\n * Extract route handlers from a source file.\n *\n * For each decorated class in `classesInFile`, slices the source from\n * the class declaration to the next class (or EOF) and runs the route\n * decorator regex within that slice. The result is a list of routes\n * tagged with their owning controller.\n *\n * Heuristic note: this assumes classes are not nested. KickJS controllers\n * are top-level by convention so this holds in practice.\n */\nexport function extractRoutesFromSource(\n source: string,\n filePath: string,\n cwd: string,\n classesInFile: DiscoveredClass[],\n mountPathByController: ReadonlyMap<string, string> = new Map(),\n): DiscoveredRoute[] {\n const out: DiscoveredRoute[] = []\n if (classesInFile.length === 0) return out\n const relPath = toRelative(filePath, cwd)\n\n // Locate each class declaration's offset in the source\n const positions: Array<{ cls: DiscoveredClass; start: number }> = []\n for (const cls of classesInFile) {\n const re = new RegExp(String.raw`class\\s+${cls.className}\\b`)\n const m = re.exec(source)\n if (m?.index !== undefined) {\n positions.push({ cls, start: m.index })\n }\n }\n positions.sort((a, b) => a.start - b.start)\n\n for (let i = 0; i < positions.length; i++) {\n const { cls, start } = positions[i]\n const end = i + 1 < positions.length ? positions[i + 1].start : source.length\n const block = source.slice(start, end)\n\n // Two-pass walk: locate each route decorator start, then balance-parse\n // forward through args and any stacked decorators to find the method\n // name. Replaces the previous single regex which mis-parsed nested\n // parens (forinda/kick-js#108).\n ROUTE_DECORATOR_START.lastIndex = 0\n let startMatch: RegExpExecArray | null\n while ((startMatch = ROUTE_DECORATOR_START.exec(block)) !== null) {\n const verb = startMatch[1]\n const decoratorStart = startMatch.index\n const openParen = ROUTE_DECORATOR_START.lastIndex - 1\n const closeParen = findBalancedClose(block, openParen)\n if (closeParen < 0) continue\n\n const routeArgs = block.slice(openParen + 1, closeParen)\n\n const pathLiteralMatch = routeArgs.match(/^\\s*['\"`]([^'\"`]*)['\"`]/)\n const path = pathLiteralMatch && pathLiteralMatch[1].length > 0 ? pathLiteralMatch[1] : '/'\n\n const methodInfo = readMethodAfterDecorators(block, closeParen + 1)\n if (!methodInfo) continue\n const { methodName, endPos } = methodInfo\n\n // Advance the regex iterator past this method so the next iteration\n // starts looking after the consumed region.\n ROUTE_DECORATOR_START.lastIndex = endPos\n\n const matchedText = block.slice(decoratorStart, endPos)\n const apiQp = extractApiQueryParams(matchedText, source)\n\n const bodyId = extractObjectFieldIdentifier(routeArgs, 'body')\n const queryId = extractObjectFieldIdentifier(routeArgs, 'query')\n const paramsId = extractObjectFieldIdentifier(routeArgs, 'params')\n\n // forinda/kick-js#235 §3 — when the controller is mounted under a path\n // with `:params` (e.g. `/orgs/:id/extensions`), surface those\n // params in `pathParams` so the typegen widens `ctx.params`\n // without adopters repeating `params: schema` on every route.\n const mountPath = mountPathByController.get(cls.className) ?? ''\n const fullPath = mountPath ? joinMountPath(mountPath, path) : path\n\n out.push({\n controller: cls.className,\n method: methodName,\n httpMethod: verb.toUpperCase() as DiscoveredRoute['httpMethod'],\n path,\n pathParams: extractPathParams(fullPath),\n queryFilterable: apiQp?.filterable ?? null,\n querySortable: apiQp?.sortable ?? null,\n querySearchable: apiQp?.searchable ?? null,\n bodySchema: bodyId\n ? { identifier: bodyId, source: resolveImportSource(source, bodyId) }\n : null,\n querySchema: queryId\n ? { identifier: queryId, source: resolveImportSource(source, queryId) }\n : null,\n paramsSchema: paramsId\n ? { identifier: paramsId, source: resolveImportSource(source, paramsId) }\n : null,\n filePath,\n relativePath: relPath,\n })\n }\n }\n\n return out\n}\n\n/** Extract `@Inject('literal')` calls from a single source file */\nexport function extractInjectsFromSource(\n source: string,\n filePath: string,\n cwd: string,\n): DiscoveredInject[] {\n const out: DiscoveredInject[] = []\n const relPath = toRelative(filePath, cwd)\n\n INJECT_LITERAL_REGEX.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = INJECT_LITERAL_REGEX.exec(source)) !== null) {\n out.push({ name: match[1], filePath, relativePath: relPath })\n }\n\n return out\n}\n\n/**\n * Extract the bounds of an object literal that begins at `openBracePos`\n * (the index of the `{` character). Returns the index of the matching `}`\n * or -1 if no match is found. Counts balanced braces only — does not\n * understand string literals so a `{` or `}` inside a string inside the\n * object will skew the depth counter (matches `findBalancedClose`).\n */\nfunction findBalancedBrace(text: string, openBracePos: number): number {\n let depth = 1\n for (let i = openBracePos + 1; i < text.length; i++) {\n const ch = text[i]\n if (ch === '{') depth++\n else if (ch === '}') {\n depth--\n if (depth === 0) return i\n }\n }\n return -1\n}\n\n/**\n * Extract plugins/adapters declared via `defineAdapter({ name: '...' })`\n * or `definePlugin({ name: '...' })` calls and via class-style adapters\n * (`class XxxAdapter implements AppAdapter` with a string-literal `name`\n * field).\n *\n * Only the literal `name:` field feeds the result — the symbol on the LHS\n * is irrelevant since `dependsOn` references the runtime name.\n */\nexport function extractPluginsAndAdaptersFromSource(\n source: string,\n filePath: string,\n cwd: string,\n): DiscoveredPluginOrAdapter[] {\n const out: DiscoveredPluginOrAdapter[] = []\n const relPath = toRelative(filePath, cwd)\n const seen = new Set<string>()\n\n // Pass 1: defineAdapter / definePlugin calls\n DEFINE_HELPER_START.lastIndex = 0\n let helperMatch: RegExpExecArray | null\n while ((helperMatch = DEFINE_HELPER_START.exec(source)) !== null) {\n const helper = helperMatch[1] as 'defineAdapter' | 'definePlugin'\n const openParen = DEFINE_HELPER_START.lastIndex - 1\n const closeParen = findBalancedClose(source, openParen)\n if (closeParen < 0) continue\n const callArgs = source.slice(openParen + 1, closeParen)\n // Look for the first `name: 'literal'` in the call args\n const nameMatch = /\\bname\\s*:\\s*['\"`]([^'\"`]+)['\"`]/.exec(callArgs)\n if (!nameMatch) continue\n const name = nameMatch[1]\n const dedupeKey = `${helper}::${name}::${filePath}`\n if (seen.has(dedupeKey)) continue\n seen.add(dedupeKey)\n out.push({\n kind: helper === 'definePlugin' ? 'plugin' : 'adapter',\n name,\n filePath,\n relativePath: relPath,\n })\n }\n\n // Pass 2: class-style adapters (`class X implements AppAdapter { name = 'X' }`)\n APP_ADAPTER_CLASS_REGEX.lastIndex = 0\n let classMatch: RegExpExecArray | null\n while ((classMatch = APP_ADAPTER_CLASS_REGEX.exec(source)) !== null) {\n const classStart = classMatch.index\n // Find the class body opening brace\n const bracePos = source.indexOf('{', classStart)\n if (bracePos < 0) continue\n const closeBrace = findBalancedBrace(source, bracePos)\n if (closeBrace < 0) continue\n const body = source.slice(bracePos + 1, closeBrace)\n const nameMatch = CLASS_NAME_FIELD_REGEX.exec(body)\n if (!nameMatch) continue\n const name = nameMatch[1]\n const dedupeKey = `class::${name}::${filePath}`\n if (seen.has(dedupeKey)) continue\n seen.add(dedupeKey)\n out.push({ kind: 'adapter', name, filePath, relativePath: relPath })\n }\n\n return out\n}\n\n/**\n * Extract context keys from `defineContextDecorator({ key: '...' })` and\n * `defineHttpContextDecorator({ key: '...' })` calls (including the\n * curried `.withParams<P>()({ key: '...' })` form). Only the literal\n * `key:` field feeds the result — the symbol on the LHS is irrelevant\n * since `dependsOn` references the runtime key string.\n *\n * Mirrors {@link extractPluginsAndAdaptersFromSource}: regex to the spec\n * object's opening paren, `findBalancedClose` to its end, then the first\n * `key: 'literal'` inside.\n */\nexport function extractContextKeysFromSource(\n source: string,\n filePath: string,\n cwd: string,\n): DiscoveredContextKey[] {\n const out: DiscoveredContextKey[] = []\n const relPath = toRelative(filePath, cwd)\n const seen = new Set<string>()\n\n CONTEXT_DECORATOR_START.lastIndex = 0\n // The match value itself is unused — we only need the loop to advance\n // and `lastIndex` to point just past the spec's opening paren.\n while (CONTEXT_DECORATOR_START.exec(source) !== null) {\n const openParen = CONTEXT_DECORATOR_START.lastIndex - 1\n const closeParen = findBalancedClose(source, openParen)\n if (closeParen < 0) continue\n const callArgs = source.slice(openParen + 1, closeParen)\n const keyMatch = /\\bkey\\s*:\\s*['\"`]([^'\"`]+)['\"`]/.exec(callArgs)\n if (!keyMatch) continue\n const key = keyMatch[1]\n if (seen.has(key)) continue\n seen.add(key)\n out.push({ key, filePath, relativePath: relPath })\n }\n\n return out\n}\n\n/**\n * Extract `defineAugmentation('Name', { description, example })` calls\n * from a source file. The metadata object is optional — when absent both\n * `description` and `example` resolve to `null`.\n */\nexport function extractAugmentationsFromSource(\n source: string,\n filePath: string,\n cwd: string,\n): DiscoveredAugmentation[] {\n const out: DiscoveredAugmentation[] = []\n const relPath = toRelative(filePath, cwd)\n\n DEFINE_AUGMENTATION_START.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = DEFINE_AUGMENTATION_START.exec(source)) !== null) {\n const name = match[1]\n let description: string | null = null\n let example: string | null = null\n\n // If the regex matched a metadata object opening (`, {`), parse it\n if (match[2]) {\n const bracePos = source.indexOf('{', match.index + match[0].length - 1)\n if (bracePos >= 0) {\n const closeBrace = findBalancedBrace(source, bracePos)\n if (closeBrace >= 0) {\n const body = source.slice(bracePos + 1, closeBrace)\n description = readStringField(body, 'description')\n example = readStringField(body, 'example')\n }\n }\n }\n\n out.push({ name, description, example, filePath, relativePath: relPath })\n }\n\n return out\n}\n\n/**\n * Pull a string-valued field out of a JS object-literal body, respecting\n * the opening quote so the value isn't truncated at the first foreign\n * quote character. Handles backslash escapes inside the literal.\n *\n * Why a custom parser instead of one regex per delimiter: real-world\n * `defineAugmentation` calls embed all three quote characters at once\n * — backtick template literals carrying TS shapes like\n * `'free' | 'pro'` (single quotes) AND `\\`ctx.get(...)\\`` (escaped\n * backticks). A character-class regex like `[^'\"`]+` truncates on the\n * first foreign quote it sees. This walker scans char-by-char from\n * the matched delimiter and only stops on the matching one.\n */\nfunction readStringField(body: string, field: string): string | null {\n // Locate `field:` followed by an opening quote. Tolerate any whitespace.\n const fieldRe = new RegExp(`\\\\b${field}\\\\s*:\\\\s*(['\"\\`])`, 'g')\n const m = fieldRe.exec(body)\n if (!m) return null\n const quote = m[1]\n const start = m.index + m[0].length\n let i = start\n let raw: string | null = null\n while (i < body.length) {\n const ch = body[i]\n if (ch === '\\\\') {\n // Skip the escaped char — supports \\`, \\', \\\", \\n, \\\\ etc.\n i += 2\n continue\n }\n if (ch === quote) {\n raw = body.slice(start, i)\n break\n }\n i++\n }\n if (raw === null) return null\n // Unescape JS string-literal escapes so the JSDoc renderer sees the\n // value the source author actually intended (`\\`` → `` ` ``, `\\'` →\n // `'`, etc). Without this, escaped backticks in a backtick template\n // literal would surface as literal backslashes in the catalogue.\n return raw.replace(/\\\\(.)/g, (_m, c) => {\n if (c === 'n') return '\\n'\n if (c === 't') return '\\t'\n if (c === 'r') return '\\r'\n return c\n })\n}\n\n/**\n * Default search order for the env schema file. Newer projects keep\n * the schema under `src/config/` so the framework's \"config\" concept\n * has a single home; older scaffolds dropped it at `src/env.ts` (kept\n * here for back-compat). The first match wins.\n */\nconst DEFAULT_ENV_FILE_CANDIDATES = [\n 'src/config/index.ts',\n 'src/config/env.ts',\n 'src/config.ts',\n 'src/env.ts',\n] as const\n\n/**\n * Look for an env schema file. When `envFile` is the string default\n * (`'src/env.ts'`) or omitted, every entry in `DEFAULT_ENV_FILE_CANDIDATES`\n * is tried in order. When the caller passes an explicit path, only that\n * path is tried (so projects can opt out of the search by setting\n * `kick.config.ts → typegen.envFile`).\n *\n * Returns a `DiscoveredEnv` if the file exists and contains both a\n * `defineEnv(...)` call and a default export — the two markers we\n * need before it's safe to emit `import type schema from '...'` in\n * the generator. Returns `null` for any other state (no candidate\n * found, no defineEnv, no default export) so the generator skips env\n * typing silently.\n */\nexport async function detectEnvFile(cwd: string, envFile: string): Promise<DiscoveredEnv | null> {\n // The CLI passes the literal default `'src/env.ts'` when the user\n // hasn't overridden it. Treat that as \"use the search list\" rather\n // than pinning to one path, so newer scaffolds at src/config/ keep\n // working without forcing every project to set typegen.envFile.\n const candidates: readonly string[] =\n envFile === 'src/env.ts' ? DEFAULT_ENV_FILE_CANDIDATES : [envFile]\n\n for (const candidate of candidates) {\n const abs = resolve(cwd, candidate)\n let source: string\n try {\n source = await readFile(abs, 'utf-8')\n } catch {\n continue\n }\n // Cheap heuristic: a schema-construction call AND a default\n // export. The default export must be the SCHEMA itself — the\n // generator emits `import type schema from '...'` and runs it\n // through schema-to-type inference. `loadEnvFromSchema(...)` is\n // deliberately NOT in the accept list because its return value is\n // the parsed env object, not the schema. Adopters routinely write\n // export default envSchema\n // export const env = loadEnvFromSchema(envSchema)\n // where only `envSchema` is the schema; detecting on\n // `loadEnvFromSchema` would also accept the anti-pattern\n // export default loadEnvFromSchema(schema)\n // which would push the parsed *env value* into `InferSchemaOutput`\n // and emit a broken `KickEnv`.\n //\n // Accept lists:\n // - `defineEnv(...)` — legacy Zod scaffold\n // - `fromZod / fromValibot / fromYup(...)` — kickjs-schema adapters\n if (!/\\bdefineEnv\\s*\\(/.test(source) && !/\\bfrom(Zod|Valibot|Yup)\\s*\\(/.test(source)) {\n continue\n }\n if (!/export\\s+default\\b/.test(source)) continue\n // Reject the \"default-export is the parsed env\" pattern\n // explicitly. Without this guard, a file that constructs the\n // schema with `fromZod(...)` and then does\n // `export default loadEnvFromSchema(envSchema)` would slip past\n // the schema-construction check above and feed the parsed env's\n // value type into `InferSchemaOutput`.\n if (/export\\s+default\\s+loadEnvFromSchema\\s*\\(/.test(source)) continue\n return {\n filePath: abs,\n relativePath: toRelative(abs, cwd),\n }\n }\n\n return null\n}\n\n/** Detect duplicate class names across files */\nexport function findCollisions(classes: DiscoveredClass[]): ClassCollision[] {\n const groups = new Map<string, DiscoveredClass[]>()\n for (const cls of classes) {\n const arr = groups.get(cls.className) ?? []\n arr.push(cls)\n groups.set(cls.className, arr)\n }\n\n const collisions: ClassCollision[] = []\n for (const [className, group] of groups) {\n // Two declarations of the same class name in different files = collision.\n // Multiple decorators on the same file/class are NOT a collision.\n const distinctFiles = new Set(group.map((c) => c.filePath))\n if (distinctFiles.size > 1) {\n collisions.push({ className, classes: group })\n }\n }\n\n // Deterministic order\n collisions.sort((a, b) => a.className.localeCompare(b.className))\n return collisions\n}\n\n/**\n * Scan a project for decorated classes, createToken definitions, and\n * `@Inject` literal usages.\n */\nexport async function scanProject(opts: ScanOptions): Promise<ScanResult> {\n const root = resolve(opts.root)\n const files = await walk(root, opts)\n\n const classes: DiscoveredClass[] = []\n const routes: DiscoveredRoute[] = []\n const tokens: DiscoveredToken[] = []\n const injects: DiscoveredInject[] = []\n const pluginsAndAdapters: DiscoveredPluginOrAdapter[] = []\n const augmentations: DiscoveredAugmentation[] = []\n const contextKeys: DiscoveredContextKey[] = []\n\n // Two passes: first collect all classes, then a second pass extracts\n // routes per file using the per-file class list as scoping context.\n // This keeps class discovery and route discovery independent.\n const sources = new Map<string, string>()\n for (const file of files) {\n let source: string\n try {\n source = await readFile(file, 'utf-8')\n } catch {\n continue\n }\n sources.set(file, source)\n classes.push(...extractClassesFromSource(source, file, opts.cwd))\n tokens.push(...extractTokensFromSource(source, file, opts.cwd))\n injects.push(...extractInjectsFromSource(source, file, opts.cwd))\n pluginsAndAdapters.push(...extractPluginsAndAdaptersFromSource(source, file, opts.cwd))\n augmentations.push(...extractAugmentationsFromSource(source, file, opts.cwd))\n contextKeys.push(...extractContextKeysFromSource(source, file, opts.cwd))\n }\n\n // forinda/kick-js#235 §3 — build a `Controller → mountPath` map from every\n // module file's `routes()` body so per-route `pathParams` can include\n // the prefix params (e.g. `/orgs/:id`) without adopters re-declaring\n // `params:` on every method. First mount wins on duplicates (rare\n // multi-mount controllers — typically share the prefix shape).\n const mountPathByController = new Map<string, string>()\n for (const [, source] of sources) {\n for (const { controller, mountPath } of extractModuleMounts(source)) {\n if (!mountPathByController.has(controller)) {\n mountPathByController.set(controller, mountPath)\n }\n }\n }\n\n for (const [file, source] of sources) {\n const classesInFile = classes.filter((c) => c.filePath === file)\n routes.push(\n ...extractRoutesFromSource(source, file, opts.cwd, classesInFile, mountPathByController),\n )\n }\n\n // forinda/kick-js#235 §4 — for every module file, extract its\n // `import.meta.glob([...])` patterns and flag any decorated class\n // whose file sits inside the module directory but isn't matched by\n // a positive pattern. Catches the \"added a new file type, forgot to\n // extend the glob\" silent-degradation case.\n // Normalize Windows backslashes to forward slashes before any\n // slicing / startsWith / glob-matching — the rest of the scanner\n // already speaks forward-slash relative paths, but absolute\n // `filePath` values may carry the platform separator on Windows.\n const orphanedClasses: OrphanedClass[] = []\n for (const [moduleFile, source] of sources) {\n if (!/\\.module\\.[mc]?[tj]sx?$/.test(moduleFile)) continue\n const patterns = extractGlobPatterns(source)\n if (patterns.length === 0) continue\n const moduleFilePosix = moduleFile.replaceAll(sep, '/')\n const moduleDir = moduleFilePosix.slice(0, moduleFilePosix.lastIndexOf('/'))\n for (const cls of classes) {\n // Skip module files themselves — they're scanner-synthesized\n // `decorator: 'Module'` entries that aren't glob contributors.\n if (cls.decorator === 'Module') continue\n const classFilePosix = cls.filePath.replaceAll(sep, '/')\n if (!classFilePosix.startsWith(moduleDir + '/')) continue\n if (classFilePosix === moduleFilePosix) continue\n const moduleRelative = classFilePosix.slice(moduleDir.length + 1)\n if (!fileMatchesAnyGlob(moduleRelative, patterns)) {\n orphanedClasses.push({\n className: cls.className,\n filePath: cls.filePath,\n relativePath: cls.relativePath,\n moduleFilePath: moduleFile,\n decorator: cls.decorator,\n })\n }\n }\n }\n\n // Deterministic ordering for stable .d.ts output\n classes.sort((a, b) => {\n if (a.className !== b.className) return a.className.localeCompare(b.className)\n return a.relativePath.localeCompare(b.relativePath)\n })\n tokens.sort(\n (a, b) => a.name.localeCompare(b.name) || a.relativePath.localeCompare(b.relativePath),\n )\n injects.sort(\n (a, b) => a.name.localeCompare(b.name) || a.relativePath.localeCompare(b.relativePath),\n )\n routes.sort(\n (a, b) => a.controller.localeCompare(b.controller) || a.method.localeCompare(b.method),\n )\n pluginsAndAdapters.sort(\n (a, b) => a.name.localeCompare(b.name) || a.relativePath.localeCompare(b.relativePath),\n )\n augmentations.sort(\n (a, b) => a.name.localeCompare(b.name) || a.relativePath.localeCompare(b.relativePath),\n )\n contextKeys.sort(\n (a, b) => a.key.localeCompare(b.key) || a.relativePath.localeCompare(b.relativePath),\n )\n\n const collisions = findCollisions(classes)\n const env = await detectEnvFile(opts.cwd, opts.envFile ?? 'src/env.ts')\n\n orphanedClasses.sort(\n (a, b) =>\n a.relativePath.localeCompare(b.relativePath) || a.className.localeCompare(b.className),\n )\n\n return {\n classes,\n routes,\n tokens,\n injects,\n collisions,\n env,\n pluginsAndAdapters,\n augmentations,\n contextKeys,\n orphanedClasses,\n }\n}\n","/**\n * Pure renderers for the DI-manifest typegen surface — the\n * `KickJsRegistry` augmentation, the `ServiceToken` / `ModuleToken`\n * unions, the `KickJsPluginRegistry` augmentation, and the\n * `defineAugmentation` catalogue.\n *\n * These used to live in `generator.ts` (the monolithic legacy pass).\n * They are now consumed by the per-domain builtin typegen plugins\n * (`kick/registry`, `kick/services`, `kick/modules`, `kick/plugins`,\n * `kick/augmentations`) so the whole pipeline is plugin-based and each\n * file is emitted + cache-tracked independently by the runner.\n *\n * Every function here is pure (scan data in → string out); no fs, no\n * config, no globals.\n *\n * @module @forinda/kickjs-cli/typegen/render/manifest\n */\n\nimport { dirname, relative, sep } from 'node:path'\nimport type {\n ClassCollision,\n DiscoveredAugmentation,\n DiscoveredClass,\n DiscoveredInject,\n DiscoveredPluginOrAdapter,\n DiscoveredToken,\n} from '../scanner'\n\n/** Header written to every generated file */\nexport const HEADER = `/* eslint-disable */\n// AUTO-GENERATED by \\`kick typegen\\`. DO NOT EDIT.\n// Re-run with \\`kick typegen\\` or rely on \\`kick dev\\` to refresh.\n`\n\n/** Decorators whose classes participate in the DI registry augmentation */\nexport const REGISTRY_DECORATORS = new Set(['Service', 'Repository', 'Injectable', 'Component'])\n\n/** Thrown when collisions are found and not allowed */\nexport class TokenCollisionError extends Error {\n readonly collisions: ClassCollision[]\n constructor(collisions: ClassCollision[]) {\n super(formatCollisionMessage(collisions))\n this.name = 'TokenCollisionError'\n this.collisions = collisions\n }\n}\n\n/** Build a human-readable message describing every collision */\nfunction formatCollisionMessage(collisions: ClassCollision[]): string {\n const lines: string[] = ['kick typegen: token collision detected']\n for (const c of collisions) {\n lines.push('')\n lines.push(` ${c.classes.length} classes named '${c.className}':`)\n for (const cls of c.classes) {\n lines.push(` - ${cls.relativePath}`)\n }\n }\n lines.push('')\n lines.push('Resolutions:')\n lines.push(' (a) Rename one of the classes')\n lines.push(\n \" (b) Use createToken<T>('namespaced/Name') and import the token explicitly — see @forinda/kickjs\",\n )\n lines.push(' (c) Pass --allow-duplicates to namespace the registry keys automatically')\n lines.push(\" (e.g. 'modules/users/UserService' instead of 'UserService')\")\n return lines.join('\\n')\n}\n\n/** Compute the module specifier (without extension) used inside `import('...')` */\nfunction importSpecifierFor(targetFile: string, fromFile: string): string {\n const fromDir = dirname(fromFile)\n let rel = relative(fromDir, targetFile).split(sep).join('/')\n rel = rel.replace(/\\.(ts|tsx|mts|cts)$/i, '')\n if (!rel.startsWith('.')) rel = './' + rel\n return rel\n}\n\n/**\n * Build the namespaced registry key for a colliding class.\n * Strips the `src/` prefix and the file extension, then appends the\n * class name. Example: `src/modules/users/user.service.ts` + `UserService`\n * → `modules/users/UserService`.\n */\nexport function namespacedKeyFor(cls: DiscoveredClass): string {\n const rel = cls.relativePath.replace(/^src\\//, '').replace(/\\.(ts|tsx|mts|cts)$/i, '')\n const parts = rel.split('/')\n parts.pop()\n const ns = parts.join('/')\n return ns ? `${ns}/${cls.className}` : cls.className\n}\n\n/**\n * Render the `KickJsRegistry` module augmentation. Each entry maps a\n * string token to the imported class type. Default-exported classes are\n * imported as `import('...').default`. `collidingNames` lists class\n * names that should be auto-namespaced; everything else gets a bare key.\n */\nexport function renderRegistry(\n classes: DiscoveredClass[],\n outFile: string,\n collidingNames: Set<string>,\n): string {\n const seen = new Set<string>()\n const entries: string[] = []\n\n for (const c of classes) {\n if (!REGISTRY_DECORATORS.has(c.decorator)) continue\n\n const key = collidingNames.has(c.className) ? namespacedKeyFor(c) : c.className\n if (seen.has(key)) continue\n seen.add(key)\n\n const spec = importSpecifierFor(c.filePath, outFile)\n const ref = c.isDefault ? `import('${spec}').default` : `import('${spec}').${c.className}`\n entries.push(` '${key}': ${ref}`)\n }\n\n const body = entries.length\n ? entries.join('\\n')\n : ' // (no services discovered yet — run `kick g service <name>` to add one)'\n\n return `${HEADER}\ndeclare module '@forinda/kickjs' {\n interface KickJsRegistry {\n${body}\n }\n}\n\nexport {}\n`\n}\n\n/** True when `str` is a bare JS identifier (usable as an unquoted key). */\nfunction isIdentifierKey(str: string): boolean {\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(str)\n}\n\n/**\n * Render the `ContextKeys` augmentation from discovered context-decorator\n * keys. Each `key:` literal becomes a `'<key>': true` entry — a key-only\n * registry (the value type is irrelevant; `true` is conventional) so\n * `dependsOn: ['<key>']` is autocompleted + typo-checked without forcing\n * a value type into `ContextMeta`. Non-identifier keys are quoted.\n */\nexport function renderContextKeys(keys: readonly { key: string }[]): string {\n const unique = [...new Set(keys.map((k) => k.key))].toSorted()\n const body = unique\n .map((k) => ` ${isIdentifierKey(k) ? k : JSON.stringify(k)}: true`)\n .join('\\n')\n return `${HEADER}\ndeclare module '@forinda/kickjs' {\n /**\n * Key-only registry of every context key produced by a\n * \\`defineContextDecorator\\` / \\`defineHttpContextDecorator\\` in the\n * project. Feeds \\`dependsOn\\` typo-checking. Value types live in\n * \\`ContextMeta\\`; this only records that the key exists.\n */\n interface ContextKeys {\n${body}\n }\n}\n\nexport {}\n`\n}\n\n/** Render a string-literal union type containing the given names */\nexport function renderUnion(typeName: string, names: string[], emptyComment: string): string {\n if (names.length === 0) {\n return `${HEADER}\n// ${emptyComment}\nexport type ${typeName} = never\n`\n }\n const sorted = [...new Set(names)].toSorted()\n return `${HEADER}\nexport type ${typeName} =\n${sorted.map((n) => ` | '${n}'`).join('\\n')}\n`\n}\n\n/**\n * Build the `ServiceToken` union members — class names (namespaced on\n * collision), plus `createToken('name')` and `@Inject('literal')`\n * literals — so tooling autocomplete sees every known token.\n */\nexport function buildServiceTokens(\n classes: DiscoveredClass[],\n tokens: DiscoveredToken[],\n injects: DiscoveredInject[],\n collidingNames: Set<string>,\n): string[] {\n const classTokens = classes\n .filter((c) => REGISTRY_DECORATORS.has(c.decorator))\n .map((c) => (collidingNames.has(c.className) ? namespacedKeyFor(c) : c.className))\n return [...classTokens, ...tokens.map((t) => t.name), ...injects.map((i) => i.name)]\n}\n\n/** Build the `ModuleToken` union members — discovered `@Module` class names. */\nexport function buildModuleTokens(classes: DiscoveredClass[]): string[] {\n return classes.filter((c) => c.decorator === 'Module').map((c) => c.className)\n}\n\n/**\n * Render the `KickJsPluginRegistry` augmentation. Each entry maps the\n * literal `name` field of a plugin/adapter to a marker type (the\n * registry value isn't load-bearing at runtime — `dependsOn` only cares\n * about `keyof`, so any non-`never` type works). We emit `'plugin'` /\n * `'adapter'` strings so DevTools can later read the registry to tell\n * the kinds apart without a second source of truth.\n *\n * When the project has no discoverable plugins/adapters, the\n * augmentation is intentionally empty rather than skipped so the\n * `keyof` constraint resolves to `never` (harmless — `dependsOn: []`\n * still works).\n */\nexport function renderPlugins(items: DiscoveredPluginOrAdapter[]): string {\n // Dedupe by name — two declarations with the same name are a runtime\n // boot-time error in `mount-sort.ts`; we surface the conflict via a\n // single registry entry rather than a duplicate-key TS error.\n const byName = new Map<string, DiscoveredPluginOrAdapter>()\n for (const item of items) {\n if (!byName.has(item.name)) byName.set(item.name, item)\n }\n\n const sorted = [...byName.values()].toSorted((a, b) => a.name.localeCompare(b.name))\n const entries = sorted.map((item) => ` '${item.name}': '${item.kind}'`).join('\\n')\n\n const body = entries\n ? entries\n : ' // (no plugins/adapters discovered yet — `defineAdapter`/`definePlugin` calls feed this)'\n\n return `${HEADER}\ndeclare module '@forinda/kickjs' {\n /**\n * Map of every plugin/adapter \\`name\\` discovered in the project. The\n * value type is the kind tag (\\`'plugin'\\` or \\`'adapter'\\`); the\n * \\`keyof\\` of this interface narrows \\`dependsOn\\` so misspelled deps\n * become compile errors instead of boot-time \\`MissingMountDepError\\`.\n */\n interface KickJsPluginRegistry {\n${body}\n }\n}\n\nexport {}\n`\n}\n\n/**\n * Render the augmentation manifest — one block per `defineAugmentation`\n * call discovered in the project. The output is a `.d.ts` file that does\n * nothing at runtime but acts as in-IDE documentation.\n */\nexport function renderAugmentations(items: DiscoveredAugmentation[]): string {\n if (items.length === 0) {\n return `${HEADER}\n// No augmentations discovered.\n//\n// Plugins advertise augmentable interfaces via:\n//\n// import { defineAugmentation } from '@forinda/kickjs'\n// defineAugmentation('FeatureFlags', {\n// description: 'Feature flag shape consumed by FlagsPlugin',\n// example: '{ beta: boolean; rolloutPercentage: number }',\n// })\n//\n// See \\`docs/guide/typegen.md#augmentations\\` for the full pattern.\nexport {}\n`\n }\n\n // Dedupe by name — multiple plugins shouldn't claim the same name,\n // but if they do we keep the first.\n const byName = new Map<string, DiscoveredAugmentation>()\n for (const item of items) {\n if (!byName.has(item.name)) byName.set(item.name, item)\n }\n\n const blocks: string[] = []\n for (const item of [...byName.values()].toSorted((a, b) => a.name.localeCompare(b.name))) {\n const docLines: string[] = []\n if (item.description) {\n for (const line of item.description.split('\\n')) docLines.push(` * ${line}`)\n }\n if (item.example) {\n docLines.push(` * @example`, ` * \\`\\`\\`ts`)\n for (const line of item.example.split('\\n')) docLines.push(` * ${line}`)\n docLines.push(` * \\`\\`\\``)\n }\n docLines.push(` * @see ${item.relativePath}`)\n blocks.push(\n ['/**', ...docLines, ' */', `export interface ${item.name}Augmentation {}`].join('\\n'),\n )\n }\n\n return `${HEADER}\n// Catalogue of augmentable interfaces in this project. The interfaces\n// below are documentation only — augment the source-of-truth interfaces\n// in your own \\`d.ts\\` files (the framework declares the actual types).\n\n${blocks.join('\\n\\n')}\n`\n}\n","/**\n * Token convention validator (architecture.md §22.4 #1).\n *\n * Warns (never errors) on `createToken('literal')` calls whose literal\n * doesn't match the convention from §22.2:\n *\n * <scope>/<PascalKey>[/<suffix>][:<instance>[:<extra>]*]\n *\n * Where `<scope>` is lowercase + may start with the reserved `kick/`\n * prefix for first-party tokens. Legacy framework tokens that started\n * with `kickjs.` (pre-§22) are exempt — they get migrated alongside\n * the first-party adapter migrations.\n *\n * The matching reserved-prefix check (third-party tokens squatting\n * `kick/`) is the responsibility of `@forinda/kickjs-lint`'s\n * `token-reserved-prefix` rule, not the typegen layer — different\n * audience (adopter codebase) and different default severity.\n *\n * @module @forinda/kickjs-cli/typegen/token-conventions\n */\n\nimport type { DiscoveredToken } from './scanner'\n\n/**\n * Regex for the §22.2 token shape. Breakdown:\n *\n * - `^(kick\\/)?` — optional reserved framework prefix.\n * - `([a-z][\\w-]*\\/[A-Z]\\w*)` — `<scope>/<PascalKey>`. Scope is\n * lowercase, key is PascalCase.\n * - `(\\/.+)?` — optional `/suffix` for sub-flavours\n * (e.g. `mycorp/Cache/redis`).\n * - `(:[a-z][\\w-]+(:[a-z][\\w-]+)*)?` — optional `:instance` (and\n * further `:extra` colon-sections) for `.scoped()` shards.\n */\nconst TOKEN_CONVENTION_REGEX =\n /^(kick\\/)?([a-z][\\w-]*\\/[A-Z]\\w*)(\\/.+)?(:[a-z][\\w-]+(:[a-z][\\w-]+)*)?$/\n\nconst LEGACY_PREFIX = 'kickjs.'\n\nexport interface TokenConventionWarning {\n token: string\n variable: string | null\n filePath: string\n reason: string\n suggestion?: string\n}\n\nexport function validateTokenConventions(\n tokens: readonly DiscoveredToken[],\n): TokenConventionWarning[] {\n const warnings: TokenConventionWarning[] = []\n for (const token of tokens) {\n const name = token.name\n if (name.startsWith(LEGACY_PREFIX)) continue\n if (TOKEN_CONVENTION_REGEX.test(name)) continue\n warnings.push({\n token: name,\n variable: token.variable,\n filePath: token.relativePath,\n reason: 'does not match `<scope>/<PascalKey>[/<suffix>][:<instance>]`',\n suggestion: suggestRename(name),\n })\n }\n return warnings\n}\n\nfunction suggestRename(name: string): string | undefined {\n if (/^[A-Z]\\w*$/.test(name)) {\n return `'<scope>/${name}' (e.g. 'mycorp/${name}')`\n }\n if (name.includes('.')) {\n return `consider '<scope>/PascalKey' instead of dotted form`\n }\n const slashLower = /^([a-z][\\w-]*)\\/([a-z]\\w*)$/.exec(name)\n if (slashLower) {\n const [, scope, key] = slashLower\n return `'${scope}/${key.charAt(0).toUpperCase()}${key.slice(1)}'`\n }\n return undefined\n}\n","/**\n * Walks every `assetMap` entry's source directory + emits a typed\n * `KickAssets` ambient augmentation (assets-plan.md PR 4). Generates\n * `.kickjs/types/assets.d.ts` so adopters get autocomplete on\n * `assets.<namespace>.<key>` and `@Asset('<namespace>/<key>')`.\n *\n * Pure module — no side effects beyond what the caller does with the\n * returned content. Mirrors the shape of `renderPlugins` /\n * `renderRegistry` in the generator so the typegen output stays\n * consistent across surfaces.\n *\n * @module @forinda/kickjs-cli/typegen/asset-types\n */\n\nimport { statSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { globSync } from 'glob'\nimport { groupAssetKeys } from '@forinda/kickjs'\nimport type { AssetMapEntry } from '../config'\n\nexport interface DiscoveredAssetEntry {\n namespace: string\n /**\n * Path under the namespace — what comes after the `<namespace>/`\n * prefix in the logical key. Stripped of extension when the file's\n * basename was unique within the namespace, or carries the full\n * extension when a collision was auto-resolved.\n */\n key: string\n}\n\nexport interface DiscoveredAssets {\n entries: DiscoveredAssetEntry[]\n count: number\n}\n\nexport function discoverAssets(\n assetMap: Record<string, AssetMapEntry> | undefined,\n cwd: string,\n): DiscoveredAssets {\n if (!assetMap) return { entries: [], count: 0 }\n\n const seen = new Map<string, DiscoveredAssetEntry>()\n for (const [namespace, entry] of Object.entries(assetMap)) {\n if (!entry || typeof entry.src !== 'string') continue\n const srcAbs = resolve(cwd, entry.src)\n if (!isDir(srcAbs)) continue\n // Mirror build.ts: same `glob` engine, same defaults — typegen +\n // build agree on what counts as an asset, including full minimatch\n // patterns (extglob, negation, etc.) that the old lite matcher\n // silently treated as match-all.\n const matches = globSync(entry.glob ?? '**/*', {\n cwd: srcAbs,\n nodir: true,\n dot: false,\n posix: true,\n })\n matches.sort()\n // Run the same key-shaping logic the build pipeline + dev resolver\n // use, so the emitted type matches the manifest exactly. Auto-mode\n // keeps extensions on collision groups; singletons stay stripped.\n const { pairs } = groupAssetKeys(namespace, matches, {\n strategy: entry.keys ?? 'auto',\n })\n for (const { key: logical } of pairs) {\n // The helper returns the full `<namespace>/<...>` form; strip\n // the namespace prefix so the entry shape matches the legacy\n // contract (key = path under namespace).\n const subKey = logical.slice(namespace.length + 1)\n seen.set(logical, { namespace, key: subKey })\n }\n }\n return { entries: [...seen.values()], count: seen.size }\n}\n\nexport function renderAssetTypes(discovered: DiscoveredAssets): string {\n const HEADER = `/* eslint-disable */\n// AUTO-GENERATED by \\`kick typegen\\`. DO NOT EDIT.\n// Re-run with \\`kick typegen\\` or rely on \\`kick dev\\` to refresh.\n`\n\n if (discovered.entries.length === 0) {\n return `${HEADER}\ndeclare module '@forinda/kickjs' {\n /**\n * Map of every typed asset discovered in the project's assetMap.\n * (No assetMap entries discovered yet — declare with\n * \\`assetMap: { name: { src: 'src/...' } }\\` in kick.config.ts.)\n */\n interface KickAssets {}\n}\n\nexport {}\n`\n }\n\n const tree: TreeNode = {}\n for (const entry of discovered.entries) {\n const path = `${entry.namespace}/${entry.key}`.split('/')\n let node = tree\n for (let i = 0; i < path.length - 1; i++) {\n const part = path[i]\n const existing = node[part]\n if (existing === LEAF) {\n // The intermediate path was previously claimed by a leaf —\n // collision between a file (`mails/welcome.ejs`) and a\n // directory (`mails/welcome/x.ejs`). Promote to a sub-tree\n // so the directory wins (consistent with the file-tree\n // semantics; the build pipeline emits a runtime warning for\n // the colliding leaf).\n const promoted: TreeNode = {}\n node[part] = promoted\n node = promoted\n } else {\n if (!existing) node[part] = {}\n node = node[part] as TreeNode\n }\n }\n const leaf = path[path.length - 1]\n if (typeof node[leaf] === 'object') {\n // The leaf collides with an already-built subtree. Skip; the\n // directory wins (matches the promote-to-subtree branch above).\n continue\n }\n node[leaf] = LEAF\n }\n\n const body = renderTree(tree, ' ')\n return `${HEADER}\ndeclare module '@forinda/kickjs' {\n /**\n * Map of every typed asset discovered in the project's assetMap.\n * Each leaf is a \\`() => string\\` thunk that returns the resolved\n * absolute path for the file in the current run mode (dev → src,\n * prod → dist).\n */\n interface KickAssets {\n${body}\n }\n}\n\nexport {}\n`\n}\n\nconst LEAF = Symbol('asset-leaf')\ntype TreeNode = { [key: string]: TreeNode | typeof LEAF }\n\nfunction renderTree(node: TreeNode, indent: string): string {\n const keys = Object.keys(node).toSorted()\n const lines: string[] = []\n for (const key of keys) {\n const child = node[key]\n const safeKey = isIdentifier(key) ? key : JSON.stringify(key)\n if (child === LEAF) {\n lines.push(`${indent}${safeKey}: () => string`)\n } else {\n lines.push(`${indent}${safeKey}: {`)\n lines.push(renderTree(child, `${indent} `))\n lines.push(`${indent}}`)\n }\n }\n return lines.join('\\n')\n}\n\nfunction isIdentifier(str: string): boolean {\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(str)\n}\n\nfunction isDir(path: string): boolean {\n try {\n return statSync(path).isDirectory()\n } catch {\n return false\n }\n}\n","/**\n * Public entry point for the KickJS typegen module.\n *\n * Used by:\n * - `kick typegen` (one-shot or watch mode)\n * - `kick dev` (auto-runs once before Vite starts; refreshes when files change)\n *\n * @module @forinda/kickjs-cli/typegen\n */\n\nimport { resolve, basename, dirname, join } from 'node:path'\nimport { mkdir, readdir, stat, unlink, writeFile } from 'node:fs/promises'\nimport { scanProject, type ScanResult } from './scanner'\nimport {\n buildModuleTokens,\n buildServiceTokens,\n REGISTRY_DECORATORS,\n TokenCollisionError,\n} from './render/manifest'\nimport { validateTokenConventions, type TokenConventionWarning } from './token-conventions'\nimport { discoverAssets } from './asset-types'\nimport type { AssetMapEntry } from '../config'\nimport type { TypegenPluginResult } from './plugin'\n\nexport type {\n DiscoveredClass,\n DiscoveredToken,\n DiscoveredInject,\n DiscoveredEnv,\n DiscoveredPluginOrAdapter,\n DiscoveredAugmentation,\n ClassCollision,\n ScanResult,\n} from './scanner'\nexport { TokenCollisionError } from './render/manifest'\nexport { validateTokenConventions, type TokenConventionWarning } from './token-conventions'\n\n/**\n * Result of a typegen run — useful for logging and tests. Computed from\n * the scan result + asset discovery; the per-file emission itself is now\n * owned entirely by the typegen plugins (see `builtin/`).\n */\nexport interface GenerateResult {\n /** Number of registry-decorated classes (KickJsRegistry entries) */\n registryEntries: number\n /** Number of unique service tokens (classes + createToken + @Inject literals) */\n serviceTokens: number\n /** Number of module tokens */\n moduleTokens: number\n /** Number of route entries */\n routeEntries: number\n /** Number of unique plugin/adapter names */\n pluginEntries: number\n /** Number of unique `defineAugmentation` calls */\n augmentationEntries: number\n /** Number of typed asset entries */\n assetEntries: number\n /** Whether a typed env augmentation will be emitted */\n envWritten: boolean\n /** Files written this pass (barrel + plugin outputs), for the sweep */\n written: string[]\n /** Number of collisions (only > 0 with allowDuplicates) */\n resolvedCollisions: number\n}\n\n/** Options for `runTypegen` */\nexport interface RunTypegenOptions {\n /** Project root (defaults to `process.cwd()`) */\n cwd?: string\n /** Source directory to scan (defaults to `src`) */\n srcDir?: string\n /** Output directory (defaults to `.kickjs/types`) */\n outDir?: string\n /** Suppress console output */\n silent?: boolean\n /**\n * When `true`, duplicate class names are auto-namespaced by file path\n * instead of throwing. `kick dev` enables this so the dev server is\n * never blocked by an in-progress rename. CLI default is `false` so\n * `kick typegen` (and CI) catches collisions early. */\n allowDuplicates?: boolean\n /**\n * Schema validator used to derive `body`/`query`/`params` types from\n * route metadata. Currently only `'zod'` is supported; `false` (the\n * default) leaves these fields as `unknown`. Loaded from\n * `kick.config.ts` `typegen.schemaValidator` when invoked via the CLI.\n */\n schemaValidator?: 'zod' | 'kickjs-schema' | false\n /**\n * Path to the env schema file (relative to `cwd`). The file must\n * default-export a `defineEnv(...)` schema for the typed `KickEnv`\n * augmentation to be emitted. Defaults to `'src/env.ts'`. Set to\n * `false` to disable env typing entirely.\n */\n envFile?: string | false\n /**\n * Asset map from `kick.config.ts`. When set, `runTypegen` walks\n * each entry's `src` directory + emits `.kickjs/types/assets.d.ts`\n * augmenting `KickAssets` for autocomplete on `assets.x.y()` and\n * `@Asset('x/y')`. Omit to skip the asset typegen pass entirely.\n */\n assetMap?: Record<string, AssetMapEntry>\n /**\n * Whether `runTypegen` should also run the TypegenPlugin pipeline\n * (`runAllPluginTypegens`) after the legacy generator pass. Defaults\n * to `true` so single-shot callers (kick g, commands/typegen, tests)\n * keep getting a fully-refreshed `.kickjs/types/` from one entry\n * point. `watchTypegen` flips this to `false` because it manages\n * the plugin pass itself + would otherwise double-run it on every\n * filesystem trigger.\n */\n runPlugins?: boolean\n}\n\n/** Resolve options to absolute paths */\nfunction resolveOptions(opts: RunTypegenOptions): {\n cwd: string\n srcDir: string\n outDir: string\n silent: boolean\n allowDuplicates: boolean\n schemaValidator: 'zod' | 'kickjs-schema' | false\n envFile: string | false\n} {\n const cwd = opts.cwd ?? process.cwd()\n return {\n cwd,\n srcDir: resolve(cwd, opts.srcDir ?? 'src'),\n outDir: resolve(cwd, opts.outDir ?? '.kickjs/types'),\n silent: opts.silent ?? false,\n allowDuplicates: opts.allowDuplicates ?? false,\n schemaValidator: opts.schemaValidator ?? false,\n envFile: opts.envFile ?? 'src/env.ts',\n }\n}\n\n/**\n * Run a single typegen pass: scan source files, generate `.d.ts` files.\n *\n * Returns the discovered scan result alongside the generation result so\n * callers (`kick dev`, devtools) can log them or feed them to other tools.\n *\n * Throws `TokenCollisionError` if duplicate class names are found and\n * `allowDuplicates` is false.\n */\nexport async function runTypegen(opts: RunTypegenOptions = {}): Promise<{\n scan: ScanResult\n result: GenerateResult\n /** Token convention warnings — empty when every literal matches §22.2. */\n tokenWarnings: TokenConventionWarning[]\n}> {\n const { cwd, srcDir, outDir, silent, allowDuplicates, envFile } = resolveOptions(opts)\n\n const start = Date.now()\n const scan = await scanProject({\n root: srcDir,\n cwd,\n // Pass through unless explicitly disabled\n envFile: envFile === false ? undefined : envFile,\n })\n\n // Collision gate. This used to live inside the legacy generator\n // (`generateTypes` threw before writing). Now that every file is\n // emitted by an isolated plugin, the gate must run here — at the\n // orchestration level, before any plugin executes — so a duplicate\n // class name fails the whole command loudly instead of being silently\n // namespaced by the registry plugin.\n if (scan.collisions.length > 0 && !allowDuplicates) {\n throw new TokenCollisionError(scan.collisions)\n }\n\n const assets = discoverAssets(opts.assetMap, cwd)\n\n // Every `.kickjs/types/*` file is now owned by a typegen plugin\n // (builtin/ + adopter plugins). Run the pipeline by default so\n // single-shot callers (kick g <leaf> / kick new / tests) stay on one\n // entry point and see a fully-refreshed `.kickjs/types/` after the\n // call returns. The `kick typegen` / `kick dev` / `kick build` /\n // watch paths opt out (`runPlugins: false`) because they drive the\n // plugin pass externally (for --check + per-plugin status) and would\n // otherwise double-run it — they call `writeTypegenArtifacts`\n // themselves after their own plugin pass.\n let pluginResults: TypegenPluginResult[] = []\n const written: string[] = []\n if (opts.runPlugins !== false) {\n try {\n const { runAllPluginTypegens } = await import('./run-plugins')\n const { loadKickConfig } = await import('../config')\n const pluginConfig = await loadKickConfig(cwd)\n pluginResults = await runAllPluginTypegens({ cwd, config: pluginConfig, silent: true })\n } catch (err) {\n // Broken plugins shouldn't block dev tooling. The runner already\n // isolates each plugin (per-plugin try/catch), so reaching here\n // means a non-plugin failure (scanner, fs). Surfacing as a throw\n // would abort the wider command (kick g, kick new); log + continue.\n if (!silent) {\n const msg = err instanceof Error ? err.message : String(err)\n console.warn(` kick typegen: plugin pipeline failed (${msg}) — continuing`)\n }\n }\n written.push(...(await writeTypegenArtifacts(outDir, pluginResults, silent)))\n }\n\n const tokenWarnings = validateTokenConventions(scan.tokens)\n const result = buildGenerateResult(scan, assets.count, written)\n const elapsed = Date.now() - start\n\n if (!silent) {\n const where = outDir.replace(cwd + '/', '')\n const collisionNote =\n result.resolvedCollisions > 0 ? `, ${result.resolvedCollisions} collisions namespaced` : ''\n const envNote = result.envWritten ? ', env typed' : ''\n const pluginNote = result.pluginEntries > 0 ? `, ${result.pluginEntries} plugins/adapters` : ''\n const augNote =\n result.augmentationEntries > 0 ? `, ${result.augmentationEntries} augmentations` : ''\n const assetNote = result.assetEntries > 0 ? `, ${result.assetEntries} assets` : ''\n console.log(\n ` kick typegen → ${result.serviceTokens} services, ${result.routeEntries} routes, ${result.moduleTokens} modules${pluginNote}${augNote}${assetNote}${envNote}${collisionNote} → ${where} (${elapsed}ms)`,\n )\n if (tokenWarnings.length > 0) {\n console.warn(\n ` kick typegen: ${tokenWarnings.length} token(s) don't match the §22.2 convention:`,\n )\n for (const warning of tokenWarnings) {\n const variableNote = warning.variable ? ` [${warning.variable}]` : ''\n console.warn(\n ` '${warning.token}' (${warning.filePath})${variableNote} — ${warning.reason}`,\n )\n if (warning.suggestion) {\n console.warn(` → suggestion: ${warning.suggestion}`)\n }\n }\n }\n if (scan.orphanedClasses.length > 0) {\n // forinda/kick-js#235 §4 — decorated classes sitting inside a\n // module directory whose globs don't pick them up. At runtime\n // the decorator never fires; downstream code paths get\n // confusing `MissingContributorError` or silent misroutes.\n console.warn(\n ` kick typegen: ${scan.orphanedClasses.length} decorated class(es) not matched by any module's import.meta.glob():`,\n )\n for (const orphan of scan.orphanedClasses) {\n console.warn(` @${orphan.decorator} ${orphan.className} (${orphan.relativePath})`)\n console.warn(` → not picked up by any glob in ${orphan.moduleFilePath}`)\n }\n }\n }\n\n return { scan, result, tokenWarnings }\n}\n\n/** Derive the logging/test `GenerateResult` from a scan — no files written here. */\nfunction buildGenerateResult(\n scan: ScanResult,\n assetCount: number,\n written: string[],\n): GenerateResult {\n const colliding = new Set(scan.collisions.map((c) => c.className))\n const registryClasses = scan.classes.filter((c) => REGISTRY_DECORATORS.has(c.decorator))\n const serviceNames = buildServiceTokens(scan.classes, scan.tokens, scan.injects, colliding)\n return {\n registryEntries: registryClasses.length,\n serviceTokens: new Set(serviceNames).size,\n moduleTokens: buildModuleTokens(scan.classes).length,\n routeEntries: scan.routes.length,\n pluginEntries: new Set(scan.pluginsAndAdapters.map((p) => p.name)).size,\n augmentationEntries: new Set(scan.augmentations.map((a) => a.name)).size,\n assetEntries: assetCount,\n envWritten: scan.env !== null,\n written,\n resolvedCollisions: scan.collisions.length,\n }\n}\n\n/**\n * Post-plugin-pass finalisation: write the `.kickjs/.gitignore` guard\n * and sweep stale legacy files. Shared by `runTypegen` (single-shot\n * mode) and the split-mode callers (`kick typegen` / `kick dev` /\n * watch) so the artifact-writing + sweep stay identical across both.\n *\n * No barrel `index.d.ts` is emitted: the scaffolded tsconfig pulls\n * `.kickjs/types/**` in via `include` globs, so every `declare module`\n * / `declare global` augmentation in the per-plugin files applies by\n * inclusion. The old barrel + its `ServiceToken`/`ModuleToken`\n * re-exports were redundant; they're swept as legacy orphans.\n *\n * Returns the list of files considered \"written\" this pass (the plugin\n * outputs) for the caller's bookkeeping.\n */\nexport async function writeTypegenArtifacts(\n outDir: string,\n pluginResults: readonly TypegenPluginResult[],\n silent: boolean,\n): Promise<string[]> {\n await mkdir(outDir, { recursive: true })\n // `.kickjs/.gitignore` keeps the generated tree out of git even if the\n // project's root .gitignore predates the `.kickjs/` convention.\n await writeFile(\n join(dirname(outDir), '.gitignore'),\n '# Auto-generated by kick typegen\\n*\\n',\n 'utf-8',\n )\n const written = pluginResults.filter((r) => r.outFile).map((r) => r.outFile as string)\n await sweepStaleTypegen(outDir, written, pluginResults, silent)\n return written\n}\n\n/**\n * Watch mode for `kick typegen --watch`.\n *\n * Uses Node's built-in `fs.watch` (recursive, available on Linux 22+ and\n * macOS 19+). Falls back gracefully if recursive watch is not supported.\n *\n * Debounces re-runs by 100ms so a bulk file change (e.g. `kick g module`\n * creating 5 files at once) emits one regen, not five.\n *\n * In watch mode collisions are reported but never thrown — the watcher\n * keeps running so the user can fix the rename and the next scan\n * recovers automatically.\n *\n * Returns a `stop()` function that closes the watcher.\n */\nexport async function watchTypegen(opts: RunTypegenOptions = {}): Promise<() => void> {\n const resolved = resolveOptions(opts)\n const { srcDir, silent, cwd } = resolved\n // Watch mode always tolerates collisions — otherwise an in-progress\n // rename would crash the dev loop. The error is still printed.\n // `runPlugins: false` keeps `runTypegen` from double-running the\n // plugin pipeline; the watcher invokes `runPlugins()` explicitly\n // after each `runLegacy()` so both phases land before the sweep.\n const runOpts: RunTypegenOptions = { ...resolved, allowDuplicates: true, runPlugins: false }\n\n // Polling is the right strategy for Docker bind mounts, WSL crosses,\n // NFS, and some kernel/filesystem combos where `fs.watch` returns\n // without errors but events silently drop. Adopters opt in via\n // `KICKJS_WATCH_POLLING=1`; default stays event-based (lower CPU).\n const forcePolling =\n process.env.KICKJS_WATCH_POLLING === '1' || process.env.KICKJS_WATCH_POLLING === 'true'\n\n // Lazy-import the plugin pipeline + config loader to avoid an eager\n // module-eval cycle (this file is reachable from plugin/builtins via\n // commands/typegen → ../typegen).\n const [{ runAllPluginTypegens }, { loadKickConfig }] = await Promise.all([\n import('./run-plugins'),\n import('../config'),\n ])\n const pluginConfig = await loadKickConfig(cwd)\n // `runLegacy` now just runs the scan + collision gate (collisions are\n // tolerated in watch mode via allowDuplicates) and refreshes the\n // logging counts; all file emission happens in `runPlugins`.\n const runLegacy = async () => {\n try {\n await runTypegen({ ...runOpts })\n } catch (err) {\n if (silent) return\n if (err instanceof TokenCollisionError) {\n console.error('\\n' + err.message + '\\n')\n } else {\n const msg = err instanceof Error ? err.message : String(err)\n console.error(` kick typegen failed: ${msg}`)\n }\n }\n }\n const runPlugins = async () => {\n try {\n const pluginResults = await runAllPluginTypegens({\n cwd,\n config: pluginConfig,\n silent: true,\n })\n await writeTypegenArtifacts(resolved.outDir, pluginResults, true)\n } catch {\n /* swallow — watcher must never die */\n }\n }\n\n // Initial run — scan/gate pass first, then plugin typegens + artifacts.\n await runLegacy()\n await runPlugins()\n\n const { watch } = await import('node:fs')\n\n let timer: ReturnType<typeof setTimeout> | null = null\n const trigger = (filename: string | null) => {\n // Only react to TypeScript source changes; ignore everything else\n if (!filename) return\n if (!/\\.(ts|tsx|mts|cts)$/.test(filename)) return\n if (filename.includes('.kickjs')) return\n if (filename.endsWith('.d.ts')) return\n\n if (timer) clearTimeout(timer)\n timer = setTimeout(() => {\n void runLegacy().then(runPlugins)\n }, 100)\n }\n\n // Forced-polling path — skip fs.watch entirely and just re-scan\n // periodically. The 2s interval matches the existing fallback so\n // adopters who flip the env var don't see a dramatic CPU jump.\n if (forcePolling) {\n if (!silent) {\n console.log(' kick typegen: polling mode (KICKJS_WATCH_POLLING)')\n }\n // Must drive BOTH phases — the plugin pass owns every\n // `.kickjs/types/kick__*` file now, so scan/gate alone (runLegacy)\n // would never refresh types on the polling path. Both closures\n // swallow their own errors, so the interval loop never dies.\n const interval = setInterval(() => {\n void runLegacy().then(runPlugins)\n }, 2000)\n return () => clearInterval(interval)\n }\n\n let watcher: ReturnType<typeof watch>\n try {\n watcher = watch(srcDir, { recursive: true }, (_event, filename) => {\n trigger(filename)\n })\n } catch (err: any) {\n if (!silent) {\n console.warn(\n ` kick typegen: watch mode unavailable (${err?.message ?? err}). Falling back to polling.`,\n )\n }\n // Polling fallback — re-run both phases every 2s (see forcePolling\n // note above: the plugin pass is the sole emitter, so scan/gate\n // alone would never refresh types).\n const interval = setInterval(() => {\n void runLegacy().then(runPlugins)\n }, 2000)\n return () => clearInterval(interval)\n }\n\n return () => {\n if (timer) clearTimeout(timer)\n watcher.close()\n }\n}\n\n/**\n * Remove orphaned typegen output. The legacy generator emitted\n * `assets.d.ts`, `env.ts`, and `routes.ts` directly; once those carved\n * into the `kick/assets`, `kick/env`, and `kick/routes` plugins, the\n * legacy file names became stale on disk for any project that had run\n * an older CLI. Without a sweep, both copies coexist and adopters get\n * duplicated `KickAssets` / `KickEnv` / `KickRoutes` augmentations.\n *\n * Strategy: enumerate the top level of `outDir`, keep the union of\n * generator-written files + plugin-written files, unlink anything\n * else. Subdirectories are left alone — typegen never creates them.\n * Errors are swallowed (silent → no log) so a transient ENOENT or\n * permission glitch never aborts the wider command.\n */\nexport async function sweepStaleTypegen(\n outDir: string,\n generatorWritten: readonly string[],\n pluginResults: readonly TypegenPluginResult[],\n silent: boolean,\n): Promise<string[]> {\n const expected = new Set<string>()\n for (const file of generatorWritten) expected.add(basename(file))\n for (const r of pluginResults) {\n if (r.outFile) expected.add(basename(r.outFile))\n }\n let entries: string[]\n try {\n entries = await readdir(outDir)\n } catch {\n return []\n }\n const removed: string[] = []\n for (const name of entries) {\n // Allowlist, NOT denylist. The earlier \"delete anything not in the\n // expected set\" form was a footgun: if the plugin pass aborted\n // (e.g. one plugin threw and the runner bubbled it up, so\n // `pluginResults` came back empty), the expected set lost\n // `kick__routes.ts` / `kick__assets.d.ts` / `kick__env.ts` and the\n // sweep deleted those good files — wiping controller route types\n // project-wide. We now only remove the specific pre-carve filenames\n // the legacy generator used to emit, and only when the current pass\n // didn't (re)write them. Anything else is left untouched.\n if (!LEGACY_ORPHAN_FILES.has(name)) continue\n if (expected.has(name)) continue\n const abs = resolve(outDir, name)\n try {\n const s = await stat(abs)\n if (!s.isFile()) continue\n await unlink(abs)\n removed.push(name)\n } catch {\n // Best-effort; don't crash the typegen pass on a stat/unlink miss.\n }\n }\n if (removed.length > 0 && !silent) {\n console.log(` kick typegen: swept ${removed.length} stale file(s): ${removed.join(', ')}`)\n }\n return removed\n}\n\n/**\n * Filenames the legacy monolithic generator (`generator.ts`, now\n * removed) emitted directly, before every augmentation became its own\n * typegen plugin. A project that upgrades across this change has these\n * as orphans on disk; the sweep removes them so the augmentations aren't\n * declared twice and the dropped `index.d.ts` barrel doesn't linger.\n *\n * Split into two waves only for documentation:\n * - `assets.d.ts` / `env.ts` / `routes.ts` — the M2.B-T8 carve.\n * - `registry.d.ts` / `services.d.ts` / `modules.d.ts` / `plugins.d.ts`\n * / `augmentations.d.ts` / `index.d.ts` — this plugin-only refactor.\n *\n * None collide with any current plugin output (all `kick__*`), so the\n * sweep can never touch live output.\n */\nconst LEGACY_ORPHAN_FILES: ReadonlySet<string> = new Set([\n 'assets.d.ts',\n 'env.ts',\n 'routes.ts',\n 'registry.d.ts',\n 'services.d.ts',\n 'modules.d.ts',\n 'plugins.d.ts',\n 'augmentations.d.ts',\n 'index.d.ts',\n])\n"],"mappings":"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"}
|
package/dist/types-DvYczI2m.mjs
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @forinda/kickjs-cli v5.11.1
|
|
3
|
-
*
|
|
4
|
-
* Copyright (c) Felix Orinda
|
|
5
|
-
*
|
|
6
|
-
* This source code is licensed under the MIT license found in the
|
|
7
|
-
* LICENSE file in the root directory of this source tree.
|
|
8
|
-
*
|
|
9
|
-
* @license MIT
|
|
10
|
-
*/
|
|
11
|
-
function e(e){return e}var t=class extends Error{constructor(e,t,n){super(`Two plugins registered the same ${e} '${t}': ${n.join(`, `)}. Plugins must use unique ${e} names.`),this.name=`KickPluginConflictError`}};export{e as n,t};
|
|
12
|
-
//# sourceMappingURL=types-DvYczI2m.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types-DvYczI2m.mjs","names":[],"sources":["../src/plugin/types.ts"],"sourcesContent":["// CLI Plugin shape.\n//\n// The kick CLI is itself a composition of plugins — every built-in\n// command (init, generate, run, typegen, db, …) ships as a KickCliPlugin\n// internally. Adopters extend the same surface from kick.config.ts to\n// add commands, generators, and typegens; the merging + conflict\n// detection runs the same way for built-ins and user plugins.\n//\n// Four contribution kinds:\n//\n// • commands[] — declarative shell-handler commands (same shape as\n// the existing kick.config.ts `commands` field).\n// • register() — programmatic commander registration. Called with\n// `(program, ctx)` so the callback has cwd + config\n// without re-loading.\n// • typegens[] — TypegenPlugin instances that `kick typegen` runs\n// after the legacy pass.\n// • generators[] — `kick g <name>` scaffolders (defineGenerator).\n// Replaces the `package.json > kickjs.generators`\n// discovery; that path stays as a deprecated\n// fallback for one minor version.\n//\n// Mirrors `definePlugin` / `defineAdapter` factory parity so adopters\n// don't have to learn a new helper-naming convention.\n\nimport type { Command } from 'commander'\n\nimport type { TypegenPlugin } from '../typegen/plugin'\nimport type { KickCommandDefinition, KickConfig } from '../config'\nimport type { GeneratorSpec } from '../generator-extension/define'\nimport type { DiscoveredGenerator } from '../generator-extension/discover'\n\n/**\n * Runtime context handed to `register()` — saves callbacks from\n * re-loading config or guessing cwd. Forward-compatible: future fields\n * land here without changing the callback signature.\n */\nexport interface KickCliPluginContext {\n /**\n * Working directory the CLI was invoked from. Plugin authors that need\n * a stable \"project base\" location (e.g. to write artifacts, resolve\n * config-relative paths) should prefer {@link projectRoot} over `cwd`\n * — `cwd` is whatever the adopter typed the command from, including\n * arbitrary subdirectories.\n */\n cwd: string\n /**\n * Resolved project root — the directory `loadKickConfig` walked up to\n * via `findProjectRoot()`. Always set to a usable absolute path: when\n * a `kick.config.{ts,js,mjs,json}` is found, that directory; otherwise\n * the nearest ancestor with a `package.json`; otherwise `cwd` itself.\n *\n * Populated by `mergeCliPlugins.register()` from the same resolution\n * that `cli.ts` performs at startup, so plugin authors get a coherent\n * view of the project root without re-walking the filesystem.\n */\n projectRoot: string\n /** Resolved kick.config.ts (null if the project has none). */\n config: KickConfig | null\n log: (msg: string) => void\n /**\n * Plugin-shipped generators merged from built-ins + adopter\n * `kick.config.ts > plugins[]`. Populated by `mergeCliPlugins` and\n * threaded through so `register()` callbacks (notably\n * `kick/generate`) can register each plugin generator as a real\n * Commander subcommand — without that, plugin generators only fire\n * via the bare-action dispatch and are invisible to `kick g --help`.\n *\n * Optional so light test harnesses that call `plugin.register(program)`\n * directly (no merge step) stay unaffected.\n */\n generators?: DiscoveredGenerator[]\n}\n\nexport interface KickCliPlugin {\n /** Stable identifier — used in error messages on conflict + de-dup. */\n name: string\n commands?: KickCommandDefinition[]\n /** Programmatic command registration. Called once at CLI startup. */\n register?: (program: Command, ctx: KickCliPluginContext) => void | Promise<void>\n typegens?: TypegenPlugin[]\n generators?: GeneratorSpec[]\n}\n\n/** Identity helper — exists for type inference + parity with definePlugin. */\nexport function defineCliPlugin(p: KickCliPlugin): KickCliPlugin {\n return p\n}\n\nexport class KickPluginConflictError extends Error {\n constructor(kind: 'plugin' | 'command' | 'typegen' | 'generator', id: string, owners: string[]) {\n super(\n `Two plugins registered the same ${kind} '${id}': ${owners.join(', ')}. ` +\n `Plugins must use unique ${kind} names.`,\n )\n this.name = 'KickPluginConflictError'\n }\n}\n"],"mappings":";;;;;;;;;;AAqFA,SAAgB,EAAgB,EAAiC,CAC/D,OAAO,CACT,CAEA,IAAa,EAAb,cAA6C,KAAM,CACjD,YAAY,EAAsD,EAAY,EAAkB,CAC9F,MACE,mCAAmC,EAAK,IAAI,EAAG,KAAK,EAAO,KAAK,IAAI,EAAE,4BACzC,EAAK,QACpC,EACA,KAAK,KAAO,yBACd,CACF"}
|