@forinda/kickjs-cli 6.2.0 → 6.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-docs-Di96qILM.mjs → agent-docs-BnMTYOiD.mjs} +3 -3
- package/dist/{agent-docs-Di96qILM.mjs.map → agent-docs-BnMTYOiD.mjs.map} +1 -1
- package/dist/{build-C_RWnIv1.mjs → build-DxVQQIQS.mjs} +3 -3
- package/dist/{build-C_RWnIv1.mjs.map → build-DxVQQIQS.mjs.map} +1 -1
- package/dist/{builtins-BdJFdAsP.mjs → builtins-DIyfh8Vq.mjs} +2 -2
- package/dist/cli.mjs +137 -137
- package/dist/{config-G8kmxDyZ.mjs → config-DQFyBrSP.mjs} +3 -3
- package/dist/{config-G8kmxDyZ.mjs.map → config-DQFyBrSP.mjs.map} +1 -1
- package/dist/{doctor-KBy5WFZJ.mjs → doctor-D6Vqf-Ws.mjs} +44 -44
- package/dist/doctor-D6Vqf-Ws.mjs.map +1 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{plugin-BH1mZC2X.mjs → plugin-Dl2YqKay.mjs} +3 -3
- package/dist/{plugin-BH1mZC2X.mjs.map → plugin-Dl2YqKay.mjs.map} +1 -1
- package/dist/{project-docs-DKOLHfqu.mjs → project-docs-CIzEv44w.mjs} +2 -2
- package/dist/{project-docs-DKOLHfqu.mjs.map → project-docs-CIzEv44w.mjs.map} +1 -1
- package/dist/{project-root-DsnEUmMN.mjs → project-root-DYK_xUvF.mjs} +3 -3
- package/dist/{project-root-DsnEUmMN.mjs.map → project-root-DYK_xUvF.mjs.map} +1 -1
- package/dist/{rolldown-runtime-BoqBCbMv.mjs → rolldown-runtime-B13ILhgX.mjs} +1 -1
- package/dist/{run-plugins-Bql3IbaE.mjs → run-plugins-u58MFV45.mjs} +13 -13
- package/dist/{run-plugins-Bql3IbaE.mjs.map → run-plugins-u58MFV45.mjs.map} +1 -1
- package/dist/{typegen-C7AgyPIB.mjs → typegen-CuciH349.mjs} +5 -5
- package/dist/{typegen-C7AgyPIB.mjs.map → typegen-CuciH349.mjs.map} +1 -1
- package/dist/{types-xMa5uCri.mjs → types-zO8PDAjk.mjs} +1 -1
- package/package.json +4 -4
- package/dist/doctor-KBy5WFZJ.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typegen-C7AgyPIB.mjs","names":["walk","toRelative","extractPathParams","extractApiQueryParams"],"sources":["../src/typegen/scanner-cache.ts","../src/typegen/extract-ast.ts","../src/typegen/scanner.ts","../src/typegen/render/manifest.ts","../src/typegen/token-conventions.ts","../src/typegen/asset-types.ts","../src/typegen/index.ts"],"sourcesContent":["/**\n * Persistent per-file extraction cache for the typegen scanner.\n *\n * The scanner's dominant cost on large projects is reading every\n * `src/**\\/*.ts` file and running the regex extractors over each one.\n * On a watch/rebuild loop almost nothing has changed, yet the old\n * `scanProject` re-read and re-scanned the entire tree every time.\n *\n * This cache stores the per-file extraction result (`FileExtract`)\n * keyed by a cheap filesystem signature (`mtimeMs:size`). On the next\n * scan we `stat()` each file — a near-free syscall, no content read —\n * and reuse the cached extract whenever the signature is unchanged.\n * Only genuinely-changed files pay the readFile + regex cost.\n *\n * We deliberately key on `mtimeMs:size` rather than a content hash:\n * hashing requires reading the file, which is exactly the cost we are\n * trying to avoid. The cross-file join phase in `scanProject` always\n * re-runs over the full (cached + fresh) extract set, so a stale entry\n * can never produce an inconsistent `ScanResult` — the worst case of a\n * signature collision (same size, identical mtime, different content)\n * is a missed re-scan, which `--no-cache` / a `clean` sidesteps.\n *\n * @module @forinda/kickjs-cli/typegen/scanner-cache\n */\n\nimport { mkdir, readFile, stat, writeFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\nimport type { FileExtract } from './scanner'\n\n/** Bump when the shape of `FileExtract` (or any extractor) changes. */\n// v2: per-file extraction switched to AST-first (extract-ast.ts) — v1\n// entries hold regex-era results that can differ (template-literal\n// paths, aliased schema-ref resolution), so they must not be served.\nconst CACHE_VERSION = 2\n\n/** The array-valued keys every `FileExtract` must carry. */\nconst EXTRACT_ARRAY_KEYS = [\n 'classes',\n 'tokens',\n 'injects',\n 'pluginsAndAdapters',\n 'augmentations',\n 'contextKeys',\n 'routes',\n 'moduleMounts',\n 'globPatterns',\n] as const\n\n/**\n * Structurally validate a cached extract before trusting it. The join\n * phase spreads these arrays (`...extract.classes`), so a truncated or\n * hand-edited `scan.json` whose entry is missing a field would crash\n * the scanner. Rejecting the entry here lets the cache self-heal — the\n * file is treated as uncached and re-scanned.\n */\nfunction isFileExtract(value: unknown): value is FileExtract {\n if (!value || typeof value !== 'object') return false\n const extract = value as Record<string, unknown>\n return EXTRACT_ARRAY_KEYS.every((key) => Array.isArray(extract[key]))\n}\n\n/** One cached file: its signature plus the extraction it produced. */\ninterface CacheEntry {\n /** `${mtimeMs}:${size}` — cheap change signature, no content read. */\n sig: string\n extract: FileExtract\n}\n\n/** On-disk cache document. */\ninterface CacheDoc {\n version: number\n /** Keyed by absolute file path. */\n files: Record<string, CacheEntry>\n}\n\n/**\n * In-memory + on-disk cache handle. Construct via `loadScanCache`,\n * consult with `get`, populate with `set`, and persist via `save`.\n * Entries for files no longer present on disk are dropped on `save`\n * (the scanner reports every live path through `markSeen`).\n */\nexport class ScanCache {\n private readonly path: string\n private readonly prev: Map<string, CacheEntry>\n private readonly next = new Map<string, FileExtract>()\n private readonly nextSig = new Map<string, string>()\n\n private constructor(path: string, prev: Map<string, CacheEntry>) {\n this.path = path\n this.prev = prev\n }\n\n /**\n * Load the cache for a given cache directory. A missing, unreadable,\n * malformed, or version-mismatched cache yields an empty cache — the\n * scan then behaves exactly like a cold first run.\n */\n static async load(cacheDir: string): Promise<ScanCache> {\n const file = join(cacheDir, 'scan.json')\n const prev = new Map<string, CacheEntry>()\n try {\n const raw = await readFile(file, 'utf-8')\n const doc = JSON.parse(raw) as CacheDoc\n if (doc.version === CACHE_VERSION && doc.files) {\n for (const [path, entry] of Object.entries(doc.files)) {\n if (entry && typeof entry.sig === 'string' && isFileExtract(entry.extract)) {\n prev.set(path, entry)\n }\n }\n }\n } catch {\n // Cold start — empty cache.\n }\n return new ScanCache(file, prev)\n }\n\n /** Compute the `mtimeMs:size` signature for a file, or null if stat fails. */\n static async signature(filePath: string): Promise<string | null> {\n try {\n const s = await stat(filePath)\n return `${s.mtimeMs}:${s.size}`\n } catch {\n return null\n }\n }\n\n /**\n * Return the cached extract for `filePath` iff its stored signature\n * matches `sig`. A hit means the file is byte-identical to last scan\n * (modulo an mtime+size collision) and need not be re-read.\n */\n get(filePath: string, sig: string): FileExtract | null {\n const entry = this.prev.get(filePath)\n return entry && entry.sig === sig ? entry.extract : null\n }\n\n /** Record a fresh (or reused) extract for the next `save`. */\n set(filePath: string, sig: string, extract: FileExtract): void {\n this.next.set(filePath, extract)\n this.nextSig.set(filePath, sig)\n }\n\n /** Every file path present in the loaded (previous) cache. */\n cachedFiles(): string[] {\n return [...this.prev.keys()]\n }\n\n /**\n * Read a previously-cached extract WITHOUT a signature check. Used by\n * the incremental scan, where Vite has already told us precisely which\n * files changed — so unchanged files are trusted as-is, skipping even\n * the `stat()` a full scan would do.\n */\n peek(filePath: string): FileExtract | null {\n return this.prev.get(filePath)?.extract ?? null\n }\n\n /**\n * Carry a previously-cached entry forward into the next `save`,\n * unchanged. Returns false if the file was not in the prior cache.\n */\n carry(filePath: string): boolean {\n const entry = this.prev.get(filePath)\n if (!entry) return false\n this.next.set(filePath, entry.extract)\n this.nextSig.set(filePath, entry.sig)\n return true\n }\n\n /**\n * Persist the cache. Only files passed through `set` this run survive,\n * so entries for deleted files are pruned automatically. Best-effort:\n * a write failure is swallowed (the cache is an optimization, never a\n * correctness dependency).\n */\n async save(): Promise<void> {\n const files: Record<string, CacheEntry> = {}\n for (const [path, extract] of this.next) {\n const sig = this.nextSig.get(path)\n if (sig) files[path] = { sig, extract }\n }\n const doc: CacheDoc = { version: CACHE_VERSION, files }\n try {\n await mkdir(dirname(this.path), { recursive: true })\n await writeFile(this.path, JSON.stringify(doc), 'utf-8')\n } catch {\n // Best-effort.\n }\n }\n}\n","/**\n * AST-based per-file extraction — oxc-parser replacement for the regex\n * extractors in `scanner.ts`.\n *\n * Produces the exact same {@link FileExtract} shape so the scanner's\n * cache / incremental / cross-file-join machinery is untouched. The\n * scanner calls {@link extractFileAst} first and falls back to the\n * regex path when the file doesn't parse (mid-edit syntax errors in\n * watch mode — regex still salvages partial results there).\n *\n * What AST extraction fixes over the regex path (forinda/kick-js#108):\n * - template-literal route paths (`@Get(\\`/v1/users/:id\\`)`) — regex\n * silently fell back to `/`\n * - nested parens/braces inside stacked decorator args\n * - string literals containing `(`/`)`/`{`/`}` skewing the\n * balanced-delimiter walkers\n * - aliased named imports in schema-ref resolution\n *\n * Everything here is pure (depends only on the source text), so results\n * stay safe to cache by filesystem signature.\n */\nimport { relative, sep } from 'node:path'\n\nimport { parseSync } from 'oxc-parser'\n\nimport {\n DECORATOR_NAMES,\n type DecoratorName,\n type DiscoveredAugmentation,\n type DiscoveredClass,\n type DiscoveredContextKey,\n type DiscoveredInject,\n type DiscoveredPluginOrAdapter,\n type DiscoveredRoute,\n type DiscoveredToken,\n type FileExtract,\n type ModuleMount,\n type SchemaRef,\n} from './scanner'\n\n// scanner.ts ⇄ extract-ast.ts is an import cycle (scanner calls\n// extractFileAst; we need its DECORATOR_NAMES). Type imports are erased,\n// but reading the DECORATOR_NAMES *value* during this module's init\n// would hit the TDZ while scanner is still evaluating — so the set is\n// built lazily on first extraction instead.\nlet decoratorNameSet: Set<string> | null = null\nfunction getDecoratorNameSet(): Set<string> {\n decoratorNameSet ??= new Set<string>(DECORATOR_NAMES)\n return decoratorNameSet\n}\n\n// oxc-parser returns ESTree-shaped plain objects. We walk them\n// structurally; a tiny structural node type keeps us honest without\n// pulling in the full @oxc-project/types surface.\ninterface Node {\n type: string\n [key: string]: unknown\n}\n\nconst HTTP_DECORATORS = new Set(['Get', 'Post', 'Put', 'Delete', 'Patch'])\n\nfunction isNode(value: unknown): value is Node {\n return typeof value === 'object' && value !== null && typeof (value as Node).type === 'string'\n}\n\n/** Depth-first walk over every AST node (objects with a string `type`). */\nfunction walk(node: unknown, visit: (node: Node) => void): void {\n if (Array.isArray(node)) {\n for (const item of node) walk(item, visit)\n return\n }\n if (!isNode(node)) return\n visit(node)\n for (const key of Object.keys(node)) {\n if (key === 'type') continue\n const value = node[key]\n if (typeof value === 'object' && value !== null) walk(value, visit)\n }\n}\n\n/** Static string value of a literal / no-substitution template, else null. */\nfunction stringValue(node: unknown): string | null {\n if (!isNode(node)) return null\n if (node.type === 'Literal' && typeof node.value === 'string') return node.value\n if (node.type === 'TemplateLiteral') {\n const quasis = node.quasis as Node[] | undefined\n const expressions = node.expressions as unknown[] | undefined\n if (quasis?.length === 1 && (expressions?.length ?? 0) === 0) {\n const cooked = (quasis[0] as { value?: { cooked?: string } }).value?.cooked\n return typeof cooked === 'string' ? cooked : null\n }\n }\n return null\n}\n\nfunction identifierName(node: unknown): string | null {\n return isNode(node) && node.type === 'Identifier' ? (node.name as string) : null\n}\n\n/** Callee name for `name(...)` calls (plain identifier callees only). */\nfunction calleeName(call: Node): string | null {\n return identifierName(call.callee)\n}\n\n/** Object-literal property lookup by static key name. */\nfunction getProp(obj: Node | null, name: string): Node | null {\n if (!obj || obj.type !== 'ObjectExpression') return null\n for (const prop of (obj.properties as Node[] | undefined) ?? []) {\n if (prop.type !== 'Property') continue\n const key = prop.key as Node\n const keyName =\n identifierName(key) ?? (key.type === 'Literal' ? String(key.value) : stringValue(key))\n if (keyName === name) return prop.value as Node\n }\n return null\n}\n\n/** First argument when it's an object literal, else null. */\nfunction firstObjectArg(call: Node): Node | null {\n const arg = (call.arguments as Node[] | undefined)?.[0]\n return isNode(arg) && arg.type === 'ObjectExpression' ? arg : null\n}\n\nfunction extractStringArrayProp(obj: Node | null, key: string): string[] {\n const value = getProp(obj, key)\n if (!isNode(value) || value.type !== 'ArrayExpression') return []\n const out: string[] = []\n for (const el of (value.elements as unknown[] | undefined) ?? []) {\n const s = stringValue(el)\n if (s !== null) out.push(s)\n }\n return out\n}\n\nfunction toRelative(filePath: string, cwd: string): string {\n return relative(cwd, filePath).split(sep).join('/')\n}\n\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/** Mirrors scanner.ts joinMountPath — slash-edge handling for #235 §3. */\nfunction joinMountPath(mountPath: string, routePath: string): string {\n const prefix = mountPath.endsWith('/') ? mountPath.slice(0, -1) : mountPath\n if (!routePath || routePath === '/') return prefix || '/'\n const suffix = routePath.startsWith('/') ? routePath : '/' + routePath\n return prefix + suffix || '/'\n}\n\n/** `implements` clause includes `name`? */\nfunction classImplements(cls: Node, name: string): boolean {\n for (const impl of (cls.implements as Node[] | undefined) ?? []) {\n const expr = (impl.expression ?? impl) as Node\n if (identifierName(expr) === name) return true\n // Qualified form (`kick.AppModule`) — match the rightmost segment.\n if (expr.type === 'TSQualifiedName' || expr.type === 'MemberExpression') {\n const right = (expr.right ?? expr.property) as Node | undefined\n if (right && identifierName(right) === name) return true\n }\n }\n return false\n}\n\n/** Decorators attached to a class / method / property / param node. */\nfunction decoratorsOf(node: Node): Node[] {\n return (node.decorators as Node[] | undefined) ?? []\n}\n\n/** `@Name(...)` decorator → { name, call } (call-expression form only). */\nfunction decoratorCall(dec: Node): { name: string; call: Node } | null {\n const expr = dec.expression as Node\n if (!isNode(expr) || expr.type !== 'CallExpression') return null\n const name = calleeName(expr)\n return name ? { name, call: expr } : null\n}\n\ninterface ImportBinding {\n source: string\n}\n\n/**\n * Per-file context shared by the extract passes: import map (local\n * binding name → module specifier) and the set of top-level const names\n * (same-file schema refs resolve to the `''` sentinel).\n */\ninterface FileContext {\n imports: Map<string, ImportBinding>\n topLevelConsts: Set<string>\n}\n\nfunction buildFileContext(program: Node): FileContext {\n const imports = new Map<string, ImportBinding>()\n const topLevelConsts = new Set<string>()\n for (const stmt of (program.body as Node[] | undefined) ?? []) {\n if (stmt.type === 'ImportDeclaration') {\n const source = stringValue(stmt.source) ?? ''\n for (const spec of (stmt.specifiers as Node[] | undefined) ?? []) {\n const local = identifierName(spec.local)\n if (local) imports.set(local, { source })\n }\n continue\n }\n // `const X = ...` / `export const X = ...` at top level\n const decl =\n stmt.type === 'VariableDeclaration'\n ? stmt\n : stmt.type === 'ExportNamedDeclaration' && isNode(stmt.declaration)\n ? (stmt.declaration as Node)\n : null\n if (isNode(decl) && decl.type === 'VariableDeclaration') {\n for (const d of (decl.declarations as Node[] | undefined) ?? []) {\n const name = identifierName(d.id)\n if (name) topLevelConsts.add(name)\n }\n }\n }\n return { imports, topLevelConsts }\n}\n\n/**\n * Mirror of scanner.ts `resolveImportSource` semantics on top of the\n * AST import map: imported → module specifier, same-file const → `''`\n * sentinel, otherwise null.\n */\nfunction resolveSchemaRef(identifier: string, ctx: FileContext): SchemaRef {\n const imported = ctx.imports.get(identifier)\n if (imported) return { identifier, source: imported.source }\n if (ctx.topLevelConsts.has(identifier)) return { identifier, source: '' }\n return { identifier, source: null }\n}\n\n/** Schema field (`body:` / `query:` / `params:`) — bare identifiers only. */\nfunction schemaFieldRef(options: Node | null, field: string, ctx: FileContext): SchemaRef | null {\n const value = getProp(options, field)\n const name = identifierName(value)\n return name ? resolveSchemaRef(name, ctx) : null\n}\n\ninterface QueryParamsConfig {\n filterable: string[]\n sortable: string[]\n searchable: string[]\n}\n\n/**\n * `@ApiQueryParams(...)` on a method — inline object literal or a\n * same-file const reference. Present-but-opaque (imported config,\n * column-object map, function call) → empty arrays, mirroring the\n * regex path's \"decorator exists but nothing statically extractable\".\n */\nfunction extractApiQueryParams(\n methodDecorators: Node[],\n topLevelConstInits: Map<string, Node>,\n): QueryParamsConfig | null {\n for (const dec of methodDecorators) {\n const dc = decoratorCall(dec)\n if (!dc || dc.name !== 'ApiQueryParams') continue\n const arg = (dc.call.arguments as Node[] | undefined)?.[0]\n let obj: Node | null = null\n if (isNode(arg) && arg.type === 'ObjectExpression') {\n obj = arg\n } else {\n const refName = identifierName(arg)\n if (refName) {\n const init = topLevelConstInits.get(refName)\n if (init && init.type === 'ObjectExpression') obj = init\n }\n }\n return {\n filterable: extractStringArrayProp(obj, 'filterable'),\n sortable: extractStringArrayProp(obj, 'sortable'),\n searchable: extractStringArrayProp(obj, 'searchable'),\n }\n }\n return null\n}\n\n/**\n * Parse + extract one source file. Returns `null` when oxc reports\n * parse errors — the caller falls back to the regex extractors, which\n * tolerate broken mid-edit sources by matching whatever still looks\n * right.\n */\nexport function extractFileAst(source: string, filePath: string, cwd: string): FileExtract | null {\n let program: Node\n try {\n const result = parseSync(filePath, source)\n if (result.errors.length > 0) return null\n program = result.program as unknown as Node\n } catch {\n return null\n }\n\n const relPath = toRelative(filePath, cwd)\n const ctx = buildFileContext(program)\n\n const classes: DiscoveredClass[] = []\n const tokens: DiscoveredToken[] = []\n const injects: DiscoveredInject[] = []\n const pluginsAndAdapters: DiscoveredPluginOrAdapter[] = []\n const augmentations: DiscoveredAugmentation[] = []\n const contextKeys: DiscoveredContextKey[] = []\n const routes: DiscoveredRoute[] = []\n const moduleMounts: ModuleMount[] = []\n const globPatterns: string[] = []\n\n const seenHelperNames = new Set<string>()\n const seenContextKeys = new Set<string>()\n const seenTokenNodes = new Set<Node>()\n /** Top-level `const X = <init>` map for @ApiQueryParams const refs. */\n const topLevelConstInits = new Map<string, Node>()\n\n for (const stmt of (program.body as Node[] | undefined) ?? []) {\n const decl =\n stmt.type === 'VariableDeclaration'\n ? stmt\n : stmt.type === 'ExportNamedDeclaration' && isNode(stmt.declaration)\n ? (stmt.declaration as Node)\n : null\n if (isNode(decl) && decl.type === 'VariableDeclaration') {\n for (const d of (decl.declarations as Node[] | undefined) ?? []) {\n const name = identifierName(d.id)\n if (name && isNode(d.init)) topLevelConstInits.set(name, d.init as Node)\n }\n }\n }\n\n // ── Pass 1: top-level statements — classes + defineModule consts ──────\n const exportedClasses: Array<{ cls: Node; isDefault: boolean }> = []\n for (const stmt of (program.body as Node[] | undefined) ?? []) {\n if (stmt.type === 'ExportNamedDeclaration' && isNode(stmt.declaration)) {\n const d = stmt.declaration as Node\n if (d.type === 'ClassDeclaration') exportedClasses.push({ cls: d, isDefault: false })\n } else if (stmt.type === 'ExportDefaultDeclaration' && isNode(stmt.declaration)) {\n const d = stmt.declaration as Node\n if (d.type === 'ClassDeclaration') exportedClasses.push({ cls: d, isDefault: true })\n }\n }\n\n for (const { cls, isDefault } of exportedClasses) {\n const className = identifierName(cls.id)\n if (!className) continue\n\n // First call-form decorator whose name is in DECORATOR_NAMES — same\n // pick as the regex (first in the stack wins).\n let tagged: DecoratorName | null = null\n for (const dec of decoratorsOf(cls)) {\n const dc = decoratorCall(dec)\n if (dc && getDecoratorNameSet().has(dc.name)) {\n tagged = dc.name as DecoratorName\n break\n }\n }\n if (tagged) {\n classes.push({ className, decorator: tagged, filePath, relativePath: relPath, isDefault })\n } else if (classImplements(cls, 'AppModule')) {\n classes.push({\n className,\n decorator: 'Module',\n filePath,\n relativePath: relPath,\n isDefault,\n })\n }\n }\n\n // v4 factory modules: `export const XModule = defineModule({...})`\n for (const stmt of (program.body as Node[] | undefined) ?? []) {\n if (stmt.type !== 'ExportNamedDeclaration' || !isNode(stmt.declaration)) continue\n const d = stmt.declaration as Node\n if (d.type !== 'VariableDeclaration') continue\n for (const declarator of (d.declarations as Node[] | undefined) ?? []) {\n const name = identifierName(declarator.id)\n const init = declarator.init as Node | undefined\n if (!name || !isNode(init) || init.type !== 'CallExpression') continue\n if (calleeName(init) !== 'defineModule') continue\n if (classes.some((c) => c.className === name)) continue\n classes.push({\n className: name,\n decorator: 'Module',\n filePath,\n relativePath: relPath,\n isDefault: false,\n })\n }\n }\n\n // ── Pass 2: whole-tree walk — tokens, injects, helpers, globs ─────────\n walk(program, (node) => {\n // createToken('name') — const-bound (variable) or bare (null)\n if (node.type === 'VariableDeclarator') {\n const init = node.init as Node | undefined\n if (isNode(init) && init.type === 'CallExpression' && calleeName(init) === 'createToken') {\n const name = stringValue((init.arguments as Node[] | undefined)?.[0])\n if (name !== null) {\n seenTokenNodes.add(init)\n tokens.push({\n name,\n variable: identifierName(node.id),\n filePath,\n relativePath: relPath,\n })\n }\n }\n return\n }\n\n if (node.type !== 'CallExpression') {\n // import.meta.glob handled below (CallExpression); decorators are\n // reached through their owning class/method nodes, but @Inject can\n // appear on constructor params too — catch every decorator node.\n if (node.type === 'Decorator') {\n const dc = decoratorCall(node)\n if (dc?.name === 'Inject') {\n const lit = stringValue((dc.call.arguments as Node[] | undefined)?.[0])\n if (lit !== null) injects.push({ name: lit, filePath, relativePath: relPath })\n }\n }\n return\n }\n\n const callee = node.callee as Node\n const name = calleeName(node)\n\n // Bare createToken('name') not consumed by the declarator pass\n if (name === 'createToken' && !seenTokenNodes.has(node)) {\n const tokenName = stringValue((node.arguments as Node[] | undefined)?.[0])\n if (tokenName !== null) {\n tokens.push({ name: tokenName, variable: null, filePath, relativePath: relPath })\n }\n return\n }\n\n // defineAdapter({ name }) / definePlugin({ name })\n if (name === 'defineAdapter' || name === 'definePlugin') {\n const literal = stringValue(getProp(firstObjectArg(node), 'name'))\n if (literal !== null) {\n const kind = name === 'definePlugin' ? 'plugin' : 'adapter'\n const dedupeKey = `${name}::${literal}::${filePath}`\n if (!seenHelperNames.has(dedupeKey)) {\n seenHelperNames.add(dedupeKey)\n pluginsAndAdapters.push({ kind, name: literal, filePath, relativePath: relPath })\n }\n }\n return\n }\n\n // defineAugmentation('Name', { description, example })\n if (name === 'defineAugmentation') {\n const args = (node.arguments as Node[] | undefined) ?? []\n const augName = stringValue(args[0])\n if (augName !== null) {\n const meta = isNode(args[1]) && args[1].type === 'ObjectExpression' ? args[1] : null\n augmentations.push({\n name: augName,\n description: stringValue(getProp(meta, 'description')),\n example: stringValue(getProp(meta, 'example')),\n filePath,\n relativePath: relPath,\n })\n }\n return\n }\n\n // defineContextDecorator({ key }) / defineHttpContextDecorator({ key })\n // — direct form\n if (name === 'defineContextDecorator' || name === 'defineHttpContextDecorator') {\n const key = stringValue(getProp(firstObjectArg(node), 'key'))\n if (key !== null && !seenContextKeys.has(key)) {\n seenContextKeys.add(key)\n contextKeys.push({ key, filePath, relativePath: relPath })\n }\n return\n }\n\n // Curried form: define(Http)ContextDecorator.withParams<P>()({ key })\n // — outer call's callee is the inner `withParams()` call.\n if (isNode(callee) && callee.type === 'CallExpression') {\n const innerCallee = callee.callee as Node\n if (\n isNode(innerCallee) &&\n innerCallee.type === 'MemberExpression' &&\n identifierName(innerCallee.property) === 'withParams'\n ) {\n const base = identifierName(innerCallee.object)\n if (base === 'defineContextDecorator' || base === 'defineHttpContextDecorator') {\n const key = stringValue(getProp(firstObjectArg(node), 'key'))\n if (key !== null && !seenContextKeys.has(key)) {\n seenContextKeys.add(key)\n contextKeys.push({ key, filePath, relativePath: relPath })\n }\n }\n }\n return\n }\n\n // import.meta.glob('...' | [...]) — module files only (caller gates)\n if (\n isNode(callee) &&\n callee.type === 'MemberExpression' &&\n identifierName(callee.property) === 'glob'\n ) {\n const obj = callee.object as Node\n if (isNode(obj) && obj.type === 'MetaProperty') {\n // Flatten every string literal across the args — matches the\n // regex behaviour (single string + array forms, options object\n // strings included only if literal; in practice options carry\n // booleans).\n walk(node.arguments, (argNode) => {\n const s = stringValue(argNode)\n if (s !== null) globPatterns.push(s)\n })\n }\n }\n })\n\n // ── Pass 3: class bodies — routes + class-style adapters ─────────────\n const allClasses: Array<{ cls: Node; className: string }> = []\n walk(program, (node) => {\n if (node.type === 'ClassDeclaration' || node.type === 'ClassExpression') {\n const className = identifierName(node.id)\n if (className) allClasses.push({ cls: node, className })\n }\n })\n\n for (const { cls, className } of allClasses) {\n const discovered = classes.find((c) => c.className === className)\n const body = (cls.body as Node | undefined)?.body as Node[] | undefined\n\n // Class-style adapters: `class X implements AppAdapter { name = '...' }`\n if (classImplements(cls, 'AppAdapter')) {\n for (const member of body ?? []) {\n if (member.type !== 'PropertyDefinition') continue\n if (identifierName(member.key) !== 'name') continue\n const literal = stringValue(member.value)\n if (literal === null) continue\n const dedupeKey = `class::${literal}::${filePath}`\n if (!seenHelperNames.has(dedupeKey)) {\n seenHelperNames.add(dedupeKey)\n pluginsAndAdapters.push({\n kind: 'adapter',\n name: literal,\n filePath,\n relativePath: relPath,\n })\n }\n break\n }\n }\n\n for (const member of body ?? []) {\n if (member.type !== 'MethodDefinition') continue\n const methodName = identifierName(member.key)\n if (!methodName) continue\n\n // Module mounts: `routes() { ... }` bodies — zip path/controller\n // pairs in source order (works for the class form; the object-\n // method form inside defineModule(build) is handled below).\n if (methodName === 'routes') {\n collectMounts(member.value as Node, moduleMounts)\n continue\n }\n\n // Route handlers — every HTTP decorator on the method emits one\n // route (stacked verbs = multiple routes, same as the regex).\n if (!discovered) continue\n const decs = decoratorsOf(member)\n const apiQp = extractApiQueryParams(decs, topLevelConstInits)\n for (const dec of decs) {\n const dc = decoratorCall(dec)\n if (!dc || !HTTP_DECORATORS.has(dc.name)) continue\n const args = (dc.call.arguments as Node[] | undefined) ?? []\n const rawPath = stringValue(args[0])\n const path = rawPath && rawPath.length > 0 ? rawPath : '/'\n const options = isNode(args[1]) && args[1].type === 'ObjectExpression' ? args[1] : null\n\n routes.push({\n controller: className,\n method: methodName,\n httpMethod: dc.name.toUpperCase() as DiscoveredRoute['httpMethod'],\n path,\n // extractFile contract: own-path params only — the cross-file\n // join re-applies mount prefixes (scanner.ts joinExtracts).\n pathParams: extractPathParams(path),\n queryFilterable: apiQp?.filterable ?? null,\n querySortable: apiQp?.sortable ?? null,\n querySearchable: apiQp?.searchable ?? null,\n bodySchema: schemaFieldRef(options, 'body', ctx),\n querySchema: schemaFieldRef(options, 'query', ctx),\n paramsSchema: schemaFieldRef(options, 'params', ctx),\n filePath,\n relativePath: relPath,\n })\n }\n }\n }\n\n // Object-method `routes()` (defineModule build objects and friends).\n walk(program, (node) => {\n if (node.type !== 'Property' || identifierName(node.key) !== 'routes') return\n const value = node.value as Node\n if (\n isNode(value) &&\n (value.type === 'FunctionExpression' || value.type === 'ArrowFunctionExpression')\n ) {\n collectMounts(value, moduleMounts)\n }\n })\n\n return {\n classes,\n tokens,\n injects,\n pluginsAndAdapters,\n augmentations,\n contextKeys,\n routes,\n moduleMounts,\n globPatterns: /\\.module\\.[mc]?[tj]sx?$/.test(filePath) ? globPatterns : [],\n }\n}\n\n/**\n * Collect `{ path: '...', controller: Ident }` pairs from a routes()\n * function body — zipped in traversal order, shorter list wins, same\n * as the regex `extractModuleMounts`.\n */\nfunction collectMounts(fn: Node, out: ModuleMount[]): void {\n const paths: string[] = []\n const controllers: string[] = []\n walk(fn.body, (node) => {\n if (node.type !== 'Property') return\n const key = identifierName(node.key)\n if (key === 'path') {\n const s = stringValue(node.value)\n if (s !== null) paths.push(s)\n } else if (key === 'controller') {\n const id = identifierName(node.value)\n if (id && /^[A-Z]/.test(id)) controllers.push(id)\n }\n })\n const n = Math.min(paths.length, controllers.length)\n for (let i = 0; i < n; i++) {\n out.push({ controller: controllers[i], mountPath: paths[i] })\n }\n}\n\n/**\n * `joinExtracts` re-derives `pathParams` with the mount prefix — the\n * scanner needs the same joiner the regex path used; re-exported here\n * so both implementations share one definition is unnecessary (the\n * scanner keeps its own). This export exists for tests only.\n */\nexport const __testing = { joinMountPath, extractPathParams }\n","/**\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'\nimport { ScanCache } from './scanner-cache'\nimport { extractFileAst } from './extract-ast'\n\n/** Decorators that mark a class as DI-managed */\nexport const DECORATOR_NAMES = [\n 'Service',\n 'Controller',\n 'Repository',\n 'Injectable',\n 'Component',\n 'Module',\n] as const\n\nexport type DecoratorName = (typeof DECORATOR_NAMES)[number]\n\n/** A single discovered decorated class */\nexport interface DiscoveredClass {\n /** Class name (e.g., 'UserService') */\n className: string\n /** Decorator that marked it (e.g., 'Service') */\n decorator: DecoratorName\n /** Absolute file path */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n /** True if exported as `default` */\n isDefault: boolean\n}\n\n/** A single route handler discovered on a controller class */\nexport interface DiscoveredRoute {\n /** Owning controller class name (e.g. 'UserController') */\n controller: string\n /** Handler method name on the controller (e.g. 'getUser') */\n method: string\n /** HTTP verb (uppercase) */\n httpMethod: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'\n /** Route path including parameter placeholders (e.g. '/:id/posts/:postId') */\n path: string\n /** URL path parameter names extracted from `:placeholder` segments */\n pathParams: string[]\n /**\n * Whitelisted query field names extracted from `@ApiQueryParams({...})`.\n * `null` means no `@ApiQueryParams` was found on this method (so the\n * generator emits an unconstrained `query` shape). An empty array means\n * the decorator existed but no fields could be statically extracted\n * (e.g. an opaque imported config).\n */\n queryFilterable: string[] | null\n querySortable: string[] | null\n querySearchable: string[] | null\n /**\n * Schema identifiers referenced from the route decorator's second arg\n * (e.g. `@Post('/', { body: createTaskSchema })`). `null` means no\n * such reference; the value carries the identifier and the resolved\n * import source (relative module path) if known.\n */\n bodySchema: SchemaRef | null\n querySchema: SchemaRef | null\n paramsSchema: SchemaRef | null\n /** Absolute file path of the controller */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/** A statically-resolved schema identifier reference */\nexport interface SchemaRef {\n /** The identifier as written (e.g. `createTaskSchema`) */\n identifier: string\n /**\n * Resolved module specifier (relative path or bare module name) where\n * the identifier is defined. `null` means the source could not be\n * statically determined (the generator falls back to `unknown`).\n */\n source: string | null\n}\n\n/** A `createToken<T>('name')` call discovered in source */\nexport interface DiscoveredToken {\n /** The literal string passed to `createToken()` */\n name: string\n /** The const variable name on the LHS, if any */\n variable: string | null\n /** Absolute file path */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/** An `@Inject('literal')` call discovered in source */\nexport interface DiscoveredInject {\n /** The literal string passed to `@Inject()` */\n name: string\n /** Absolute file path */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/** A name collision — same class name in two or more files */\nexport interface ClassCollision {\n /** The colliding class name */\n className: string\n /** All files declaring the class */\n classes: DiscoveredClass[]\n}\n\n/**\n * Information about a discovered env schema file. The typegen\n * generator uses this to emit a `KickEnv` + `NodeJS.ProcessEnv`\n * augmentation that flows through to `@Value` and `process.env`.\n *\n * `null` means no env file was found at the configured location.\n */\nexport interface DiscoveredEnv {\n /** Absolute path to the env schema file */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/**\n * A plugin or adapter discovered in source — either via `defineAdapter({ name })`\n * / `definePlugin({ name })` calls, or via a class that `implements AppAdapter`\n * and declares a string-literal `name` field.\n *\n * The `name` here is the literal string passed to the framework (the value\n * `dependsOn` references), NOT the symbol on the LHS. `defineAdapter` lets\n * authors choose any name they want; the symbol is irrelevant at runtime.\n */\nexport interface DiscoveredPluginOrAdapter {\n /** Whether this is a plugin (`definePlugin`) or adapter (`defineAdapter` / class) */\n kind: 'plugin' | 'adapter'\n /** The string literal passed as `name` (the value `dependsOn` references) */\n name: string\n /** Absolute file path */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/**\n * A context key discovered from a `defineContextDecorator({ key })` or\n * `defineHttpContextDecorator({ key })` call (including the curried\n * `.withParams<P>()({ key })` form). Feeds the `kick/context` typegen\n * plugin, which emits the `ContextKeys` augmentation so `dependsOn`\n * typo-checking is automatic and complete.\n */\nexport interface DiscoveredContextKey {\n /** The literal `key:` value the contributor writes. */\n key: string\n /** Absolute file path. */\n filePath: string\n /** Path relative to scan root, with forward slashes. */\n relativePath: string\n}\n\n/**\n * A `defineAugmentation('Name', meta)` call discovered in source. Plugins\n * call this to advertise an augmentable interface so the typegen can list\n * every augmentation surface in one generated file.\n */\nexport interface DiscoveredAugmentation {\n /** The literal string passed as the first arg to `defineAugmentation` */\n name: string\n /** Optional `description` extracted from the second-arg object literal */\n description: string | null\n /** Optional `example` extracted from the second-arg object literal */\n example: string | null\n /** Absolute file path */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/**\n * A decorated class whose file sits inside a module directory but\n * isn't picked up by any of the module's `import.meta.glob(...)`\n * patterns. Surfaced as a typegen warning per forinda/kick-js#235 §4\n * so adopters notice silent registration drift before it bites them\n * at runtime with a `MissingContributorError` or wrong code path.\n */\nexport interface OrphanedClass {\n /** The decorated class name */\n className: string\n /** Absolute path of the class file */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n /** Absolute path of the module file whose globs didn't match */\n moduleFilePath: string\n /** The decorator name (`Service`, `Controller`, `Repository`, …) */\n decorator: DecoratorName\n}\n\n/** Aggregated scanner output */\nexport interface ScanResult {\n classes: DiscoveredClass[]\n routes: DiscoveredRoute[]\n tokens: DiscoveredToken[]\n injects: DiscoveredInject[]\n collisions: ClassCollision[]\n /** Discovered env schema file (or null if none found at the configured path) */\n env: DiscoveredEnv | null\n /** Plugins/adapters discovered via `defineAdapter`/`definePlugin`/`implements AppAdapter` */\n pluginsAndAdapters: DiscoveredPluginOrAdapter[]\n /** Augmentation interfaces declared via `defineAugmentation('Name', meta)` */\n augmentations: DiscoveredAugmentation[]\n /** Context keys from `define(Http)ContextDecorator({ key })` calls */\n contextKeys: DiscoveredContextKey[]\n /**\n * Decorated classes that sit inside a module directory but aren't\n * picked up by any of the module's `import.meta.glob(...)` patterns.\n * Empty when every decorator file is matched. forinda/kick-js#235 §4.\n */\n orphanedClasses: OrphanedClass[]\n}\n\n/** Options for the scanner */\nexport interface ScanOptions {\n /** Root directory to scan (e.g., absolute path to `src`) */\n root: string\n /** Project root used to compute relative paths (e.g., process.cwd()) */\n cwd: string\n /** Glob-like extensions to scan */\n extensions?: string[]\n /** Substrings that exclude a path (matched against relative path) */\n exclude?: string[]\n /**\n * Path to the env schema file, relative to `cwd`. Defaults to\n * `'src/env.ts'`. The file must contain a `defineEnv(...)` call\n * with a default export for the typegen to emit a typed `KickEnv`\n * augmentation. If the file does not exist or doesn't match the\n * expected shape, env typing is skipped silently.\n */\n envFile?: string\n /**\n * Directory for the persistent per-file extraction cache. When set,\n * unchanged files (matched by `mtimeMs:size` signature) are served\n * from `<cacheDir>/scan.json` instead of being re-read and re-scanned.\n * Omit to disable caching (every scan is a cold read — the original\n * behaviour). Typically `<cwd>/.kickjs/cache`.\n */\n cacheDir?: 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 the v4 module factory form\n * `export const XModule = defineModule({ ... })` (the `class implements\n * AppModule` form above is the deprecated v3 style). The const name is\n * the module's identity and becomes its `ModuleToken` entry. An\n * optional generic / type annotation on the const is tolerated.\n */\nconst DEFINE_MODULE_REGEX =\n /export\\s+const\\s+(\\w+)\\s*(?::\\s*[^=]+)?=\\s*defineModule\\s*(?:<[^>]*>)?\\s*\\(/g\n\n/**\n * Match a `createToken<T>('name')` call with optional `export const X =`\n * or `const X =` prefix. Tolerates whitespace and the type parameter\n * being absent (`createToken('name')`).\n */\nconst CREATE_TOKEN_REGEX =\n /(?:export\\s+)?const\\s+(\\w+)\\s*(?::\\s*[^=]+)?=\\s*createToken\\s*(?:<[^>]*>)?\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]\\s*\\)/g\n\n/**\n * Match a bare `createToken<T>('name')` call (no const assignment) so\n * we still pick up dynamically-used tokens.\n */\nconst BARE_CREATE_TOKEN_REGEX = /createToken\\s*(?:<[^>]*>)?\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]\\s*\\)/g\n\n/** Match `@Inject('literal')` — only literals; computed args are skipped */\nconst INJECT_LITERAL_REGEX = /@Inject\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]\\s*\\)/g\n\n/**\n * Match the start of a `defineAdapter(...)` or `definePlugin(...)` call,\n * tolerating optional `<TConfig, TExtra>` generics. Captures the helper\n * name. The callsite's first-arg object is parsed forward via\n * `findBalancedClose` so nested objects/parens don't confuse us.\n */\nconst DEFINE_HELPER_START = /\\b(defineAdapter|definePlugin)\\s*(?:<[^>]*>)?\\s*\\(/g\n\n/**\n * Match the start of a `defineContextDecorator(...)` /\n * `defineHttpContextDecorator(...)` call up to the `(` that opens the\n * spec object — tolerating optional `<...>` generics AND the curried\n * `.withParams<P>()(...)` form (the empty `()` is consumed so the next\n * `(` is the spec). The spec's `key:` literal is then read forward via\n * `findBalancedClose`, mirroring `DEFINE_HELPER_START`.\n */\n// `(?:<(?:[^<>]|<[^<>]*>)*>)?` tolerates one level of nested generics\n// (e.g. `defineHttpContextDecorator<'tenant', Record<string, never>>`),\n// which a flat `<[^>]*>` would truncate at the inner `>`.\nconst CONTEXT_DECORATOR_START =\n /\\b(?:defineContextDecorator|defineHttpContextDecorator)\\s*(?:\\.withParams\\s*<(?:[^<>]|<[^<>]*>)*>\\s*\\(\\s*\\))?\\s*(?:<(?:[^<>]|<[^<>]*>)*>)?\\s*\\(/g\n\n/**\n * Match a class declaration whose `implements` clause includes `AppAdapter`.\n * Captures the class name. Used to pick up the (rare, post-defineAdapter)\n * legacy class-style adapters so their literal `name = '...'` field can\n * still feed `KickJsPluginRegistry`.\n */\nconst APP_ADAPTER_CLASS_REGEX = new RegExp(\n String.raw`export\\s+(?:default\\s+)?(?:abstract\\s+)?class\\s+(\\w+)` +\n String.raw`(?:\\s+extends\\s+\\w+(?:<[^>]*>)?)?` +\n String.raw`\\s+implements\\s+[^{]*\\bAppAdapter\\b`,\n 'g',\n)\n\n/** Match a string-literal `name = '...'` field on a class body. */\nconst CLASS_NAME_FIELD_REGEX = /\\bname\\s*(?::\\s*[^=]+)?=\\s*['\"`]([^'\"`]+)['\"`]/\n\n/**\n * Match the start of a `defineAugmentation('Name', ...)` call. Captures\n * the literal name. The optional second-arg object is parsed forward so\n * `description` / `example` can be pulled out.\n */\nconst DEFINE_AUGMENTATION_START = /\\bdefineAugmentation\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]\\s*(,\\s*\\{)?/g\n\n/** HTTP route decorator names recognised by the scanner */\nconst HTTP_DECORATORS = ['Get', 'Post', 'Put', 'Delete', 'Patch'] as const\n\n/**\n * Locate the start of a route decorator: `@Get(`, `@Post(`, etc.\n * Used by `extractRoutesFromSource`; the rest of the route declaration\n * (balanced parens, stacked decorators, method name) is parsed by walking\n * the source forward from this match. The previous all-in-one regex\n * couldn't handle nested parens in stacked decorator args (e.g.\n * `@ApiResponse(201, { schema: z.object({ id: z.string() }) })`) — see\n * forinda/kick-js#108.\n */\nconst ROUTE_DECORATOR_START = new RegExp(String.raw`@(${HTTP_DECORATORS.join('|')})\\s*\\(`, 'g')\n\n/**\n * Find the index of the `)` that balances the `(` at `openPos`.\n * Returns -1 if no matching `)` exists. Counts balanced parens only;\n * does not understand string literals, so a `(` or `)` inside a string\n * inside the args will skew the depth counter (matches the limitation\n * of `extractRouteOptionsArg`).\n */\nfunction findBalancedClose(text: string, openPos: number): number {\n let depth = 1\n for (let i = openPos + 1; i < text.length; i++) {\n const ch = text[i]\n if (ch === '(') depth++\n else if (ch === ')') {\n depth--\n if (depth === 0) return i\n }\n }\n return -1\n}\n\n/**\n * Walk forward from the end of a route decorator past any stacked\n * decorators (`@ApiOperation(...)`, `@ApiResponse(...)`, `@Middleware(fn)`,\n * etc.), then past optional `public`/`private`/`protected` and `async`,\n * and capture the method name + opening `(`.\n *\n * Returns the method name and the position immediately after the method's\n * opening `(`, or `null` if the source between the route decorator and\n * the method body doesn't fit the expected shape.\n */\nfunction readMethodAfterDecorators(\n block: string,\n startPos: number,\n): { methodName: string; endPos: number } | null {\n let pos = startPos\n // Stacked decorators: @PascalCase optionally followed by balanced (...)\n while (pos < block.length) {\n while (pos < block.length && /\\s/.test(block[pos])) pos++\n if (block[pos] !== '@') break\n const decMatch = block.slice(pos).match(/^@([A-Z]\\w*)/)\n if (!decMatch) break\n pos += decMatch[0].length\n while (pos < block.length && /\\s/.test(block[pos])) pos++\n if (block[pos] === '(') {\n const close = findBalancedClose(block, pos)\n if (close < 0) return null\n pos = close + 1\n }\n }\n // Modifiers + async\n while (pos < block.length && /\\s/.test(block[pos])) pos++\n for (const mod of ['public', 'private', 'protected'] as const) {\n if (block.slice(pos, pos + mod.length) === mod && /\\s/.test(block.charAt(pos + mod.length))) {\n pos += mod.length\n while (pos < block.length && /\\s/.test(block[pos])) pos++\n break\n }\n }\n if (block.slice(pos, pos + 5) === 'async' && /\\s/.test(block.charAt(pos + 5))) {\n pos += 5\n while (pos < block.length && /\\s/.test(block[pos])) pos++\n }\n // Method name + `(`\n const methodMatch = block.slice(pos).match(/^([a-zA-Z_]\\w*)\\s*\\(/)\n if (!methodMatch) return null\n return { methodName: methodMatch[1], endPos: pos + methodMatch[0].length }\n}\n\n/** Extract `:placeholder` segments from an Express route path */\nfunction extractPathParams(path: string): string[] {\n const matches = path.match(/:([a-zA-Z_]\\w*)/g) ?? []\n return matches.map((m) => m.slice(1))\n}\n\n/**\n * Join a controller's mount-prefix path with a per-route path.\n * Handles the slash edge cases so `'/orgs/:id'` + `'/'` becomes\n * `'/orgs/:id'` (no trailing slash) and `'/orgs/:id'` + `'/:code'`\n * becomes `'/orgs/:id/:code'`. forinda/kick-js#235 §3.\n */\nfunction joinMountPath(mountPath: string, routePath: string): string {\n const prefix = mountPath.endsWith('/') ? mountPath.slice(0, -1) : mountPath\n if (!routePath || routePath === '/') return prefix || '/'\n const suffix = routePath.startsWith('/') ? routePath : '/' + routePath\n return prefix + suffix || '/'\n}\n\n/**\n * Match the `routes()` method body on a class implementing `AppModule`.\n * Captures the body region so we can scan it for `path:` + `controller:`\n * pairs. Tolerates `routes(): ModuleRoutes` and stripped return type.\n */\nconst ROUTES_METHOD_START =\n /\\b(?:public\\s+|private\\s+|protected\\s+)?routes\\s*\\([^)]*\\)\\s*(?::\\s*[A-Za-z_][\\w<>[\\]\\s,|]*\\s*)?\\{/g\n\n/**\n * Match `path: '/...'` inside a routes-method body. Picks up both\n * single-quoted and double-quoted / template literal forms.\n */\nconst PATH_FIELD_REGEX = /\\bpath\\s*:\\s*['\"`]([^'\"`]*)['\"`]/g\n\n/** Match `controller: SomeController` (bare identifier only). */\nconst CONTROLLER_FIELD_REGEX = /\\bcontroller\\s*:\\s*([A-Z]\\w*)\\b/g\n\n/**\n * Match the start of an `import.meta.glob(...)` call. The first arg\n * (string or string array) gets parsed forward via balanced-paren\n * walking to handle whitespace + line breaks inside the array.\n * forinda/kick-js#235 §4.\n */\nconst IMPORT_META_GLOB_START = /\\bimport\\.meta\\.glob\\s*\\(/g\n\n/**\n * Extract every glob pattern from `import.meta.glob([...patterns], ...)` calls\n * in a module file. Single-string form (`import.meta.glob('./**\\\\/*.ts')`) and\n * array form both supported. Negation patterns (`!./**\\\\/*.test.ts`)\n * are returned with the leading `!` preserved so the caller can apply\n * exclusion logic.\n */\nexport function extractGlobPatterns(source: string): string[] {\n const patterns: string[] = []\n IMPORT_META_GLOB_START.lastIndex = 0\n while (IMPORT_META_GLOB_START.exec(source) !== null) {\n const openParen = IMPORT_META_GLOB_START.lastIndex - 1\n const closeParen = findBalancedClose(source, openParen)\n if (closeParen < 0) continue\n const args = source.slice(openParen + 1, closeParen)\n // Pull out every string literal inside the first-arg region —\n // we don't bother distinguishing the array form from the bare\n // string; both end up as flat patterns.\n const literalRe = /['\"`]([^'\"`]+)['\"`]/g\n let lit: RegExpExecArray | null\n while ((lit = literalRe.exec(args)) !== null) {\n patterns.push(lit[1] as string)\n }\n }\n return patterns\n}\n\n/**\n * Convert a Vite-style glob (e.g. `./**\\\\/*.controller.ts`) to a\n * RegExp. Supports `**` (any path segments including `/`), `*` (any\n * chars within one segment), `?` (single char). Brace alternation is\n * intentionally not handled — none of the templated globs use it,\n * and a false-negative on an unusual pattern is safer than a false-\n * positive (better to skip the warning than to wrongly silence one).\n */\nfunction globToRegex(pattern: string): RegExp {\n // Process `?` before any of the substitutions that insert `?` into\n // the output (e.g. the `(?:.+/)?` non-capture group for `**/`).\n // If we left it for last, the `?` → `.` pass would mangle those\n // groups into `(.:.+\\/).` — broken regex.\n const escaped = pattern\n .replace(/[.+^$()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\?/g, '.')\n .replace(/\\*\\*\\//g, '___DOUBLESTAR_SLASH___')\n .replace(/\\*\\*/g, '___DOUBLESTAR___')\n .replace(/\\*/g, '[^/]*')\n .replace(/___DOUBLESTAR_SLASH___/g, '(?:.+/)?')\n .replace(/___DOUBLESTAR___/g, '.*')\n return new RegExp('^' + escaped + '$')\n}\n\n/**\n * Decide whether a file (relative to the module file's directory)\n * matches any of the module's positive glob patterns. Negation\n * patterns (`!./**\\\\/*.test.ts`) subtract; a file matched by both a\n * positive and a negation is excluded.\n */\nexport function fileMatchesAnyGlob(\n moduleRelativePath: string,\n patterns: readonly string[],\n): boolean {\n const normalised = moduleRelativePath.startsWith('./')\n ? moduleRelativePath\n : './' + moduleRelativePath\n let matched = false\n for (const pattern of patterns) {\n const isNegation = pattern.startsWith('!')\n const body = isNegation ? pattern.slice(1) : pattern\n if (globToRegex(body).test(normalised)) {\n matched = !isNegation\n }\n }\n return matched\n}\n\n/**\n * A `{ controller, mountPath }` pair extracted from a module's\n * `routes()` body. Multiple entries appear when a module returns an\n * array (multi-mount). forinda/kick-js#235 §3.\n */\nexport interface ModuleMount {\n controller: string\n mountPath: string\n}\n\n/**\n * Scan a module file's `routes()` body for `{ path, controller }` pairs.\n * A single return value or an array of return values both work — we\n * regex out every `path: '...'` and every `controller: Ident` and\n * zip them in order. Adopter writing wildly creative bodies won't be\n * matched; that's fine — the scanner falls back to no-mount behaviour\n * (per-route path only) which is the pre-fix behaviour.\n *\n * Returns a list of `{ controller, mountPath }` entries. A controller\n * that appears multiple times in `routes()` (rare; multi-mount\n * version-bundled controllers) gets multiple entries; the route\n * scanner uses the first one for path-param extraction since the\n * pattern usually shares the prefix.\n */\nexport function extractModuleMounts(source: string): ModuleMount[] {\n const out: ModuleMount[] = []\n ROUTES_METHOD_START.lastIndex = 0\n let m: RegExpExecArray | null\n while ((m = ROUTES_METHOD_START.exec(source)) !== null) {\n const openBrace = source.indexOf('{', m.index + m[0].length - 1)\n if (openBrace < 0) continue\n const closeBrace = findBalancedBrace(source, openBrace)\n if (closeBrace < 0) continue\n const body = source.slice(openBrace + 1, closeBrace)\n\n const paths: string[] = []\n PATH_FIELD_REGEX.lastIndex = 0\n let p: RegExpExecArray | null\n while ((p = PATH_FIELD_REGEX.exec(body)) !== null) {\n paths.push(p[1] ?? '')\n }\n\n const controllers: string[] = []\n CONTROLLER_FIELD_REGEX.lastIndex = 0\n let c: RegExpExecArray | null\n while ((c = CONTROLLER_FIELD_REGEX.exec(body)) !== null) {\n controllers.push(c[1] as string)\n }\n\n // Zip — if counts mismatch, take the shorter of the two so we\n // never assign a wrong controller to a path.\n const n = Math.min(paths.length, controllers.length)\n for (let i = 0; i < n; i++) {\n out.push({ controller: controllers[i] as string, mountPath: paths[i] as string })\n }\n }\n return out\n}\n\n/**\n * Extract a bare identifier value from a single field in an object literal\n * embedded in a string. Returns `null` if the field is missing or its value\n * isn't a bare identifier (e.g. an inline object, function call, etc.).\n *\n * Example: `extractObjectFieldIdentifier(\"'/' , { body: createTaskSchema }\", 'body')`\n * returns `'createTaskSchema'`.\n */\nfunction extractObjectFieldIdentifier(text: string, field: string): string | null {\n // Look for `field: <identifier>` not followed by `(` (function call) or `{` (inline object)\n const re = new RegExp(String.raw`\\b${field}\\s*:\\s*([A-Za-z_$][\\w$]*)`, 'g')\n const m = re.exec(text)\n if (!m) return null\n return m[1]\n}\n\n/**\n * Resolve a bare identifier to its module source by inspecting the file's\n * top-level imports and same-file `const` declarations.\n *\n * - `import { X } from './path'` → returns `'./path'`\n * - `import X from './path'` (default import) → returns `'./path'`\n * - `import * as X from './path'` → returns `'./path'`\n * - `const X = z.object(...)` (same file) → returns `null` (caller emits a self-import)\n *\n * Returns `null` when the identifier cannot be resolved.\n */\nfunction resolveImportSource(source: string, identifier: string): string | null {\n // Named import: `import { X, Y as Z } from './path'`\n const namedRe = new RegExp(\n String.raw`import\\s*(?:type\\s+)?\\{[^}]*\\b${identifier}\\b[^}]*\\}\\s*from\\s*['\"\\`]([^'\"\\`]+)['\"\\`]`,\n )\n const named = namedRe.exec(source)\n if (named) return named[1]\n\n // Default import: `import X from './path'`\n const defaultRe = new RegExp(\n String.raw`import\\s+(?:type\\s+)?${identifier}\\s+from\\s*['\"\\`]([^'\"\\`]+)['\"\\`]`,\n )\n const def = defaultRe.exec(source)\n if (def) return def[1]\n\n // Namespace import: `import * as X from './path'`\n const nsRe = new RegExp(\n String.raw`import\\s*\\*\\s*as\\s+${identifier}\\s+from\\s*['\"\\`]([^'\"\\`]+)['\"\\`]`,\n )\n const ns = nsRe.exec(source)\n if (ns) return ns[1]\n\n // Same-file const declaration — return empty string as a sentinel meaning\n // \"current file\". The generator turns this into a self-relative reference.\n const constRe = new RegExp(String.raw`(?:^|\\n)\\s*(?:export\\s+)?const\\s+${identifier}\\b`)\n if (constRe.test(source)) return ''\n\n return null\n}\n\n/**\n * Extract whitelist arrays from an `@ApiQueryParams(...)` decorator\n * within `decoratorBlock`. Handles two forms:\n *\n * - Inline literal: `@ApiQueryParams({ filterable: ['a', 'b'], ... })`\n * - Const reference: `@ApiQueryParams(SOME_CONFIG)` — looks up\n * `const SOME_CONFIG = { ... }` in the same file (`fullSource`).\n *\n * Returns `null` if no `@ApiQueryParams` is present. Returns\n * `{ filterable: [], sortable: [], searchable: [] }` if the decorator\n * is present but no fields could be statically extracted (opaque\n * imports, column-object configs, function calls, etc.).\n */\nfunction extractApiQueryParams(\n decoratorBlock: string,\n fullSource: string,\n): { filterable: string[]; sortable: string[]; searchable: string[] } | null {\n const apiMatch = /@ApiQueryParams\\s*\\(\\s*([\\s\\S]*?)\\s*\\)\\s*$/.exec(decoratorBlock)\n if (!apiMatch) {\n // Try without anchoring to the end (decorator may not be the last in the block)\n const loose = /@ApiQueryParams\\s*\\(([\\s\\S]*?)\\)/.exec(decoratorBlock)\n if (!loose) return null\n return parseApiQueryParamsArg(loose[1].trim(), fullSource)\n }\n return parseApiQueryParamsArg(apiMatch[1].trim(), fullSource)\n}\n\nfunction parseApiQueryParamsArg(\n arg: string,\n fullSource: string,\n): { filterable: string[]; sortable: string[]; searchable: string[] } {\n // Inline literal — starts with `{`\n if (arg.startsWith('{')) {\n return parseInlineConfigLiteral(arg)\n }\n // Const reference — bare identifier (possibly with type assertion)\n const idMatch = /^([A-Za-z_]\\w*)/.exec(arg)\n if (idMatch) {\n const ident = idMatch[1]\n // Look for `const IDENT = { ... }` in the same source file\n const constRe = new RegExp(\n String.raw`const\\s+${ident}\\s*(?::\\s*[^=]+)?=\\s*(\\{[\\s\\S]*?\\n\\})`,\n 'm',\n )\n const constMatch = constRe.exec(fullSource)\n if (constMatch) {\n return parseInlineConfigLiteral(constMatch[1])\n }\n }\n // Fallback: decorator present but extraction failed\n return { filterable: [], sortable: [], searchable: [] }\n}\n\n/** Extract a string array literal for one config key from an inline object literal */\nfunction extractStringArray(literal: string, key: string): string[] {\n const re = new RegExp(String.raw`${key}\\s*:\\s*\\[([\\s\\S]*?)\\]`)\n const m = re.exec(literal)\n if (!m) return []\n return Array.from(m[1].matchAll(/['\"`]([^'\"`]+)['\"`]/g)).map((x) => x[1])\n}\n\n/** Parse an inline `{ filterable: [...], sortable: [...], searchable: [...] }` literal */\nfunction parseInlineConfigLiteral(literal: string): {\n filterable: string[]\n sortable: string[]\n searchable: string[]\n} {\n return {\n filterable: extractStringArray(literal, 'filterable'),\n sortable: extractStringArray(literal, 'sortable'),\n searchable: extractStringArray(literal, 'searchable'),\n }\n}\n\n/** Recursively walk a directory and yield matching file paths */\nasync function walk(dir: string, opts: ScanOptions): Promise<string[]> {\n const exts = opts.extensions ?? DEFAULT_EXTENSIONS\n const excludes = opts.exclude ?? DEFAULT_EXCLUDES\n const out: string[] = []\n\n let entries: Dirent[]\n try {\n entries = (await readdir(dir, { withFileTypes: true, encoding: 'utf-8' })) as Dirent[]\n } catch {\n return out\n }\n\n for (const entry of entries) {\n const full = join(dir, entry.name)\n const rel = relative(opts.cwd, full)\n\n if (excludes.some((ex) => rel.includes(ex))) continue\n\n if (entry.isDirectory()) {\n out.push(...(await walk(full, opts)))\n } else if (entry.isFile()) {\n if (exts.some((ext) => entry.name.endsWith(ext))) {\n out.push(full)\n }\n }\n }\n\n return out\n}\n\n/** Compute the forward-slash relative path used in scanner output */\nfunction toRelative(filePath: string, cwd: string): string {\n return relative(cwd, filePath).split(sep).join('/')\n}\n\n/** Extract decorated classes from a single source file */\nexport function extractClassesFromSource(\n source: string,\n filePath: string,\n cwd: string,\n): DiscoveredClass[] {\n const out: DiscoveredClass[] = []\n const relPath = toRelative(filePath, cwd)\n\n DECORATED_CLASS_REGEX.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = DECORATED_CLASS_REGEX.exec(source)) !== null) {\n const [, decorator, defaultMarker, className] = match\n out.push({\n className,\n decorator: decorator as DecoratorName,\n filePath,\n relativePath: relPath,\n isDefault: Boolean(defaultMarker),\n })\n }\n\n // KickJS modules are undecorated classes that `implements AppModule`.\n // Tag them with the synthetic `Module` decorator so downstream code that\n // already filters by `c.decorator === 'Module'` keeps working.\n APP_MODULE_CLASS_REGEX.lastIndex = 0\n let modMatch: RegExpExecArray | null\n while ((modMatch = APP_MODULE_CLASS_REGEX.exec(source)) !== null) {\n const [, defaultMarker, className] = modMatch\n if (out.some((c) => c.className === className && c.filePath === filePath)) continue\n out.push({\n className,\n decorator: 'Module',\n filePath,\n relativePath: relPath,\n isDefault: Boolean(defaultMarker),\n })\n }\n\n // v4 factory modules: `export const XModule = defineModule({ ... })`.\n // Tag with the synthetic `Module` decorator (same as the v3 class form)\n // so `buildModuleTokens` populates `ModuleToken` — without this, a\n // project that uses only `defineModule()` emits `ModuleToken = never`.\n DEFINE_MODULE_REGEX.lastIndex = 0\n let defMatch: RegExpExecArray | null\n while ((defMatch = DEFINE_MODULE_REGEX.exec(source)) !== null) {\n const [, className] = defMatch\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: false,\n })\n }\n\n return out\n}\n\n/** Extract `createToken('name')` definitions from a single source file */\nexport function extractTokensFromSource(\n source: string,\n filePath: string,\n cwd: string,\n): DiscoveredToken[] {\n const out: DiscoveredToken[] = []\n const relPath = toRelative(filePath, cwd)\n const seen = new Set<string>()\n\n // First pass: const-bound tokens (preferred — we get the variable name)\n CREATE_TOKEN_REGEX.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = CREATE_TOKEN_REGEX.exec(source)) !== null) {\n const [full, variable, name] = match\n seen.add(full)\n out.push({ name, variable, filePath, relativePath: relPath })\n }\n\n // Second pass: bare calls not captured above (rare but possible)\n BARE_CREATE_TOKEN_REGEX.lastIndex = 0\n while ((match = BARE_CREATE_TOKEN_REGEX.exec(source)) !== null) {\n if (seen.has(match[0])) continue\n out.push({\n name: match[1],\n variable: null,\n filePath,\n relativePath: relPath,\n })\n }\n\n return out\n}\n\n/**\n * Extract route handlers from a source file.\n *\n * For each decorated class in `classesInFile`, slices the source from\n * the class declaration to the next class (or EOF) and runs the route\n * decorator regex within that slice. The result is a list of routes\n * tagged with their owning controller.\n *\n * Heuristic note: this assumes classes are not nested. KickJS controllers\n * are top-level by convention so this holds in practice.\n */\nexport function extractRoutesFromSource(\n source: string,\n filePath: string,\n cwd: string,\n classesInFile: DiscoveredClass[],\n mountPathByController: ReadonlyMap<string, string> = new Map(),\n): DiscoveredRoute[] {\n const out: DiscoveredRoute[] = []\n if (classesInFile.length === 0) return out\n const relPath = toRelative(filePath, cwd)\n\n // Locate each class declaration's offset in the source\n const positions: Array<{ cls: DiscoveredClass; start: number }> = []\n for (const cls of classesInFile) {\n const re = new RegExp(String.raw`class\\s+${cls.className}\\b`)\n const m = re.exec(source)\n if (m?.index !== undefined) {\n positions.push({ cls, start: m.index })\n }\n }\n positions.sort((a, b) => a.start - b.start)\n\n for (let i = 0; i < positions.length; i++) {\n const { cls, start } = positions[i]\n const end = i + 1 < positions.length ? positions[i + 1].start : source.length\n const block = source.slice(start, end)\n\n // Two-pass walk: locate each route decorator start, then balance-parse\n // forward through args and any stacked decorators to find the method\n // name. Replaces the previous single regex which mis-parsed nested\n // parens (forinda/kick-js#108).\n ROUTE_DECORATOR_START.lastIndex = 0\n let startMatch: RegExpExecArray | null\n while ((startMatch = ROUTE_DECORATOR_START.exec(block)) !== null) {\n const verb = startMatch[1]\n const decoratorStart = startMatch.index\n const openParen = ROUTE_DECORATOR_START.lastIndex - 1\n const closeParen = findBalancedClose(block, openParen)\n if (closeParen < 0) continue\n\n const routeArgs = block.slice(openParen + 1, closeParen)\n\n const pathLiteralMatch = routeArgs.match(/^\\s*['\"`]([^'\"`]*)['\"`]/)\n const path = pathLiteralMatch && pathLiteralMatch[1].length > 0 ? pathLiteralMatch[1] : '/'\n\n const methodInfo = readMethodAfterDecorators(block, closeParen + 1)\n if (!methodInfo) continue\n const { methodName, endPos } = methodInfo\n\n // Advance the regex iterator past this method so the next iteration\n // starts looking after the consumed region.\n ROUTE_DECORATOR_START.lastIndex = endPos\n\n const matchedText = block.slice(decoratorStart, endPos)\n const apiQp = extractApiQueryParams(matchedText, source)\n\n const bodyId = extractObjectFieldIdentifier(routeArgs, 'body')\n const queryId = extractObjectFieldIdentifier(routeArgs, 'query')\n const paramsId = extractObjectFieldIdentifier(routeArgs, 'params')\n\n // forinda/kick-js#235 §3 — when the controller is mounted under a path\n // with `:params` (e.g. `/orgs/:id/extensions`), surface those\n // params in `pathParams` so the typegen widens `ctx.params`\n // without adopters repeating `params: schema` on every route.\n const mountPath = mountPathByController.get(cls.className) ?? ''\n const fullPath = mountPath ? joinMountPath(mountPath, path) : path\n\n out.push({\n controller: cls.className,\n method: methodName,\n httpMethod: verb.toUpperCase() as DiscoveredRoute['httpMethod'],\n path,\n pathParams: extractPathParams(fullPath),\n queryFilterable: apiQp?.filterable ?? null,\n querySortable: apiQp?.sortable ?? null,\n querySearchable: apiQp?.searchable ?? null,\n bodySchema: bodyId\n ? { identifier: bodyId, source: resolveImportSource(source, bodyId) }\n : null,\n querySchema: queryId\n ? { identifier: queryId, source: resolveImportSource(source, queryId) }\n : null,\n paramsSchema: paramsId\n ? { identifier: paramsId, source: resolveImportSource(source, paramsId) }\n : null,\n filePath,\n relativePath: relPath,\n })\n }\n }\n\n return out\n}\n\n/** Extract `@Inject('literal')` calls from a single source file */\nexport function extractInjectsFromSource(\n source: string,\n filePath: string,\n cwd: string,\n): DiscoveredInject[] {\n const out: DiscoveredInject[] = []\n const relPath = toRelative(filePath, cwd)\n\n INJECT_LITERAL_REGEX.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = INJECT_LITERAL_REGEX.exec(source)) !== null) {\n out.push({ name: match[1], filePath, relativePath: relPath })\n }\n\n return out\n}\n\n/**\n * Extract the bounds of an object literal that begins at `openBracePos`\n * (the index of the `{` character). Returns the index of the matching `}`\n * or -1 if no match is found. Counts balanced braces only — does not\n * understand string literals so a `{` or `}` inside a string inside the\n * object will skew the depth counter (matches `findBalancedClose`).\n */\nfunction findBalancedBrace(text: string, openBracePos: number): number {\n let depth = 1\n for (let i = openBracePos + 1; i < text.length; i++) {\n const ch = text[i]\n if (ch === '{') depth++\n else if (ch === '}') {\n depth--\n if (depth === 0) return i\n }\n }\n return -1\n}\n\n/**\n * Extract plugins/adapters declared via `defineAdapter({ name: '...' })`\n * or `definePlugin({ name: '...' })` calls and via class-style adapters\n * (`class XxxAdapter implements AppAdapter` with a string-literal `name`\n * field).\n *\n * Only the literal `name:` field feeds the result — the symbol on the LHS\n * is irrelevant since `dependsOn` references the runtime name.\n */\nexport function extractPluginsAndAdaptersFromSource(\n source: string,\n filePath: string,\n cwd: string,\n): DiscoveredPluginOrAdapter[] {\n const out: DiscoveredPluginOrAdapter[] = []\n const relPath = toRelative(filePath, cwd)\n const seen = new Set<string>()\n\n // Pass 1: defineAdapter / definePlugin calls\n DEFINE_HELPER_START.lastIndex = 0\n let helperMatch: RegExpExecArray | null\n while ((helperMatch = DEFINE_HELPER_START.exec(source)) !== null) {\n const helper = helperMatch[1] as 'defineAdapter' | 'definePlugin'\n const openParen = DEFINE_HELPER_START.lastIndex - 1\n const closeParen = findBalancedClose(source, openParen)\n if (closeParen < 0) continue\n const callArgs = source.slice(openParen + 1, closeParen)\n // Look for the first `name: 'literal'` in the call args\n const nameMatch = /\\bname\\s*:\\s*['\"`]([^'\"`]+)['\"`]/.exec(callArgs)\n if (!nameMatch) continue\n const name = nameMatch[1]\n const dedupeKey = `${helper}::${name}::${filePath}`\n if (seen.has(dedupeKey)) continue\n seen.add(dedupeKey)\n out.push({\n kind: helper === 'definePlugin' ? 'plugin' : 'adapter',\n name,\n filePath,\n relativePath: relPath,\n })\n }\n\n // Pass 2: class-style adapters (`class X implements AppAdapter { name = 'X' }`)\n APP_ADAPTER_CLASS_REGEX.lastIndex = 0\n let classMatch: RegExpExecArray | null\n while ((classMatch = APP_ADAPTER_CLASS_REGEX.exec(source)) !== null) {\n const classStart = classMatch.index\n // Find the class body opening brace\n const bracePos = source.indexOf('{', classStart)\n if (bracePos < 0) continue\n const closeBrace = findBalancedBrace(source, bracePos)\n if (closeBrace < 0) continue\n const body = source.slice(bracePos + 1, closeBrace)\n const nameMatch = CLASS_NAME_FIELD_REGEX.exec(body)\n if (!nameMatch) continue\n const name = nameMatch[1]\n const dedupeKey = `class::${name}::${filePath}`\n if (seen.has(dedupeKey)) continue\n seen.add(dedupeKey)\n out.push({ kind: 'adapter', name, filePath, relativePath: relPath })\n }\n\n return out\n}\n\n/**\n * Extract context keys from `defineContextDecorator({ key: '...' })` and\n * `defineHttpContextDecorator({ key: '...' })` calls (including the\n * curried `.withParams<P>()({ key: '...' })` form). Only the literal\n * `key:` field feeds the result — the symbol on the LHS is irrelevant\n * since `dependsOn` references the runtime key string.\n *\n * Mirrors {@link extractPluginsAndAdaptersFromSource}: regex to the spec\n * object's opening paren, `findBalancedClose` to its end, then the first\n * `key: 'literal'` inside.\n */\nexport function extractContextKeysFromSource(\n source: string,\n filePath: string,\n cwd: string,\n): DiscoveredContextKey[] {\n const out: DiscoveredContextKey[] = []\n const relPath = toRelative(filePath, cwd)\n const seen = new Set<string>()\n\n CONTEXT_DECORATOR_START.lastIndex = 0\n // The match value itself is unused — we only need the loop to advance\n // and `lastIndex` to point just past the spec's opening paren.\n while (CONTEXT_DECORATOR_START.exec(source) !== null) {\n const openParen = CONTEXT_DECORATOR_START.lastIndex - 1\n const closeParen = findBalancedClose(source, openParen)\n if (closeParen < 0) continue\n const callArgs = source.slice(openParen + 1, closeParen)\n const keyMatch = /\\bkey\\s*:\\s*['\"`]([^'\"`]+)['\"`]/.exec(callArgs)\n if (!keyMatch) continue\n const key = keyMatch[1]\n if (seen.has(key)) continue\n seen.add(key)\n out.push({ key, filePath, relativePath: relPath })\n }\n\n return out\n}\n\n/**\n * Extract `defineAugmentation('Name', { description, example })` calls\n * from a source file. The metadata object is optional — when absent both\n * `description` and `example` resolve to `null`.\n */\nexport function extractAugmentationsFromSource(\n source: string,\n filePath: string,\n cwd: string,\n): DiscoveredAugmentation[] {\n const out: DiscoveredAugmentation[] = []\n const relPath = toRelative(filePath, cwd)\n\n DEFINE_AUGMENTATION_START.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = DEFINE_AUGMENTATION_START.exec(source)) !== null) {\n const name = match[1]\n let description: string | null = null\n let example: string | null = null\n\n // If the regex matched a metadata object opening (`, {`), parse it\n if (match[2]) {\n const bracePos = source.indexOf('{', match.index + match[0].length - 1)\n if (bracePos >= 0) {\n const closeBrace = findBalancedBrace(source, bracePos)\n if (closeBrace >= 0) {\n const body = source.slice(bracePos + 1, closeBrace)\n description = readStringField(body, 'description')\n example = readStringField(body, 'example')\n }\n }\n }\n\n out.push({ name, description, example, filePath, relativePath: relPath })\n }\n\n return out\n}\n\n/**\n * Pull a string-valued field out of a JS object-literal body, respecting\n * the opening quote so the value isn't truncated at the first foreign\n * quote character. Handles backslash escapes inside the literal.\n *\n * Why a custom parser instead of one regex per delimiter: real-world\n * `defineAugmentation` calls embed all three quote characters at once\n * — backtick template literals carrying TS shapes like\n * `'free' | 'pro'` (single quotes) AND `\\`ctx.get(...)\\`` (escaped\n * backticks). A character-class regex like `[^'\"`]+` truncates on the\n * first foreign quote it sees. This walker scans char-by-char from\n * the matched delimiter and only stops on the matching one.\n */\nfunction readStringField(body: string, field: string): string | null {\n // Locate `field:` followed by an opening quote. Tolerate any whitespace.\n const fieldRe = new RegExp(`\\\\b${field}\\\\s*:\\\\s*(['\"\\`])`, 'g')\n const m = fieldRe.exec(body)\n if (!m) return null\n const quote = m[1]\n const start = m.index + m[0].length\n let i = start\n let raw: string | null = null\n while (i < body.length) {\n const ch = body[i]\n if (ch === '\\\\') {\n // Skip the escaped char — supports \\`, \\', \\\", \\n, \\\\ etc.\n i += 2\n continue\n }\n if (ch === quote) {\n raw = body.slice(start, i)\n break\n }\n i++\n }\n if (raw === null) return null\n // Unescape JS string-literal escapes so the JSDoc renderer sees the\n // value the source author actually intended (`\\`` → `` ` ``, `\\'` →\n // `'`, etc). Without this, escaped backticks in a backtick template\n // literal would surface as literal backslashes in the catalogue.\n return raw.replace(/\\\\(.)/g, (_m, c) => {\n if (c === 'n') return '\\n'\n if (c === 't') return '\\t'\n if (c === 'r') return '\\r'\n return c\n })\n}\n\n/**\n * Default search order for the env schema file. Newer projects keep\n * the schema under `src/config/` so the framework's \"config\" concept\n * has a single home; older scaffolds dropped it at `src/env.ts` (kept\n * here for back-compat). The first match wins.\n */\nconst DEFAULT_ENV_FILE_CANDIDATES = [\n 'src/config/index.ts',\n 'src/config/env.ts',\n 'src/config.ts',\n 'src/env.ts',\n] as const\n\n/**\n * Look for an env schema file. When `envFile` is the string default\n * (`'src/env.ts'`) or omitted, every entry in `DEFAULT_ENV_FILE_CANDIDATES`\n * is tried in order. When the caller passes an explicit path, only that\n * path is tried (so projects can opt out of the search by setting\n * `kick.config.ts → typegen.envFile`).\n *\n * Returns a `DiscoveredEnv` if the file exists and contains both a\n * `defineEnv(...)` call and a default export — the two markers we\n * need before it's safe to emit `import type schema from '...'` in\n * the generator. Returns `null` for any other state (no candidate\n * found, no defineEnv, no default export) so the generator skips env\n * typing silently.\n */\nexport async function detectEnvFile(cwd: string, envFile: string): Promise<DiscoveredEnv | null> {\n // The CLI passes the literal default `'src/env.ts'` when the user\n // hasn't overridden it. Treat that as \"use the search list\" rather\n // than pinning to one path, so newer scaffolds at src/config/ keep\n // working without forcing every project to set typegen.envFile.\n const candidates: readonly string[] =\n envFile === 'src/env.ts' ? DEFAULT_ENV_FILE_CANDIDATES : [envFile]\n\n for (const candidate of candidates) {\n const abs = resolve(cwd, candidate)\n let source: string\n try {\n source = await readFile(abs, 'utf-8')\n } catch {\n continue\n }\n // Cheap heuristic: a schema-construction call AND a default\n // export. The default export must be the SCHEMA itself — the\n // generator emits `import type schema from '...'` and runs it\n // through schema-to-type inference. `loadEnvFromSchema(...)` is\n // deliberately NOT in the accept list because its return value is\n // the parsed env object, not the schema. Adopters routinely write\n // export default envSchema\n // export const env = loadEnvFromSchema(envSchema)\n // where only `envSchema` is the schema; detecting on\n // `loadEnvFromSchema` would also accept the anti-pattern\n // export default loadEnvFromSchema(schema)\n // which would push the parsed *env value* into `InferSchemaOutput`\n // and emit a broken `KickEnv`.\n //\n // Accept lists:\n // - `defineEnv(...)` — legacy Zod scaffold\n // - `fromZod / fromValibot / fromYup(...)` — kickjs-schema adapters\n if (!/\\bdefineEnv\\s*\\(/.test(source) && !/\\bfrom(Zod|Valibot|Yup)\\s*\\(/.test(source)) {\n continue\n }\n if (!/export\\s+default\\b/.test(source)) continue\n // Reject the \"default-export is the parsed env\" pattern\n // explicitly. Without this guard, a file that constructs the\n // schema with `fromZod(...)` and then does\n // `export default loadEnvFromSchema(envSchema)` would slip past\n // the schema-construction check above and feed the parsed env's\n // value type into `InferSchemaOutput`.\n if (/export\\s+default\\s+loadEnvFromSchema\\s*\\(/.test(source)) continue\n return {\n filePath: abs,\n relativePath: toRelative(abs, cwd),\n }\n }\n\n return null\n}\n\n/** Detect duplicate class names across files */\nexport function findCollisions(classes: DiscoveredClass[]): ClassCollision[] {\n const groups = new Map<string, DiscoveredClass[]>()\n for (const cls of classes) {\n const arr = groups.get(cls.className) ?? []\n arr.push(cls)\n groups.set(cls.className, arr)\n }\n\n const collisions: ClassCollision[] = []\n for (const [className, group] of groups) {\n // Two declarations of the same class name in different files = collision.\n // Multiple decorators on the same file/class are NOT a collision.\n const distinctFiles = new Set(group.map((c) => c.filePath))\n if (distinctFiles.size > 1) {\n collisions.push({ className, classes: group })\n }\n }\n\n // Deterministic order\n collisions.sort((a, b) => a.className.localeCompare(b.className))\n return collisions\n}\n\n/**\n * The complete per-file extraction result. Every field here is a pure\n * function of a single file's source text — no cross-file context — so\n * the whole object is cacheable keyed by the file's signature.\n *\n * The one subtlety is `routes`: their `pathParams` are computed with an\n * EMPTY mount map (own-path params only). The cross-file mount prefix\n * is re-applied during the join phase of `scanProject`, which is a\n * cheap pure-JS recompute (no regex, no I/O). This keeps routes fully\n * cacheable even though their final `pathParams` depend on a sibling\n * module file's `routes()` mount path.\n */\nexport interface FileExtract {\n classes: DiscoveredClass[]\n tokens: DiscoveredToken[]\n injects: DiscoveredInject[]\n pluginsAndAdapters: DiscoveredPluginOrAdapter[]\n augmentations: DiscoveredAugmentation[]\n contextKeys: DiscoveredContextKey[]\n /** Routes with own-path `pathParams` only — mount prefix applied at join. */\n routes: DiscoveredRoute[]\n /** `{ controller, mountPath }` pairs from this file's `routes()` body. */\n moduleMounts: ModuleMount[]\n /** `import.meta.glob([...])` patterns (only non-empty for `*.module.ts`). */\n globPatterns: string[]\n}\n\n/**\n * Run per-file extraction over one source string. Pure: depends only\n * on the file's own text, so the result is safe to cache by filesystem\n * signature (see `scanner-cache.ts`).\n *\n * AST-first (oxc-parser — exact decorator/argument parsing, template-\n * literal route paths, no balanced-delimiter heuristics), falling back\n * to the regex extractors when the file doesn't parse — mid-edit\n * syntax errors in watch mode still yield partial results that keep\n * the dev loop alive.\n */\nexport function extractFile(source: string, filePath: string, cwd: string): FileExtract {\n const ast = extractFileAst(source, filePath, cwd)\n if (ast) return ast\n return extractFileRegex(source, filePath, cwd)\n}\n\n/**\n * The original regex extraction pipeline — fallback for unparseable\n * sources and the parity baseline for the AST extractor's tests.\n */\nexport function extractFileRegex(source: string, filePath: string, cwd: string): FileExtract {\n const classes = extractClassesFromSource(source, filePath, cwd)\n return {\n classes,\n tokens: extractTokensFromSource(source, filePath, cwd),\n injects: extractInjectsFromSource(source, filePath, cwd),\n pluginsAndAdapters: extractPluginsAndAdaptersFromSource(source, filePath, cwd),\n augmentations: extractAugmentationsFromSource(source, filePath, cwd),\n contextKeys: extractContextKeysFromSource(source, filePath, cwd),\n // Empty mount map → own-path params only; prefix re-applied at join.\n routes: extractRoutesFromSource(source, filePath, cwd, classes, new Map()),\n moduleMounts: extractModuleMounts(source),\n globPatterns: /\\.module\\.[mc]?[tj]sx?$/.test(filePath) ? extractGlobPatterns(source) : [],\n }\n}\n\n/**\n * Read + extract a single file, consulting the optional persistent\n * cache first. On a signature hit the file is not read at all — the\n * cached extract is returned directly. Returns null when the file\n * cannot be read (deleted mid-scan / permission error).\n */\nasync function loadFileExtract(\n file: string,\n cwd: string,\n cache: ScanCache | null,\n): Promise<FileExtract | null> {\n const sig = cache ? await ScanCache.signature(file) : null\n if (cache && sig) {\n const hit = cache.get(file, sig)\n if (hit) {\n cache.set(file, sig, hit)\n return hit\n }\n }\n let source: string\n try {\n source = await readFile(file, 'utf-8')\n } catch {\n return null\n }\n const extract = extractFile(source, file, cwd)\n if (cache && sig) cache.set(file, sig, extract)\n return extract\n}\n\n/** Map a concurrency-bounded async fn over items, preserving order. */\nasync function mapConcurrent<T, R>(\n items: T[],\n limit: number,\n fn: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n const out: R[] = []\n let next = 0\n const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {\n for (;;) {\n const i = next++\n if (i >= items.length) return\n out[i] = await fn(items[i], i)\n }\n })\n await Promise.all(workers)\n return out\n}\n\n/**\n * Scan a project for decorated classes, createToken definitions, and\n * `@Inject` literal usages.\n *\n * Per-file extraction is read + parsed concurrently and, when\n * `opts.cacheDir` is set, served from a persistent signature cache so\n * unchanged files are never re-read on a watch/rebuild. The cross-file\n * join phase (mount-prefix resolution, orphan detection) always runs\n * over the full extract set, so cached entries can never desync output.\n */\nexport async function scanProject(opts: ScanOptions): Promise<ScanResult> {\n const root = resolve(opts.root)\n // Sort the walk output so the cross-file join sees files in the same\n // order the incremental path does (which sorts its working set). The\n // §3 mount-map is first-wins, so a stable order keeps a multi-mount\n // controller's pathParams identical across cold and incremental runs\n // (and across filesystems with different readdir ordering).\n const files = (await walk(root, opts)).toSorted()\n\n const cache = opts.cacheDir ? await ScanCache.load(opts.cacheDir) : null\n\n // Concurrent read + per-file extraction (cache-aware). I/O-bound, so\n // a modest fan-out hides per-file latency without thrashing the FD\n // table. Order is preserved for deterministic downstream iteration.\n const extracts = await mapConcurrent(files, 16, (file) => loadFileExtract(file, opts.cwd, cache))\n\n const joined = joinExtracts(files, extracts)\n const env = await detectEnvFile(opts.cwd, opts.envFile ?? 'src/env.ts')\n\n // Persist the refreshed cache (prunes entries for deleted files).\n if (cache) await cache.save()\n\n return { ...joined, env }\n}\n\n/** A precise set of filesystem changes, as reported by a watcher. */\nexport interface ScanDelta {\n /** Files added or modified since the last scan (absolute or cwd-relative). */\n changed: string[]\n /** Files deleted since the last scan. */\n removed: string[]\n}\n\n/**\n * Does `file` belong in the scan? Mirrors `walk()`'s ext + exclude\n * filtering so a watcher event for a `.d.ts`, a test, or a node_modules\n * file is ignored just as the full walk would ignore it.\n */\nfunction isScannableFile(file: string, root: string, opts: ScanOptions): boolean {\n const exts = opts.extensions ?? DEFAULT_EXTENSIONS\n const excludes = opts.exclude ?? DEFAULT_EXCLUDES\n if (!file.startsWith(root + sep) && file !== root) return false\n if (!exts.some((ext) => file.endsWith(ext))) return false\n const rel = relative(opts.cwd, file)\n if (excludes.some((ex) => rel.includes(ex))) return false\n return true\n}\n\n/**\n * Incremental scan driven by an exact watcher delta (e.g. Vite's\n * chokidar events). Unlike `scanProject` this performs NO directory\n * walk and NO `stat()` of unchanged files: it loads the persistent\n * cache, re-extracts only the `changed` files, drops `removed` ones,\n * and re-runs the cheap cross-file join over the resulting set.\n *\n * Requires a warm cache. With no `cacheDir`, an empty cache (cold\n * start), it transparently falls back to a full `scanProject` so the\n * caller never has to special-case the first run.\n */\nexport async function scanProjectIncremental(\n opts: ScanOptions,\n delta: ScanDelta,\n): Promise<ScanResult> {\n if (!opts.cacheDir) return scanProject(opts)\n const root = resolve(opts.root)\n const cache = await ScanCache.load(opts.cacheDir)\n const cachedFiles = cache.cachedFiles()\n if (cachedFiles.length === 0) return scanProject(opts)\n\n const removed = new Set(delta.removed.map((f) => resolve(opts.cwd, f)))\n const changed = delta.changed\n .map((f) => resolve(opts.cwd, f))\n .filter((f) => !removed.has(f) && isScannableFile(f, root, opts))\n const changedSet = new Set(changed)\n\n // Working set = (previously cached ∪ newly added) − removed.\n const working = new Set(cachedFiles)\n for (const f of changedSet) working.add(f)\n for (const f of removed) working.delete(f)\n\n // Re-extract only the changed files (concurrent). Everything else is\n // served from the cache without a read or a stat.\n const fresh = new Map<string, FileExtract>()\n await mapConcurrent(changed, 16, async (file) => {\n if (!working.has(file)) return\n const sig = await ScanCache.signature(file)\n let source: string\n try {\n source = await readFile(file, 'utf-8')\n } catch {\n working.delete(file)\n return\n }\n const extract = extractFile(source, file, opts.cwd)\n fresh.set(file, extract)\n if (sig) cache.set(file, sig, extract)\n })\n\n const orderedFiles = [...working].toSorted()\n const extracts = orderedFiles.map((file) => {\n const f = fresh.get(file)\n if (f) return f\n cache.carry(file) // keep the unchanged entry in the next saved cache\n return cache.peek(file)\n })\n\n const joined = joinExtracts(orderedFiles, extracts)\n const env = await detectEnvFile(opts.cwd, opts.envFile ?? 'src/env.ts')\n await cache.save()\n\n return { ...joined, env }\n}\n\n/**\n * Cross-file join over a set of per-file extracts: resolves mount-prefix\n * route params, detects glob-orphaned classes, concatenates and sorts\n * every discovered entity into a deterministic `ScanResult` (minus the\n * async `env` field, which the caller attaches). Pure and synchronous —\n * shared by both `scanProject` and `scanProjectIncremental`.\n */\nfunction joinExtracts(files: string[], extracts: (FileExtract | null)[]): Omit<ScanResult, 'env'> {\n const classes: DiscoveredClass[] = []\n const routes: DiscoveredRoute[] = []\n const tokens: DiscoveredToken[] = []\n const injects: DiscoveredInject[] = []\n const pluginsAndAdapters: DiscoveredPluginOrAdapter[] = []\n const augmentations: DiscoveredAugmentation[] = []\n const contextKeys: DiscoveredContextKey[] = []\n\n // forinda/kick-js#235 §3 — build a `Controller → mountPath` map from every\n // module file's `routes()` body so per-route `pathParams` can include\n // the prefix params (e.g. `/orgs/:id`) without adopters re-declaring\n // `params:` on every method. First mount wins on duplicates (rare\n // multi-mount controllers — typically share the prefix shape).\n const mountPathByController = new Map<string, string>()\n for (const extract of extracts) {\n if (!extract) continue\n for (const { controller, mountPath } of extract.moduleMounts) {\n if (!mountPathByController.has(controller)) {\n mountPathByController.set(controller, mountPath)\n }\n }\n }\n\n // A per-file glob-pattern map drives the §4 orphan pass below without\n // re-reading module sources.\n const globPatternsByFile = new Map<string, string[]>()\n for (let i = 0; i < files.length; i++) {\n const extract = extracts[i]\n if (!extract) continue\n classes.push(...extract.classes)\n tokens.push(...extract.tokens)\n injects.push(...extract.injects)\n pluginsAndAdapters.push(...extract.pluginsAndAdapters)\n augmentations.push(...extract.augmentations)\n contextKeys.push(...extract.contextKeys)\n if (extract.globPatterns.length > 0) globPatternsByFile.set(files[i], extract.globPatterns)\n\n // Re-apply the cross-file mount prefix to each cached route's\n // pathParams. Routes were extracted with an empty mount map, so\n // their pathParams currently carry own-path params only.\n for (const route of extract.routes) {\n const mountPath = mountPathByController.get(route.controller)\n if (mountPath) {\n const fullPath = joinMountPath(mountPath, route.path)\n routes.push({ ...route, pathParams: extractPathParams(fullPath) })\n } else {\n routes.push(route)\n }\n }\n }\n\n // forinda/kick-js#235 §4 — for every module file, extract its\n // `import.meta.glob([...])` patterns and flag any decorated class\n // whose file sits inside the module directory but isn't matched by\n // a positive pattern. Catches the \"added a new file type, forgot to\n // extend the glob\" silent-degradation case.\n // Normalize Windows backslashes to forward slashes before any\n // slicing / startsWith / glob-matching — the rest of the scanner\n // already speaks forward-slash relative paths, but absolute\n // `filePath` values may carry the platform separator on Windows.\n const orphanedClasses: OrphanedClass[] = []\n for (const [moduleFile, patterns] of globPatternsByFile) {\n if (!/\\.module\\.[mc]?[tj]sx?$/.test(moduleFile)) continue\n if (patterns.length === 0) continue\n const moduleFilePosix = moduleFile.replaceAll(sep, '/')\n const moduleDir = moduleFilePosix.slice(0, moduleFilePosix.lastIndexOf('/'))\n for (const cls of classes) {\n // Skip module files themselves — they're scanner-synthesized\n // `decorator: 'Module'` entries that aren't glob contributors.\n if (cls.decorator === 'Module') continue\n const classFilePosix = cls.filePath.replaceAll(sep, '/')\n if (!classFilePosix.startsWith(moduleDir + '/')) continue\n if (classFilePosix === moduleFilePosix) continue\n const moduleRelative = classFilePosix.slice(moduleDir.length + 1)\n if (!fileMatchesAnyGlob(moduleRelative, patterns)) {\n orphanedClasses.push({\n className: cls.className,\n filePath: cls.filePath,\n relativePath: cls.relativePath,\n moduleFilePath: moduleFile,\n decorator: cls.decorator,\n })\n }\n }\n }\n\n // Deterministic ordering for stable .d.ts output\n classes.sort((a, b) => {\n if (a.className !== b.className) return a.className.localeCompare(b.className)\n return a.relativePath.localeCompare(b.relativePath)\n })\n tokens.sort(\n (a, b) => a.name.localeCompare(b.name) || a.relativePath.localeCompare(b.relativePath),\n )\n injects.sort(\n (a, b) => a.name.localeCompare(b.name) || a.relativePath.localeCompare(b.relativePath),\n )\n routes.sort(\n (a, b) => a.controller.localeCompare(b.controller) || a.method.localeCompare(b.method),\n )\n pluginsAndAdapters.sort(\n (a, b) => a.name.localeCompare(b.name) || a.relativePath.localeCompare(b.relativePath),\n )\n augmentations.sort(\n (a, b) => a.name.localeCompare(b.name) || a.relativePath.localeCompare(b.relativePath),\n )\n contextKeys.sort(\n (a, b) => a.key.localeCompare(b.key) || a.relativePath.localeCompare(b.relativePath),\n )\n\n const collisions = findCollisions(classes)\n\n orphanedClasses.sort(\n (a, b) =>\n a.relativePath.localeCompare(b.relativePath) || a.className.localeCompare(b.className),\n )\n\n return {\n classes,\n routes,\n tokens,\n injects,\n collisions,\n pluginsAndAdapters,\n augmentations,\n contextKeys,\n orphanedClasses,\n }\n}\n","/**\n * Pure renderers for the DI-manifest typegen surface — the\n * `KickJsRegistry` augmentation, the `ServiceToken` / `ModuleToken`\n * unions, the `KickJsPluginRegistry` augmentation, and the\n * `defineAugmentation` catalogue.\n *\n * These used to live in `generator.ts` (the monolithic legacy pass).\n * They are now consumed by the per-domain builtin typegen plugins\n * (`kick/registry`, `kick/services`, `kick/modules`, `kick/plugins`,\n * `kick/augmentations`) so the whole pipeline is plugin-based and each\n * file is emitted + cache-tracked independently by the runner.\n *\n * Every function here is pure (scan data in → string out); no fs, no\n * config, no globals.\n *\n * @module @forinda/kickjs-cli/typegen/render/manifest\n */\n\nimport { dirname, relative, sep } from 'node:path'\nimport type {\n ClassCollision,\n DiscoveredAugmentation,\n DiscoveredClass,\n DiscoveredInject,\n DiscoveredPluginOrAdapter,\n DiscoveredToken,\n} from '../scanner'\n\n/** Header written to every generated file */\nexport const HEADER = `/* eslint-disable */\n// AUTO-GENERATED by \\`kick typegen\\`. DO NOT EDIT.\n// Re-run with \\`kick typegen\\` or rely on \\`kick dev\\` to refresh.\n`\n\n/** Decorators whose classes participate in the DI registry augmentation */\nexport const REGISTRY_DECORATORS = new Set(['Service', 'Repository', 'Injectable', 'Component'])\n\n/** Thrown when collisions are found and not allowed */\nexport class TokenCollisionError extends Error {\n readonly collisions: ClassCollision[]\n constructor(collisions: ClassCollision[]) {\n super(formatCollisionMessage(collisions))\n this.name = 'TokenCollisionError'\n this.collisions = collisions\n }\n}\n\n/** Build a human-readable message describing every collision */\nfunction formatCollisionMessage(collisions: ClassCollision[]): string {\n const lines: string[] = ['kick typegen: token collision detected']\n for (const c of collisions) {\n lines.push('')\n lines.push(` ${c.classes.length} classes named '${c.className}':`)\n for (const cls of c.classes) {\n lines.push(` - ${cls.relativePath}`)\n }\n }\n lines.push('')\n lines.push('Resolutions:')\n lines.push(' (a) Rename one of the classes')\n lines.push(\n \" (b) Use createToken<T>('namespaced/Name') and import the token explicitly — see @forinda/kickjs\",\n )\n lines.push(' (c) Pass --allow-duplicates to namespace the registry keys automatically')\n lines.push(\" (e.g. 'modules/users/UserService' instead of 'UserService')\")\n return lines.join('\\n')\n}\n\n/** Compute the module specifier (without extension) used inside `import('...')` */\nfunction importSpecifierFor(targetFile: string, fromFile: string): string {\n const fromDir = dirname(fromFile)\n let rel = relative(fromDir, targetFile).split(sep).join('/')\n rel = rel.replace(/\\.(ts|tsx|mts|cts)$/i, '')\n if (!rel.startsWith('.')) rel = './' + rel\n return rel\n}\n\n/**\n * Build the namespaced registry key for a colliding class.\n * Strips the `src/` prefix and the file extension, then appends the\n * class name. Example: `src/modules/users/user.service.ts` + `UserService`\n * → `modules/users/UserService`.\n */\nexport function namespacedKeyFor(cls: DiscoveredClass): string {\n const rel = cls.relativePath.replace(/^src\\//, '').replace(/\\.(ts|tsx|mts|cts)$/i, '')\n const parts = rel.split('/')\n parts.pop()\n const ns = parts.join('/')\n return ns ? `${ns}/${cls.className}` : cls.className\n}\n\n/**\n * Render the `KickJsRegistry` module augmentation. Each entry maps a\n * string token to the imported class type. Default-exported classes are\n * imported as `import('...').default`. `collidingNames` lists class\n * names that should be auto-namespaced; everything else gets a bare key.\n */\nexport function renderRegistry(\n classes: DiscoveredClass[],\n outFile: string,\n collidingNames: Set<string>,\n): string {\n const seen = new Set<string>()\n const entries: string[] = []\n\n for (const c of classes) {\n if (!REGISTRY_DECORATORS.has(c.decorator)) continue\n\n const key = collidingNames.has(c.className) ? namespacedKeyFor(c) : c.className\n if (seen.has(key)) continue\n seen.add(key)\n\n const spec = importSpecifierFor(c.filePath, outFile)\n const ref = c.isDefault ? `import('${spec}').default` : `import('${spec}').${c.className}`\n entries.push(` '${key}': ${ref}`)\n }\n\n const body = entries.length\n ? entries.join('\\n')\n : ' // (no services discovered yet — run `kick g service <name>` to add one)'\n\n return `${HEADER}\ndeclare module '@forinda/kickjs' {\n interface KickJsRegistry {\n${body}\n }\n}\n\nexport {}\n`\n}\n\n/** True when `str` is a bare JS identifier (usable as an unquoted key). */\nfunction isIdentifierKey(str: string): boolean {\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(str)\n}\n\n/**\n * Render the `ContextKeys` augmentation from discovered context-decorator\n * keys. Each `key:` literal becomes a `'<key>': true` entry — a key-only\n * registry (the value type is irrelevant; `true` is conventional) so\n * `dependsOn: ['<key>']` is autocompleted + typo-checked without forcing\n * a value type into `ContextMeta`. Non-identifier keys are quoted.\n */\nexport function renderContextKeys(keys: readonly { key: string }[]): string {\n const unique = [...new Set(keys.map((k) => k.key))].toSorted()\n const body = unique\n .map((k) => ` ${isIdentifierKey(k) ? k : JSON.stringify(k)}: true`)\n .join('\\n')\n return `${HEADER}\ndeclare module '@forinda/kickjs' {\n /**\n * Key-only registry of every context key produced by a\n * \\`defineContextDecorator\\` / \\`defineHttpContextDecorator\\` in the\n * project. Feeds \\`dependsOn\\` typo-checking. Value types live in\n * \\`ContextMeta\\`; this only records that the key exists.\n */\n interface ContextKeys {\n${body}\n }\n}\n\nexport {}\n`\n}\n\n/** Render a string-literal union type containing the given names */\nexport function renderUnion(typeName: string, names: string[], emptyComment: string): string {\n if (names.length === 0) {\n return `${HEADER}\n// ${emptyComment}\nexport type ${typeName} = never\n`\n }\n const sorted = [...new Set(names)].toSorted()\n return `${HEADER}\nexport type ${typeName} =\n${sorted.map((n) => ` | '${n}'`).join('\\n')}\n`\n}\n\n/**\n * Build the `ServiceToken` union members — class names (namespaced on\n * collision), plus `createToken('name')` and `@Inject('literal')`\n * literals — so tooling autocomplete sees every known token.\n */\nexport function buildServiceTokens(\n classes: DiscoveredClass[],\n tokens: DiscoveredToken[],\n injects: DiscoveredInject[],\n collidingNames: Set<string>,\n): string[] {\n const classTokens = classes\n .filter((c) => REGISTRY_DECORATORS.has(c.decorator))\n .map((c) => (collidingNames.has(c.className) ? namespacedKeyFor(c) : c.className))\n return [...classTokens, ...tokens.map((t) => t.name), ...injects.map((i) => i.name)]\n}\n\n/** Build the `ModuleToken` union members — discovered `@Module` class names. */\nexport function buildModuleTokens(classes: DiscoveredClass[]): string[] {\n return classes.filter((c) => c.decorator === 'Module').map((c) => c.className)\n}\n\n/**\n * Render the `KickJsPluginRegistry` augmentation. Each entry maps the\n * literal `name` field of a plugin/adapter to a marker type (the\n * registry value isn't load-bearing at runtime — `dependsOn` only cares\n * about `keyof`, so any non-`never` type works). We emit `'plugin'` /\n * `'adapter'` strings so DevTools can later read the registry to tell\n * the kinds apart without a second source of truth.\n *\n * When the project has no discoverable plugins/adapters, the\n * augmentation is intentionally empty rather than skipped so the\n * `keyof` constraint resolves to `never` (harmless — `dependsOn: []`\n * still works).\n */\nexport function renderPlugins(items: DiscoveredPluginOrAdapter[]): string {\n // Dedupe by name — two declarations with the same name are a runtime\n // boot-time error in `mount-sort.ts`; we surface the conflict via a\n // single registry entry rather than a duplicate-key TS error.\n const byName = new Map<string, DiscoveredPluginOrAdapter>()\n for (const item of items) {\n if (!byName.has(item.name)) byName.set(item.name, item)\n }\n\n const sorted = [...byName.values()].toSorted((a, b) => a.name.localeCompare(b.name))\n const entries = sorted.map((item) => ` '${item.name}': '${item.kind}'`).join('\\n')\n\n const body = entries\n ? entries\n : ' // (no plugins/adapters discovered yet — `defineAdapter`/`definePlugin` calls feed this)'\n\n return `${HEADER}\ndeclare module '@forinda/kickjs' {\n /**\n * Map of every plugin/adapter \\`name\\` discovered in the project. The\n * value type is the kind tag (\\`'plugin'\\` or \\`'adapter'\\`); the\n * \\`keyof\\` of this interface narrows \\`dependsOn\\` so misspelled deps\n * become compile errors instead of boot-time \\`MissingMountDepError\\`.\n */\n interface KickJsPluginRegistry {\n${body}\n }\n}\n\nexport {}\n`\n}\n\n/**\n * Render the augmentation manifest — one block per `defineAugmentation`\n * call discovered in the project. The output is a `.d.ts` file that does\n * nothing at runtime but acts as in-IDE documentation.\n */\nexport function renderAugmentations(items: DiscoveredAugmentation[]): string {\n if (items.length === 0) {\n return `${HEADER}\n// No augmentations discovered.\n//\n// Plugins advertise augmentable interfaces via:\n//\n// import { defineAugmentation } from '@forinda/kickjs'\n// defineAugmentation('FeatureFlags', {\n// description: 'Feature flag shape consumed by FlagsPlugin',\n// example: '{ beta: boolean; rolloutPercentage: number }',\n// })\n//\n// See \\`docs/guide/typegen.md#augmentations\\` for the full pattern.\nexport {}\n`\n }\n\n // Dedupe by name — multiple plugins shouldn't claim the same name,\n // but if they do we keep the first.\n const byName = new Map<string, DiscoveredAugmentation>()\n for (const item of items) {\n if (!byName.has(item.name)) byName.set(item.name, item)\n }\n\n const blocks: string[] = []\n for (const item of [...byName.values()].toSorted((a, b) => a.name.localeCompare(b.name))) {\n const docLines: string[] = []\n if (item.description) {\n for (const line of item.description.split('\\n')) docLines.push(` * ${line}`)\n }\n if (item.example) {\n docLines.push(` * @example`, ` * \\`\\`\\`ts`)\n for (const line of item.example.split('\\n')) docLines.push(` * ${line}`)\n docLines.push(` * \\`\\`\\``)\n }\n docLines.push(` * @see ${item.relativePath}`)\n blocks.push(\n ['/**', ...docLines, ' */', `export interface ${item.name}Augmentation {}`].join('\\n'),\n )\n }\n\n return `${HEADER}\n// Catalogue of augmentable interfaces in this project. The interfaces\n// below are documentation only — augment the source-of-truth interfaces\n// in your own \\`d.ts\\` files (the framework declares the actual types).\n\n${blocks.join('\\n\\n')}\n`\n}\n","/**\n * Token convention validator (architecture.md §22.4 #1).\n *\n * Warns (never errors) on `createToken('literal')` calls whose literal\n * doesn't match the convention from §22.2:\n *\n * <scope>/<PascalKey>[/<suffix>][:<instance>[:<extra>]*]\n *\n * Where `<scope>` is lowercase + may start with the reserved `kick/`\n * prefix for first-party tokens. Legacy framework tokens that started\n * with `kickjs.` (pre-§22) are exempt — they get migrated alongside\n * the first-party adapter migrations.\n *\n * The matching reserved-prefix check (third-party tokens squatting\n * `kick/`) is the responsibility of `@forinda/kickjs-lint`'s\n * `token-reserved-prefix` rule, not the typegen layer — different\n * audience (adopter codebase) and different default severity.\n *\n * @module @forinda/kickjs-cli/typegen/token-conventions\n */\n\nimport type { DiscoveredToken } from './scanner'\n\n/**\n * Regex for the §22.2 token shape. Breakdown:\n *\n * - `^(kick\\/)?` — optional reserved framework prefix.\n * - `([a-z][\\w-]*\\/[A-Z]\\w*)` — `<scope>/<PascalKey>`. Scope is\n * lowercase, key is PascalCase.\n * - `(\\/.+)?` — optional `/suffix` for sub-flavours\n * (e.g. `mycorp/Cache/redis`).\n * - `(:[a-z][\\w-]+(:[a-z][\\w-]+)*)?` — optional `:instance` (and\n * further `:extra` colon-sections) for `.scoped()` shards.\n */\nconst TOKEN_CONVENTION_REGEX =\n /^(kick\\/)?([a-z][\\w-]*\\/[A-Z]\\w*)(\\/.+)?(:[a-z][\\w-]+(:[a-z][\\w-]+)*)?$/\n\nconst LEGACY_PREFIX = 'kickjs.'\n\nexport interface TokenConventionWarning {\n token: string\n variable: string | null\n filePath: string\n reason: string\n suggestion?: string\n}\n\nexport function validateTokenConventions(\n tokens: readonly DiscoveredToken[],\n): TokenConventionWarning[] {\n const warnings: TokenConventionWarning[] = []\n for (const token of tokens) {\n const name = token.name\n if (name.startsWith(LEGACY_PREFIX)) continue\n if (TOKEN_CONVENTION_REGEX.test(name)) continue\n warnings.push({\n token: name,\n variable: token.variable,\n filePath: token.relativePath,\n reason: 'does not match `<scope>/<PascalKey>[/<suffix>][:<instance>]`',\n suggestion: suggestRename(name),\n })\n }\n return warnings\n}\n\nfunction suggestRename(name: string): string | undefined {\n if (/^[A-Z]\\w*$/.test(name)) {\n return `'<scope>/${name}' (e.g. 'mycorp/${name}')`\n }\n if (name.includes('.')) {\n return `consider '<scope>/PascalKey' instead of dotted form`\n }\n const slashLower = /^([a-z][\\w-]*)\\/([a-z]\\w*)$/.exec(name)\n if (slashLower) {\n const [, scope, key] = slashLower\n return `'${scope}/${key.charAt(0).toUpperCase()}${key.slice(1)}'`\n }\n return undefined\n}\n","/**\n * Walks every `assetMap` entry's source directory + emits a typed\n * `KickAssets` ambient augmentation (assets-plan.md PR 4). Generates\n * `.kickjs/types/assets.d.ts` so adopters get autocomplete on\n * `assets.<namespace>.<key>` and `@Asset('<namespace>/<key>')`.\n *\n * Pure module — no side effects beyond what the caller does with the\n * returned content. Mirrors the shape of `renderPlugins` /\n * `renderRegistry` in the generator so the typegen output stays\n * consistent across surfaces.\n *\n * @module @forinda/kickjs-cli/typegen/asset-types\n */\n\nimport { statSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { globSync } from 'glob'\nimport { groupAssetKeys } from '@forinda/kickjs'\nimport type { AssetMapEntry } from '../config'\n\nexport interface DiscoveredAssetEntry {\n namespace: string\n /**\n * Path under the namespace — what comes after the `<namespace>/`\n * prefix in the logical key. Stripped of extension when the file's\n * basename was unique within the namespace, or carries the full\n * extension when a collision was auto-resolved.\n */\n key: string\n}\n\nexport interface DiscoveredAssets {\n entries: DiscoveredAssetEntry[]\n count: number\n}\n\nexport function discoverAssets(\n assetMap: Record<string, AssetMapEntry> | undefined,\n cwd: string,\n): DiscoveredAssets {\n if (!assetMap) return { entries: [], count: 0 }\n\n const seen = new Map<string, DiscoveredAssetEntry>()\n for (const [namespace, entry] of Object.entries(assetMap)) {\n if (!entry || typeof entry.src !== 'string') continue\n const srcAbs = resolve(cwd, entry.src)\n if (!isDir(srcAbs)) continue\n // Mirror build.ts: same `glob` engine, same defaults — typegen +\n // build agree on what counts as an asset, including full minimatch\n // patterns (extglob, negation, etc.) that the old lite matcher\n // silently treated as match-all.\n const matches = globSync(entry.glob ?? '**/*', {\n cwd: srcAbs,\n nodir: true,\n dot: false,\n posix: true,\n })\n matches.sort()\n // Run the same key-shaping logic the build pipeline + dev resolver\n // use, so the emitted type matches the manifest exactly. Auto-mode\n // keeps extensions on collision groups; singletons stay stripped.\n const { pairs } = groupAssetKeys(namespace, matches, {\n strategy: entry.keys ?? 'auto',\n })\n for (const { key: logical } of pairs) {\n // The helper returns the full `<namespace>/<...>` form; strip\n // the namespace prefix so the entry shape matches the legacy\n // contract (key = path under namespace).\n const subKey = logical.slice(namespace.length + 1)\n seen.set(logical, { namespace, key: subKey })\n }\n }\n return { entries: [...seen.values()], count: seen.size }\n}\n\nexport function renderAssetTypes(discovered: DiscoveredAssets): string {\n const HEADER = `/* eslint-disable */\n// AUTO-GENERATED by \\`kick typegen\\`. DO NOT EDIT.\n// Re-run with \\`kick typegen\\` or rely on \\`kick dev\\` to refresh.\n`\n\n if (discovered.entries.length === 0) {\n return `${HEADER}\ndeclare module '@forinda/kickjs' {\n /**\n * Map of every typed asset discovered in the project's assetMap.\n * (No assetMap entries discovered yet — declare with\n * \\`assetMap: { name: { src: 'src/...' } }\\` in kick.config.ts.)\n */\n interface KickAssets {}\n}\n\nexport {}\n`\n }\n\n const tree: TreeNode = {}\n for (const entry of discovered.entries) {\n const path = `${entry.namespace}/${entry.key}`.split('/')\n let node = tree\n for (let i = 0; i < path.length - 1; i++) {\n const part = path[i]\n const existing = node[part]\n if (existing === LEAF) {\n // The intermediate path was previously claimed by a leaf —\n // collision between a file (`mails/welcome.ejs`) and a\n // directory (`mails/welcome/x.ejs`). Promote to a sub-tree\n // so the directory wins (consistent with the file-tree\n // semantics; the build pipeline emits a runtime warning for\n // the colliding leaf).\n const promoted: TreeNode = {}\n node[part] = promoted\n node = promoted\n } else {\n if (!existing) node[part] = {}\n node = node[part] as TreeNode\n }\n }\n const leaf = path[path.length - 1]\n if (typeof node[leaf] === 'object') {\n // The leaf collides with an already-built subtree. Skip; the\n // directory wins (matches the promote-to-subtree branch above).\n continue\n }\n node[leaf] = LEAF\n }\n\n const body = renderTree(tree, ' ')\n return `${HEADER}\ndeclare module '@forinda/kickjs' {\n /**\n * Map of every typed asset discovered in the project's assetMap.\n * Each leaf is a \\`() => string\\` thunk that returns the resolved\n * absolute path for the file in the current run mode (dev → src,\n * prod → dist).\n */\n interface KickAssets {\n${body}\n }\n}\n\nexport {}\n`\n}\n\nconst LEAF = Symbol('asset-leaf')\ntype TreeNode = { [key: string]: TreeNode | typeof LEAF }\n\nfunction renderTree(node: TreeNode, indent: string): string {\n const keys = Object.keys(node).toSorted()\n const lines: string[] = []\n for (const key of keys) {\n const child = node[key]\n const safeKey = isIdentifier(key) ? key : JSON.stringify(key)\n if (child === LEAF) {\n lines.push(`${indent}${safeKey}: () => string`)\n } else {\n lines.push(`${indent}${safeKey}: {`)\n lines.push(renderTree(child, `${indent} `))\n lines.push(`${indent}}`)\n }\n }\n return lines.join('\\n')\n}\n\nfunction isIdentifier(str: string): boolean {\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(str)\n}\n\nfunction isDir(path: string): boolean {\n try {\n return statSync(path).isDirectory()\n } catch {\n return false\n }\n}\n","/**\n * Public entry point for the KickJS typegen module.\n *\n * Used by:\n * - `kick typegen` (one-shot or watch mode)\n * - `kick dev` (auto-runs once before Vite starts; refreshes when files change)\n *\n * @module @forinda/kickjs-cli/typegen\n */\n\nimport { resolve, basename, dirname, join } from 'node:path'\nimport { mkdir, readdir, stat, unlink, writeFile } from 'node:fs/promises'\nimport { scanProject, scanProjectIncremental, type ScanDelta, type ScanResult } from './scanner'\nimport {\n buildModuleTokens,\n buildServiceTokens,\n REGISTRY_DECORATORS,\n TokenCollisionError,\n} from './render/manifest'\nimport { validateTokenConventions, type TokenConventionWarning } from './token-conventions'\nimport { discoverAssets } from './asset-types'\nimport type { AssetMapEntry } from '../config'\nimport type { TypegenPluginResult } from './plugin'\n\nexport type {\n DiscoveredClass,\n DiscoveredToken,\n DiscoveredInject,\n DiscoveredEnv,\n DiscoveredPluginOrAdapter,\n DiscoveredAugmentation,\n ClassCollision,\n ScanResult,\n} from './scanner'\nexport { TokenCollisionError } from './render/manifest'\nexport { validateTokenConventions, type TokenConventionWarning } from './token-conventions'\n\n/**\n * Result of a typegen run — useful for logging and tests. Computed from\n * the scan result + asset discovery; the per-file emission itself is now\n * owned entirely by the typegen plugins (see `builtin/`).\n */\nexport interface GenerateResult {\n /** Number of registry-decorated classes (KickJsRegistry entries) */\n registryEntries: number\n /** Number of unique service tokens (classes + createToken + @Inject literals) */\n serviceTokens: number\n /** Number of module tokens */\n moduleTokens: number\n /** Number of route entries */\n routeEntries: number\n /** Number of unique plugin/adapter names */\n pluginEntries: number\n /** Number of unique `defineAugmentation` calls */\n augmentationEntries: number\n /** Number of typed asset entries */\n assetEntries: number\n /** Whether a typed env augmentation will be emitted */\n envWritten: boolean\n /** Files written this pass (barrel + plugin outputs), for the sweep */\n written: string[]\n /** Number of collisions (only > 0 with allowDuplicates) */\n resolvedCollisions: number\n}\n\n/** Options for `runTypegen` */\nexport interface RunTypegenOptions {\n /** Project root (defaults to `process.cwd()`) */\n cwd?: string\n /** Source directory to scan (defaults to `src`) */\n srcDir?: string\n /** Output directory (defaults to `.kickjs/types`) */\n outDir?: string\n /** Suppress console output */\n silent?: boolean\n /**\n * When `true`, duplicate class names are auto-namespaced by file path\n * instead of throwing. `kick dev` enables this so the dev server is\n * never blocked by an in-progress rename. CLI default is `false` so\n * `kick typegen` (and CI) catches collisions early. */\n allowDuplicates?: boolean\n /**\n * Disable the persistent per-file scan cache (`.kickjs/cache/scan.json`).\n * Every file is re-read + re-extracted from cold. Escape hatch for the\n * rare `mtimeMs:size` signature collision (a file edited so fast its\n * mtime + size are unchanged) where the cache would serve a stale\n * extract. Wired to `kick typegen --no-cache` / `kick dev --no-typegen-cache`.\n */\n noCache?: boolean\n /**\n * Schema validator used to derive `body`/`query`/`params` types from\n * route metadata. Currently only `'zod'` is supported; `false` (the\n * default) leaves these fields as `unknown`. Loaded from\n * `kick.config.ts` `typegen.schemaValidator` when invoked via the CLI.\n */\n schemaValidator?: 'zod' | 'kickjs-schema' | false\n /**\n * Path to the env schema file (relative to `cwd`). The file must\n * default-export a `defineEnv(...)` schema for the typed `KickEnv`\n * augmentation to be emitted. Defaults to `'src/env.ts'`. Set to\n * `false` to disable env typing entirely.\n */\n envFile?: string | false\n /**\n * Asset map from `kick.config.ts`. When set, `runTypegen` walks\n * each entry's `src` directory + emits `.kickjs/types/assets.d.ts`\n * augmenting `KickAssets` for autocomplete on `assets.x.y()` and\n * `@Asset('x/y')`. Omit to skip the asset typegen pass entirely.\n */\n assetMap?: Record<string, AssetMapEntry>\n /**\n * Whether `runTypegen` should also run the TypegenPlugin pipeline\n * (`runAllPluginTypegens`) after the legacy generator pass. Defaults\n * to `true` so single-shot callers (kick g, commands/typegen, tests)\n * keep getting a fully-refreshed `.kickjs/types/` from one entry\n * point. `watchTypegen` flips this to `false` because it manages\n * the plugin pass itself + would otherwise double-run it on every\n * filesystem trigger.\n */\n runPlugins?: boolean\n /**\n * Exact watcher delta (Vite chokidar events). When present, the scan\n * runs incrementally — re-extracting only the changed files and\n * skipping the directory walk — and the same delta is forwarded to\n * the plugin pass. `kick dev` supplies this on every file change.\n */\n changedFiles?: ScanDelta\n}\n\n/** Resolve options to absolute paths */\nfunction resolveOptions(opts: RunTypegenOptions): {\n cwd: string\n srcDir: string\n outDir: string\n silent: boolean\n allowDuplicates: boolean\n schemaValidator: 'zod' | 'kickjs-schema' | false\n envFile: string | false\n} {\n const cwd = opts.cwd ?? process.cwd()\n return {\n cwd,\n srcDir: resolve(cwd, opts.srcDir ?? 'src'),\n outDir: resolve(cwd, opts.outDir ?? '.kickjs/types'),\n silent: opts.silent ?? false,\n allowDuplicates: opts.allowDuplicates ?? false,\n schemaValidator: opts.schemaValidator ?? false,\n envFile: opts.envFile ?? 'src/env.ts',\n }\n}\n\n/**\n * Run a single typegen pass: scan source files, generate `.d.ts` files.\n *\n * Returns the discovered scan result alongside the generation result so\n * callers (`kick dev`, devtools) can log them or feed them to other tools.\n *\n * Throws `TokenCollisionError` if duplicate class names are found and\n * `allowDuplicates` is false.\n */\nexport async function runTypegen(opts: RunTypegenOptions = {}): Promise<{\n scan: ScanResult\n result: GenerateResult\n /** Token convention warnings — empty when every literal matches §22.2. */\n tokenWarnings: TokenConventionWarning[]\n}> {\n const { cwd, srcDir, outDir, silent, allowDuplicates, envFile } = resolveOptions(opts)\n\n const start = Date.now()\n const scanOpts = {\n root: srcDir,\n cwd,\n // Omitting cacheDir disables the persistent scan cache entirely —\n // every file is re-read cold (the --no-cache escape hatch).\n cacheDir: opts.noCache ? undefined : resolve(cwd, '.kickjs', 'cache'),\n // Pass through unless explicitly disabled\n envFile: envFile === false ? undefined : envFile,\n }\n const scan = opts.changedFiles\n ? await scanProjectIncremental(scanOpts, opts.changedFiles)\n : await scanProject(scanOpts)\n\n // Collision gate. This used to live inside the legacy generator\n // (`generateTypes` threw before writing). Now that every file is\n // emitted by an isolated plugin, the gate must run here — at the\n // orchestration level, before any plugin executes — so a duplicate\n // class name fails the whole command loudly instead of being silently\n // namespaced by the registry plugin.\n if (scan.collisions.length > 0 && !allowDuplicates) {\n throw new TokenCollisionError(scan.collisions)\n }\n\n const assets = discoverAssets(opts.assetMap, cwd)\n\n // Every `.kickjs/types/*` file is now owned by a typegen plugin\n // (builtin/ + adopter plugins). Run the pipeline by default so\n // single-shot callers (kick g <leaf> / kick new / tests) stay on one\n // entry point and see a fully-refreshed `.kickjs/types/` after the\n // call returns. The `kick typegen` / `kick dev` / `kick build` /\n // watch paths opt out (`runPlugins: false`) because they drive the\n // plugin pass externally (for --check + per-plugin status) and would\n // otherwise double-run it — they call `writeTypegenArtifacts`\n // themselves after their own plugin pass.\n let pluginResults: TypegenPluginResult[] = []\n const written: string[] = []\n if (opts.runPlugins !== false) {\n try {\n const { runAllPluginTypegens } = await import('./run-plugins')\n const { loadKickConfig } = await import('../config')\n const pluginConfig = await loadKickConfig(cwd)\n pluginResults = await runAllPluginTypegens({\n cwd,\n config: pluginConfig,\n silent: true,\n changedFiles: opts.changedFiles,\n })\n } catch (err) {\n // Broken plugins shouldn't block dev tooling. The runner already\n // isolates each plugin (per-plugin try/catch), so reaching here\n // means a non-plugin failure (scanner, fs). Surfacing as a throw\n // would abort the wider command (kick g, kick new); log + continue.\n if (!silent) {\n const msg = err instanceof Error ? err.message : String(err)\n console.warn(` kick typegen: plugin pipeline failed (${msg}) — continuing`)\n }\n }\n written.push(...(await writeTypegenArtifacts(outDir, pluginResults, silent)))\n }\n\n const tokenWarnings = validateTokenConventions(scan.tokens)\n const result = buildGenerateResult(scan, assets.count, written)\n const elapsed = Date.now() - start\n\n if (!silent) {\n const where = outDir.replace(cwd + '/', '')\n const collisionNote =\n result.resolvedCollisions > 0 ? `, ${result.resolvedCollisions} collisions namespaced` : ''\n const envNote = result.envWritten ? ', env typed' : ''\n const pluginNote = result.pluginEntries > 0 ? `, ${result.pluginEntries} plugins/adapters` : ''\n const augNote =\n result.augmentationEntries > 0 ? `, ${result.augmentationEntries} augmentations` : ''\n const assetNote = result.assetEntries > 0 ? `, ${result.assetEntries} assets` : ''\n console.log(\n ` kick typegen → ${result.serviceTokens} services, ${result.routeEntries} routes, ${result.moduleTokens} modules${pluginNote}${augNote}${assetNote}${envNote}${collisionNote} → ${where} (${elapsed}ms)`,\n )\n if (tokenWarnings.length > 0) {\n console.warn(\n ` kick typegen: ${tokenWarnings.length} token(s) don't match the §22.2 convention:`,\n )\n for (const warning of tokenWarnings) {\n const variableNote = warning.variable ? ` [${warning.variable}]` : ''\n console.warn(\n ` '${warning.token}' (${warning.filePath})${variableNote} — ${warning.reason}`,\n )\n if (warning.suggestion) {\n console.warn(` → suggestion: ${warning.suggestion}`)\n }\n }\n }\n if (scan.orphanedClasses.length > 0) {\n // forinda/kick-js#235 §4 — decorated classes sitting inside a\n // module directory whose globs don't pick them up. At runtime\n // the decorator never fires; downstream code paths get\n // confusing `MissingContributorError` or silent misroutes.\n console.warn(\n ` kick typegen: ${scan.orphanedClasses.length} decorated class(es) not matched by any module's import.meta.glob():`,\n )\n for (const orphan of scan.orphanedClasses) {\n console.warn(` @${orphan.decorator} ${orphan.className} (${orphan.relativePath})`)\n console.warn(` → not picked up by any glob in ${orphan.moduleFilePath}`)\n }\n }\n }\n\n return { scan, result, tokenWarnings }\n}\n\n/** Derive the logging/test `GenerateResult` from a scan — no files written here. */\nfunction buildGenerateResult(\n scan: ScanResult,\n assetCount: number,\n written: string[],\n): GenerateResult {\n const colliding = new Set(scan.collisions.map((c) => c.className))\n const registryClasses = scan.classes.filter((c) => REGISTRY_DECORATORS.has(c.decorator))\n const serviceNames = buildServiceTokens(scan.classes, scan.tokens, scan.injects, colliding)\n return {\n registryEntries: registryClasses.length,\n serviceTokens: new Set(serviceNames).size,\n moduleTokens: buildModuleTokens(scan.classes).length,\n routeEntries: scan.routes.length,\n pluginEntries: new Set(scan.pluginsAndAdapters.map((p) => p.name)).size,\n augmentationEntries: new Set(scan.augmentations.map((a) => a.name)).size,\n assetEntries: assetCount,\n envWritten: scan.env !== null,\n written,\n resolvedCollisions: scan.collisions.length,\n }\n}\n\n/**\n * Post-plugin-pass finalisation: write the `.kickjs/.gitignore` guard\n * and sweep stale legacy files. Shared by `runTypegen` (single-shot\n * mode) and the split-mode callers (`kick typegen` / `kick dev` /\n * watch) so the artifact-writing + sweep stay identical across both.\n *\n * No barrel `index.d.ts` is emitted: the scaffolded tsconfig pulls\n * `.kickjs/types/**` in via `include` globs, so every `declare module`\n * / `declare global` augmentation in the per-plugin files applies by\n * inclusion. The old barrel + its `ServiceToken`/`ModuleToken`\n * re-exports were redundant; they're swept as legacy orphans.\n *\n * Returns the list of files considered \"written\" this pass (the plugin\n * outputs) for the caller's bookkeeping.\n */\nexport async function writeTypegenArtifacts(\n outDir: string,\n pluginResults: readonly TypegenPluginResult[],\n silent: boolean,\n): Promise<string[]> {\n await mkdir(outDir, { recursive: true })\n // `.kickjs/.gitignore` keeps the generated tree out of git even if the\n // project's root .gitignore predates the `.kickjs/` convention.\n await writeFile(\n join(dirname(outDir), '.gitignore'),\n '# Auto-generated by kick typegen\\n*\\n',\n 'utf-8',\n )\n const written = pluginResults.filter((r) => r.outFile).map((r) => r.outFile as string)\n await sweepStaleTypegen(outDir, written, pluginResults, silent)\n return written\n}\n\n/**\n * Watch mode for `kick typegen --watch`.\n *\n * Uses Node's built-in `fs.watch` (recursive, available on Linux 22+ and\n * macOS 19+). Falls back gracefully if recursive watch is not supported.\n *\n * Debounces re-runs by 100ms so a bulk file change (e.g. `kick g module`\n * creating 5 files at once) emits one regen, not five.\n *\n * In watch mode collisions are reported but never thrown — the watcher\n * keeps running so the user can fix the rename and the next scan\n * recovers automatically.\n *\n * Returns a `stop()` function that closes the watcher.\n */\nexport async function watchTypegen(opts: RunTypegenOptions = {}): Promise<() => void> {\n const resolved = resolveOptions(opts)\n const { srcDir, silent, cwd } = resolved\n // Watch mode always tolerates collisions — otherwise an in-progress\n // rename would crash the dev loop. The error is still printed.\n // `runPlugins: false` keeps `runTypegen` from double-running the\n // plugin pipeline; the watcher invokes `runPlugins()` explicitly\n // after each `runLegacy()` so both phases land before the sweep.\n // `resolved` comes from resolveOptions, which doesn't carry noCache —\n // thread it through explicitly so `kick typegen --watch --no-cache`\n // disables the scan cache on every rebuild too.\n const runOpts: RunTypegenOptions = {\n ...resolved,\n allowDuplicates: true,\n runPlugins: false,\n noCache: opts.noCache,\n }\n\n // Polling is the right strategy for Docker bind mounts, WSL crosses,\n // NFS, and some kernel/filesystem combos where `fs.watch` returns\n // without errors but events silently drop. Adopters opt in via\n // `KICKJS_WATCH_POLLING=1`; default stays event-based (lower CPU).\n const forcePolling =\n process.env.KICKJS_WATCH_POLLING === '1' || process.env.KICKJS_WATCH_POLLING === 'true'\n\n // Lazy-import the plugin pipeline + config loader to avoid an eager\n // module-eval cycle (this file is reachable from plugin/builtins via\n // commands/typegen → ../typegen).\n const [{ runAllPluginTypegens }, { loadKickConfig }] = await Promise.all([\n import('./run-plugins'),\n import('../config'),\n ])\n const pluginConfig = await loadKickConfig(cwd)\n // `runLegacy` now just runs the scan + collision gate (collisions are\n // tolerated in watch mode via allowDuplicates) and refreshes the\n // logging counts; all file emission happens in `runPlugins`.\n const runLegacy = async () => {\n try {\n await runTypegen({ ...runOpts })\n } catch (err) {\n if (silent) return\n if (err instanceof TokenCollisionError) {\n console.error('\\n' + err.message + '\\n')\n } else {\n const msg = err instanceof Error ? err.message : String(err)\n console.error(` kick typegen failed: ${msg}`)\n }\n }\n }\n const runPlugins = async () => {\n try {\n const pluginResults = await runAllPluginTypegens({\n cwd,\n config: pluginConfig,\n silent: true,\n })\n await writeTypegenArtifacts(resolved.outDir, pluginResults, true)\n } catch {\n /* swallow — watcher must never die */\n }\n }\n\n // Initial run — scan/gate pass first, then plugin typegens + artifacts.\n await runLegacy()\n await runPlugins()\n\n const { watch } = await import('node:fs')\n\n let timer: ReturnType<typeof setTimeout> | null = null\n const trigger = (filename: string | null) => {\n // Only react to TypeScript source changes; ignore everything else\n if (!filename) return\n if (!/\\.(ts|tsx|mts|cts)$/.test(filename)) return\n if (filename.includes('.kickjs')) return\n if (filename.endsWith('.d.ts')) return\n\n if (timer) clearTimeout(timer)\n timer = setTimeout(() => {\n void runLegacy().then(runPlugins)\n }, 100)\n }\n\n // Forced-polling path — skip fs.watch entirely and just re-scan\n // periodically. The 2s interval matches the existing fallback so\n // adopters who flip the env var don't see a dramatic CPU jump.\n if (forcePolling) {\n if (!silent) {\n console.log(' kick typegen: polling mode (KICKJS_WATCH_POLLING)')\n }\n // Must drive BOTH phases — the plugin pass owns every\n // `.kickjs/types/kick__*` file now, so scan/gate alone (runLegacy)\n // would never refresh types on the polling path. Both closures\n // swallow their own errors, so the interval loop never dies.\n const interval = setInterval(() => {\n void runLegacy().then(runPlugins)\n }, 2000)\n return () => clearInterval(interval)\n }\n\n let watcher: ReturnType<typeof watch>\n try {\n watcher = watch(srcDir, { recursive: true }, (_event, filename) => {\n trigger(filename)\n })\n } catch (err: any) {\n if (!silent) {\n console.warn(\n ` kick typegen: watch mode unavailable (${err?.message ?? err}). Falling back to polling.`,\n )\n }\n // Polling fallback — re-run both phases every 2s (see forcePolling\n // note above: the plugin pass is the sole emitter, so scan/gate\n // alone would never refresh types).\n const interval = setInterval(() => {\n void runLegacy().then(runPlugins)\n }, 2000)\n return () => clearInterval(interval)\n }\n\n return () => {\n if (timer) clearTimeout(timer)\n watcher.close()\n }\n}\n\n/**\n * Remove orphaned typegen output. The legacy generator emitted\n * `assets.d.ts`, `env.ts`, and `routes.ts` directly; once those carved\n * into the `kick/assets`, `kick/env`, and `kick/routes` plugins, the\n * legacy file names became stale on disk for any project that had run\n * an older CLI. Without a sweep, both copies coexist and adopters get\n * duplicated `KickAssets` / `KickEnv` / `KickRoutes` augmentations.\n *\n * Strategy: enumerate the top level of `outDir`, keep the union of\n * generator-written files + plugin-written files, unlink anything\n * else. Subdirectories are left alone — typegen never creates them.\n * Errors are swallowed (silent → no log) so a transient ENOENT or\n * permission glitch never aborts the wider command.\n */\nexport async function sweepStaleTypegen(\n outDir: string,\n generatorWritten: readonly string[],\n pluginResults: readonly TypegenPluginResult[],\n silent: boolean,\n): Promise<string[]> {\n const expected = new Set<string>()\n for (const file of generatorWritten) expected.add(basename(file))\n for (const r of pluginResults) {\n if (r.outFile) expected.add(basename(r.outFile))\n }\n let entries: string[]\n try {\n entries = await readdir(outDir)\n } catch {\n return []\n }\n const removed: string[] = []\n for (const name of entries) {\n // Allowlist, NOT denylist. The earlier \"delete anything not in the\n // expected set\" form was a footgun: if the plugin pass aborted\n // (e.g. one plugin threw and the runner bubbled it up, so\n // `pluginResults` came back empty), the expected set lost\n // `kick__routes.ts` / `kick__assets.d.ts` / `kick__env.ts` and the\n // sweep deleted those good files — wiping controller route types\n // project-wide. We now only remove the specific pre-carve filenames\n // the legacy generator used to emit, and only when the current pass\n // didn't (re)write them. Anything else is left untouched.\n if (!LEGACY_ORPHAN_FILES.has(name)) continue\n if (expected.has(name)) continue\n const abs = resolve(outDir, name)\n try {\n const s = await stat(abs)\n if (!s.isFile()) continue\n await unlink(abs)\n removed.push(name)\n } catch {\n // Best-effort; don't crash the typegen pass on a stat/unlink miss.\n }\n }\n if (removed.length > 0 && !silent) {\n console.log(` kick typegen: swept ${removed.length} stale file(s): ${removed.join(', ')}`)\n }\n return removed\n}\n\n/**\n * Filenames the legacy monolithic generator (`generator.ts`, now\n * removed) emitted directly, before every augmentation became its own\n * typegen plugin. A project that upgrades across this change has these\n * as orphans on disk; the sweep removes them so the augmentations aren't\n * declared twice and the dropped `index.d.ts` barrel doesn't linger.\n *\n * Split into two waves only for documentation:\n * - `assets.d.ts` / `env.ts` / `routes.ts` — the M2.B-T8 carve.\n * - `registry.d.ts` / `services.d.ts` / `modules.d.ts` / `plugins.d.ts`\n * / `augmentations.d.ts` / `index.d.ts` — this plugin-only refactor.\n *\n * None collide with any current plugin output (all `kick__*`), so the\n * sweep can never touch live output.\n */\nconst LEGACY_ORPHAN_FILES: ReadonlySet<string> = new Set([\n 'assets.d.ts',\n 'env.ts',\n 'routes.ts',\n 'registry.d.ts',\n 'services.d.ts',\n 'modules.d.ts',\n 'plugins.d.ts',\n 'augmentations.d.ts',\n 'index.d.ts',\n])\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"typegen-CuciH349.mjs","names":["walk","toRelative","extractPathParams","extractApiQueryParams"],"sources":["../src/typegen/scanner-cache.ts","../src/typegen/extract-ast.ts","../src/typegen/scanner.ts","../src/typegen/render/manifest.ts","../src/typegen/token-conventions.ts","../src/typegen/asset-types.ts","../src/typegen/index.ts"],"sourcesContent":["/**\n * Persistent per-file extraction cache for the typegen scanner.\n *\n * The scanner's dominant cost on large projects is reading every\n * `src/**\\/*.ts` file and running the regex extractors over each one.\n * On a watch/rebuild loop almost nothing has changed, yet the old\n * `scanProject` re-read and re-scanned the entire tree every time.\n *\n * This cache stores the per-file extraction result (`FileExtract`)\n * keyed by a cheap filesystem signature (`mtimeMs:size`). On the next\n * scan we `stat()` each file — a near-free syscall, no content read —\n * and reuse the cached extract whenever the signature is unchanged.\n * Only genuinely-changed files pay the readFile + regex cost.\n *\n * We deliberately key on `mtimeMs:size` rather than a content hash:\n * hashing requires reading the file, which is exactly the cost we are\n * trying to avoid. The cross-file join phase in `scanProject` always\n * re-runs over the full (cached + fresh) extract set, so a stale entry\n * can never produce an inconsistent `ScanResult` — the worst case of a\n * signature collision (same size, identical mtime, different content)\n * is a missed re-scan, which `--no-cache` / a `clean` sidesteps.\n *\n * @module @forinda/kickjs-cli/typegen/scanner-cache\n */\n\nimport { mkdir, readFile, stat, writeFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\nimport type { FileExtract } from './scanner'\n\n/** Bump when the shape of `FileExtract` (or any extractor) changes. */\n// v2: per-file extraction switched to AST-first (extract-ast.ts) — v1\n// entries hold regex-era results that can differ (template-literal\n// paths, aliased schema-ref resolution), so they must not be served.\nconst CACHE_VERSION = 2\n\n/** The array-valued keys every `FileExtract` must carry. */\nconst EXTRACT_ARRAY_KEYS = [\n 'classes',\n 'tokens',\n 'injects',\n 'pluginsAndAdapters',\n 'augmentations',\n 'contextKeys',\n 'routes',\n 'moduleMounts',\n 'globPatterns',\n] as const\n\n/**\n * Structurally validate a cached extract before trusting it. The join\n * phase spreads these arrays (`...extract.classes`), so a truncated or\n * hand-edited `scan.json` whose entry is missing a field would crash\n * the scanner. Rejecting the entry here lets the cache self-heal — the\n * file is treated as uncached and re-scanned.\n */\nfunction isFileExtract(value: unknown): value is FileExtract {\n if (!value || typeof value !== 'object') return false\n const extract = value as Record<string, unknown>\n return EXTRACT_ARRAY_KEYS.every((key) => Array.isArray(extract[key]))\n}\n\n/** One cached file: its signature plus the extraction it produced. */\ninterface CacheEntry {\n /** `${mtimeMs}:${size}` — cheap change signature, no content read. */\n sig: string\n extract: FileExtract\n}\n\n/** On-disk cache document. */\ninterface CacheDoc {\n version: number\n /** Keyed by absolute file path. */\n files: Record<string, CacheEntry>\n}\n\n/**\n * In-memory + on-disk cache handle. Construct via `loadScanCache`,\n * consult with `get`, populate with `set`, and persist via `save`.\n * Entries for files no longer present on disk are dropped on `save`\n * (the scanner reports every live path through `markSeen`).\n */\nexport class ScanCache {\n private readonly path: string\n private readonly prev: Map<string, CacheEntry>\n private readonly next = new Map<string, FileExtract>()\n private readonly nextSig = new Map<string, string>()\n\n private constructor(path: string, prev: Map<string, CacheEntry>) {\n this.path = path\n this.prev = prev\n }\n\n /**\n * Load the cache for a given cache directory. A missing, unreadable,\n * malformed, or version-mismatched cache yields an empty cache — the\n * scan then behaves exactly like a cold first run.\n */\n static async load(cacheDir: string): Promise<ScanCache> {\n const file = join(cacheDir, 'scan.json')\n const prev = new Map<string, CacheEntry>()\n try {\n const raw = await readFile(file, 'utf-8')\n const doc = JSON.parse(raw) as CacheDoc\n if (doc.version === CACHE_VERSION && doc.files) {\n for (const [path, entry] of Object.entries(doc.files)) {\n if (entry && typeof entry.sig === 'string' && isFileExtract(entry.extract)) {\n prev.set(path, entry)\n }\n }\n }\n } catch {\n // Cold start — empty cache.\n }\n return new ScanCache(file, prev)\n }\n\n /** Compute the `mtimeMs:size` signature for a file, or null if stat fails. */\n static async signature(filePath: string): Promise<string | null> {\n try {\n const s = await stat(filePath)\n return `${s.mtimeMs}:${s.size}`\n } catch {\n return null\n }\n }\n\n /**\n * Return the cached extract for `filePath` iff its stored signature\n * matches `sig`. A hit means the file is byte-identical to last scan\n * (modulo an mtime+size collision) and need not be re-read.\n */\n get(filePath: string, sig: string): FileExtract | null {\n const entry = this.prev.get(filePath)\n return entry && entry.sig === sig ? entry.extract : null\n }\n\n /** Record a fresh (or reused) extract for the next `save`. */\n set(filePath: string, sig: string, extract: FileExtract): void {\n this.next.set(filePath, extract)\n this.nextSig.set(filePath, sig)\n }\n\n /** Every file path present in the loaded (previous) cache. */\n cachedFiles(): string[] {\n return [...this.prev.keys()]\n }\n\n /**\n * Read a previously-cached extract WITHOUT a signature check. Used by\n * the incremental scan, where Vite has already told us precisely which\n * files changed — so unchanged files are trusted as-is, skipping even\n * the `stat()` a full scan would do.\n */\n peek(filePath: string): FileExtract | null {\n return this.prev.get(filePath)?.extract ?? null\n }\n\n /**\n * Carry a previously-cached entry forward into the next `save`,\n * unchanged. Returns false if the file was not in the prior cache.\n */\n carry(filePath: string): boolean {\n const entry = this.prev.get(filePath)\n if (!entry) return false\n this.next.set(filePath, entry.extract)\n this.nextSig.set(filePath, entry.sig)\n return true\n }\n\n /**\n * Persist the cache. Only files passed through `set` this run survive,\n * so entries for deleted files are pruned automatically. Best-effort:\n * a write failure is swallowed (the cache is an optimization, never a\n * correctness dependency).\n */\n async save(): Promise<void> {\n const files: Record<string, CacheEntry> = {}\n for (const [path, extract] of this.next) {\n const sig = this.nextSig.get(path)\n if (sig) files[path] = { sig, extract }\n }\n const doc: CacheDoc = { version: CACHE_VERSION, files }\n try {\n await mkdir(dirname(this.path), { recursive: true })\n await writeFile(this.path, JSON.stringify(doc), 'utf-8')\n } catch {\n // Best-effort.\n }\n }\n}\n","/**\n * AST-based per-file extraction — oxc-parser replacement for the regex\n * extractors in `scanner.ts`.\n *\n * Produces the exact same {@link FileExtract} shape so the scanner's\n * cache / incremental / cross-file-join machinery is untouched. The\n * scanner calls {@link extractFileAst} first and falls back to the\n * regex path when the file doesn't parse (mid-edit syntax errors in\n * watch mode — regex still salvages partial results there).\n *\n * What AST extraction fixes over the regex path (forinda/kick-js#108):\n * - template-literal route paths (`@Get(\\`/v1/users/:id\\`)`) — regex\n * silently fell back to `/`\n * - nested parens/braces inside stacked decorator args\n * - string literals containing `(`/`)`/`{`/`}` skewing the\n * balanced-delimiter walkers\n * - aliased named imports in schema-ref resolution\n *\n * Everything here is pure (depends only on the source text), so results\n * stay safe to cache by filesystem signature.\n */\nimport { relative, sep } from 'node:path'\n\nimport { parseSync } from 'oxc-parser'\n\nimport {\n DECORATOR_NAMES,\n type DecoratorName,\n type DiscoveredAugmentation,\n type DiscoveredClass,\n type DiscoveredContextKey,\n type DiscoveredInject,\n type DiscoveredPluginOrAdapter,\n type DiscoveredRoute,\n type DiscoveredToken,\n type FileExtract,\n type ModuleMount,\n type SchemaRef,\n} from './scanner'\n\n// scanner.ts ⇄ extract-ast.ts is an import cycle (scanner calls\n// extractFileAst; we need its DECORATOR_NAMES). Type imports are erased,\n// but reading the DECORATOR_NAMES *value* during this module's init\n// would hit the TDZ while scanner is still evaluating — so the set is\n// built lazily on first extraction instead.\nlet decoratorNameSet: Set<string> | null = null\nfunction getDecoratorNameSet(): Set<string> {\n decoratorNameSet ??= new Set<string>(DECORATOR_NAMES)\n return decoratorNameSet\n}\n\n// oxc-parser returns ESTree-shaped plain objects. We walk them\n// structurally; a tiny structural node type keeps us honest without\n// pulling in the full @oxc-project/types surface.\ninterface Node {\n type: string\n [key: string]: unknown\n}\n\nconst HTTP_DECORATORS = new Set(['Get', 'Post', 'Put', 'Delete', 'Patch'])\n\nfunction isNode(value: unknown): value is Node {\n return typeof value === 'object' && value !== null && typeof (value as Node).type === 'string'\n}\n\n/** Depth-first walk over every AST node (objects with a string `type`). */\nfunction walk(node: unknown, visit: (node: Node) => void): void {\n if (Array.isArray(node)) {\n for (const item of node) walk(item, visit)\n return\n }\n if (!isNode(node)) return\n visit(node)\n for (const key of Object.keys(node)) {\n if (key === 'type') continue\n const value = node[key]\n if (typeof value === 'object' && value !== null) walk(value, visit)\n }\n}\n\n/** Static string value of a literal / no-substitution template, else null. */\nfunction stringValue(node: unknown): string | null {\n if (!isNode(node)) return null\n if (node.type === 'Literal' && typeof node.value === 'string') return node.value\n if (node.type === 'TemplateLiteral') {\n const quasis = node.quasis as Node[] | undefined\n const expressions = node.expressions as unknown[] | undefined\n if (quasis?.length === 1 && (expressions?.length ?? 0) === 0) {\n const cooked = (quasis[0] as { value?: { cooked?: string } }).value?.cooked\n return typeof cooked === 'string' ? cooked : null\n }\n }\n return null\n}\n\nfunction identifierName(node: unknown): string | null {\n return isNode(node) && node.type === 'Identifier' ? (node.name as string) : null\n}\n\n/** Callee name for `name(...)` calls (plain identifier callees only). */\nfunction calleeName(call: Node): string | null {\n return identifierName(call.callee)\n}\n\n/** Object-literal property lookup by static key name. */\nfunction getProp(obj: Node | null, name: string): Node | null {\n if (!obj || obj.type !== 'ObjectExpression') return null\n for (const prop of (obj.properties as Node[] | undefined) ?? []) {\n if (prop.type !== 'Property') continue\n const key = prop.key as Node\n const keyName =\n identifierName(key) ?? (key.type === 'Literal' ? String(key.value) : stringValue(key))\n if (keyName === name) return prop.value as Node\n }\n return null\n}\n\n/** First argument when it's an object literal, else null. */\nfunction firstObjectArg(call: Node): Node | null {\n const arg = (call.arguments as Node[] | undefined)?.[0]\n return isNode(arg) && arg.type === 'ObjectExpression' ? arg : null\n}\n\nfunction extractStringArrayProp(obj: Node | null, key: string): string[] {\n const value = getProp(obj, key)\n if (!isNode(value) || value.type !== 'ArrayExpression') return []\n const out: string[] = []\n for (const el of (value.elements as unknown[] | undefined) ?? []) {\n const s = stringValue(el)\n if (s !== null) out.push(s)\n }\n return out\n}\n\nfunction toRelative(filePath: string, cwd: string): string {\n return relative(cwd, filePath).split(sep).join('/')\n}\n\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/** Mirrors scanner.ts joinMountPath — slash-edge handling for #235 §3. */\nfunction joinMountPath(mountPath: string, routePath: string): string {\n const prefix = mountPath.endsWith('/') ? mountPath.slice(0, -1) : mountPath\n if (!routePath || routePath === '/') return prefix || '/'\n const suffix = routePath.startsWith('/') ? routePath : '/' + routePath\n return prefix + suffix || '/'\n}\n\n/** `implements` clause includes `name`? */\nfunction classImplements(cls: Node, name: string): boolean {\n for (const impl of (cls.implements as Node[] | undefined) ?? []) {\n const expr = (impl.expression ?? impl) as Node\n if (identifierName(expr) === name) return true\n // Qualified form (`kick.AppModule`) — match the rightmost segment.\n if (expr.type === 'TSQualifiedName' || expr.type === 'MemberExpression') {\n const right = (expr.right ?? expr.property) as Node | undefined\n if (right && identifierName(right) === name) return true\n }\n }\n return false\n}\n\n/** Decorators attached to a class / method / property / param node. */\nfunction decoratorsOf(node: Node): Node[] {\n return (node.decorators as Node[] | undefined) ?? []\n}\n\n/** `@Name(...)` decorator → { name, call } (call-expression form only). */\nfunction decoratorCall(dec: Node): { name: string; call: Node } | null {\n const expr = dec.expression as Node\n if (!isNode(expr) || expr.type !== 'CallExpression') return null\n const name = calleeName(expr)\n return name ? { name, call: expr } : null\n}\n\ninterface ImportBinding {\n source: string\n}\n\n/**\n * Per-file context shared by the extract passes: import map (local\n * binding name → module specifier) and the set of top-level const names\n * (same-file schema refs resolve to the `''` sentinel).\n */\ninterface FileContext {\n imports: Map<string, ImportBinding>\n topLevelConsts: Set<string>\n}\n\nfunction buildFileContext(program: Node): FileContext {\n const imports = new Map<string, ImportBinding>()\n const topLevelConsts = new Set<string>()\n for (const stmt of (program.body as Node[] | undefined) ?? []) {\n if (stmt.type === 'ImportDeclaration') {\n const source = stringValue(stmt.source) ?? ''\n for (const spec of (stmt.specifiers as Node[] | undefined) ?? []) {\n const local = identifierName(spec.local)\n if (local) imports.set(local, { source })\n }\n continue\n }\n // `const X = ...` / `export const X = ...` at top level\n const decl =\n stmt.type === 'VariableDeclaration'\n ? stmt\n : stmt.type === 'ExportNamedDeclaration' && isNode(stmt.declaration)\n ? (stmt.declaration as Node)\n : null\n if (isNode(decl) && decl.type === 'VariableDeclaration') {\n for (const d of (decl.declarations as Node[] | undefined) ?? []) {\n const name = identifierName(d.id)\n if (name) topLevelConsts.add(name)\n }\n }\n }\n return { imports, topLevelConsts }\n}\n\n/**\n * Mirror of scanner.ts `resolveImportSource` semantics on top of the\n * AST import map: imported → module specifier, same-file const → `''`\n * sentinel, otherwise null.\n */\nfunction resolveSchemaRef(identifier: string, ctx: FileContext): SchemaRef {\n const imported = ctx.imports.get(identifier)\n if (imported) return { identifier, source: imported.source }\n if (ctx.topLevelConsts.has(identifier)) return { identifier, source: '' }\n return { identifier, source: null }\n}\n\n/** Schema field (`body:` / `query:` / `params:`) — bare identifiers only. */\nfunction schemaFieldRef(options: Node | null, field: string, ctx: FileContext): SchemaRef | null {\n const value = getProp(options, field)\n const name = identifierName(value)\n return name ? resolveSchemaRef(name, ctx) : null\n}\n\ninterface QueryParamsConfig {\n filterable: string[]\n sortable: string[]\n searchable: string[]\n}\n\n/**\n * `@ApiQueryParams(...)` on a method — inline object literal or a\n * same-file const reference. Present-but-opaque (imported config,\n * column-object map, function call) → empty arrays, mirroring the\n * regex path's \"decorator exists but nothing statically extractable\".\n */\nfunction extractApiQueryParams(\n methodDecorators: Node[],\n topLevelConstInits: Map<string, Node>,\n): QueryParamsConfig | null {\n for (const dec of methodDecorators) {\n const dc = decoratorCall(dec)\n if (!dc || dc.name !== 'ApiQueryParams') continue\n const arg = (dc.call.arguments as Node[] | undefined)?.[0]\n let obj: Node | null = null\n if (isNode(arg) && arg.type === 'ObjectExpression') {\n obj = arg\n } else {\n const refName = identifierName(arg)\n if (refName) {\n const init = topLevelConstInits.get(refName)\n if (init && init.type === 'ObjectExpression') obj = init\n }\n }\n return {\n filterable: extractStringArrayProp(obj, 'filterable'),\n sortable: extractStringArrayProp(obj, 'sortable'),\n searchable: extractStringArrayProp(obj, 'searchable'),\n }\n }\n return null\n}\n\n/**\n * Parse + extract one source file. Returns `null` when oxc reports\n * parse errors — the caller falls back to the regex extractors, which\n * tolerate broken mid-edit sources by matching whatever still looks\n * right.\n */\nexport function extractFileAst(source: string, filePath: string, cwd: string): FileExtract | null {\n let program: Node\n try {\n const result = parseSync(filePath, source)\n if (result.errors.length > 0) return null\n program = result.program as unknown as Node\n } catch {\n return null\n }\n\n const relPath = toRelative(filePath, cwd)\n const ctx = buildFileContext(program)\n\n const classes: DiscoveredClass[] = []\n const tokens: DiscoveredToken[] = []\n const injects: DiscoveredInject[] = []\n const pluginsAndAdapters: DiscoveredPluginOrAdapter[] = []\n const augmentations: DiscoveredAugmentation[] = []\n const contextKeys: DiscoveredContextKey[] = []\n const routes: DiscoveredRoute[] = []\n const moduleMounts: ModuleMount[] = []\n const globPatterns: string[] = []\n\n const seenHelperNames = new Set<string>()\n const seenContextKeys = new Set<string>()\n const seenTokenNodes = new Set<Node>()\n /** Top-level `const X = <init>` map for @ApiQueryParams const refs. */\n const topLevelConstInits = new Map<string, Node>()\n\n for (const stmt of (program.body as Node[] | undefined) ?? []) {\n const decl =\n stmt.type === 'VariableDeclaration'\n ? stmt\n : stmt.type === 'ExportNamedDeclaration' && isNode(stmt.declaration)\n ? (stmt.declaration as Node)\n : null\n if (isNode(decl) && decl.type === 'VariableDeclaration') {\n for (const d of (decl.declarations as Node[] | undefined) ?? []) {\n const name = identifierName(d.id)\n if (name && isNode(d.init)) topLevelConstInits.set(name, d.init as Node)\n }\n }\n }\n\n // ── Pass 1: top-level statements — classes + defineModule consts ──────\n const exportedClasses: Array<{ cls: Node; isDefault: boolean }> = []\n for (const stmt of (program.body as Node[] | undefined) ?? []) {\n if (stmt.type === 'ExportNamedDeclaration' && isNode(stmt.declaration)) {\n const d = stmt.declaration as Node\n if (d.type === 'ClassDeclaration') exportedClasses.push({ cls: d, isDefault: false })\n } else if (stmt.type === 'ExportDefaultDeclaration' && isNode(stmt.declaration)) {\n const d = stmt.declaration as Node\n if (d.type === 'ClassDeclaration') exportedClasses.push({ cls: d, isDefault: true })\n }\n }\n\n for (const { cls, isDefault } of exportedClasses) {\n const className = identifierName(cls.id)\n if (!className) continue\n\n // First call-form decorator whose name is in DECORATOR_NAMES — same\n // pick as the regex (first in the stack wins).\n let tagged: DecoratorName | null = null\n for (const dec of decoratorsOf(cls)) {\n const dc = decoratorCall(dec)\n if (dc && getDecoratorNameSet().has(dc.name)) {\n tagged = dc.name as DecoratorName\n break\n }\n }\n if (tagged) {\n classes.push({ className, decorator: tagged, filePath, relativePath: relPath, isDefault })\n } else if (classImplements(cls, 'AppModule')) {\n classes.push({\n className,\n decorator: 'Module',\n filePath,\n relativePath: relPath,\n isDefault,\n })\n }\n }\n\n // v4 factory modules: `export const XModule = defineModule({...})`\n for (const stmt of (program.body as Node[] | undefined) ?? []) {\n if (stmt.type !== 'ExportNamedDeclaration' || !isNode(stmt.declaration)) continue\n const d = stmt.declaration as Node\n if (d.type !== 'VariableDeclaration') continue\n for (const declarator of (d.declarations as Node[] | undefined) ?? []) {\n const name = identifierName(declarator.id)\n const init = declarator.init as Node | undefined\n if (!name || !isNode(init) || init.type !== 'CallExpression') continue\n if (calleeName(init) !== 'defineModule') continue\n if (classes.some((c) => c.className === name)) continue\n classes.push({\n className: name,\n decorator: 'Module',\n filePath,\n relativePath: relPath,\n isDefault: false,\n })\n }\n }\n\n // ── Pass 2: whole-tree walk — tokens, injects, helpers, globs ─────────\n walk(program, (node) => {\n // createToken('name') — const-bound (variable) or bare (null)\n if (node.type === 'VariableDeclarator') {\n const init = node.init as Node | undefined\n if (isNode(init) && init.type === 'CallExpression' && calleeName(init) === 'createToken') {\n const name = stringValue((init.arguments as Node[] | undefined)?.[0])\n if (name !== null) {\n seenTokenNodes.add(init)\n tokens.push({\n name,\n variable: identifierName(node.id),\n filePath,\n relativePath: relPath,\n })\n }\n }\n return\n }\n\n if (node.type !== 'CallExpression') {\n // import.meta.glob handled below (CallExpression); decorators are\n // reached through their owning class/method nodes, but @Inject can\n // appear on constructor params too — catch every decorator node.\n if (node.type === 'Decorator') {\n const dc = decoratorCall(node)\n if (dc?.name === 'Inject') {\n const lit = stringValue((dc.call.arguments as Node[] | undefined)?.[0])\n if (lit !== null) injects.push({ name: lit, filePath, relativePath: relPath })\n }\n }\n return\n }\n\n const callee = node.callee as Node\n const name = calleeName(node)\n\n // Bare createToken('name') not consumed by the declarator pass\n if (name === 'createToken' && !seenTokenNodes.has(node)) {\n const tokenName = stringValue((node.arguments as Node[] | undefined)?.[0])\n if (tokenName !== null) {\n tokens.push({ name: tokenName, variable: null, filePath, relativePath: relPath })\n }\n return\n }\n\n // defineAdapter({ name }) / definePlugin({ name })\n if (name === 'defineAdapter' || name === 'definePlugin') {\n const literal = stringValue(getProp(firstObjectArg(node), 'name'))\n if (literal !== null) {\n const kind = name === 'definePlugin' ? 'plugin' : 'adapter'\n const dedupeKey = `${name}::${literal}::${filePath}`\n if (!seenHelperNames.has(dedupeKey)) {\n seenHelperNames.add(dedupeKey)\n pluginsAndAdapters.push({ kind, name: literal, filePath, relativePath: relPath })\n }\n }\n return\n }\n\n // defineAugmentation('Name', { description, example })\n if (name === 'defineAugmentation') {\n const args = (node.arguments as Node[] | undefined) ?? []\n const augName = stringValue(args[0])\n if (augName !== null) {\n const meta = isNode(args[1]) && args[1].type === 'ObjectExpression' ? args[1] : null\n augmentations.push({\n name: augName,\n description: stringValue(getProp(meta, 'description')),\n example: stringValue(getProp(meta, 'example')),\n filePath,\n relativePath: relPath,\n })\n }\n return\n }\n\n // defineContextDecorator({ key }) / defineHttpContextDecorator({ key })\n // — direct form\n if (name === 'defineContextDecorator' || name === 'defineHttpContextDecorator') {\n const key = stringValue(getProp(firstObjectArg(node), 'key'))\n if (key !== null && !seenContextKeys.has(key)) {\n seenContextKeys.add(key)\n contextKeys.push({ key, filePath, relativePath: relPath })\n }\n return\n }\n\n // Curried form: define(Http)ContextDecorator.withParams<P>()({ key })\n // — outer call's callee is the inner `withParams()` call.\n if (isNode(callee) && callee.type === 'CallExpression') {\n const innerCallee = callee.callee as Node\n if (\n isNode(innerCallee) &&\n innerCallee.type === 'MemberExpression' &&\n identifierName(innerCallee.property) === 'withParams'\n ) {\n const base = identifierName(innerCallee.object)\n if (base === 'defineContextDecorator' || base === 'defineHttpContextDecorator') {\n const key = stringValue(getProp(firstObjectArg(node), 'key'))\n if (key !== null && !seenContextKeys.has(key)) {\n seenContextKeys.add(key)\n contextKeys.push({ key, filePath, relativePath: relPath })\n }\n }\n }\n return\n }\n\n // import.meta.glob('...' | [...]) — module files only (caller gates)\n if (\n isNode(callee) &&\n callee.type === 'MemberExpression' &&\n identifierName(callee.property) === 'glob'\n ) {\n const obj = callee.object as Node\n if (isNode(obj) && obj.type === 'MetaProperty') {\n // Flatten every string literal across the args — matches the\n // regex behaviour (single string + array forms, options object\n // strings included only if literal; in practice options carry\n // booleans).\n walk(node.arguments, (argNode) => {\n const s = stringValue(argNode)\n if (s !== null) globPatterns.push(s)\n })\n }\n }\n })\n\n // ── Pass 3: class bodies — routes + class-style adapters ─────────────\n const allClasses: Array<{ cls: Node; className: string }> = []\n walk(program, (node) => {\n if (node.type === 'ClassDeclaration' || node.type === 'ClassExpression') {\n const className = identifierName(node.id)\n if (className) allClasses.push({ cls: node, className })\n }\n })\n\n for (const { cls, className } of allClasses) {\n const discovered = classes.find((c) => c.className === className)\n const body = (cls.body as Node | undefined)?.body as Node[] | undefined\n\n // Class-style adapters: `class X implements AppAdapter { name = '...' }`\n if (classImplements(cls, 'AppAdapter')) {\n for (const member of body ?? []) {\n if (member.type !== 'PropertyDefinition') continue\n if (identifierName(member.key) !== 'name') continue\n const literal = stringValue(member.value)\n if (literal === null) continue\n const dedupeKey = `class::${literal}::${filePath}`\n if (!seenHelperNames.has(dedupeKey)) {\n seenHelperNames.add(dedupeKey)\n pluginsAndAdapters.push({\n kind: 'adapter',\n name: literal,\n filePath,\n relativePath: relPath,\n })\n }\n break\n }\n }\n\n for (const member of body ?? []) {\n if (member.type !== 'MethodDefinition') continue\n const methodName = identifierName(member.key)\n if (!methodName) continue\n\n // Module mounts: `routes() { ... }` bodies — zip path/controller\n // pairs in source order (works for the class form; the object-\n // method form inside defineModule(build) is handled below).\n if (methodName === 'routes') {\n collectMounts(member.value as Node, moduleMounts)\n continue\n }\n\n // Route handlers — every HTTP decorator on the method emits one\n // route (stacked verbs = multiple routes, same as the regex).\n if (!discovered) continue\n const decs = decoratorsOf(member)\n const apiQp = extractApiQueryParams(decs, topLevelConstInits)\n for (const dec of decs) {\n const dc = decoratorCall(dec)\n if (!dc || !HTTP_DECORATORS.has(dc.name)) continue\n const args = (dc.call.arguments as Node[] | undefined) ?? []\n const rawPath = stringValue(args[0])\n const path = rawPath && rawPath.length > 0 ? rawPath : '/'\n const options = isNode(args[1]) && args[1].type === 'ObjectExpression' ? args[1] : null\n\n routes.push({\n controller: className,\n method: methodName,\n httpMethod: dc.name.toUpperCase() as DiscoveredRoute['httpMethod'],\n path,\n // extractFile contract: own-path params only — the cross-file\n // join re-applies mount prefixes (scanner.ts joinExtracts).\n pathParams: extractPathParams(path),\n queryFilterable: apiQp?.filterable ?? null,\n querySortable: apiQp?.sortable ?? null,\n querySearchable: apiQp?.searchable ?? null,\n bodySchema: schemaFieldRef(options, 'body', ctx),\n querySchema: schemaFieldRef(options, 'query', ctx),\n paramsSchema: schemaFieldRef(options, 'params', ctx),\n filePath,\n relativePath: relPath,\n })\n }\n }\n }\n\n // Object-method `routes()` (defineModule build objects and friends).\n walk(program, (node) => {\n if (node.type !== 'Property' || identifierName(node.key) !== 'routes') return\n const value = node.value as Node\n if (\n isNode(value) &&\n (value.type === 'FunctionExpression' || value.type === 'ArrowFunctionExpression')\n ) {\n collectMounts(value, moduleMounts)\n }\n })\n\n return {\n classes,\n tokens,\n injects,\n pluginsAndAdapters,\n augmentations,\n contextKeys,\n routes,\n moduleMounts,\n globPatterns: /\\.module\\.[mc]?[tj]sx?$/.test(filePath) ? globPatterns : [],\n }\n}\n\n/**\n * Collect `{ path: '...', controller: Ident }` pairs from a routes()\n * function body — zipped in traversal order, shorter list wins, same\n * as the regex `extractModuleMounts`.\n */\nfunction collectMounts(fn: Node, out: ModuleMount[]): void {\n const paths: string[] = []\n const controllers: string[] = []\n walk(fn.body, (node) => {\n if (node.type !== 'Property') return\n const key = identifierName(node.key)\n if (key === 'path') {\n const s = stringValue(node.value)\n if (s !== null) paths.push(s)\n } else if (key === 'controller') {\n const id = identifierName(node.value)\n if (id && /^[A-Z]/.test(id)) controllers.push(id)\n }\n })\n const n = Math.min(paths.length, controllers.length)\n for (let i = 0; i < n; i++) {\n out.push({ controller: controllers[i], mountPath: paths[i] })\n }\n}\n\n/**\n * `joinExtracts` re-derives `pathParams` with the mount prefix — the\n * scanner needs the same joiner the regex path used; re-exported here\n * so both implementations share one definition is unnecessary (the\n * scanner keeps its own). This export exists for tests only.\n */\nexport const __testing = { joinMountPath, extractPathParams }\n","/**\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'\nimport { ScanCache } from './scanner-cache'\nimport { extractFileAst } from './extract-ast'\n\n/** Decorators that mark a class as DI-managed */\nexport const DECORATOR_NAMES = [\n 'Service',\n 'Controller',\n 'Repository',\n 'Injectable',\n 'Component',\n 'Module',\n] as const\n\nexport type DecoratorName = (typeof DECORATOR_NAMES)[number]\n\n/** A single discovered decorated class */\nexport interface DiscoveredClass {\n /** Class name (e.g., 'UserService') */\n className: string\n /** Decorator that marked it (e.g., 'Service') */\n decorator: DecoratorName\n /** Absolute file path */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n /** True if exported as `default` */\n isDefault: boolean\n}\n\n/** A single route handler discovered on a controller class */\nexport interface DiscoveredRoute {\n /** Owning controller class name (e.g. 'UserController') */\n controller: string\n /** Handler method name on the controller (e.g. 'getUser') */\n method: string\n /** HTTP verb (uppercase) */\n httpMethod: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'\n /** Route path including parameter placeholders (e.g. '/:id/posts/:postId') */\n path: string\n /** URL path parameter names extracted from `:placeholder` segments */\n pathParams: string[]\n /**\n * Whitelisted query field names extracted from `@ApiQueryParams({...})`.\n * `null` means no `@ApiQueryParams` was found on this method (so the\n * generator emits an unconstrained `query` shape). An empty array means\n * the decorator existed but no fields could be statically extracted\n * (e.g. an opaque imported config).\n */\n queryFilterable: string[] | null\n querySortable: string[] | null\n querySearchable: string[] | null\n /**\n * Schema identifiers referenced from the route decorator's second arg\n * (e.g. `@Post('/', { body: createTaskSchema })`). `null` means no\n * such reference; the value carries the identifier and the resolved\n * import source (relative module path) if known.\n */\n bodySchema: SchemaRef | null\n querySchema: SchemaRef | null\n paramsSchema: SchemaRef | null\n /** Absolute file path of the controller */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/** A statically-resolved schema identifier reference */\nexport interface SchemaRef {\n /** The identifier as written (e.g. `createTaskSchema`) */\n identifier: string\n /**\n * Resolved module specifier (relative path or bare module name) where\n * the identifier is defined. `null` means the source could not be\n * statically determined (the generator falls back to `unknown`).\n */\n source: string | null\n}\n\n/** A `createToken<T>('name')` call discovered in source */\nexport interface DiscoveredToken {\n /** The literal string passed to `createToken()` */\n name: string\n /** The const variable name on the LHS, if any */\n variable: string | null\n /** Absolute file path */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/** An `@Inject('literal')` call discovered in source */\nexport interface DiscoveredInject {\n /** The literal string passed to `@Inject()` */\n name: string\n /** Absolute file path */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/** A name collision — same class name in two or more files */\nexport interface ClassCollision {\n /** The colliding class name */\n className: string\n /** All files declaring the class */\n classes: DiscoveredClass[]\n}\n\n/**\n * Information about a discovered env schema file. The typegen\n * generator uses this to emit a `KickEnv` + `NodeJS.ProcessEnv`\n * augmentation that flows through to `@Value` and `process.env`.\n *\n * `null` means no env file was found at the configured location.\n */\nexport interface DiscoveredEnv {\n /** Absolute path to the env schema file */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/**\n * A plugin or adapter discovered in source — either via `defineAdapter({ name })`\n * / `definePlugin({ name })` calls, or via a class that `implements AppAdapter`\n * and declares a string-literal `name` field.\n *\n * The `name` here is the literal string passed to the framework (the value\n * `dependsOn` references), NOT the symbol on the LHS. `defineAdapter` lets\n * authors choose any name they want; the symbol is irrelevant at runtime.\n */\nexport interface DiscoveredPluginOrAdapter {\n /** Whether this is a plugin (`definePlugin`) or adapter (`defineAdapter` / class) */\n kind: 'plugin' | 'adapter'\n /** The string literal passed as `name` (the value `dependsOn` references) */\n name: string\n /** Absolute file path */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/**\n * A context key discovered from a `defineContextDecorator({ key })` or\n * `defineHttpContextDecorator({ key })` call (including the curried\n * `.withParams<P>()({ key })` form). Feeds the `kick/context` typegen\n * plugin, which emits the `ContextKeys` augmentation so `dependsOn`\n * typo-checking is automatic and complete.\n */\nexport interface DiscoveredContextKey {\n /** The literal `key:` value the contributor writes. */\n key: string\n /** Absolute file path. */\n filePath: string\n /** Path relative to scan root, with forward slashes. */\n relativePath: string\n}\n\n/**\n * A `defineAugmentation('Name', meta)` call discovered in source. Plugins\n * call this to advertise an augmentable interface so the typegen can list\n * every augmentation surface in one generated file.\n */\nexport interface DiscoveredAugmentation {\n /** The literal string passed as the first arg to `defineAugmentation` */\n name: string\n /** Optional `description` extracted from the second-arg object literal */\n description: string | null\n /** Optional `example` extracted from the second-arg object literal */\n example: string | null\n /** Absolute file path */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n}\n\n/**\n * A decorated class whose file sits inside a module directory but\n * isn't picked up by any of the module's `import.meta.glob(...)`\n * patterns. Surfaced as a typegen warning per forinda/kick-js#235 §4\n * so adopters notice silent registration drift before it bites them\n * at runtime with a `MissingContributorError` or wrong code path.\n */\nexport interface OrphanedClass {\n /** The decorated class name */\n className: string\n /** Absolute path of the class file */\n filePath: string\n /** Path relative to scan root, with forward slashes */\n relativePath: string\n /** Absolute path of the module file whose globs didn't match */\n moduleFilePath: string\n /** The decorator name (`Service`, `Controller`, `Repository`, …) */\n decorator: DecoratorName\n}\n\n/** Aggregated scanner output */\nexport interface ScanResult {\n classes: DiscoveredClass[]\n routes: DiscoveredRoute[]\n tokens: DiscoveredToken[]\n injects: DiscoveredInject[]\n collisions: ClassCollision[]\n /** Discovered env schema file (or null if none found at the configured path) */\n env: DiscoveredEnv | null\n /** Plugins/adapters discovered via `defineAdapter`/`definePlugin`/`implements AppAdapter` */\n pluginsAndAdapters: DiscoveredPluginOrAdapter[]\n /** Augmentation interfaces declared via `defineAugmentation('Name', meta)` */\n augmentations: DiscoveredAugmentation[]\n /** Context keys from `define(Http)ContextDecorator({ key })` calls */\n contextKeys: DiscoveredContextKey[]\n /**\n * Decorated classes that sit inside a module directory but aren't\n * picked up by any of the module's `import.meta.glob(...)` patterns.\n * Empty when every decorator file is matched. forinda/kick-js#235 §4.\n */\n orphanedClasses: OrphanedClass[]\n}\n\n/** Options for the scanner */\nexport interface ScanOptions {\n /** Root directory to scan (e.g., absolute path to `src`) */\n root: string\n /** Project root used to compute relative paths (e.g., process.cwd()) */\n cwd: string\n /** Glob-like extensions to scan */\n extensions?: string[]\n /** Substrings that exclude a path (matched against relative path) */\n exclude?: string[]\n /**\n * Path to the env schema file, relative to `cwd`. Defaults to\n * `'src/env.ts'`. The file must contain a `defineEnv(...)` call\n * with a default export for the typegen to emit a typed `KickEnv`\n * augmentation. If the file does not exist or doesn't match the\n * expected shape, env typing is skipped silently.\n */\n envFile?: string\n /**\n * Directory for the persistent per-file extraction cache. When set,\n * unchanged files (matched by `mtimeMs:size` signature) are served\n * from `<cacheDir>/scan.json` instead of being re-read and re-scanned.\n * Omit to disable caching (every scan is a cold read — the original\n * behaviour). Typically `<cwd>/.kickjs/cache`.\n */\n cacheDir?: 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 the v4 module factory form\n * `export const XModule = defineModule({ ... })` (the `class implements\n * AppModule` form above is the deprecated v3 style). The const name is\n * the module's identity and becomes its `ModuleToken` entry. An\n * optional generic / type annotation on the const is tolerated.\n */\nconst DEFINE_MODULE_REGEX =\n /export\\s+const\\s+(\\w+)\\s*(?::\\s*[^=]+)?=\\s*defineModule\\s*(?:<[^>]*>)?\\s*\\(/g\n\n/**\n * Match a `createToken<T>('name')` call with optional `export const X =`\n * or `const X =` prefix. Tolerates whitespace and the type parameter\n * being absent (`createToken('name')`).\n */\nconst CREATE_TOKEN_REGEX =\n /(?:export\\s+)?const\\s+(\\w+)\\s*(?::\\s*[^=]+)?=\\s*createToken\\s*(?:<[^>]*>)?\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]\\s*\\)/g\n\n/**\n * Match a bare `createToken<T>('name')` call (no const assignment) so\n * we still pick up dynamically-used tokens.\n */\nconst BARE_CREATE_TOKEN_REGEX = /createToken\\s*(?:<[^>]*>)?\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]\\s*\\)/g\n\n/** Match `@Inject('literal')` — only literals; computed args are skipped */\nconst INJECT_LITERAL_REGEX = /@Inject\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]\\s*\\)/g\n\n/**\n * Match the start of a `defineAdapter(...)` or `definePlugin(...)` call,\n * tolerating optional `<TConfig, TExtra>` generics. Captures the helper\n * name. The callsite's first-arg object is parsed forward via\n * `findBalancedClose` so nested objects/parens don't confuse us.\n */\nconst DEFINE_HELPER_START = /\\b(defineAdapter|definePlugin)\\s*(?:<[^>]*>)?\\s*\\(/g\n\n/**\n * Match the start of a `defineContextDecorator(...)` /\n * `defineHttpContextDecorator(...)` call up to the `(` that opens the\n * spec object — tolerating optional `<...>` generics AND the curried\n * `.withParams<P>()(...)` form (the empty `()` is consumed so the next\n * `(` is the spec). The spec's `key:` literal is then read forward via\n * `findBalancedClose`, mirroring `DEFINE_HELPER_START`.\n */\n// `(?:<(?:[^<>]|<[^<>]*>)*>)?` tolerates one level of nested generics\n// (e.g. `defineHttpContextDecorator<'tenant', Record<string, never>>`),\n// which a flat `<[^>]*>` would truncate at the inner `>`.\nconst CONTEXT_DECORATOR_START =\n /\\b(?:defineContextDecorator|defineHttpContextDecorator)\\s*(?:\\.withParams\\s*<(?:[^<>]|<[^<>]*>)*>\\s*\\(\\s*\\))?\\s*(?:<(?:[^<>]|<[^<>]*>)*>)?\\s*\\(/g\n\n/**\n * Match a class declaration whose `implements` clause includes `AppAdapter`.\n * Captures the class name. Used to pick up the (rare, post-defineAdapter)\n * legacy class-style adapters so their literal `name = '...'` field can\n * still feed `KickJsPluginRegistry`.\n */\nconst APP_ADAPTER_CLASS_REGEX = new RegExp(\n String.raw`export\\s+(?:default\\s+)?(?:abstract\\s+)?class\\s+(\\w+)` +\n String.raw`(?:\\s+extends\\s+\\w+(?:<[^>]*>)?)?` +\n String.raw`\\s+implements\\s+[^{]*\\bAppAdapter\\b`,\n 'g',\n)\n\n/** Match a string-literal `name = '...'` field on a class body. */\nconst CLASS_NAME_FIELD_REGEX = /\\bname\\s*(?::\\s*[^=]+)?=\\s*['\"`]([^'\"`]+)['\"`]/\n\n/**\n * Match the start of a `defineAugmentation('Name', ...)` call. Captures\n * the literal name. The optional second-arg object is parsed forward so\n * `description` / `example` can be pulled out.\n */\nconst DEFINE_AUGMENTATION_START = /\\bdefineAugmentation\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]\\s*(,\\s*\\{)?/g\n\n/** HTTP route decorator names recognised by the scanner */\nconst HTTP_DECORATORS = ['Get', 'Post', 'Put', 'Delete', 'Patch'] as const\n\n/**\n * Locate the start of a route decorator: `@Get(`, `@Post(`, etc.\n * Used by `extractRoutesFromSource`; the rest of the route declaration\n * (balanced parens, stacked decorators, method name) is parsed by walking\n * the source forward from this match. The previous all-in-one regex\n * couldn't handle nested parens in stacked decorator args (e.g.\n * `@ApiResponse(201, { schema: z.object({ id: z.string() }) })`) — see\n * forinda/kick-js#108.\n */\nconst ROUTE_DECORATOR_START = new RegExp(String.raw`@(${HTTP_DECORATORS.join('|')})\\s*\\(`, 'g')\n\n/**\n * Find the index of the `)` that balances the `(` at `openPos`.\n * Returns -1 if no matching `)` exists. Counts balanced parens only;\n * does not understand string literals, so a `(` or `)` inside a string\n * inside the args will skew the depth counter (matches the limitation\n * of `extractRouteOptionsArg`).\n */\nfunction findBalancedClose(text: string, openPos: number): number {\n let depth = 1\n for (let i = openPos + 1; i < text.length; i++) {\n const ch = text[i]\n if (ch === '(') depth++\n else if (ch === ')') {\n depth--\n if (depth === 0) return i\n }\n }\n return -1\n}\n\n/**\n * Walk forward from the end of a route decorator past any stacked\n * decorators (`@ApiOperation(...)`, `@ApiResponse(...)`, `@Middleware(fn)`,\n * etc.), then past optional `public`/`private`/`protected` and `async`,\n * and capture the method name + opening `(`.\n *\n * Returns the method name and the position immediately after the method's\n * opening `(`, or `null` if the source between the route decorator and\n * the method body doesn't fit the expected shape.\n */\nfunction readMethodAfterDecorators(\n block: string,\n startPos: number,\n): { methodName: string; endPos: number } | null {\n let pos = startPos\n // Stacked decorators: @PascalCase optionally followed by balanced (...)\n while (pos < block.length) {\n while (pos < block.length && /\\s/.test(block[pos])) pos++\n if (block[pos] !== '@') break\n const decMatch = block.slice(pos).match(/^@([A-Z]\\w*)/)\n if (!decMatch) break\n pos += decMatch[0].length\n while (pos < block.length && /\\s/.test(block[pos])) pos++\n if (block[pos] === '(') {\n const close = findBalancedClose(block, pos)\n if (close < 0) return null\n pos = close + 1\n }\n }\n // Modifiers + async\n while (pos < block.length && /\\s/.test(block[pos])) pos++\n for (const mod of ['public', 'private', 'protected'] as const) {\n if (block.slice(pos, pos + mod.length) === mod && /\\s/.test(block.charAt(pos + mod.length))) {\n pos += mod.length\n while (pos < block.length && /\\s/.test(block[pos])) pos++\n break\n }\n }\n if (block.slice(pos, pos + 5) === 'async' && /\\s/.test(block.charAt(pos + 5))) {\n pos += 5\n while (pos < block.length && /\\s/.test(block[pos])) pos++\n }\n // Method name + `(`\n const methodMatch = block.slice(pos).match(/^([a-zA-Z_]\\w*)\\s*\\(/)\n if (!methodMatch) return null\n return { methodName: methodMatch[1], endPos: pos + methodMatch[0].length }\n}\n\n/** Extract `:placeholder` segments from an Express route path */\nfunction extractPathParams(path: string): string[] {\n const matches = path.match(/:([a-zA-Z_]\\w*)/g) ?? []\n return matches.map((m) => m.slice(1))\n}\n\n/**\n * Join a controller's mount-prefix path with a per-route path.\n * Handles the slash edge cases so `'/orgs/:id'` + `'/'` becomes\n * `'/orgs/:id'` (no trailing slash) and `'/orgs/:id'` + `'/:code'`\n * becomes `'/orgs/:id/:code'`. forinda/kick-js#235 §3.\n */\nfunction joinMountPath(mountPath: string, routePath: string): string {\n const prefix = mountPath.endsWith('/') ? mountPath.slice(0, -1) : mountPath\n if (!routePath || routePath === '/') return prefix || '/'\n const suffix = routePath.startsWith('/') ? routePath : '/' + routePath\n return prefix + suffix || '/'\n}\n\n/**\n * Match the `routes()` method body on a class implementing `AppModule`.\n * Captures the body region so we can scan it for `path:` + `controller:`\n * pairs. Tolerates `routes(): ModuleRoutes` and stripped return type.\n */\nconst ROUTES_METHOD_START =\n /\\b(?:public\\s+|private\\s+|protected\\s+)?routes\\s*\\([^)]*\\)\\s*(?::\\s*[A-Za-z_][\\w<>[\\]\\s,|]*\\s*)?\\{/g\n\n/**\n * Match `path: '/...'` inside a routes-method body. Picks up both\n * single-quoted and double-quoted / template literal forms.\n */\nconst PATH_FIELD_REGEX = /\\bpath\\s*:\\s*['\"`]([^'\"`]*)['\"`]/g\n\n/** Match `controller: SomeController` (bare identifier only). */\nconst CONTROLLER_FIELD_REGEX = /\\bcontroller\\s*:\\s*([A-Z]\\w*)\\b/g\n\n/**\n * Match the start of an `import.meta.glob(...)` call. The first arg\n * (string or string array) gets parsed forward via balanced-paren\n * walking to handle whitespace + line breaks inside the array.\n * forinda/kick-js#235 §4.\n */\nconst IMPORT_META_GLOB_START = /\\bimport\\.meta\\.glob\\s*\\(/g\n\n/**\n * Extract every glob pattern from `import.meta.glob([...patterns], ...)` calls\n * in a module file. Single-string form (`import.meta.glob('./**\\\\/*.ts')`) and\n * array form both supported. Negation patterns (`!./**\\\\/*.test.ts`)\n * are returned with the leading `!` preserved so the caller can apply\n * exclusion logic.\n */\nexport function extractGlobPatterns(source: string): string[] {\n const patterns: string[] = []\n IMPORT_META_GLOB_START.lastIndex = 0\n while (IMPORT_META_GLOB_START.exec(source) !== null) {\n const openParen = IMPORT_META_GLOB_START.lastIndex - 1\n const closeParen = findBalancedClose(source, openParen)\n if (closeParen < 0) continue\n const args = source.slice(openParen + 1, closeParen)\n // Pull out every string literal inside the first-arg region —\n // we don't bother distinguishing the array form from the bare\n // string; both end up as flat patterns.\n const literalRe = /['\"`]([^'\"`]+)['\"`]/g\n let lit: RegExpExecArray | null\n while ((lit = literalRe.exec(args)) !== null) {\n patterns.push(lit[1] as string)\n }\n }\n return patterns\n}\n\n/**\n * Convert a Vite-style glob (e.g. `./**\\\\/*.controller.ts`) to a\n * RegExp. Supports `**` (any path segments including `/`), `*` (any\n * chars within one segment), `?` (single char). Brace alternation is\n * intentionally not handled — none of the templated globs use it,\n * and a false-negative on an unusual pattern is safer than a false-\n * positive (better to skip the warning than to wrongly silence one).\n */\nfunction globToRegex(pattern: string): RegExp {\n // Process `?` before any of the substitutions that insert `?` into\n // the output (e.g. the `(?:.+/)?` non-capture group for `**/`).\n // If we left it for last, the `?` → `.` pass would mangle those\n // groups into `(.:.+\\/).` — broken regex.\n const escaped = pattern\n .replace(/[.+^$()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\?/g, '.')\n .replace(/\\*\\*\\//g, '___DOUBLESTAR_SLASH___')\n .replace(/\\*\\*/g, '___DOUBLESTAR___')\n .replace(/\\*/g, '[^/]*')\n .replace(/___DOUBLESTAR_SLASH___/g, '(?:.+/)?')\n .replace(/___DOUBLESTAR___/g, '.*')\n return new RegExp('^' + escaped + '$')\n}\n\n/**\n * Decide whether a file (relative to the module file's directory)\n * matches any of the module's positive glob patterns. Negation\n * patterns (`!./**\\\\/*.test.ts`) subtract; a file matched by both a\n * positive and a negation is excluded.\n */\nexport function fileMatchesAnyGlob(\n moduleRelativePath: string,\n patterns: readonly string[],\n): boolean {\n const normalised = moduleRelativePath.startsWith('./')\n ? moduleRelativePath\n : './' + moduleRelativePath\n let matched = false\n for (const pattern of patterns) {\n const isNegation = pattern.startsWith('!')\n const body = isNegation ? pattern.slice(1) : pattern\n if (globToRegex(body).test(normalised)) {\n matched = !isNegation\n }\n }\n return matched\n}\n\n/**\n * A `{ controller, mountPath }` pair extracted from a module's\n * `routes()` body. Multiple entries appear when a module returns an\n * array (multi-mount). forinda/kick-js#235 §3.\n */\nexport interface ModuleMount {\n controller: string\n mountPath: string\n}\n\n/**\n * Scan a module file's `routes()` body for `{ path, controller }` pairs.\n * A single return value or an array of return values both work — we\n * regex out every `path: '...'` and every `controller: Ident` and\n * zip them in order. Adopter writing wildly creative bodies won't be\n * matched; that's fine — the scanner falls back to no-mount behaviour\n * (per-route path only) which is the pre-fix behaviour.\n *\n * Returns a list of `{ controller, mountPath }` entries. A controller\n * that appears multiple times in `routes()` (rare; multi-mount\n * version-bundled controllers) gets multiple entries; the route\n * scanner uses the first one for path-param extraction since the\n * pattern usually shares the prefix.\n */\nexport function extractModuleMounts(source: string): ModuleMount[] {\n const out: ModuleMount[] = []\n ROUTES_METHOD_START.lastIndex = 0\n let m: RegExpExecArray | null\n while ((m = ROUTES_METHOD_START.exec(source)) !== null) {\n const openBrace = source.indexOf('{', m.index + m[0].length - 1)\n if (openBrace < 0) continue\n const closeBrace = findBalancedBrace(source, openBrace)\n if (closeBrace < 0) continue\n const body = source.slice(openBrace + 1, closeBrace)\n\n const paths: string[] = []\n PATH_FIELD_REGEX.lastIndex = 0\n let p: RegExpExecArray | null\n while ((p = PATH_FIELD_REGEX.exec(body)) !== null) {\n paths.push(p[1] ?? '')\n }\n\n const controllers: string[] = []\n CONTROLLER_FIELD_REGEX.lastIndex = 0\n let c: RegExpExecArray | null\n while ((c = CONTROLLER_FIELD_REGEX.exec(body)) !== null) {\n controllers.push(c[1] as string)\n }\n\n // Zip — if counts mismatch, take the shorter of the two so we\n // never assign a wrong controller to a path.\n const n = Math.min(paths.length, controllers.length)\n for (let i = 0; i < n; i++) {\n out.push({ controller: controllers[i] as string, mountPath: paths[i] as string })\n }\n }\n return out\n}\n\n/**\n * Extract a bare identifier value from a single field in an object literal\n * embedded in a string. Returns `null` if the field is missing or its value\n * isn't a bare identifier (e.g. an inline object, function call, etc.).\n *\n * Example: `extractObjectFieldIdentifier(\"'/' , { body: createTaskSchema }\", 'body')`\n * returns `'createTaskSchema'`.\n */\nfunction extractObjectFieldIdentifier(text: string, field: string): string | null {\n // Look for `field: <identifier>` not followed by `(` (function call) or `{` (inline object)\n const re = new RegExp(String.raw`\\b${field}\\s*:\\s*([A-Za-z_$][\\w$]*)`, 'g')\n const m = re.exec(text)\n if (!m) return null\n return m[1]\n}\n\n/**\n * Resolve a bare identifier to its module source by inspecting the file's\n * top-level imports and same-file `const` declarations.\n *\n * - `import { X } from './path'` → returns `'./path'`\n * - `import X from './path'` (default import) → returns `'./path'`\n * - `import * as X from './path'` → returns `'./path'`\n * - `const X = z.object(...)` (same file) → returns `null` (caller emits a self-import)\n *\n * Returns `null` when the identifier cannot be resolved.\n */\nfunction resolveImportSource(source: string, identifier: string): string | null {\n // Named import: `import { X, Y as Z } from './path'`\n const namedRe = new RegExp(\n String.raw`import\\s*(?:type\\s+)?\\{[^}]*\\b${identifier}\\b[^}]*\\}\\s*from\\s*['\"\\`]([^'\"\\`]+)['\"\\`]`,\n )\n const named = namedRe.exec(source)\n if (named) return named[1]\n\n // Default import: `import X from './path'`\n const defaultRe = new RegExp(\n String.raw`import\\s+(?:type\\s+)?${identifier}\\s+from\\s*['\"\\`]([^'\"\\`]+)['\"\\`]`,\n )\n const def = defaultRe.exec(source)\n if (def) return def[1]\n\n // Namespace import: `import * as X from './path'`\n const nsRe = new RegExp(\n String.raw`import\\s*\\*\\s*as\\s+${identifier}\\s+from\\s*['\"\\`]([^'\"\\`]+)['\"\\`]`,\n )\n const ns = nsRe.exec(source)\n if (ns) return ns[1]\n\n // Same-file const declaration — return empty string as a sentinel meaning\n // \"current file\". The generator turns this into a self-relative reference.\n const constRe = new RegExp(String.raw`(?:^|\\n)\\s*(?:export\\s+)?const\\s+${identifier}\\b`)\n if (constRe.test(source)) return ''\n\n return null\n}\n\n/**\n * Extract whitelist arrays from an `@ApiQueryParams(...)` decorator\n * within `decoratorBlock`. Handles two forms:\n *\n * - Inline literal: `@ApiQueryParams({ filterable: ['a', 'b'], ... })`\n * - Const reference: `@ApiQueryParams(SOME_CONFIG)` — looks up\n * `const SOME_CONFIG = { ... }` in the same file (`fullSource`).\n *\n * Returns `null` if no `@ApiQueryParams` is present. Returns\n * `{ filterable: [], sortable: [], searchable: [] }` if the decorator\n * is present but no fields could be statically extracted (opaque\n * imports, column-object configs, function calls, etc.).\n */\nfunction extractApiQueryParams(\n decoratorBlock: string,\n fullSource: string,\n): { filterable: string[]; sortable: string[]; searchable: string[] } | null {\n const apiMatch = /@ApiQueryParams\\s*\\(\\s*([\\s\\S]*?)\\s*\\)\\s*$/.exec(decoratorBlock)\n if (!apiMatch) {\n // Try without anchoring to the end (decorator may not be the last in the block)\n const loose = /@ApiQueryParams\\s*\\(([\\s\\S]*?)\\)/.exec(decoratorBlock)\n if (!loose) return null\n return parseApiQueryParamsArg(loose[1].trim(), fullSource)\n }\n return parseApiQueryParamsArg(apiMatch[1].trim(), fullSource)\n}\n\nfunction parseApiQueryParamsArg(\n arg: string,\n fullSource: string,\n): { filterable: string[]; sortable: string[]; searchable: string[] } {\n // Inline literal — starts with `{`\n if (arg.startsWith('{')) {\n return parseInlineConfigLiteral(arg)\n }\n // Const reference — bare identifier (possibly with type assertion)\n const idMatch = /^([A-Za-z_]\\w*)/.exec(arg)\n if (idMatch) {\n const ident = idMatch[1]\n // Look for `const IDENT = { ... }` in the same source file\n const constRe = new RegExp(\n String.raw`const\\s+${ident}\\s*(?::\\s*[^=]+)?=\\s*(\\{[\\s\\S]*?\\n\\})`,\n 'm',\n )\n const constMatch = constRe.exec(fullSource)\n if (constMatch) {\n return parseInlineConfigLiteral(constMatch[1])\n }\n }\n // Fallback: decorator present but extraction failed\n return { filterable: [], sortable: [], searchable: [] }\n}\n\n/** Extract a string array literal for one config key from an inline object literal */\nfunction extractStringArray(literal: string, key: string): string[] {\n const re = new RegExp(String.raw`${key}\\s*:\\s*\\[([\\s\\S]*?)\\]`)\n const m = re.exec(literal)\n if (!m) return []\n return Array.from(m[1].matchAll(/['\"`]([^'\"`]+)['\"`]/g)).map((x) => x[1])\n}\n\n/** Parse an inline `{ filterable: [...], sortable: [...], searchable: [...] }` literal */\nfunction parseInlineConfigLiteral(literal: string): {\n filterable: string[]\n sortable: string[]\n searchable: string[]\n} {\n return {\n filterable: extractStringArray(literal, 'filterable'),\n sortable: extractStringArray(literal, 'sortable'),\n searchable: extractStringArray(literal, 'searchable'),\n }\n}\n\n/** Recursively walk a directory and yield matching file paths */\nasync function walk(dir: string, opts: ScanOptions): Promise<string[]> {\n const exts = opts.extensions ?? DEFAULT_EXTENSIONS\n const excludes = opts.exclude ?? DEFAULT_EXCLUDES\n const out: string[] = []\n\n let entries: Dirent[]\n try {\n entries = (await readdir(dir, { withFileTypes: true, encoding: 'utf-8' })) as Dirent[]\n } catch {\n return out\n }\n\n for (const entry of entries) {\n const full = join(dir, entry.name)\n const rel = relative(opts.cwd, full)\n\n if (excludes.some((ex) => rel.includes(ex))) continue\n\n if (entry.isDirectory()) {\n out.push(...(await walk(full, opts)))\n } else if (entry.isFile()) {\n if (exts.some((ext) => entry.name.endsWith(ext))) {\n out.push(full)\n }\n }\n }\n\n return out\n}\n\n/** Compute the forward-slash relative path used in scanner output */\nfunction toRelative(filePath: string, cwd: string): string {\n return relative(cwd, filePath).split(sep).join('/')\n}\n\n/** Extract decorated classes from a single source file */\nexport function extractClassesFromSource(\n source: string,\n filePath: string,\n cwd: string,\n): DiscoveredClass[] {\n const out: DiscoveredClass[] = []\n const relPath = toRelative(filePath, cwd)\n\n DECORATED_CLASS_REGEX.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = DECORATED_CLASS_REGEX.exec(source)) !== null) {\n const [, decorator, defaultMarker, className] = match\n out.push({\n className,\n decorator: decorator as DecoratorName,\n filePath,\n relativePath: relPath,\n isDefault: Boolean(defaultMarker),\n })\n }\n\n // KickJS modules are undecorated classes that `implements AppModule`.\n // Tag them with the synthetic `Module` decorator so downstream code that\n // already filters by `c.decorator === 'Module'` keeps working.\n APP_MODULE_CLASS_REGEX.lastIndex = 0\n let modMatch: RegExpExecArray | null\n while ((modMatch = APP_MODULE_CLASS_REGEX.exec(source)) !== null) {\n const [, defaultMarker, className] = modMatch\n if (out.some((c) => c.className === className && c.filePath === filePath)) continue\n out.push({\n className,\n decorator: 'Module',\n filePath,\n relativePath: relPath,\n isDefault: Boolean(defaultMarker),\n })\n }\n\n // v4 factory modules: `export const XModule = defineModule({ ... })`.\n // Tag with the synthetic `Module` decorator (same as the v3 class form)\n // so `buildModuleTokens` populates `ModuleToken` — without this, a\n // project that uses only `defineModule()` emits `ModuleToken = never`.\n DEFINE_MODULE_REGEX.lastIndex = 0\n let defMatch: RegExpExecArray | null\n while ((defMatch = DEFINE_MODULE_REGEX.exec(source)) !== null) {\n const [, className] = defMatch\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: false,\n })\n }\n\n return out\n}\n\n/** Extract `createToken('name')` definitions from a single source file */\nexport function extractTokensFromSource(\n source: string,\n filePath: string,\n cwd: string,\n): DiscoveredToken[] {\n const out: DiscoveredToken[] = []\n const relPath = toRelative(filePath, cwd)\n const seen = new Set<string>()\n\n // First pass: const-bound tokens (preferred — we get the variable name)\n CREATE_TOKEN_REGEX.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = CREATE_TOKEN_REGEX.exec(source)) !== null) {\n const [full, variable, name] = match\n seen.add(full)\n out.push({ name, variable, filePath, relativePath: relPath })\n }\n\n // Second pass: bare calls not captured above (rare but possible)\n BARE_CREATE_TOKEN_REGEX.lastIndex = 0\n while ((match = BARE_CREATE_TOKEN_REGEX.exec(source)) !== null) {\n if (seen.has(match[0])) continue\n out.push({\n name: match[1],\n variable: null,\n filePath,\n relativePath: relPath,\n })\n }\n\n return out\n}\n\n/**\n * Extract route handlers from a source file.\n *\n * For each decorated class in `classesInFile`, slices the source from\n * the class declaration to the next class (or EOF) and runs the route\n * decorator regex within that slice. The result is a list of routes\n * tagged with their owning controller.\n *\n * Heuristic note: this assumes classes are not nested. KickJS controllers\n * are top-level by convention so this holds in practice.\n */\nexport function extractRoutesFromSource(\n source: string,\n filePath: string,\n cwd: string,\n classesInFile: DiscoveredClass[],\n mountPathByController: ReadonlyMap<string, string> = new Map(),\n): DiscoveredRoute[] {\n const out: DiscoveredRoute[] = []\n if (classesInFile.length === 0) return out\n const relPath = toRelative(filePath, cwd)\n\n // Locate each class declaration's offset in the source\n const positions: Array<{ cls: DiscoveredClass; start: number }> = []\n for (const cls of classesInFile) {\n const re = new RegExp(String.raw`class\\s+${cls.className}\\b`)\n const m = re.exec(source)\n if (m?.index !== undefined) {\n positions.push({ cls, start: m.index })\n }\n }\n positions.sort((a, b) => a.start - b.start)\n\n for (let i = 0; i < positions.length; i++) {\n const { cls, start } = positions[i]\n const end = i + 1 < positions.length ? positions[i + 1].start : source.length\n const block = source.slice(start, end)\n\n // Two-pass walk: locate each route decorator start, then balance-parse\n // forward through args and any stacked decorators to find the method\n // name. Replaces the previous single regex which mis-parsed nested\n // parens (forinda/kick-js#108).\n ROUTE_DECORATOR_START.lastIndex = 0\n let startMatch: RegExpExecArray | null\n while ((startMatch = ROUTE_DECORATOR_START.exec(block)) !== null) {\n const verb = startMatch[1]\n const decoratorStart = startMatch.index\n const openParen = ROUTE_DECORATOR_START.lastIndex - 1\n const closeParen = findBalancedClose(block, openParen)\n if (closeParen < 0) continue\n\n const routeArgs = block.slice(openParen + 1, closeParen)\n\n const pathLiteralMatch = routeArgs.match(/^\\s*['\"`]([^'\"`]*)['\"`]/)\n const path = pathLiteralMatch && pathLiteralMatch[1].length > 0 ? pathLiteralMatch[1] : '/'\n\n const methodInfo = readMethodAfterDecorators(block, closeParen + 1)\n if (!methodInfo) continue\n const { methodName, endPos } = methodInfo\n\n // Advance the regex iterator past this method so the next iteration\n // starts looking after the consumed region.\n ROUTE_DECORATOR_START.lastIndex = endPos\n\n const matchedText = block.slice(decoratorStart, endPos)\n const apiQp = extractApiQueryParams(matchedText, source)\n\n const bodyId = extractObjectFieldIdentifier(routeArgs, 'body')\n const queryId = extractObjectFieldIdentifier(routeArgs, 'query')\n const paramsId = extractObjectFieldIdentifier(routeArgs, 'params')\n\n // forinda/kick-js#235 §3 — when the controller is mounted under a path\n // with `:params` (e.g. `/orgs/:id/extensions`), surface those\n // params in `pathParams` so the typegen widens `ctx.params`\n // without adopters repeating `params: schema` on every route.\n const mountPath = mountPathByController.get(cls.className) ?? ''\n const fullPath = mountPath ? joinMountPath(mountPath, path) : path\n\n out.push({\n controller: cls.className,\n method: methodName,\n httpMethod: verb.toUpperCase() as DiscoveredRoute['httpMethod'],\n path,\n pathParams: extractPathParams(fullPath),\n queryFilterable: apiQp?.filterable ?? null,\n querySortable: apiQp?.sortable ?? null,\n querySearchable: apiQp?.searchable ?? null,\n bodySchema: bodyId\n ? { identifier: bodyId, source: resolveImportSource(source, bodyId) }\n : null,\n querySchema: queryId\n ? { identifier: queryId, source: resolveImportSource(source, queryId) }\n : null,\n paramsSchema: paramsId\n ? { identifier: paramsId, source: resolveImportSource(source, paramsId) }\n : null,\n filePath,\n relativePath: relPath,\n })\n }\n }\n\n return out\n}\n\n/** Extract `@Inject('literal')` calls from a single source file */\nexport function extractInjectsFromSource(\n source: string,\n filePath: string,\n cwd: string,\n): DiscoveredInject[] {\n const out: DiscoveredInject[] = []\n const relPath = toRelative(filePath, cwd)\n\n INJECT_LITERAL_REGEX.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = INJECT_LITERAL_REGEX.exec(source)) !== null) {\n out.push({ name: match[1], filePath, relativePath: relPath })\n }\n\n return out\n}\n\n/**\n * Extract the bounds of an object literal that begins at `openBracePos`\n * (the index of the `{` character). Returns the index of the matching `}`\n * or -1 if no match is found. Counts balanced braces only — does not\n * understand string literals so a `{` or `}` inside a string inside the\n * object will skew the depth counter (matches `findBalancedClose`).\n */\nfunction findBalancedBrace(text: string, openBracePos: number): number {\n let depth = 1\n for (let i = openBracePos + 1; i < text.length; i++) {\n const ch = text[i]\n if (ch === '{') depth++\n else if (ch === '}') {\n depth--\n if (depth === 0) return i\n }\n }\n return -1\n}\n\n/**\n * Extract plugins/adapters declared via `defineAdapter({ name: '...' })`\n * or `definePlugin({ name: '...' })` calls and via class-style adapters\n * (`class XxxAdapter implements AppAdapter` with a string-literal `name`\n * field).\n *\n * Only the literal `name:` field feeds the result — the symbol on the LHS\n * is irrelevant since `dependsOn` references the runtime name.\n */\nexport function extractPluginsAndAdaptersFromSource(\n source: string,\n filePath: string,\n cwd: string,\n): DiscoveredPluginOrAdapter[] {\n const out: DiscoveredPluginOrAdapter[] = []\n const relPath = toRelative(filePath, cwd)\n const seen = new Set<string>()\n\n // Pass 1: defineAdapter / definePlugin calls\n DEFINE_HELPER_START.lastIndex = 0\n let helperMatch: RegExpExecArray | null\n while ((helperMatch = DEFINE_HELPER_START.exec(source)) !== null) {\n const helper = helperMatch[1] as 'defineAdapter' | 'definePlugin'\n const openParen = DEFINE_HELPER_START.lastIndex - 1\n const closeParen = findBalancedClose(source, openParen)\n if (closeParen < 0) continue\n const callArgs = source.slice(openParen + 1, closeParen)\n // Look for the first `name: 'literal'` in the call args\n const nameMatch = /\\bname\\s*:\\s*['\"`]([^'\"`]+)['\"`]/.exec(callArgs)\n if (!nameMatch) continue\n const name = nameMatch[1]\n const dedupeKey = `${helper}::${name}::${filePath}`\n if (seen.has(dedupeKey)) continue\n seen.add(dedupeKey)\n out.push({\n kind: helper === 'definePlugin' ? 'plugin' : 'adapter',\n name,\n filePath,\n relativePath: relPath,\n })\n }\n\n // Pass 2: class-style adapters (`class X implements AppAdapter { name = 'X' }`)\n APP_ADAPTER_CLASS_REGEX.lastIndex = 0\n let classMatch: RegExpExecArray | null\n while ((classMatch = APP_ADAPTER_CLASS_REGEX.exec(source)) !== null) {\n const classStart = classMatch.index\n // Find the class body opening brace\n const bracePos = source.indexOf('{', classStart)\n if (bracePos < 0) continue\n const closeBrace = findBalancedBrace(source, bracePos)\n if (closeBrace < 0) continue\n const body = source.slice(bracePos + 1, closeBrace)\n const nameMatch = CLASS_NAME_FIELD_REGEX.exec(body)\n if (!nameMatch) continue\n const name = nameMatch[1]\n const dedupeKey = `class::${name}::${filePath}`\n if (seen.has(dedupeKey)) continue\n seen.add(dedupeKey)\n out.push({ kind: 'adapter', name, filePath, relativePath: relPath })\n }\n\n return out\n}\n\n/**\n * Extract context keys from `defineContextDecorator({ key: '...' })` and\n * `defineHttpContextDecorator({ key: '...' })` calls (including the\n * curried `.withParams<P>()({ key: '...' })` form). Only the literal\n * `key:` field feeds the result — the symbol on the LHS is irrelevant\n * since `dependsOn` references the runtime key string.\n *\n * Mirrors {@link extractPluginsAndAdaptersFromSource}: regex to the spec\n * object's opening paren, `findBalancedClose` to its end, then the first\n * `key: 'literal'` inside.\n */\nexport function extractContextKeysFromSource(\n source: string,\n filePath: string,\n cwd: string,\n): DiscoveredContextKey[] {\n const out: DiscoveredContextKey[] = []\n const relPath = toRelative(filePath, cwd)\n const seen = new Set<string>()\n\n CONTEXT_DECORATOR_START.lastIndex = 0\n // The match value itself is unused — we only need the loop to advance\n // and `lastIndex` to point just past the spec's opening paren.\n while (CONTEXT_DECORATOR_START.exec(source) !== null) {\n const openParen = CONTEXT_DECORATOR_START.lastIndex - 1\n const closeParen = findBalancedClose(source, openParen)\n if (closeParen < 0) continue\n const callArgs = source.slice(openParen + 1, closeParen)\n const keyMatch = /\\bkey\\s*:\\s*['\"`]([^'\"`]+)['\"`]/.exec(callArgs)\n if (!keyMatch) continue\n const key = keyMatch[1]\n if (seen.has(key)) continue\n seen.add(key)\n out.push({ key, filePath, relativePath: relPath })\n }\n\n return out\n}\n\n/**\n * Extract `defineAugmentation('Name', { description, example })` calls\n * from a source file. The metadata object is optional — when absent both\n * `description` and `example` resolve to `null`.\n */\nexport function extractAugmentationsFromSource(\n source: string,\n filePath: string,\n cwd: string,\n): DiscoveredAugmentation[] {\n const out: DiscoveredAugmentation[] = []\n const relPath = toRelative(filePath, cwd)\n\n DEFINE_AUGMENTATION_START.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = DEFINE_AUGMENTATION_START.exec(source)) !== null) {\n const name = match[1]\n let description: string | null = null\n let example: string | null = null\n\n // If the regex matched a metadata object opening (`, {`), parse it\n if (match[2]) {\n const bracePos = source.indexOf('{', match.index + match[0].length - 1)\n if (bracePos >= 0) {\n const closeBrace = findBalancedBrace(source, bracePos)\n if (closeBrace >= 0) {\n const body = source.slice(bracePos + 1, closeBrace)\n description = readStringField(body, 'description')\n example = readStringField(body, 'example')\n }\n }\n }\n\n out.push({ name, description, example, filePath, relativePath: relPath })\n }\n\n return out\n}\n\n/**\n * Pull a string-valued field out of a JS object-literal body, respecting\n * the opening quote so the value isn't truncated at the first foreign\n * quote character. Handles backslash escapes inside the literal.\n *\n * Why a custom parser instead of one regex per delimiter: real-world\n * `defineAugmentation` calls embed all three quote characters at once\n * — backtick template literals carrying TS shapes like\n * `'free' | 'pro'` (single quotes) AND `\\`ctx.get(...)\\`` (escaped\n * backticks). A character-class regex like `[^'\"`]+` truncates on the\n * first foreign quote it sees. This walker scans char-by-char from\n * the matched delimiter and only stops on the matching one.\n */\nfunction readStringField(body: string, field: string): string | null {\n // Locate `field:` followed by an opening quote. Tolerate any whitespace.\n const fieldRe = new RegExp(`\\\\b${field}\\\\s*:\\\\s*(['\"\\`])`, 'g')\n const m = fieldRe.exec(body)\n if (!m) return null\n const quote = m[1]\n const start = m.index + m[0].length\n let i = start\n let raw: string | null = null\n while (i < body.length) {\n const ch = body[i]\n if (ch === '\\\\') {\n // Skip the escaped char — supports \\`, \\', \\\", \\n, \\\\ etc.\n i += 2\n continue\n }\n if (ch === quote) {\n raw = body.slice(start, i)\n break\n }\n i++\n }\n if (raw === null) return null\n // Unescape JS string-literal escapes so the JSDoc renderer sees the\n // value the source author actually intended (`\\`` → `` ` ``, `\\'` →\n // `'`, etc). Without this, escaped backticks in a backtick template\n // literal would surface as literal backslashes in the catalogue.\n return raw.replace(/\\\\(.)/g, (_m, c) => {\n if (c === 'n') return '\\n'\n if (c === 't') return '\\t'\n if (c === 'r') return '\\r'\n return c\n })\n}\n\n/**\n * Default search order for the env schema file. Newer projects keep\n * the schema under `src/config/` so the framework's \"config\" concept\n * has a single home; older scaffolds dropped it at `src/env.ts` (kept\n * here for back-compat). The first match wins.\n */\nconst DEFAULT_ENV_FILE_CANDIDATES = [\n 'src/config/index.ts',\n 'src/config/env.ts',\n 'src/config.ts',\n 'src/env.ts',\n] as const\n\n/**\n * Look for an env schema file. When `envFile` is the string default\n * (`'src/env.ts'`) or omitted, every entry in `DEFAULT_ENV_FILE_CANDIDATES`\n * is tried in order. When the caller passes an explicit path, only that\n * path is tried (so projects can opt out of the search by setting\n * `kick.config.ts → typegen.envFile`).\n *\n * Returns a `DiscoveredEnv` if the file exists and contains both a\n * `defineEnv(...)` call and a default export — the two markers we\n * need before it's safe to emit `import type schema from '...'` in\n * the generator. Returns `null` for any other state (no candidate\n * found, no defineEnv, no default export) so the generator skips env\n * typing silently.\n */\nexport async function detectEnvFile(cwd: string, envFile: string): Promise<DiscoveredEnv | null> {\n // The CLI passes the literal default `'src/env.ts'` when the user\n // hasn't overridden it. Treat that as \"use the search list\" rather\n // than pinning to one path, so newer scaffolds at src/config/ keep\n // working without forcing every project to set typegen.envFile.\n const candidates: readonly string[] =\n envFile === 'src/env.ts' ? DEFAULT_ENV_FILE_CANDIDATES : [envFile]\n\n for (const candidate of candidates) {\n const abs = resolve(cwd, candidate)\n let source: string\n try {\n source = await readFile(abs, 'utf-8')\n } catch {\n continue\n }\n // Cheap heuristic: a schema-construction call AND a default\n // export. The default export must be the SCHEMA itself — the\n // generator emits `import type schema from '...'` and runs it\n // through schema-to-type inference. `loadEnvFromSchema(...)` is\n // deliberately NOT in the accept list because its return value is\n // the parsed env object, not the schema. Adopters routinely write\n // export default envSchema\n // export const env = loadEnvFromSchema(envSchema)\n // where only `envSchema` is the schema; detecting on\n // `loadEnvFromSchema` would also accept the anti-pattern\n // export default loadEnvFromSchema(schema)\n // which would push the parsed *env value* into `InferSchemaOutput`\n // and emit a broken `KickEnv`.\n //\n // Accept lists:\n // - `defineEnv(...)` — legacy Zod scaffold\n // - `fromZod / fromValibot / fromYup(...)` — kickjs-schema adapters\n if (!/\\bdefineEnv\\s*\\(/.test(source) && !/\\bfrom(Zod|Valibot|Yup)\\s*\\(/.test(source)) {\n continue\n }\n if (!/export\\s+default\\b/.test(source)) continue\n // Reject the \"default-export is the parsed env\" pattern\n // explicitly. Without this guard, a file that constructs the\n // schema with `fromZod(...)` and then does\n // `export default loadEnvFromSchema(envSchema)` would slip past\n // the schema-construction check above and feed the parsed env's\n // value type into `InferSchemaOutput`.\n if (/export\\s+default\\s+loadEnvFromSchema\\s*\\(/.test(source)) continue\n return {\n filePath: abs,\n relativePath: toRelative(abs, cwd),\n }\n }\n\n return null\n}\n\n/** Detect duplicate class names across files */\nexport function findCollisions(classes: DiscoveredClass[]): ClassCollision[] {\n const groups = new Map<string, DiscoveredClass[]>()\n for (const cls of classes) {\n const arr = groups.get(cls.className) ?? []\n arr.push(cls)\n groups.set(cls.className, arr)\n }\n\n const collisions: ClassCollision[] = []\n for (const [className, group] of groups) {\n // Two declarations of the same class name in different files = collision.\n // Multiple decorators on the same file/class are NOT a collision.\n const distinctFiles = new Set(group.map((c) => c.filePath))\n if (distinctFiles.size > 1) {\n collisions.push({ className, classes: group })\n }\n }\n\n // Deterministic order\n collisions.sort((a, b) => a.className.localeCompare(b.className))\n return collisions\n}\n\n/**\n * The complete per-file extraction result. Every field here is a pure\n * function of a single file's source text — no cross-file context — so\n * the whole object is cacheable keyed by the file's signature.\n *\n * The one subtlety is `routes`: their `pathParams` are computed with an\n * EMPTY mount map (own-path params only). The cross-file mount prefix\n * is re-applied during the join phase of `scanProject`, which is a\n * cheap pure-JS recompute (no regex, no I/O). This keeps routes fully\n * cacheable even though their final `pathParams` depend on a sibling\n * module file's `routes()` mount path.\n */\nexport interface FileExtract {\n classes: DiscoveredClass[]\n tokens: DiscoveredToken[]\n injects: DiscoveredInject[]\n pluginsAndAdapters: DiscoveredPluginOrAdapter[]\n augmentations: DiscoveredAugmentation[]\n contextKeys: DiscoveredContextKey[]\n /** Routes with own-path `pathParams` only — mount prefix applied at join. */\n routes: DiscoveredRoute[]\n /** `{ controller, mountPath }` pairs from this file's `routes()` body. */\n moduleMounts: ModuleMount[]\n /** `import.meta.glob([...])` patterns (only non-empty for `*.module.ts`). */\n globPatterns: string[]\n}\n\n/**\n * Run per-file extraction over one source string. Pure: depends only\n * on the file's own text, so the result is safe to cache by filesystem\n * signature (see `scanner-cache.ts`).\n *\n * AST-first (oxc-parser — exact decorator/argument parsing, template-\n * literal route paths, no balanced-delimiter heuristics), falling back\n * to the regex extractors when the file doesn't parse — mid-edit\n * syntax errors in watch mode still yield partial results that keep\n * the dev loop alive.\n */\nexport function extractFile(source: string, filePath: string, cwd: string): FileExtract {\n const ast = extractFileAst(source, filePath, cwd)\n if (ast) return ast\n return extractFileRegex(source, filePath, cwd)\n}\n\n/**\n * The original regex extraction pipeline — fallback for unparseable\n * sources and the parity baseline for the AST extractor's tests.\n */\nexport function extractFileRegex(source: string, filePath: string, cwd: string): FileExtract {\n const classes = extractClassesFromSource(source, filePath, cwd)\n return {\n classes,\n tokens: extractTokensFromSource(source, filePath, cwd),\n injects: extractInjectsFromSource(source, filePath, cwd),\n pluginsAndAdapters: extractPluginsAndAdaptersFromSource(source, filePath, cwd),\n augmentations: extractAugmentationsFromSource(source, filePath, cwd),\n contextKeys: extractContextKeysFromSource(source, filePath, cwd),\n // Empty mount map → own-path params only; prefix re-applied at join.\n routes: extractRoutesFromSource(source, filePath, cwd, classes, new Map()),\n moduleMounts: extractModuleMounts(source),\n globPatterns: /\\.module\\.[mc]?[tj]sx?$/.test(filePath) ? extractGlobPatterns(source) : [],\n }\n}\n\n/**\n * Read + extract a single file, consulting the optional persistent\n * cache first. On a signature hit the file is not read at all — the\n * cached extract is returned directly. Returns null when the file\n * cannot be read (deleted mid-scan / permission error).\n */\nasync function loadFileExtract(\n file: string,\n cwd: string,\n cache: ScanCache | null,\n): Promise<FileExtract | null> {\n const sig = cache ? await ScanCache.signature(file) : null\n if (cache && sig) {\n const hit = cache.get(file, sig)\n if (hit) {\n cache.set(file, sig, hit)\n return hit\n }\n }\n let source: string\n try {\n source = await readFile(file, 'utf-8')\n } catch {\n return null\n }\n const extract = extractFile(source, file, cwd)\n if (cache && sig) cache.set(file, sig, extract)\n return extract\n}\n\n/** Map a concurrency-bounded async fn over items, preserving order. */\nasync function mapConcurrent<T, R>(\n items: T[],\n limit: number,\n fn: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n const out: R[] = []\n let next = 0\n const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {\n for (;;) {\n const i = next++\n if (i >= items.length) return\n out[i] = await fn(items[i], i)\n }\n })\n await Promise.all(workers)\n return out\n}\n\n/**\n * Scan a project for decorated classes, createToken definitions, and\n * `@Inject` literal usages.\n *\n * Per-file extraction is read + parsed concurrently and, when\n * `opts.cacheDir` is set, served from a persistent signature cache so\n * unchanged files are never re-read on a watch/rebuild. The cross-file\n * join phase (mount-prefix resolution, orphan detection) always runs\n * over the full extract set, so cached entries can never desync output.\n */\nexport async function scanProject(opts: ScanOptions): Promise<ScanResult> {\n const root = resolve(opts.root)\n // Sort the walk output so the cross-file join sees files in the same\n // order the incremental path does (which sorts its working set). The\n // §3 mount-map is first-wins, so a stable order keeps a multi-mount\n // controller's pathParams identical across cold and incremental runs\n // (and across filesystems with different readdir ordering).\n const files = (await walk(root, opts)).toSorted()\n\n const cache = opts.cacheDir ? await ScanCache.load(opts.cacheDir) : null\n\n // Concurrent read + per-file extraction (cache-aware). I/O-bound, so\n // a modest fan-out hides per-file latency without thrashing the FD\n // table. Order is preserved for deterministic downstream iteration.\n const extracts = await mapConcurrent(files, 16, (file) => loadFileExtract(file, opts.cwd, cache))\n\n const joined = joinExtracts(files, extracts)\n const env = await detectEnvFile(opts.cwd, opts.envFile ?? 'src/env.ts')\n\n // Persist the refreshed cache (prunes entries for deleted files).\n if (cache) await cache.save()\n\n return { ...joined, env }\n}\n\n/** A precise set of filesystem changes, as reported by a watcher. */\nexport interface ScanDelta {\n /** Files added or modified since the last scan (absolute or cwd-relative). */\n changed: string[]\n /** Files deleted since the last scan. */\n removed: string[]\n}\n\n/**\n * Does `file` belong in the scan? Mirrors `walk()`'s ext + exclude\n * filtering so a watcher event for a `.d.ts`, a test, or a node_modules\n * file is ignored just as the full walk would ignore it.\n */\nfunction isScannableFile(file: string, root: string, opts: ScanOptions): boolean {\n const exts = opts.extensions ?? DEFAULT_EXTENSIONS\n const excludes = opts.exclude ?? DEFAULT_EXCLUDES\n if (!file.startsWith(root + sep) && file !== root) return false\n if (!exts.some((ext) => file.endsWith(ext))) return false\n const rel = relative(opts.cwd, file)\n if (excludes.some((ex) => rel.includes(ex))) return false\n return true\n}\n\n/**\n * Incremental scan driven by an exact watcher delta (e.g. Vite's\n * chokidar events). Unlike `scanProject` this performs NO directory\n * walk and NO `stat()` of unchanged files: it loads the persistent\n * cache, re-extracts only the `changed` files, drops `removed` ones,\n * and re-runs the cheap cross-file join over the resulting set.\n *\n * Requires a warm cache. With no `cacheDir`, an empty cache (cold\n * start), it transparently falls back to a full `scanProject` so the\n * caller never has to special-case the first run.\n */\nexport async function scanProjectIncremental(\n opts: ScanOptions,\n delta: ScanDelta,\n): Promise<ScanResult> {\n if (!opts.cacheDir) return scanProject(opts)\n const root = resolve(opts.root)\n const cache = await ScanCache.load(opts.cacheDir)\n const cachedFiles = cache.cachedFiles()\n if (cachedFiles.length === 0) return scanProject(opts)\n\n const removed = new Set(delta.removed.map((f) => resolve(opts.cwd, f)))\n const changed = delta.changed\n .map((f) => resolve(opts.cwd, f))\n .filter((f) => !removed.has(f) && isScannableFile(f, root, opts))\n const changedSet = new Set(changed)\n\n // Working set = (previously cached ∪ newly added) − removed.\n const working = new Set(cachedFiles)\n for (const f of changedSet) working.add(f)\n for (const f of removed) working.delete(f)\n\n // Re-extract only the changed files (concurrent). Everything else is\n // served from the cache without a read or a stat.\n const fresh = new Map<string, FileExtract>()\n await mapConcurrent(changed, 16, async (file) => {\n if (!working.has(file)) return\n const sig = await ScanCache.signature(file)\n let source: string\n try {\n source = await readFile(file, 'utf-8')\n } catch {\n working.delete(file)\n return\n }\n const extract = extractFile(source, file, opts.cwd)\n fresh.set(file, extract)\n if (sig) cache.set(file, sig, extract)\n })\n\n const orderedFiles = [...working].toSorted()\n const extracts = orderedFiles.map((file) => {\n const f = fresh.get(file)\n if (f) return f\n cache.carry(file) // keep the unchanged entry in the next saved cache\n return cache.peek(file)\n })\n\n const joined = joinExtracts(orderedFiles, extracts)\n const env = await detectEnvFile(opts.cwd, opts.envFile ?? 'src/env.ts')\n await cache.save()\n\n return { ...joined, env }\n}\n\n/**\n * Cross-file join over a set of per-file extracts: resolves mount-prefix\n * route params, detects glob-orphaned classes, concatenates and sorts\n * every discovered entity into a deterministic `ScanResult` (minus the\n * async `env` field, which the caller attaches). Pure and synchronous —\n * shared by both `scanProject` and `scanProjectIncremental`.\n */\nfunction joinExtracts(files: string[], extracts: (FileExtract | null)[]): Omit<ScanResult, 'env'> {\n const classes: DiscoveredClass[] = []\n const routes: DiscoveredRoute[] = []\n const tokens: DiscoveredToken[] = []\n const injects: DiscoveredInject[] = []\n const pluginsAndAdapters: DiscoveredPluginOrAdapter[] = []\n const augmentations: DiscoveredAugmentation[] = []\n const contextKeys: DiscoveredContextKey[] = []\n\n // forinda/kick-js#235 §3 — build a `Controller → mountPath` map from every\n // module file's `routes()` body so per-route `pathParams` can include\n // the prefix params (e.g. `/orgs/:id`) without adopters re-declaring\n // `params:` on every method. First mount wins on duplicates (rare\n // multi-mount controllers — typically share the prefix shape).\n const mountPathByController = new Map<string, string>()\n for (const extract of extracts) {\n if (!extract) continue\n for (const { controller, mountPath } of extract.moduleMounts) {\n if (!mountPathByController.has(controller)) {\n mountPathByController.set(controller, mountPath)\n }\n }\n }\n\n // A per-file glob-pattern map drives the §4 orphan pass below without\n // re-reading module sources.\n const globPatternsByFile = new Map<string, string[]>()\n for (let i = 0; i < files.length; i++) {\n const extract = extracts[i]\n if (!extract) continue\n classes.push(...extract.classes)\n tokens.push(...extract.tokens)\n injects.push(...extract.injects)\n pluginsAndAdapters.push(...extract.pluginsAndAdapters)\n augmentations.push(...extract.augmentations)\n contextKeys.push(...extract.contextKeys)\n if (extract.globPatterns.length > 0) globPatternsByFile.set(files[i], extract.globPatterns)\n\n // Re-apply the cross-file mount prefix to each cached route's\n // pathParams. Routes were extracted with an empty mount map, so\n // their pathParams currently carry own-path params only.\n for (const route of extract.routes) {\n const mountPath = mountPathByController.get(route.controller)\n if (mountPath) {\n const fullPath = joinMountPath(mountPath, route.path)\n routes.push({ ...route, pathParams: extractPathParams(fullPath) })\n } else {\n routes.push(route)\n }\n }\n }\n\n // forinda/kick-js#235 §4 — for every module file, extract its\n // `import.meta.glob([...])` patterns and flag any decorated class\n // whose file sits inside the module directory but isn't matched by\n // a positive pattern. Catches the \"added a new file type, forgot to\n // extend the glob\" silent-degradation case.\n // Normalize Windows backslashes to forward slashes before any\n // slicing / startsWith / glob-matching — the rest of the scanner\n // already speaks forward-slash relative paths, but absolute\n // `filePath` values may carry the platform separator on Windows.\n const orphanedClasses: OrphanedClass[] = []\n for (const [moduleFile, patterns] of globPatternsByFile) {\n if (!/\\.module\\.[mc]?[tj]sx?$/.test(moduleFile)) continue\n if (patterns.length === 0) continue\n const moduleFilePosix = moduleFile.replaceAll(sep, '/')\n const moduleDir = moduleFilePosix.slice(0, moduleFilePosix.lastIndexOf('/'))\n for (const cls of classes) {\n // Skip module files themselves — they're scanner-synthesized\n // `decorator: 'Module'` entries that aren't glob contributors.\n if (cls.decorator === 'Module') continue\n const classFilePosix = cls.filePath.replaceAll(sep, '/')\n if (!classFilePosix.startsWith(moduleDir + '/')) continue\n if (classFilePosix === moduleFilePosix) continue\n const moduleRelative = classFilePosix.slice(moduleDir.length + 1)\n if (!fileMatchesAnyGlob(moduleRelative, patterns)) {\n orphanedClasses.push({\n className: cls.className,\n filePath: cls.filePath,\n relativePath: cls.relativePath,\n moduleFilePath: moduleFile,\n decorator: cls.decorator,\n })\n }\n }\n }\n\n // Deterministic ordering for stable .d.ts output\n classes.sort((a, b) => {\n if (a.className !== b.className) return a.className.localeCompare(b.className)\n return a.relativePath.localeCompare(b.relativePath)\n })\n tokens.sort(\n (a, b) => a.name.localeCompare(b.name) || a.relativePath.localeCompare(b.relativePath),\n )\n injects.sort(\n (a, b) => a.name.localeCompare(b.name) || a.relativePath.localeCompare(b.relativePath),\n )\n routes.sort(\n (a, b) => a.controller.localeCompare(b.controller) || a.method.localeCompare(b.method),\n )\n pluginsAndAdapters.sort(\n (a, b) => a.name.localeCompare(b.name) || a.relativePath.localeCompare(b.relativePath),\n )\n augmentations.sort(\n (a, b) => a.name.localeCompare(b.name) || a.relativePath.localeCompare(b.relativePath),\n )\n contextKeys.sort(\n (a, b) => a.key.localeCompare(b.key) || a.relativePath.localeCompare(b.relativePath),\n )\n\n const collisions = findCollisions(classes)\n\n orphanedClasses.sort(\n (a, b) =>\n a.relativePath.localeCompare(b.relativePath) || a.className.localeCompare(b.className),\n )\n\n return {\n classes,\n routes,\n tokens,\n injects,\n collisions,\n pluginsAndAdapters,\n augmentations,\n contextKeys,\n orphanedClasses,\n }\n}\n","/**\n * Pure renderers for the DI-manifest typegen surface — the\n * `KickJsRegistry` augmentation, the `ServiceToken` / `ModuleToken`\n * unions, the `KickJsPluginRegistry` augmentation, and the\n * `defineAugmentation` catalogue.\n *\n * These used to live in `generator.ts` (the monolithic legacy pass).\n * They are now consumed by the per-domain builtin typegen plugins\n * (`kick/registry`, `kick/services`, `kick/modules`, `kick/plugins`,\n * `kick/augmentations`) so the whole pipeline is plugin-based and each\n * file is emitted + cache-tracked independently by the runner.\n *\n * Every function here is pure (scan data in → string out); no fs, no\n * config, no globals.\n *\n * @module @forinda/kickjs-cli/typegen/render/manifest\n */\n\nimport { dirname, relative, sep } from 'node:path'\nimport type {\n ClassCollision,\n DiscoveredAugmentation,\n DiscoveredClass,\n DiscoveredInject,\n DiscoveredPluginOrAdapter,\n DiscoveredToken,\n} from '../scanner'\n\n/** Header written to every generated file */\nexport const HEADER = `/* eslint-disable */\n// AUTO-GENERATED by \\`kick typegen\\`. DO NOT EDIT.\n// Re-run with \\`kick typegen\\` or rely on \\`kick dev\\` to refresh.\n`\n\n/** Decorators whose classes participate in the DI registry augmentation */\nexport const REGISTRY_DECORATORS = new Set(['Service', 'Repository', 'Injectable', 'Component'])\n\n/** Thrown when collisions are found and not allowed */\nexport class TokenCollisionError extends Error {\n readonly collisions: ClassCollision[]\n constructor(collisions: ClassCollision[]) {\n super(formatCollisionMessage(collisions))\n this.name = 'TokenCollisionError'\n this.collisions = collisions\n }\n}\n\n/** Build a human-readable message describing every collision */\nfunction formatCollisionMessage(collisions: ClassCollision[]): string {\n const lines: string[] = ['kick typegen: token collision detected']\n for (const c of collisions) {\n lines.push('')\n lines.push(` ${c.classes.length} classes named '${c.className}':`)\n for (const cls of c.classes) {\n lines.push(` - ${cls.relativePath}`)\n }\n }\n lines.push('')\n lines.push('Resolutions:')\n lines.push(' (a) Rename one of the classes')\n lines.push(\n \" (b) Use createToken<T>('namespaced/Name') and import the token explicitly — see @forinda/kickjs\",\n )\n lines.push(' (c) Pass --allow-duplicates to namespace the registry keys automatically')\n lines.push(\" (e.g. 'modules/users/UserService' instead of 'UserService')\")\n return lines.join('\\n')\n}\n\n/** Compute the module specifier (without extension) used inside `import('...')` */\nfunction importSpecifierFor(targetFile: string, fromFile: string): string {\n const fromDir = dirname(fromFile)\n let rel = relative(fromDir, targetFile).split(sep).join('/')\n rel = rel.replace(/\\.(ts|tsx|mts|cts)$/i, '')\n if (!rel.startsWith('.')) rel = './' + rel\n return rel\n}\n\n/**\n * Build the namespaced registry key for a colliding class.\n * Strips the `src/` prefix and the file extension, then appends the\n * class name. Example: `src/modules/users/user.service.ts` + `UserService`\n * → `modules/users/UserService`.\n */\nexport function namespacedKeyFor(cls: DiscoveredClass): string {\n const rel = cls.relativePath.replace(/^src\\//, '').replace(/\\.(ts|tsx|mts|cts)$/i, '')\n const parts = rel.split('/')\n parts.pop()\n const ns = parts.join('/')\n return ns ? `${ns}/${cls.className}` : cls.className\n}\n\n/**\n * Render the `KickJsRegistry` module augmentation. Each entry maps a\n * string token to the imported class type. Default-exported classes are\n * imported as `import('...').default`. `collidingNames` lists class\n * names that should be auto-namespaced; everything else gets a bare key.\n */\nexport function renderRegistry(\n classes: DiscoveredClass[],\n outFile: string,\n collidingNames: Set<string>,\n): string {\n const seen = new Set<string>()\n const entries: string[] = []\n\n for (const c of classes) {\n if (!REGISTRY_DECORATORS.has(c.decorator)) continue\n\n const key = collidingNames.has(c.className) ? namespacedKeyFor(c) : c.className\n if (seen.has(key)) continue\n seen.add(key)\n\n const spec = importSpecifierFor(c.filePath, outFile)\n const ref = c.isDefault ? `import('${spec}').default` : `import('${spec}').${c.className}`\n entries.push(` '${key}': ${ref}`)\n }\n\n const body = entries.length\n ? entries.join('\\n')\n : ' // (no services discovered yet — run `kick g service <name>` to add one)'\n\n return `${HEADER}\ndeclare module '@forinda/kickjs' {\n interface KickJsRegistry {\n${body}\n }\n}\n\nexport {}\n`\n}\n\n/** True when `str` is a bare JS identifier (usable as an unquoted key). */\nfunction isIdentifierKey(str: string): boolean {\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(str)\n}\n\n/**\n * Render the `ContextKeys` augmentation from discovered context-decorator\n * keys. Each `key:` literal becomes a `'<key>': true` entry — a key-only\n * registry (the value type is irrelevant; `true` is conventional) so\n * `dependsOn: ['<key>']` is autocompleted + typo-checked without forcing\n * a value type into `ContextMeta`. Non-identifier keys are quoted.\n */\nexport function renderContextKeys(keys: readonly { key: string }[]): string {\n const unique = [...new Set(keys.map((k) => k.key))].toSorted()\n const body = unique\n .map((k) => ` ${isIdentifierKey(k) ? k : JSON.stringify(k)}: true`)\n .join('\\n')\n return `${HEADER}\ndeclare module '@forinda/kickjs' {\n /**\n * Key-only registry of every context key produced by a\n * \\`defineContextDecorator\\` / \\`defineHttpContextDecorator\\` in the\n * project. Feeds \\`dependsOn\\` typo-checking. Value types live in\n * \\`ContextMeta\\`; this only records that the key exists.\n */\n interface ContextKeys {\n${body}\n }\n}\n\nexport {}\n`\n}\n\n/** Render a string-literal union type containing the given names */\nexport function renderUnion(typeName: string, names: string[], emptyComment: string): string {\n if (names.length === 0) {\n return `${HEADER}\n// ${emptyComment}\nexport type ${typeName} = never\n`\n }\n const sorted = [...new Set(names)].toSorted()\n return `${HEADER}\nexport type ${typeName} =\n${sorted.map((n) => ` | '${n}'`).join('\\n')}\n`\n}\n\n/**\n * Build the `ServiceToken` union members — class names (namespaced on\n * collision), plus `createToken('name')` and `@Inject('literal')`\n * literals — so tooling autocomplete sees every known token.\n */\nexport function buildServiceTokens(\n classes: DiscoveredClass[],\n tokens: DiscoveredToken[],\n injects: DiscoveredInject[],\n collidingNames: Set<string>,\n): string[] {\n const classTokens = classes\n .filter((c) => REGISTRY_DECORATORS.has(c.decorator))\n .map((c) => (collidingNames.has(c.className) ? namespacedKeyFor(c) : c.className))\n return [...classTokens, ...tokens.map((t) => t.name), ...injects.map((i) => i.name)]\n}\n\n/** Build the `ModuleToken` union members — discovered `@Module` class names. */\nexport function buildModuleTokens(classes: DiscoveredClass[]): string[] {\n return classes.filter((c) => c.decorator === 'Module').map((c) => c.className)\n}\n\n/**\n * Render the `KickJsPluginRegistry` augmentation. Each entry maps the\n * literal `name` field of a plugin/adapter to a marker type (the\n * registry value isn't load-bearing at runtime — `dependsOn` only cares\n * about `keyof`, so any non-`never` type works). We emit `'plugin'` /\n * `'adapter'` strings so DevTools can later read the registry to tell\n * the kinds apart without a second source of truth.\n *\n * When the project has no discoverable plugins/adapters, the\n * augmentation is intentionally empty rather than skipped so the\n * `keyof` constraint resolves to `never` (harmless — `dependsOn: []`\n * still works).\n */\nexport function renderPlugins(items: DiscoveredPluginOrAdapter[]): string {\n // Dedupe by name — two declarations with the same name are a runtime\n // boot-time error in `mount-sort.ts`; we surface the conflict via a\n // single registry entry rather than a duplicate-key TS error.\n const byName = new Map<string, DiscoveredPluginOrAdapter>()\n for (const item of items) {\n if (!byName.has(item.name)) byName.set(item.name, item)\n }\n\n const sorted = [...byName.values()].toSorted((a, b) => a.name.localeCompare(b.name))\n const entries = sorted.map((item) => ` '${item.name}': '${item.kind}'`).join('\\n')\n\n const body = entries\n ? entries\n : ' // (no plugins/adapters discovered yet — `defineAdapter`/`definePlugin` calls feed this)'\n\n return `${HEADER}\ndeclare module '@forinda/kickjs' {\n /**\n * Map of every plugin/adapter \\`name\\` discovered in the project. The\n * value type is the kind tag (\\`'plugin'\\` or \\`'adapter'\\`); the\n * \\`keyof\\` of this interface narrows \\`dependsOn\\` so misspelled deps\n * become compile errors instead of boot-time \\`MissingMountDepError\\`.\n */\n interface KickJsPluginRegistry {\n${body}\n }\n}\n\nexport {}\n`\n}\n\n/**\n * Render the augmentation manifest — one block per `defineAugmentation`\n * call discovered in the project. The output is a `.d.ts` file that does\n * nothing at runtime but acts as in-IDE documentation.\n */\nexport function renderAugmentations(items: DiscoveredAugmentation[]): string {\n if (items.length === 0) {\n return `${HEADER}\n// No augmentations discovered.\n//\n// Plugins advertise augmentable interfaces via:\n//\n// import { defineAugmentation } from '@forinda/kickjs'\n// defineAugmentation('FeatureFlags', {\n// description: 'Feature flag shape consumed by FlagsPlugin',\n// example: '{ beta: boolean; rolloutPercentage: number }',\n// })\n//\n// See \\`docs/guide/typegen.md#augmentations\\` for the full pattern.\nexport {}\n`\n }\n\n // Dedupe by name — multiple plugins shouldn't claim the same name,\n // but if they do we keep the first.\n const byName = new Map<string, DiscoveredAugmentation>()\n for (const item of items) {\n if (!byName.has(item.name)) byName.set(item.name, item)\n }\n\n const blocks: string[] = []\n for (const item of [...byName.values()].toSorted((a, b) => a.name.localeCompare(b.name))) {\n const docLines: string[] = []\n if (item.description) {\n for (const line of item.description.split('\\n')) docLines.push(` * ${line}`)\n }\n if (item.example) {\n docLines.push(` * @example`, ` * \\`\\`\\`ts`)\n for (const line of item.example.split('\\n')) docLines.push(` * ${line}`)\n docLines.push(` * \\`\\`\\``)\n }\n docLines.push(` * @see ${item.relativePath}`)\n blocks.push(\n ['/**', ...docLines, ' */', `export interface ${item.name}Augmentation {}`].join('\\n'),\n )\n }\n\n return `${HEADER}\n// Catalogue of augmentable interfaces in this project. The interfaces\n// below are documentation only — augment the source-of-truth interfaces\n// in your own \\`d.ts\\` files (the framework declares the actual types).\n\n${blocks.join('\\n\\n')}\n`\n}\n","/**\n * Token convention validator (architecture.md §22.4 #1).\n *\n * Warns (never errors) on `createToken('literal')` calls whose literal\n * doesn't match the convention from §22.2:\n *\n * <scope>/<PascalKey>[/<suffix>][:<instance>[:<extra>]*]\n *\n * Where `<scope>` is lowercase + may start with the reserved `kick/`\n * prefix for first-party tokens. Legacy framework tokens that started\n * with `kickjs.` (pre-§22) are exempt — they get migrated alongside\n * the first-party adapter migrations.\n *\n * The matching reserved-prefix check (third-party tokens squatting\n * `kick/`) is the responsibility of `@forinda/kickjs-lint`'s\n * `token-reserved-prefix` rule, not the typegen layer — different\n * audience (adopter codebase) and different default severity.\n *\n * @module @forinda/kickjs-cli/typegen/token-conventions\n */\n\nimport type { DiscoveredToken } from './scanner'\n\n/**\n * Regex for the §22.2 token shape. Breakdown:\n *\n * - `^(kick\\/)?` — optional reserved framework prefix.\n * - `([a-z][\\w-]*\\/[A-Z]\\w*)` — `<scope>/<PascalKey>`. Scope is\n * lowercase, key is PascalCase.\n * - `(\\/.+)?` — optional `/suffix` for sub-flavours\n * (e.g. `mycorp/Cache/redis`).\n * - `(:[a-z][\\w-]+(:[a-z][\\w-]+)*)?` — optional `:instance` (and\n * further `:extra` colon-sections) for `.scoped()` shards.\n */\nconst TOKEN_CONVENTION_REGEX =\n /^(kick\\/)?([a-z][\\w-]*\\/[A-Z]\\w*)(\\/.+)?(:[a-z][\\w-]+(:[a-z][\\w-]+)*)?$/\n\nconst LEGACY_PREFIX = 'kickjs.'\n\nexport interface TokenConventionWarning {\n token: string\n variable: string | null\n filePath: string\n reason: string\n suggestion?: string\n}\n\nexport function validateTokenConventions(\n tokens: readonly DiscoveredToken[],\n): TokenConventionWarning[] {\n const warnings: TokenConventionWarning[] = []\n for (const token of tokens) {\n const name = token.name\n if (name.startsWith(LEGACY_PREFIX)) continue\n if (TOKEN_CONVENTION_REGEX.test(name)) continue\n warnings.push({\n token: name,\n variable: token.variable,\n filePath: token.relativePath,\n reason: 'does not match `<scope>/<PascalKey>[/<suffix>][:<instance>]`',\n suggestion: suggestRename(name),\n })\n }\n return warnings\n}\n\nfunction suggestRename(name: string): string | undefined {\n if (/^[A-Z]\\w*$/.test(name)) {\n return `'<scope>/${name}' (e.g. 'mycorp/${name}')`\n }\n if (name.includes('.')) {\n return `consider '<scope>/PascalKey' instead of dotted form`\n }\n const slashLower = /^([a-z][\\w-]*)\\/([a-z]\\w*)$/.exec(name)\n if (slashLower) {\n const [, scope, key] = slashLower\n return `'${scope}/${key.charAt(0).toUpperCase()}${key.slice(1)}'`\n }\n return undefined\n}\n","/**\n * Walks every `assetMap` entry's source directory + emits a typed\n * `KickAssets` ambient augmentation (assets-plan.md PR 4). Generates\n * `.kickjs/types/assets.d.ts` so adopters get autocomplete on\n * `assets.<namespace>.<key>` and `@Asset('<namespace>/<key>')`.\n *\n * Pure module — no side effects beyond what the caller does with the\n * returned content. Mirrors the shape of `renderPlugins` /\n * `renderRegistry` in the generator so the typegen output stays\n * consistent across surfaces.\n *\n * @module @forinda/kickjs-cli/typegen/asset-types\n */\n\nimport { statSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { globSync } from 'glob'\nimport { groupAssetKeys } from '@forinda/kickjs'\nimport type { AssetMapEntry } from '../config'\n\nexport interface DiscoveredAssetEntry {\n namespace: string\n /**\n * Path under the namespace — what comes after the `<namespace>/`\n * prefix in the logical key. Stripped of extension when the file's\n * basename was unique within the namespace, or carries the full\n * extension when a collision was auto-resolved.\n */\n key: string\n}\n\nexport interface DiscoveredAssets {\n entries: DiscoveredAssetEntry[]\n count: number\n}\n\nexport function discoverAssets(\n assetMap: Record<string, AssetMapEntry> | undefined,\n cwd: string,\n): DiscoveredAssets {\n if (!assetMap) return { entries: [], count: 0 }\n\n const seen = new Map<string, DiscoveredAssetEntry>()\n for (const [namespace, entry] of Object.entries(assetMap)) {\n if (!entry || typeof entry.src !== 'string') continue\n const srcAbs = resolve(cwd, entry.src)\n if (!isDir(srcAbs)) continue\n // Mirror build.ts: same `glob` engine, same defaults — typegen +\n // build agree on what counts as an asset, including full minimatch\n // patterns (extglob, negation, etc.) that the old lite matcher\n // silently treated as match-all.\n const matches = globSync(entry.glob ?? '**/*', {\n cwd: srcAbs,\n nodir: true,\n dot: false,\n posix: true,\n })\n matches.sort()\n // Run the same key-shaping logic the build pipeline + dev resolver\n // use, so the emitted type matches the manifest exactly. Auto-mode\n // keeps extensions on collision groups; singletons stay stripped.\n const { pairs } = groupAssetKeys(namespace, matches, {\n strategy: entry.keys ?? 'auto',\n })\n for (const { key: logical } of pairs) {\n // The helper returns the full `<namespace>/<...>` form; strip\n // the namespace prefix so the entry shape matches the legacy\n // contract (key = path under namespace).\n const subKey = logical.slice(namespace.length + 1)\n seen.set(logical, { namespace, key: subKey })\n }\n }\n return { entries: [...seen.values()], count: seen.size }\n}\n\nexport function renderAssetTypes(discovered: DiscoveredAssets): string {\n const HEADER = `/* eslint-disable */\n// AUTO-GENERATED by \\`kick typegen\\`. DO NOT EDIT.\n// Re-run with \\`kick typegen\\` or rely on \\`kick dev\\` to refresh.\n`\n\n if (discovered.entries.length === 0) {\n return `${HEADER}\ndeclare module '@forinda/kickjs' {\n /**\n * Map of every typed asset discovered in the project's assetMap.\n * (No assetMap entries discovered yet — declare with\n * \\`assetMap: { name: { src: 'src/...' } }\\` in kick.config.ts.)\n */\n interface KickAssets {}\n}\n\nexport {}\n`\n }\n\n const tree: TreeNode = {}\n for (const entry of discovered.entries) {\n const path = `${entry.namespace}/${entry.key}`.split('/')\n let node = tree\n for (let i = 0; i < path.length - 1; i++) {\n const part = path[i]\n const existing = node[part]\n if (existing === LEAF) {\n // The intermediate path was previously claimed by a leaf —\n // collision between a file (`mails/welcome.ejs`) and a\n // directory (`mails/welcome/x.ejs`). Promote to a sub-tree\n // so the directory wins (consistent with the file-tree\n // semantics; the build pipeline emits a runtime warning for\n // the colliding leaf).\n const promoted: TreeNode = {}\n node[part] = promoted\n node = promoted\n } else {\n if (!existing) node[part] = {}\n node = node[part] as TreeNode\n }\n }\n const leaf = path[path.length - 1]\n if (typeof node[leaf] === 'object') {\n // The leaf collides with an already-built subtree. Skip; the\n // directory wins (matches the promote-to-subtree branch above).\n continue\n }\n node[leaf] = LEAF\n }\n\n const body = renderTree(tree, ' ')\n return `${HEADER}\ndeclare module '@forinda/kickjs' {\n /**\n * Map of every typed asset discovered in the project's assetMap.\n * Each leaf is a \\`() => string\\` thunk that returns the resolved\n * absolute path for the file in the current run mode (dev → src,\n * prod → dist).\n */\n interface KickAssets {\n${body}\n }\n}\n\nexport {}\n`\n}\n\nconst LEAF = Symbol('asset-leaf')\ntype TreeNode = { [key: string]: TreeNode | typeof LEAF }\n\nfunction renderTree(node: TreeNode, indent: string): string {\n const keys = Object.keys(node).toSorted()\n const lines: string[] = []\n for (const key of keys) {\n const child = node[key]\n const safeKey = isIdentifier(key) ? key : JSON.stringify(key)\n if (child === LEAF) {\n lines.push(`${indent}${safeKey}: () => string`)\n } else {\n lines.push(`${indent}${safeKey}: {`)\n lines.push(renderTree(child, `${indent} `))\n lines.push(`${indent}}`)\n }\n }\n return lines.join('\\n')\n}\n\nfunction isIdentifier(str: string): boolean {\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(str)\n}\n\nfunction isDir(path: string): boolean {\n try {\n return statSync(path).isDirectory()\n } catch {\n return false\n }\n}\n","/**\n * Public entry point for the KickJS typegen module.\n *\n * Used by:\n * - `kick typegen` (one-shot or watch mode)\n * - `kick dev` (auto-runs once before Vite starts; refreshes when files change)\n *\n * @module @forinda/kickjs-cli/typegen\n */\n\nimport { resolve, basename, dirname, join } from 'node:path'\nimport { mkdir, readdir, stat, unlink, writeFile } from 'node:fs/promises'\nimport { scanProject, scanProjectIncremental, type ScanDelta, type ScanResult } from './scanner'\nimport {\n buildModuleTokens,\n buildServiceTokens,\n REGISTRY_DECORATORS,\n TokenCollisionError,\n} from './render/manifest'\nimport { validateTokenConventions, type TokenConventionWarning } from './token-conventions'\nimport { discoverAssets } from './asset-types'\nimport type { AssetMapEntry } from '../config'\nimport type { TypegenPluginResult } from './plugin'\n\nexport type {\n DiscoveredClass,\n DiscoveredToken,\n DiscoveredInject,\n DiscoveredEnv,\n DiscoveredPluginOrAdapter,\n DiscoveredAugmentation,\n ClassCollision,\n ScanResult,\n} from './scanner'\nexport { TokenCollisionError } from './render/manifest'\nexport { validateTokenConventions, type TokenConventionWarning } from './token-conventions'\n\n/**\n * Result of a typegen run — useful for logging and tests. Computed from\n * the scan result + asset discovery; the per-file emission itself is now\n * owned entirely by the typegen plugins (see `builtin/`).\n */\nexport interface GenerateResult {\n /** Number of registry-decorated classes (KickJsRegistry entries) */\n registryEntries: number\n /** Number of unique service tokens (classes + createToken + @Inject literals) */\n serviceTokens: number\n /** Number of module tokens */\n moduleTokens: number\n /** Number of route entries */\n routeEntries: number\n /** Number of unique plugin/adapter names */\n pluginEntries: number\n /** Number of unique `defineAugmentation` calls */\n augmentationEntries: number\n /** Number of typed asset entries */\n assetEntries: number\n /** Whether a typed env augmentation will be emitted */\n envWritten: boolean\n /** Files written this pass (barrel + plugin outputs), for the sweep */\n written: string[]\n /** Number of collisions (only > 0 with allowDuplicates) */\n resolvedCollisions: number\n}\n\n/** Options for `runTypegen` */\nexport interface RunTypegenOptions {\n /** Project root (defaults to `process.cwd()`) */\n cwd?: string\n /** Source directory to scan (defaults to `src`) */\n srcDir?: string\n /** Output directory (defaults to `.kickjs/types`) */\n outDir?: string\n /** Suppress console output */\n silent?: boolean\n /**\n * When `true`, duplicate class names are auto-namespaced by file path\n * instead of throwing. `kick dev` enables this so the dev server is\n * never blocked by an in-progress rename. CLI default is `false` so\n * `kick typegen` (and CI) catches collisions early. */\n allowDuplicates?: boolean\n /**\n * Disable the persistent per-file scan cache (`.kickjs/cache/scan.json`).\n * Every file is re-read + re-extracted from cold. Escape hatch for the\n * rare `mtimeMs:size` signature collision (a file edited so fast its\n * mtime + size are unchanged) where the cache would serve a stale\n * extract. Wired to `kick typegen --no-cache` / `kick dev --no-typegen-cache`.\n */\n noCache?: boolean\n /**\n * Schema validator used to derive `body`/`query`/`params` types from\n * route metadata. Currently only `'zod'` is supported; `false` (the\n * default) leaves these fields as `unknown`. Loaded from\n * `kick.config.ts` `typegen.schemaValidator` when invoked via the CLI.\n */\n schemaValidator?: 'zod' | 'kickjs-schema' | false\n /**\n * Path to the env schema file (relative to `cwd`). The file must\n * default-export a `defineEnv(...)` schema for the typed `KickEnv`\n * augmentation to be emitted. Defaults to `'src/env.ts'`. Set to\n * `false` to disable env typing entirely.\n */\n envFile?: string | false\n /**\n * Asset map from `kick.config.ts`. When set, `runTypegen` walks\n * each entry's `src` directory + emits `.kickjs/types/assets.d.ts`\n * augmenting `KickAssets` for autocomplete on `assets.x.y()` and\n * `@Asset('x/y')`. Omit to skip the asset typegen pass entirely.\n */\n assetMap?: Record<string, AssetMapEntry>\n /**\n * Whether `runTypegen` should also run the TypegenPlugin pipeline\n * (`runAllPluginTypegens`) after the legacy generator pass. Defaults\n * to `true` so single-shot callers (kick g, commands/typegen, tests)\n * keep getting a fully-refreshed `.kickjs/types/` from one entry\n * point. `watchTypegen` flips this to `false` because it manages\n * the plugin pass itself + would otherwise double-run it on every\n * filesystem trigger.\n */\n runPlugins?: boolean\n /**\n * Exact watcher delta (Vite chokidar events). When present, the scan\n * runs incrementally — re-extracting only the changed files and\n * skipping the directory walk — and the same delta is forwarded to\n * the plugin pass. `kick dev` supplies this on every file change.\n */\n changedFiles?: ScanDelta\n}\n\n/** Resolve options to absolute paths */\nfunction resolveOptions(opts: RunTypegenOptions): {\n cwd: string\n srcDir: string\n outDir: string\n silent: boolean\n allowDuplicates: boolean\n schemaValidator: 'zod' | 'kickjs-schema' | false\n envFile: string | false\n} {\n const cwd = opts.cwd ?? process.cwd()\n return {\n cwd,\n srcDir: resolve(cwd, opts.srcDir ?? 'src'),\n outDir: resolve(cwd, opts.outDir ?? '.kickjs/types'),\n silent: opts.silent ?? false,\n allowDuplicates: opts.allowDuplicates ?? false,\n schemaValidator: opts.schemaValidator ?? false,\n envFile: opts.envFile ?? 'src/env.ts',\n }\n}\n\n/**\n * Run a single typegen pass: scan source files, generate `.d.ts` files.\n *\n * Returns the discovered scan result alongside the generation result so\n * callers (`kick dev`, devtools) can log them or feed them to other tools.\n *\n * Throws `TokenCollisionError` if duplicate class names are found and\n * `allowDuplicates` is false.\n */\nexport async function runTypegen(opts: RunTypegenOptions = {}): Promise<{\n scan: ScanResult\n result: GenerateResult\n /** Token convention warnings — empty when every literal matches §22.2. */\n tokenWarnings: TokenConventionWarning[]\n}> {\n const { cwd, srcDir, outDir, silent, allowDuplicates, envFile } = resolveOptions(opts)\n\n const start = Date.now()\n const scanOpts = {\n root: srcDir,\n cwd,\n // Omitting cacheDir disables the persistent scan cache entirely —\n // every file is re-read cold (the --no-cache escape hatch).\n cacheDir: opts.noCache ? undefined : resolve(cwd, '.kickjs', 'cache'),\n // Pass through unless explicitly disabled\n envFile: envFile === false ? undefined : envFile,\n }\n const scan = opts.changedFiles\n ? await scanProjectIncremental(scanOpts, opts.changedFiles)\n : await scanProject(scanOpts)\n\n // Collision gate. This used to live inside the legacy generator\n // (`generateTypes` threw before writing). Now that every file is\n // emitted by an isolated plugin, the gate must run here — at the\n // orchestration level, before any plugin executes — so a duplicate\n // class name fails the whole command loudly instead of being silently\n // namespaced by the registry plugin.\n if (scan.collisions.length > 0 && !allowDuplicates) {\n throw new TokenCollisionError(scan.collisions)\n }\n\n const assets = discoverAssets(opts.assetMap, cwd)\n\n // Every `.kickjs/types/*` file is now owned by a typegen plugin\n // (builtin/ + adopter plugins). Run the pipeline by default so\n // single-shot callers (kick g <leaf> / kick new / tests) stay on one\n // entry point and see a fully-refreshed `.kickjs/types/` after the\n // call returns. The `kick typegen` / `kick dev` / `kick build` /\n // watch paths opt out (`runPlugins: false`) because they drive the\n // plugin pass externally (for --check + per-plugin status) and would\n // otherwise double-run it — they call `writeTypegenArtifacts`\n // themselves after their own plugin pass.\n let pluginResults: TypegenPluginResult[] = []\n const written: string[] = []\n if (opts.runPlugins !== false) {\n try {\n const { runAllPluginTypegens } = await import('./run-plugins')\n const { loadKickConfig } = await import('../config')\n const pluginConfig = await loadKickConfig(cwd)\n pluginResults = await runAllPluginTypegens({\n cwd,\n config: pluginConfig,\n silent: true,\n changedFiles: opts.changedFiles,\n })\n } catch (err) {\n // Broken plugins shouldn't block dev tooling. The runner already\n // isolates each plugin (per-plugin try/catch), so reaching here\n // means a non-plugin failure (scanner, fs). Surfacing as a throw\n // would abort the wider command (kick g, kick new); log + continue.\n if (!silent) {\n const msg = err instanceof Error ? err.message : String(err)\n console.warn(` kick typegen: plugin pipeline failed (${msg}) — continuing`)\n }\n }\n written.push(...(await writeTypegenArtifacts(outDir, pluginResults, silent)))\n }\n\n const tokenWarnings = validateTokenConventions(scan.tokens)\n const result = buildGenerateResult(scan, assets.count, written)\n const elapsed = Date.now() - start\n\n if (!silent) {\n const where = outDir.replace(cwd + '/', '')\n const collisionNote =\n result.resolvedCollisions > 0 ? `, ${result.resolvedCollisions} collisions namespaced` : ''\n const envNote = result.envWritten ? ', env typed' : ''\n const pluginNote = result.pluginEntries > 0 ? `, ${result.pluginEntries} plugins/adapters` : ''\n const augNote =\n result.augmentationEntries > 0 ? `, ${result.augmentationEntries} augmentations` : ''\n const assetNote = result.assetEntries > 0 ? `, ${result.assetEntries} assets` : ''\n console.log(\n ` kick typegen → ${result.serviceTokens} services, ${result.routeEntries} routes, ${result.moduleTokens} modules${pluginNote}${augNote}${assetNote}${envNote}${collisionNote} → ${where} (${elapsed}ms)`,\n )\n if (tokenWarnings.length > 0) {\n console.warn(\n ` kick typegen: ${tokenWarnings.length} token(s) don't match the §22.2 convention:`,\n )\n for (const warning of tokenWarnings) {\n const variableNote = warning.variable ? ` [${warning.variable}]` : ''\n console.warn(\n ` '${warning.token}' (${warning.filePath})${variableNote} — ${warning.reason}`,\n )\n if (warning.suggestion) {\n console.warn(` → suggestion: ${warning.suggestion}`)\n }\n }\n }\n if (scan.orphanedClasses.length > 0) {\n // forinda/kick-js#235 §4 — decorated classes sitting inside a\n // module directory whose globs don't pick them up. At runtime\n // the decorator never fires; downstream code paths get\n // confusing `MissingContributorError` or silent misroutes.\n console.warn(\n ` kick typegen: ${scan.orphanedClasses.length} decorated class(es) not matched by any module's import.meta.glob():`,\n )\n for (const orphan of scan.orphanedClasses) {\n console.warn(` @${orphan.decorator} ${orphan.className} (${orphan.relativePath})`)\n console.warn(` → not picked up by any glob in ${orphan.moduleFilePath}`)\n }\n }\n }\n\n return { scan, result, tokenWarnings }\n}\n\n/** Derive the logging/test `GenerateResult` from a scan — no files written here. */\nfunction buildGenerateResult(\n scan: ScanResult,\n assetCount: number,\n written: string[],\n): GenerateResult {\n const colliding = new Set(scan.collisions.map((c) => c.className))\n const registryClasses = scan.classes.filter((c) => REGISTRY_DECORATORS.has(c.decorator))\n const serviceNames = buildServiceTokens(scan.classes, scan.tokens, scan.injects, colliding)\n return {\n registryEntries: registryClasses.length,\n serviceTokens: new Set(serviceNames).size,\n moduleTokens: buildModuleTokens(scan.classes).length,\n routeEntries: scan.routes.length,\n pluginEntries: new Set(scan.pluginsAndAdapters.map((p) => p.name)).size,\n augmentationEntries: new Set(scan.augmentations.map((a) => a.name)).size,\n assetEntries: assetCount,\n envWritten: scan.env !== null,\n written,\n resolvedCollisions: scan.collisions.length,\n }\n}\n\n/**\n * Post-plugin-pass finalisation: write the `.kickjs/.gitignore` guard\n * and sweep stale legacy files. Shared by `runTypegen` (single-shot\n * mode) and the split-mode callers (`kick typegen` / `kick dev` /\n * watch) so the artifact-writing + sweep stay identical across both.\n *\n * No barrel `index.d.ts` is emitted: the scaffolded tsconfig pulls\n * `.kickjs/types/**` in via `include` globs, so every `declare module`\n * / `declare global` augmentation in the per-plugin files applies by\n * inclusion. The old barrel + its `ServiceToken`/`ModuleToken`\n * re-exports were redundant; they're swept as legacy orphans.\n *\n * Returns the list of files considered \"written\" this pass (the plugin\n * outputs) for the caller's bookkeeping.\n */\nexport async function writeTypegenArtifacts(\n outDir: string,\n pluginResults: readonly TypegenPluginResult[],\n silent: boolean,\n): Promise<string[]> {\n await mkdir(outDir, { recursive: true })\n // `.kickjs/.gitignore` keeps the generated tree out of git even if the\n // project's root .gitignore predates the `.kickjs/` convention.\n await writeFile(\n join(dirname(outDir), '.gitignore'),\n '# Auto-generated by kick typegen\\n*\\n',\n 'utf-8',\n )\n const written = pluginResults.filter((r) => r.outFile).map((r) => r.outFile as string)\n await sweepStaleTypegen(outDir, written, pluginResults, silent)\n return written\n}\n\n/**\n * Watch mode for `kick typegen --watch`.\n *\n * Uses Node's built-in `fs.watch` (recursive, available on Linux 22+ and\n * macOS 19+). Falls back gracefully if recursive watch is not supported.\n *\n * Debounces re-runs by 100ms so a bulk file change (e.g. `kick g module`\n * creating 5 files at once) emits one regen, not five.\n *\n * In watch mode collisions are reported but never thrown — the watcher\n * keeps running so the user can fix the rename and the next scan\n * recovers automatically.\n *\n * Returns a `stop()` function that closes the watcher.\n */\nexport async function watchTypegen(opts: RunTypegenOptions = {}): Promise<() => void> {\n const resolved = resolveOptions(opts)\n const { srcDir, silent, cwd } = resolved\n // Watch mode always tolerates collisions — otherwise an in-progress\n // rename would crash the dev loop. The error is still printed.\n // `runPlugins: false` keeps `runTypegen` from double-running the\n // plugin pipeline; the watcher invokes `runPlugins()` explicitly\n // after each `runLegacy()` so both phases land before the sweep.\n // `resolved` comes from resolveOptions, which doesn't carry noCache —\n // thread it through explicitly so `kick typegen --watch --no-cache`\n // disables the scan cache on every rebuild too.\n const runOpts: RunTypegenOptions = {\n ...resolved,\n allowDuplicates: true,\n runPlugins: false,\n noCache: opts.noCache,\n }\n\n // Polling is the right strategy for Docker bind mounts, WSL crosses,\n // NFS, and some kernel/filesystem combos where `fs.watch` returns\n // without errors but events silently drop. Adopters opt in via\n // `KICKJS_WATCH_POLLING=1`; default stays event-based (lower CPU).\n const forcePolling =\n process.env.KICKJS_WATCH_POLLING === '1' || process.env.KICKJS_WATCH_POLLING === 'true'\n\n // Lazy-import the plugin pipeline + config loader to avoid an eager\n // module-eval cycle (this file is reachable from plugin/builtins via\n // commands/typegen → ../typegen).\n const [{ runAllPluginTypegens }, { loadKickConfig }] = await Promise.all([\n import('./run-plugins'),\n import('../config'),\n ])\n const pluginConfig = await loadKickConfig(cwd)\n // `runLegacy` now just runs the scan + collision gate (collisions are\n // tolerated in watch mode via allowDuplicates) and refreshes the\n // logging counts; all file emission happens in `runPlugins`.\n const runLegacy = async () => {\n try {\n await runTypegen({ ...runOpts })\n } catch (err) {\n if (silent) return\n if (err instanceof TokenCollisionError) {\n console.error('\\n' + err.message + '\\n')\n } else {\n const msg = err instanceof Error ? err.message : String(err)\n console.error(` kick typegen failed: ${msg}`)\n }\n }\n }\n const runPlugins = async () => {\n try {\n const pluginResults = await runAllPluginTypegens({\n cwd,\n config: pluginConfig,\n silent: true,\n })\n await writeTypegenArtifacts(resolved.outDir, pluginResults, true)\n } catch {\n /* swallow — watcher must never die */\n }\n }\n\n // Initial run — scan/gate pass first, then plugin typegens + artifacts.\n await runLegacy()\n await runPlugins()\n\n const { watch } = await import('node:fs')\n\n let timer: ReturnType<typeof setTimeout> | null = null\n const trigger = (filename: string | null) => {\n // Only react to TypeScript source changes; ignore everything else\n if (!filename) return\n if (!/\\.(ts|tsx|mts|cts)$/.test(filename)) return\n if (filename.includes('.kickjs')) return\n if (filename.endsWith('.d.ts')) return\n\n if (timer) clearTimeout(timer)\n timer = setTimeout(() => {\n void runLegacy().then(runPlugins)\n }, 100)\n }\n\n // Forced-polling path — skip fs.watch entirely and just re-scan\n // periodically. The 2s interval matches the existing fallback so\n // adopters who flip the env var don't see a dramatic CPU jump.\n if (forcePolling) {\n if (!silent) {\n console.log(' kick typegen: polling mode (KICKJS_WATCH_POLLING)')\n }\n // Must drive BOTH phases — the plugin pass owns every\n // `.kickjs/types/kick__*` file now, so scan/gate alone (runLegacy)\n // would never refresh types on the polling path. Both closures\n // swallow their own errors, so the interval loop never dies.\n const interval = setInterval(() => {\n void runLegacy().then(runPlugins)\n }, 2000)\n return () => clearInterval(interval)\n }\n\n let watcher: ReturnType<typeof watch>\n try {\n watcher = watch(srcDir, { recursive: true }, (_event, filename) => {\n trigger(filename)\n })\n } catch (err: any) {\n if (!silent) {\n console.warn(\n ` kick typegen: watch mode unavailable (${err?.message ?? err}). Falling back to polling.`,\n )\n }\n // Polling fallback — re-run both phases every 2s (see forcePolling\n // note above: the plugin pass is the sole emitter, so scan/gate\n // alone would never refresh types).\n const interval = setInterval(() => {\n void runLegacy().then(runPlugins)\n }, 2000)\n return () => clearInterval(interval)\n }\n\n return () => {\n if (timer) clearTimeout(timer)\n watcher.close()\n }\n}\n\n/**\n * Remove orphaned typegen output. The legacy generator emitted\n * `assets.d.ts`, `env.ts`, and `routes.ts` directly; once those carved\n * into the `kick/assets`, `kick/env`, and `kick/routes` plugins, the\n * legacy file names became stale on disk for any project that had run\n * an older CLI. Without a sweep, both copies coexist and adopters get\n * duplicated `KickAssets` / `KickEnv` / `KickRoutes` augmentations.\n *\n * Strategy: enumerate the top level of `outDir`, keep the union of\n * generator-written files + plugin-written files, unlink anything\n * else. Subdirectories are left alone — typegen never creates them.\n * Errors are swallowed (silent → no log) so a transient ENOENT or\n * permission glitch never aborts the wider command.\n */\nexport async function sweepStaleTypegen(\n outDir: string,\n generatorWritten: readonly string[],\n pluginResults: readonly TypegenPluginResult[],\n silent: boolean,\n): Promise<string[]> {\n const expected = new Set<string>()\n for (const file of generatorWritten) expected.add(basename(file))\n for (const r of pluginResults) {\n if (r.outFile) expected.add(basename(r.outFile))\n }\n let entries: string[]\n try {\n entries = await readdir(outDir)\n } catch {\n return []\n }\n const removed: string[] = []\n for (const name of entries) {\n // Allowlist, NOT denylist. The earlier \"delete anything not in the\n // expected set\" form was a footgun: if the plugin pass aborted\n // (e.g. one plugin threw and the runner bubbled it up, so\n // `pluginResults` came back empty), the expected set lost\n // `kick__routes.ts` / `kick__assets.d.ts` / `kick__env.ts` and the\n // sweep deleted those good files — wiping controller route types\n // project-wide. We now only remove the specific pre-carve filenames\n // the legacy generator used to emit, and only when the current pass\n // didn't (re)write them. Anything else is left untouched.\n if (!LEGACY_ORPHAN_FILES.has(name)) continue\n if (expected.has(name)) continue\n const abs = resolve(outDir, name)\n try {\n const s = await stat(abs)\n if (!s.isFile()) continue\n await unlink(abs)\n removed.push(name)\n } catch {\n // Best-effort; don't crash the typegen pass on a stat/unlink miss.\n }\n }\n if (removed.length > 0 && !silent) {\n console.log(` kick typegen: swept ${removed.length} stale file(s): ${removed.join(', ')}`)\n }\n return removed\n}\n\n/**\n * Filenames the legacy monolithic generator (`generator.ts`, now\n * removed) emitted directly, before every augmentation became its own\n * typegen plugin. A project that upgrades across this change has these\n * as orphans on disk; the sweep removes them so the augmentations aren't\n * declared twice and the dropped `index.d.ts` barrel doesn't linger.\n *\n * Split into two waves only for documentation:\n * - `assets.d.ts` / `env.ts` / `routes.ts` — the M2.B-T8 carve.\n * - `registry.d.ts` / `services.d.ts` / `modules.d.ts` / `plugins.d.ts`\n * / `augmentations.d.ts` / `index.d.ts` — this plugin-only refactor.\n *\n * None collide with any current plugin output (all `kick__*`), so the\n * sweep can never touch live output.\n */\nconst LEGACY_ORPHAN_FILES: ReadonlySet<string> = new Set([\n 'assets.d.ts',\n 'env.ts',\n 'routes.ts',\n 'registry.d.ts',\n 'services.d.ts',\n 'modules.d.ts',\n 'plugins.d.ts',\n 'augmentations.d.ts',\n 'index.d.ts',\n])\n"],"mappings":"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"}
|