{"version":3,"sources":["../../src/bin/generate-api-routes.ts","../../src/middlewares/openapi/services.ts","../../src/middlewares/openapi/utils.ts"],"sourcesContent":["#!/usr/bin/env node\nimport fs from 'fs';\nimport path from 'path';\nimport { parseApiRoutes } from '../middlewares/openapi/services';\n\nconst args = process.argv.slice(2);\n\nfunction getArg(name: string, defaultValue: string): string {\n  const index = args.indexOf(name);\n  if (index !== -1 && args[index + 1]) {\n    return args[index + 1];\n  }\n  return defaultValue;\n}\n\nconst serverDir = path.resolve(process.cwd(), getArg('--server-dir', './server'));\nconst outDir = path.resolve(process.cwd(), getArg('--out-dir', './dist'));\nconst filename = getArg('--filename', 'api-routes.json');\n\ntry {\n  const routes = parseApiRoutes(serverDir);\n  if (!fs.existsSync(outDir)) {\n    fs.mkdirSync(outDir, { recursive: true });\n  }\n  const outPath = path.resolve(outDir, filename);\n  fs.writeFileSync(outPath, JSON.stringify(routes, null, 2));\n  console.log(`[api-routes] Generated ${outPath} (${routes.length} routes)`);\n} catch (error) {\n  console.warn('[api-routes] Failed to generate api-routes.json, writing empty fallback:', error);\n  if (!fs.existsSync(outDir)) {\n    fs.mkdirSync(outDir, { recursive: true });\n  }\n  fs.writeFileSync(path.resolve(outDir, filename), JSON.stringify([], null, 2));\n}\n","import { promises as fs } from 'node:fs';\nimport * as fsSync from 'node:fs';\nimport path from 'node:path';\nimport ts from 'typescript';\nimport type { SourceInfo, EnhanceOptions, EnhanceResult } from './types';\nimport { findControllerFiles, buildSourceMap, enhanceOpenApiPaths } from './utils';\n\n/**\n * Enhances OpenAPI JSON with source file location metadata\n * Can be called programmatically or run as a script\n */\nexport async function enhanceOpenApiWithSourceInfo(options: EnhanceOptions = {}): Promise<EnhanceResult> {\n  const startTime = Date.now();\n\n  const openapiPath = options.openapiPath || path.resolve(__dirname, '../client/src/api/gen/openapi.json');\n  const serverDir = options.serverDir || path.resolve(__dirname, '../server');\n  const writeFile = options.writeFile !== false;\n\n  let openapi: any;\n  if (options.openapiData) {\n    // Use provided data (for in-memory enhancement)\n    openapi = JSON.parse(JSON.stringify(options.openapiData)); // Deep clone\n  } else {\n    // Read from file\n    const openapiContent = await fs.readFile(openapiPath, 'utf-8');\n    openapi = JSON.parse(openapiContent);\n  }\n\n  const controllerFiles = await findControllerFiles(serverDir);\n  const sourceMap = await buildSourceMap(controllerFiles, processControllerFile);\n  const enhanced = enhanceOpenApiPaths(openapi, sourceMap);\n\n  if (writeFile) {\n    await fs.writeFile(openapiPath, JSON.stringify(openapi, null, 2) + '\\n', 'utf-8');\n  }\n\n  const duration = Date.now() - startTime;\n\n  return {\n    openapi,\n    stats: {\n      duration,\n      controllersFound: controllerFiles.length,\n      endpointsExtracted: sourceMap.size,\n      endpointsEnhanced: enhanced,\n    },\n  };\n}\n\n/**\n * Process a single controller file\n */\nasync function processControllerFile(filePath: string): Promise<Map<string, SourceInfo>> {\n  const relativePath = path.relative(process.cwd(), filePath);\n\n  // Parse file\n  const content = await fs.readFile(filePath, 'utf-8');\n  const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);\n\n  return extractControllerMetadata(sourceFile, relativePath);\n}\n\n/**\n * Extract controller metadata from TypeScript source file\n */\nfunction extractControllerMetadata(sourceFile: ts.SourceFile, filePath: string): Map<string, SourceInfo> {\n  const metadata = new Map<string, SourceInfo>();\n  let controllerPath = '';\n  let className = '';\n\n  // Helper function to get decorators from both old and new TypeScript APIs\n  function getDecorators(node: ts.Node): readonly ts.Decorator[] {\n    // TypeScript 5.x: decorators are in modifiers array\n    if ('modifiers' in node && Array.isArray(node.modifiers)) {\n      return (node.modifiers as ts.ModifierLike[]).filter(\n        (mod): mod is ts.Decorator => mod.kind === ts.SyntaxKind.Decorator,\n      );\n    }\n    // TypeScript 4.x: decorators are in decorators array\n    if ('decorators' in node && Array.isArray(node.decorators)) {\n      return node.decorators as readonly ts.Decorator[];\n    }\n    return [];\n  }\n\n  function visit(node: ts.Node): void {\n    // Extract @Controller decorator and its path\n    if (ts.isClassDeclaration(node)) {\n      const decorators = getDecorators(node);\n\n      // Extract class name\n      if (node.name) {\n        className = node.name.getText(sourceFile);\n      }\n\n      for (const decorator of decorators) {\n        if (ts.isCallExpression(decorator.expression)) {\n          const expression = decorator.expression;\n          const decoratorName = expression.expression.getText(sourceFile);\n\n          if (decoratorName === 'Controller') {\n            if (expression.arguments.length > 0) {\n              const arg = expression.arguments[0];\n              if (ts.isStringLiteral(arg)) {\n                controllerPath = arg.text;\n              }\n            }\n          }\n        }\n      }\n    }\n\n    // Extract methods with HTTP decorators\n    if (ts.isMethodDeclaration(node) && node.name) {\n      const methodName = node.name.getText(sourceFile);\n      let httpMethod = '';\n      let routePath = '';\n      const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));\n\n      const decorators = getDecorators(node);\n\n      for (const decorator of decorators) {\n        if (ts.isCallExpression(decorator.expression)) {\n          const decoratorName = decorator.expression.expression.getText(sourceFile);\n          if (['Get', 'Post', 'Put', 'Delete', 'Patch', 'Options', 'Head', 'All'].includes(decoratorName)) {\n            httpMethod = decoratorName.toLowerCase();\n            if (decorator.expression.arguments.length > 0) {\n              const arg = decorator.expression.arguments[0];\n              if (ts.isStringLiteral(arg)) {\n                routePath = arg.text;\n              }\n            }\n          }\n        }\n      }\n\n      if (httpMethod && methodName && className) {\n        const operationId = `${className}_${methodName}`;\n        metadata.set(operationId, {\n          file: filePath,\n          line: line + 1,\n          method: httpMethod,\n          controllerPath,\n          routePath,\n        });\n      }\n    }\n\n    ts.forEachChild(node, visit);\n  }\n\n  visit(sourceFile);\n  return metadata;\n}\n\n// --- API Route Parsing (synchronous, for build-time injection) ---\n\nconst HTTP_DECORATOR_NAMES = ['Get', 'Post', 'Put', 'Delete', 'Patch', 'Options', 'Head', 'All'];\n\nfunction findControllerFilesSync(dir: string): string[] {\n  const files: string[] = [];\n  function scan(currentDir: string): void {\n    let entries: fsSync.Dirent[];\n    try {\n      entries = fsSync.readdirSync(currentDir, { withFileTypes: true });\n    } catch {\n      return;\n    }\n    for (const entry of entries) {\n      const fullPath = path.join(currentDir, entry.name);\n      if (entry.isDirectory()) {\n        scan(fullPath);\n      } else if (entry.isFile() && entry.name.endsWith('.controller.ts')) {\n        files.push(fullPath);\n      }\n    }\n  }\n  scan(dir);\n  return files;\n}\n\nfunction normalizeApiPath(controllerPath: string, routePath: string): string {\n  const combined = routePath ? `${controllerPath}/${routePath}` : controllerPath;\n  let normalized = combined.replace(/\\/+/g, '/');\n  if (!normalized.startsWith('/')) normalized = `/${normalized}`;\n  if (normalized.length > 1 && normalized.endsWith('/')) normalized = normalized.slice(0, -1);\n  return normalized;\n}\n\nfunction parseControllerRoutes(filePath: string): Array<{ method: string; path: string }> {\n  let content: string;\n  try {\n    content = fsSync.readFileSync(filePath, 'utf-8');\n  } catch {\n    return [];\n  }\n\n  const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);\n  const routes: Array<{ method: string; path: string }> = [];\n\n  function getDecoratorsCompat(node: ts.Node): readonly ts.Decorator[] {\n    if ('modifiers' in node && Array.isArray(node.modifiers)) {\n      return (node.modifiers as ts.ModifierLike[]).filter(\n        (mod): mod is ts.Decorator => mod.kind === ts.SyntaxKind.Decorator,\n      );\n    }\n    if ('decorators' in node && Array.isArray(node.decorators)) {\n      return node.decorators as readonly ts.Decorator[];\n    }\n    return [];\n  }\n\n  function getStringArg(expr: ts.CallExpression): string {\n    if (expr.arguments.length > 0 && ts.isStringLiteral(expr.arguments[0])) {\n      return expr.arguments[0].text;\n    }\n    return '';\n  }\n\n  function visit(node: ts.Node): void {\n    if (ts.isClassDeclaration(node)) {\n      let controllerPath = '';\n      for (const dec of getDecoratorsCompat(node)) {\n        if (ts.isCallExpression(dec.expression)) {\n          const name = dec.expression.expression.getText(sourceFile);\n          if (name === 'Controller') {\n            controllerPath = getStringArg(dec.expression);\n            break;\n          }\n        }\n      }\n\n      for (const member of node.members) {\n        if (!ts.isMethodDeclaration(member)) continue;\n        for (const dec of getDecoratorsCompat(member)) {\n          if (!ts.isCallExpression(dec.expression)) continue;\n          const decoratorName = dec.expression.expression.getText(sourceFile);\n          if (!HTTP_DECORATOR_NAMES.includes(decoratorName)) continue;\n\n          const routePath = getStringArg(dec.expression);\n          const fullPath = normalizeApiPath(controllerPath, routePath);\n          const method = decoratorName === 'All' ? '*' : decoratorName.toUpperCase();\n          routes.push({ method, path: fullPath });\n        }\n      }\n    }\n    ts.forEachChild(node, visit);\n  }\n\n  visit(sourceFile);\n  return routes;\n}\n\n/**\n * Scan serverDir for NestJS controller files and extract all parameterized API routes.\n * Synchronous — safe to call at preset config time (before bundling).\n */\nexport function parseApiRoutes(serverDir: string): Array<{ method: string; path: string }> {\n  const resolvedDir = path.resolve(serverDir);\n  const controllerFiles = findControllerFilesSync(resolvedDir);\n  const routes: Array<{ method: string; path: string }> = [];\n  for (const filePath of controllerFiles) {\n    routes.push(...parseControllerRoutes(filePath));\n  }\n  return routes;\n}\n","import path from 'node:path';\nimport { promises as fs } from 'node:fs';\nimport type { SourceInfo } from './types';\n\n/**\n * Find all controller files in a directory\n */\nexport async function findControllerFiles(dir: string): Promise<string[]> {\n  const files: string[] = [];\n\n  async function scan(currentDir: string): Promise<void> {\n    const entries = await fs.readdir(currentDir, { withFileTypes: true });\n\n    for (const entry of entries) {\n      const fullPath = path.join(currentDir, entry.name);\n\n      if (entry.isDirectory()) {\n        await scan(fullPath);\n      } else if (entry.isFile() && entry.name.endsWith('.controller.ts')) {\n        files.push(fullPath);\n      }\n    }\n  }\n\n  await scan(dir);\n  return files;\n}\n\n/**\n * Build source map from controller files\n */\nexport async function buildSourceMap(\n  controllerFiles: string[],\n  processFile: (filePath: string) => Promise<Map<string, SourceInfo>>,\n): Promise<Map<string, SourceInfo>> {\n  const sourceMap = new Map<string, SourceInfo>();\n\n  // Process files in parallel with a concurrency limit\n  const concurrency = 10;\n  const results: Map<string, SourceInfo>[] = [];\n\n  for (let i = 0; i < controllerFiles.length; i += concurrency) {\n    const batch = controllerFiles.slice(i, i + concurrency);\n    const batchResults = await Promise.all(batch.map((filePath) => processFile(filePath)));\n    results.push(...batchResults);\n  }\n\n  // Merge results\n  for (const metadata of results) {\n    for (const [operationId, info] of metadata.entries()) {\n      sourceMap.set(operationId, info);\n    }\n  }\n\n  return sourceMap;\n}\n\n/**\n * Try to match operationId with different formats\n * Supports:\n * - Direct match: ClassName_methodName\n * - Camel case: classNameMethodName\n * - Method only: methodName\n */\nfunction findSourceInfo(operationId: string, sourceMap: Map<string, SourceInfo>): SourceInfo | undefined {\n  // Try direct match first\n  const directMatch = sourceMap.get(operationId);\n  if (directMatch) {\n    return directMatch;\n  }\n\n  // Try matching with different formats\n  for (const [key, value] of sourceMap.entries()) {\n    // key format: ClassName_methodName\n    const [className, methodName] = key.split('_');\n    if (!className || !methodName) continue;\n\n    // Try camelCase format: classNameMethodName\n    const camelCaseId = className.charAt(0).toLowerCase() + className.slice(1) + methodName.charAt(0).toUpperCase() + methodName.slice(1);\n    if (operationId === camelCaseId) {\n      return value;\n    }\n\n    // Try method name only\n    if (operationId === methodName) {\n      return value;\n    }\n  }\n\n  return undefined;\n}\n\n/**\n * Enhance OpenAPI paths with source information\n */\nexport function enhanceOpenApiPaths(openapi: any, sourceMap: Map<string, SourceInfo>): number {\n  let enhancedCount = 0;\n\n  if (!openapi.paths) {\n    return enhancedCount;\n  }\n\n  for (const pathItem of Object.values(openapi.paths)) {\n    if (!pathItem || typeof pathItem !== 'object') continue;\n\n    for (const operation of Object.values(pathItem)) {\n      if (operation && typeof operation === 'object' && 'operationId' in operation) {\n        const sourceInfo = findSourceInfo(operation.operationId as string, sourceMap);\n        if (sourceInfo) {\n          operation['x-source'] = {\n            file: sourceInfo.file,\n            line: sourceInfo.line,\n          };\n          enhancedCount++;\n        }\n      }\n    }\n  }\n\n  return enhancedCount;\n}\n\n/**\n * Transform OpenAPI paths by removing basePath prefix\n */\nexport function transformOpenapiPaths(openapi: any, basePath: string): any {\n  if (basePath === '/' || !openapi.paths) {\n    return openapi;\n  }\n\n  const newPaths: any = {};\n  Object.keys(openapi.paths).forEach((key) => {\n    const staticApiKey = key.startsWith(basePath) ? key.slice(basePath.length) : key;\n    newPaths[staticApiKey] = openapi.paths[key];\n  });\n\n  return {\n    ...openapi,\n    paths: newPaths,\n    basePath,\n  };\n}\n"],"mappings":"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","names":["import_node_fs","import_node_path","HTTP_DECORATOR_NAMES","findControllerFilesSync","dir","files","scan","currentDir","entries","readdirSync","withFileTypes","entry","fullPath","path","join","name","isDirectory","isFile","endsWith","push","normalizeApiPath","controllerPath","routePath","combined","normalized","replace","startsWith","length","slice","parseControllerRoutes","filePath","content","readFileSync","sourceFile","ts","createSourceFile","ScriptTarget","Latest","routes","getDecoratorsCompat","node","Array","isArray","modifiers","filter","mod","kind","SyntaxKind","Decorator","decorators","getStringArg","expr","arguments","isStringLiteral","text","visit","isClassDeclaration","dec","isCallExpression","expression","getText","member","members","isMethodDeclaration","decoratorName","includes","method","toUpperCase","forEachChild","parseApiRoutes","serverDir","resolvedDir","resolve","controllerFiles","args","process","argv","slice","getArg","name","defaultValue","index","indexOf","serverDir","path","resolve","cwd","outDir","filename","routes","parseApiRoutes","fs","existsSync","mkdirSync","recursive","outPath","writeFileSync","JSON","stringify","console","log","length","error","warn"]}