@forinda/kickjs-cli 3.1.2 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs-cli v3.1.2
2
+ * @forinda/kickjs-cli v3.2.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -970,4 +970,4 @@ async function runTypegen(opts = {}) {
970
970
  //#endregion
971
971
  export { runTypegen };
972
972
 
973
- //# sourceMappingURL=typegen-D-the-Kw.mjs.map
973
+ //# sourceMappingURL=typegen-C30frihW.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"typegen-D-the-Kw.mjs","names":[],"sources":["../src/typegen/scanner.ts","../src/typegen/generator.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/** 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}\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/** 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 * 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): 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 out.push({\n controller: cls.className,\n method: methodName,\n httpMethod: verb.toUpperCase() as DiscoveredRoute['httpMethod'],\n path,\n pathParams: extractPathParams(path),\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 * 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: defineEnv(...) call AND a default export.\n // We don't try to evaluate the file — the generator emits an\n // `import type schema from '...'` and lets the user's tsc do the\n // actual schema-to-type inference.\n if (!/\\bdefineEnv\\s*\\(/.test(source)) continue\n if (!/export\\s+default\\b/.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\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 }\n\n for (const [file, source] of sources) {\n const classesInFile = classes.filter((c) => c.filePath === file)\n routes.push(...extractRoutesFromSource(source, file, opts.cwd, classesInFile))\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\n const collisions = findCollisions(classes)\n const env = await detectEnvFile(opts.cwd, opts.envFile ?? 'src/env.ts')\n\n return { classes, routes, tokens, injects, collisions, env }\n}\n","/**\n * Generates `.d.ts` files inside `.kickjs/types/` from the discovered\n * decorated classes and DI tokens. Pattern modeled on React Router's\n * `.react-router/types/` directory.\n *\n * Outputs:\n * - `.kickjs/types/registry.d.ts` — module augmentation for `KickJsRegistry`\n * that gives `container.resolve('UserService')` the right return type.\n * - `.kickjs/types/services.d.ts` — string-literal union of all known\n * service-style tokens for tooling autocomplete.\n * - `.kickjs/types/modules.d.ts` — string-literal union of discovered\n * module class names.\n * - `.kickjs/types/index.d.ts` — re-exports the above (single import target).\n * - `.kickjs/.gitignore` — gitignores the whole folder so generated files\n * never get committed.\n *\n * ## Collision behaviour\n *\n * If `findCollisions()` returns any duplicate class names:\n * - **Default (`allowDuplicates: false`)** — `generateTypes` throws a\n * `TokenCollisionError` with a clear message listing every conflicting\n * file. The caller (CLI) prints it and exits non-zero. Nothing is\n * written to disk.\n * - **`allowDuplicates: true`** — colliding classes are auto-namespaced\n * by their relative file path so the registry keys become e.g.\n * `'modules/users/UserService'` instead of `'UserService'`. Non-colliding\n * classes still get bare `'ClassName'` keys (smart default).\n *\n * @module @forinda/kickjs-cli/typegen/generator\n */\n\nimport { mkdir, writeFile } from 'node:fs/promises'\nimport { dirname, join, relative, resolve, sep } from 'node:path'\nimport type {\n ClassCollision,\n DiscoveredClass,\n DiscoveredEnv,\n DiscoveredInject,\n DiscoveredRoute,\n DiscoveredToken,\n} from './scanner'\n\n/** Header written to every generated file */\nconst 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 */\nconst REGISTRY_DECORATORS = new Set(['Service', 'Repository', 'Injectable', 'Component'])\n\n/** Thrown by `generateTypes` 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 */\nfunction namespacedKeyFor(cls: DiscoveredClass): string {\n const rel = cls.relativePath.replace(/^src\\//, '').replace(/\\.(ts|tsx|mts|cts)$/i, '')\n // Drop the trailing filename if it's just the class in kebab/snake form —\n // keep the directory path as the namespace.\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.\n *\n * Default-exported classes are imported as `import('...').default`.\n *\n * `collidingNames` lists class names that should be auto-namespaced;\n * everything else gets a bare key.\n */\nfunction 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/** Render a string-literal union type containing the given names */\nfunction 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)].sort()\n return `${HEADER}\nexport type ${typeName} =\n${sorted.map((n) => ` | '${n}'`).join('\\n')}\n`\n}\n\n/** Render the barrel index that re-exports the union types */\nfunction renderIndex(includeEnv: boolean): string {\n const envImport = includeEnv ? \"import './env'\\n\" : ''\n return `${HEADER}\nexport type { ServiceToken } from './services'\nexport type { ModuleToken } from './modules'\n\n// The registry, routes, and env augmentations are loaded as side-effects —\n// importing this file (or having it on tsconfig include) is enough for\n// \\`container.resolve()\\`, \\`Ctx<KickRoutes.UserController['getUser']>\\`,\n// and \\`@Value('PORT')\\` to resolve.\nimport './registry'\nimport './routes'\n${envImport}`\n}\n\n/**\n * Render the `query` field's TypeScript type for a single route.\n *\n * - When `@ApiQueryParams` is absent (`queryFilterable === null`), emits\n * `unknown` so the user gets nothing extra.\n * - When the decorator is present, emits an object literal whose keys\n * are the standard query string keys (`filter`, `sort`, `q`, `page`,\n * `limit`). `sort` is narrowed to a string-literal union of allowed\n * field names with optional `-` direction prefix.\n */\nfunction renderQueryShape(m: DiscoveredRoute): string {\n if (m.queryFilterable === null) return 'unknown'\n const sortable = m.querySortable ?? []\n const sortType =\n sortable.length > 0 ? sortable.flatMap((f) => [`'${f}'`, `'-${f}'`]).join(' | ') : 'string'\n return `{ filter?: string | string[]; sort?: ${sortType}; q?: string; page?: string; limit?: string }`\n}\n\n/** Render JSDoc lines summarising the @ApiQueryParams whitelist */\nfunction renderQueryDocLines(m: DiscoveredRoute): string[] {\n const lines: string[] = []\n if (m.queryFilterable && m.queryFilterable.length > 0) {\n lines.push(`Filterable: ${m.queryFilterable.join(', ')}`)\n }\n if (m.querySortable && m.querySortable.length > 0) {\n lines.push(`Sortable: ${m.querySortable.join(', ')}`)\n }\n if (m.querySearchable && m.querySearchable.length > 0) {\n lines.push(`Searchable: ${m.querySearchable.join(', ')}`)\n }\n return lines\n}\n\n/**\n * Plan a schema import for hoisting at the top of `routes.ts`. Returns\n * the alias the in-namespace code should use, or `null` if the schema\n * cannot be referenced (no validator configured, or source unresolvable).\n *\n * Aliases are unique per (alias-counter) so two schemas named\n * `createTaskSchema` from different modules don't collide.\n */\nfunction planSchemaImport(\n schema: { identifier: string; source: string | null } | null,\n routeFilePath: string,\n routesOutFile: string,\n schemaValidator: 'zod' | false,\n imports: Map<string, { identifier: string; specifier: string }>,\n): string | null {\n if (!schema || schemaValidator !== 'zod') return null\n if (schema.source === null) return null\n const specifier = resolveSchemaImportSpecifier(schema.source, routeFilePath, routesOutFile)\n if (specifier === 'unknown') return null\n const key = `${specifier}::${schema.identifier}`\n let alias = imports.get(key)?.specifier\n if (!alias) {\n alias = `_S${imports.size}`\n imports.set(key, { identifier: schema.identifier, specifier: alias })\n } else {\n alias = imports.get(key)!.specifier\n }\n return alias\n}\n\n/** Build the `import type { ... } from '...'` lines for hoisted schema imports */\nfunction renderSchemaImports(\n imports: Map<string, { identifier: string; specifier: string }>,\n): string {\n if (imports.size === 0) return ''\n const lines: string[] = []\n for (const [key, value] of imports) {\n const [path] = key.split('::')\n lines.push(`import type { ${value.identifier} as ${value.specifier} } from '${path}'`)\n }\n return lines.join('\\n') + '\\n'\n}\n\n/**\n * Compute the import specifier the generated `routes.d.ts` should use to\n * reach a schema declared either in the controller file (empty string)\n * or imported from elsewhere (relative path or bare module name).\n *\n * - Bare module names (`zod`, `@scope/pkg`) are returned as-is.\n * - Relative paths (`./users.dto`, `../shared/schema`) are resolved\n * against the controller's file path, then re-relativised against the\n * directory containing `routes.d.ts`.\n * - Empty string (same-file schema) becomes a relative path from the\n * `routes.d.ts` directory back to the controller file.\n */\nfunction resolveSchemaImportSpecifier(\n source: string | null,\n routeFilePath: string,\n routesOutFile: string,\n): string {\n if (source === null) return 'unknown'\n const routesDir = dirname(routesOutFile)\n\n // Same-file schema — point at the controller file itself\n if (source === '') {\n let rel = relative(routesDir, routeFilePath).split(sep).join('/')\n rel = rel.replace(/\\.(ts|tsx|mts|cts)$/i, '')\n if (!rel.startsWith('.')) rel = './' + rel\n return rel\n }\n\n // Bare module name (no leading `.` and not absolute) → keep as-is\n if (!source.startsWith('.') && !source.startsWith('/')) {\n return source\n }\n\n // Relative import → resolve against the controller's directory, then\n // re-relativise against the routes.d.ts directory\n const controllerDir = dirname(routeFilePath)\n const absoluteTarget = resolve(controllerDir, source)\n let rel = relative(routesDir, absoluteTarget).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 * Render the `KickEnv` + `NodeJS.ProcessEnv` augmentation file from a\n * detected env schema. Mirrors the routes.ts pattern: emits as a `.ts`\n * file (not `.d.ts`) so the top-level `import type schema from '...'`\n * actually resolves under `moduleResolution: 'bundler'`.\n *\n * Returns `null` when no env file was discovered, so the caller can\n * skip writing the file altogether (rather than emitting an empty\n * augmentation that would shadow `KickEnv` to a useless `{}`).\n */\nfunction renderEnv(env: DiscoveredEnv | null, envOutFile: string): string | null {\n if (!env) return null\n // Compute the relative import path from .kickjs/types/env.ts back\n // to the user's env schema file, stripping the extension so TS can\n // resolve it.\n const envOutDir = dirname(envOutFile)\n let rel = relative(envOutDir, env.filePath).split(sep).join('/')\n rel = rel.replace(/\\.(ts|tsx|mts|cts)$/i, '')\n if (!rel.startsWith('.')) rel = './' + rel\n\n return `${HEADER}\n// Importing the schema as a type lets us infer its shape without\n// pulling in any runtime code. \\`Awaited<>\\` strips an accidental\n// Promise wrap on dynamic-imported defaults.\nimport type _envSchema from '${rel}'\n\n// Local type alias — interfaces can only \\`extend\\` an identifier,\n// not an inline import expression, so we resolve the schema's\n// inferred shape into a named type first.\ntype _KickEnvShape = import('zod').infer<typeof _envSchema>\n\ndeclare global {\n /**\n * Typed environment registry. Augmented from \\`${env.relativePath}\\`\n * so \\`@Value('PORT')\\`, \\`Env<'PORT'>\\`, and \\`process.env.PORT\\` are\n * all type-safe and autocomplete.\n */\n interface KickEnv extends _KickEnvShape {}\n\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace NodeJS {\n /**\n * Narrow \\`process.env\\` so known keys exist as \\`string\\` (the raw\n * pre-Zod-coercion form). \\`@Value\\` and the \\`ConfigService\\` apply\n * the schema's transforms internally; access \\`process.env\\` directly\n * only when you need the raw string. Unknown keys still resolve to\n * \\`string | undefined\\` via the base @types/node declaration.\n */\n interface ProcessEnv extends Record<keyof KickEnv, string> {}\n }\n}\n\nexport {}\n`\n}\n\n/**\n * Render the `KickRoutes` global namespace augmentation. Each interface\n * inside corresponds to a controller class; each property is a single\n * route method on that controller, conforming to `RouteShape`.\n *\n * Fills `params` from URL patterns, `query` from `@ApiQueryParams`, and\n * `body`/`query`/`params` (when schema-validated) from the configured\n * schema validator. `response` is emitted as `unknown`.\n */\nfunction renderRoutes(\n routes: DiscoveredRoute[],\n routesOutFile: string,\n schemaValidator: 'zod' | false,\n): string {\n if (routes.length === 0) {\n return `${HEADER}\n// (no routes discovered yet — annotate a controller method with\n// @Get/@Post/@Put/@Delete/@Patch and re-run \\`kick typegen\\`)\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace KickRoutes {}\n}\n\nexport {}\n`\n }\n\n // Group routes by controller for emission\n const byController = new Map<string, DiscoveredRoute[]>()\n for (const r of routes) {\n const arr = byController.get(r.controller) ?? []\n arr.push(r)\n byController.set(r.controller, arr)\n }\n\n // Hoisted schema imports — collected during interface rendering, then\n // emitted at the top of `routes.ts` so the in-namespace type references\n // resolve correctly. (Inline `import('...').X` inside `.d.ts` files\n // silently degrades to `unknown` with `moduleResolution: 'bundler'`.)\n const schemaImports = new Map<string, { identifier: string; specifier: string }>()\n\n const renderField = (\n schema: { identifier: string; source: string | null } | null,\n routeFilePath: string,\n ): string | null => {\n const alias = planSchemaImport(\n schema,\n routeFilePath,\n routesOutFile,\n schemaValidator,\n schemaImports,\n )\n return alias ? `import('zod').infer<typeof ${alias}>` : null\n }\n\n const interfaces: string[] = []\n for (const [controller, methods] of byController) {\n const lines: string[] = [` interface ${controller} {`]\n for (const m of methods) {\n // Empty `{}` (rather than `Record<string, never>`) so that accessing\n // an unknown property on a paramless route is a type error in strict\n // mode. `Record<string, never>` returns `never` for any access which\n // unfortunately is assignable to anything and silently passes.\n const urlParamsType =\n m.pathParams.length > 0 ? `{ ${m.pathParams.map((p) => `${p}: string`).join('; ')} }` : '{}'\n\n // Schema-driven types win over the URL-pattern / `unknown` defaults\n // when the user has wired a schema in the route decorator.\n const bodySchemaType = renderField(m.bodySchema, m.filePath)\n const querySchemaType = renderField(m.querySchema, m.filePath)\n const paramsSchemaType = renderField(m.paramsSchema, m.filePath)\n\n const paramsType = paramsSchemaType ?? urlParamsType\n const bodyType = bodySchemaType ?? 'unknown'\n const queryType = querySchemaType ?? renderQueryShape(m)\n const docLines = renderQueryDocLines(m)\n lines.push(\n ` /**`,\n ` * ${m.httpMethod} ${m.path}`,\n ...docLines.map((d) => ` * ${d}`),\n ` */`,\n ` ${m.method}: {`,\n ` params: ${paramsType}`,\n ` body: ${bodyType}`,\n ` query: ${queryType}`,\n ` response: unknown`,\n ` }`,\n )\n }\n lines.push(' }')\n interfaces.push(lines.join('\\n'))\n }\n\n const importBlock = renderSchemaImports(schemaImports)\n const interfaceBlock = interfaces.join('\\n')\n\n return `${HEADER}${importBlock}\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace KickRoutes {\n${interfaceBlock}\n }\n}\n\nexport {}\n`\n}\n\n/** Result of a typegen run — useful for logging and tests */\nexport interface GenerateResult {\n /** Number of registry entries written */\n registryEntries: number\n /** Number of service tokens (classes + createToken + @Inject literals) */\n serviceTokens: number\n /** Number of module tokens written */\n moduleTokens: number\n /** Number of route entries written into KickRoutes */\n routeEntries: number\n /** Whether a typed env augmentation was emitted */\n envWritten: boolean\n /** Files that were written */\n written: string[]\n /** Number of collisions that were auto-namespaced (only > 0 with allowDuplicates) */\n resolvedCollisions: number\n}\n\n/** Options for `generateTypes` */\nexport interface GenerateOptions {\n /** Discovered classes from the scanner */\n classes: DiscoveredClass[]\n /** Discovered route handlers from the scanner */\n routes?: DiscoveredRoute[]\n /** Discovered `createToken('name')` calls */\n tokens?: DiscoveredToken[]\n /** Discovered `@Inject('literal')` calls */\n injects?: DiscoveredInject[]\n /** Detected duplicate class names from the scanner */\n collisions?: ClassCollision[]\n /** Discovered env schema file (or null if none) */\n env?: DiscoveredEnv | null\n /** Output directory (typically `<cwd>/.kickjs/types`) */\n outDir: string\n /**\n * When `true`, colliding class names are auto-namespaced by file path\n * instead of throwing. Default: `false`.\n */\n allowDuplicates?: boolean\n /**\n * Schema validator the project uses. When `'zod'`, the generator\n * emits `z.infer<typeof import('...').schema>` for any route whose\n * decorator declared a body/query/params schema identifier that\n * could be statically resolved. When `false` (or omitted), schemas\n * are ignored and `body`/`query`/`params` keep their `unknown`\n * placeholders.\n *\n * Future: `'joi'`, `'yup'`, `'json-schema'`, custom adapters.\n */\n schemaValidator?: 'zod' | false\n}\n\n/** Write all generated `.d.ts` files to `outDir` */\nexport async function generateTypes(opts: GenerateOptions): Promise<GenerateResult> {\n const {\n classes,\n routes = [],\n tokens = [],\n injects = [],\n collisions = [],\n env = null,\n outDir,\n allowDuplicates = false,\n schemaValidator = false,\n } = opts\n\n if (collisions.length > 0 && !allowDuplicates) {\n throw new TokenCollisionError(collisions)\n }\n\n await mkdir(outDir, { recursive: true })\n\n const registryFile = join(outDir, 'registry.d.ts')\n const servicesFile = join(outDir, 'services.d.ts')\n const modulesFile = join(outDir, 'modules.d.ts')\n // routes.ts (NOT .d.ts) — TypeScript silently degrades top-level\n // imports inside `.d.ts` files to `unknown` when the user's tsconfig\n // has `moduleResolution: 'bundler'`. Emitting as a regular `.ts` file\n // makes the schema imports resolve correctly so `z.infer<typeof X>`\n // produces a proper type. The file contains only type declarations\n // so it has zero runtime impact.\n const routesFile = join(outDir, 'routes.ts')\n // env.ts (same .ts vs .d.ts story as routes.ts)\n const envFile = join(outDir, 'env.ts')\n const indexFile = join(outDir, 'index.d.ts')\n\n const collidingNames = new Set(collisions.map((c) => c.className))\n const registryContent = renderRegistry(classes, registryFile, collidingNames)\n\n // ServiceToken union — combines class names, createToken literals, and\n // @Inject literals so tooling autocomplete sees every known token.\n const classTokens = classes\n .filter((c) => REGISTRY_DECORATORS.has(c.decorator))\n .map((c) => (collidingNames.has(c.className) ? namespacedKeyFor(c) : c.className))\n const tokenLiterals = tokens.map((t) => t.name)\n const injectLiterals = injects.map((i) => i.name)\n const allServices = [...classTokens, ...tokenLiterals, ...injectLiterals]\n\n const modules = classes.filter((c) => c.decorator === 'Module').map((c) => c.className)\n\n const servicesContent = renderUnion(\n 'ServiceToken',\n allServices,\n '(no tokens discovered — declare with createToken<T>() or `kick g service <name>`)',\n )\n const modulesContent = renderUnion(\n 'ModuleToken',\n modules,\n '(no @Module classes discovered — `kick g module <name>` to add one)',\n )\n const routesContent = renderRoutes(routes, routesFile, schemaValidator)\n const envContent = renderEnv(env, envFile)\n const indexContent = renderIndex(envContent !== null)\n\n await writeFile(registryFile, registryContent, 'utf-8')\n await writeFile(servicesFile, servicesContent, 'utf-8')\n await writeFile(modulesFile, modulesContent, 'utf-8')\n await writeFile(routesFile, routesContent, 'utf-8')\n await writeFile(indexFile, indexContent, 'utf-8')\n\n const written = [registryFile, servicesFile, modulesFile, routesFile, indexFile]\n if (envContent) {\n await writeFile(envFile, envContent, 'utf-8')\n written.push(envFile)\n }\n\n // Write `.gitignore` at the .kickjs root (one level up from outDir)\n const kickjsRoot = dirname(outDir)\n await writeFile(join(kickjsRoot, '.gitignore'), '# Auto-generated by kick typegen\\n*\\n', 'utf-8')\n\n return {\n registryEntries: classTokens.length,\n serviceTokens: new Set(allServices).size,\n moduleTokens: modules.length,\n routeEntries: routes.length,\n envWritten: envContent !== null,\n written,\n resolvedCollisions: collisions.length,\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 } from 'node:path'\nimport { scanProject, type ScanResult } from './scanner'\nimport { generateTypes, type GenerateResult, TokenCollisionError } from './generator'\n\nexport type {\n DiscoveredClass,\n DiscoveredToken,\n DiscoveredInject,\n DiscoveredEnv,\n ClassCollision,\n ScanResult,\n} from './scanner'\nexport type { GenerateResult } from './generator'\nexport { TokenCollisionError } from './generator'\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' | 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\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' | 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}> {\n const { cwd, srcDir, outDir, silent, allowDuplicates, schemaValidator, envFile } =\n 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 const result = await generateTypes({\n classes: scan.classes,\n routes: scan.routes,\n tokens: scan.tokens,\n injects: scan.injects,\n collisions: scan.collisions,\n env: envFile === false ? null : scan.env,\n outDir,\n allowDuplicates,\n schemaValidator,\n })\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 console.log(\n ` kick typegen → ${result.serviceTokens} services, ${result.routeEntries} routes, ${result.moduleTokens} modules${envNote}${collisionNote} → ${where} (${elapsed}ms)`,\n )\n }\n\n return { scan, result }\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 } = resolved\n // Watch mode always tolerates collisions — otherwise an in-progress\n // rename would crash the dev loop. The error is still printed.\n const runOpts: RunTypegenOptions = { ...resolved, allowDuplicates: true }\n\n // Initial run\n await safeRun(runOpts, silent)\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 safeRun(runOpts, silent)\n }, 100)\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-scan every 2s\n const interval = setInterval(() => {\n safeRun({ ...runOpts, silent: true }, true)\n }, 2000)\n return () => clearInterval(interval)\n }\n\n return () => {\n if (timer) clearTimeout(timer)\n watcher.close()\n }\n}\n\n/** Run typegen swallowing errors so the watcher loop never dies */\nasync function safeRun(opts: RunTypegenOptions, silent: boolean): Promise<void> {\n try {\n await runTypegen(opts)\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"],"mappings":"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"}
1
+ {"version":3,"file":"typegen-C30frihW.mjs","names":[],"sources":["../src/typegen/scanner.ts","../src/typegen/generator.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/** 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}\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/** 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 * 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): 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 out.push({\n controller: cls.className,\n method: methodName,\n httpMethod: verb.toUpperCase() as DiscoveredRoute['httpMethod'],\n path,\n pathParams: extractPathParams(path),\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 * 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: defineEnv(...) call AND a default export.\n // We don't try to evaluate the file — the generator emits an\n // `import type schema from '...'` and lets the user's tsc do the\n // actual schema-to-type inference.\n if (!/\\bdefineEnv\\s*\\(/.test(source)) continue\n if (!/export\\s+default\\b/.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\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 }\n\n for (const [file, source] of sources) {\n const classesInFile = classes.filter((c) => c.filePath === file)\n routes.push(...extractRoutesFromSource(source, file, opts.cwd, classesInFile))\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\n const collisions = findCollisions(classes)\n const env = await detectEnvFile(opts.cwd, opts.envFile ?? 'src/env.ts')\n\n return { classes, routes, tokens, injects, collisions, env }\n}\n","/**\n * Generates `.d.ts` files inside `.kickjs/types/` from the discovered\n * decorated classes and DI tokens. Pattern modeled on React Router's\n * `.react-router/types/` directory.\n *\n * Outputs:\n * - `.kickjs/types/registry.d.ts` — module augmentation for `KickJsRegistry`\n * that gives `container.resolve('UserService')` the right return type.\n * - `.kickjs/types/services.d.ts` — string-literal union of all known\n * service-style tokens for tooling autocomplete.\n * - `.kickjs/types/modules.d.ts` — string-literal union of discovered\n * module class names.\n * - `.kickjs/types/index.d.ts` — re-exports the above (single import target).\n * - `.kickjs/.gitignore` — gitignores the whole folder so generated files\n * never get committed.\n *\n * ## Collision behaviour\n *\n * If `findCollisions()` returns any duplicate class names:\n * - **Default (`allowDuplicates: false`)** — `generateTypes` throws a\n * `TokenCollisionError` with a clear message listing every conflicting\n * file. The caller (CLI) prints it and exits non-zero. Nothing is\n * written to disk.\n * - **`allowDuplicates: true`** — colliding classes are auto-namespaced\n * by their relative file path so the registry keys become e.g.\n * `'modules/users/UserService'` instead of `'UserService'`. Non-colliding\n * classes still get bare `'ClassName'` keys (smart default).\n *\n * @module @forinda/kickjs-cli/typegen/generator\n */\n\nimport { mkdir, writeFile } from 'node:fs/promises'\nimport { dirname, join, relative, resolve, sep } from 'node:path'\nimport type {\n ClassCollision,\n DiscoveredClass,\n DiscoveredEnv,\n DiscoveredInject,\n DiscoveredRoute,\n DiscoveredToken,\n} from './scanner'\n\n/** Header written to every generated file */\nconst 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 */\nconst REGISTRY_DECORATORS = new Set(['Service', 'Repository', 'Injectable', 'Component'])\n\n/** Thrown by `generateTypes` 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 */\nfunction namespacedKeyFor(cls: DiscoveredClass): string {\n const rel = cls.relativePath.replace(/^src\\//, '').replace(/\\.(ts|tsx|mts|cts)$/i, '')\n // Drop the trailing filename if it's just the class in kebab/snake form —\n // keep the directory path as the namespace.\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.\n *\n * Default-exported classes are imported as `import('...').default`.\n *\n * `collidingNames` lists class names that should be auto-namespaced;\n * everything else gets a bare key.\n */\nfunction 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/** Render a string-literal union type containing the given names */\nfunction 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)].sort()\n return `${HEADER}\nexport type ${typeName} =\n${sorted.map((n) => ` | '${n}'`).join('\\n')}\n`\n}\n\n/** Render the barrel index that re-exports the union types */\nfunction renderIndex(includeEnv: boolean): string {\n const envImport = includeEnv ? \"import './env'\\n\" : ''\n return `${HEADER}\nexport type { ServiceToken } from './services'\nexport type { ModuleToken } from './modules'\n\n// The registry, routes, and env augmentations are loaded as side-effects —\n// importing this file (or having it on tsconfig include) is enough for\n// \\`container.resolve()\\`, \\`Ctx<KickRoutes.UserController['getUser']>\\`,\n// and \\`@Value('PORT')\\` to resolve.\nimport './registry'\nimport './routes'\n${envImport}`\n}\n\n/**\n * Render the `query` field's TypeScript type for a single route.\n *\n * - When `@ApiQueryParams` is absent (`queryFilterable === null`), emits\n * `unknown` so the user gets nothing extra.\n * - When the decorator is present, emits an object literal whose keys\n * are the standard query string keys (`filter`, `sort`, `q`, `page`,\n * `limit`). `sort` is narrowed to a string-literal union of allowed\n * field names with optional `-` direction prefix.\n */\nfunction renderQueryShape(m: DiscoveredRoute): string {\n if (m.queryFilterable === null) return 'unknown'\n const sortable = m.querySortable ?? []\n const sortType =\n sortable.length > 0 ? sortable.flatMap((f) => [`'${f}'`, `'-${f}'`]).join(' | ') : 'string'\n return `{ filter?: string | string[]; sort?: ${sortType}; q?: string; page?: string; limit?: string }`\n}\n\n/** Render JSDoc lines summarising the @ApiQueryParams whitelist */\nfunction renderQueryDocLines(m: DiscoveredRoute): string[] {\n const lines: string[] = []\n if (m.queryFilterable && m.queryFilterable.length > 0) {\n lines.push(`Filterable: ${m.queryFilterable.join(', ')}`)\n }\n if (m.querySortable && m.querySortable.length > 0) {\n lines.push(`Sortable: ${m.querySortable.join(', ')}`)\n }\n if (m.querySearchable && m.querySearchable.length > 0) {\n lines.push(`Searchable: ${m.querySearchable.join(', ')}`)\n }\n return lines\n}\n\n/**\n * Plan a schema import for hoisting at the top of `routes.ts`. Returns\n * the alias the in-namespace code should use, or `null` if the schema\n * cannot be referenced (no validator configured, or source unresolvable).\n *\n * Aliases are unique per (alias-counter) so two schemas named\n * `createTaskSchema` from different modules don't collide.\n */\nfunction planSchemaImport(\n schema: { identifier: string; source: string | null } | null,\n routeFilePath: string,\n routesOutFile: string,\n schemaValidator: 'zod' | false,\n imports: Map<string, { identifier: string; specifier: string }>,\n): string | null {\n if (!schema || schemaValidator !== 'zod') return null\n if (schema.source === null) return null\n const specifier = resolveSchemaImportSpecifier(schema.source, routeFilePath, routesOutFile)\n if (specifier === 'unknown') return null\n const key = `${specifier}::${schema.identifier}`\n let alias = imports.get(key)?.specifier\n if (!alias) {\n alias = `_S${imports.size}`\n imports.set(key, { identifier: schema.identifier, specifier: alias })\n } else {\n alias = imports.get(key)!.specifier\n }\n return alias\n}\n\n/** Build the `import type { ... } from '...'` lines for hoisted schema imports */\nfunction renderSchemaImports(\n imports: Map<string, { identifier: string; specifier: string }>,\n): string {\n if (imports.size === 0) return ''\n const lines: string[] = []\n for (const [key, value] of imports) {\n const [path] = key.split('::')\n lines.push(`import type { ${value.identifier} as ${value.specifier} } from '${path}'`)\n }\n return lines.join('\\n') + '\\n'\n}\n\n/**\n * Compute the import specifier the generated `routes.d.ts` should use to\n * reach a schema declared either in the controller file (empty string)\n * or imported from elsewhere (relative path or bare module name).\n *\n * - Bare module names (`zod`, `@scope/pkg`) are returned as-is.\n * - Relative paths (`./users.dto`, `../shared/schema`) are resolved\n * against the controller's file path, then re-relativised against the\n * directory containing `routes.d.ts`.\n * - Empty string (same-file schema) becomes a relative path from the\n * `routes.d.ts` directory back to the controller file.\n */\nfunction resolveSchemaImportSpecifier(\n source: string | null,\n routeFilePath: string,\n routesOutFile: string,\n): string {\n if (source === null) return 'unknown'\n const routesDir = dirname(routesOutFile)\n\n // Same-file schema — point at the controller file itself\n if (source === '') {\n let rel = relative(routesDir, routeFilePath).split(sep).join('/')\n rel = rel.replace(/\\.(ts|tsx|mts|cts)$/i, '')\n if (!rel.startsWith('.')) rel = './' + rel\n return rel\n }\n\n // Bare module name (no leading `.` and not absolute) → keep as-is\n if (!source.startsWith('.') && !source.startsWith('/')) {\n return source\n }\n\n // Relative import → resolve against the controller's directory, then\n // re-relativise against the routes.d.ts directory\n const controllerDir = dirname(routeFilePath)\n const absoluteTarget = resolve(controllerDir, source)\n let rel = relative(routesDir, absoluteTarget).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 * Render the `KickEnv` + `NodeJS.ProcessEnv` augmentation file from a\n * detected env schema. Mirrors the routes.ts pattern: emits as a `.ts`\n * file (not `.d.ts`) so the top-level `import type schema from '...'`\n * actually resolves under `moduleResolution: 'bundler'`.\n *\n * Returns `null` when no env file was discovered, so the caller can\n * skip writing the file altogether (rather than emitting an empty\n * augmentation that would shadow `KickEnv` to a useless `{}`).\n */\nfunction renderEnv(env: DiscoveredEnv | null, envOutFile: string): string | null {\n if (!env) return null\n // Compute the relative import path from .kickjs/types/env.ts back\n // to the user's env schema file, stripping the extension so TS can\n // resolve it.\n const envOutDir = dirname(envOutFile)\n let rel = relative(envOutDir, env.filePath).split(sep).join('/')\n rel = rel.replace(/\\.(ts|tsx|mts|cts)$/i, '')\n if (!rel.startsWith('.')) rel = './' + rel\n\n return `${HEADER}\n// Importing the schema as a type lets us infer its shape without\n// pulling in any runtime code. \\`Awaited<>\\` strips an accidental\n// Promise wrap on dynamic-imported defaults.\nimport type _envSchema from '${rel}'\n\n// Local type alias — interfaces can only \\`extend\\` an identifier,\n// not an inline import expression, so we resolve the schema's\n// inferred shape into a named type first.\ntype _KickEnvShape = import('zod').infer<typeof _envSchema>\n\ndeclare global {\n /**\n * Typed environment registry. Augmented from \\`${env.relativePath}\\`\n * so \\`@Value('PORT')\\`, \\`Env<'PORT'>\\`, and \\`process.env.PORT\\` are\n * all type-safe and autocomplete.\n */\n interface KickEnv extends _KickEnvShape {}\n\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace NodeJS {\n /**\n * Narrow \\`process.env\\` so known keys exist as \\`string\\` (the raw\n * pre-Zod-coercion form). \\`@Value\\` and the \\`ConfigService\\` apply\n * the schema's transforms internally; access \\`process.env\\` directly\n * only when you need the raw string. Unknown keys still resolve to\n * \\`string | undefined\\` via the base @types/node declaration.\n */\n interface ProcessEnv extends Record<keyof KickEnv, string> {}\n }\n}\n\nexport {}\n`\n}\n\n/**\n * Render the `KickRoutes` global namespace augmentation. Each interface\n * inside corresponds to a controller class; each property is a single\n * route method on that controller, conforming to `RouteShape`.\n *\n * Fills `params` from URL patterns, `query` from `@ApiQueryParams`, and\n * `body`/`query`/`params` (when schema-validated) from the configured\n * schema validator. `response` is emitted as `unknown`.\n */\nfunction renderRoutes(\n routes: DiscoveredRoute[],\n routesOutFile: string,\n schemaValidator: 'zod' | false,\n): string {\n if (routes.length === 0) {\n return `${HEADER}\n// (no routes discovered yet — annotate a controller method with\n// @Get/@Post/@Put/@Delete/@Patch and re-run \\`kick typegen\\`)\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace KickRoutes {}\n}\n\nexport {}\n`\n }\n\n // Group routes by controller for emission\n const byController = new Map<string, DiscoveredRoute[]>()\n for (const r of routes) {\n const arr = byController.get(r.controller) ?? []\n arr.push(r)\n byController.set(r.controller, arr)\n }\n\n // Hoisted schema imports — collected during interface rendering, then\n // emitted at the top of `routes.ts` so the in-namespace type references\n // resolve correctly. (Inline `import('...').X` inside `.d.ts` files\n // silently degrades to `unknown` with `moduleResolution: 'bundler'`.)\n const schemaImports = new Map<string, { identifier: string; specifier: string }>()\n\n const renderField = (\n schema: { identifier: string; source: string | null } | null,\n routeFilePath: string,\n ): string | null => {\n const alias = planSchemaImport(\n schema,\n routeFilePath,\n routesOutFile,\n schemaValidator,\n schemaImports,\n )\n return alias ? `import('zod').infer<typeof ${alias}>` : null\n }\n\n const interfaces: string[] = []\n for (const [controller, methods] of byController) {\n const lines: string[] = [` interface ${controller} {`]\n for (const m of methods) {\n // Empty `{}` (rather than `Record<string, never>`) so that accessing\n // an unknown property on a paramless route is a type error in strict\n // mode. `Record<string, never>` returns `never` for any access which\n // unfortunately is assignable to anything and silently passes.\n const urlParamsType =\n m.pathParams.length > 0 ? `{ ${m.pathParams.map((p) => `${p}: string`).join('; ')} }` : '{}'\n\n // Schema-driven types win over the URL-pattern / `unknown` defaults\n // when the user has wired a schema in the route decorator.\n const bodySchemaType = renderField(m.bodySchema, m.filePath)\n const querySchemaType = renderField(m.querySchema, m.filePath)\n const paramsSchemaType = renderField(m.paramsSchema, m.filePath)\n\n const paramsType = paramsSchemaType ?? urlParamsType\n const bodyType = bodySchemaType ?? 'unknown'\n const queryType = querySchemaType ?? renderQueryShape(m)\n const docLines = renderQueryDocLines(m)\n lines.push(\n ` /**`,\n ` * ${m.httpMethod} ${m.path}`,\n ...docLines.map((d) => ` * ${d}`),\n ` */`,\n ` ${m.method}: {`,\n ` params: ${paramsType}`,\n ` body: ${bodyType}`,\n ` query: ${queryType}`,\n ` response: unknown`,\n ` }`,\n )\n }\n lines.push(' }')\n interfaces.push(lines.join('\\n'))\n }\n\n const importBlock = renderSchemaImports(schemaImports)\n const interfaceBlock = interfaces.join('\\n')\n\n return `${HEADER}${importBlock}\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace KickRoutes {\n${interfaceBlock}\n }\n}\n\nexport {}\n`\n}\n\n/** Result of a typegen run — useful for logging and tests */\nexport interface GenerateResult {\n /** Number of registry entries written */\n registryEntries: number\n /** Number of service tokens (classes + createToken + @Inject literals) */\n serviceTokens: number\n /** Number of module tokens written */\n moduleTokens: number\n /** Number of route entries written into KickRoutes */\n routeEntries: number\n /** Whether a typed env augmentation was emitted */\n envWritten: boolean\n /** Files that were written */\n written: string[]\n /** Number of collisions that were auto-namespaced (only > 0 with allowDuplicates) */\n resolvedCollisions: number\n}\n\n/** Options for `generateTypes` */\nexport interface GenerateOptions {\n /** Discovered classes from the scanner */\n classes: DiscoveredClass[]\n /** Discovered route handlers from the scanner */\n routes?: DiscoveredRoute[]\n /** Discovered `createToken('name')` calls */\n tokens?: DiscoveredToken[]\n /** Discovered `@Inject('literal')` calls */\n injects?: DiscoveredInject[]\n /** Detected duplicate class names from the scanner */\n collisions?: ClassCollision[]\n /** Discovered env schema file (or null if none) */\n env?: DiscoveredEnv | null\n /** Output directory (typically `<cwd>/.kickjs/types`) */\n outDir: string\n /**\n * When `true`, colliding class names are auto-namespaced by file path\n * instead of throwing. Default: `false`.\n */\n allowDuplicates?: boolean\n /**\n * Schema validator the project uses. When `'zod'`, the generator\n * emits `z.infer<typeof import('...').schema>` for any route whose\n * decorator declared a body/query/params schema identifier that\n * could be statically resolved. When `false` (or omitted), schemas\n * are ignored and `body`/`query`/`params` keep their `unknown`\n * placeholders.\n *\n * Future: `'joi'`, `'yup'`, `'json-schema'`, custom adapters.\n */\n schemaValidator?: 'zod' | false\n}\n\n/** Write all generated `.d.ts` files to `outDir` */\nexport async function generateTypes(opts: GenerateOptions): Promise<GenerateResult> {\n const {\n classes,\n routes = [],\n tokens = [],\n injects = [],\n collisions = [],\n env = null,\n outDir,\n allowDuplicates = false,\n schemaValidator = false,\n } = opts\n\n if (collisions.length > 0 && !allowDuplicates) {\n throw new TokenCollisionError(collisions)\n }\n\n await mkdir(outDir, { recursive: true })\n\n const registryFile = join(outDir, 'registry.d.ts')\n const servicesFile = join(outDir, 'services.d.ts')\n const modulesFile = join(outDir, 'modules.d.ts')\n // routes.ts (NOT .d.ts) — TypeScript silently degrades top-level\n // imports inside `.d.ts` files to `unknown` when the user's tsconfig\n // has `moduleResolution: 'bundler'`. Emitting as a regular `.ts` file\n // makes the schema imports resolve correctly so `z.infer<typeof X>`\n // produces a proper type. The file contains only type declarations\n // so it has zero runtime impact.\n const routesFile = join(outDir, 'routes.ts')\n // env.ts (same .ts vs .d.ts story as routes.ts)\n const envFile = join(outDir, 'env.ts')\n const indexFile = join(outDir, 'index.d.ts')\n\n const collidingNames = new Set(collisions.map((c) => c.className))\n const registryContent = renderRegistry(classes, registryFile, collidingNames)\n\n // ServiceToken union — combines class names, createToken literals, and\n // @Inject literals so tooling autocomplete sees every known token.\n const classTokens = classes\n .filter((c) => REGISTRY_DECORATORS.has(c.decorator))\n .map((c) => (collidingNames.has(c.className) ? namespacedKeyFor(c) : c.className))\n const tokenLiterals = tokens.map((t) => t.name)\n const injectLiterals = injects.map((i) => i.name)\n const allServices = [...classTokens, ...tokenLiterals, ...injectLiterals]\n\n const modules = classes.filter((c) => c.decorator === 'Module').map((c) => c.className)\n\n const servicesContent = renderUnion(\n 'ServiceToken',\n allServices,\n '(no tokens discovered — declare with createToken<T>() or `kick g service <name>`)',\n )\n const modulesContent = renderUnion(\n 'ModuleToken',\n modules,\n '(no @Module classes discovered — `kick g module <name>` to add one)',\n )\n const routesContent = renderRoutes(routes, routesFile, schemaValidator)\n const envContent = renderEnv(env, envFile)\n const indexContent = renderIndex(envContent !== null)\n\n await writeFile(registryFile, registryContent, 'utf-8')\n await writeFile(servicesFile, servicesContent, 'utf-8')\n await writeFile(modulesFile, modulesContent, 'utf-8')\n await writeFile(routesFile, routesContent, 'utf-8')\n await writeFile(indexFile, indexContent, 'utf-8')\n\n const written = [registryFile, servicesFile, modulesFile, routesFile, indexFile]\n if (envContent) {\n await writeFile(envFile, envContent, 'utf-8')\n written.push(envFile)\n }\n\n // Write `.gitignore` at the .kickjs root (one level up from outDir)\n const kickjsRoot = dirname(outDir)\n await writeFile(join(kickjsRoot, '.gitignore'), '# Auto-generated by kick typegen\\n*\\n', 'utf-8')\n\n return {\n registryEntries: classTokens.length,\n serviceTokens: new Set(allServices).size,\n moduleTokens: modules.length,\n routeEntries: routes.length,\n envWritten: envContent !== null,\n written,\n resolvedCollisions: collisions.length,\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 } from 'node:path'\nimport { scanProject, type ScanResult } from './scanner'\nimport { generateTypes, type GenerateResult, TokenCollisionError } from './generator'\n\nexport type {\n DiscoveredClass,\n DiscoveredToken,\n DiscoveredInject,\n DiscoveredEnv,\n ClassCollision,\n ScanResult,\n} from './scanner'\nexport type { GenerateResult } from './generator'\nexport { TokenCollisionError } from './generator'\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' | 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\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' | 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}> {\n const { cwd, srcDir, outDir, silent, allowDuplicates, schemaValidator, envFile } =\n 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 const result = await generateTypes({\n classes: scan.classes,\n routes: scan.routes,\n tokens: scan.tokens,\n injects: scan.injects,\n collisions: scan.collisions,\n env: envFile === false ? null : scan.env,\n outDir,\n allowDuplicates,\n schemaValidator,\n })\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 console.log(\n ` kick typegen → ${result.serviceTokens} services, ${result.routeEntries} routes, ${result.moduleTokens} modules${envNote}${collisionNote} → ${where} (${elapsed}ms)`,\n )\n }\n\n return { scan, result }\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 } = resolved\n // Watch mode always tolerates collisions — otherwise an in-progress\n // rename would crash the dev loop. The error is still printed.\n const runOpts: RunTypegenOptions = { ...resolved, allowDuplicates: true }\n\n // Initial run\n await safeRun(runOpts, silent)\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 safeRun(runOpts, silent)\n }, 100)\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-scan every 2s\n const interval = setInterval(() => {\n safeRun({ ...runOpts, silent: true }, true)\n }, 2000)\n return () => clearInterval(interval)\n }\n\n return () => {\n if (timer) clearTimeout(timer)\n watcher.close()\n }\n}\n\n/** Run typegen swallowing errors so the watcher loop never dies */\nasync function safeRun(opts: RunTypegenOptions, silent: boolean): Promise<void> {\n try {\n await runTypegen(opts)\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"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forinda/kickjs-cli",
3
- "version": "3.1.2",
3
+ "version": "3.2.0",
4
4
  "description": "CLI for KickJS — project scaffolding, DDD module generation, dev/build/start",
5
5
  "keywords": [
6
6
  "kickjs",
@@ -87,7 +87,7 @@
87
87
  "typescript": "^5.9.2",
88
88
  "vite": "^8.0.3",
89
89
  "vitest": "^4.1.2",
90
- "@forinda/kickjs-ai": "3.1.2"
90
+ "@forinda/kickjs-ai": "3.2.0"
91
91
  },
92
92
  "publishConfig": {
93
93
  "access": "public"