@forinda/kickjs-cli 2.2.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs-cli v2.2.0
2
+ * @forinda/kickjs-cli v2.2.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs-cli v2.2.0
2
+ * @forinda/kickjs-cli v2.2.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -3536,7 +3536,7 @@ async function initProject(options) {
3536
3536
  }
3537
3537
  }
3538
3538
  try {
3539
- const { runTypegen } = await import("./typegen-DCnJdqP1.mjs");
3539
+ const { runTypegen } = await import("./typegen-UejiKdXA.mjs");
3540
3540
  await runTypegen({
3541
3541
  cwd: dir,
3542
3542
  allowDuplicates: true,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs-cli v2.2.0
2
+ * @forinda/kickjs-cli v2.2.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -883,4 +883,4 @@ async function runTypegen(opts = {}) {
883
883
  //#endregion
884
884
  export { runTypegen };
885
885
 
886
- //# sourceMappingURL=typegen-DCnJdqP1.mjs.map
886
+ //# sourceMappingURL=typegen-UejiKdXA.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"typegen-DCnJdqP1.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 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 * Match a route decorator immediately followed by a method declaration.\n * Captures the HTTP verb, path literal (or empty), and method name.\n *\n * Tolerates:\n * - Optional second arg to the route decorator (`@Get('/path', { ... })`)\n * - Stacked decorators between the route and the method (`@Get('/') @Use(...)`)\n * - Path-less decorators (`@Get()` → defaults to `/`)\n * - `async` modifier on the method\n *\n * Run within a class body slice (see extractRoutesFromSource) so the\n * captured method name is unambiguously a method on that class.\n */\nconst ROUTE_METHOD_REGEX = new RegExp(\n String.raw`@(${HTTP_DECORATORS.join('|')})\\s*\\(` +\n String.raw`(?:\\s*['\"\\`]([^'\"\\`]*)['\"\\`])?[^)]*\\)` +\n String.raw`(?:\\s*@[A-Z]\\w*(?:\\s*\\([^)]*\\))?)*` +\n String.raw`\\s*(?:public\\s+|private\\s+|protected\\s+)?(?:async\\s+)?` +\n String.raw`([a-zA-Z_]\\w*)\\s*\\(`,\n 'g',\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 * Given the matched text of a route decorator + method declaration, return\n * the substring inside the route decorator's argument list (between the\n * outermost `(` and `)`). Returns `null` if no parens are found.\n *\n * Example input:\n * `@Post('/', { body: createTaskSchema, name: 'CreateTask' }) async create(`\n * Returns:\n * `'/', { body: createTaskSchema, name: 'CreateTask' }`\n */\nfunction extractRouteOptionsArg(matchedText: string): string | null {\n const open = matchedText.indexOf('(')\n if (open < 0) return null\n let depth = 1\n for (let i = open + 1; i < matchedText.length; i++) {\n const ch = matchedText[i]\n if (ch === '(') depth++\n else if (ch === ')') {\n depth--\n if (depth === 0) return matchedText.slice(open + 1, i)\n }\n }\n return null\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 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 ROUTE_METHOD_REGEX.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = ROUTE_METHOD_REGEX.exec(block)) !== null) {\n const [matchedText, verb, pathLiteral, methodName] = match\n const path = pathLiteral && pathLiteral.length > 0 ? pathLiteral : '/'\n\n // The route regex already greedily matched any stacked decorators\n // BETWEEN the route decorator and the method declaration. Inspect\n // the matched substring for an `@ApiQueryParams(...)` call.\n const apiQp = extractApiQueryParams(matchedText, source)\n\n // The route decorator's second argument carries body/query/params\n // schema references. Extract them from the leading slice of the\n // matched text (the part before any stacked decorators).\n const routeArgs = extractRouteOptionsArg(matchedText)\n const bodyId = routeArgs ? extractObjectFieldIdentifier(routeArgs, 'body') : null\n const queryId = routeArgs ? extractObjectFieldIdentifier(routeArgs, 'query') : null\n const paramsId = routeArgs ? extractObjectFieldIdentifier(routeArgs, 'params') : null\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 * Look for an env schema file at `<cwd>/<envFile>`. Returns a\n * `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.\n *\n * Returns `null` for any other state (file missing, no defineEnv, no\n * default export) so the generator skips env typing silently. Users\n * who want env typing must opt in by writing `src/env.ts` to the\n * documented shape.\n */\nexport async function detectEnvFile(cwd: string, envFile: string): Promise<DiscoveredEnv | null> {\n const abs = resolve(cwd, envFile)\n let source: string\n try {\n source = await readFile(abs, 'utf-8')\n } catch {\n return null\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)) return null\n if (!/export\\s+default\\b/.test(source)) return null\n return {\n filePath: abs,\n relativePath: toRelative(abs, cwd),\n }\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-UejiKdXA.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 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 * Match a route decorator immediately followed by a method declaration.\n * Captures the HTTP verb, path literal (or empty), and method name.\n *\n * Tolerates:\n * - Optional second arg to the route decorator (`@Get('/path', { ... })`)\n * - Stacked decorators between the route and the method (`@Get('/') @Use(...)`)\n * - Path-less decorators (`@Get()` → defaults to `/`)\n * - `async` modifier on the method\n *\n * Run within a class body slice (see extractRoutesFromSource) so the\n * captured method name is unambiguously a method on that class.\n */\nconst ROUTE_METHOD_REGEX = new RegExp(\n String.raw`@(${HTTP_DECORATORS.join('|')})\\s*\\(` +\n String.raw`(?:\\s*['\"\\`]([^'\"\\`]*)['\"\\`])?[^)]*\\)` +\n String.raw`(?:\\s*@[A-Z]\\w*(?:\\s*\\([^)]*\\))?)*` +\n String.raw`\\s*(?:public\\s+|private\\s+|protected\\s+)?(?:async\\s+)?` +\n String.raw`([a-zA-Z_]\\w*)\\s*\\(`,\n 'g',\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 * Given the matched text of a route decorator + method declaration, return\n * the substring inside the route decorator's argument list (between the\n * outermost `(` and `)`). Returns `null` if no parens are found.\n *\n * Example input:\n * `@Post('/', { body: createTaskSchema, name: 'CreateTask' }) async create(`\n * Returns:\n * `'/', { body: createTaskSchema, name: 'CreateTask' }`\n */\nfunction extractRouteOptionsArg(matchedText: string): string | null {\n const open = matchedText.indexOf('(')\n if (open < 0) return null\n let depth = 1\n for (let i = open + 1; i < matchedText.length; i++) {\n const ch = matchedText[i]\n if (ch === '(') depth++\n else if (ch === ')') {\n depth--\n if (depth === 0) return matchedText.slice(open + 1, i)\n }\n }\n return null\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 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 ROUTE_METHOD_REGEX.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = ROUTE_METHOD_REGEX.exec(block)) !== null) {\n const [matchedText, verb, pathLiteral, methodName] = match\n const path = pathLiteral && pathLiteral.length > 0 ? pathLiteral : '/'\n\n // The route regex already greedily matched any stacked decorators\n // BETWEEN the route decorator and the method declaration. Inspect\n // the matched substring for an `@ApiQueryParams(...)` call.\n const apiQp = extractApiQueryParams(matchedText, source)\n\n // The route decorator's second argument carries body/query/params\n // schema references. Extract them from the leading slice of the\n // matched text (the part before any stacked decorators).\n const routeArgs = extractRouteOptionsArg(matchedText)\n const bodyId = routeArgs ? extractObjectFieldIdentifier(routeArgs, 'body') : null\n const queryId = routeArgs ? extractObjectFieldIdentifier(routeArgs, 'query') : null\n const paramsId = routeArgs ? extractObjectFieldIdentifier(routeArgs, 'params') : null\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 * Look for an env schema file at `<cwd>/<envFile>`. Returns a\n * `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.\n *\n * Returns `null` for any other state (file missing, no defineEnv, no\n * default export) so the generator skips env typing silently. Users\n * who want env typing must opt in by writing `src/env.ts` to the\n * documented shape.\n */\nexport async function detectEnvFile(cwd: string, envFile: string): Promise<DiscoveredEnv | null> {\n const abs = resolve(cwd, envFile)\n let source: string\n try {\n source = await readFile(abs, 'utf-8')\n } catch {\n return null\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)) return null\n if (!/export\\s+default\\b/.test(source)) return null\n return {\n filePath: abs,\n relativePath: toRelative(abs, cwd),\n }\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": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "description": "CLI for KickJS — project scaffolding, DDD module generation, dev/build/start",
5
5
  "keywords": [
6
6
  "kickjs",
@@ -19,23 +19,26 @@
19
19
  "domain-driven-design",
20
20
  "vite",
21
21
  "hmr",
22
- "@forinda/kickjs-core",
23
- "@forinda/kickjs-http",
22
+ "@forinda/kickjs",
23
+ "@forinda/kickjs-auth",
24
+ "@forinda/kickjs-cli",
24
25
  "@forinda/kickjs-config",
25
- "@forinda/kickjs-swagger",
26
- "@forinda/kickjs-testing",
27
- "@forinda/kickjs-prisma",
28
- "@forinda/kickjs-ws",
26
+ "@forinda/kickjs-core",
27
+ "@forinda/kickjs-cron",
28
+ "@forinda/kickjs-devtools",
29
29
  "@forinda/kickjs-drizzle",
30
- "@forinda/kickjs-otel",
31
30
  "@forinda/kickjs-graphql",
32
- "@forinda/kickjs-auth",
33
- "@forinda/kickjs-cron",
31
+ "@forinda/kickjs-http",
34
32
  "@forinda/kickjs-mailer",
35
- "@forinda/kickjs-queue",
36
33
  "@forinda/kickjs-multi-tenant",
37
- "@forinda/kickjs-devtools",
38
- "@forinda/kickjs-notifications"
34
+ "@forinda/kickjs-notifications",
35
+ "@forinda/kickjs-otel",
36
+ "@forinda/kickjs-prisma",
37
+ "@forinda/kickjs-queue",
38
+ "@forinda/kickjs-swagger",
39
+ "@forinda/kickjs-testing",
40
+ "@forinda/kickjs-vite",
41
+ "@forinda/kickjs-ws"
39
42
  ],
40
43
  "type": "module",
41
44
  "main": "dist/index.mjs",