@forinda/kickjs-cli 5.4.2 → 5.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. package/dist/{builtins-CB0cpCRy.mjs → builtins-B9mT_e0x.mjs} +136 -136
  2. package/dist/{builtins-DNnIUbGs.mjs → builtins-f9GCF9UO.mjs} +73 -73
  3. package/dist/{builtins-DNnIUbGs.mjs.map → builtins-f9GCF9UO.mjs.map} +1 -1
  4. package/dist/cli.mjs +2 -2
  5. package/dist/{config-f_GHcOYT.mjs → config-DDTX-zCM.mjs} +3 -3
  6. package/dist/config-DDTX-zCM.mjs.map +1 -0
  7. package/dist/{config-CAjDTnMg.mjs → config-DO_ZcO15.mjs} +2 -2
  8. package/dist/{generator-extension-Ds2fzYZS.mjs → generator-extension-B0VOAwMA.mjs} +3 -3
  9. package/dist/{generator-extension-Ds2fzYZS.mjs.map → generator-extension-B0VOAwMA.mjs.map} +1 -1
  10. package/dist/index.d.mts +57 -1
  11. package/dist/index.d.mts.map +1 -1
  12. package/dist/index.mjs +2 -2
  13. package/dist/{plugin-CiWyeMpX.mjs → plugin-BWHJGpYB.mjs} +3 -3
  14. package/dist/{plugin-CiWyeMpX.mjs.map → plugin-BWHJGpYB.mjs.map} +1 -1
  15. package/dist/{plugin-Dz0Yu4Ow.mjs → plugin-DLnoaSX8.mjs} +2 -2
  16. package/dist/{rolldown-runtime-iJll81ez.mjs → rolldown-runtime-Bdez_B3Y.mjs} +1 -1
  17. package/dist/{run-plugins-DBOc1G96.mjs → run-plugins-GWzfZyd_.mjs} +2 -2
  18. package/dist/{typegen-COBqEd4w.mjs → typegen-B05WVNbq.mjs} +3 -3
  19. package/dist/{typegen-DzmDwZvN.mjs → typegen-BEWcYO4F.mjs} +4 -4
  20. package/dist/{typegen-DzmDwZvN.mjs.map → typegen-BEWcYO4F.mjs.map} +1 -1
  21. package/dist/{types-DucsCMzP.mjs → types-CjlT7_CZ.mjs} +2 -2
  22. package/dist/{types-DucsCMzP.mjs.map → types-CjlT7_CZ.mjs.map} +1 -1
  23. package/package.json +3 -3
  24. package/dist/config-f_GHcOYT.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"typegen-DzmDwZvN.mjs","names":[],"sources":["../src/typegen/scanner.ts","../src/typegen/generator.ts","../src/typegen/token-conventions.ts","../src/typegen/asset-types.ts","../src/typegen/index.ts"],"sourcesContent":["/**\n * Static scanner for KickJS decorated classes and DI tokens.\n *\n * Walks `src/**\\/*.ts` (excluding tests and node_modules) and extracts:\n *\n * - Decorated classes (`@Service`, `@Controller`, `@Repository`, etc.)\n * - `createToken<T>('name')` definitions\n * - `@Inject('literal')` calls\n *\n * The output feeds the type generator, which emits `.kickjs/types/*.d.ts`\n * files used by the user's tsc to make `container.resolve()` and module\n * discovery type-safe.\n *\n * This is intentionally regex-based (not AST-based) to avoid the\n * ts-morph / typescript compiler dependency. Pattern from\n * `packages/vite/src/module-discovery.ts` which already uses regex\n * to detect `*.module.ts` exports.\n *\n * ## Collision detection\n *\n * Two classes with the same name across different files is a collision.\n * The scanner records all collisions in `ScanResult.collisions` so the\n * caller (generator) can decide whether to hard-error or auto-namespace.\n *\n * @module @forinda/kickjs-cli/typegen/scanner\n */\n\nimport type { Dirent } from 'node:fs'\nimport { readdir, readFile } from 'node:fs/promises'\nimport { join, relative, resolve, sep } from 'node:path'\n\n/** Decorators that mark a class as DI-managed */\nexport const DECORATOR_NAMES = [\n 'Service',\n 'Controller',\n 'Repository',\n 'Injectable',\n 'Component',\n 'Module',\n] as const\n\nexport type DecoratorName = (typeof DECORATOR_NAMES)[number]\n\n/** A single discovered decorated class */\nexport interface DiscoveredClass {\n /** Class name (e.g., 'UserService') */\n className: string\n /** Decorator that marked it (e.g., 'Service') */\n decorator: DecoratorName\n /** Absolute file path */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n /** True if exported as `default` */\n isDefault: boolean\n}\n\n/** A single route handler discovered on a controller class */\nexport interface DiscoveredRoute {\n /** Owning controller class name (e.g. 'UserController') */\n controller: string\n /** Handler method name on the controller (e.g. 'getUser') */\n method: string\n /** HTTP verb (uppercase) */\n httpMethod: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'\n /** Route path including parameter placeholders (e.g. '/:id/posts/:postId') */\n path: string\n /** URL path parameter names extracted from `:placeholder` segments */\n pathParams: string[]\n /**\n * Whitelisted query field names extracted from `@ApiQueryParams({...})`.\n * `null` means no `@ApiQueryParams` was found on this method (so the\n * generator emits an unconstrained `query` shape). An empty array means\n * the decorator existed but no fields could be statically extracted\n * (e.g. an opaque imported config).\n */\n queryFilterable: string[] | null\n querySortable: string[] | null\n querySearchable: string[] | null\n /**\n * Schema identifiers referenced from the route decorator's second arg\n * (e.g. `@Post('/', { body: createTaskSchema })`). `null` means no\n * such reference; the value carries the identifier and the resolved\n * import source (relative module path) if known.\n */\n bodySchema: SchemaRef | null\n querySchema: SchemaRef | null\n paramsSchema: SchemaRef | null\n /** Absolute file path of the controller */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/** A statically-resolved schema identifier reference */\nexport interface SchemaRef {\n /** The identifier as written (e.g. `createTaskSchema`) */\n identifier: string\n /**\n * Resolved module specifier (relative path or bare module name) where\n * the identifier is defined. `null` means the source could not be\n * statically determined (the generator falls back to `unknown`).\n */\n source: string | null\n}\n\n/** A `createToken<T>('name')` call discovered in source */\nexport interface DiscoveredToken {\n /** The literal string passed to `createToken()` */\n name: string\n /** The const variable name on the LHS, if any */\n variable: string | null\n /** Absolute file path */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/** An `@Inject('literal')` call discovered in source */\nexport interface DiscoveredInject {\n /** The literal string passed to `@Inject()` */\n name: string\n /** Absolute file path */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/** A name collision — same class name in two or more files */\nexport interface ClassCollision {\n /** The colliding class name */\n className: string\n /** All files declaring the class */\n classes: DiscoveredClass[]\n}\n\n/**\n * Information about a discovered env schema file. The typegen\n * generator uses this to emit a `KickEnv` + `NodeJS.ProcessEnv`\n * augmentation that flows through to `@Value` and `process.env`.\n *\n * `null` means no env file was found at the configured location.\n */\nexport interface DiscoveredEnv {\n /** Absolute path to the env schema file */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/**\n * A plugin or adapter discovered in source — either via `defineAdapter({ name })`\n * / `definePlugin({ name })` calls, or via a class that `implements AppAdapter`\n * and declares a string-literal `name` field.\n *\n * The `name` here is the literal string passed to the framework (the value\n * `dependsOn` references), NOT the symbol on the LHS. `defineAdapter` lets\n * authors choose any name they want; the symbol is irrelevant at runtime.\n */\nexport interface DiscoveredPluginOrAdapter {\n /** Whether this is a plugin (`definePlugin`) or adapter (`defineAdapter` / class) */\n kind: 'plugin' | 'adapter'\n /** The string literal passed as `name` (the value `dependsOn` references) */\n name: string\n /** Absolute file path */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/**\n * A `defineAugmentation('Name', meta)` call discovered in source. Plugins\n * call this to advertise an augmentable interface so the typegen can list\n * every augmentation surface in one generated file.\n */\nexport interface DiscoveredAugmentation {\n /** The literal string passed as the first arg to `defineAugmentation` */\n name: string\n /** Optional `description` extracted from the second-arg object literal */\n description: string | null\n /** Optional `example` extracted from the second-arg object literal */\n example: string | null\n /** Absolute file path */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/** Aggregated scanner output */\nexport interface ScanResult {\n classes: DiscoveredClass[]\n routes: DiscoveredRoute[]\n tokens: DiscoveredToken[]\n injects: DiscoveredInject[]\n collisions: ClassCollision[]\n /** Discovered env schema file (or null if none found at the configured path) */\n env: DiscoveredEnv | null\n /** Plugins/adapters discovered via `defineAdapter`/`definePlugin`/`implements AppAdapter` */\n pluginsAndAdapters: DiscoveredPluginOrAdapter[]\n /** Augmentation interfaces declared via `defineAugmentation('Name', meta)` */\n augmentations: DiscoveredAugmentation[]\n}\n\n/** Options for the scanner */\nexport interface ScanOptions {\n /** Root directory to scan (e.g., absolute path to `src`) */\n root: string\n /** Project root used to compute relative paths (e.g., process.cwd()) */\n cwd: string\n /** Glob-like extensions to scan */\n extensions?: string[]\n /** Substrings that exclude a path (matched against relative path) */\n exclude?: string[]\n /**\n * Path to the env schema file, relative to `cwd`. Defaults to\n * `'src/env.ts'`. The file must contain a `defineEnv(...)` call\n * with a default export for the typegen to emit a typed `KickEnv`\n * augmentation. If the file does not exist or doesn't match the\n * expected shape, env typing is skipped silently.\n */\n envFile?: string\n}\n\nconst DEFAULT_EXTENSIONS = ['.ts', '.tsx', '.mts', '.cts']\nconst DEFAULT_EXCLUDES = ['node_modules', '.kickjs', 'dist', 'build', '.test.', '.spec.', '.d.ts']\n\n/**\n * Match a class-level decorator immediately followed by an exported\n * class declaration. Captures decorator name and class name.\n */\nconst DECORATED_CLASS_REGEX = new RegExp(\n String.raw`@(${DECORATOR_NAMES.join('|')})\\s*\\([^)]*\\)` +\n String.raw`(?:\\s*@[A-Z]\\w*(?:\\s*\\([^)]*\\))?)*` +\n String.raw`\\s*export\\s+(default\\s+)?(?:abstract\\s+)?class\\s+(\\w+)`,\n 'g',\n)\n\n/**\n * Match an exported class declaration that implements `AppModule`.\n * KickJS modules are not decorated — they implement the `AppModule`\n * interface — so the decorated-class scanner never picks them up. This\n * regex captures them by name so `ModuleToken` can be populated.\n *\n * Tolerates an `extends BaseClass` clause before `implements`, multiple\n * implements clauses (`implements Foo, AppModule`), and `default` exports.\n */\nconst APP_MODULE_CLASS_REGEX = new RegExp(\n String.raw`export\\s+(default\\s+)?(?:abstract\\s+)?class\\s+(\\w+)` +\n String.raw`(?:\\s+extends\\s+\\w+(?:<[^>]*>)?)?` +\n String.raw`\\s+implements\\s+[^{]*\\bAppModule\\b`,\n 'g',\n)\n\n/**\n * Match a `createToken<T>('name')` call with optional `export const X =`\n * or `const X =` prefix. Tolerates whitespace and the type parameter\n * being absent (`createToken('name')`).\n */\nconst CREATE_TOKEN_REGEX =\n /(?:export\\s+)?const\\s+(\\w+)\\s*(?::\\s*[^=]+)?=\\s*createToken\\s*(?:<[^>]*>)?\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]\\s*\\)/g\n\n/**\n * Match a bare `createToken<T>('name')` call (no const assignment) so\n * we still pick up dynamically-used tokens.\n */\nconst BARE_CREATE_TOKEN_REGEX = /createToken\\s*(?:<[^>]*>)?\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]\\s*\\)/g\n\n/** Match `@Inject('literal')` — only literals; computed args are skipped */\nconst INJECT_LITERAL_REGEX = /@Inject\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]\\s*\\)/g\n\n/**\n * Match the start of a `defineAdapter(...)` or `definePlugin(...)` call,\n * tolerating optional `<TConfig, TExtra>` generics. Captures the helper\n * name. The callsite's first-arg object is parsed forward via\n * `findBalancedClose` so nested objects/parens don't confuse us.\n */\nconst DEFINE_HELPER_START = /\\b(defineAdapter|definePlugin)\\s*(?:<[^>]*>)?\\s*\\(/g\n\n/**\n * Match a class declaration whose `implements` clause includes `AppAdapter`.\n * Captures the class name. Used to pick up the (rare, post-defineAdapter)\n * legacy class-style adapters so their literal `name = '...'` field can\n * still feed `KickJsPluginRegistry`.\n */\nconst APP_ADAPTER_CLASS_REGEX = new RegExp(\n String.raw`export\\s+(?:default\\s+)?(?:abstract\\s+)?class\\s+(\\w+)` +\n String.raw`(?:\\s+extends\\s+\\w+(?:<[^>]*>)?)?` +\n String.raw`\\s+implements\\s+[^{]*\\bAppAdapter\\b`,\n 'g',\n)\n\n/** Match a string-literal `name = '...'` field on a class body. */\nconst CLASS_NAME_FIELD_REGEX = /\\bname\\s*(?::\\s*[^=]+)?=\\s*['\"`]([^'\"`]+)['\"`]/\n\n/**\n * Match the start of a `defineAugmentation('Name', ...)` call. Captures\n * the literal name. The optional second-arg object is parsed forward so\n * `description` / `example` can be pulled out.\n */\nconst DEFINE_AUGMENTATION_START = /\\bdefineAugmentation\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]\\s*(,\\s*\\{)?/g\n\n/** HTTP route decorator names recognised by the scanner */\nconst HTTP_DECORATORS = ['Get', 'Post', 'Put', 'Delete', 'Patch'] as const\n\n/**\n * Locate the start of a route decorator: `@Get(`, `@Post(`, etc.\n * Used by `extractRoutesFromSource`; the rest of the route declaration\n * (balanced parens, stacked decorators, method name) is parsed by walking\n * the source forward from this match. The previous all-in-one regex\n * couldn't handle nested parens in stacked decorator args (e.g.\n * `@ApiResponse(201, { schema: z.object({ id: z.string() }) })`) — see\n * forinda/kick-js#108.\n */\nconst ROUTE_DECORATOR_START = new RegExp(String.raw`@(${HTTP_DECORATORS.join('|')})\\s*\\(`, 'g')\n\n/**\n * Find the index of the `)` that balances the `(` at `openPos`.\n * Returns -1 if no matching `)` exists. Counts balanced parens only;\n * does not understand string literals, so a `(` or `)` inside a string\n * inside the args will skew the depth counter (matches the limitation\n * of `extractRouteOptionsArg`).\n */\nfunction findBalancedClose(text: string, openPos: number): number {\n let depth = 1\n for (let i = openPos + 1; i < text.length; i++) {\n const ch = text[i]\n if (ch === '(') depth++\n else if (ch === ')') {\n depth--\n if (depth === 0) return i\n }\n }\n return -1\n}\n\n/**\n * Walk forward from the end of a route decorator past any stacked\n * decorators (`@ApiOperation(...)`, `@ApiResponse(...)`, `@Middleware(fn)`,\n * etc.), then past optional `public`/`private`/`protected` and `async`,\n * and capture the method name + opening `(`.\n *\n * Returns the method name and the position immediately after the method's\n * opening `(`, or `null` if the source between the route decorator and\n * the method body doesn't fit the expected shape.\n */\nfunction readMethodAfterDecorators(\n block: string,\n startPos: number,\n): { methodName: string; endPos: number } | null {\n let pos = startPos\n // Stacked decorators: @PascalCase optionally followed by balanced (...)\n while (pos < block.length) {\n while (pos < block.length && /\\s/.test(block[pos])) pos++\n if (block[pos] !== '@') break\n const decMatch = block.slice(pos).match(/^@([A-Z]\\w*)/)\n if (!decMatch) break\n pos += decMatch[0].length\n while (pos < block.length && /\\s/.test(block[pos])) pos++\n if (block[pos] === '(') {\n const close = findBalancedClose(block, pos)\n if (close < 0) return null\n pos = close + 1\n }\n }\n // Modifiers + async\n while (pos < block.length && /\\s/.test(block[pos])) pos++\n for (const mod of ['public', 'private', 'protected'] as const) {\n if (block.slice(pos, pos + mod.length) === mod && /\\s/.test(block.charAt(pos + mod.length))) {\n pos += mod.length\n while (pos < block.length && /\\s/.test(block[pos])) pos++\n break\n }\n }\n if (block.slice(pos, pos + 5) === 'async' && /\\s/.test(block.charAt(pos + 5))) {\n pos += 5\n while (pos < block.length && /\\s/.test(block[pos])) pos++\n }\n // Method name + `(`\n const methodMatch = block.slice(pos).match(/^([a-zA-Z_]\\w*)\\s*\\(/)\n if (!methodMatch) return null\n return { methodName: methodMatch[1], endPos: pos + methodMatch[0].length }\n}\n\n/** Extract `:placeholder` segments from an Express route path */\nfunction extractPathParams(path: string): string[] {\n const matches = path.match(/:([a-zA-Z_]\\w*)/g) ?? []\n return matches.map((m) => m.slice(1))\n}\n\n/**\n * Extract a bare identifier value from a single field in an object literal\n * embedded in a string. Returns `null` if the field is missing or its value\n * isn't a bare identifier (e.g. an inline object, function call, etc.).\n *\n * Example: `extractObjectFieldIdentifier(\"'/' , { body: createTaskSchema }\", 'body')`\n * returns `'createTaskSchema'`.\n */\nfunction extractObjectFieldIdentifier(text: string, field: string): string | null {\n // Look for `field: <identifier>` not followed by `(` (function call) or `{` (inline object)\n const re = new RegExp(String.raw`\\b${field}\\s*:\\s*([A-Za-z_$][\\w$]*)`, 'g')\n const m = re.exec(text)\n if (!m) return null\n return m[1]\n}\n\n/**\n * Resolve a bare identifier to its module source by inspecting the file's\n * top-level imports and same-file `const` declarations.\n *\n * - `import { X } from './path'` → returns `'./path'`\n * - `import X from './path'` (default import) → returns `'./path'`\n * - `import * as X from './path'` → returns `'./path'`\n * - `const X = z.object(...)` (same file) → returns `null` (caller emits a self-import)\n *\n * Returns `null` when the identifier cannot be resolved.\n */\nfunction resolveImportSource(source: string, identifier: string): string | null {\n // Named import: `import { X, Y as Z } from './path'`\n const namedRe = new RegExp(\n String.raw`import\\s*(?:type\\s+)?\\{[^}]*\\b${identifier}\\b[^}]*\\}\\s*from\\s*['\"\\`]([^'\"\\`]+)['\"\\`]`,\n )\n const named = namedRe.exec(source)\n if (named) return named[1]\n\n // Default import: `import X from './path'`\n const defaultRe = new RegExp(\n String.raw`import\\s+(?:type\\s+)?${identifier}\\s+from\\s*['\"\\`]([^'\"\\`]+)['\"\\`]`,\n )\n const def = defaultRe.exec(source)\n if (def) return def[1]\n\n // Namespace import: `import * as X from './path'`\n const nsRe = new RegExp(\n String.raw`import\\s*\\*\\s*as\\s+${identifier}\\s+from\\s*['\"\\`]([^'\"\\`]+)['\"\\`]`,\n )\n const ns = nsRe.exec(source)\n if (ns) return ns[1]\n\n // Same-file const declaration — return empty string as a sentinel meaning\n // \"current file\". The generator turns this into a self-relative reference.\n const constRe = new RegExp(String.raw`(?:^|\\n)\\s*(?:export\\s+)?const\\s+${identifier}\\b`)\n if (constRe.test(source)) return ''\n\n return null\n}\n\n/**\n * Extract whitelist arrays from an `@ApiQueryParams(...)` decorator\n * within `decoratorBlock`. Handles two forms:\n *\n * - Inline literal: `@ApiQueryParams({ filterable: ['a', 'b'], ... })`\n * - Const reference: `@ApiQueryParams(SOME_CONFIG)` — looks up\n * `const SOME_CONFIG = { ... }` in the same file (`fullSource`).\n *\n * Returns `null` if no `@ApiQueryParams` is present. Returns\n * `{ filterable: [], sortable: [], searchable: [] }` if the decorator\n * is present but no fields could be statically extracted (opaque\n * imports, column-object configs, function calls, etc.).\n */\nfunction extractApiQueryParams(\n decoratorBlock: string,\n fullSource: string,\n): { filterable: string[]; sortable: string[]; searchable: string[] } | null {\n const apiMatch = /@ApiQueryParams\\s*\\(\\s*([\\s\\S]*?)\\s*\\)\\s*$/.exec(decoratorBlock)\n if (!apiMatch) {\n // Try without anchoring to the end (decorator may not be the last in the block)\n const loose = /@ApiQueryParams\\s*\\(([\\s\\S]*?)\\)/.exec(decoratorBlock)\n if (!loose) return null\n return parseApiQueryParamsArg(loose[1].trim(), fullSource)\n }\n return parseApiQueryParamsArg(apiMatch[1].trim(), fullSource)\n}\n\nfunction parseApiQueryParamsArg(\n arg: string,\n fullSource: string,\n): { filterable: string[]; sortable: string[]; searchable: string[] } {\n // Inline literal — starts with `{`\n if (arg.startsWith('{')) {\n return parseInlineConfigLiteral(arg)\n }\n // Const reference — bare identifier (possibly with type assertion)\n const idMatch = /^([A-Za-z_]\\w*)/.exec(arg)\n if (idMatch) {\n const ident = idMatch[1]\n // Look for `const IDENT = { ... }` in the same source file\n const constRe = new RegExp(\n String.raw`const\\s+${ident}\\s*(?::\\s*[^=]+)?=\\s*(\\{[\\s\\S]*?\\n\\})`,\n 'm',\n )\n const constMatch = constRe.exec(fullSource)\n if (constMatch) {\n return parseInlineConfigLiteral(constMatch[1])\n }\n }\n // Fallback: decorator present but extraction failed\n return { filterable: [], sortable: [], searchable: [] }\n}\n\n/** Extract a string array literal for one config key from an inline object literal */\nfunction extractStringArray(literal: string, key: string): string[] {\n const re = new RegExp(String.raw`${key}\\s*:\\s*\\[([\\s\\S]*?)\\]`)\n const m = re.exec(literal)\n if (!m) return []\n return Array.from(m[1].matchAll(/['\"`]([^'\"`]+)['\"`]/g)).map((x) => x[1])\n}\n\n/** Parse an inline `{ filterable: [...], sortable: [...], searchable: [...] }` literal */\nfunction parseInlineConfigLiteral(literal: string): {\n filterable: string[]\n sortable: string[]\n searchable: string[]\n} {\n return {\n filterable: extractStringArray(literal, 'filterable'),\n sortable: extractStringArray(literal, 'sortable'),\n searchable: extractStringArray(literal, 'searchable'),\n }\n}\n\n/** Recursively walk a directory and yield matching file paths */\nasync function walk(dir: string, opts: ScanOptions): Promise<string[]> {\n const exts = opts.extensions ?? DEFAULT_EXTENSIONS\n const excludes = opts.exclude ?? DEFAULT_EXCLUDES\n const out: string[] = []\n\n let entries: Dirent[]\n try {\n entries = (await readdir(dir, { withFileTypes: true, encoding: 'utf-8' })) as Dirent[]\n } catch {\n return out\n }\n\n for (const entry of entries) {\n const full = join(dir, entry.name)\n const rel = relative(opts.cwd, full)\n\n if (excludes.some((ex) => rel.includes(ex))) continue\n\n if (entry.isDirectory()) {\n out.push(...(await walk(full, opts)))\n } else if (entry.isFile()) {\n if (exts.some((ext) => entry.name.endsWith(ext))) {\n out.push(full)\n }\n }\n }\n\n return out\n}\n\n/** Compute the forward-slash relative path used in scanner output */\nfunction toRelative(filePath: string, cwd: string): string {\n return relative(cwd, filePath).split(sep).join('/')\n}\n\n/** Extract decorated classes from a single source file */\nexport function extractClassesFromSource(\n source: string,\n filePath: string,\n cwd: string,\n): DiscoveredClass[] {\n const out: DiscoveredClass[] = []\n const relPath = toRelative(filePath, cwd)\n\n DECORATED_CLASS_REGEX.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = DECORATED_CLASS_REGEX.exec(source)) !== null) {\n const [, decorator, defaultMarker, className] = match\n out.push({\n className,\n decorator: decorator as DecoratorName,\n filePath,\n relativePath: relPath,\n isDefault: Boolean(defaultMarker),\n })\n }\n\n // KickJS modules are undecorated classes that `implements AppModule`.\n // Tag them with the synthetic `Module` decorator so downstream code that\n // already filters by `c.decorator === 'Module'` keeps working.\n APP_MODULE_CLASS_REGEX.lastIndex = 0\n let modMatch: RegExpExecArray | null\n while ((modMatch = APP_MODULE_CLASS_REGEX.exec(source)) !== null) {\n const [, defaultMarker, className] = modMatch\n if (out.some((c) => c.className === className && c.filePath === filePath)) continue\n out.push({\n className,\n decorator: 'Module',\n filePath,\n relativePath: relPath,\n isDefault: Boolean(defaultMarker),\n })\n }\n\n return out\n}\n\n/** Extract `createToken('name')` definitions from a single source file */\nexport function extractTokensFromSource(\n source: string,\n filePath: string,\n cwd: string,\n): DiscoveredToken[] {\n const out: DiscoveredToken[] = []\n const relPath = toRelative(filePath, cwd)\n const seen = new Set<string>()\n\n // First pass: const-bound tokens (preferred — we get the variable name)\n CREATE_TOKEN_REGEX.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = CREATE_TOKEN_REGEX.exec(source)) !== null) {\n const [full, variable, name] = match\n seen.add(full)\n out.push({ name, variable, filePath, relativePath: relPath })\n }\n\n // Second pass: bare calls not captured above (rare but possible)\n BARE_CREATE_TOKEN_REGEX.lastIndex = 0\n while ((match = BARE_CREATE_TOKEN_REGEX.exec(source)) !== null) {\n if (seen.has(match[0])) continue\n out.push({\n name: match[1],\n variable: null,\n filePath,\n relativePath: relPath,\n })\n }\n\n return out\n}\n\n/**\n * Extract route handlers from a source file.\n *\n * For each decorated class in `classesInFile`, slices the source from\n * the class declaration to the next class (or EOF) and runs the route\n * decorator regex within that slice. The result is a list of routes\n * tagged with their owning controller.\n *\n * Heuristic note: this assumes classes are not nested. KickJS controllers\n * are top-level by convention so this holds in practice.\n */\nexport function extractRoutesFromSource(\n source: string,\n filePath: string,\n cwd: string,\n classesInFile: DiscoveredClass[],\n): DiscoveredRoute[] {\n const out: DiscoveredRoute[] = []\n if (classesInFile.length === 0) return out\n const relPath = toRelative(filePath, cwd)\n\n // Locate each class declaration's offset in the source\n const positions: Array<{ cls: DiscoveredClass; start: number }> = []\n for (const cls of classesInFile) {\n const re = new RegExp(String.raw`class\\s+${cls.className}\\b`)\n const m = re.exec(source)\n if (m?.index !== undefined) {\n positions.push({ cls, start: m.index })\n }\n }\n positions.sort((a, b) => a.start - b.start)\n\n for (let i = 0; i < positions.length; i++) {\n const { cls, start } = positions[i]\n const end = i + 1 < positions.length ? positions[i + 1].start : source.length\n const block = source.slice(start, end)\n\n // Two-pass walk: locate each route decorator start, then balance-parse\n // forward through args and any stacked decorators to find the method\n // name. Replaces the previous single regex which mis-parsed nested\n // parens (forinda/kick-js#108).\n ROUTE_DECORATOR_START.lastIndex = 0\n let startMatch: RegExpExecArray | null\n while ((startMatch = ROUTE_DECORATOR_START.exec(block)) !== null) {\n const verb = startMatch[1]\n const decoratorStart = startMatch.index\n const openParen = ROUTE_DECORATOR_START.lastIndex - 1\n const closeParen = findBalancedClose(block, openParen)\n if (closeParen < 0) continue\n\n const routeArgs = block.slice(openParen + 1, closeParen)\n\n const pathLiteralMatch = routeArgs.match(/^\\s*['\"`]([^'\"`]*)['\"`]/)\n const path = pathLiteralMatch && pathLiteralMatch[1].length > 0 ? pathLiteralMatch[1] : '/'\n\n const methodInfo = readMethodAfterDecorators(block, closeParen + 1)\n if (!methodInfo) continue\n const { methodName, endPos } = methodInfo\n\n // Advance the regex iterator past this method so the next iteration\n // starts looking after the consumed region.\n ROUTE_DECORATOR_START.lastIndex = endPos\n\n const matchedText = block.slice(decoratorStart, endPos)\n const apiQp = extractApiQueryParams(matchedText, source)\n\n const bodyId = extractObjectFieldIdentifier(routeArgs, 'body')\n const queryId = extractObjectFieldIdentifier(routeArgs, 'query')\n const paramsId = extractObjectFieldIdentifier(routeArgs, 'params')\n\n out.push({\n controller: cls.className,\n method: methodName,\n httpMethod: verb.toUpperCase() as DiscoveredRoute['httpMethod'],\n path,\n pathParams: extractPathParams(path),\n queryFilterable: apiQp?.filterable ?? null,\n querySortable: apiQp?.sortable ?? null,\n querySearchable: apiQp?.searchable ?? null,\n bodySchema: bodyId\n ? { identifier: bodyId, source: resolveImportSource(source, bodyId) }\n : null,\n querySchema: queryId\n ? { identifier: queryId, source: resolveImportSource(source, queryId) }\n : null,\n paramsSchema: paramsId\n ? { identifier: paramsId, source: resolveImportSource(source, paramsId) }\n : null,\n filePath,\n relativePath: relPath,\n })\n }\n }\n\n return out\n}\n\n/** Extract `@Inject('literal')` calls from a single source file */\nexport function extractInjectsFromSource(\n source: string,\n filePath: string,\n cwd: string,\n): DiscoveredInject[] {\n const out: DiscoveredInject[] = []\n const relPath = toRelative(filePath, cwd)\n\n INJECT_LITERAL_REGEX.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = INJECT_LITERAL_REGEX.exec(source)) !== null) {\n out.push({ name: match[1], filePath, relativePath: relPath })\n }\n\n return out\n}\n\n/**\n * Extract the bounds of an object literal that begins at `openBracePos`\n * (the index of the `{` character). Returns the index of the matching `}`\n * or -1 if no match is found. Counts balanced braces only — does not\n * understand string literals so a `{` or `}` inside a string inside the\n * object will skew the depth counter (matches `findBalancedClose`).\n */\nfunction findBalancedBrace(text: string, openBracePos: number): number {\n let depth = 1\n for (let i = openBracePos + 1; i < text.length; i++) {\n const ch = text[i]\n if (ch === '{') depth++\n else if (ch === '}') {\n depth--\n if (depth === 0) return i\n }\n }\n return -1\n}\n\n/**\n * Extract plugins/adapters declared via `defineAdapter({ name: '...' })`\n * or `definePlugin({ name: '...' })` calls and via class-style adapters\n * (`class XxxAdapter implements AppAdapter` with a string-literal `name`\n * field).\n *\n * Only the literal `name:` field feeds the result — the symbol on the LHS\n * is irrelevant since `dependsOn` references the runtime name.\n */\nexport function extractPluginsAndAdaptersFromSource(\n source: string,\n filePath: string,\n cwd: string,\n): DiscoveredPluginOrAdapter[] {\n const out: DiscoveredPluginOrAdapter[] = []\n const relPath = toRelative(filePath, cwd)\n const seen = new Set<string>()\n\n // Pass 1: defineAdapter / definePlugin calls\n DEFINE_HELPER_START.lastIndex = 0\n let helperMatch: RegExpExecArray | null\n while ((helperMatch = DEFINE_HELPER_START.exec(source)) !== null) {\n const helper = helperMatch[1] as 'defineAdapter' | 'definePlugin'\n const openParen = DEFINE_HELPER_START.lastIndex - 1\n const closeParen = findBalancedClose(source, openParen)\n if (closeParen < 0) continue\n const callArgs = source.slice(openParen + 1, closeParen)\n // Look for the first `name: 'literal'` in the call args\n const nameMatch = /\\bname\\s*:\\s*['\"`]([^'\"`]+)['\"`]/.exec(callArgs)\n if (!nameMatch) continue\n const name = nameMatch[1]\n const dedupeKey = `${helper}::${name}::${filePath}`\n if (seen.has(dedupeKey)) continue\n seen.add(dedupeKey)\n out.push({\n kind: helper === 'definePlugin' ? 'plugin' : 'adapter',\n name,\n filePath,\n relativePath: relPath,\n })\n }\n\n // Pass 2: class-style adapters (`class X implements AppAdapter { name = 'X' }`)\n APP_ADAPTER_CLASS_REGEX.lastIndex = 0\n let classMatch: RegExpExecArray | null\n while ((classMatch = APP_ADAPTER_CLASS_REGEX.exec(source)) !== null) {\n const classStart = classMatch.index\n // Find the class body opening brace\n const bracePos = source.indexOf('{', classStart)\n if (bracePos < 0) continue\n const closeBrace = findBalancedBrace(source, bracePos)\n if (closeBrace < 0) continue\n const body = source.slice(bracePos + 1, closeBrace)\n const nameMatch = CLASS_NAME_FIELD_REGEX.exec(body)\n if (!nameMatch) continue\n const name = nameMatch[1]\n const dedupeKey = `class::${name}::${filePath}`\n if (seen.has(dedupeKey)) continue\n seen.add(dedupeKey)\n out.push({ kind: 'adapter', name, filePath, relativePath: relPath })\n }\n\n return out\n}\n\n/**\n * Extract `defineAugmentation('Name', { description, example })` calls\n * from a source file. The metadata object is optional — when absent both\n * `description` and `example` resolve to `null`.\n */\nexport function extractAugmentationsFromSource(\n source: string,\n filePath: string,\n cwd: string,\n): DiscoveredAugmentation[] {\n const out: DiscoveredAugmentation[] = []\n const relPath = toRelative(filePath, cwd)\n\n DEFINE_AUGMENTATION_START.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = DEFINE_AUGMENTATION_START.exec(source)) !== null) {\n const name = match[1]\n let description: string | null = null\n let example: string | null = null\n\n // If the regex matched a metadata object opening (`, {`), parse it\n if (match[2]) {\n const bracePos = source.indexOf('{', match.index + match[0].length - 1)\n if (bracePos >= 0) {\n const closeBrace = findBalancedBrace(source, bracePos)\n if (closeBrace >= 0) {\n const body = source.slice(bracePos + 1, closeBrace)\n description = readStringField(body, 'description')\n example = readStringField(body, 'example')\n }\n }\n }\n\n out.push({ name, description, example, filePath, relativePath: relPath })\n }\n\n return out\n}\n\n/**\n * Pull a string-valued field out of a JS object-literal body, respecting\n * the opening quote so the value isn't truncated at the first foreign\n * quote character. Handles backslash escapes inside the literal.\n *\n * Why a custom parser instead of one regex per delimiter: real-world\n * `defineAugmentation` calls embed all three quote characters at once\n * — backtick template literals carrying TS shapes like\n * `'free' | 'pro'` (single quotes) AND `\\`ctx.get(...)\\`` (escaped\n * backticks). A character-class regex like `[^'\"`]+` truncates on the\n * first foreign quote it sees. This walker scans char-by-char from\n * the matched delimiter and only stops on the matching one.\n */\nfunction readStringField(body: string, field: string): string | null {\n // Locate `field:` followed by an opening quote. Tolerate any whitespace.\n const fieldRe = new RegExp(`\\\\b${field}\\\\s*:\\\\s*(['\"\\`])`, 'g')\n const m = fieldRe.exec(body)\n if (!m) return null\n const quote = m[1]\n const start = m.index + m[0].length\n let i = start\n let raw: string | null = null\n while (i < body.length) {\n const ch = body[i]\n if (ch === '\\\\') {\n // Skip the escaped char — supports \\`, \\', \\\", \\n, \\\\ etc.\n i += 2\n continue\n }\n if (ch === quote) {\n raw = body.slice(start, i)\n break\n }\n i++\n }\n if (raw === null) return null\n // Unescape JS string-literal escapes so the JSDoc renderer sees the\n // value the source author actually intended (`\\`` → `` ` ``, `\\'` →\n // `'`, etc). Without this, escaped backticks in a backtick template\n // literal would surface as literal backslashes in the catalogue.\n return raw.replace(/\\\\(.)/g, (_m, c) => {\n if (c === 'n') return '\\n'\n if (c === 't') return '\\t'\n if (c === 'r') return '\\r'\n return c\n })\n}\n\n/**\n * Default search order for the env schema file. Newer projects keep\n * the schema under `src/config/` so the framework's \"config\" concept\n * has a single home; older scaffolds dropped it at `src/env.ts` (kept\n * here for back-compat). The first match wins.\n */\nconst DEFAULT_ENV_FILE_CANDIDATES = [\n 'src/config/index.ts',\n 'src/config/env.ts',\n 'src/config.ts',\n 'src/env.ts',\n] as const\n\n/**\n * Look for an env schema file. When `envFile` is the string default\n * (`'src/env.ts'`) or omitted, every entry in `DEFAULT_ENV_FILE_CANDIDATES`\n * is tried in order. When the caller passes an explicit path, only that\n * path is tried (so projects can opt out of the search by setting\n * `kick.config.ts → typegen.envFile`).\n *\n * Returns a `DiscoveredEnv` if the file exists and contains both a\n * `defineEnv(...)` call and a default export — the two markers we\n * need before it's safe to emit `import type schema from '...'` in\n * the generator. Returns `null` for any other state (no candidate\n * found, no defineEnv, no default export) so the generator skips env\n * typing silently.\n */\nexport async function detectEnvFile(cwd: string, envFile: string): Promise<DiscoveredEnv | null> {\n // The CLI passes the literal default `'src/env.ts'` when the user\n // hasn't overridden it. Treat that as \"use the search list\" rather\n // than pinning to one path, so newer scaffolds at src/config/ keep\n // working without forcing every project to set typegen.envFile.\n const candidates: readonly string[] =\n envFile === 'src/env.ts' ? DEFAULT_ENV_FILE_CANDIDATES : [envFile]\n\n for (const candidate of candidates) {\n const abs = resolve(cwd, candidate)\n let source: string\n try {\n source = await readFile(abs, 'utf-8')\n } catch {\n continue\n }\n // Cheap heuristic: defineEnv(...) call AND a default export.\n // We don't try to evaluate the file — the generator emits an\n // `import type schema from '...'` and lets the user's tsc do the\n // actual schema-to-type inference.\n if (!/\\bdefineEnv\\s*\\(/.test(source)) continue\n if (!/export\\s+default\\b/.test(source)) continue\n return {\n filePath: abs,\n relativePath: toRelative(abs, cwd),\n }\n }\n\n return null\n}\n\n/** Detect duplicate class names across files */\nexport function findCollisions(classes: DiscoveredClass[]): ClassCollision[] {\n const groups = new Map<string, DiscoveredClass[]>()\n for (const cls of classes) {\n const arr = groups.get(cls.className) ?? []\n arr.push(cls)\n groups.set(cls.className, arr)\n }\n\n const collisions: ClassCollision[] = []\n for (const [className, group] of groups) {\n // Two declarations of the same class name in different files = collision.\n // Multiple decorators on the same file/class are NOT a collision.\n const distinctFiles = new Set(group.map((c) => c.filePath))\n if (distinctFiles.size > 1) {\n collisions.push({ className, classes: group })\n }\n }\n\n // Deterministic order\n collisions.sort((a, b) => a.className.localeCompare(b.className))\n return collisions\n}\n\n/**\n * Scan a project for decorated classes, createToken definitions, and\n * `@Inject` literal usages.\n */\nexport async function scanProject(opts: ScanOptions): Promise<ScanResult> {\n const root = resolve(opts.root)\n const files = await walk(root, opts)\n\n const classes: DiscoveredClass[] = []\n const routes: DiscoveredRoute[] = []\n const tokens: DiscoveredToken[] = []\n const injects: DiscoveredInject[] = []\n const pluginsAndAdapters: DiscoveredPluginOrAdapter[] = []\n const augmentations: DiscoveredAugmentation[] = []\n\n // Two passes: first collect all classes, then a second pass extracts\n // routes per file using the per-file class list as scoping context.\n // This keeps class discovery and route discovery independent.\n const sources = new Map<string, string>()\n for (const file of files) {\n let source: string\n try {\n source = await readFile(file, 'utf-8')\n } catch {\n continue\n }\n sources.set(file, source)\n classes.push(...extractClassesFromSource(source, file, opts.cwd))\n tokens.push(...extractTokensFromSource(source, file, opts.cwd))\n injects.push(...extractInjectsFromSource(source, file, opts.cwd))\n pluginsAndAdapters.push(...extractPluginsAndAdaptersFromSource(source, file, opts.cwd))\n augmentations.push(...extractAugmentationsFromSource(source, file, opts.cwd))\n }\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 pluginsAndAdapters.sort(\n (a, b) => a.name.localeCompare(b.name) || a.relativePath.localeCompare(b.relativePath),\n )\n augmentations.sort(\n (a, b) => a.name.localeCompare(b.name) || a.relativePath.localeCompare(b.relativePath),\n )\n\n const collisions = findCollisions(classes)\n const env = await detectEnvFile(opts.cwd, opts.envFile ?? 'src/env.ts')\n\n return {\n classes,\n routes,\n tokens,\n injects,\n collisions,\n env,\n pluginsAndAdapters,\n augmentations,\n }\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, sep } from 'node:path'\nimport type {\n ClassCollision,\n DiscoveredAugmentation,\n DiscoveredClass,\n DiscoveredEnv,\n DiscoveredInject,\n DiscoveredPluginOrAdapter,\n DiscoveredRoute,\n DiscoveredToken,\n} from './scanner'\nimport type { DiscoveredAssets } from './asset-types'\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)].toSorted()\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, includeAssets: boolean): string {\n // The kick/routes + kick/env + kick/assets TypegenPlugins write their\n // output to `kick__routes.ts` / `kick__env.ts` / `kick__assets.d.ts`\n // (see typegen runner + builtin plugins). The index re-exports them\n // via side-effect imports so `tsconfig include` of `.kickjs/types/`\n // is enough to wire up the augmentations — same surface adopters had\n // before the carve. Assets/env are conditional: the plugins skip\n // emission when there's nothing to declare, so the import would\n // dangle.\n const envImport = includeEnv ? \"import './kick__env'\\n\" : ''\n const assetsImport = includeAssets ? \"import './kick__assets'\\n\" : ''\n return `${HEADER}\nexport type { ServiceToken } from './services'\nexport type { ModuleToken } from './modules'\n\n// The registry, routes, plugins, assets, and env augmentations are\n// loaded as side-effects — importing this file (or having it on\n// tsconfig include) is enough for \\`container.resolve()\\`,\n// \\`Ctx<KickRoutes.UserController['getUser']>\\`,\n// \\`dependsOn: ['TenantAdapter']\\`, \\`assets.mails.welcome()\\`, and\n// \\`@Value('PORT')\\` to resolve.\nimport './registry'\nimport './kick__routes'\nimport './plugins'\nimport './augmentations'\n${assetsImport}${envImport}`\n}\n\n/**\n * Render the `KickJsPluginRegistry` augmentation. Each entry maps the\n * literal `name` field of a plugin/adapter to a marker type (the\n * registry value isn't load-bearing at runtime — `dependsOn` only cares\n * about `keyof`, so any non-`never` type works). We emit `'plugin'` /\n * `'adapter'` strings so DevTools can later read the registry to tell\n * the kinds apart without a second source of truth.\n *\n * When the project has no discoverable plugins/adapters, the augmentation\n * is intentionally empty rather than skipped so the `keyof` constraint\n * resolves to `never` (which is harmless — `dependsOn: []` still works).\n */\nfunction renderPlugins(items: DiscoveredPluginOrAdapter[]): string {\n // Dedupe by name — two declarations with the same name are a runtime\n // boot-time error in `mount-sort.ts`; we surface the conflict via a\n // single registry entry rather than a duplicate-key TS error.\n const byName = new Map<string, DiscoveredPluginOrAdapter>()\n for (const item of items) {\n if (!byName.has(item.name)) byName.set(item.name, item)\n }\n\n const sorted = [...byName.values()].toSorted((a, b) => a.name.localeCompare(b.name))\n const entries = sorted.map((item) => ` '${item.name}': '${item.kind}'`).join('\\n')\n\n const body = entries\n ? entries\n : ' // (no plugins/adapters discovered yet — `defineAdapter`/`definePlugin` calls feed this)'\n\n return `${HEADER}\ndeclare module '@forinda/kickjs' {\n /**\n * Map of every plugin/adapter \\`name\\` discovered in the project. The\n * value type is the kind tag (\\`'plugin'\\` or \\`'adapter'\\`); the\n * \\`keyof\\` of this interface narrows \\`dependsOn\\` so misspelled deps\n * become compile errors instead of boot-time \\`MissingMountDepError\\`.\n */\n interface KickJsPluginRegistry {\n${body}\n }\n}\n\nexport {}\n`\n}\n\n/**\n * Render the augmentation manifest — one block per `defineAugmentation`\n * call discovered in the project. The output is a `.d.ts` file that\n * does nothing at runtime but acts as in-IDE documentation: adopters\n * jumping into it see every interface their plugins offer for\n * augmentation, alongside any `description` / `example` the plugin\n * authors provided.\n */\nfunction renderAugmentations(items: DiscoveredAugmentation[]): string {\n if (items.length === 0) {\n return `${HEADER}\n// No augmentations discovered.\n//\n// Plugins advertise augmentable interfaces via:\n//\n// import { defineAugmentation } from '@forinda/kickjs'\n// defineAugmentation('FeatureFlags', {\n// description: 'Feature flag shape consumed by FlagsPlugin',\n// example: '{ beta: boolean; rolloutPercentage: number }',\n// })\n//\n// See \\`docs/guide/typegen.md#augmentations\\` for the full pattern.\nexport {}\n`\n }\n\n // Dedupe by name — multiple plugins shouldn't claim the same name,\n // but if they do we keep the first.\n const byName = new Map<string, DiscoveredAugmentation>()\n for (const item of items) {\n if (!byName.has(item.name)) byName.set(item.name, item)\n }\n\n const blocks: string[] = []\n for (const item of [...byName.values()].toSorted((a, b) => a.name.localeCompare(b.name))) {\n const docLines: string[] = []\n if (item.description) {\n // Description may itself be multi-line — preserve line breaks\n // so JSDoc renderers wrap intent correctly.\n for (const line of item.description.split('\\n')) docLines.push(` * ${line}`)\n }\n if (item.example) {\n // Split the example so each line is prefixed with ` * ` — without\n // this, raw newlines inside the example string broke the JSDoc\n // comment and IDEs rendered the whole block as one mangled line.\n // This is what made `defineAugmentation({ example: '<huge object>' })`\n // unusable for anything beyond a one-liner.\n docLines.push(` * @example`, ` * \\`\\`\\`ts`)\n for (const line of item.example.split('\\n')) docLines.push(` * ${line}`)\n docLines.push(` * \\`\\`\\``)\n }\n docLines.push(` * @see ${item.relativePath}`)\n blocks.push(\n ['/**', ...docLines, ' */', `export interface ${item.name}Augmentation {}`].join('\\n'),\n )\n }\n\n return `${HEADER}\n// Catalogue of augmentable interfaces in this project. The interfaces\n// below are documentation only — augment the source-of-truth interfaces\n// in your own \\`d.ts\\` files (the framework declares the actual types).\n\n${blocks.join('\\n\\n')}\n`\n}\n\n/** 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 /** Number of plugin/adapter names registered into KickJsPluginRegistry */\n pluginEntries: number\n /** Number of `defineAugmentation` calls catalogued */\n augmentationEntries: number\n /** Number of typed asset entries (every file in every assetMap dir) */\n assetEntries: 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 /** Plugins/adapters discovered via `defineAdapter`/`definePlugin`/`implements AppAdapter` */\n pluginsAndAdapters?: DiscoveredPluginOrAdapter[]\n /** `defineAugmentation` calls discovered in the project */\n augmentations?: DiscoveredAugmentation[]\n /** Discovered typed assets from the assetMap walker (PR 4 of asset-manager). */\n assets?: DiscoveredAssets\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 pluginsAndAdapters = [],\n augmentations = [],\n assets = { entries: [], count: 0 } as DiscoveredAssets,\n outDir,\n allowDuplicates = false,\n schemaValidator: _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 + env.ts + assets.d.ts are owned by the `kick/routes`,\n // `kick/env`, and `kick/assets` typegen plugins (M2.B-T8 carve).\n // They write `kick__routes.ts` / `kick__env.ts` / `kick__assets.d.ts`\n // via the plugin runner. Legacy emissions of all three files are\n // gone — `assets.d.ts` was the last holdover (#TBD) and stopped\n // emitting once the plugin pipeline proved stable.\n const pluginsFile = join(outDir, 'plugins.d.ts')\n const augmentationsFile = join(outDir, 'augmentations.d.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 pluginsContent = renderPlugins(pluginsAndAdapters)\n const augmentationsContent = renderAugmentations(augmentations)\n const indexContent = renderIndex(env !== null, assets.count > 0)\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(pluginsFile, pluginsContent, 'utf-8')\n await writeFile(augmentationsFile, augmentationsContent, 'utf-8')\n await writeFile(indexFile, indexContent, 'utf-8')\n\n const written = [\n registryFile,\n servicesFile,\n modulesFile,\n pluginsFile,\n augmentationsFile,\n indexFile,\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 // Dedupe the plugin/adapter count — two declarations with the same\n // name collapse to one registry entry (mount-sort still throws at\n // boot, but the typegen layer reports the unique surface).\n const uniquePluginNames = new Set(pluginsAndAdapters.map((p) => p.name)).size\n const uniqueAugmentations = new Set(augmentations.map((a) => a.name)).size\n\n return {\n registryEntries: classTokens.length,\n serviceTokens: new Set(allServices).size,\n moduleTokens: modules.length,\n routeEntries: routes.length,\n pluginEntries: uniquePluginNames,\n augmentationEntries: uniqueAugmentations,\n assetEntries: assets.count,\n envWritten: env !== null,\n written,\n resolvedCollisions: collisions.length,\n }\n}\n","/**\n * Token convention validator (architecture.md §22.4 #1).\n *\n * Warns (never errors) on `createToken('literal')` calls whose literal\n * doesn't match the convention from §22.2:\n *\n * <scope>/<PascalKey>[/<suffix>][:<instance>[:<extra>]*]\n *\n * Where `<scope>` is lowercase + may start with the reserved `kick/`\n * prefix for first-party tokens. Legacy framework tokens that started\n * with `kickjs.` (pre-§22) are exempt — they get migrated alongside\n * the first-party adapter migrations.\n *\n * The matching reserved-prefix check (third-party tokens squatting\n * `kick/`) is the responsibility of `@forinda/kickjs-lint`'s\n * `token-reserved-prefix` rule, not the typegen layer — different\n * audience (adopter codebase) and different default severity.\n *\n * @module @forinda/kickjs-cli/typegen/token-conventions\n */\n\nimport type { DiscoveredToken } from './scanner'\n\n/**\n * Regex for the §22.2 token shape. Breakdown:\n *\n * - `^(kick\\/)?` — optional reserved framework prefix.\n * - `([a-z][\\w-]*\\/[A-Z]\\w*)` — `<scope>/<PascalKey>`. Scope is\n * lowercase, key is PascalCase.\n * - `(\\/.+)?` — optional `/suffix` for sub-flavours\n * (e.g. `mycorp/Cache/redis`).\n * - `(:[a-z][\\w-]+(:[a-z][\\w-]+)*)?` — optional `:instance` (and\n * further `:extra` colon-sections) for `.scoped()` shards.\n */\nconst TOKEN_CONVENTION_REGEX =\n /^(kick\\/)?([a-z][\\w-]*\\/[A-Z]\\w*)(\\/.+)?(:[a-z][\\w-]+(:[a-z][\\w-]+)*)?$/\n\nconst LEGACY_PREFIX = 'kickjs.'\n\nexport interface TokenConventionWarning {\n token: string\n variable: string | null\n filePath: string\n reason: string\n suggestion?: string\n}\n\nexport function validateTokenConventions(\n tokens: readonly DiscoveredToken[],\n): TokenConventionWarning[] {\n const warnings: TokenConventionWarning[] = []\n for (const token of tokens) {\n const name = token.name\n if (name.startsWith(LEGACY_PREFIX)) continue\n if (TOKEN_CONVENTION_REGEX.test(name)) continue\n warnings.push({\n token: name,\n variable: token.variable,\n filePath: token.relativePath,\n reason: 'does not match `<scope>/<PascalKey>[/<suffix>][:<instance>]`',\n suggestion: suggestRename(name),\n })\n }\n return warnings\n}\n\nfunction suggestRename(name: string): string | undefined {\n if (/^[A-Z]\\w*$/.test(name)) {\n return `'<scope>/${name}' (e.g. 'mycorp/${name}')`\n }\n if (name.includes('.')) {\n return `consider '<scope>/PascalKey' instead of dotted form`\n }\n const slashLower = /^([a-z][\\w-]*)\\/([a-z]\\w*)$/.exec(name)\n if (slashLower) {\n const [, scope, key] = slashLower\n return `'${scope}/${key.charAt(0).toUpperCase()}${key.slice(1)}'`\n }\n return undefined\n}\n","/**\n * Walks every `assetMap` entry's source directory + emits a typed\n * `KickAssets` ambient augmentation (assets-plan.md PR 4). Generates\n * `.kickjs/types/assets.d.ts` so adopters get autocomplete on\n * `assets.<namespace>.<key>` and `@Asset('<namespace>/<key>')`.\n *\n * Pure module — no side effects beyond what the caller does with the\n * returned content. Mirrors the shape of `renderPlugins` /\n * `renderRegistry` in the generator so the typegen output stays\n * consistent across surfaces.\n *\n * @module @forinda/kickjs-cli/typegen/asset-types\n */\n\nimport { statSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { globSync } from 'glob'\nimport { groupAssetKeys } from '@forinda/kickjs'\nimport type { AssetMapEntry } from '../config'\n\nexport interface DiscoveredAssetEntry {\n namespace: string\n /**\n * Path under the namespace — what comes after the `<namespace>/`\n * prefix in the logical key. Stripped of extension when the file's\n * basename was unique within the namespace, or carries the full\n * extension when a collision was auto-resolved.\n */\n key: string\n}\n\nexport interface DiscoveredAssets {\n entries: DiscoveredAssetEntry[]\n count: number\n}\n\nexport function discoverAssets(\n assetMap: Record<string, AssetMapEntry> | undefined,\n cwd: string,\n): DiscoveredAssets {\n if (!assetMap) return { entries: [], count: 0 }\n\n const seen = new Map<string, DiscoveredAssetEntry>()\n for (const [namespace, entry] of Object.entries(assetMap)) {\n if (!entry || typeof entry.src !== 'string') continue\n const srcAbs = resolve(cwd, entry.src)\n if (!isDir(srcAbs)) continue\n // Mirror build.ts: same `glob` engine, same defaults — typegen +\n // build agree on what counts as an asset, including full minimatch\n // patterns (extglob, negation, etc.) that the old lite matcher\n // silently treated as match-all.\n const matches = globSync(entry.glob ?? '**/*', {\n cwd: srcAbs,\n nodir: true,\n dot: false,\n posix: true,\n })\n matches.sort()\n // Run the same key-shaping logic the build pipeline + dev resolver\n // use, so the emitted type matches the manifest exactly. Auto-mode\n // keeps extensions on collision groups; singletons stay stripped.\n const { pairs } = groupAssetKeys(namespace, matches, {\n strategy: entry.keys ?? 'auto',\n })\n for (const { key: logical } of pairs) {\n // The helper returns the full `<namespace>/<...>` form; strip\n // the namespace prefix so the entry shape matches the legacy\n // contract (key = path under namespace).\n const subKey = logical.slice(namespace.length + 1)\n seen.set(logical, { namespace, key: subKey })\n }\n }\n return { entries: [...seen.values()], count: seen.size }\n}\n\nexport function renderAssetTypes(discovered: DiscoveredAssets): string {\n const HEADER = `/* eslint-disable */\n// AUTO-GENERATED by \\`kick typegen\\`. DO NOT EDIT.\n// Re-run with \\`kick typegen\\` or rely on \\`kick dev\\` to refresh.\n`\n\n if (discovered.entries.length === 0) {\n return `${HEADER}\ndeclare module '@forinda/kickjs' {\n /**\n * Map of every typed asset discovered in the project's assetMap.\n * (No assetMap entries discovered yet — declare with\n * \\`assetMap: { name: { src: 'src/...' } }\\` in kick.config.ts.)\n */\n interface KickAssets {}\n}\n\nexport {}\n`\n }\n\n const tree: TreeNode = {}\n for (const entry of discovered.entries) {\n const path = `${entry.namespace}/${entry.key}`.split('/')\n let node = tree\n for (let i = 0; i < path.length - 1; i++) {\n const part = path[i]\n const existing = node[part]\n if (existing === LEAF) {\n // The intermediate path was previously claimed by a leaf —\n // collision between a file (`mails/welcome.ejs`) and a\n // directory (`mails/welcome/x.ejs`). Promote to a sub-tree\n // so the directory wins (consistent with the file-tree\n // semantics; the build pipeline emits a runtime warning for\n // the colliding leaf).\n const promoted: TreeNode = {}\n node[part] = promoted\n node = promoted\n } else {\n if (!existing) node[part] = {}\n node = node[part] as TreeNode\n }\n }\n const leaf = path[path.length - 1]\n if (typeof node[leaf] === 'object') {\n // The leaf collides with an already-built subtree. Skip; the\n // directory wins (matches the promote-to-subtree branch above).\n continue\n }\n node[leaf] = LEAF\n }\n\n const body = renderTree(tree, ' ')\n return `${HEADER}\ndeclare module '@forinda/kickjs' {\n /**\n * Map of every typed asset discovered in the project's assetMap.\n * Each leaf is a \\`() => string\\` thunk that returns the resolved\n * absolute path for the file in the current run mode (dev → src,\n * prod → dist).\n */\n interface KickAssets {\n${body}\n }\n}\n\nexport {}\n`\n}\n\nconst LEAF = Symbol('asset-leaf')\ntype TreeNode = { [key: string]: TreeNode | typeof LEAF }\n\nfunction renderTree(node: TreeNode, indent: string): string {\n const keys = Object.keys(node).toSorted()\n const lines: string[] = []\n for (const key of keys) {\n const child = node[key]\n const safeKey = isIdentifier(key) ? key : JSON.stringify(key)\n if (child === LEAF) {\n lines.push(`${indent}${safeKey}: () => string`)\n } else {\n lines.push(`${indent}${safeKey}: {`)\n lines.push(renderTree(child, `${indent} `))\n lines.push(`${indent}}`)\n }\n }\n return lines.join('\\n')\n}\n\nfunction isIdentifier(str: string): boolean {\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(str)\n}\n\nfunction isDir(path: string): boolean {\n try {\n return statSync(path).isDirectory()\n } catch {\n return false\n }\n}\n","/**\n * Public entry point for the KickJS typegen module.\n *\n * Used by:\n * - `kick typegen` (one-shot or watch mode)\n * - `kick dev` (auto-runs once before Vite starts; refreshes when files change)\n *\n * @module @forinda/kickjs-cli/typegen\n */\n\nimport { resolve, basename } from 'node:path'\nimport { readdir, stat, unlink } from 'node:fs/promises'\nimport { scanProject, type ScanResult } from './scanner'\nimport { generateTypes, type GenerateResult, TokenCollisionError } from './generator'\nimport { validateTokenConventions, type TokenConventionWarning } from './token-conventions'\nimport { discoverAssets } from './asset-types'\nimport type { AssetMapEntry } from '../config'\nimport type { TypegenPluginResult } from './plugin'\n\nexport type {\n DiscoveredClass,\n DiscoveredToken,\n DiscoveredInject,\n DiscoveredEnv,\n DiscoveredPluginOrAdapter,\n DiscoveredAugmentation,\n ClassCollision,\n ScanResult,\n} from './scanner'\nexport type { GenerateResult } from './generator'\nexport { TokenCollisionError } from './generator'\nexport { validateTokenConventions, type TokenConventionWarning } from './token-conventions'\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 * Asset map from `kick.config.ts`. When set, `runTypegen` walks\n * each entry's `src` directory + emits `.kickjs/types/assets.d.ts`\n * augmenting `KickAssets` for autocomplete on `assets.x.y()` and\n * `@Asset('x/y')`. Omit to skip the asset typegen pass entirely.\n */\n assetMap?: Record<string, AssetMapEntry>\n /**\n * Whether `runTypegen` should also run the TypegenPlugin pipeline\n * (`runAllPluginTypegens`) after the legacy generator pass. Defaults\n * to `true` so single-shot callers (kick g, commands/typegen, tests)\n * keep getting a fully-refreshed `.kickjs/types/` from one entry\n * point. `watchTypegen` flips this to `false` because it manages\n * the plugin pass itself + would otherwise double-run it on every\n * filesystem trigger.\n */\n runPlugins?: boolean\n}\n\n/** Resolve options to absolute paths */\nfunction resolveOptions(opts: RunTypegenOptions): {\n cwd: string\n srcDir: string\n outDir: string\n silent: boolean\n allowDuplicates: boolean\n schemaValidator: 'zod' | false\n envFile: string | false\n} {\n const cwd = opts.cwd ?? process.cwd()\n return {\n cwd,\n srcDir: resolve(cwd, opts.srcDir ?? 'src'),\n outDir: resolve(cwd, opts.outDir ?? '.kickjs/types'),\n silent: opts.silent ?? false,\n allowDuplicates: opts.allowDuplicates ?? false,\n schemaValidator: opts.schemaValidator ?? false,\n envFile: opts.envFile ?? 'src/env.ts',\n }\n}\n\n/**\n * Run a single typegen pass: scan source files, generate `.d.ts` files.\n *\n * Returns the discovered scan result alongside the generation result so\n * callers (`kick dev`, devtools) can log them or feed them to other tools.\n *\n * Throws `TokenCollisionError` if duplicate class names are found and\n * `allowDuplicates` is false.\n */\nexport async function runTypegen(opts: RunTypegenOptions = {}): Promise<{\n scan: ScanResult\n result: GenerateResult\n /** Token convention warnings — empty when every literal matches §22.2. */\n tokenWarnings: TokenConventionWarning[]\n}> {\n const { cwd, srcDir, outDir, silent, allowDuplicates, 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 assets = discoverAssets(opts.assetMap, cwd)\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 pluginsAndAdapters: scan.pluginsAndAdapters,\n augmentations: scan.augmentations,\n assets,\n outDir,\n allowDuplicates,\n schemaValidator,\n })\n // M2.B-T8 carve: routes + env live in plugin typegens, not the\n // legacy generator above. Run the plugin pipeline as part of\n // `runTypegen` by default so single-shot callers (kick g <leaf> /\n // kick new / tests) stay on one entry point and see a fully-\n // refreshed `.kickjs/types/` after the call returns. Watch mode\n // and the `kick typegen` / `kick dev` / `kick build` CLI paths\n // opt out (`runPlugins: false`) because they drive the plugin\n // pass externally and would otherwise double-run it.\n let pluginResults: TypegenPluginResult[] = []\n if (opts.runPlugins !== false) {\n try {\n const { runAllPluginTypegens } = await import('./run-plugins')\n const { loadKickConfig } = await import('../config')\n const pluginConfig = await loadKickConfig(cwd)\n pluginResults = await runAllPluginTypegens({ cwd, config: pluginConfig, silent: true })\n } catch (err) {\n // Broken plugins shouldn't block dev tooling — generators have\n // already written the rest of `.kickjs/types/`, and surfacing\n // here as a throw would abort the wider command (kick g, kick\n // new). When `silent` is false, log the message so the failure\n // is at least visible; otherwise swallow.\n if (!silent) {\n const msg = err instanceof Error ? err.message : String(err)\n console.warn(` kick typegen: plugin pipeline failed (${msg}) — continuing`)\n }\n }\n }\n\n // Sweep stale `.kickjs/types/*` files. Older CLI versions emitted\n // `env.ts`, `routes.ts`, and (until this fix) `assets.d.ts` directly\n // from the legacy generator. Once those carved out into `kick/env`,\n // `kick/routes`, and `kick/assets` typegen plugins, the legacy\n // file names became orphans — but were never deleted on subsequent\n // runs, so adopters who upgraded saw `KickAssets`/`KickEnv`/\n // `KickRoutes` declared twice (once legacy, once `kick__*`). Sweep\n // walks the output dir at the end of every pass and unlinks any\n // top-level file not in the expected set, so a single `kick typegen`\n // run after upgrade self-heals.\n if (opts.runPlugins !== false) {\n await sweepStaleTypegen(outDir, result.written, pluginResults, silent)\n }\n\n const tokenWarnings = validateTokenConventions(scan.tokens)\n const elapsed = Date.now() - start\n\n if (!silent) {\n const where = outDir.replace(cwd + '/', '')\n const collisionNote =\n result.resolvedCollisions > 0 ? `, ${result.resolvedCollisions} collisions namespaced` : ''\n const envNote = result.envWritten ? ', env typed' : ''\n const pluginNote = result.pluginEntries > 0 ? `, ${result.pluginEntries} plugins/adapters` : ''\n const augNote =\n result.augmentationEntries > 0 ? `, ${result.augmentationEntries} augmentations` : ''\n const assetNote = result.assetEntries > 0 ? `, ${result.assetEntries} assets` : ''\n console.log(\n ` kick typegen → ${result.serviceTokens} services, ${result.routeEntries} routes, ${result.moduleTokens} modules${pluginNote}${augNote}${assetNote}${envNote}${collisionNote} → ${where} (${elapsed}ms)`,\n )\n if (tokenWarnings.length > 0) {\n console.warn(\n ` kick typegen: ${tokenWarnings.length} token(s) don't match the §22.2 convention:`,\n )\n for (const warning of tokenWarnings) {\n const variableNote = warning.variable ? ` [${warning.variable}]` : ''\n console.warn(\n ` '${warning.token}' (${warning.filePath})${variableNote} — ${warning.reason}`,\n )\n if (warning.suggestion) {\n console.warn(` → suggestion: ${warning.suggestion}`)\n }\n }\n }\n }\n\n return { scan, result, tokenWarnings }\n}\n\n/**\n * Watch mode for `kick typegen --watch`.\n *\n * Uses Node's built-in `fs.watch` (recursive, available on Linux 22+ and\n * macOS 19+). Falls back gracefully if recursive watch is not supported.\n *\n * Debounces re-runs by 100ms so a bulk file change (e.g. `kick g module`\n * creating 5 files at once) emits one regen, not five.\n *\n * In watch mode collisions are reported but never thrown — the watcher\n * keeps running so the user can fix the rename and the next scan\n * recovers automatically.\n *\n * Returns a `stop()` function that closes the watcher.\n */\nexport async function watchTypegen(opts: RunTypegenOptions = {}): Promise<() => void> {\n const resolved = resolveOptions(opts)\n const { srcDir, silent, cwd } = resolved\n // Watch mode always tolerates collisions — otherwise an in-progress\n // rename would crash the dev loop. The error is still printed.\n // `runPlugins: false` keeps `runTypegen` from double-running the\n // plugin pipeline; the watcher invokes `runPlugins()` explicitly\n // after each `runLegacy()` so both phases land before the sweep.\n const runOpts: RunTypegenOptions = { ...resolved, allowDuplicates: true, runPlugins: false }\n\n // Polling is the right strategy for Docker bind mounts, WSL crosses,\n // NFS, and some kernel/filesystem combos where `fs.watch` returns\n // without errors but events silently drop. Adopters opt in via\n // `KICKJS_WATCH_POLLING=1`; default stays event-based (lower CPU).\n const forcePolling =\n process.env.KICKJS_WATCH_POLLING === '1' || process.env.KICKJS_WATCH_POLLING === 'true'\n\n // Lazy-import the plugin pipeline + config loader to avoid an eager\n // module-eval cycle (this file is reachable from plugin/builtins via\n // commands/typegen → ../typegen).\n const [{ runAllPluginTypegens }, { loadKickConfig }] = await Promise.all([\n import('./run-plugins'),\n import('../config'),\n ])\n const pluginConfig = await loadKickConfig(cwd)\n // Track the most recent generator + plugin outputs so the post-run\n // sweep knows the full expected set. The legacy generator returns\n // its written list on each call (captured below via a closure), and\n // the plugin pipeline returns one per plugin.\n let lastGeneratorWritten: readonly string[] = []\n const runLegacy = async () => {\n try {\n const out = await runTypegen({ ...runOpts })\n lastGeneratorWritten = out.result.written\n } catch (err) {\n if (silent) return\n if (err instanceof TokenCollisionError) {\n console.error('\\n' + err.message + '\\n')\n } else {\n const msg = err instanceof Error ? err.message : String(err)\n console.error(` kick typegen failed: ${msg}`)\n }\n }\n }\n const runPlugins = async () => {\n try {\n const pluginResults = await runAllPluginTypegens({\n cwd,\n config: pluginConfig,\n silent: true,\n })\n await sweepStaleTypegen(resolved.outDir, lastGeneratorWritten, pluginResults, true)\n } catch {\n /* swallow — watcher must never die */\n }\n }\n\n // Initial run — legacy pass first, then plugin typegens.\n await runLegacy()\n await runPlugins()\n\n const { watch } = await import('node:fs')\n\n let timer: ReturnType<typeof setTimeout> | null = null\n const trigger = (filename: string | null) => {\n // Only react to TypeScript source changes; ignore everything else\n if (!filename) return\n if (!/\\.(ts|tsx|mts|cts)$/.test(filename)) return\n if (filename.includes('.kickjs')) return\n if (filename.endsWith('.d.ts')) return\n\n if (timer) clearTimeout(timer)\n timer = setTimeout(() => {\n void runLegacy().then(runPlugins)\n }, 100)\n }\n\n // Forced-polling path — skip fs.watch entirely and just re-scan\n // periodically. The 2s interval matches the existing fallback so\n // adopters who flip the env var don't see a dramatic CPU jump.\n if (forcePolling) {\n if (!silent) {\n console.log(' kick typegen: polling mode (KICKJS_WATCH_POLLING)')\n }\n const interval = setInterval(() => {\n safeRun({ ...runOpts, silent: true }, true)\n }, 2000)\n return () => clearInterval(interval)\n }\n\n let watcher: ReturnType<typeof watch>\n try {\n watcher = watch(srcDir, { recursive: true }, (_event, filename) => {\n trigger(filename)\n })\n } catch (err: any) {\n if (!silent) {\n console.warn(\n ` kick typegen: watch mode unavailable (${err?.message ?? err}). Falling back to polling.`,\n )\n }\n // Polling fallback — re-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\n/**\n * Remove orphaned typegen output. The legacy generator emitted\n * `assets.d.ts`, `env.ts`, and `routes.ts` directly; once those carved\n * into the `kick/assets`, `kick/env`, and `kick/routes` plugins, the\n * legacy file names became stale on disk for any project that had run\n * an older CLI. Without a sweep, both copies coexist and adopters get\n * duplicated `KickAssets` / `KickEnv` / `KickRoutes` augmentations.\n *\n * Strategy: enumerate the top level of `outDir`, keep the union of\n * generator-written files + plugin-written files, unlink anything\n * else. Subdirectories are left alone — typegen never creates them.\n * Errors are swallowed (silent → no log) so a transient ENOENT or\n * permission glitch never aborts the wider command.\n */\nexport async function sweepStaleTypegen(\n outDir: string,\n generatorWritten: readonly string[],\n pluginResults: readonly TypegenPluginResult[],\n silent: boolean,\n): Promise<string[]> {\n const expected = new Set<string>()\n for (const file of generatorWritten) expected.add(basename(file))\n for (const r of pluginResults) {\n if (r.outFile) expected.add(basename(r.outFile))\n }\n // Hidden files we own at the types/ level — none today, but the\n // gitignore at .kickjs/.gitignore lives one level up so it's not\n // a candidate here.\n let entries: string[]\n try {\n entries = await readdir(outDir)\n } catch {\n return []\n }\n const removed: string[] = []\n for (const name of entries) {\n if (expected.has(name)) continue\n const abs = resolve(outDir, name)\n try {\n const s = await stat(abs)\n if (!s.isFile()) continue\n await unlink(abs)\n removed.push(name)\n } catch {\n // Best-effort; don't crash the typegen pass on a stat/unlink miss.\n }\n }\n if (removed.length > 0 && !silent) {\n console.log(` kick typegen: swept ${removed.length} stale file(s): ${removed.join(', ')}`)\n }\n return removed\n}\n"],"mappings":";;;;;;;;;;iXAgCA,MAAa,EAAkB,CAC7B,UACA,aACA,aACA,aACA,YACA,SACD,CAwLK,EAAqB,CAAC,MAAO,OAAQ,OAAQ,OAAO,CACpD,EAAmB,CAAC,eAAgB,UAAW,OAAQ,QAAS,SAAU,SAAU,QAAQ,CAM5F,EAAwB,IAAI,OAChC,OAAO,GAAG,KAAK,EAAgB,KAAK,IAAI,CAAC,eACvC,OAAO,GAAG,qCACV,OAAO,GAAG,yDACZ,IACD,CAWK,EAAyB,IAAI,OACjC,OAAO,GAAG,sDACR,OAAO,GAAG,oCACV,OAAO,GAAG,qCACZ,IACD,CAOK,EACJ,8GAMI,EAA0B,8DAG1B,EAAuB,2CAQvB,EAAsB,sDAQtB,EAA0B,IAAI,OAClC,OAAO,GAAG,wDACR,OAAO,GAAG,oCACV,OAAO,GAAG,sCACZ,IACD,CAGK,EAAyB,iDAOzB,EAA4B,+DAc5B,EAAwB,IAAI,OAAO,OAAO,GAAG,KAAK,CAX/B,MAAO,OAAQ,MAAO,SAAU,QAWc,CAAC,KAAK,IAAI,CAAC,QAAS,IAAI,CAS/F,SAAS,EAAkB,EAAc,EAAyB,CAChE,IAAI,EAAQ,EACZ,IAAK,IAAI,EAAI,EAAU,EAAG,EAAI,EAAK,OAAQ,IAAK,CAC9C,IAAM,EAAK,EAAK,GAChB,GAAI,IAAO,IAAK,YACP,IAAO,MACd,IACI,IAAU,GAAG,OAAO,EAG5B,MAAO,GAaT,SAAS,EACP,EACA,EAC+C,CAC/C,IAAI,EAAM,EAEV,KAAO,EAAM,EAAM,QAAQ,CACzB,KAAO,EAAM,EAAM,QAAU,KAAK,KAAK,EAAM,GAAK,EAAE,IACpD,GAAI,EAAM,KAAS,IAAK,MACxB,IAAM,EAAW,EAAM,MAAM,EAAI,CAAC,MAAM,eAAe,CACvD,GAAI,CAAC,EAAU,MAEf,IADA,GAAO,EAAS,GAAG,OACZ,EAAM,EAAM,QAAU,KAAK,KAAK,EAAM,GAAK,EAAE,IACpD,GAAI,EAAM,KAAS,IAAK,CACtB,IAAM,EAAQ,EAAkB,EAAO,EAAI,CAC3C,GAAI,EAAQ,EAAG,OAAO,KACtB,EAAM,EAAQ,GAIlB,KAAO,EAAM,EAAM,QAAU,KAAK,KAAK,EAAM,GAAK,EAAE,IACpD,IAAK,IAAM,IAAO,CAAC,SAAU,UAAW,YAAY,CAClD,GAAI,EAAM,MAAM,EAAK,EAAM,EAAI,OAAO,GAAK,GAAO,KAAK,KAAK,EAAM,OAAO,EAAM,EAAI,OAAO,CAAC,CAAE,CAE3F,IADA,GAAO,EAAI,OACJ,EAAM,EAAM,QAAU,KAAK,KAAK,EAAM,GAAK,EAAE,IACpD,MAGJ,GAAI,EAAM,MAAM,EAAK,EAAM,EAAE,GAAK,SAAW,KAAK,KAAK,EAAM,OAAO,EAAM,EAAE,CAAC,CAE3E,IADA,GAAO,EACA,EAAM,EAAM,QAAU,KAAK,KAAK,EAAM,GAAK,EAAE,IAGtD,IAAM,EAAc,EAAM,MAAM,EAAI,CAAC,MAAM,uBAAuB,CAElE,OADK,EACE,CAAE,WAAY,EAAY,GAAI,OAAQ,EAAM,EAAY,GAAG,OAAQ,CADjD,KAK3B,SAAS,EAAkB,EAAwB,CAEjD,OADgB,EAAK,MAAM,mBAAmB,EAAI,EAAE,EACrC,IAAK,GAAM,EAAE,MAAM,EAAE,CAAC,CAWvC,SAAS,EAA6B,EAAc,EAA8B,CAGhF,IAAM,EAAI,IADK,OAAO,OAAO,GAAG,KAAK,EAAM,2BAA4B,IAC3D,CAAC,KAAK,EAAK,CAEvB,OADK,EACE,EAAE,GADM,KAejB,SAAS,EAAoB,EAAgB,EAAmC,CAK9E,IAAM,EAAQ,IAHM,OAClB,OAAO,GAAG,iCAAiC,EAAW,2CAEnC,CAAC,KAAK,EAAO,CAClC,GAAI,EAAO,OAAO,EAAM,GAMxB,IAAM,EAAM,IAHU,OACpB,OAAO,GAAG,wBAAwB,EAAW,kCAE1B,CAAC,KAAK,EAAO,CAClC,GAAI,EAAK,OAAO,EAAI,GAMpB,IAAM,EAAK,IAHM,OACf,OAAO,GAAG,sBAAsB,EAAW,kCAE9B,CAAC,KAAK,EAAO,CAQ5B,OAPI,EAAW,EAAG,GAKd,IADgB,OAAO,OAAO,GAAG,oCAAoC,EAAW,IACzE,CAAC,KAAK,EAAO,CAAS,GAE1B,KAgBT,SAAS,EACP,EACA,EAC2E,CAC3E,IAAM,EAAW,6CAA6C,KAAK,EAAe,CAClF,GAAI,CAAC,EAAU,CAEb,IAAM,EAAQ,mCAAmC,KAAK,EAAe,CAErE,OADK,EACE,EAAuB,EAAM,GAAG,MAAM,CAAE,EAAW,CADvC,KAGrB,OAAO,EAAuB,EAAS,GAAG,MAAM,CAAE,EAAW,CAG/D,SAAS,EACP,EACA,EACoE,CAEpE,GAAI,EAAI,WAAW,IAAI,CACrB,OAAO,EAAyB,EAAI,CAGtC,IAAM,EAAU,kBAAkB,KAAK,EAAI,CAC3C,GAAI,EAAS,CACX,IAAM,EAAQ,EAAQ,GAMhB,EAAa,IAJC,OAClB,OAAO,GAAG,WAAW,EAAM,uCAC3B,IAEwB,CAAC,KAAK,EAAW,CAC3C,GAAI,EACF,OAAO,EAAyB,EAAW,GAAG,CAIlD,MAAO,CAAE,WAAY,EAAE,CAAE,SAAU,EAAE,CAAE,WAAY,EAAE,CAAE,CAIzD,SAAS,EAAmB,EAAiB,EAAuB,CAElE,IAAM,EAAI,IADK,OAAO,OAAO,GAAG,GAAG,EAAI,uBAC3B,CAAC,KAAK,EAAQ,CAE1B,OADK,EACE,MAAM,KAAK,EAAE,GAAG,SAAS,uBAAuB,CAAC,CAAC,IAAK,GAAM,EAAE,GAAG,CAD1D,EAAE,CAKnB,SAAS,EAAyB,EAIhC,CACA,MAAO,CACL,WAAY,EAAmB,EAAS,aAAa,CACrD,SAAU,EAAmB,EAAS,WAAW,CACjD,WAAY,EAAmB,EAAS,aAAa,CACtD,CAIH,eAAe,EAAK,EAAa,EAAsC,CACrE,IAAM,EAAO,EAAK,YAAc,EAC1B,EAAW,EAAK,SAAW,EAC3B,EAAgB,EAAE,CAEpB,EACJ,GAAI,CACF,EAAW,MAAM,EAAQ,EAAK,CAAE,cAAe,GAAM,SAAU,QAAS,CAAC,MACnE,CACN,OAAO,EAGT,IAAK,IAAM,KAAS,EAAS,CAC3B,IAAM,EAAO,EAAK,EAAK,EAAM,KAAK,CAC5B,EAAM,EAAS,EAAK,IAAK,EAAK,CAEhC,EAAS,KAAM,GAAO,EAAI,SAAS,EAAG,CAAC,GAEvC,EAAM,aAAa,CACrB,EAAI,KAAK,GAAI,MAAM,EAAK,EAAM,EAAK,CAAE,CAC5B,EAAM,QAAQ,EACnB,EAAK,KAAM,GAAQ,EAAM,KAAK,SAAS,EAAI,CAAC,EAC9C,EAAI,KAAK,EAAK,EAKpB,OAAO,EAIT,SAAS,EAAW,EAAkB,EAAqB,CACzD,OAAO,EAAS,EAAK,EAAS,CAAC,MAAM,EAAI,CAAC,KAAK,IAAI,CAIrD,SAAgB,GACd,EACA,EACA,EACmB,CACnB,IAAM,EAAyB,EAAE,CAC3B,EAAU,EAAW,EAAU,EAAI,CAEzC,EAAsB,UAAY,EAClC,IAAI,EACJ,MAAQ,EAAQ,EAAsB,KAAK,EAAO,IAAM,MAAM,CAC5D,GAAM,EAAG,EAAW,EAAe,GAAa,EAChD,EAAI,KAAK,CACP,YACW,YACX,WACA,aAAc,EACd,UAAW,EAAQ,EACpB,CAAC,CAMJ,EAAuB,UAAY,EACnC,IAAI,EACJ,MAAQ,EAAW,EAAuB,KAAK,EAAO,IAAM,MAAM,CAChE,GAAM,EAAG,EAAe,GAAa,EACjC,EAAI,KAAM,GAAM,EAAE,YAAc,GAAa,EAAE,WAAa,EAAS,EACzE,EAAI,KAAK,CACP,YACA,UAAW,SACX,WACA,aAAc,EACd,UAAW,EAAQ,EACpB,CAAC,CAGJ,OAAO,EAIT,SAAgB,GACd,EACA,EACA,EACmB,CACnB,IAAM,EAAyB,EAAE,CAC3B,EAAU,EAAW,EAAU,EAAI,CACnC,EAAO,IAAI,IAGjB,EAAmB,UAAY,EAC/B,IAAI,EACJ,MAAQ,EAAQ,EAAmB,KAAK,EAAO,IAAM,MAAM,CACzD,GAAM,CAAC,EAAM,EAAU,GAAQ,EAC/B,EAAK,IAAI,EAAK,CACd,EAAI,KAAK,CAAE,OAAM,WAAU,WAAU,aAAc,EAAS,CAAC,CAK/D,IADA,EAAwB,UAAY,GAC5B,EAAQ,EAAwB,KAAK,EAAO,IAAM,MACpD,EAAK,IAAI,EAAM,GAAG,EACtB,EAAI,KAAK,CACP,KAAM,EAAM,GACZ,SAAU,KACV,WACA,aAAc,EACf,CAAC,CAGJ,OAAO,EAcT,SAAgB,GACd,EACA,EACA,EACA,EACmB,CACnB,IAAM,EAAyB,EAAE,CACjC,GAAI,EAAc,SAAW,EAAG,OAAO,EACvC,IAAM,EAAU,EAAW,EAAU,EAAI,CAGnC,EAA4D,EAAE,CACpE,IAAK,IAAM,KAAO,EAAe,CAE/B,IAAM,EAAI,IADK,OAAO,OAAO,GAAG,WAAW,EAAI,UAAU,IAC7C,CAAC,KAAK,EAAO,CACrB,GAAG,QAAU,IAAA,IACf,EAAU,KAAK,CAAE,MAAK,MAAO,EAAE,MAAO,CAAC,CAG3C,EAAU,MAAM,EAAG,IAAM,EAAE,MAAQ,EAAE,MAAM,CAE3C,IAAK,IAAI,EAAI,EAAG,EAAI,EAAU,OAAQ,IAAK,CACzC,GAAM,CAAE,MAAK,SAAU,EAAU,GAC3B,EAAM,EAAI,EAAI,EAAU,OAAS,EAAU,EAAI,GAAG,MAAQ,EAAO,OACjE,EAAQ,EAAO,MAAM,EAAO,EAAI,CAMtC,EAAsB,UAAY,EAClC,IAAI,EACJ,MAAQ,EAAa,EAAsB,KAAK,EAAM,IAAM,MAAM,CAChE,IAAM,EAAO,EAAW,GAClB,EAAiB,EAAW,MAC5B,EAAY,EAAsB,UAAY,EAC9C,EAAa,EAAkB,EAAO,EAAU,CACtD,GAAI,EAAa,EAAG,SAEpB,IAAM,EAAY,EAAM,MAAM,EAAY,EAAG,EAAW,CAElD,EAAmB,EAAU,MAAM,0BAA0B,CAC7D,EAAO,GAAoB,EAAiB,GAAG,OAAS,EAAI,EAAiB,GAAK,IAElF,EAAa,EAA0B,EAAO,EAAa,EAAE,CACnE,GAAI,CAAC,EAAY,SACjB,GAAM,CAAE,aAAY,UAAW,EAI/B,EAAsB,UAAY,EAGlC,IAAM,EAAQ,EADM,EAAM,MAAM,EAAgB,EACD,CAAE,EAAO,CAElD,EAAS,EAA6B,EAAW,OAAO,CACxD,EAAU,EAA6B,EAAW,QAAQ,CAC1D,EAAW,EAA6B,EAAW,SAAS,CAElE,EAAI,KAAK,CACP,WAAY,EAAI,UAChB,OAAQ,EACR,WAAY,EAAK,aAAa,CAC9B,OACA,WAAY,EAAkB,EAAK,CACnC,gBAAiB,GAAO,YAAc,KACtC,cAAe,GAAO,UAAY,KAClC,gBAAiB,GAAO,YAAc,KACtC,WAAY,EACR,CAAE,WAAY,EAAQ,OAAQ,EAAoB,EAAQ,EAAO,CAAE,CACnE,KACJ,YAAa,EACT,CAAE,WAAY,EAAS,OAAQ,EAAoB,EAAQ,EAAQ,CAAE,CACrE,KACJ,aAAc,EACV,CAAE,WAAY,EAAU,OAAQ,EAAoB,EAAQ,EAAS,CAAE,CACvE,KACJ,WACA,aAAc,EACf,CAAC,EAIN,OAAO,EAIT,SAAgB,GACd,EACA,EACA,EACoB,CACpB,IAAM,EAA0B,EAAE,CAC5B,EAAU,EAAW,EAAU,EAAI,CAEzC,EAAqB,UAAY,EACjC,IAAI,EACJ,MAAQ,EAAQ,EAAqB,KAAK,EAAO,IAAM,MACrD,EAAI,KAAK,CAAE,KAAM,EAAM,GAAI,WAAU,aAAc,EAAS,CAAC,CAG/D,OAAO,EAUT,SAAS,EAAkB,EAAc,EAA8B,CACrE,IAAI,EAAQ,EACZ,IAAK,IAAI,EAAI,EAAe,EAAG,EAAI,EAAK,OAAQ,IAAK,CACnD,IAAM,EAAK,EAAK,GAChB,GAAI,IAAO,IAAK,YACP,IAAO,MACd,IACI,IAAU,GAAG,OAAO,EAG5B,MAAO,GAYT,SAAgB,GACd,EACA,EACA,EAC6B,CAC7B,IAAM,EAAmC,EAAE,CACrC,EAAU,EAAW,EAAU,EAAI,CACnC,EAAO,IAAI,IAGjB,EAAoB,UAAY,EAChC,IAAI,EACJ,MAAQ,EAAc,EAAoB,KAAK,EAAO,IAAM,MAAM,CAChE,IAAM,EAAS,EAAY,GACrB,EAAY,EAAoB,UAAY,EAC5C,EAAa,EAAkB,EAAQ,EAAU,CACvD,GAAI,EAAa,EAAG,SACpB,IAAM,EAAW,EAAO,MAAM,EAAY,EAAG,EAAW,CAElD,EAAY,mCAAmC,KAAK,EAAS,CACnE,GAAI,CAAC,EAAW,SAChB,IAAM,EAAO,EAAU,GACjB,EAAY,GAAG,EAAO,IAAI,EAAK,IAAI,IACrC,EAAK,IAAI,EAAU,GACvB,EAAK,IAAI,EAAU,CACnB,EAAI,KAAK,CACP,KAAM,IAAW,eAAiB,SAAW,UAC7C,OACA,WACA,aAAc,EACf,CAAC,EAIJ,EAAwB,UAAY,EACpC,IAAI,EACJ,MAAQ,EAAa,EAAwB,KAAK,EAAO,IAAM,MAAM,CACnE,IAAM,EAAa,EAAW,MAExB,EAAW,EAAO,QAAQ,IAAK,EAAW,CAChD,GAAI,EAAW,EAAG,SAClB,IAAM,EAAa,EAAkB,EAAQ,EAAS,CACtD,GAAI,EAAa,EAAG,SACpB,IAAM,EAAO,EAAO,MAAM,EAAW,EAAG,EAAW,CAC7C,EAAY,EAAuB,KAAK,EAAK,CACnD,GAAI,CAAC,EAAW,SAChB,IAAM,EAAO,EAAU,GACjB,EAAY,UAAU,EAAK,IAAI,IACjC,EAAK,IAAI,EAAU,GACvB,EAAK,IAAI,EAAU,CACnB,EAAI,KAAK,CAAE,KAAM,UAAW,OAAM,WAAU,aAAc,EAAS,CAAC,EAGtE,OAAO,EAQT,SAAgB,GACd,EACA,EACA,EAC0B,CAC1B,IAAM,EAAgC,EAAE,CAClC,EAAU,EAAW,EAAU,EAAI,CAEzC,EAA0B,UAAY,EACtC,IAAI,EACJ,MAAQ,EAAQ,EAA0B,KAAK,EAAO,IAAM,MAAM,CAChE,IAAM,EAAO,EAAM,GACf,EAA6B,KAC7B,EAAyB,KAG7B,GAAI,EAAM,GAAI,CACZ,IAAM,EAAW,EAAO,QAAQ,IAAK,EAAM,MAAQ,EAAM,GAAG,OAAS,EAAE,CACvE,GAAI,GAAY,EAAG,CACjB,IAAM,EAAa,EAAkB,EAAQ,EAAS,CACtD,GAAI,GAAc,EAAG,CACnB,IAAM,EAAO,EAAO,MAAM,EAAW,EAAG,EAAW,CACnD,EAAc,EAAgB,EAAM,cAAc,CAClD,EAAU,EAAgB,EAAM,UAAU,GAKhD,EAAI,KAAK,CAAE,OAAM,cAAa,UAAS,WAAU,aAAc,EAAS,CAAC,CAG3E,OAAO,EAgBT,SAAS,EAAgB,EAAc,EAA8B,CAGnE,IAAM,EADc,OAAO,MAAM,EAAM,mBAAoB,IAC1C,CAAC,KAAK,EAAK,CAC5B,GAAI,CAAC,EAAG,OAAO,KACf,IAAM,EAAQ,EAAE,GACV,EAAQ,EAAE,MAAQ,EAAE,GAAG,OACzB,EAAI,EACJ,EAAqB,KACzB,KAAO,EAAI,EAAK,QAAQ,CACtB,IAAM,EAAK,EAAK,GAChB,GAAI,IAAO,KAAM,CAEf,GAAK,EACL,SAEF,GAAI,IAAO,EAAO,CAChB,EAAM,EAAK,MAAM,EAAO,EAAE,CAC1B,MAEF,IAOF,OALI,IAAQ,KAAa,KAKlB,EAAI,QAAQ,UAAW,EAAI,IAC5B,IAAM,IAAY;EAClB,IAAM,IAAY,IAClB,IAAM,IAAY,KACf,EACP,CASJ,MAAM,GAA8B,CAClC,sBACA,oBACA,gBACA,aACD,CAgBD,eAAsB,GAAc,EAAa,EAAgD,CAK/F,IAAM,EACJ,IAAY,aAAe,GAA8B,CAAC,EAAQ,CAEpE,IAAK,IAAM,KAAa,EAAY,CAClC,IAAM,EAAM,EAAQ,EAAK,EAAU,CAC/B,EACJ,GAAI,CACF,EAAS,MAAM,EAAS,EAAK,QAAQ,MAC/B,CACN,SAMG,sBAAmB,KAAK,EAAO,EAC/B,qBAAqB,KAAK,EAAO,CACtC,MAAO,CACL,SAAU,EACV,aAAc,EAAW,EAAK,EAAI,CACnC,CAGH,OAAO,KAIT,SAAgB,GAAe,EAA8C,CAC3E,IAAM,EAAS,IAAI,IACnB,IAAK,IAAM,KAAO,EAAS,CACzB,IAAM,EAAM,EAAO,IAAI,EAAI,UAAU,EAAI,EAAE,CAC3C,EAAI,KAAK,EAAI,CACb,EAAO,IAAI,EAAI,UAAW,EAAI,CAGhC,IAAM,EAA+B,EAAE,CACvC,IAAK,GAAM,CAAC,EAAW,KAAU,EAI3B,IADsB,IAAI,EAAM,IAAK,GAAM,EAAE,SAAS,CACzC,CAAC,KAAO,GACvB,EAAW,KAAK,CAAE,YAAW,QAAS,EAAO,CAAC,CAMlD,OADA,EAAW,MAAM,EAAG,IAAM,EAAE,UAAU,cAAc,EAAE,UAAU,CAAC,CAC1D,EAOT,eAAsB,EAAY,EAAwC,CAExE,IAAM,EAAQ,MAAM,EADP,EAAQ,EAAK,KACG,CAAE,EAAK,CAE9B,EAA6B,EAAE,CAC/B,EAA4B,EAAE,CAC9B,EAA4B,EAAE,CAC9B,EAA8B,EAAE,CAChC,EAAkD,EAAE,CACpD,EAA0C,EAAE,CAK5C,EAAU,IAAI,IACpB,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAI,EACJ,GAAI,CACF,EAAS,MAAM,EAAS,EAAM,QAAQ,MAChC,CACN,SAEF,EAAQ,IAAI,EAAM,EAAO,CACzB,EAAQ,KAAK,GAAG,GAAyB,EAAQ,EAAM,EAAK,IAAI,CAAC,CACjE,EAAO,KAAK,GAAG,GAAwB,EAAQ,EAAM,EAAK,IAAI,CAAC,CAC/D,EAAQ,KAAK,GAAG,GAAyB,EAAQ,EAAM,EAAK,IAAI,CAAC,CACjE,EAAmB,KAAK,GAAG,GAAoC,EAAQ,EAAM,EAAK,IAAI,CAAC,CACvF,EAAc,KAAK,GAAG,GAA+B,EAAQ,EAAM,EAAK,IAAI,CAAC,CAG/E,IAAK,GAAM,CAAC,EAAM,KAAW,EAAS,CACpC,IAAM,EAAgB,EAAQ,OAAQ,GAAM,EAAE,WAAa,EAAK,CAChE,EAAO,KAAK,GAAG,GAAwB,EAAQ,EAAM,EAAK,IAAK,EAAc,CAAC,CA2BhF,OAvBA,EAAQ,MAAM,EAAG,IACX,EAAE,YAAc,EAAE,UACf,EAAE,aAAa,cAAc,EAAE,aAAa,CADX,EAAE,UAAU,cAAc,EAAE,UAAU,CAE9E,CACF,EAAO,MACJ,EAAG,IAAM,EAAE,KAAK,cAAc,EAAE,KAAK,EAAI,EAAE,aAAa,cAAc,EAAE,aAAa,CACvF,CACD,EAAQ,MACL,EAAG,IAAM,EAAE,KAAK,cAAc,EAAE,KAAK,EAAI,EAAE,aAAa,cAAc,EAAE,aAAa,CACvF,CACD,EAAO,MACJ,EAAG,IAAM,EAAE,WAAW,cAAc,EAAE,WAAW,EAAI,EAAE,OAAO,cAAc,EAAE,OAAO,CACvF,CACD,EAAmB,MAChB,EAAG,IAAM,EAAE,KAAK,cAAc,EAAE,KAAK,EAAI,EAAE,aAAa,cAAc,EAAE,aAAa,CACvF,CACD,EAAc,MACX,EAAG,IAAM,EAAE,KAAK,cAAc,EAAE,KAAK,EAAI,EAAE,aAAa,cAAc,EAAE,aAAa,CACvF,CAKM,CACL,UACA,SACA,SACA,UACA,WARiB,GAAe,EAQtB,CACV,IAAA,MARgB,GAAc,EAAK,IAAK,EAAK,SAAW,aAAa,CASrE,qBACA,gBACD,CCngCH,MAAM,EAAS,6IAMT,EAAsB,IAAI,IAAI,CAAC,UAAW,aAAc,aAAc,YAAY,CAAC,CAGzF,IAAa,EAAb,cAAyC,KAAM,CAC7C,WACA,YAAY,EAA8B,CACxC,MAAM,GAAuB,EAAW,CAAC,CACzC,KAAK,KAAO,sBACZ,KAAK,WAAa,IAKtB,SAAS,GAAuB,EAAsC,CACpE,IAAM,EAAkB,CAAC,yCAAyC,CAClE,IAAK,IAAM,KAAK,EAAY,CAC1B,EAAM,KAAK,GAAG,CACd,EAAM,KAAK,KAAK,EAAE,QAAQ,OAAO,kBAAkB,EAAE,UAAU,IAAI,CACnE,IAAK,IAAM,KAAO,EAAE,QAClB,EAAM,KAAK,SAAS,EAAI,eAAe,CAW3C,OARA,EAAM,KAAK,GAAG,CACd,EAAM,KAAK,eAAe,CAC1B,EAAM,KAAK,kCAAkC,CAC7C,EAAM,KACJ,oGACD,CACD,EAAM,KAAK,6EAA6E,CACxF,EAAM,KAAK,oEAAoE,CACxE,EAAM,KAAK;EAAK,CAIzB,SAAS,GAAmB,EAAoB,EAA0B,CAExE,IAAI,EAAM,EADM,EAAQ,EACE,CAAE,EAAW,CAAC,MAAM,EAAI,CAAC,KAAK,IAAI,CAG5D,MAFA,GAAM,EAAI,QAAQ,uBAAwB,GAAG,CACxC,EAAI,WAAW,IAAI,GAAE,EAAM,KAAO,GAChC,EAST,SAAS,GAAiB,EAA8B,CAItD,IAAM,EAHM,EAAI,aAAa,QAAQ,SAAU,GAAG,CAAC,QAAQ,uBAAwB,GAGlE,CAAC,MAAM,IAAI,CAC5B,EAAM,KAAK,CACX,IAAM,EAAK,EAAM,KAAK,IAAI,CAC1B,OAAO,EAAK,GAAG,EAAG,GAAG,EAAI,YAAc,EAAI,UAY7C,SAAS,GACP,EACA,EACA,EACQ,CACR,IAAM,EAAO,IAAI,IACX,EAAoB,EAAE,CAE5B,IAAK,IAAM,KAAK,EAAS,CACvB,GAAI,CAAC,EAAoB,IAAI,EAAE,UAAU,CAAE,SAE3C,IAAM,EAAM,EAAe,IAAI,EAAE,UAAU,CAAG,GAAiB,EAAE,CAAG,EAAE,UACtE,GAAI,EAAK,IAAI,EAAI,CAAE,SACnB,EAAK,IAAI,EAAI,CAEb,IAAM,EAAO,GAAmB,EAAE,SAAU,EAAQ,CAC9C,EAAM,EAAE,UAAY,WAAW,EAAK,YAAc,WAAW,EAAK,KAAK,EAAE,YAC/E,EAAQ,KAAK,QAAQ,EAAI,KAAK,IAAM,CAOtC,MAAO,GAAG,EAAO;;;EAJJ,EAAQ,OACjB,EAAQ,KAAK;EAAK,CAClB,+EAKC;;;;;EASP,SAAS,EAAY,EAAkB,EAAiB,EAA8B,CAQpF,OAPI,EAAM,SAAW,EACZ,GAAG,EAAO;KAChB,EAAa;cACJ,EAAS;EAId,GAAG,EAAO;cACL,EAAS;EAFN,CAAC,GAAG,IAAI,IAAI,EAAM,CAAC,CAAC,UAG7B,CAAC,IAAK,GAAM,QAAQ,EAAE,GAAG,CAAC,KAAK;EAAK,CAAC;EAK7C,SAAS,GAAY,EAAqB,EAAgC,CAWxE,MAAO,GAAG,EAAO;;;;;;;;;;;;;;EADI,EAAgB;EAA8B,KADjD,EAAa;EAA2B,KA+B5D,SAAS,GAAc,EAA4C,CAIjE,IAAM,EAAS,IAAI,IACnB,IAAK,IAAM,KAAQ,EACZ,EAAO,IAAI,EAAK,KAAK,EAAE,EAAO,IAAI,EAAK,KAAM,EAAK,CAUzD,MAAO,GAAG,EAAO;;;;;;;;;EAPF,CAAC,GAAG,EAAO,QAAQ,CAAC,CAAC,UAAU,EAAG,IAAM,EAAE,KAAK,cAAc,EAAE,KAAK,CAC7D,CAAC,IAAK,GAAS,QAAQ,EAAK,KAAK,MAAM,EAAK,KAAK,GAAG,CAAC,KAAK;EAEnE,EAET,+FAWC;;;;;EAgBP,SAAS,GAAoB,EAAyC,CACpE,GAAI,EAAM,SAAW,EACnB,MAAO,GAAG,EAAO;;;;;;;;;;;;;EAkBnB,IAAM,EAAS,IAAI,IACnB,IAAK,IAAM,KAAQ,EACZ,EAAO,IAAI,EAAK,KAAK,EAAE,EAAO,IAAI,EAAK,KAAM,EAAK,CAGzD,IAAM,EAAmB,EAAE,CAC3B,IAAK,IAAM,IAAQ,CAAC,GAAG,EAAO,QAAQ,CAAC,CAAC,UAAU,EAAG,IAAM,EAAE,KAAK,cAAc,EAAE,KAAK,CAAC,CAAE,CACxF,IAAM,EAAqB,EAAE,CAC7B,GAAI,EAAK,YAGP,IAAK,IAAM,KAAQ,EAAK,YAAY,MAAM;EAAK,CAAE,EAAS,KAAK,MAAM,IAAO,CAE9E,GAAI,EAAK,QAAS,CAMhB,EAAS,KAAK,cAAe,WAAc,CAC3C,IAAK,IAAM,KAAQ,EAAK,QAAQ,MAAM;EAAK,CAAE,EAAS,KAAK,MAAM,IAAO,CACxE,EAAS,KAAK,SAAY,CAE5B,EAAS,KAAK,WAAW,EAAK,eAAe,CAC7C,EAAO,KACL,CAAC,MAAO,GAAG,EAAU,MAAO,oBAAoB,EAAK,KAAK,iBAAiB,CAAC,KAAK;EAAK,CACvF,CAGH,MAAO,GAAG,EAAO;;;;;EAKjB,EAAO,KAAK;;EAAO,CAAC;EAqEtB,eAAsB,GAAc,EAAgD,CAClF,GAAM,CACJ,UACA,SAAS,EAAE,CACX,SAAS,EAAE,CACX,UAAU,EAAE,CACZ,aAAa,EAAE,CACf,MAAM,KACN,qBAAqB,EAAE,CACvB,gBAAgB,EAAE,CAClB,SAAS,CAAE,QAAS,EAAE,CAAE,MAAO,EAAG,CAClC,SACA,kBAAkB,GAClB,gBAAiB,EAAmB,IAClC,EAEJ,GAAI,EAAW,OAAS,GAAK,CAAC,EAC5B,MAAM,IAAI,EAAoB,EAAW,CAG3C,MAAM,EAAM,EAAQ,CAAE,UAAW,GAAM,CAAC,CAExC,IAAM,EAAe,EAAK,EAAQ,gBAAgB,CAC5C,EAAe,EAAK,EAAQ,gBAAgB,CAC5C,EAAc,EAAK,EAAQ,eAAe,CAO1C,EAAc,EAAK,EAAQ,eAAe,CAC1C,EAAoB,EAAK,EAAQ,qBAAqB,CACtD,EAAY,EAAK,EAAQ,aAAa,CAEtC,EAAiB,IAAI,IAAI,EAAW,IAAK,GAAM,EAAE,UAAU,CAAC,CAC5D,EAAkB,GAAe,EAAS,EAAc,EAAe,CAIvE,EAAc,EACjB,OAAQ,GAAM,EAAoB,IAAI,EAAE,UAAU,CAAC,CACnD,IAAK,GAAO,EAAe,IAAI,EAAE,UAAU,CAAG,GAAiB,EAAE,CAAG,EAAE,UAAW,CAC9E,EAAgB,EAAO,IAAK,GAAM,EAAE,KAAK,CACzC,EAAiB,EAAQ,IAAK,GAAM,EAAE,KAAK,CAC3C,EAAc,CAAC,GAAG,EAAa,GAAG,EAAe,GAAG,EAAe,CAEnE,EAAU,EAAQ,OAAQ,GAAM,EAAE,YAAc,SAAS,CAAC,IAAK,GAAM,EAAE,UAAU,CAEjF,EAAkB,EACtB,eACA,EACA,oFACD,CACK,EAAiB,EACrB,cACA,EACA,sEACD,CACK,EAAiB,GAAc,EAAmB,CAClD,EAAuB,GAAoB,EAAc,CACzD,EAAe,GAAY,IAAQ,KAAM,EAAO,MAAQ,EAAE,CAEhE,MAAM,EAAU,EAAc,EAAiB,QAAQ,CACvD,MAAM,EAAU,EAAc,EAAiB,QAAQ,CACvD,MAAM,EAAU,EAAa,EAAgB,QAAQ,CACrD,MAAM,EAAU,EAAa,EAAgB,QAAQ,CACrD,MAAM,EAAU,EAAmB,EAAsB,QAAQ,CACjE,MAAM,EAAU,EAAW,EAAc,QAAQ,CAEjD,IAAM,EAAU,CACd,EACA,EACA,EACA,EACA,EACA,EACD,CAID,MAAM,EAAU,EADG,EAAQ,EACI,CAAE,aAAa,CAAE;;EAAyC,QAAQ,CAKjG,IAAM,EAAoB,IAAI,IAAI,EAAmB,IAAK,GAAM,EAAE,KAAK,CAAC,CAAC,KACnE,EAAsB,IAAI,IAAI,EAAc,IAAK,GAAM,EAAE,KAAK,CAAC,CAAC,KAEtE,MAAO,CACL,gBAAiB,EAAY,OAC7B,cAAe,IAAI,IAAI,EAAY,CAAC,KACpC,aAAc,EAAQ,OACtB,aAAc,EAAO,OACrB,cAAe,EACf,oBAAqB,EACrB,aAAc,EAAO,MACrB,WAAY,IAAQ,KACpB,UACA,mBAAoB,EAAW,OAChC,CCxbH,MAAM,GACJ,0EAYF,SAAgB,GACd,EAC0B,CAC1B,IAAM,EAAqC,EAAE,CAC7C,IAAK,IAAM,KAAS,EAAQ,CAC1B,IAAM,EAAO,EAAM,KACf,EAAK,WAAW,UAAc,EAC9B,GAAuB,KAAK,EAAK,EACrC,EAAS,KAAK,CACZ,MAAO,EACP,SAAU,EAAM,SAChB,SAAU,EAAM,aAChB,OAAQ,+DACR,WAAY,GAAc,EAAK,CAChC,CAAC,CAEJ,OAAO,EAGT,SAAS,GAAc,EAAkC,CACvD,GAAI,aAAa,KAAK,EAAK,CACzB,MAAO,YAAY,EAAK,kBAAkB,EAAK,IAEjD,GAAI,EAAK,SAAS,IAAI,CACpB,MAAO,sDAET,IAAM,EAAa,8BAA8B,KAAK,EAAK,CAC3D,GAAI,EAAY,CACd,GAAM,EAAG,EAAO,GAAO,EACvB,MAAO,IAAI,EAAM,GAAG,EAAI,OAAO,EAAE,CAAC,aAAa,GAAG,EAAI,MAAM,EAAE,CAAC,ICxCnE,SAAgB,EACd,EACA,EACkB,CAClB,GAAI,CAAC,EAAU,MAAO,CAAE,QAAS,EAAE,CAAE,MAAO,EAAG,CAE/C,IAAM,EAAO,IAAI,IACjB,IAAK,GAAM,CAAC,EAAW,KAAU,OAAO,QAAQ,EAAS,CAAE,CACzD,GAAI,CAAC,GAAS,OAAO,EAAM,KAAQ,SAAU,SAC7C,IAAM,EAAS,EAAQ,EAAK,EAAM,IAAI,CACtC,GAAI,CAAC,GAAM,EAAO,CAAE,SAKpB,IAAM,EAAU,EAAS,EAAM,MAAQ,OAAQ,CAC7C,IAAK,EACL,MAAO,GACP,IAAK,GACL,MAAO,GACR,CAAC,CACF,EAAQ,MAAM,CAId,GAAM,CAAE,SAAU,EAAe,EAAW,EAAS,CACnD,SAAU,EAAM,MAAQ,OACzB,CAAC,CACF,IAAK,GAAM,CAAE,IAAK,KAAa,EAAO,CAIpC,IAAM,EAAS,EAAQ,MAAM,EAAU,OAAS,EAAE,CAClD,EAAK,IAAI,EAAS,CAAE,YAAW,IAAK,EAAQ,CAAC,EAGjD,MAAO,CAAE,QAAS,CAAC,GAAG,EAAK,QAAQ,CAAC,CAAE,MAAO,EAAK,KAAM,CAG1D,SAAgB,GAAiB,EAAsC,CACrE,IAAM,EAAS,6IAKf,GAAI,EAAW,QAAQ,SAAW,EAChC,MAAO,GAAG,EAAO;;;;;;;;;;;EAcnB,IAAM,EAAiB,EAAE,CACzB,IAAK,IAAM,KAAS,EAAW,QAAS,CACtC,IAAM,EAAO,GAAG,EAAM,UAAU,GAAG,EAAM,MAAM,MAAM,IAAI,CACrD,EAAO,EACX,IAAK,IAAI,EAAI,EAAG,EAAI,EAAK,OAAS,EAAG,IAAK,CACxC,IAAM,EAAO,EAAK,GACZ,EAAW,EAAK,GACtB,GAAI,IAAa,EAAM,CAOrB,IAAM,EAAqB,EAAE,CAC7B,EAAK,GAAQ,EACb,EAAO,OAEF,IAAU,EAAK,GAAQ,EAAE,EAC9B,EAAO,EAAK,GAGhB,IAAM,EAAO,EAAK,EAAK,OAAS,GAC5B,OAAO,EAAK,IAAU,WAK1B,EAAK,GAAQ,GAIf,MAAO,GAAG,EAAO;;;;;;;;;EADJ,EAAW,EAAM,OAU1B,CAAC;;;;;EAQP,MAAM,EAAO,OAAO,aAAa,CAGjC,SAAS,EAAW,EAAgB,EAAwB,CAC1D,IAAM,EAAO,OAAO,KAAK,EAAK,CAAC,UAAU,CACnC,EAAkB,EAAE,CAC1B,IAAK,IAAM,KAAO,EAAM,CACtB,IAAM,EAAQ,EAAK,GACb,EAAU,GAAa,EAAI,CAAG,EAAM,KAAK,UAAU,EAAI,CACzD,IAAU,EACZ,EAAM,KAAK,GAAG,IAAS,EAAQ,gBAAgB,EAE/C,EAAM,KAAK,GAAG,IAAS,EAAQ,KAAK,CACpC,EAAM,KAAK,EAAW,EAAO,GAAG,EAAO,IAAI,CAAC,CAC5C,EAAM,KAAK,GAAG,EAAO,GAAG,EAG5B,OAAO,EAAM,KAAK;EAAK,CAGzB,SAAS,GAAa,EAAsB,CAC1C,MAAO,6BAA6B,KAAK,EAAI,CAG/C,SAAS,GAAM,EAAuB,CACpC,GAAI,CACF,OAAO,EAAS,EAAK,CAAC,aAAa,MAC7B,CACN,MAAO,6EC1FX,SAAS,EAAe,EAQtB,CACA,IAAM,EAAM,EAAK,KAAO,QAAQ,KAAK,CACrC,MAAO,CACL,MACA,OAAQ,EAAQ,EAAK,EAAK,QAAU,MAAM,CAC1C,OAAQ,EAAQ,EAAK,EAAK,QAAU,gBAAgB,CACpD,OAAQ,EAAK,QAAU,GACvB,gBAAiB,EAAK,iBAAmB,GACzC,gBAAiB,EAAK,iBAAmB,GACzC,QAAS,EAAK,SAAW,aAC1B,CAYH,eAAsB,EAAW,EAA0B,EAAE,CAK1D,CACD,GAAM,CAAE,MAAK,SAAQ,SAAQ,SAAQ,kBAAiB,kBAAiB,WACrE,EAAe,EAAK,CAEhB,EAAQ,KAAK,KAAK,CAClB,EAAO,MAAM,EAAY,CAC7B,KAAM,EACN,MAEA,QAAS,IAAY,GAAQ,IAAA,GAAY,EAC1C,CAAC,CACI,EAAS,EAAe,EAAK,SAAU,EAAI,CAC3C,EAAS,MAAM,GAAc,CACjC,QAAS,EAAK,QACd,OAAQ,EAAK,OACb,OAAQ,EAAK,OACb,QAAS,EAAK,QACd,WAAY,EAAK,WACjB,IAAK,IAAY,GAAQ,KAAO,EAAK,IACrC,mBAAoB,EAAK,mBACzB,cAAe,EAAK,cACpB,SACA,SACA,kBACA,kBACD,CAAC,CASE,EAAuC,EAAE,CAC7C,GAAI,EAAK,aAAe,GACtB,GAAI,CACF,GAAM,CAAE,wBAAyB,MAAM,OAAO,8BACxC,CAAE,kBAAmB,MAAM,OAAO,yBAAA,KAAA,GAAA,EAAA,EAAA,CAExC,EAAgB,MAAM,EAAqB,CAAE,MAAK,OAAQ,MAD/B,EAAe,EAAI,CAC0B,OAAQ,GAAM,CAAC,OAChF,EAAK,CAMZ,GAAI,CAAC,EAAQ,CACX,IAAM,EAAM,aAAe,MAAQ,EAAI,QAAU,OAAO,EAAI,CAC5D,QAAQ,KAAK,2CAA2C,EAAI,gBAAgB,EAe9E,EAAK,aAAe,IACtB,MAAM,EAAkB,EAAQ,EAAO,QAAS,EAAe,EAAO,CAGxE,IAAM,EAAgB,GAAyB,EAAK,OAAO,CACrD,EAAU,KAAK,KAAK,CAAG,EAE7B,GAAI,CAAC,EAAQ,CACX,IAAM,EAAQ,EAAO,QAAQ,EAAM,IAAK,GAAG,CACrC,EACJ,EAAO,mBAAqB,EAAI,KAAK,EAAO,mBAAmB,wBAA0B,GACrF,EAAU,EAAO,WAAa,cAAgB,GAC9C,EAAa,EAAO,cAAgB,EAAI,KAAK,EAAO,cAAc,mBAAqB,GACvF,EACJ,EAAO,oBAAsB,EAAI,KAAK,EAAO,oBAAoB,gBAAkB,GAC/E,EAAY,EAAO,aAAe,EAAI,KAAK,EAAO,aAAa,SAAW,GAIhF,GAHA,QAAQ,IACN,oBAAoB,EAAO,cAAc,aAAa,EAAO,aAAa,WAAW,EAAO,aAAa,UAAU,IAAa,IAAU,IAAY,IAAU,EAAc,KAAK,EAAM,IAAI,EAAQ,KACtM,CACG,EAAc,OAAS,EAAG,CAC5B,QAAQ,KACN,mBAAmB,EAAc,OAAO,6CACzC,CACD,IAAK,IAAM,KAAW,EAAe,CACnC,IAAM,EAAe,EAAQ,SAAW,KAAK,EAAQ,SAAS,GAAK,GACnE,QAAQ,KACN,QAAQ,EAAQ,MAAM,KAAK,EAAQ,SAAS,GAAG,EAAa,KAAK,EAAQ,SAC1E,CACG,EAAQ,YACV,QAAQ,KAAK,uBAAuB,EAAQ,aAAa,GAMjE,MAAO,CAAE,OAAM,SAAQ,gBAAe,CAkBxC,eAAsB,GAAa,EAA0B,EAAE,CAAuB,CACpF,IAAM,EAAW,EAAe,EAAK,CAC/B,CAAE,SAAQ,SAAQ,OAAQ,EAM1B,EAA6B,CAAE,GAAG,EAAU,gBAAiB,GAAM,WAAY,GAAO,CAMtF,EACJ,QAAQ,IAAI,uBAAyB,KAAO,QAAQ,IAAI,uBAAyB,OAK7E,CAAC,CAAE,wBAAwB,CAAE,mBAAoB,MAAM,QAAQ,IAAI,CACvE,OAAO,8BACP,OAAO,yBAAA,KAAA,GAAA,EAAA,EAAA,CACR,CAAC,CACI,EAAe,MAAM,EAAe,EAAI,CAK1C,EAA0C,EAAE,CAC1C,EAAY,SAAY,CAC5B,GAAI,CAEF,GAAuB,MADL,EAAW,CAAE,GAAG,EAAS,CAAC,EACjB,OAAO,cAC3B,EAAK,CACZ,GAAI,EAAQ,OACZ,GAAI,aAAe,EACjB,QAAQ,MAAM;EAAO,EAAI,QAAU;EAAK,KACnC,CACL,IAAM,EAAM,aAAe,MAAQ,EAAI,QAAU,OAAO,EAAI,CAC5D,QAAQ,MAAM,0BAA0B,IAAM,IAI9C,EAAa,SAAY,CAC7B,GAAI,CACF,IAAM,EAAgB,MAAM,EAAqB,CAC/C,MACA,OAAQ,EACR,OAAQ,GACT,CAAC,CACF,MAAM,EAAkB,EAAS,OAAQ,EAAsB,EAAe,GAAK,MAC7E,IAMV,MAAM,GAAW,CACjB,MAAM,GAAY,CAElB,GAAM,CAAE,SAAU,MAAM,OAAO,WAE3B,EAA8C,KAC5C,EAAW,GAA4B,CAEtC,GACA,sBAAsB,KAAK,EAAS,GACrC,EAAS,SAAS,UAAU,EAC5B,EAAS,SAAS,QAAQ,GAE1B,GAAO,aAAa,EAAM,CAC9B,EAAQ,eAAiB,CAClB,GAAW,CAAC,KAAK,EAAW,EAChC,IAAI,IAMT,GAAI,EAAc,CACX,GACH,QAAQ,IAAI,sDAAsD,CAEpE,IAAM,EAAW,gBAAkB,CACjC,EAAQ,CAAE,GAAG,EAAS,OAAQ,GAAM,CAAE,GAAK,EAC1C,IAAK,CACR,UAAa,cAAc,EAAS,CAGtC,IAAI,EACJ,GAAI,CACF,EAAU,EAAM,EAAQ,CAAE,UAAW,GAAM,EAAG,EAAQ,IAAa,CACjE,EAAQ,EAAS,EACjB,OACK,EAAU,CACZ,GACH,QAAQ,KACN,2CAA2C,GAAK,SAAW,EAAI,6BAChE,CAGH,IAAM,EAAW,gBAAkB,CACjC,EAAQ,CAAE,GAAG,EAAS,OAAQ,GAAM,CAAE,GAAK,EAC1C,IAAK,CACR,UAAa,cAAc,EAAS,CAGtC,UAAa,CACP,GAAO,aAAa,EAAM,CAC9B,EAAQ,OAAO,EAKnB,eAAe,EAAQ,EAAyB,EAAgC,CAC9E,GAAI,CACF,MAAM,EAAW,EAAK,OACf,EAAK,CACZ,GAAI,EAAQ,OACZ,GAAI,aAAe,EACjB,QAAQ,MAAM;EAAO,EAAI,QAAU;EAAK,KACnC,CACL,IAAM,EAAM,aAAe,MAAQ,EAAI,QAAU,OAAO,EAAI,CAC5D,QAAQ,MAAM,0BAA0B,IAAM,GAmBpD,eAAsB,EACpB,EACA,EACA,EACA,EACmB,CACnB,IAAM,EAAW,IAAI,IACrB,IAAK,IAAM,KAAQ,EAAkB,EAAS,IAAI,EAAS,EAAK,CAAC,CACjE,IAAK,IAAM,KAAK,EACV,EAAE,SAAS,EAAS,IAAI,EAAS,EAAE,QAAQ,CAAC,CAKlD,IAAI,EACJ,GAAI,CACF,EAAU,MAAM,EAAQ,EAAO,MACzB,CACN,MAAO,EAAE,CAEX,IAAM,EAAoB,EAAE,CAC5B,IAAK,IAAM,KAAQ,EAAS,CAC1B,GAAI,EAAS,IAAI,EAAK,CAAE,SACxB,IAAM,EAAM,EAAQ,EAAQ,EAAK,CACjC,GAAI,CAEF,GAAI,EAAC,MADW,EAAK,EAAI,EAClB,QAAQ,CAAE,SACjB,MAAM,EAAO,EAAI,CACjB,EAAQ,KAAK,EAAK,MACZ,GAOV,OAHI,EAAQ,OAAS,GAAK,CAAC,GACzB,QAAQ,IAAI,yBAAyB,EAAQ,OAAO,kBAAkB,EAAQ,KAAK,KAAK,GAAG,CAEtF"}
1
+ {"version":3,"file":"typegen-BEWcYO4F.mjs","names":[],"sources":["../src/typegen/scanner.ts","../src/typegen/generator.ts","../src/typegen/token-conventions.ts","../src/typegen/asset-types.ts","../src/typegen/index.ts"],"sourcesContent":["/**\n * Static scanner for KickJS decorated classes and DI tokens.\n *\n * Walks `src/**\\/*.ts` (excluding tests and node_modules) and extracts:\n *\n * - Decorated classes (`@Service`, `@Controller`, `@Repository`, etc.)\n * - `createToken<T>('name')` definitions\n * - `@Inject('literal')` calls\n *\n * The output feeds the type generator, which emits `.kickjs/types/*.d.ts`\n * files used by the user's tsc to make `container.resolve()` and module\n * discovery type-safe.\n *\n * This is intentionally regex-based (not AST-based) to avoid the\n * ts-morph / typescript compiler dependency. Pattern from\n * `packages/vite/src/module-discovery.ts` which already uses regex\n * to detect `*.module.ts` exports.\n *\n * ## Collision detection\n *\n * Two classes with the same name across different files is a collision.\n * The scanner records all collisions in `ScanResult.collisions` so the\n * caller (generator) can decide whether to hard-error or auto-namespace.\n *\n * @module @forinda/kickjs-cli/typegen/scanner\n */\n\nimport type { Dirent } from 'node:fs'\nimport { readdir, readFile } from 'node:fs/promises'\nimport { join, relative, resolve, sep } from 'node:path'\n\n/** Decorators that mark a class as DI-managed */\nexport const DECORATOR_NAMES = [\n 'Service',\n 'Controller',\n 'Repository',\n 'Injectable',\n 'Component',\n 'Module',\n] as const\n\nexport type DecoratorName = (typeof DECORATOR_NAMES)[number]\n\n/** A single discovered decorated class */\nexport interface DiscoveredClass {\n /** Class name (e.g., 'UserService') */\n className: string\n /** Decorator that marked it (e.g., 'Service') */\n decorator: DecoratorName\n /** Absolute file path */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n /** True if exported as `default` */\n isDefault: boolean\n}\n\n/** A single route handler discovered on a controller class */\nexport interface DiscoveredRoute {\n /** Owning controller class name (e.g. 'UserController') */\n controller: string\n /** Handler method name on the controller (e.g. 'getUser') */\n method: string\n /** HTTP verb (uppercase) */\n httpMethod: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'\n /** Route path including parameter placeholders (e.g. '/:id/posts/:postId') */\n path: string\n /** URL path parameter names extracted from `:placeholder` segments */\n pathParams: string[]\n /**\n * Whitelisted query field names extracted from `@ApiQueryParams({...})`.\n * `null` means no `@ApiQueryParams` was found on this method (so the\n * generator emits an unconstrained `query` shape). An empty array means\n * the decorator existed but no fields could be statically extracted\n * (e.g. an opaque imported config).\n */\n queryFilterable: string[] | null\n querySortable: string[] | null\n querySearchable: string[] | null\n /**\n * Schema identifiers referenced from the route decorator's second arg\n * (e.g. `@Post('/', { body: createTaskSchema })`). `null` means no\n * such reference; the value carries the identifier and the resolved\n * import source (relative module path) if known.\n */\n bodySchema: SchemaRef | null\n querySchema: SchemaRef | null\n paramsSchema: SchemaRef | null\n /** Absolute file path of the controller */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/** A statically-resolved schema identifier reference */\nexport interface SchemaRef {\n /** The identifier as written (e.g. `createTaskSchema`) */\n identifier: string\n /**\n * Resolved module specifier (relative path or bare module name) where\n * the identifier is defined. `null` means the source could not be\n * statically determined (the generator falls back to `unknown`).\n */\n source: string | null\n}\n\n/** A `createToken<T>('name')` call discovered in source */\nexport interface DiscoveredToken {\n /** The literal string passed to `createToken()` */\n name: string\n /** The const variable name on the LHS, if any */\n variable: string | null\n /** Absolute file path */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/** An `@Inject('literal')` call discovered in source */\nexport interface DiscoveredInject {\n /** The literal string passed to `@Inject()` */\n name: string\n /** Absolute file path */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/** A name collision — same class name in two or more files */\nexport interface ClassCollision {\n /** The colliding class name */\n className: string\n /** All files declaring the class */\n classes: DiscoveredClass[]\n}\n\n/**\n * Information about a discovered env schema file. The typegen\n * generator uses this to emit a `KickEnv` + `NodeJS.ProcessEnv`\n * augmentation that flows through to `@Value` and `process.env`.\n *\n * `null` means no env file was found at the configured location.\n */\nexport interface DiscoveredEnv {\n /** Absolute path to the env schema file */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/**\n * A plugin or adapter discovered in source — either via `defineAdapter({ name })`\n * / `definePlugin({ name })` calls, or via a class that `implements AppAdapter`\n * and declares a string-literal `name` field.\n *\n * The `name` here is the literal string passed to the framework (the value\n * `dependsOn` references), NOT the symbol on the LHS. `defineAdapter` lets\n * authors choose any name they want; the symbol is irrelevant at runtime.\n */\nexport interface DiscoveredPluginOrAdapter {\n /** Whether this is a plugin (`definePlugin`) or adapter (`defineAdapter` / class) */\n kind: 'plugin' | 'adapter'\n /** The string literal passed as `name` (the value `dependsOn` references) */\n name: string\n /** Absolute file path */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/**\n * A `defineAugmentation('Name', meta)` call discovered in source. Plugins\n * call this to advertise an augmentable interface so the typegen can list\n * every augmentation surface in one generated file.\n */\nexport interface DiscoveredAugmentation {\n /** The literal string passed as the first arg to `defineAugmentation` */\n name: string\n /** Optional `description` extracted from the second-arg object literal */\n description: string | null\n /** Optional `example` extracted from the second-arg object literal */\n example: string | null\n /** Absolute file path */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/** Aggregated scanner output */\nexport interface ScanResult {\n classes: DiscoveredClass[]\n routes: DiscoveredRoute[]\n tokens: DiscoveredToken[]\n injects: DiscoveredInject[]\n collisions: ClassCollision[]\n /** Discovered env schema file (or null if none found at the configured path) */\n env: DiscoveredEnv | null\n /** Plugins/adapters discovered via `defineAdapter`/`definePlugin`/`implements AppAdapter` */\n pluginsAndAdapters: DiscoveredPluginOrAdapter[]\n /** Augmentation interfaces declared via `defineAugmentation('Name', meta)` */\n augmentations: DiscoveredAugmentation[]\n}\n\n/** Options for the scanner */\nexport interface ScanOptions {\n /** Root directory to scan (e.g., absolute path to `src`) */\n root: string\n /** Project root used to compute relative paths (e.g., process.cwd()) */\n cwd: string\n /** Glob-like extensions to scan */\n extensions?: string[]\n /** Substrings that exclude a path (matched against relative path) */\n exclude?: string[]\n /**\n * Path to the env schema file, relative to `cwd`. Defaults to\n * `'src/env.ts'`. The file must contain a `defineEnv(...)` call\n * with a default export for the typegen to emit a typed `KickEnv`\n * augmentation. If the file does not exist or doesn't match the\n * expected shape, env typing is skipped silently.\n */\n envFile?: string\n}\n\nconst DEFAULT_EXTENSIONS = ['.ts', '.tsx', '.mts', '.cts']\nconst DEFAULT_EXCLUDES = ['node_modules', '.kickjs', 'dist', 'build', '.test.', '.spec.', '.d.ts']\n\n/**\n * Match a class-level decorator immediately followed by an exported\n * class declaration. Captures decorator name and class name.\n */\nconst DECORATED_CLASS_REGEX = new RegExp(\n String.raw`@(${DECORATOR_NAMES.join('|')})\\s*\\([^)]*\\)` +\n String.raw`(?:\\s*@[A-Z]\\w*(?:\\s*\\([^)]*\\))?)*` +\n String.raw`\\s*export\\s+(default\\s+)?(?:abstract\\s+)?class\\s+(\\w+)`,\n 'g',\n)\n\n/**\n * Match an exported class declaration that implements `AppModule`.\n * KickJS modules are not decorated — they implement the `AppModule`\n * interface — so the decorated-class scanner never picks them up. This\n * regex captures them by name so `ModuleToken` can be populated.\n *\n * Tolerates an `extends BaseClass` clause before `implements`, multiple\n * implements clauses (`implements Foo, AppModule`), and `default` exports.\n */\nconst APP_MODULE_CLASS_REGEX = new RegExp(\n String.raw`export\\s+(default\\s+)?(?:abstract\\s+)?class\\s+(\\w+)` +\n String.raw`(?:\\s+extends\\s+\\w+(?:<[^>]*>)?)?` +\n String.raw`\\s+implements\\s+[^{]*\\bAppModule\\b`,\n 'g',\n)\n\n/**\n * Match a `createToken<T>('name')` call with optional `export const X =`\n * or `const X =` prefix. Tolerates whitespace and the type parameter\n * being absent (`createToken('name')`).\n */\nconst CREATE_TOKEN_REGEX =\n /(?:export\\s+)?const\\s+(\\w+)\\s*(?::\\s*[^=]+)?=\\s*createToken\\s*(?:<[^>]*>)?\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]\\s*\\)/g\n\n/**\n * Match a bare `createToken<T>('name')` call (no const assignment) so\n * we still pick up dynamically-used tokens.\n */\nconst BARE_CREATE_TOKEN_REGEX = /createToken\\s*(?:<[^>]*>)?\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]\\s*\\)/g\n\n/** Match `@Inject('literal')` — only literals; computed args are skipped */\nconst INJECT_LITERAL_REGEX = /@Inject\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]\\s*\\)/g\n\n/**\n * Match the start of a `defineAdapter(...)` or `definePlugin(...)` call,\n * tolerating optional `<TConfig, TExtra>` generics. Captures the helper\n * name. The callsite's first-arg object is parsed forward via\n * `findBalancedClose` so nested objects/parens don't confuse us.\n */\nconst DEFINE_HELPER_START = /\\b(defineAdapter|definePlugin)\\s*(?:<[^>]*>)?\\s*\\(/g\n\n/**\n * Match a class declaration whose `implements` clause includes `AppAdapter`.\n * Captures the class name. Used to pick up the (rare, post-defineAdapter)\n * legacy class-style adapters so their literal `name = '...'` field can\n * still feed `KickJsPluginRegistry`.\n */\nconst APP_ADAPTER_CLASS_REGEX = new RegExp(\n String.raw`export\\s+(?:default\\s+)?(?:abstract\\s+)?class\\s+(\\w+)` +\n String.raw`(?:\\s+extends\\s+\\w+(?:<[^>]*>)?)?` +\n String.raw`\\s+implements\\s+[^{]*\\bAppAdapter\\b`,\n 'g',\n)\n\n/** Match a string-literal `name = '...'` field on a class body. */\nconst CLASS_NAME_FIELD_REGEX = /\\bname\\s*(?::\\s*[^=]+)?=\\s*['\"`]([^'\"`]+)['\"`]/\n\n/**\n * Match the start of a `defineAugmentation('Name', ...)` call. Captures\n * the literal name. The optional second-arg object is parsed forward so\n * `description` / `example` can be pulled out.\n */\nconst DEFINE_AUGMENTATION_START = /\\bdefineAugmentation\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]\\s*(,\\s*\\{)?/g\n\n/** HTTP route decorator names recognised by the scanner */\nconst HTTP_DECORATORS = ['Get', 'Post', 'Put', 'Delete', 'Patch'] as const\n\n/**\n * Locate the start of a route decorator: `@Get(`, `@Post(`, etc.\n * Used by `extractRoutesFromSource`; the rest of the route declaration\n * (balanced parens, stacked decorators, method name) is parsed by walking\n * the source forward from this match. The previous all-in-one regex\n * couldn't handle nested parens in stacked decorator args (e.g.\n * `@ApiResponse(201, { schema: z.object({ id: z.string() }) })`) — see\n * forinda/kick-js#108.\n */\nconst ROUTE_DECORATOR_START = new RegExp(String.raw`@(${HTTP_DECORATORS.join('|')})\\s*\\(`, 'g')\n\n/**\n * Find the index of the `)` that balances the `(` at `openPos`.\n * Returns -1 if no matching `)` exists. Counts balanced parens only;\n * does not understand string literals, so a `(` or `)` inside a string\n * inside the args will skew the depth counter (matches the limitation\n * of `extractRouteOptionsArg`).\n */\nfunction findBalancedClose(text: string, openPos: number): number {\n let depth = 1\n for (let i = openPos + 1; i < text.length; i++) {\n const ch = text[i]\n if (ch === '(') depth++\n else if (ch === ')') {\n depth--\n if (depth === 0) return i\n }\n }\n return -1\n}\n\n/**\n * Walk forward from the end of a route decorator past any stacked\n * decorators (`@ApiOperation(...)`, `@ApiResponse(...)`, `@Middleware(fn)`,\n * etc.), then past optional `public`/`private`/`protected` and `async`,\n * and capture the method name + opening `(`.\n *\n * Returns the method name and the position immediately after the method's\n * opening `(`, or `null` if the source between the route decorator and\n * the method body doesn't fit the expected shape.\n */\nfunction readMethodAfterDecorators(\n block: string,\n startPos: number,\n): { methodName: string; endPos: number } | null {\n let pos = startPos\n // Stacked decorators: @PascalCase optionally followed by balanced (...)\n while (pos < block.length) {\n while (pos < block.length && /\\s/.test(block[pos])) pos++\n if (block[pos] !== '@') break\n const decMatch = block.slice(pos).match(/^@([A-Z]\\w*)/)\n if (!decMatch) break\n pos += decMatch[0].length\n while (pos < block.length && /\\s/.test(block[pos])) pos++\n if (block[pos] === '(') {\n const close = findBalancedClose(block, pos)\n if (close < 0) return null\n pos = close + 1\n }\n }\n // Modifiers + async\n while (pos < block.length && /\\s/.test(block[pos])) pos++\n for (const mod of ['public', 'private', 'protected'] as const) {\n if (block.slice(pos, pos + mod.length) === mod && /\\s/.test(block.charAt(pos + mod.length))) {\n pos += mod.length\n while (pos < block.length && /\\s/.test(block[pos])) pos++\n break\n }\n }\n if (block.slice(pos, pos + 5) === 'async' && /\\s/.test(block.charAt(pos + 5))) {\n pos += 5\n while (pos < block.length && /\\s/.test(block[pos])) pos++\n }\n // Method name + `(`\n const methodMatch = block.slice(pos).match(/^([a-zA-Z_]\\w*)\\s*\\(/)\n if (!methodMatch) return null\n return { methodName: methodMatch[1], endPos: pos + methodMatch[0].length }\n}\n\n/** Extract `:placeholder` segments from an Express route path */\nfunction extractPathParams(path: string): string[] {\n const matches = path.match(/:([a-zA-Z_]\\w*)/g) ?? []\n return matches.map((m) => m.slice(1))\n}\n\n/**\n * Extract a bare identifier value from a single field in an object literal\n * embedded in a string. Returns `null` if the field is missing or its value\n * isn't a bare identifier (e.g. an inline object, function call, etc.).\n *\n * Example: `extractObjectFieldIdentifier(\"'/' , { body: createTaskSchema }\", 'body')`\n * returns `'createTaskSchema'`.\n */\nfunction extractObjectFieldIdentifier(text: string, field: string): string | null {\n // Look for `field: <identifier>` not followed by `(` (function call) or `{` (inline object)\n const re = new RegExp(String.raw`\\b${field}\\s*:\\s*([A-Za-z_$][\\w$]*)`, 'g')\n const m = re.exec(text)\n if (!m) return null\n return m[1]\n}\n\n/**\n * Resolve a bare identifier to its module source by inspecting the file's\n * top-level imports and same-file `const` declarations.\n *\n * - `import { X } from './path'` → returns `'./path'`\n * - `import X from './path'` (default import) → returns `'./path'`\n * - `import * as X from './path'` → returns `'./path'`\n * - `const X = z.object(...)` (same file) → returns `null` (caller emits a self-import)\n *\n * Returns `null` when the identifier cannot be resolved.\n */\nfunction resolveImportSource(source: string, identifier: string): string | null {\n // Named import: `import { X, Y as Z } from './path'`\n const namedRe = new RegExp(\n String.raw`import\\s*(?:type\\s+)?\\{[^}]*\\b${identifier}\\b[^}]*\\}\\s*from\\s*['\"\\`]([^'\"\\`]+)['\"\\`]`,\n )\n const named = namedRe.exec(source)\n if (named) return named[1]\n\n // Default import: `import X from './path'`\n const defaultRe = new RegExp(\n String.raw`import\\s+(?:type\\s+)?${identifier}\\s+from\\s*['\"\\`]([^'\"\\`]+)['\"\\`]`,\n )\n const def = defaultRe.exec(source)\n if (def) return def[1]\n\n // Namespace import: `import * as X from './path'`\n const nsRe = new RegExp(\n String.raw`import\\s*\\*\\s*as\\s+${identifier}\\s+from\\s*['\"\\`]([^'\"\\`]+)['\"\\`]`,\n )\n const ns = nsRe.exec(source)\n if (ns) return ns[1]\n\n // Same-file const declaration — return empty string as a sentinel meaning\n // \"current file\". The generator turns this into a self-relative reference.\n const constRe = new RegExp(String.raw`(?:^|\\n)\\s*(?:export\\s+)?const\\s+${identifier}\\b`)\n if (constRe.test(source)) return ''\n\n return null\n}\n\n/**\n * Extract whitelist arrays from an `@ApiQueryParams(...)` decorator\n * within `decoratorBlock`. Handles two forms:\n *\n * - Inline literal: `@ApiQueryParams({ filterable: ['a', 'b'], ... })`\n * - Const reference: `@ApiQueryParams(SOME_CONFIG)` — looks up\n * `const SOME_CONFIG = { ... }` in the same file (`fullSource`).\n *\n * Returns `null` if no `@ApiQueryParams` is present. Returns\n * `{ filterable: [], sortable: [], searchable: [] }` if the decorator\n * is present but no fields could be statically extracted (opaque\n * imports, column-object configs, function calls, etc.).\n */\nfunction extractApiQueryParams(\n decoratorBlock: string,\n fullSource: string,\n): { filterable: string[]; sortable: string[]; searchable: string[] } | null {\n const apiMatch = /@ApiQueryParams\\s*\\(\\s*([\\s\\S]*?)\\s*\\)\\s*$/.exec(decoratorBlock)\n if (!apiMatch) {\n // Try without anchoring to the end (decorator may not be the last in the block)\n const loose = /@ApiQueryParams\\s*\\(([\\s\\S]*?)\\)/.exec(decoratorBlock)\n if (!loose) return null\n return parseApiQueryParamsArg(loose[1].trim(), fullSource)\n }\n return parseApiQueryParamsArg(apiMatch[1].trim(), fullSource)\n}\n\nfunction parseApiQueryParamsArg(\n arg: string,\n fullSource: string,\n): { filterable: string[]; sortable: string[]; searchable: string[] } {\n // Inline literal — starts with `{`\n if (arg.startsWith('{')) {\n return parseInlineConfigLiteral(arg)\n }\n // Const reference — bare identifier (possibly with type assertion)\n const idMatch = /^([A-Za-z_]\\w*)/.exec(arg)\n if (idMatch) {\n const ident = idMatch[1]\n // Look for `const IDENT = { ... }` in the same source file\n const constRe = new RegExp(\n String.raw`const\\s+${ident}\\s*(?::\\s*[^=]+)?=\\s*(\\{[\\s\\S]*?\\n\\})`,\n 'm',\n )\n const constMatch = constRe.exec(fullSource)\n if (constMatch) {\n return parseInlineConfigLiteral(constMatch[1])\n }\n }\n // Fallback: decorator present but extraction failed\n return { filterable: [], sortable: [], searchable: [] }\n}\n\n/** Extract a string array literal for one config key from an inline object literal */\nfunction extractStringArray(literal: string, key: string): string[] {\n const re = new RegExp(String.raw`${key}\\s*:\\s*\\[([\\s\\S]*?)\\]`)\n const m = re.exec(literal)\n if (!m) return []\n return Array.from(m[1].matchAll(/['\"`]([^'\"`]+)['\"`]/g)).map((x) => x[1])\n}\n\n/** Parse an inline `{ filterable: [...], sortable: [...], searchable: [...] }` literal */\nfunction parseInlineConfigLiteral(literal: string): {\n filterable: string[]\n sortable: string[]\n searchable: string[]\n} {\n return {\n filterable: extractStringArray(literal, 'filterable'),\n sortable: extractStringArray(literal, 'sortable'),\n searchable: extractStringArray(literal, 'searchable'),\n }\n}\n\n/** Recursively walk a directory and yield matching file paths */\nasync function walk(dir: string, opts: ScanOptions): Promise<string[]> {\n const exts = opts.extensions ?? DEFAULT_EXTENSIONS\n const excludes = opts.exclude ?? DEFAULT_EXCLUDES\n const out: string[] = []\n\n let entries: Dirent[]\n try {\n entries = (await readdir(dir, { withFileTypes: true, encoding: 'utf-8' })) as Dirent[]\n } catch {\n return out\n }\n\n for (const entry of entries) {\n const full = join(dir, entry.name)\n const rel = relative(opts.cwd, full)\n\n if (excludes.some((ex) => rel.includes(ex))) continue\n\n if (entry.isDirectory()) {\n out.push(...(await walk(full, opts)))\n } else if (entry.isFile()) {\n if (exts.some((ext) => entry.name.endsWith(ext))) {\n out.push(full)\n }\n }\n }\n\n return out\n}\n\n/** Compute the forward-slash relative path used in scanner output */\nfunction toRelative(filePath: string, cwd: string): string {\n return relative(cwd, filePath).split(sep).join('/')\n}\n\n/** Extract decorated classes from a single source file */\nexport function extractClassesFromSource(\n source: string,\n filePath: string,\n cwd: string,\n): DiscoveredClass[] {\n const out: DiscoveredClass[] = []\n const relPath = toRelative(filePath, cwd)\n\n DECORATED_CLASS_REGEX.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = DECORATED_CLASS_REGEX.exec(source)) !== null) {\n const [, decorator, defaultMarker, className] = match\n out.push({\n className,\n decorator: decorator as DecoratorName,\n filePath,\n relativePath: relPath,\n isDefault: Boolean(defaultMarker),\n })\n }\n\n // KickJS modules are undecorated classes that `implements AppModule`.\n // Tag them with the synthetic `Module` decorator so downstream code that\n // already filters by `c.decorator === 'Module'` keeps working.\n APP_MODULE_CLASS_REGEX.lastIndex = 0\n let modMatch: RegExpExecArray | null\n while ((modMatch = APP_MODULE_CLASS_REGEX.exec(source)) !== null) {\n const [, defaultMarker, className] = modMatch\n if (out.some((c) => c.className === className && c.filePath === filePath)) continue\n out.push({\n className,\n decorator: 'Module',\n filePath,\n relativePath: relPath,\n isDefault: Boolean(defaultMarker),\n })\n }\n\n return out\n}\n\n/** Extract `createToken('name')` definitions from a single source file */\nexport function extractTokensFromSource(\n source: string,\n filePath: string,\n cwd: string,\n): DiscoveredToken[] {\n const out: DiscoveredToken[] = []\n const relPath = toRelative(filePath, cwd)\n const seen = new Set<string>()\n\n // First pass: const-bound tokens (preferred — we get the variable name)\n CREATE_TOKEN_REGEX.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = CREATE_TOKEN_REGEX.exec(source)) !== null) {\n const [full, variable, name] = match\n seen.add(full)\n out.push({ name, variable, filePath, relativePath: relPath })\n }\n\n // Second pass: bare calls not captured above (rare but possible)\n BARE_CREATE_TOKEN_REGEX.lastIndex = 0\n while ((match = BARE_CREATE_TOKEN_REGEX.exec(source)) !== null) {\n if (seen.has(match[0])) continue\n out.push({\n name: match[1],\n variable: null,\n filePath,\n relativePath: relPath,\n })\n }\n\n return out\n}\n\n/**\n * Extract route handlers from a source file.\n *\n * For each decorated class in `classesInFile`, slices the source from\n * the class declaration to the next class (or EOF) and runs the route\n * decorator regex within that slice. The result is a list of routes\n * tagged with their owning controller.\n *\n * Heuristic note: this assumes classes are not nested. KickJS controllers\n * are top-level by convention so this holds in practice.\n */\nexport function extractRoutesFromSource(\n source: string,\n filePath: string,\n cwd: string,\n classesInFile: DiscoveredClass[],\n): DiscoveredRoute[] {\n const out: DiscoveredRoute[] = []\n if (classesInFile.length === 0) return out\n const relPath = toRelative(filePath, cwd)\n\n // Locate each class declaration's offset in the source\n const positions: Array<{ cls: DiscoveredClass; start: number }> = []\n for (const cls of classesInFile) {\n const re = new RegExp(String.raw`class\\s+${cls.className}\\b`)\n const m = re.exec(source)\n if (m?.index !== undefined) {\n positions.push({ cls, start: m.index })\n }\n }\n positions.sort((a, b) => a.start - b.start)\n\n for (let i = 0; i < positions.length; i++) {\n const { cls, start } = positions[i]\n const end = i + 1 < positions.length ? positions[i + 1].start : source.length\n const block = source.slice(start, end)\n\n // Two-pass walk: locate each route decorator start, then balance-parse\n // forward through args and any stacked decorators to find the method\n // name. Replaces the previous single regex which mis-parsed nested\n // parens (forinda/kick-js#108).\n ROUTE_DECORATOR_START.lastIndex = 0\n let startMatch: RegExpExecArray | null\n while ((startMatch = ROUTE_DECORATOR_START.exec(block)) !== null) {\n const verb = startMatch[1]\n const decoratorStart = startMatch.index\n const openParen = ROUTE_DECORATOR_START.lastIndex - 1\n const closeParen = findBalancedClose(block, openParen)\n if (closeParen < 0) continue\n\n const routeArgs = block.slice(openParen + 1, closeParen)\n\n const pathLiteralMatch = routeArgs.match(/^\\s*['\"`]([^'\"`]*)['\"`]/)\n const path = pathLiteralMatch && pathLiteralMatch[1].length > 0 ? pathLiteralMatch[1] : '/'\n\n const methodInfo = readMethodAfterDecorators(block, closeParen + 1)\n if (!methodInfo) continue\n const { methodName, endPos } = methodInfo\n\n // Advance the regex iterator past this method so the next iteration\n // starts looking after the consumed region.\n ROUTE_DECORATOR_START.lastIndex = endPos\n\n const matchedText = block.slice(decoratorStart, endPos)\n const apiQp = extractApiQueryParams(matchedText, source)\n\n const bodyId = extractObjectFieldIdentifier(routeArgs, 'body')\n const queryId = extractObjectFieldIdentifier(routeArgs, 'query')\n const paramsId = extractObjectFieldIdentifier(routeArgs, 'params')\n\n out.push({\n controller: cls.className,\n method: methodName,\n httpMethod: verb.toUpperCase() as DiscoveredRoute['httpMethod'],\n path,\n pathParams: extractPathParams(path),\n queryFilterable: apiQp?.filterable ?? null,\n querySortable: apiQp?.sortable ?? null,\n querySearchable: apiQp?.searchable ?? null,\n bodySchema: bodyId\n ? { identifier: bodyId, source: resolveImportSource(source, bodyId) }\n : null,\n querySchema: queryId\n ? { identifier: queryId, source: resolveImportSource(source, queryId) }\n : null,\n paramsSchema: paramsId\n ? { identifier: paramsId, source: resolveImportSource(source, paramsId) }\n : null,\n filePath,\n relativePath: relPath,\n })\n }\n }\n\n return out\n}\n\n/** Extract `@Inject('literal')` calls from a single source file */\nexport function extractInjectsFromSource(\n source: string,\n filePath: string,\n cwd: string,\n): DiscoveredInject[] {\n const out: DiscoveredInject[] = []\n const relPath = toRelative(filePath, cwd)\n\n INJECT_LITERAL_REGEX.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = INJECT_LITERAL_REGEX.exec(source)) !== null) {\n out.push({ name: match[1], filePath, relativePath: relPath })\n }\n\n return out\n}\n\n/**\n * Extract the bounds of an object literal that begins at `openBracePos`\n * (the index of the `{` character). Returns the index of the matching `}`\n * or -1 if no match is found. Counts balanced braces only — does not\n * understand string literals so a `{` or `}` inside a string inside the\n * object will skew the depth counter (matches `findBalancedClose`).\n */\nfunction findBalancedBrace(text: string, openBracePos: number): number {\n let depth = 1\n for (let i = openBracePos + 1; i < text.length; i++) {\n const ch = text[i]\n if (ch === '{') depth++\n else if (ch === '}') {\n depth--\n if (depth === 0) return i\n }\n }\n return -1\n}\n\n/**\n * Extract plugins/adapters declared via `defineAdapter({ name: '...' })`\n * or `definePlugin({ name: '...' })` calls and via class-style adapters\n * (`class XxxAdapter implements AppAdapter` with a string-literal `name`\n * field).\n *\n * Only the literal `name:` field feeds the result — the symbol on the LHS\n * is irrelevant since `dependsOn` references the runtime name.\n */\nexport function extractPluginsAndAdaptersFromSource(\n source: string,\n filePath: string,\n cwd: string,\n): DiscoveredPluginOrAdapter[] {\n const out: DiscoveredPluginOrAdapter[] = []\n const relPath = toRelative(filePath, cwd)\n const seen = new Set<string>()\n\n // Pass 1: defineAdapter / definePlugin calls\n DEFINE_HELPER_START.lastIndex = 0\n let helperMatch: RegExpExecArray | null\n while ((helperMatch = DEFINE_HELPER_START.exec(source)) !== null) {\n const helper = helperMatch[1] as 'defineAdapter' | 'definePlugin'\n const openParen = DEFINE_HELPER_START.lastIndex - 1\n const closeParen = findBalancedClose(source, openParen)\n if (closeParen < 0) continue\n const callArgs = source.slice(openParen + 1, closeParen)\n // Look for the first `name: 'literal'` in the call args\n const nameMatch = /\\bname\\s*:\\s*['\"`]([^'\"`]+)['\"`]/.exec(callArgs)\n if (!nameMatch) continue\n const name = nameMatch[1]\n const dedupeKey = `${helper}::${name}::${filePath}`\n if (seen.has(dedupeKey)) continue\n seen.add(dedupeKey)\n out.push({\n kind: helper === 'definePlugin' ? 'plugin' : 'adapter',\n name,\n filePath,\n relativePath: relPath,\n })\n }\n\n // Pass 2: class-style adapters (`class X implements AppAdapter { name = 'X' }`)\n APP_ADAPTER_CLASS_REGEX.lastIndex = 0\n let classMatch: RegExpExecArray | null\n while ((classMatch = APP_ADAPTER_CLASS_REGEX.exec(source)) !== null) {\n const classStart = classMatch.index\n // Find the class body opening brace\n const bracePos = source.indexOf('{', classStart)\n if (bracePos < 0) continue\n const closeBrace = findBalancedBrace(source, bracePos)\n if (closeBrace < 0) continue\n const body = source.slice(bracePos + 1, closeBrace)\n const nameMatch = CLASS_NAME_FIELD_REGEX.exec(body)\n if (!nameMatch) continue\n const name = nameMatch[1]\n const dedupeKey = `class::${name}::${filePath}`\n if (seen.has(dedupeKey)) continue\n seen.add(dedupeKey)\n out.push({ kind: 'adapter', name, filePath, relativePath: relPath })\n }\n\n return out\n}\n\n/**\n * Extract `defineAugmentation('Name', { description, example })` calls\n * from a source file. The metadata object is optional — when absent both\n * `description` and `example` resolve to `null`.\n */\nexport function extractAugmentationsFromSource(\n source: string,\n filePath: string,\n cwd: string,\n): DiscoveredAugmentation[] {\n const out: DiscoveredAugmentation[] = []\n const relPath = toRelative(filePath, cwd)\n\n DEFINE_AUGMENTATION_START.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = DEFINE_AUGMENTATION_START.exec(source)) !== null) {\n const name = match[1]\n let description: string | null = null\n let example: string | null = null\n\n // If the regex matched a metadata object opening (`, {`), parse it\n if (match[2]) {\n const bracePos = source.indexOf('{', match.index + match[0].length - 1)\n if (bracePos >= 0) {\n const closeBrace = findBalancedBrace(source, bracePos)\n if (closeBrace >= 0) {\n const body = source.slice(bracePos + 1, closeBrace)\n description = readStringField(body, 'description')\n example = readStringField(body, 'example')\n }\n }\n }\n\n out.push({ name, description, example, filePath, relativePath: relPath })\n }\n\n return out\n}\n\n/**\n * Pull a string-valued field out of a JS object-literal body, respecting\n * the opening quote so the value isn't truncated at the first foreign\n * quote character. Handles backslash escapes inside the literal.\n *\n * Why a custom parser instead of one regex per delimiter: real-world\n * `defineAugmentation` calls embed all three quote characters at once\n * — backtick template literals carrying TS shapes like\n * `'free' | 'pro'` (single quotes) AND `\\`ctx.get(...)\\`` (escaped\n * backticks). A character-class regex like `[^'\"`]+` truncates on the\n * first foreign quote it sees. This walker scans char-by-char from\n * the matched delimiter and only stops on the matching one.\n */\nfunction readStringField(body: string, field: string): string | null {\n // Locate `field:` followed by an opening quote. Tolerate any whitespace.\n const fieldRe = new RegExp(`\\\\b${field}\\\\s*:\\\\s*(['\"\\`])`, 'g')\n const m = fieldRe.exec(body)\n if (!m) return null\n const quote = m[1]\n const start = m.index + m[0].length\n let i = start\n let raw: string | null = null\n while (i < body.length) {\n const ch = body[i]\n if (ch === '\\\\') {\n // Skip the escaped char — supports \\`, \\', \\\", \\n, \\\\ etc.\n i += 2\n continue\n }\n if (ch === quote) {\n raw = body.slice(start, i)\n break\n }\n i++\n }\n if (raw === null) return null\n // Unescape JS string-literal escapes so the JSDoc renderer sees the\n // value the source author actually intended (`\\`` → `` ` ``, `\\'` →\n // `'`, etc). Without this, escaped backticks in a backtick template\n // literal would surface as literal backslashes in the catalogue.\n return raw.replace(/\\\\(.)/g, (_m, c) => {\n if (c === 'n') return '\\n'\n if (c === 't') return '\\t'\n if (c === 'r') return '\\r'\n return c\n })\n}\n\n/**\n * Default search order for the env schema file. Newer projects keep\n * the schema under `src/config/` so the framework's \"config\" concept\n * has a single home; older scaffolds dropped it at `src/env.ts` (kept\n * here for back-compat). The first match wins.\n */\nconst DEFAULT_ENV_FILE_CANDIDATES = [\n 'src/config/index.ts',\n 'src/config/env.ts',\n 'src/config.ts',\n 'src/env.ts',\n] as const\n\n/**\n * Look for an env schema file. When `envFile` is the string default\n * (`'src/env.ts'`) or omitted, every entry in `DEFAULT_ENV_FILE_CANDIDATES`\n * is tried in order. When the caller passes an explicit path, only that\n * path is tried (so projects can opt out of the search by setting\n * `kick.config.ts → typegen.envFile`).\n *\n * Returns a `DiscoveredEnv` if the file exists and contains both a\n * `defineEnv(...)` call and a default export — the two markers we\n * need before it's safe to emit `import type schema from '...'` in\n * the generator. Returns `null` for any other state (no candidate\n * found, no defineEnv, no default export) so the generator skips env\n * typing silently.\n */\nexport async function detectEnvFile(cwd: string, envFile: string): Promise<DiscoveredEnv | null> {\n // The CLI passes the literal default `'src/env.ts'` when the user\n // hasn't overridden it. Treat that as \"use the search list\" rather\n // than pinning to one path, so newer scaffolds at src/config/ keep\n // working without forcing every project to set typegen.envFile.\n const candidates: readonly string[] =\n envFile === 'src/env.ts' ? DEFAULT_ENV_FILE_CANDIDATES : [envFile]\n\n for (const candidate of candidates) {\n const abs = resolve(cwd, candidate)\n let source: string\n try {\n source = await readFile(abs, 'utf-8')\n } catch {\n continue\n }\n // Cheap heuristic: defineEnv(...) call AND a default export.\n // We don't try to evaluate the file — the generator emits an\n // `import type schema from '...'` and lets the user's tsc do the\n // actual schema-to-type inference.\n if (!/\\bdefineEnv\\s*\\(/.test(source)) continue\n if (!/export\\s+default\\b/.test(source)) continue\n return {\n filePath: abs,\n relativePath: toRelative(abs, cwd),\n }\n }\n\n return null\n}\n\n/** Detect duplicate class names across files */\nexport function findCollisions(classes: DiscoveredClass[]): ClassCollision[] {\n const groups = new Map<string, DiscoveredClass[]>()\n for (const cls of classes) {\n const arr = groups.get(cls.className) ?? []\n arr.push(cls)\n groups.set(cls.className, arr)\n }\n\n const collisions: ClassCollision[] = []\n for (const [className, group] of groups) {\n // Two declarations of the same class name in different files = collision.\n // Multiple decorators on the same file/class are NOT a collision.\n const distinctFiles = new Set(group.map((c) => c.filePath))\n if (distinctFiles.size > 1) {\n collisions.push({ className, classes: group })\n }\n }\n\n // Deterministic order\n collisions.sort((a, b) => a.className.localeCompare(b.className))\n return collisions\n}\n\n/**\n * Scan a project for decorated classes, createToken definitions, and\n * `@Inject` literal usages.\n */\nexport async function scanProject(opts: ScanOptions): Promise<ScanResult> {\n const root = resolve(opts.root)\n const files = await walk(root, opts)\n\n const classes: DiscoveredClass[] = []\n const routes: DiscoveredRoute[] = []\n const tokens: DiscoveredToken[] = []\n const injects: DiscoveredInject[] = []\n const pluginsAndAdapters: DiscoveredPluginOrAdapter[] = []\n const augmentations: DiscoveredAugmentation[] = []\n\n // Two passes: first collect all classes, then a second pass extracts\n // routes per file using the per-file class list as scoping context.\n // This keeps class discovery and route discovery independent.\n const sources = new Map<string, string>()\n for (const file of files) {\n let source: string\n try {\n source = await readFile(file, 'utf-8')\n } catch {\n continue\n }\n sources.set(file, source)\n classes.push(...extractClassesFromSource(source, file, opts.cwd))\n tokens.push(...extractTokensFromSource(source, file, opts.cwd))\n injects.push(...extractInjectsFromSource(source, file, opts.cwd))\n pluginsAndAdapters.push(...extractPluginsAndAdaptersFromSource(source, file, opts.cwd))\n augmentations.push(...extractAugmentationsFromSource(source, file, opts.cwd))\n }\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 pluginsAndAdapters.sort(\n (a, b) => a.name.localeCompare(b.name) || a.relativePath.localeCompare(b.relativePath),\n )\n augmentations.sort(\n (a, b) => a.name.localeCompare(b.name) || a.relativePath.localeCompare(b.relativePath),\n )\n\n const collisions = findCollisions(classes)\n const env = await detectEnvFile(opts.cwd, opts.envFile ?? 'src/env.ts')\n\n return {\n classes,\n routes,\n tokens,\n injects,\n collisions,\n env,\n pluginsAndAdapters,\n augmentations,\n }\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, sep } from 'node:path'\nimport type {\n ClassCollision,\n DiscoveredAugmentation,\n DiscoveredClass,\n DiscoveredEnv,\n DiscoveredInject,\n DiscoveredPluginOrAdapter,\n DiscoveredRoute,\n DiscoveredToken,\n} from './scanner'\nimport type { DiscoveredAssets } from './asset-types'\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)].toSorted()\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, includeAssets: boolean): string {\n // The kick/routes + kick/env + kick/assets TypegenPlugins write their\n // output to `kick__routes.ts` / `kick__env.ts` / `kick__assets.d.ts`\n // (see typegen runner + builtin plugins). The index re-exports them\n // via side-effect imports so `tsconfig include` of `.kickjs/types/`\n // is enough to wire up the augmentations — same surface adopters had\n // before the carve. Assets/env are conditional: the plugins skip\n // emission when there's nothing to declare, so the import would\n // dangle.\n const envImport = includeEnv ? \"import './kick__env'\\n\" : ''\n const assetsImport = includeAssets ? \"import './kick__assets'\\n\" : ''\n return `${HEADER}\nexport type { ServiceToken } from './services'\nexport type { ModuleToken } from './modules'\n\n// The registry, routes, plugins, assets, and env augmentations are\n// loaded as side-effects — importing this file (or having it on\n// tsconfig include) is enough for \\`container.resolve()\\`,\n// \\`Ctx<KickRoutes.UserController['getUser']>\\`,\n// \\`dependsOn: ['TenantAdapter']\\`, \\`assets.mails.welcome()\\`, and\n// \\`@Value('PORT')\\` to resolve.\nimport './registry'\nimport './kick__routes'\nimport './plugins'\nimport './augmentations'\n${assetsImport}${envImport}`\n}\n\n/**\n * Render the `KickJsPluginRegistry` augmentation. Each entry maps the\n * literal `name` field of a plugin/adapter to a marker type (the\n * registry value isn't load-bearing at runtime — `dependsOn` only cares\n * about `keyof`, so any non-`never` type works). We emit `'plugin'` /\n * `'adapter'` strings so DevTools can later read the registry to tell\n * the kinds apart without a second source of truth.\n *\n * When the project has no discoverable plugins/adapters, the augmentation\n * is intentionally empty rather than skipped so the `keyof` constraint\n * resolves to `never` (which is harmless — `dependsOn: []` still works).\n */\nfunction renderPlugins(items: DiscoveredPluginOrAdapter[]): string {\n // Dedupe by name — two declarations with the same name are a runtime\n // boot-time error in `mount-sort.ts`; we surface the conflict via a\n // single registry entry rather than a duplicate-key TS error.\n const byName = new Map<string, DiscoveredPluginOrAdapter>()\n for (const item of items) {\n if (!byName.has(item.name)) byName.set(item.name, item)\n }\n\n const sorted = [...byName.values()].toSorted((a, b) => a.name.localeCompare(b.name))\n const entries = sorted.map((item) => ` '${item.name}': '${item.kind}'`).join('\\n')\n\n const body = entries\n ? entries\n : ' // (no plugins/adapters discovered yet — `defineAdapter`/`definePlugin` calls feed this)'\n\n return `${HEADER}\ndeclare module '@forinda/kickjs' {\n /**\n * Map of every plugin/adapter \\`name\\` discovered in the project. The\n * value type is the kind tag (\\`'plugin'\\` or \\`'adapter'\\`); the\n * \\`keyof\\` of this interface narrows \\`dependsOn\\` so misspelled deps\n * become compile errors instead of boot-time \\`MissingMountDepError\\`.\n */\n interface KickJsPluginRegistry {\n${body}\n }\n}\n\nexport {}\n`\n}\n\n/**\n * Render the augmentation manifest — one block per `defineAugmentation`\n * call discovered in the project. The output is a `.d.ts` file that\n * does nothing at runtime but acts as in-IDE documentation: adopters\n * jumping into it see every interface their plugins offer for\n * augmentation, alongside any `description` / `example` the plugin\n * authors provided.\n */\nfunction renderAugmentations(items: DiscoveredAugmentation[]): string {\n if (items.length === 0) {\n return `${HEADER}\n// No augmentations discovered.\n//\n// Plugins advertise augmentable interfaces via:\n//\n// import { defineAugmentation } from '@forinda/kickjs'\n// defineAugmentation('FeatureFlags', {\n// description: 'Feature flag shape consumed by FlagsPlugin',\n// example: '{ beta: boolean; rolloutPercentage: number }',\n// })\n//\n// See \\`docs/guide/typegen.md#augmentations\\` for the full pattern.\nexport {}\n`\n }\n\n // Dedupe by name — multiple plugins shouldn't claim the same name,\n // but if they do we keep the first.\n const byName = new Map<string, DiscoveredAugmentation>()\n for (const item of items) {\n if (!byName.has(item.name)) byName.set(item.name, item)\n }\n\n const blocks: string[] = []\n for (const item of [...byName.values()].toSorted((a, b) => a.name.localeCompare(b.name))) {\n const docLines: string[] = []\n if (item.description) {\n // Description may itself be multi-line — preserve line breaks\n // so JSDoc renderers wrap intent correctly.\n for (const line of item.description.split('\\n')) docLines.push(` * ${line}`)\n }\n if (item.example) {\n // Split the example so each line is prefixed with ` * ` — without\n // this, raw newlines inside the example string broke the JSDoc\n // comment and IDEs rendered the whole block as one mangled line.\n // This is what made `defineAugmentation({ example: '<huge object>' })`\n // unusable for anything beyond a one-liner.\n docLines.push(` * @example`, ` * \\`\\`\\`ts`)\n for (const line of item.example.split('\\n')) docLines.push(` * ${line}`)\n docLines.push(` * \\`\\`\\``)\n }\n docLines.push(` * @see ${item.relativePath}`)\n blocks.push(\n ['/**', ...docLines, ' */', `export interface ${item.name}Augmentation {}`].join('\\n'),\n )\n }\n\n return `${HEADER}\n// Catalogue of augmentable interfaces in this project. The interfaces\n// below are documentation only — augment the source-of-truth interfaces\n// in your own \\`d.ts\\` files (the framework declares the actual types).\n\n${blocks.join('\\n\\n')}\n`\n}\n\n/** 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 /** Number of plugin/adapter names registered into KickJsPluginRegistry */\n pluginEntries: number\n /** Number of `defineAugmentation` calls catalogued */\n augmentationEntries: number\n /** Number of typed asset entries (every file in every assetMap dir) */\n assetEntries: 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 /** Plugins/adapters discovered via `defineAdapter`/`definePlugin`/`implements AppAdapter` */\n pluginsAndAdapters?: DiscoveredPluginOrAdapter[]\n /** `defineAugmentation` calls discovered in the project */\n augmentations?: DiscoveredAugmentation[]\n /** Discovered typed assets from the assetMap walker (PR 4 of asset-manager). */\n assets?: DiscoveredAssets\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 pluginsAndAdapters = [],\n augmentations = [],\n assets = { entries: [], count: 0 } as DiscoveredAssets,\n outDir,\n allowDuplicates = false,\n schemaValidator: _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 + env.ts + assets.d.ts are owned by the `kick/routes`,\n // `kick/env`, and `kick/assets` typegen plugins (M2.B-T8 carve).\n // They write `kick__routes.ts` / `kick__env.ts` / `kick__assets.d.ts`\n // via the plugin runner. Legacy emissions of all three files are\n // gone — `assets.d.ts` was the last holdover (#TBD) and stopped\n // emitting once the plugin pipeline proved stable.\n const pluginsFile = join(outDir, 'plugins.d.ts')\n const augmentationsFile = join(outDir, 'augmentations.d.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 pluginsContent = renderPlugins(pluginsAndAdapters)\n const augmentationsContent = renderAugmentations(augmentations)\n const indexContent = renderIndex(env !== null, assets.count > 0)\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(pluginsFile, pluginsContent, 'utf-8')\n await writeFile(augmentationsFile, augmentationsContent, 'utf-8')\n await writeFile(indexFile, indexContent, 'utf-8')\n\n const written = [\n registryFile,\n servicesFile,\n modulesFile,\n pluginsFile,\n augmentationsFile,\n indexFile,\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 // Dedupe the plugin/adapter count — two declarations with the same\n // name collapse to one registry entry (mount-sort still throws at\n // boot, but the typegen layer reports the unique surface).\n const uniquePluginNames = new Set(pluginsAndAdapters.map((p) => p.name)).size\n const uniqueAugmentations = new Set(augmentations.map((a) => a.name)).size\n\n return {\n registryEntries: classTokens.length,\n serviceTokens: new Set(allServices).size,\n moduleTokens: modules.length,\n routeEntries: routes.length,\n pluginEntries: uniquePluginNames,\n augmentationEntries: uniqueAugmentations,\n assetEntries: assets.count,\n envWritten: env !== null,\n written,\n resolvedCollisions: collisions.length,\n }\n}\n","/**\n * Token convention validator (architecture.md §22.4 #1).\n *\n * Warns (never errors) on `createToken('literal')` calls whose literal\n * doesn't match the convention from §22.2:\n *\n * <scope>/<PascalKey>[/<suffix>][:<instance>[:<extra>]*]\n *\n * Where `<scope>` is lowercase + may start with the reserved `kick/`\n * prefix for first-party tokens. Legacy framework tokens that started\n * with `kickjs.` (pre-§22) are exempt — they get migrated alongside\n * the first-party adapter migrations.\n *\n * The matching reserved-prefix check (third-party tokens squatting\n * `kick/`) is the responsibility of `@forinda/kickjs-lint`'s\n * `token-reserved-prefix` rule, not the typegen layer — different\n * audience (adopter codebase) and different default severity.\n *\n * @module @forinda/kickjs-cli/typegen/token-conventions\n */\n\nimport type { DiscoveredToken } from './scanner'\n\n/**\n * Regex for the §22.2 token shape. Breakdown:\n *\n * - `^(kick\\/)?` — optional reserved framework prefix.\n * - `([a-z][\\w-]*\\/[A-Z]\\w*)` — `<scope>/<PascalKey>`. Scope is\n * lowercase, key is PascalCase.\n * - `(\\/.+)?` — optional `/suffix` for sub-flavours\n * (e.g. `mycorp/Cache/redis`).\n * - `(:[a-z][\\w-]+(:[a-z][\\w-]+)*)?` — optional `:instance` (and\n * further `:extra` colon-sections) for `.scoped()` shards.\n */\nconst TOKEN_CONVENTION_REGEX =\n /^(kick\\/)?([a-z][\\w-]*\\/[A-Z]\\w*)(\\/.+)?(:[a-z][\\w-]+(:[a-z][\\w-]+)*)?$/\n\nconst LEGACY_PREFIX = 'kickjs.'\n\nexport interface TokenConventionWarning {\n token: string\n variable: string | null\n filePath: string\n reason: string\n suggestion?: string\n}\n\nexport function validateTokenConventions(\n tokens: readonly DiscoveredToken[],\n): TokenConventionWarning[] {\n const warnings: TokenConventionWarning[] = []\n for (const token of tokens) {\n const name = token.name\n if (name.startsWith(LEGACY_PREFIX)) continue\n if (TOKEN_CONVENTION_REGEX.test(name)) continue\n warnings.push({\n token: name,\n variable: token.variable,\n filePath: token.relativePath,\n reason: 'does not match `<scope>/<PascalKey>[/<suffix>][:<instance>]`',\n suggestion: suggestRename(name),\n })\n }\n return warnings\n}\n\nfunction suggestRename(name: string): string | undefined {\n if (/^[A-Z]\\w*$/.test(name)) {\n return `'<scope>/${name}' (e.g. 'mycorp/${name}')`\n }\n if (name.includes('.')) {\n return `consider '<scope>/PascalKey' instead of dotted form`\n }\n const slashLower = /^([a-z][\\w-]*)\\/([a-z]\\w*)$/.exec(name)\n if (slashLower) {\n const [, scope, key] = slashLower\n return `'${scope}/${key.charAt(0).toUpperCase()}${key.slice(1)}'`\n }\n return undefined\n}\n","/**\n * Walks every `assetMap` entry's source directory + emits a typed\n * `KickAssets` ambient augmentation (assets-plan.md PR 4). Generates\n * `.kickjs/types/assets.d.ts` so adopters get autocomplete on\n * `assets.<namespace>.<key>` and `@Asset('<namespace>/<key>')`.\n *\n * Pure module — no side effects beyond what the caller does with the\n * returned content. Mirrors the shape of `renderPlugins` /\n * `renderRegistry` in the generator so the typegen output stays\n * consistent across surfaces.\n *\n * @module @forinda/kickjs-cli/typegen/asset-types\n */\n\nimport { statSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { globSync } from 'glob'\nimport { groupAssetKeys } from '@forinda/kickjs'\nimport type { AssetMapEntry } from '../config'\n\nexport interface DiscoveredAssetEntry {\n namespace: string\n /**\n * Path under the namespace — what comes after the `<namespace>/`\n * prefix in the logical key. Stripped of extension when the file's\n * basename was unique within the namespace, or carries the full\n * extension when a collision was auto-resolved.\n */\n key: string\n}\n\nexport interface DiscoveredAssets {\n entries: DiscoveredAssetEntry[]\n count: number\n}\n\nexport function discoverAssets(\n assetMap: Record<string, AssetMapEntry> | undefined,\n cwd: string,\n): DiscoveredAssets {\n if (!assetMap) return { entries: [], count: 0 }\n\n const seen = new Map<string, DiscoveredAssetEntry>()\n for (const [namespace, entry] of Object.entries(assetMap)) {\n if (!entry || typeof entry.src !== 'string') continue\n const srcAbs = resolve(cwd, entry.src)\n if (!isDir(srcAbs)) continue\n // Mirror build.ts: same `glob` engine, same defaults — typegen +\n // build agree on what counts as an asset, including full minimatch\n // patterns (extglob, negation, etc.) that the old lite matcher\n // silently treated as match-all.\n const matches = globSync(entry.glob ?? '**/*', {\n cwd: srcAbs,\n nodir: true,\n dot: false,\n posix: true,\n })\n matches.sort()\n // Run the same key-shaping logic the build pipeline + dev resolver\n // use, so the emitted type matches the manifest exactly. Auto-mode\n // keeps extensions on collision groups; singletons stay stripped.\n const { pairs } = groupAssetKeys(namespace, matches, {\n strategy: entry.keys ?? 'auto',\n })\n for (const { key: logical } of pairs) {\n // The helper returns the full `<namespace>/<...>` form; strip\n // the namespace prefix so the entry shape matches the legacy\n // contract (key = path under namespace).\n const subKey = logical.slice(namespace.length + 1)\n seen.set(logical, { namespace, key: subKey })\n }\n }\n return { entries: [...seen.values()], count: seen.size }\n}\n\nexport function renderAssetTypes(discovered: DiscoveredAssets): string {\n const HEADER = `/* eslint-disable */\n// AUTO-GENERATED by \\`kick typegen\\`. DO NOT EDIT.\n// Re-run with \\`kick typegen\\` or rely on \\`kick dev\\` to refresh.\n`\n\n if (discovered.entries.length === 0) {\n return `${HEADER}\ndeclare module '@forinda/kickjs' {\n /**\n * Map of every typed asset discovered in the project's assetMap.\n * (No assetMap entries discovered yet — declare with\n * \\`assetMap: { name: { src: 'src/...' } }\\` in kick.config.ts.)\n */\n interface KickAssets {}\n}\n\nexport {}\n`\n }\n\n const tree: TreeNode = {}\n for (const entry of discovered.entries) {\n const path = `${entry.namespace}/${entry.key}`.split('/')\n let node = tree\n for (let i = 0; i < path.length - 1; i++) {\n const part = path[i]\n const existing = node[part]\n if (existing === LEAF) {\n // The intermediate path was previously claimed by a leaf —\n // collision between a file (`mails/welcome.ejs`) and a\n // directory (`mails/welcome/x.ejs`). Promote to a sub-tree\n // so the directory wins (consistent with the file-tree\n // semantics; the build pipeline emits a runtime warning for\n // the colliding leaf).\n const promoted: TreeNode = {}\n node[part] = promoted\n node = promoted\n } else {\n if (!existing) node[part] = {}\n node = node[part] as TreeNode\n }\n }\n const leaf = path[path.length - 1]\n if (typeof node[leaf] === 'object') {\n // The leaf collides with an already-built subtree. Skip; the\n // directory wins (matches the promote-to-subtree branch above).\n continue\n }\n node[leaf] = LEAF\n }\n\n const body = renderTree(tree, ' ')\n return `${HEADER}\ndeclare module '@forinda/kickjs' {\n /**\n * Map of every typed asset discovered in the project's assetMap.\n * Each leaf is a \\`() => string\\` thunk that returns the resolved\n * absolute path for the file in the current run mode (dev → src,\n * prod → dist).\n */\n interface KickAssets {\n${body}\n }\n}\n\nexport {}\n`\n}\n\nconst LEAF = Symbol('asset-leaf')\ntype TreeNode = { [key: string]: TreeNode | typeof LEAF }\n\nfunction renderTree(node: TreeNode, indent: string): string {\n const keys = Object.keys(node).toSorted()\n const lines: string[] = []\n for (const key of keys) {\n const child = node[key]\n const safeKey = isIdentifier(key) ? key : JSON.stringify(key)\n if (child === LEAF) {\n lines.push(`${indent}${safeKey}: () => string`)\n } else {\n lines.push(`${indent}${safeKey}: {`)\n lines.push(renderTree(child, `${indent} `))\n lines.push(`${indent}}`)\n }\n }\n return lines.join('\\n')\n}\n\nfunction isIdentifier(str: string): boolean {\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(str)\n}\n\nfunction isDir(path: string): boolean {\n try {\n return statSync(path).isDirectory()\n } catch {\n return false\n }\n}\n","/**\n * Public entry point for the KickJS typegen module.\n *\n * Used by:\n * - `kick typegen` (one-shot or watch mode)\n * - `kick dev` (auto-runs once before Vite starts; refreshes when files change)\n *\n * @module @forinda/kickjs-cli/typegen\n */\n\nimport { resolve, basename } from 'node:path'\nimport { readdir, stat, unlink } from 'node:fs/promises'\nimport { scanProject, type ScanResult } from './scanner'\nimport { generateTypes, type GenerateResult, TokenCollisionError } from './generator'\nimport { validateTokenConventions, type TokenConventionWarning } from './token-conventions'\nimport { discoverAssets } from './asset-types'\nimport type { AssetMapEntry } from '../config'\nimport type { TypegenPluginResult } from './plugin'\n\nexport type {\n DiscoveredClass,\n DiscoveredToken,\n DiscoveredInject,\n DiscoveredEnv,\n DiscoveredPluginOrAdapter,\n DiscoveredAugmentation,\n ClassCollision,\n ScanResult,\n} from './scanner'\nexport type { GenerateResult } from './generator'\nexport { TokenCollisionError } from './generator'\nexport { validateTokenConventions, type TokenConventionWarning } from './token-conventions'\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 * Asset map from `kick.config.ts`. When set, `runTypegen` walks\n * each entry's `src` directory + emits `.kickjs/types/assets.d.ts`\n * augmenting `KickAssets` for autocomplete on `assets.x.y()` and\n * `@Asset('x/y')`. Omit to skip the asset typegen pass entirely.\n */\n assetMap?: Record<string, AssetMapEntry>\n /**\n * Whether `runTypegen` should also run the TypegenPlugin pipeline\n * (`runAllPluginTypegens`) after the legacy generator pass. Defaults\n * to `true` so single-shot callers (kick g, commands/typegen, tests)\n * keep getting a fully-refreshed `.kickjs/types/` from one entry\n * point. `watchTypegen` flips this to `false` because it manages\n * the plugin pass itself + would otherwise double-run it on every\n * filesystem trigger.\n */\n runPlugins?: boolean\n}\n\n/** Resolve options to absolute paths */\nfunction resolveOptions(opts: RunTypegenOptions): {\n cwd: string\n srcDir: string\n outDir: string\n silent: boolean\n allowDuplicates: boolean\n schemaValidator: 'zod' | false\n envFile: string | false\n} {\n const cwd = opts.cwd ?? process.cwd()\n return {\n cwd,\n srcDir: resolve(cwd, opts.srcDir ?? 'src'),\n outDir: resolve(cwd, opts.outDir ?? '.kickjs/types'),\n silent: opts.silent ?? false,\n allowDuplicates: opts.allowDuplicates ?? false,\n schemaValidator: opts.schemaValidator ?? false,\n envFile: opts.envFile ?? 'src/env.ts',\n }\n}\n\n/**\n * Run a single typegen pass: scan source files, generate `.d.ts` files.\n *\n * Returns the discovered scan result alongside the generation result so\n * callers (`kick dev`, devtools) can log them or feed them to other tools.\n *\n * Throws `TokenCollisionError` if duplicate class names are found and\n * `allowDuplicates` is false.\n */\nexport async function runTypegen(opts: RunTypegenOptions = {}): Promise<{\n scan: ScanResult\n result: GenerateResult\n /** Token convention warnings — empty when every literal matches §22.2. */\n tokenWarnings: TokenConventionWarning[]\n}> {\n const { cwd, srcDir, outDir, silent, allowDuplicates, 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 assets = discoverAssets(opts.assetMap, cwd)\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 pluginsAndAdapters: scan.pluginsAndAdapters,\n augmentations: scan.augmentations,\n assets,\n outDir,\n allowDuplicates,\n schemaValidator,\n })\n // M2.B-T8 carve: routes + env live in plugin typegens, not the\n // legacy generator above. Run the plugin pipeline as part of\n // `runTypegen` by default so single-shot callers (kick g <leaf> /\n // kick new / tests) stay on one entry point and see a fully-\n // refreshed `.kickjs/types/` after the call returns. Watch mode\n // and the `kick typegen` / `kick dev` / `kick build` CLI paths\n // opt out (`runPlugins: false`) because they drive the plugin\n // pass externally and would otherwise double-run it.\n let pluginResults: TypegenPluginResult[] = []\n if (opts.runPlugins !== false) {\n try {\n const { runAllPluginTypegens } = await import('./run-plugins')\n const { loadKickConfig } = await import('../config')\n const pluginConfig = await loadKickConfig(cwd)\n pluginResults = await runAllPluginTypegens({ cwd, config: pluginConfig, silent: true })\n } catch (err) {\n // Broken plugins shouldn't block dev tooling — generators have\n // already written the rest of `.kickjs/types/`, and surfacing\n // here as a throw would abort the wider command (kick g, kick\n // new). When `silent` is false, log the message so the failure\n // is at least visible; otherwise swallow.\n if (!silent) {\n const msg = err instanceof Error ? err.message : String(err)\n console.warn(` kick typegen: plugin pipeline failed (${msg}) — continuing`)\n }\n }\n }\n\n // Sweep stale `.kickjs/types/*` files. Older CLI versions emitted\n // `env.ts`, `routes.ts`, and (until this fix) `assets.d.ts` directly\n // from the legacy generator. Once those carved out into `kick/env`,\n // `kick/routes`, and `kick/assets` typegen plugins, the legacy\n // file names became orphans — but were never deleted on subsequent\n // runs, so adopters who upgraded saw `KickAssets`/`KickEnv`/\n // `KickRoutes` declared twice (once legacy, once `kick__*`). Sweep\n // walks the output dir at the end of every pass and unlinks any\n // top-level file not in the expected set, so a single `kick typegen`\n // run after upgrade self-heals.\n if (opts.runPlugins !== false) {\n await sweepStaleTypegen(outDir, result.written, pluginResults, silent)\n }\n\n const tokenWarnings = validateTokenConventions(scan.tokens)\n const elapsed = Date.now() - start\n\n if (!silent) {\n const where = outDir.replace(cwd + '/', '')\n const collisionNote =\n result.resolvedCollisions > 0 ? `, ${result.resolvedCollisions} collisions namespaced` : ''\n const envNote = result.envWritten ? ', env typed' : ''\n const pluginNote = result.pluginEntries > 0 ? `, ${result.pluginEntries} plugins/adapters` : ''\n const augNote =\n result.augmentationEntries > 0 ? `, ${result.augmentationEntries} augmentations` : ''\n const assetNote = result.assetEntries > 0 ? `, ${result.assetEntries} assets` : ''\n console.log(\n ` kick typegen → ${result.serviceTokens} services, ${result.routeEntries} routes, ${result.moduleTokens} modules${pluginNote}${augNote}${assetNote}${envNote}${collisionNote} → ${where} (${elapsed}ms)`,\n )\n if (tokenWarnings.length > 0) {\n console.warn(\n ` kick typegen: ${tokenWarnings.length} token(s) don't match the §22.2 convention:`,\n )\n for (const warning of tokenWarnings) {\n const variableNote = warning.variable ? ` [${warning.variable}]` : ''\n console.warn(\n ` '${warning.token}' (${warning.filePath})${variableNote} — ${warning.reason}`,\n )\n if (warning.suggestion) {\n console.warn(` → suggestion: ${warning.suggestion}`)\n }\n }\n }\n }\n\n return { scan, result, tokenWarnings }\n}\n\n/**\n * Watch mode for `kick typegen --watch`.\n *\n * Uses Node's built-in `fs.watch` (recursive, available on Linux 22+ and\n * macOS 19+). Falls back gracefully if recursive watch is not supported.\n *\n * Debounces re-runs by 100ms so a bulk file change (e.g. `kick g module`\n * creating 5 files at once) emits one regen, not five.\n *\n * In watch mode collisions are reported but never thrown — the watcher\n * keeps running so the user can fix the rename and the next scan\n * recovers automatically.\n *\n * Returns a `stop()` function that closes the watcher.\n */\nexport async function watchTypegen(opts: RunTypegenOptions = {}): Promise<() => void> {\n const resolved = resolveOptions(opts)\n const { srcDir, silent, cwd } = resolved\n // Watch mode always tolerates collisions — otherwise an in-progress\n // rename would crash the dev loop. The error is still printed.\n // `runPlugins: false` keeps `runTypegen` from double-running the\n // plugin pipeline; the watcher invokes `runPlugins()` explicitly\n // after each `runLegacy()` so both phases land before the sweep.\n const runOpts: RunTypegenOptions = { ...resolved, allowDuplicates: true, runPlugins: false }\n\n // Polling is the right strategy for Docker bind mounts, WSL crosses,\n // NFS, and some kernel/filesystem combos where `fs.watch` returns\n // without errors but events silently drop. Adopters opt in via\n // `KICKJS_WATCH_POLLING=1`; default stays event-based (lower CPU).\n const forcePolling =\n process.env.KICKJS_WATCH_POLLING === '1' || process.env.KICKJS_WATCH_POLLING === 'true'\n\n // Lazy-import the plugin pipeline + config loader to avoid an eager\n // module-eval cycle (this file is reachable from plugin/builtins via\n // commands/typegen → ../typegen).\n const [{ runAllPluginTypegens }, { loadKickConfig }] = await Promise.all([\n import('./run-plugins'),\n import('../config'),\n ])\n const pluginConfig = await loadKickConfig(cwd)\n // Track the most recent generator + plugin outputs so the post-run\n // sweep knows the full expected set. The legacy generator returns\n // its written list on each call (captured below via a closure), and\n // the plugin pipeline returns one per plugin.\n let lastGeneratorWritten: readonly string[] = []\n const runLegacy = async () => {\n try {\n const out = await runTypegen({ ...runOpts })\n lastGeneratorWritten = out.result.written\n } catch (err) {\n if (silent) return\n if (err instanceof TokenCollisionError) {\n console.error('\\n' + err.message + '\\n')\n } else {\n const msg = err instanceof Error ? err.message : String(err)\n console.error(` kick typegen failed: ${msg}`)\n }\n }\n }\n const runPlugins = async () => {\n try {\n const pluginResults = await runAllPluginTypegens({\n cwd,\n config: pluginConfig,\n silent: true,\n })\n await sweepStaleTypegen(resolved.outDir, lastGeneratorWritten, pluginResults, true)\n } catch {\n /* swallow — watcher must never die */\n }\n }\n\n // Initial run — legacy pass first, then plugin typegens.\n await runLegacy()\n await runPlugins()\n\n const { watch } = await import('node:fs')\n\n let timer: ReturnType<typeof setTimeout> | null = null\n const trigger = (filename: string | null) => {\n // Only react to TypeScript source changes; ignore everything else\n if (!filename) return\n if (!/\\.(ts|tsx|mts|cts)$/.test(filename)) return\n if (filename.includes('.kickjs')) return\n if (filename.endsWith('.d.ts')) return\n\n if (timer) clearTimeout(timer)\n timer = setTimeout(() => {\n void runLegacy().then(runPlugins)\n }, 100)\n }\n\n // Forced-polling path — skip fs.watch entirely and just re-scan\n // periodically. The 2s interval matches the existing fallback so\n // adopters who flip the env var don't see a dramatic CPU jump.\n if (forcePolling) {\n if (!silent) {\n console.log(' kick typegen: polling mode (KICKJS_WATCH_POLLING)')\n }\n const interval = setInterval(() => {\n safeRun({ ...runOpts, silent: true }, true)\n }, 2000)\n return () => clearInterval(interval)\n }\n\n let watcher: ReturnType<typeof watch>\n try {\n watcher = watch(srcDir, { recursive: true }, (_event, filename) => {\n trigger(filename)\n })\n } catch (err: any) {\n if (!silent) {\n console.warn(\n ` kick typegen: watch mode unavailable (${err?.message ?? err}). Falling back to polling.`,\n )\n }\n // Polling fallback — re-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\n/**\n * Remove orphaned typegen output. The legacy generator emitted\n * `assets.d.ts`, `env.ts`, and `routes.ts` directly; once those carved\n * into the `kick/assets`, `kick/env`, and `kick/routes` plugins, the\n * legacy file names became stale on disk for any project that had run\n * an older CLI. Without a sweep, both copies coexist and adopters get\n * duplicated `KickAssets` / `KickEnv` / `KickRoutes` augmentations.\n *\n * Strategy: enumerate the top level of `outDir`, keep the union of\n * generator-written files + plugin-written files, unlink anything\n * else. Subdirectories are left alone — typegen never creates them.\n * Errors are swallowed (silent → no log) so a transient ENOENT or\n * permission glitch never aborts the wider command.\n */\nexport async function sweepStaleTypegen(\n outDir: string,\n generatorWritten: readonly string[],\n pluginResults: readonly TypegenPluginResult[],\n silent: boolean,\n): Promise<string[]> {\n const expected = new Set<string>()\n for (const file of generatorWritten) expected.add(basename(file))\n for (const r of pluginResults) {\n if (r.outFile) expected.add(basename(r.outFile))\n }\n // Hidden files we own at the types/ level — none today, but the\n // gitignore at .kickjs/.gitignore lives one level up so it's not\n // a candidate here.\n let entries: string[]\n try {\n entries = await readdir(outDir)\n } catch {\n return []\n }\n const removed: string[] = []\n for (const name of entries) {\n if (expected.has(name)) continue\n const abs = resolve(outDir, name)\n try {\n const s = await stat(abs)\n if (!s.isFile()) continue\n await unlink(abs)\n removed.push(name)\n } catch {\n // Best-effort; don't crash the typegen pass on a stat/unlink miss.\n }\n }\n if (removed.length > 0 && !silent) {\n console.log(` kick typegen: swept ${removed.length} stale file(s): ${removed.join(', ')}`)\n }\n return removed\n}\n"],"mappings":";;;;;;;;;;iXAgCA,MAAa,EAAkB,CAC7B,UACA,aACA,aACA,aACA,YACA,SACD,CAwLK,EAAqB,CAAC,MAAO,OAAQ,OAAQ,OAAO,CACpD,EAAmB,CAAC,eAAgB,UAAW,OAAQ,QAAS,SAAU,SAAU,QAAQ,CAM5F,EAAwB,IAAI,OAChC,OAAO,GAAG,KAAK,EAAgB,KAAK,IAAI,CAAC,eACvC,OAAO,GAAG,qCACV,OAAO,GAAG,yDACZ,IACD,CAWK,EAAyB,IAAI,OACjC,OAAO,GAAG,sDACR,OAAO,GAAG,oCACV,OAAO,GAAG,qCACZ,IACD,CAOK,EACJ,8GAMI,EAA0B,8DAG1B,EAAuB,2CAQvB,EAAsB,sDAQtB,EAA0B,IAAI,OAClC,OAAO,GAAG,wDACR,OAAO,GAAG,oCACV,OAAO,GAAG,sCACZ,IACD,CAGK,EAAyB,iDAOzB,EAA4B,+DAc5B,EAAwB,IAAI,OAAO,OAAO,GAAG,KAAK,CAX/B,MAAO,OAAQ,MAAO,SAAU,QAWc,CAAC,KAAK,IAAI,CAAC,QAAS,IAAI,CAS/F,SAAS,EAAkB,EAAc,EAAyB,CAChE,IAAI,EAAQ,EACZ,IAAK,IAAI,EAAI,EAAU,EAAG,EAAI,EAAK,OAAQ,IAAK,CAC9C,IAAM,EAAK,EAAK,GAChB,GAAI,IAAO,IAAK,YACP,IAAO,MACd,IACI,IAAU,GAAG,OAAO,EAG5B,MAAO,GAaT,SAAS,EACP,EACA,EAC+C,CAC/C,IAAI,EAAM,EAEV,KAAO,EAAM,EAAM,QAAQ,CACzB,KAAO,EAAM,EAAM,QAAU,KAAK,KAAK,EAAM,GAAK,EAAE,IACpD,GAAI,EAAM,KAAS,IAAK,MACxB,IAAM,EAAW,EAAM,MAAM,EAAI,CAAC,MAAM,eAAe,CACvD,GAAI,CAAC,EAAU,MAEf,IADA,GAAO,EAAS,GAAG,OACZ,EAAM,EAAM,QAAU,KAAK,KAAK,EAAM,GAAK,EAAE,IACpD,GAAI,EAAM,KAAS,IAAK,CACtB,IAAM,EAAQ,EAAkB,EAAO,EAAI,CAC3C,GAAI,EAAQ,EAAG,OAAO,KACtB,EAAM,EAAQ,GAIlB,KAAO,EAAM,EAAM,QAAU,KAAK,KAAK,EAAM,GAAK,EAAE,IACpD,IAAK,IAAM,IAAO,CAAC,SAAU,UAAW,YAAY,CAClD,GAAI,EAAM,MAAM,EAAK,EAAM,EAAI,OAAO,GAAK,GAAO,KAAK,KAAK,EAAM,OAAO,EAAM,EAAI,OAAO,CAAC,CAAE,CAE3F,IADA,GAAO,EAAI,OACJ,EAAM,EAAM,QAAU,KAAK,KAAK,EAAM,GAAK,EAAE,IACpD,MAGJ,GAAI,EAAM,MAAM,EAAK,EAAM,EAAE,GAAK,SAAW,KAAK,KAAK,EAAM,OAAO,EAAM,EAAE,CAAC,CAE3E,IADA,GAAO,EACA,EAAM,EAAM,QAAU,KAAK,KAAK,EAAM,GAAK,EAAE,IAGtD,IAAM,EAAc,EAAM,MAAM,EAAI,CAAC,MAAM,uBAAuB,CAElE,OADK,EACE,CAAE,WAAY,EAAY,GAAI,OAAQ,EAAM,EAAY,GAAG,OAAQ,CADjD,KAK3B,SAAS,EAAkB,EAAwB,CAEjD,OADgB,EAAK,MAAM,mBAAmB,EAAI,EAAE,EACrC,IAAK,GAAM,EAAE,MAAM,EAAE,CAAC,CAWvC,SAAS,EAA6B,EAAc,EAA8B,CAGhF,IAAM,EAAI,IADK,OAAO,OAAO,GAAG,KAAK,EAAM,2BAA4B,IAC3D,CAAC,KAAK,EAAK,CAEvB,OADK,EACE,EAAE,GADM,KAejB,SAAS,EAAoB,EAAgB,EAAmC,CAK9E,IAAM,EAAQ,IAHM,OAClB,OAAO,GAAG,iCAAiC,EAAW,2CAEnC,CAAC,KAAK,EAAO,CAClC,GAAI,EAAO,OAAO,EAAM,GAMxB,IAAM,EAAM,IAHU,OACpB,OAAO,GAAG,wBAAwB,EAAW,kCAE1B,CAAC,KAAK,EAAO,CAClC,GAAI,EAAK,OAAO,EAAI,GAMpB,IAAM,EAAK,IAHM,OACf,OAAO,GAAG,sBAAsB,EAAW,kCAE9B,CAAC,KAAK,EAAO,CAQ5B,OAPI,EAAW,EAAG,GAKd,IADgB,OAAO,OAAO,GAAG,oCAAoC,EAAW,IACzE,CAAC,KAAK,EAAO,CAAS,GAE1B,KAgBT,SAAS,EACP,EACA,EAC2E,CAC3E,IAAM,EAAW,6CAA6C,KAAK,EAAe,CAClF,GAAI,CAAC,EAAU,CAEb,IAAM,EAAQ,mCAAmC,KAAK,EAAe,CAErE,OADK,EACE,EAAuB,EAAM,GAAG,MAAM,CAAE,EAAW,CADvC,KAGrB,OAAO,EAAuB,EAAS,GAAG,MAAM,CAAE,EAAW,CAG/D,SAAS,EACP,EACA,EACoE,CAEpE,GAAI,EAAI,WAAW,IAAI,CACrB,OAAO,EAAyB,EAAI,CAGtC,IAAM,EAAU,kBAAkB,KAAK,EAAI,CAC3C,GAAI,EAAS,CACX,IAAM,EAAQ,EAAQ,GAMhB,EAAa,IAJC,OAClB,OAAO,GAAG,WAAW,EAAM,uCAC3B,IAEwB,CAAC,KAAK,EAAW,CAC3C,GAAI,EACF,OAAO,EAAyB,EAAW,GAAG,CAIlD,MAAO,CAAE,WAAY,EAAE,CAAE,SAAU,EAAE,CAAE,WAAY,EAAE,CAAE,CAIzD,SAAS,EAAmB,EAAiB,EAAuB,CAElE,IAAM,EAAI,IADK,OAAO,OAAO,GAAG,GAAG,EAAI,uBAC3B,CAAC,KAAK,EAAQ,CAE1B,OADK,EACE,MAAM,KAAK,EAAE,GAAG,SAAS,uBAAuB,CAAC,CAAC,IAAK,GAAM,EAAE,GAAG,CAD1D,EAAE,CAKnB,SAAS,EAAyB,EAIhC,CACA,MAAO,CACL,WAAY,EAAmB,EAAS,aAAa,CACrD,SAAU,EAAmB,EAAS,WAAW,CACjD,WAAY,EAAmB,EAAS,aAAa,CACtD,CAIH,eAAe,EAAK,EAAa,EAAsC,CACrE,IAAM,EAAO,EAAK,YAAc,EAC1B,EAAW,EAAK,SAAW,EAC3B,EAAgB,EAAE,CAEpB,EACJ,GAAI,CACF,EAAW,MAAM,EAAQ,EAAK,CAAE,cAAe,GAAM,SAAU,QAAS,CAAC,MACnE,CACN,OAAO,EAGT,IAAK,IAAM,KAAS,EAAS,CAC3B,IAAM,EAAO,EAAK,EAAK,EAAM,KAAK,CAC5B,EAAM,EAAS,EAAK,IAAK,EAAK,CAEhC,EAAS,KAAM,GAAO,EAAI,SAAS,EAAG,CAAC,GAEvC,EAAM,aAAa,CACrB,EAAI,KAAK,GAAI,MAAM,EAAK,EAAM,EAAK,CAAE,CAC5B,EAAM,QAAQ,EACnB,EAAK,KAAM,GAAQ,EAAM,KAAK,SAAS,EAAI,CAAC,EAC9C,EAAI,KAAK,EAAK,EAKpB,OAAO,EAIT,SAAS,EAAW,EAAkB,EAAqB,CACzD,OAAO,EAAS,EAAK,EAAS,CAAC,MAAM,EAAI,CAAC,KAAK,IAAI,CAIrD,SAAgB,GACd,EACA,EACA,EACmB,CACnB,IAAM,EAAyB,EAAE,CAC3B,EAAU,EAAW,EAAU,EAAI,CAEzC,EAAsB,UAAY,EAClC,IAAI,EACJ,MAAQ,EAAQ,EAAsB,KAAK,EAAO,IAAM,MAAM,CAC5D,GAAM,EAAG,EAAW,EAAe,GAAa,EAChD,EAAI,KAAK,CACP,YACW,YACX,WACA,aAAc,EACd,UAAW,EAAQ,EACpB,CAAC,CAMJ,EAAuB,UAAY,EACnC,IAAI,EACJ,MAAQ,EAAW,EAAuB,KAAK,EAAO,IAAM,MAAM,CAChE,GAAM,EAAG,EAAe,GAAa,EACjC,EAAI,KAAM,GAAM,EAAE,YAAc,GAAa,EAAE,WAAa,EAAS,EACzE,EAAI,KAAK,CACP,YACA,UAAW,SACX,WACA,aAAc,EACd,UAAW,EAAQ,EACpB,CAAC,CAGJ,OAAO,EAIT,SAAgB,GACd,EACA,EACA,EACmB,CACnB,IAAM,EAAyB,EAAE,CAC3B,EAAU,EAAW,EAAU,EAAI,CACnC,EAAO,IAAI,IAGjB,EAAmB,UAAY,EAC/B,IAAI,EACJ,MAAQ,EAAQ,EAAmB,KAAK,EAAO,IAAM,MAAM,CACzD,GAAM,CAAC,EAAM,EAAU,GAAQ,EAC/B,EAAK,IAAI,EAAK,CACd,EAAI,KAAK,CAAE,OAAM,WAAU,WAAU,aAAc,EAAS,CAAC,CAK/D,IADA,EAAwB,UAAY,GAC5B,EAAQ,EAAwB,KAAK,EAAO,IAAM,MACpD,EAAK,IAAI,EAAM,GAAG,EACtB,EAAI,KAAK,CACP,KAAM,EAAM,GACZ,SAAU,KACV,WACA,aAAc,EACf,CAAC,CAGJ,OAAO,EAcT,SAAgB,GACd,EACA,EACA,EACA,EACmB,CACnB,IAAM,EAAyB,EAAE,CACjC,GAAI,EAAc,SAAW,EAAG,OAAO,EACvC,IAAM,EAAU,EAAW,EAAU,EAAI,CAGnC,EAA4D,EAAE,CACpE,IAAK,IAAM,KAAO,EAAe,CAE/B,IAAM,EAAI,IADK,OAAO,OAAO,GAAG,WAAW,EAAI,UAAU,IAC7C,CAAC,KAAK,EAAO,CACrB,GAAG,QAAU,IAAA,IACf,EAAU,KAAK,CAAE,MAAK,MAAO,EAAE,MAAO,CAAC,CAG3C,EAAU,MAAM,EAAG,IAAM,EAAE,MAAQ,EAAE,MAAM,CAE3C,IAAK,IAAI,EAAI,EAAG,EAAI,EAAU,OAAQ,IAAK,CACzC,GAAM,CAAE,MAAK,SAAU,EAAU,GAC3B,EAAM,EAAI,EAAI,EAAU,OAAS,EAAU,EAAI,GAAG,MAAQ,EAAO,OACjE,EAAQ,EAAO,MAAM,EAAO,EAAI,CAMtC,EAAsB,UAAY,EAClC,IAAI,EACJ,MAAQ,EAAa,EAAsB,KAAK,EAAM,IAAM,MAAM,CAChE,IAAM,EAAO,EAAW,GAClB,EAAiB,EAAW,MAC5B,EAAY,EAAsB,UAAY,EAC9C,EAAa,EAAkB,EAAO,EAAU,CACtD,GAAI,EAAa,EAAG,SAEpB,IAAM,EAAY,EAAM,MAAM,EAAY,EAAG,EAAW,CAElD,EAAmB,EAAU,MAAM,0BAA0B,CAC7D,EAAO,GAAoB,EAAiB,GAAG,OAAS,EAAI,EAAiB,GAAK,IAElF,EAAa,EAA0B,EAAO,EAAa,EAAE,CACnE,GAAI,CAAC,EAAY,SACjB,GAAM,CAAE,aAAY,UAAW,EAI/B,EAAsB,UAAY,EAGlC,IAAM,EAAQ,EADM,EAAM,MAAM,EAAgB,EACD,CAAE,EAAO,CAElD,EAAS,EAA6B,EAAW,OAAO,CACxD,EAAU,EAA6B,EAAW,QAAQ,CAC1D,EAAW,EAA6B,EAAW,SAAS,CAElE,EAAI,KAAK,CACP,WAAY,EAAI,UAChB,OAAQ,EACR,WAAY,EAAK,aAAa,CAC9B,OACA,WAAY,EAAkB,EAAK,CACnC,gBAAiB,GAAO,YAAc,KACtC,cAAe,GAAO,UAAY,KAClC,gBAAiB,GAAO,YAAc,KACtC,WAAY,EACR,CAAE,WAAY,EAAQ,OAAQ,EAAoB,EAAQ,EAAO,CAAE,CACnE,KACJ,YAAa,EACT,CAAE,WAAY,EAAS,OAAQ,EAAoB,EAAQ,EAAQ,CAAE,CACrE,KACJ,aAAc,EACV,CAAE,WAAY,EAAU,OAAQ,EAAoB,EAAQ,EAAS,CAAE,CACvE,KACJ,WACA,aAAc,EACf,CAAC,EAIN,OAAO,EAIT,SAAgB,GACd,EACA,EACA,EACoB,CACpB,IAAM,EAA0B,EAAE,CAC5B,EAAU,EAAW,EAAU,EAAI,CAEzC,EAAqB,UAAY,EACjC,IAAI,EACJ,MAAQ,EAAQ,EAAqB,KAAK,EAAO,IAAM,MACrD,EAAI,KAAK,CAAE,KAAM,EAAM,GAAI,WAAU,aAAc,EAAS,CAAC,CAG/D,OAAO,EAUT,SAAS,EAAkB,EAAc,EAA8B,CACrE,IAAI,EAAQ,EACZ,IAAK,IAAI,EAAI,EAAe,EAAG,EAAI,EAAK,OAAQ,IAAK,CACnD,IAAM,EAAK,EAAK,GAChB,GAAI,IAAO,IAAK,YACP,IAAO,MACd,IACI,IAAU,GAAG,OAAO,EAG5B,MAAO,GAYT,SAAgB,GACd,EACA,EACA,EAC6B,CAC7B,IAAM,EAAmC,EAAE,CACrC,EAAU,EAAW,EAAU,EAAI,CACnC,EAAO,IAAI,IAGjB,EAAoB,UAAY,EAChC,IAAI,EACJ,MAAQ,EAAc,EAAoB,KAAK,EAAO,IAAM,MAAM,CAChE,IAAM,EAAS,EAAY,GACrB,EAAY,EAAoB,UAAY,EAC5C,EAAa,EAAkB,EAAQ,EAAU,CACvD,GAAI,EAAa,EAAG,SACpB,IAAM,EAAW,EAAO,MAAM,EAAY,EAAG,EAAW,CAElD,EAAY,mCAAmC,KAAK,EAAS,CACnE,GAAI,CAAC,EAAW,SAChB,IAAM,EAAO,EAAU,GACjB,EAAY,GAAG,EAAO,IAAI,EAAK,IAAI,IACrC,EAAK,IAAI,EAAU,GACvB,EAAK,IAAI,EAAU,CACnB,EAAI,KAAK,CACP,KAAM,IAAW,eAAiB,SAAW,UAC7C,OACA,WACA,aAAc,EACf,CAAC,EAIJ,EAAwB,UAAY,EACpC,IAAI,EACJ,MAAQ,EAAa,EAAwB,KAAK,EAAO,IAAM,MAAM,CACnE,IAAM,EAAa,EAAW,MAExB,EAAW,EAAO,QAAQ,IAAK,EAAW,CAChD,GAAI,EAAW,EAAG,SAClB,IAAM,EAAa,EAAkB,EAAQ,EAAS,CACtD,GAAI,EAAa,EAAG,SACpB,IAAM,EAAO,EAAO,MAAM,EAAW,EAAG,EAAW,CAC7C,EAAY,EAAuB,KAAK,EAAK,CACnD,GAAI,CAAC,EAAW,SAChB,IAAM,EAAO,EAAU,GACjB,EAAY,UAAU,EAAK,IAAI,IACjC,EAAK,IAAI,EAAU,GACvB,EAAK,IAAI,EAAU,CACnB,EAAI,KAAK,CAAE,KAAM,UAAW,OAAM,WAAU,aAAc,EAAS,CAAC,EAGtE,OAAO,EAQT,SAAgB,GACd,EACA,EACA,EAC0B,CAC1B,IAAM,EAAgC,EAAE,CAClC,EAAU,EAAW,EAAU,EAAI,CAEzC,EAA0B,UAAY,EACtC,IAAI,EACJ,MAAQ,EAAQ,EAA0B,KAAK,EAAO,IAAM,MAAM,CAChE,IAAM,EAAO,EAAM,GACf,EAA6B,KAC7B,EAAyB,KAG7B,GAAI,EAAM,GAAI,CACZ,IAAM,EAAW,EAAO,QAAQ,IAAK,EAAM,MAAQ,EAAM,GAAG,OAAS,EAAE,CACvE,GAAI,GAAY,EAAG,CACjB,IAAM,EAAa,EAAkB,EAAQ,EAAS,CACtD,GAAI,GAAc,EAAG,CACnB,IAAM,EAAO,EAAO,MAAM,EAAW,EAAG,EAAW,CACnD,EAAc,EAAgB,EAAM,cAAc,CAClD,EAAU,EAAgB,EAAM,UAAU,GAKhD,EAAI,KAAK,CAAE,OAAM,cAAa,UAAS,WAAU,aAAc,EAAS,CAAC,CAG3E,OAAO,EAgBT,SAAS,EAAgB,EAAc,EAA8B,CAGnE,IAAM,EADc,OAAO,MAAM,EAAM,mBAAoB,IAC1C,CAAC,KAAK,EAAK,CAC5B,GAAI,CAAC,EAAG,OAAO,KACf,IAAM,EAAQ,EAAE,GACV,EAAQ,EAAE,MAAQ,EAAE,GAAG,OACzB,EAAI,EACJ,EAAqB,KACzB,KAAO,EAAI,EAAK,QAAQ,CACtB,IAAM,EAAK,EAAK,GAChB,GAAI,IAAO,KAAM,CAEf,GAAK,EACL,SAEF,GAAI,IAAO,EAAO,CAChB,EAAM,EAAK,MAAM,EAAO,EAAE,CAC1B,MAEF,IAOF,OALI,IAAQ,KAAa,KAKlB,EAAI,QAAQ,UAAW,EAAI,IAC5B,IAAM,IAAY;EAClB,IAAM,IAAY,IAClB,IAAM,IAAY,KACf,EACP,CASJ,MAAM,GAA8B,CAClC,sBACA,oBACA,gBACA,aACD,CAgBD,eAAsB,GAAc,EAAa,EAAgD,CAK/F,IAAM,EACJ,IAAY,aAAe,GAA8B,CAAC,EAAQ,CAEpE,IAAK,IAAM,KAAa,EAAY,CAClC,IAAM,EAAM,EAAQ,EAAK,EAAU,CAC/B,EACJ,GAAI,CACF,EAAS,MAAM,EAAS,EAAK,QAAQ,MAC/B,CACN,SAMG,sBAAmB,KAAK,EAAO,EAC/B,qBAAqB,KAAK,EAAO,CACtC,MAAO,CACL,SAAU,EACV,aAAc,EAAW,EAAK,EAAI,CACnC,CAGH,OAAO,KAIT,SAAgB,GAAe,EAA8C,CAC3E,IAAM,EAAS,IAAI,IACnB,IAAK,IAAM,KAAO,EAAS,CACzB,IAAM,EAAM,EAAO,IAAI,EAAI,UAAU,EAAI,EAAE,CAC3C,EAAI,KAAK,EAAI,CACb,EAAO,IAAI,EAAI,UAAW,EAAI,CAGhC,IAAM,EAA+B,EAAE,CACvC,IAAK,GAAM,CAAC,EAAW,KAAU,EAI3B,IADsB,IAAI,EAAM,IAAK,GAAM,EAAE,SAAS,CACzC,CAAC,KAAO,GACvB,EAAW,KAAK,CAAE,YAAW,QAAS,EAAO,CAAC,CAMlD,OADA,EAAW,MAAM,EAAG,IAAM,EAAE,UAAU,cAAc,EAAE,UAAU,CAAC,CAC1D,EAOT,eAAsB,EAAY,EAAwC,CAExE,IAAM,EAAQ,MAAM,EADP,EAAQ,EAAK,KACG,CAAE,EAAK,CAE9B,EAA6B,EAAE,CAC/B,EAA4B,EAAE,CAC9B,EAA4B,EAAE,CAC9B,EAA8B,EAAE,CAChC,EAAkD,EAAE,CACpD,EAA0C,EAAE,CAK5C,EAAU,IAAI,IACpB,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAI,EACJ,GAAI,CACF,EAAS,MAAM,EAAS,EAAM,QAAQ,MAChC,CACN,SAEF,EAAQ,IAAI,EAAM,EAAO,CACzB,EAAQ,KAAK,GAAG,GAAyB,EAAQ,EAAM,EAAK,IAAI,CAAC,CACjE,EAAO,KAAK,GAAG,GAAwB,EAAQ,EAAM,EAAK,IAAI,CAAC,CAC/D,EAAQ,KAAK,GAAG,GAAyB,EAAQ,EAAM,EAAK,IAAI,CAAC,CACjE,EAAmB,KAAK,GAAG,GAAoC,EAAQ,EAAM,EAAK,IAAI,CAAC,CACvF,EAAc,KAAK,GAAG,GAA+B,EAAQ,EAAM,EAAK,IAAI,CAAC,CAG/E,IAAK,GAAM,CAAC,EAAM,KAAW,EAAS,CACpC,IAAM,EAAgB,EAAQ,OAAQ,GAAM,EAAE,WAAa,EAAK,CAChE,EAAO,KAAK,GAAG,GAAwB,EAAQ,EAAM,EAAK,IAAK,EAAc,CAAC,CA2BhF,OAvBA,EAAQ,MAAM,EAAG,IACX,EAAE,YAAc,EAAE,UACf,EAAE,aAAa,cAAc,EAAE,aAAa,CADX,EAAE,UAAU,cAAc,EAAE,UAAU,CAE9E,CACF,EAAO,MACJ,EAAG,IAAM,EAAE,KAAK,cAAc,EAAE,KAAK,EAAI,EAAE,aAAa,cAAc,EAAE,aAAa,CACvF,CACD,EAAQ,MACL,EAAG,IAAM,EAAE,KAAK,cAAc,EAAE,KAAK,EAAI,EAAE,aAAa,cAAc,EAAE,aAAa,CACvF,CACD,EAAO,MACJ,EAAG,IAAM,EAAE,WAAW,cAAc,EAAE,WAAW,EAAI,EAAE,OAAO,cAAc,EAAE,OAAO,CACvF,CACD,EAAmB,MAChB,EAAG,IAAM,EAAE,KAAK,cAAc,EAAE,KAAK,EAAI,EAAE,aAAa,cAAc,EAAE,aAAa,CACvF,CACD,EAAc,MACX,EAAG,IAAM,EAAE,KAAK,cAAc,EAAE,KAAK,EAAI,EAAE,aAAa,cAAc,EAAE,aAAa,CACvF,CAKM,CACL,UACA,SACA,SACA,UACA,WARiB,GAAe,EAQtB,CACV,IAAA,MARgB,GAAc,EAAK,IAAK,EAAK,SAAW,aAAa,CASrE,qBACA,gBACD,CCngCH,MAAM,EAAS,6IAMT,EAAsB,IAAI,IAAI,CAAC,UAAW,aAAc,aAAc,YAAY,CAAC,CAGzF,IAAa,EAAb,cAAyC,KAAM,CAC7C,WACA,YAAY,EAA8B,CACxC,MAAM,GAAuB,EAAW,CAAC,CACzC,KAAK,KAAO,sBACZ,KAAK,WAAa,IAKtB,SAAS,GAAuB,EAAsC,CACpE,IAAM,EAAkB,CAAC,yCAAyC,CAClE,IAAK,IAAM,KAAK,EAAY,CAC1B,EAAM,KAAK,GAAG,CACd,EAAM,KAAK,KAAK,EAAE,QAAQ,OAAO,kBAAkB,EAAE,UAAU,IAAI,CACnE,IAAK,IAAM,KAAO,EAAE,QAClB,EAAM,KAAK,SAAS,EAAI,eAAe,CAW3C,OARA,EAAM,KAAK,GAAG,CACd,EAAM,KAAK,eAAe,CAC1B,EAAM,KAAK,kCAAkC,CAC7C,EAAM,KACJ,oGACD,CACD,EAAM,KAAK,6EAA6E,CACxF,EAAM,KAAK,oEAAoE,CACxE,EAAM,KAAK;EAAK,CAIzB,SAAS,GAAmB,EAAoB,EAA0B,CAExE,IAAI,EAAM,EADM,EAAQ,EACE,CAAE,EAAW,CAAC,MAAM,EAAI,CAAC,KAAK,IAAI,CAG5D,MAFA,GAAM,EAAI,QAAQ,uBAAwB,GAAG,CACxC,EAAI,WAAW,IAAI,GAAE,EAAM,KAAO,GAChC,EAST,SAAS,GAAiB,EAA8B,CAItD,IAAM,EAHM,EAAI,aAAa,QAAQ,SAAU,GAAG,CAAC,QAAQ,uBAAwB,GAGlE,CAAC,MAAM,IAAI,CAC5B,EAAM,KAAK,CACX,IAAM,EAAK,EAAM,KAAK,IAAI,CAC1B,OAAO,EAAK,GAAG,EAAG,GAAG,EAAI,YAAc,EAAI,UAY7C,SAAS,GACP,EACA,EACA,EACQ,CACR,IAAM,EAAO,IAAI,IACX,EAAoB,EAAE,CAE5B,IAAK,IAAM,KAAK,EAAS,CACvB,GAAI,CAAC,EAAoB,IAAI,EAAE,UAAU,CAAE,SAE3C,IAAM,EAAM,EAAe,IAAI,EAAE,UAAU,CAAG,GAAiB,EAAE,CAAG,EAAE,UACtE,GAAI,EAAK,IAAI,EAAI,CAAE,SACnB,EAAK,IAAI,EAAI,CAEb,IAAM,EAAO,GAAmB,EAAE,SAAU,EAAQ,CAC9C,EAAM,EAAE,UAAY,WAAW,EAAK,YAAc,WAAW,EAAK,KAAK,EAAE,YAC/E,EAAQ,KAAK,QAAQ,EAAI,KAAK,IAAM,CAOtC,MAAO,GAAG,EAAO;;;EAJJ,EAAQ,OACjB,EAAQ,KAAK;EAAK,CAClB,+EAKC;;;;;EASP,SAAS,EAAY,EAAkB,EAAiB,EAA8B,CAQpF,OAPI,EAAM,SAAW,EACZ,GAAG,EAAO;KAChB,EAAa;cACJ,EAAS;EAId,GAAG,EAAO;cACL,EAAS;EAFN,CAAC,GAAG,IAAI,IAAI,EAAM,CAAC,CAAC,UAG7B,CAAC,IAAK,GAAM,QAAQ,EAAE,GAAG,CAAC,KAAK;EAAK,CAAC;EAK7C,SAAS,GAAY,EAAqB,EAAgC,CAWxE,MAAO,GAAG,EAAO;;;;;;;;;;;;;;EADI,EAAgB;EAA8B,KADjD,EAAa;EAA2B,KA+B5D,SAAS,GAAc,EAA4C,CAIjE,IAAM,EAAS,IAAI,IACnB,IAAK,IAAM,KAAQ,EACZ,EAAO,IAAI,EAAK,KAAK,EAAE,EAAO,IAAI,EAAK,KAAM,EAAK,CAUzD,MAAO,GAAG,EAAO;;;;;;;;;EAPF,CAAC,GAAG,EAAO,QAAQ,CAAC,CAAC,UAAU,EAAG,IAAM,EAAE,KAAK,cAAc,EAAE,KAAK,CAC7D,CAAC,IAAK,GAAS,QAAQ,EAAK,KAAK,MAAM,EAAK,KAAK,GAAG,CAAC,KAAK;EAEnE,EAET,+FAWC;;;;;EAgBP,SAAS,GAAoB,EAAyC,CACpE,GAAI,EAAM,SAAW,EACnB,MAAO,GAAG,EAAO;;;;;;;;;;;;;EAkBnB,IAAM,EAAS,IAAI,IACnB,IAAK,IAAM,KAAQ,EACZ,EAAO,IAAI,EAAK,KAAK,EAAE,EAAO,IAAI,EAAK,KAAM,EAAK,CAGzD,IAAM,EAAmB,EAAE,CAC3B,IAAK,IAAM,IAAQ,CAAC,GAAG,EAAO,QAAQ,CAAC,CAAC,UAAU,EAAG,IAAM,EAAE,KAAK,cAAc,EAAE,KAAK,CAAC,CAAE,CACxF,IAAM,EAAqB,EAAE,CAC7B,GAAI,EAAK,YAGP,IAAK,IAAM,KAAQ,EAAK,YAAY,MAAM;EAAK,CAAE,EAAS,KAAK,MAAM,IAAO,CAE9E,GAAI,EAAK,QAAS,CAMhB,EAAS,KAAK,cAAe,WAAc,CAC3C,IAAK,IAAM,KAAQ,EAAK,QAAQ,MAAM;EAAK,CAAE,EAAS,KAAK,MAAM,IAAO,CACxE,EAAS,KAAK,SAAY,CAE5B,EAAS,KAAK,WAAW,EAAK,eAAe,CAC7C,EAAO,KACL,CAAC,MAAO,GAAG,EAAU,MAAO,oBAAoB,EAAK,KAAK,iBAAiB,CAAC,KAAK;EAAK,CACvF,CAGH,MAAO,GAAG,EAAO;;;;;EAKjB,EAAO,KAAK;;EAAO,CAAC;EAqEtB,eAAsB,GAAc,EAAgD,CAClF,GAAM,CACJ,UACA,SAAS,EAAE,CACX,SAAS,EAAE,CACX,UAAU,EAAE,CACZ,aAAa,EAAE,CACf,MAAM,KACN,qBAAqB,EAAE,CACvB,gBAAgB,EAAE,CAClB,SAAS,CAAE,QAAS,EAAE,CAAE,MAAO,EAAG,CAClC,SACA,kBAAkB,GAClB,gBAAiB,EAAmB,IAClC,EAEJ,GAAI,EAAW,OAAS,GAAK,CAAC,EAC5B,MAAM,IAAI,EAAoB,EAAW,CAG3C,MAAM,EAAM,EAAQ,CAAE,UAAW,GAAM,CAAC,CAExC,IAAM,EAAe,EAAK,EAAQ,gBAAgB,CAC5C,EAAe,EAAK,EAAQ,gBAAgB,CAC5C,EAAc,EAAK,EAAQ,eAAe,CAO1C,EAAc,EAAK,EAAQ,eAAe,CAC1C,EAAoB,EAAK,EAAQ,qBAAqB,CACtD,EAAY,EAAK,EAAQ,aAAa,CAEtC,EAAiB,IAAI,IAAI,EAAW,IAAK,GAAM,EAAE,UAAU,CAAC,CAC5D,EAAkB,GAAe,EAAS,EAAc,EAAe,CAIvE,EAAc,EACjB,OAAQ,GAAM,EAAoB,IAAI,EAAE,UAAU,CAAC,CACnD,IAAK,GAAO,EAAe,IAAI,EAAE,UAAU,CAAG,GAAiB,EAAE,CAAG,EAAE,UAAW,CAC9E,EAAgB,EAAO,IAAK,GAAM,EAAE,KAAK,CACzC,EAAiB,EAAQ,IAAK,GAAM,EAAE,KAAK,CAC3C,EAAc,CAAC,GAAG,EAAa,GAAG,EAAe,GAAG,EAAe,CAEnE,EAAU,EAAQ,OAAQ,GAAM,EAAE,YAAc,SAAS,CAAC,IAAK,GAAM,EAAE,UAAU,CAEjF,EAAkB,EACtB,eACA,EACA,oFACD,CACK,EAAiB,EACrB,cACA,EACA,sEACD,CACK,EAAiB,GAAc,EAAmB,CAClD,EAAuB,GAAoB,EAAc,CACzD,EAAe,GAAY,IAAQ,KAAM,EAAO,MAAQ,EAAE,CAEhE,MAAM,EAAU,EAAc,EAAiB,QAAQ,CACvD,MAAM,EAAU,EAAc,EAAiB,QAAQ,CACvD,MAAM,EAAU,EAAa,EAAgB,QAAQ,CACrD,MAAM,EAAU,EAAa,EAAgB,QAAQ,CACrD,MAAM,EAAU,EAAmB,EAAsB,QAAQ,CACjE,MAAM,EAAU,EAAW,EAAc,QAAQ,CAEjD,IAAM,EAAU,CACd,EACA,EACA,EACA,EACA,EACA,EACD,CAID,MAAM,EAAU,EADG,EAAQ,EACI,CAAE,aAAa,CAAE;;EAAyC,QAAQ,CAKjG,IAAM,EAAoB,IAAI,IAAI,EAAmB,IAAK,GAAM,EAAE,KAAK,CAAC,CAAC,KACnE,EAAsB,IAAI,IAAI,EAAc,IAAK,GAAM,EAAE,KAAK,CAAC,CAAC,KAEtE,MAAO,CACL,gBAAiB,EAAY,OAC7B,cAAe,IAAI,IAAI,EAAY,CAAC,KACpC,aAAc,EAAQ,OACtB,aAAc,EAAO,OACrB,cAAe,EACf,oBAAqB,EACrB,aAAc,EAAO,MACrB,WAAY,IAAQ,KACpB,UACA,mBAAoB,EAAW,OAChC,CCxbH,MAAM,GACJ,0EAYF,SAAgB,GACd,EAC0B,CAC1B,IAAM,EAAqC,EAAE,CAC7C,IAAK,IAAM,KAAS,EAAQ,CAC1B,IAAM,EAAO,EAAM,KACf,EAAK,WAAW,UAAc,EAC9B,GAAuB,KAAK,EAAK,EACrC,EAAS,KAAK,CACZ,MAAO,EACP,SAAU,EAAM,SAChB,SAAU,EAAM,aAChB,OAAQ,+DACR,WAAY,GAAc,EAAK,CAChC,CAAC,CAEJ,OAAO,EAGT,SAAS,GAAc,EAAkC,CACvD,GAAI,aAAa,KAAK,EAAK,CACzB,MAAO,YAAY,EAAK,kBAAkB,EAAK,IAEjD,GAAI,EAAK,SAAS,IAAI,CACpB,MAAO,sDAET,IAAM,EAAa,8BAA8B,KAAK,EAAK,CAC3D,GAAI,EAAY,CACd,GAAM,EAAG,EAAO,GAAO,EACvB,MAAO,IAAI,EAAM,GAAG,EAAI,OAAO,EAAE,CAAC,aAAa,GAAG,EAAI,MAAM,EAAE,CAAC,ICxCnE,SAAgB,EACd,EACA,EACkB,CAClB,GAAI,CAAC,EAAU,MAAO,CAAE,QAAS,EAAE,CAAE,MAAO,EAAG,CAE/C,IAAM,EAAO,IAAI,IACjB,IAAK,GAAM,CAAC,EAAW,KAAU,OAAO,QAAQ,EAAS,CAAE,CACzD,GAAI,CAAC,GAAS,OAAO,EAAM,KAAQ,SAAU,SAC7C,IAAM,EAAS,EAAQ,EAAK,EAAM,IAAI,CACtC,GAAI,CAAC,GAAM,EAAO,CAAE,SAKpB,IAAM,EAAU,EAAS,EAAM,MAAQ,OAAQ,CAC7C,IAAK,EACL,MAAO,GACP,IAAK,GACL,MAAO,GACR,CAAC,CACF,EAAQ,MAAM,CAId,GAAM,CAAE,SAAU,EAAe,EAAW,EAAS,CACnD,SAAU,EAAM,MAAQ,OACzB,CAAC,CACF,IAAK,GAAM,CAAE,IAAK,KAAa,EAAO,CAIpC,IAAM,EAAS,EAAQ,MAAM,EAAU,OAAS,EAAE,CAClD,EAAK,IAAI,EAAS,CAAE,YAAW,IAAK,EAAQ,CAAC,EAGjD,MAAO,CAAE,QAAS,CAAC,GAAG,EAAK,QAAQ,CAAC,CAAE,MAAO,EAAK,KAAM,CAG1D,SAAgB,GAAiB,EAAsC,CACrE,IAAM,EAAS,6IAKf,GAAI,EAAW,QAAQ,SAAW,EAChC,MAAO,GAAG,EAAO;;;;;;;;;;;EAcnB,IAAM,EAAiB,EAAE,CACzB,IAAK,IAAM,KAAS,EAAW,QAAS,CACtC,IAAM,EAAO,GAAG,EAAM,UAAU,GAAG,EAAM,MAAM,MAAM,IAAI,CACrD,EAAO,EACX,IAAK,IAAI,EAAI,EAAG,EAAI,EAAK,OAAS,EAAG,IAAK,CACxC,IAAM,EAAO,EAAK,GACZ,EAAW,EAAK,GACtB,GAAI,IAAa,EAAM,CAOrB,IAAM,EAAqB,EAAE,CAC7B,EAAK,GAAQ,EACb,EAAO,OAEF,IAAU,EAAK,GAAQ,EAAE,EAC9B,EAAO,EAAK,GAGhB,IAAM,EAAO,EAAK,EAAK,OAAS,GAC5B,OAAO,EAAK,IAAU,WAK1B,EAAK,GAAQ,GAIf,MAAO,GAAG,EAAO;;;;;;;;;EADJ,EAAW,EAAM,OAU1B,CAAC;;;;;EAQP,MAAM,EAAO,OAAO,aAAa,CAGjC,SAAS,EAAW,EAAgB,EAAwB,CAC1D,IAAM,EAAO,OAAO,KAAK,EAAK,CAAC,UAAU,CACnC,EAAkB,EAAE,CAC1B,IAAK,IAAM,KAAO,EAAM,CACtB,IAAM,EAAQ,EAAK,GACb,EAAU,GAAa,EAAI,CAAG,EAAM,KAAK,UAAU,EAAI,CACzD,IAAU,EACZ,EAAM,KAAK,GAAG,IAAS,EAAQ,gBAAgB,EAE/C,EAAM,KAAK,GAAG,IAAS,EAAQ,KAAK,CACpC,EAAM,KAAK,EAAW,EAAO,GAAG,EAAO,IAAI,CAAC,CAC5C,EAAM,KAAK,GAAG,EAAO,GAAG,EAG5B,OAAO,EAAM,KAAK;EAAK,CAGzB,SAAS,GAAa,EAAsB,CAC1C,MAAO,6BAA6B,KAAK,EAAI,CAG/C,SAAS,GAAM,EAAuB,CACpC,GAAI,CACF,OAAO,EAAS,EAAK,CAAC,aAAa,MAC7B,CACN,MAAO,6EC1FX,SAAS,EAAe,EAQtB,CACA,IAAM,EAAM,EAAK,KAAO,QAAQ,KAAK,CACrC,MAAO,CACL,MACA,OAAQ,EAAQ,EAAK,EAAK,QAAU,MAAM,CAC1C,OAAQ,EAAQ,EAAK,EAAK,QAAU,gBAAgB,CACpD,OAAQ,EAAK,QAAU,GACvB,gBAAiB,EAAK,iBAAmB,GACzC,gBAAiB,EAAK,iBAAmB,GACzC,QAAS,EAAK,SAAW,aAC1B,CAYH,eAAsB,EAAW,EAA0B,EAAE,CAK1D,CACD,GAAM,CAAE,MAAK,SAAQ,SAAQ,SAAQ,kBAAiB,kBAAiB,WACrE,EAAe,EAAK,CAEhB,EAAQ,KAAK,KAAK,CAClB,EAAO,MAAM,EAAY,CAC7B,KAAM,EACN,MAEA,QAAS,IAAY,GAAQ,IAAA,GAAY,EAC1C,CAAC,CACI,EAAS,EAAe,EAAK,SAAU,EAAI,CAC3C,EAAS,MAAM,GAAc,CACjC,QAAS,EAAK,QACd,OAAQ,EAAK,OACb,OAAQ,EAAK,OACb,QAAS,EAAK,QACd,WAAY,EAAK,WACjB,IAAK,IAAY,GAAQ,KAAO,EAAK,IACrC,mBAAoB,EAAK,mBACzB,cAAe,EAAK,cACpB,SACA,SACA,kBACA,kBACD,CAAC,CASE,EAAuC,EAAE,CAC7C,GAAI,EAAK,aAAe,GACtB,GAAI,CACF,GAAM,CAAE,wBAAyB,MAAM,OAAO,8BACxC,CAAE,kBAAmB,MAAM,OAAO,yBAAA,KAAA,GAAA,EAAA,EAAA,CAExC,EAAgB,MAAM,EAAqB,CAAE,MAAK,OAAQ,MAD/B,EAAe,EAAI,CAC0B,OAAQ,GAAM,CAAC,OAChF,EAAK,CAMZ,GAAI,CAAC,EAAQ,CACX,IAAM,EAAM,aAAe,MAAQ,EAAI,QAAU,OAAO,EAAI,CAC5D,QAAQ,KAAK,2CAA2C,EAAI,gBAAgB,EAe9E,EAAK,aAAe,IACtB,MAAM,EAAkB,EAAQ,EAAO,QAAS,EAAe,EAAO,CAGxE,IAAM,EAAgB,GAAyB,EAAK,OAAO,CACrD,EAAU,KAAK,KAAK,CAAG,EAE7B,GAAI,CAAC,EAAQ,CACX,IAAM,EAAQ,EAAO,QAAQ,EAAM,IAAK,GAAG,CACrC,EACJ,EAAO,mBAAqB,EAAI,KAAK,EAAO,mBAAmB,wBAA0B,GACrF,EAAU,EAAO,WAAa,cAAgB,GAC9C,EAAa,EAAO,cAAgB,EAAI,KAAK,EAAO,cAAc,mBAAqB,GACvF,EACJ,EAAO,oBAAsB,EAAI,KAAK,EAAO,oBAAoB,gBAAkB,GAC/E,EAAY,EAAO,aAAe,EAAI,KAAK,EAAO,aAAa,SAAW,GAIhF,GAHA,QAAQ,IACN,oBAAoB,EAAO,cAAc,aAAa,EAAO,aAAa,WAAW,EAAO,aAAa,UAAU,IAAa,IAAU,IAAY,IAAU,EAAc,KAAK,EAAM,IAAI,EAAQ,KACtM,CACG,EAAc,OAAS,EAAG,CAC5B,QAAQ,KACN,mBAAmB,EAAc,OAAO,6CACzC,CACD,IAAK,IAAM,KAAW,EAAe,CACnC,IAAM,EAAe,EAAQ,SAAW,KAAK,EAAQ,SAAS,GAAK,GACnE,QAAQ,KACN,QAAQ,EAAQ,MAAM,KAAK,EAAQ,SAAS,GAAG,EAAa,KAAK,EAAQ,SAC1E,CACG,EAAQ,YACV,QAAQ,KAAK,uBAAuB,EAAQ,aAAa,GAMjE,MAAO,CAAE,OAAM,SAAQ,gBAAe,CAkBxC,eAAsB,GAAa,EAA0B,EAAE,CAAuB,CACpF,IAAM,EAAW,EAAe,EAAK,CAC/B,CAAE,SAAQ,SAAQ,OAAQ,EAM1B,EAA6B,CAAE,GAAG,EAAU,gBAAiB,GAAM,WAAY,GAAO,CAMtF,EACJ,QAAQ,IAAI,uBAAyB,KAAO,QAAQ,IAAI,uBAAyB,OAK7E,CAAC,CAAE,wBAAwB,CAAE,mBAAoB,MAAM,QAAQ,IAAI,CACvE,OAAO,8BACP,OAAO,yBAAA,KAAA,GAAA,EAAA,EAAA,CACR,CAAC,CACI,EAAe,MAAM,EAAe,EAAI,CAK1C,EAA0C,EAAE,CAC1C,EAAY,SAAY,CAC5B,GAAI,CAEF,GAAuB,MADL,EAAW,CAAE,GAAG,EAAS,CAAC,EACjB,OAAO,cAC3B,EAAK,CACZ,GAAI,EAAQ,OACZ,GAAI,aAAe,EACjB,QAAQ,MAAM;EAAO,EAAI,QAAU;EAAK,KACnC,CACL,IAAM,EAAM,aAAe,MAAQ,EAAI,QAAU,OAAO,EAAI,CAC5D,QAAQ,MAAM,0BAA0B,IAAM,IAI9C,EAAa,SAAY,CAC7B,GAAI,CACF,IAAM,EAAgB,MAAM,EAAqB,CAC/C,MACA,OAAQ,EACR,OAAQ,GACT,CAAC,CACF,MAAM,EAAkB,EAAS,OAAQ,EAAsB,EAAe,GAAK,MAC7E,IAMV,MAAM,GAAW,CACjB,MAAM,GAAY,CAElB,GAAM,CAAE,SAAU,MAAM,OAAO,WAE3B,EAA8C,KAC5C,EAAW,GAA4B,CAEtC,GACA,sBAAsB,KAAK,EAAS,GACrC,EAAS,SAAS,UAAU,EAC5B,EAAS,SAAS,QAAQ,GAE1B,GAAO,aAAa,EAAM,CAC9B,EAAQ,eAAiB,CAClB,GAAW,CAAC,KAAK,EAAW,EAChC,IAAI,IAMT,GAAI,EAAc,CACX,GACH,QAAQ,IAAI,sDAAsD,CAEpE,IAAM,EAAW,gBAAkB,CACjC,EAAQ,CAAE,GAAG,EAAS,OAAQ,GAAM,CAAE,GAAK,EAC1C,IAAK,CACR,UAAa,cAAc,EAAS,CAGtC,IAAI,EACJ,GAAI,CACF,EAAU,EAAM,EAAQ,CAAE,UAAW,GAAM,EAAG,EAAQ,IAAa,CACjE,EAAQ,EAAS,EACjB,OACK,EAAU,CACZ,GACH,QAAQ,KACN,2CAA2C,GAAK,SAAW,EAAI,6BAChE,CAGH,IAAM,EAAW,gBAAkB,CACjC,EAAQ,CAAE,GAAG,EAAS,OAAQ,GAAM,CAAE,GAAK,EAC1C,IAAK,CACR,UAAa,cAAc,EAAS,CAGtC,UAAa,CACP,GAAO,aAAa,EAAM,CAC9B,EAAQ,OAAO,EAKnB,eAAe,EAAQ,EAAyB,EAAgC,CAC9E,GAAI,CACF,MAAM,EAAW,EAAK,OACf,EAAK,CACZ,GAAI,EAAQ,OACZ,GAAI,aAAe,EACjB,QAAQ,MAAM;EAAO,EAAI,QAAU;EAAK,KACnC,CACL,IAAM,EAAM,aAAe,MAAQ,EAAI,QAAU,OAAO,EAAI,CAC5D,QAAQ,MAAM,0BAA0B,IAAM,GAmBpD,eAAsB,EACpB,EACA,EACA,EACA,EACmB,CACnB,IAAM,EAAW,IAAI,IACrB,IAAK,IAAM,KAAQ,EAAkB,EAAS,IAAI,EAAS,EAAK,CAAC,CACjE,IAAK,IAAM,KAAK,EACV,EAAE,SAAS,EAAS,IAAI,EAAS,EAAE,QAAQ,CAAC,CAKlD,IAAI,EACJ,GAAI,CACF,EAAU,MAAM,EAAQ,EAAO,MACzB,CACN,MAAO,EAAE,CAEX,IAAM,EAAoB,EAAE,CAC5B,IAAK,IAAM,KAAQ,EAAS,CAC1B,GAAI,EAAS,IAAI,EAAK,CAAE,SACxB,IAAM,EAAM,EAAQ,EAAQ,EAAK,CACjC,GAAI,CAEF,GAAI,EAAC,MADW,EAAK,EAAI,EAClB,QAAQ,CAAE,SACjB,MAAM,EAAO,EAAI,CACjB,EAAQ,KAAK,EAAK,MACZ,GAOV,OAHI,EAAQ,OAAS,GAAK,CAAC,GACzB,QAAQ,IAAI,yBAAyB,EAAQ,OAAO,kBAAkB,EAAQ,KAAK,KAAK,GAAG,CAEtF"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs-cli v5.4.2
2
+ * @forinda/kickjs-cli v5.4.4
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -9,4 +9,4 @@
9
9
  * @license MIT
10
10
  */
11
11
  function e(e){return e}var t=class extends Error{constructor(e,t,n){super(`Two plugins registered the same ${e} '${t}': ${n.join(`, `)}. Plugins must use unique ${e} names.`),this.name=`KickPluginConflictError`}};export{e as n,t};
12
- //# sourceMappingURL=types-DucsCMzP.mjs.map
12
+ //# sourceMappingURL=types-CjlT7_CZ.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"types-DucsCMzP.mjs","names":[],"sources":["../src/plugin/types.ts"],"sourcesContent":["// CLI Plugin shape.\n//\n// The kick CLI is itself a composition of plugins — every built-in\n// command (init, generate, run, typegen, db, …) ships as a KickCliPlugin\n// internally. Adopters extend the same surface from kick.config.ts to\n// add commands, generators, and typegens; the merging + conflict\n// detection runs the same way for built-ins and user plugins.\n//\n// Four contribution kinds:\n//\n// • commands[] — declarative shell-handler commands (same shape as\n// the existing kick.config.ts `commands` field).\n// • register() — programmatic commander registration. Called with\n// `(program, ctx)` so the callback has cwd + config\n// without re-loading.\n// • typegens[] — TypegenPlugin instances that `kick typegen` runs\n// after the legacy pass.\n// • generators[] — `kick g <name>` scaffolders (defineGenerator).\n// Replaces the `package.json > kickjs.generators`\n// discovery; that path stays as a deprecated\n// fallback for one minor version.\n//\n// Mirrors `definePlugin` / `defineAdapter` factory parity so adopters\n// don't have to learn a new helper-naming convention.\n\nimport type { Command } from 'commander'\n\nimport type { TypegenPlugin } from '../typegen/plugin'\nimport type { KickCommandDefinition, KickConfig } from '../config'\nimport type { GeneratorSpec } from '../generator-extension/define'\n\n/**\n * Runtime context handed to `register()` — saves callbacks from\n * re-loading config or guessing cwd. Forward-compatible: future fields\n * land here without changing the callback signature.\n */\nexport interface KickCliPluginContext {\n cwd: string\n /** Resolved kick.config.ts (null if the project has none). */\n config: KickConfig | null\n log: (msg: string) => void\n}\n\nexport interface KickCliPlugin {\n /** Stable identifier — used in error messages on conflict + de-dup. */\n name: string\n commands?: KickCommandDefinition[]\n /** Programmatic command registration. Called once at CLI startup. */\n register?: (program: Command, ctx: KickCliPluginContext) => void | Promise<void>\n typegens?: TypegenPlugin[]\n generators?: GeneratorSpec[]\n}\n\n/** Identity helper — exists for type inference + parity with definePlugin. */\nexport function defineCliPlugin(p: KickCliPlugin): KickCliPlugin {\n return p\n}\n\nexport class KickPluginConflictError extends Error {\n constructor(kind: 'plugin' | 'command' | 'typegen' | 'generator', id: string, owners: string[]) {\n super(\n `Two plugins registered the same ${kind} '${id}': ${owners.join(', ')}. ` +\n `Plugins must use unique ${kind} names.`,\n )\n this.name = 'KickPluginConflictError'\n }\n}\n"],"mappings":";;;;;;;;;;AAsDA,SAAgB,EAAgB,EAAiC,CAC/D,OAAO,EAGT,IAAa,EAAb,cAA6C,KAAM,CACjD,YAAY,EAAsD,EAAY,EAAkB,CAC9F,MACE,mCAAmC,EAAK,IAAI,EAAG,KAAK,EAAO,KAAK,KAAK,CAAC,4BACzC,EAAK,SACnC,CACD,KAAK,KAAO"}
1
+ {"version":3,"file":"types-CjlT7_CZ.mjs","names":[],"sources":["../src/plugin/types.ts"],"sourcesContent":["// CLI Plugin shape.\n//\n// The kick CLI is itself a composition of plugins — every built-in\n// command (init, generate, run, typegen, db, …) ships as a KickCliPlugin\n// internally. Adopters extend the same surface from kick.config.ts to\n// add commands, generators, and typegens; the merging + conflict\n// detection runs the same way for built-ins and user plugins.\n//\n// Four contribution kinds:\n//\n// • commands[] — declarative shell-handler commands (same shape as\n// the existing kick.config.ts `commands` field).\n// • register() — programmatic commander registration. Called with\n// `(program, ctx)` so the callback has cwd + config\n// without re-loading.\n// • typegens[] — TypegenPlugin instances that `kick typegen` runs\n// after the legacy pass.\n// • generators[] — `kick g <name>` scaffolders (defineGenerator).\n// Replaces the `package.json > kickjs.generators`\n// discovery; that path stays as a deprecated\n// fallback for one minor version.\n//\n// Mirrors `definePlugin` / `defineAdapter` factory parity so adopters\n// don't have to learn a new helper-naming convention.\n\nimport type { Command } from 'commander'\n\nimport type { TypegenPlugin } from '../typegen/plugin'\nimport type { KickCommandDefinition, KickConfig } from '../config'\nimport type { GeneratorSpec } from '../generator-extension/define'\n\n/**\n * Runtime context handed to `register()` — saves callbacks from\n * re-loading config or guessing cwd. Forward-compatible: future fields\n * land here without changing the callback signature.\n */\nexport interface KickCliPluginContext {\n cwd: string\n /** Resolved kick.config.ts (null if the project has none). */\n config: KickConfig | null\n log: (msg: string) => void\n}\n\nexport interface KickCliPlugin {\n /** Stable identifier — used in error messages on conflict + de-dup. */\n name: string\n commands?: KickCommandDefinition[]\n /** Programmatic command registration. Called once at CLI startup. */\n register?: (program: Command, ctx: KickCliPluginContext) => void | Promise<void>\n typegens?: TypegenPlugin[]\n generators?: GeneratorSpec[]\n}\n\n/** Identity helper — exists for type inference + parity with definePlugin. */\nexport function defineCliPlugin(p: KickCliPlugin): KickCliPlugin {\n return p\n}\n\nexport class KickPluginConflictError extends Error {\n constructor(kind: 'plugin' | 'command' | 'typegen' | 'generator', id: string, owners: string[]) {\n super(\n `Two plugins registered the same ${kind} '${id}': ${owners.join(', ')}. ` +\n `Plugins must use unique ${kind} names.`,\n )\n this.name = 'KickPluginConflictError'\n }\n}\n"],"mappings":";;;;;;;;;;AAsDA,SAAgB,EAAgB,EAAiC,CAC/D,OAAO,EAGT,IAAa,EAAb,cAA6C,KAAM,CACjD,YAAY,EAAsD,EAAY,EAAkB,CAC9F,MACE,mCAAmC,EAAK,IAAI,EAAG,KAAK,EAAO,KAAK,KAAK,CAAC,4BACzC,EAAK,SACnC,CACD,KAAK,KAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forinda/kickjs-cli",
3
- "version": "5.4.2",
3
+ "version": "5.4.4",
4
4
  "description": "CLI for KickJS — project scaffolding, DDD module generation, dev/build/start",
5
5
  "keywords": [
6
6
  "kickjs",
@@ -70,7 +70,7 @@
70
70
  "picocolors": "^1.1.1",
71
71
  "pluralize": "^8.0.0",
72
72
  "@forinda/kickjs": "5.5.0",
73
- "@forinda/kickjs-db": "5.4.1"
73
+ "@forinda/kickjs-db": "5.6.0"
74
74
  },
75
75
  "devDependencies": {
76
76
  "@swc/core": "^1.15.33",
@@ -82,7 +82,7 @@
82
82
  "vite": "^8.0.9",
83
83
  "vitest": "^4.1.5",
84
84
  "@forinda/kickjs-ai": "5.2.1",
85
- "@forinda/kickjs-db-pg": "7.0.1"
85
+ "@forinda/kickjs-db-pg": "9.0.0"
86
86
  },
87
87
  "publishConfig": {
88
88
  "access": "public"