openapi-ai-generator 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +22 -16
- package/dist/cli.js.map +1 -1
- package/dist/index.d.mts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +21 -18
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +21 -18
- package/dist/index.mjs.map +1 -1
- package/dist/plugin.js +21 -16
- package/dist/plugin.js.map +1 -1
- package/dist/plugin.mjs +21 -16
- package/dist/plugin.mjs.map +1 -1
- package/package.json +3 -1
package/dist/cli.js
CHANGED
|
@@ -85,10 +85,11 @@ function createModel(provider) {
|
|
|
85
85
|
}
|
|
86
86
|
function createAzureModel() {
|
|
87
87
|
const endpoint = requireEnv("AZURE_OPENAI_ENDPOINT");
|
|
88
|
+
const resourceName = endpoint?.match(/https?:\/\/([^.]+)/)?.[1];
|
|
88
89
|
const apiKey = requireEnv("AZURE_OPENAI_API_KEY");
|
|
89
90
|
const deployment = requireEnv("AZURE_OPENAI_DEPLOYMENT");
|
|
90
91
|
const { createAzure } = require("@ai-sdk/azure");
|
|
91
|
-
const azure = createAzure({
|
|
92
|
+
const azure = createAzure({ resourceName, apiKey });
|
|
92
93
|
return azure(deployment);
|
|
93
94
|
}
|
|
94
95
|
function createOpenAIModel() {
|
|
@@ -275,12 +276,22 @@ var defaults = {
|
|
|
275
276
|
cache: true,
|
|
276
277
|
cacheDir: ".openapi-cache",
|
|
277
278
|
include: ["src/app/api/**/route.ts"],
|
|
278
|
-
exclude: []
|
|
279
|
+
exclude: [],
|
|
280
|
+
envFile: [".env", ".env.local"]
|
|
279
281
|
};
|
|
280
282
|
function resolveConfig(config) {
|
|
283
|
+
let envFile;
|
|
284
|
+
if (config.envFile === false) {
|
|
285
|
+
envFile = false;
|
|
286
|
+
} else if (typeof config.envFile === "string") {
|
|
287
|
+
envFile = [config.envFile];
|
|
288
|
+
} else {
|
|
289
|
+
envFile = config.envFile ?? defaults.envFile;
|
|
290
|
+
}
|
|
281
291
|
return {
|
|
282
292
|
...defaults,
|
|
283
293
|
...config,
|
|
294
|
+
envFile,
|
|
284
295
|
output: {
|
|
285
296
|
scalarDocs: false,
|
|
286
297
|
scalarPath: "src/app/api/docs/route.ts",
|
|
@@ -318,20 +329,8 @@ async function importConfig(filePath) {
|
|
|
318
329
|
return import(url);
|
|
319
330
|
}
|
|
320
331
|
async function importTypeScriptConfig(filePath) {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const url = (0, import_url.pathToFileURL)(filePath).href;
|
|
324
|
-
return await import(url);
|
|
325
|
-
} catch {
|
|
326
|
-
try {
|
|
327
|
-
require("ts-node/register");
|
|
328
|
-
return require(filePath);
|
|
329
|
-
} catch {
|
|
330
|
-
throw new Error(
|
|
331
|
-
`Cannot load TypeScript config file: ${filePath}. Install tsx or ts-node, or use a .js config file.`
|
|
332
|
-
);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
332
|
+
require("tsx/cjs");
|
|
333
|
+
return require(filePath);
|
|
335
334
|
}
|
|
336
335
|
|
|
337
336
|
// src/generator.ts
|
|
@@ -490,11 +489,18 @@ function extractJsdoc(sourceCode) {
|
|
|
490
489
|
}
|
|
491
490
|
|
|
492
491
|
// src/index.ts
|
|
492
|
+
var import_node_path = require("path");
|
|
493
493
|
async function generate(options = {}) {
|
|
494
494
|
const cwd = options.cwd ?? process.cwd();
|
|
495
495
|
const config = await loadConfig(options.config);
|
|
496
496
|
if (options.provider) config.provider = options.provider;
|
|
497
497
|
if (options.cache === false) config.cache = false;
|
|
498
|
+
if (config.envFile !== false) {
|
|
499
|
+
const { config: dotenvConfig } = await import("dotenv");
|
|
500
|
+
for (const file of config.envFile) {
|
|
501
|
+
dotenvConfig({ path: (0, import_node_path.resolve)(cwd, file), override: false });
|
|
502
|
+
}
|
|
503
|
+
}
|
|
498
504
|
console.log(`[openapi-ai-generator] Scanning routes...`);
|
|
499
505
|
const routes = await scanRoutes(config.include, config.exclude, cwd);
|
|
500
506
|
console.log(`[openapi-ai-generator] Found ${routes.length} route(s)`);
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/analyzer.ts","../src/cache.ts","../src/providers/index.ts","../src/config.ts","../src/generator.ts","../src/scanner.ts","../src/index.ts"],"sourcesContent":["import { Command } from 'commander';\n\nimport type { Provider } from './config.js';\n\nimport { generate } from './index.js';\n\nconst program = new Command();\n\nprogram\n\t.name('openapi-ai-generator')\n\t.description('Generate OpenAPI 3.1 specs from Next.js API routes using AI')\n\t.version('0.1.0');\n\nprogram\n\t.command('generate')\n\t.description('Scan Next.js API routes and generate an OpenAPI spec')\n\t.option('-c, --config <path>', 'Path to config file (default: openapi-gen.config.ts)')\n\t.option('-p, --provider <provider>', 'Override provider (azure | openai | anthropic)')\n\t.option('--no-cache', 'Disable caching (always re-analyze all routes)')\n\t.action(async (opts: { config?: string; provider?: string; cache: boolean }) => {\n\t\ttry {\n\t\t\tconst result = await generate({\n\t\t\t\tconfig: opts.config,\n\t\t\t\tprovider: opts.provider as Provider | undefined,\n\t\t\t\tcache: opts.cache,\n\t\t\t});\n\n\t\t\tconsole.log(`\\n✓ OpenAPI spec generated successfully`);\n\t\t\tconsole.log(` Routes analyzed: ${result.routesAnalyzed}`);\n\t\t\tconsole.log(` From cache: ${result.routesFromCache}`);\n\t\t\tconsole.log(\n\t\t\t\t` LLM calls made: ${result.routesAnalyzed - result.routesFromCache - (result.routesSkippedLLM - result.routesFromCache)}`,\n\t\t\t);\n\t\t\tconsole.log(` Spec written to: ${result.specPath}`);\n\t\t} catch (err) {\n\t\t\tconsole.error('[openapi-ai-generator] Error:', err instanceof Error ? err.message : err);\n\t\t\tprocess.exit(1);\n\t\t}\n\t});\n\nprogram.parse();\n","import type { LanguageModel } from 'ai';\nimport { generateText } from 'ai';\n\nimport type { JSDocMode, Provider } from './config.js';\nimport type { RouteInfo } from './scanner.js';\n\nimport { computeHash, RouteCache } from './cache.js';\nimport { createModel, getModelId } from './providers/index.js';\n\nexport interface AnalyzeOptions {\n\tprovider: Provider;\n\tjsdocMode: JSDocMode;\n\tcache: boolean;\n\tcacheDir: string;\n}\n\nexport interface AnalyzedRoute {\n\turlPath: string;\n\tpathItem: Record<string, unknown>;\n\tfromCache: boolean;\n\tskippedLLM: boolean;\n}\n\nexport async function analyzeRoutes(\n\troutes: RouteInfo[],\n\toptions: AnalyzeOptions,\n): Promise<AnalyzedRoute[]> {\n\tconst modelId = getModelId(options.provider);\n\tconst cache = options.cache ? new RouteCache(options.cacheDir) : null;\n\n\t// Lazy-init the model only when we actually need it\n\tlet model: LanguageModel | null = null;\n\tconst getModel = (): LanguageModel => {\n\t\tif (!model) model = createModel(options.provider);\n\t\treturn model;\n\t};\n\n\tconst results: AnalyzedRoute[] = [];\n\n\tfor (const route of routes) {\n\t\tconst result = await analyzeRoute(route, options, modelId, cache, getModel);\n\t\tresults.push(result);\n\t}\n\n\treturn results;\n}\n\nasync function analyzeRoute(\n\troute: RouteInfo,\n\toptions: AnalyzeOptions,\n\tmodelId: string,\n\tcache: RouteCache | null,\n\tgetModel: () => LanguageModel,\n): Promise<AnalyzedRoute> {\n\t// If @openapi-exact is present or jsdocMode is 'exact', skip LLM\n\tif (route.hasExactJsdoc && route.exactPathItem) {\n\t\treturn {\n\t\t\turlPath: route.urlPath,\n\t\t\tpathItem: route.exactPathItem,\n\t\t\tfromCache: false,\n\t\t\tskippedLLM: true,\n\t\t};\n\t}\n\n\tif (options.jsdocMode === 'exact') {\n\t\t// In exact mode, if there's a @openapi tag, use it; otherwise still use LLM\n\t\tif (route.hasExactJsdoc && route.exactPathItem) {\n\t\t\treturn {\n\t\t\t\turlPath: route.urlPath,\n\t\t\t\tpathItem: route.exactPathItem,\n\t\t\t\tfromCache: false,\n\t\t\t\tskippedLLM: true,\n\t\t\t};\n\t\t}\n\t}\n\n\t// Compute cache hash\n\tconst hash = computeHash(route.sourceCode, options.provider, modelId);\n\n\t// Check cache\n\tif (cache) {\n\t\tconst cached = cache.get(hash);\n\t\tif (cached) {\n\t\t\treturn {\n\t\t\t\turlPath: route.urlPath,\n\t\t\t\tpathItem: cached,\n\t\t\t\tfromCache: true,\n\t\t\t\tskippedLLM: true,\n\t\t\t};\n\t\t}\n\t}\n\n\t// Call LLM\n\tlet pathItem: Record<string, unknown>;\n\ttry {\n\t\tpathItem = await callLLM(route, options.jsdocMode, getModel());\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tconst isContentFilter =\n\t\t\tmessage.includes('content filtering policy') ||\n\t\t\tmessage.includes('content_filter') ||\n\t\t\t(err as { status?: number })?.status === 400;\n\n\t\tif (isContentFilter) {\n\t\t\tconsole.warn(\n\t\t\t\t`Warning: Content filter blocked response for ${route.urlPath}. ` +\n\t\t\t\t\t`Skipping route. Use @openapi-exact JSDoc to provide the spec manually.`,\n\t\t\t);\n\t\t\treturn {\n\t\t\t\turlPath: route.urlPath,\n\t\t\t\tpathItem: {},\n\t\t\t\tfromCache: false,\n\t\t\t\tskippedLLM: false,\n\t\t\t};\n\t\t}\n\t\tthrow err;\n\t}\n\n\t// Store in cache\n\tif (cache) {\n\t\tcache.set(hash, pathItem);\n\t}\n\n\treturn {\n\t\turlPath: route.urlPath,\n\t\tpathItem,\n\t\tfromCache: false,\n\t\tskippedLLM: false,\n\t};\n}\n\nfunction buildPrompt(route: RouteInfo, jsdocMode: JSDocMode): string {\n\tconst jsDocSection =\n\t\troute.jsdocComments.length > 0\n\t\t\t? `JSDoc COMMENTS (use as ${jsdocMode === 'context' ? 'additional context' : 'primary source'}):\\n${route.jsdocComments.join('\\n\\n')}`\n\t\t\t: 'No JSDoc comments found.';\n\n\treturn `You are a technical documentation tool that reads existing source code and produces OpenAPI 3.1 documentation data. You do not write or execute code — you only read and describe it.\n\n## Task\n\nRead the Next.js API route source file below and produce a JSON object that documents its HTTP endpoints according to the OpenAPI 3.1 PathItem schema.\n\n## Route metadata\n\n- File: ${route.relativePath}\n- URL path: ${route.urlPath}\n\n## Source file contents\n\n\\`\\`\\`typescript\n${route.sourceCode}\n\\`\\`\\`\n\n${jsDocSection}\n\n## Instructions\n\nFor each exported function named GET, POST, PUT, PATCH, or DELETE, document:\n- operationId: a unique camelCase identifier\n- summary: a short one-line description\n- description: a fuller explanation of what the endpoint does\n- parameters: path params from URL segments like {id}, and query params from searchParams usage\n- requestBody: schema inferred from request.json() calls and TypeScript types (POST/PUT/PATCH only)\n- responses: per status code, inferred from NextResponse.json() calls and return type annotations\n- tags: inferred from the URL path segments\n- security: noted if the code checks for auth tokens, session cookies, or middleware guards\n\n## Output format\n\nReturn a single raw JSON object matching the OpenAPI 3.1 PathItem schema. No explanation, no markdown fences, no extra text — only the JSON object.`;\n}\n\nasync function callLLM(\n\troute: RouteInfo,\n\tjsdocMode: JSDocMode,\n\tmodel: LanguageModel,\n): Promise<Record<string, unknown>> {\n\tconst prompt = buildPrompt(route, jsdocMode);\n\n\tconst { text } = await generateText({\n\t\tmodel,\n\t\tprompt,\n\t\ttemperature: 0,\n\t});\n\n\treturn parsePathItem(text, route.urlPath);\n}\n\nfunction parsePathItem(text: string, urlPath: string): Record<string, unknown> {\n\t// Strip any accidental markdown code fences\n\tlet json = text.trim();\n\tif (json.startsWith('```')) {\n\t\tjson = json\n\t\t\t.replace(/^```(?:json)?\\s*/i, '')\n\t\t\t.replace(/\\s*```$/, '')\n\t\t\t.trim();\n\t}\n\n\ttry {\n\t\tconst parsed = JSON.parse(json);\n\t\tif (typeof parsed !== 'object' || Array.isArray(parsed) || parsed === null) {\n\t\t\tthrow new Error('Response is not a JSON object');\n\t\t}\n\t\treturn parsed;\n\t} catch (err) {\n\t\tconsole.warn(\n\t\t\t`Warning: Failed to parse LLM response for ${urlPath}. Using empty PathItem.`,\n\t\t\terr,\n\t\t);\n\t\treturn {};\n\t}\n}\n","import { createHash } from 'crypto';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';\nimport { join } from 'path';\n\nexport interface CacheEntry {\n\thash: string;\n\tpathItem: Record<string, unknown>;\n\tcachedAt: string;\n}\n\nexport function computeHash(content: string, provider: string, modelId: string): string {\n\treturn createHash('sha256').update(content).update(provider).update(modelId).digest('hex');\n}\n\nexport class RouteCache {\n\tprivate readonly cacheDir: string;\n\n\tconstructor(cacheDir: string) {\n\t\tthis.cacheDir = cacheDir;\n\t}\n\n\tprivate ensureDir(): void {\n\t\tif (!existsSync(this.cacheDir)) {\n\t\t\tmkdirSync(this.cacheDir, { recursive: true });\n\t\t}\n\t}\n\n\tget(hash: string): Record<string, unknown> | null {\n\t\tconst filePath = join(this.cacheDir, `${hash}.json`);\n\t\tif (!existsSync(filePath)) return null;\n\t\ttry {\n\t\t\tconst entry: CacheEntry = JSON.parse(readFileSync(filePath, 'utf8'));\n\t\t\treturn entry.pathItem;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tset(hash: string, pathItem: Record<string, unknown>): void {\n\t\tthis.ensureDir();\n\t\tconst entry: CacheEntry = {\n\t\t\thash,\n\t\t\tpathItem,\n\t\t\tcachedAt: new Date().toISOString(),\n\t\t};\n\t\tconst filePath = join(this.cacheDir, `${hash}.json`);\n\t\twriteFileSync(filePath, JSON.stringify(entry, null, 2), 'utf8');\n\t}\n}\n","import type { LanguageModel } from 'ai';\n\nimport type { Provider } from '../config.js';\n\nexport function createModel(provider: Provider): LanguageModel {\n\tswitch (provider) {\n\t\tcase 'azure':\n\t\t\treturn createAzureModel();\n\t\tcase 'openai':\n\t\t\treturn createOpenAIModel();\n\t\tcase 'anthropic':\n\t\t\treturn createAnthropicModel();\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = provider;\n\t\t\tthrow new Error(`Unknown provider: ${_exhaustive}`);\n\t\t}\n\t}\n}\n\nfunction createAzureModel(): LanguageModel {\n\tconst endpoint = requireEnv('AZURE_OPENAI_ENDPOINT');\n\tconst apiKey = requireEnv('AZURE_OPENAI_API_KEY');\n\tconst deployment = requireEnv('AZURE_OPENAI_DEPLOYMENT');\n\n\t// Dynamic import to avoid loading unused provider SDKs\n\tconst { createAzure } = require('@ai-sdk/azure');\n\tconst azure = createAzure({ endpoint, apiKey });\n\treturn azure(deployment);\n}\n\nfunction createOpenAIModel(): LanguageModel {\n\tconst apiKey = requireEnv('OPENAI_API_KEY');\n\tconst model = process.env.OPENAI_MODEL ?? 'gpt-4o';\n\n\tconst { createOpenAI } = require('@ai-sdk/openai');\n\tconst openai = createOpenAI({ apiKey });\n\treturn openai(model);\n}\n\nfunction createAnthropicModel(): LanguageModel {\n\tconst apiKey = requireEnv('ANTHROPIC_API_KEY');\n\tconst model = process.env.ANTHROPIC_MODEL ?? 'claude-sonnet-4-6';\n\n\tconst { createAnthropic } = require('@ai-sdk/anthropic');\n\tconst anthropic = createAnthropic({ apiKey });\n\treturn anthropic(model);\n}\n\nfunction requireEnv(name: string): string {\n\tconst val = process.env[name];\n\tif (!val) {\n\t\tthrow new Error(`Required environment variable ${name} is not set.`);\n\t}\n\treturn val;\n}\n\nexport function getModelId(provider: Provider): string {\n\tswitch (provider) {\n\t\tcase 'azure':\n\t\t\treturn process.env.AZURE_OPENAI_DEPLOYMENT ?? 'unknown';\n\t\tcase 'openai':\n\t\t\treturn process.env.OPENAI_MODEL ?? 'gpt-4o';\n\t\tcase 'anthropic':\n\t\t\treturn process.env.ANTHROPIC_MODEL ?? 'claude-sonnet-4-6';\n\t}\n}\n","import { existsSync } from 'fs';\nimport { resolve } from 'path';\nimport { pathToFileURL } from 'url';\n\nexport type Provider = 'azure' | 'openai' | 'anthropic';\nexport type JSDocMode = 'context' | 'exact';\n\nexport interface OpenAPIGenConfig {\n\tprovider: Provider;\n\toutput: {\n\t\tspecPath: string;\n\t\tscalarDocs?: boolean;\n\t\tscalarPath?: string;\n\t};\n\topenapi: {\n\t\ttitle: string;\n\t\tversion: string;\n\t\tdescription?: string;\n\t\tservers?: Array<{ url: string; description?: string }>;\n\t};\n\tjsdocMode?: JSDocMode;\n\tcache?: boolean;\n\tcacheDir?: string;\n\tinclude?: string[];\n\texclude?: string[];\n}\n\nexport interface ResolvedConfig extends Required<OpenAPIGenConfig> {\n\toutput: Required<OpenAPIGenConfig['output']>;\n\topenapi: Required<OpenAPIGenConfig['openapi']>;\n}\n\nconst defaults: Omit<ResolvedConfig, 'provider' | 'output' | 'openapi'> = {\n\tjsdocMode: 'context',\n\tcache: true,\n\tcacheDir: '.openapi-cache',\n\tinclude: ['src/app/api/**/route.ts'],\n\texclude: [],\n};\n\nexport function resolveConfig(config: OpenAPIGenConfig): ResolvedConfig {\n\treturn {\n\t\t...defaults,\n\t\t...config,\n\t\toutput: {\n\t\t\tscalarDocs: false,\n\t\t\tscalarPath: 'src/app/api/docs/route.ts',\n\t\t\t...config.output,\n\t\t},\n\t\topenapi: {\n\t\t\tdescription: '',\n\t\t\tservers: [],\n\t\t\t...config.openapi,\n\t\t},\n\t};\n}\n\nexport async function loadConfig(configPath?: string): Promise<ResolvedConfig> {\n\tconst searchPaths = configPath\n\t\t? [configPath]\n\t\t: [\n\t\t\t\t'openapi-gen.config.ts',\n\t\t\t\t'openapi-gen.config.js',\n\t\t\t\t'openapi-gen.config.mjs',\n\t\t\t\t'openapi-gen.config.cjs',\n\t\t\t];\n\n\tfor (const p of searchPaths) {\n\t\tconst abs = resolve(process.cwd(), p);\n\t\tif (existsSync(abs)) {\n\t\t\tconst mod = await importConfig(abs);\n\t\t\tconst config: OpenAPIGenConfig = mod.default ?? mod;\n\t\t\treturn resolveConfig(config);\n\t\t}\n\t}\n\n\tthrow new Error('No openapi-gen.config.ts found. Create one at your project root.');\n}\n\nasync function importConfig(\n\tfilePath: string,\n): Promise<{ default?: OpenAPIGenConfig } & OpenAPIGenConfig> {\n\t// For .ts files, try to use tsx/ts-node if available, else fall back to require\n\tif (filePath.endsWith('.ts')) {\n\t\treturn importTypeScriptConfig(filePath);\n\t}\n\tconst url = pathToFileURL(filePath).href;\n\treturn import(url);\n}\n\nasync function importTypeScriptConfig(\n\tfilePath: string,\n): Promise<{ default?: OpenAPIGenConfig } & OpenAPIGenConfig> {\n\t// Try dynamic import with tsx register if available\n\ttry {\n\t\t// Check if tsx is available\n\t\tawait import('module');\n\t\t// Use tsx/ts-node loader\n\t\tconst url = pathToFileURL(filePath).href;\n\t\treturn await import(url);\n\t} catch {\n\t\t// Fall back: try require with ts-node\n\t\ttry {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-require-imports\n\t\t\trequire('ts-node/register');\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-require-imports\n\t\t\treturn require(filePath);\n\t\t} catch {\n\t\t\tthrow new Error(\n\t\t\t\t`Cannot load TypeScript config file: ${filePath}. ` +\n\t\t\t\t\t'Install tsx or ts-node, or use a .js config file.',\n\t\t\t);\n\t\t}\n\t}\n}\n","import { existsSync, mkdirSync, writeFileSync } from 'fs';\nimport { dirname, join, resolve } from 'path';\n\nimport type { AnalyzedRoute } from './analyzer.js';\nimport type { ResolvedConfig } from './config.js';\n\nexport interface OpenAPISpec {\n\topenapi: '3.1.0';\n\tinfo: {\n\t\ttitle: string;\n\t\tversion: string;\n\t\tdescription?: string;\n\t};\n\tservers?: Array<{ url: string; description?: string }>;\n\tpaths: Record<string, unknown>;\n}\n\nexport function assembleSpec(config: ResolvedConfig, routes: AnalyzedRoute[]): OpenAPISpec {\n\tconst paths: Record<string, unknown> = {};\n\n\tfor (const route of routes) {\n\t\tif (Object.keys(route.pathItem).length > 0) {\n\t\t\tpaths[route.urlPath] = route.pathItem;\n\t\t}\n\t}\n\n\tconst spec: OpenAPISpec = {\n\t\topenapi: '3.1.0',\n\t\tinfo: {\n\t\t\ttitle: config.openapi.title,\n\t\t\tversion: config.openapi.version,\n\t\t\t...(config.openapi.description ? { description: config.openapi.description } : {}),\n\t\t},\n\t\tpaths,\n\t};\n\n\tif (config.openapi.servers && config.openapi.servers.length > 0) {\n\t\tspec.servers = config.openapi.servers;\n\t}\n\n\treturn spec;\n}\n\nexport function writeOutputFiles(\n\tconfig: ResolvedConfig,\n\tspec: OpenAPISpec,\n\tcwd: string = process.cwd(),\n): void {\n\twriteSpecFiles(config, spec, cwd);\n\n\tif (config.output.scalarDocs) {\n\t\twriteScalarRoute(config, cwd);\n\t}\n}\n\nfunction writeSpecFiles(config: ResolvedConfig, spec: OpenAPISpec, cwd: string): void {\n\tconst specRoutePath = resolve(cwd, config.output.specPath);\n\tconst specDir = dirname(specRoutePath);\n\n\tensureDir(specDir);\n\n\t// Write spec.json co-located with the route\n\tconst specJsonPath = join(specDir, 'spec.json');\n\twriteFileSync(specJsonPath, JSON.stringify(spec, null, 2), 'utf8');\n\n\t// Write the Next.js route that serves the spec\n\tconst routeContent = `import spec from './spec.json';\n\nexport const dynamic = 'force-static';\n\nexport function GET() {\n return Response.json(spec);\n}\n`;\n\twriteFileSync(specRoutePath, routeContent, 'utf8');\n}\n\nfunction writeScalarRoute(config: ResolvedConfig, cwd: string): void {\n\tconst scalarRoutePath = resolve(cwd, config.output.scalarPath);\n\tensureDir(dirname(scalarRoutePath));\n\n\t// Derive the spec URL from the specPath\n\tconst specUrl = filePathToApiUrl(config.output.specPath);\n\n\tconst routeContent = `export const dynamic = 'force-static';\n\nexport function GET() {\n return new Response(\n \\`<!doctype html>\n<html>\n <head>\n <title>API Docs</title>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n </head>\n <body>\n <script\n id=\"api-reference\"\n data-url=\"${specUrl}\"\n ></script>\n <script src=\"https://cdn.jsdelivr.net/npm/@scalar/api-reference\"></script>\n </body>\n</html>\\`,\n { headers: { 'Content-Type': 'text/html' } }\n );\n}\n`;\n\twriteFileSync(scalarRoutePath, routeContent, 'utf8');\n}\n\n/**\n * Convert a file path like src/app/api/openapi.json/route.ts to /api/openapi.json\n */\nfunction filePathToApiUrl(filePath: string): string {\n\tlet path = filePath.replace(/\\\\/g, '/');\n\tpath = path.replace(/^(src\\/)?app\\//, '');\n\tpath = path.replace(/\\/route\\.(ts|tsx|js|jsx)$/, '');\n\tif (!path.startsWith('/')) path = `/${path}`;\n\treturn path;\n}\n\nfunction ensureDir(dir: string): void {\n\tif (!existsSync(dir)) {\n\t\tmkdirSync(dir, { recursive: true });\n\t}\n}\n","import { readFileSync } from 'fs';\nimport { relative } from 'path';\n\nexport interface RouteInfo {\n\tfilePath: string;\n\trelativePath: string;\n\turlPath: string;\n\tsourceCode: string;\n\tjsdocComments: string[];\n\thasExactJsdoc: boolean;\n\texactPathItem?: Record<string, unknown>;\n}\n\nexport async function scanRoutes(\n\tinclude: string[],\n\texclude: string[],\n\tcwd: string = process.cwd(),\n): Promise<RouteInfo[]> {\n\t// Lazy import so that importing this module does not eagerly load fast-glob.\n\t// This keeps the module lightweight and allows Vitest to mock it cleanly.\n\tconst { default: fg } = await import('fast-glob');\n\tconst files = await fg(include, {\n\t\tcwd,\n\t\tignore: exclude,\n\t\tabsolute: true,\n\t});\n\n\treturn files.map((filePath) => parseRoute(filePath, cwd));\n}\n\nfunction parseRoute(filePath: string, cwd: string): RouteInfo {\n\tconst relativePath = relative(cwd, filePath);\n\tconst sourceCode = readFileSync(filePath, 'utf8');\n\tconst urlPath = filePathToUrlPath(relativePath);\n\tconst { jsdocComments, hasExactJsdoc, exactPathItem } = extractJsdoc(sourceCode);\n\n\treturn {\n\t\tfilePath,\n\t\trelativePath,\n\t\turlPath,\n\t\tsourceCode,\n\t\tjsdocComments,\n\t\thasExactJsdoc,\n\t\texactPathItem,\n\t};\n}\n\n/**\n * Convert a Next.js route file path to an OpenAPI URL path.\n * e.g. src/app/api/users/[id]/route.ts -> /api/users/{id}\n */\nexport function filePathToUrlPath(filePath: string): string {\n\t// Normalize separators\n\tlet path = filePath.replace(/\\\\/g, '/');\n\n\t// Remove leading src/ or app/ prefixes\n\tpath = path.replace(/^(src\\/)?app\\//, '');\n\n\t// Remove trailing /route.ts or /route.js\n\tpath = path.replace(/\\/route\\.(ts|tsx|js|jsx)$/, '');\n\n\t// Convert Next.js dynamic segments [param] to OpenAPI {param}\n\tpath = path.replace(/\\[([^\\]]+)\\]/g, (_, param) => {\n\t\t// Handle catch-all [...param] and optional [[...param]]\n\t\tif (param.startsWith('...')) {\n\t\t\treturn `{${param.slice(3)}}`;\n\t\t}\n\t\treturn `{${param}}`;\n\t});\n\n\t// Ensure leading slash\n\tif (!path.startsWith('/')) {\n\t\tpath = `/${path}`;\n\t}\n\n\treturn path;\n}\n\ninterface JsdocResult {\n\tjsdocComments: string[];\n\thasExactJsdoc: boolean;\n\texactPathItem?: Record<string, unknown>;\n}\n\nfunction extractJsdoc(sourceCode: string): JsdocResult {\n\t// Match all JSDoc comment blocks /** ... */\n\tconst jsdocRegex = /\\/\\*\\*([\\s\\S]*?)\\*\\//g;\n\tconst jsdocComments: string[] = [];\n\tlet hasExactJsdoc = false;\n\tlet exactPathItem: Record<string, unknown> | undefined;\n\n\tconst match: RegExpExecArray | null = jsdocRegex.exec(sourceCode);\n\twhile (match !== null) {\n\t\tconst comment = match[0];\n\t\tjsdocComments.push(comment);\n\n\t\t// Check for @openapi-exact tag\n\t\tif (/@openapi-exact/.test(comment)) {\n\t\t\thasExactJsdoc = true;\n\t\t\t// Try to extract the JSON from @openapi tag\n\t\t\tconst openapiMatch = comment.match(/@openapi\\s+([\\s\\S]*?)(?=\\s*\\*\\/|\\s*\\*\\s*@)/);\n\t\t\tif (openapiMatch) {\n\t\t\t\ttry {\n\t\t\t\t\t// Clean up JSDoc asterisks from the JSON\n\t\t\t\t\tconst jsonStr = openapiMatch[1]\n\t\t\t\t\t\t.split('\\n')\n\t\t\t\t\t\t.map((line) => line.replace(/^\\s*\\*\\s?/, ''))\n\t\t\t\t\t\t.join('\\n')\n\t\t\t\t\t\t.trim();\n\t\t\t\t\texactPathItem = JSON.parse(jsonStr);\n\t\t\t\t} catch {\n\t\t\t\t\t// If JSON parse fails, fall through to LLM\n\t\t\t\t\thasExactJsdoc = false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { jsdocComments, hasExactJsdoc, exactPathItem };\n}\n","export type { JSDocMode, OpenAPIGenConfig, Provider, ResolvedConfig } from './config.js';\n\nexport { analyzeRoutes } from './analyzer.js';\nexport { loadConfig, resolveConfig } from './config.js';\nexport { assembleSpec, writeOutputFiles } from './generator.js';\nexport { filePathToUrlPath, scanRoutes } from './scanner.js';\n\nimport type { OpenAPIGenConfig } from './config.js';\n\nimport { analyzeRoutes } from './analyzer.js';\nimport { loadConfig } from './config.js';\nimport { assembleSpec, writeOutputFiles } from './generator.js';\nimport { scanRoutes } from './scanner.js';\n\nexport interface GenerateOptions {\n\tconfig?: string;\n\tprovider?: OpenAPIGenConfig['provider'];\n\tcache?: boolean;\n\tcwd?: string;\n}\n\nexport interface GenerateResult {\n\troutesAnalyzed: number;\n\troutesFromCache: number;\n\troutesSkippedLLM: number;\n\tspecPath: string;\n}\n\nexport async function generate(options: GenerateOptions = {}): Promise<GenerateResult> {\n\tconst cwd = options.cwd ?? process.cwd();\n\tconst config = await loadConfig(options.config);\n\n\t// Allow CLI overrides\n\tif (options.provider) config.provider = options.provider;\n\tif (options.cache === false) config.cache = false;\n\n\tconsole.log(`[openapi-ai-generator] Scanning routes...`);\n\tconst routes = await scanRoutes(config.include, config.exclude, cwd);\n\tconsole.log(`[openapi-ai-generator] Found ${routes.length} route(s)`);\n\n\tconsole.log(`[openapi-ai-generator] Analyzing routes with provider: ${config.provider}`);\n\tconst analyzed = await analyzeRoutes(routes, {\n\t\tprovider: config.provider,\n\t\tjsdocMode: config.jsdocMode,\n\t\tcache: config.cache,\n\t\tcacheDir: config.cacheDir,\n\t});\n\n\tconst fromCache = analyzed.filter((r) => r.fromCache).length;\n\tconst skippedLLM = analyzed.filter((r) => r.skippedLLM).length;\n\tconsole.log(\n\t\t`[openapi-ai-generator] ${analyzed.length} routes analyzed (${fromCache} from cache, ${skippedLLM - fromCache} exact JSDoc)`,\n\t);\n\n\tconst spec = assembleSpec(config, analyzed);\n\twriteOutputFiles(config, spec, cwd);\n\n\tconsole.log(`[openapi-ai-generator] Spec written to ${config.output.specPath}`);\n\tif (config.output.scalarDocs) {\n\t\tconsole.log(`[openapi-ai-generator] Scalar docs written to ${config.output.scalarPath}`);\n\t}\n\n\treturn {\n\t\troutesAnalyzed: analyzed.length,\n\t\troutesFromCache: fromCache,\n\t\troutesSkippedLLM: skippedLLM,\n\t\tspecPath: config.output.specPath,\n\t};\n}\n"],"mappings":"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","names":["import_fs","import_path","import_fs","import_path","import_fs","import_path"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/analyzer.ts","../src/cache.ts","../src/providers/index.ts","../src/config.ts","../src/generator.ts","../src/scanner.ts","../src/index.ts"],"sourcesContent":["import { Command } from 'commander';\n\nimport type { Provider } from './config.js';\n\nimport { generate } from './index.js';\n\nconst program = new Command();\n\nprogram\n\t.name('openapi-ai-generator')\n\t.description('Generate OpenAPI 3.1 specs from Next.js API routes using AI')\n\t.version('0.1.0');\n\nprogram\n\t.command('generate')\n\t.description('Scan Next.js API routes and generate an OpenAPI spec')\n\t.option('-c, --config <path>', 'Path to config file (default: openapi-gen.config.ts)')\n\t.option('-p, --provider <provider>', 'Override provider (azure | openai | anthropic)')\n\t.option('--no-cache', 'Disable caching (always re-analyze all routes)')\n\t.action(async (opts: { config?: string; provider?: string; cache: boolean }) => {\n\t\ttry {\n\t\t\tconst result = await generate({\n\t\t\t\tconfig: opts.config,\n\t\t\t\tprovider: opts.provider as Provider | undefined,\n\t\t\t\tcache: opts.cache,\n\t\t\t});\n\n\t\t\tconsole.log(`\\n✓ OpenAPI spec generated successfully`);\n\t\t\tconsole.log(` Routes analyzed: ${result.routesAnalyzed}`);\n\t\t\tconsole.log(` From cache: ${result.routesFromCache}`);\n\t\t\tconsole.log(\n\t\t\t\t` LLM calls made: ${result.routesAnalyzed - result.routesFromCache - (result.routesSkippedLLM - result.routesFromCache)}`,\n\t\t\t);\n\t\t\tconsole.log(` Spec written to: ${result.specPath}`);\n\t\t} catch (err) {\n\t\t\tconsole.error('[openapi-ai-generator] Error:', err instanceof Error ? err.message : err);\n\t\t\tprocess.exit(1);\n\t\t}\n\t});\n\nprogram.parse();\n","import type { LanguageModel } from \"ai\";\nimport { generateText } from \"ai\";\n\nimport type { JSDocMode, Provider } from \"./config.js\";\nimport type { RouteInfo } from \"./scanner.js\";\n\nimport { computeHash, RouteCache } from \"./cache.js\";\nimport { createModel, getModelId } from \"./providers/index.js\";\n\nexport interface AnalyzeOptions {\n provider: Provider;\n jsdocMode: JSDocMode;\n cache: boolean;\n cacheDir: string;\n}\n\nexport interface AnalyzedRoute {\n urlPath: string;\n pathItem: Record<string, unknown>;\n fromCache: boolean;\n skippedLLM: boolean;\n}\n\nexport async function analyzeRoutes(\n routes: RouteInfo[],\n options: AnalyzeOptions,\n): Promise<AnalyzedRoute[]> {\n const modelId = getModelId(options.provider);\n const cache = options.cache ? new RouteCache(options.cacheDir) : null;\n\n // Lazy-init the model only when we actually need it\n let model: LanguageModel | null = null;\n const getModel = (): LanguageModel => {\n if (!model) model = createModel(options.provider);\n return model;\n };\n\n const results: AnalyzedRoute[] = [];\n\n for (const route of routes) {\n const result = await analyzeRoute(route, options, modelId, cache, getModel);\n results.push(result);\n }\n\n return results;\n}\n\nasync function analyzeRoute(\n route: RouteInfo,\n options: AnalyzeOptions,\n modelId: string,\n cache: RouteCache | null,\n getModel: () => LanguageModel,\n): Promise<AnalyzedRoute> {\n // If @openapi-exact is present or jsdocMode is 'exact', skip LLM\n if (route.hasExactJsdoc && route.exactPathItem) {\n return {\n urlPath: route.urlPath,\n pathItem: route.exactPathItem,\n fromCache: false,\n skippedLLM: true,\n };\n }\n\n if (options.jsdocMode === \"exact\") {\n // In exact mode, if there's a @openapi tag, use it; otherwise still use LLM\n if (route.hasExactJsdoc && route.exactPathItem) {\n return {\n urlPath: route.urlPath,\n pathItem: route.exactPathItem,\n fromCache: false,\n skippedLLM: true,\n };\n }\n }\n\n // Compute cache hash\n const hash = computeHash(route.sourceCode, options.provider, modelId);\n\n // Check cache\n if (cache) {\n const cached = cache.get(hash);\n if (cached) {\n return {\n urlPath: route.urlPath,\n pathItem: cached,\n fromCache: true,\n skippedLLM: true,\n };\n }\n }\n\n // Call LLM\n let pathItem: Record<string, unknown>;\n try {\n pathItem = await callLLM(route, options.jsdocMode, getModel());\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const isContentFilter =\n message.includes(\"content filtering policy\") ||\n message.includes(\"content_filter\") ||\n (err as { status?: number })?.status === 400;\n\n if (isContentFilter) {\n console.warn(\n `Warning: Content filter blocked response for ${route.urlPath}. ` +\n `Skipping route. Use @openapi-exact JSDoc to provide the spec manually.`,\n );\n return {\n urlPath: route.urlPath,\n pathItem: {},\n fromCache: false,\n skippedLLM: false,\n };\n }\n throw err;\n }\n\n // Store in cache\n if (cache) {\n cache.set(hash, pathItem);\n }\n\n return {\n urlPath: route.urlPath,\n pathItem,\n fromCache: false,\n skippedLLM: false,\n };\n}\n\nfunction buildPrompt(route: RouteInfo, jsdocMode: JSDocMode): string {\n const jsDocSection =\n route.jsdocComments.length > 0\n ? `JSDoc COMMENTS (use as ${jsdocMode === \"context\" ? \"additional context\" : \"primary source\"}):\\n${route.jsdocComments.join(\"\\n\\n\")}`\n : \"No JSDoc comments found.\";\n\n return `You are a technical documentation tool that reads existing source code and produces OpenAPI 3.1 documentation data. You do not write or execute code — you only read and describe it.\n\n## Task\n\nRead the Next.js API route source file below and produce a JSON object that documents its HTTP endpoints according to the OpenAPI 3.1 PathItem schema.\n\n## Route metadata\n\n- File: ${route.relativePath}\n- URL path: ${route.urlPath}\n\n## Source file contents\n\n\\`\\`\\`typescript\n${route.sourceCode}\n\\`\\`\\`\n\n${jsDocSection}\n\n## Instructions\n\nFor each exported function named GET, POST, PUT, PATCH, or DELETE, document:\n- operationId: a unique camelCase identifier\n- summary: a short one-line description\n- description: a fuller explanation of what the endpoint does\n- parameters: path params from URL segments like {id}, and query params from searchParams usage\n- requestBody: schema inferred from request.json() calls and TypeScript types (POST/PUT/PATCH only)\n- responses: per status code, inferred from NextResponse.json() calls and return type annotations\n- tags: inferred from the URL path segments\n- security: noted if the code checks for auth tokens, session cookies, or middleware guards\n\n## Output format\n\nReturn a single raw JSON object matching the OpenAPI 3.1 PathItem schema. No explanation, no markdown fences, no extra text — only the JSON object.`;\n}\n\nasync function callLLM(\n route: RouteInfo,\n jsdocMode: JSDocMode,\n model: LanguageModel,\n): Promise<Record<string, unknown>> {\n const prompt = buildPrompt(route, jsdocMode);\n\n const { text } = await generateText({\n model,\n prompt,\n temperature: 0,\n });\n\n return parsePathItem(text, route.urlPath);\n}\n\nfunction parsePathItem(text: string, urlPath: string): Record<string, unknown> {\n // Strip any accidental markdown code fences\n let json = text.trim();\n if (json.startsWith(\"```\")) {\n json = json\n .replace(/^```(?:json)?\\s*/i, \"\")\n .replace(/\\s*```$/, \"\")\n .trim();\n }\n\n try {\n const parsed = JSON.parse(json);\n if (\n typeof parsed !== \"object\" ||\n Array.isArray(parsed) ||\n parsed === null\n ) {\n throw new Error(\"Response is not a JSON object\");\n }\n return parsed;\n } catch (err) {\n console.warn(\n `Warning: Failed to parse LLM response for ${urlPath}. Using empty PathItem.`,\n err,\n );\n return {};\n }\n}\n","import { createHash } from 'crypto';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';\nimport { join } from 'path';\n\nexport interface CacheEntry {\n\thash: string;\n\tpathItem: Record<string, unknown>;\n\tcachedAt: string;\n}\n\nexport function computeHash(content: string, provider: string, modelId: string): string {\n\treturn createHash('sha256').update(content).update(provider).update(modelId).digest('hex');\n}\n\nexport class RouteCache {\n\tprivate readonly cacheDir: string;\n\n\tconstructor(cacheDir: string) {\n\t\tthis.cacheDir = cacheDir;\n\t}\n\n\tprivate ensureDir(): void {\n\t\tif (!existsSync(this.cacheDir)) {\n\t\t\tmkdirSync(this.cacheDir, { recursive: true });\n\t\t}\n\t}\n\n\tget(hash: string): Record<string, unknown> | null {\n\t\tconst filePath = join(this.cacheDir, `${hash}.json`);\n\t\tif (!existsSync(filePath)) return null;\n\t\ttry {\n\t\t\tconst entry: CacheEntry = JSON.parse(readFileSync(filePath, 'utf8'));\n\t\t\treturn entry.pathItem;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tset(hash: string, pathItem: Record<string, unknown>): void {\n\t\tthis.ensureDir();\n\t\tconst entry: CacheEntry = {\n\t\t\thash,\n\t\t\tpathItem,\n\t\t\tcachedAt: new Date().toISOString(),\n\t\t};\n\t\tconst filePath = join(this.cacheDir, `${hash}.json`);\n\t\twriteFileSync(filePath, JSON.stringify(entry, null, 2), 'utf8');\n\t}\n}\n","import type { LanguageModel } from \"ai\";\n\nimport type { Provider } from \"../config.js\";\n\nexport function createModel(provider: Provider): LanguageModel {\n switch (provider) {\n case \"azure\":\n return createAzureModel();\n case \"openai\":\n return createOpenAIModel();\n case \"anthropic\":\n return createAnthropicModel();\n default: {\n const _exhaustive: never = provider;\n throw new Error(`Unknown provider: ${_exhaustive}`);\n }\n }\n}\n\nfunction createAzureModel(): LanguageModel {\n const endpoint = requireEnv(\"AZURE_OPENAI_ENDPOINT\");\n const resourceName = endpoint?.match(/https?:\\/\\/([^.]+)/)?.[1];\n const apiKey = requireEnv(\"AZURE_OPENAI_API_KEY\");\n const deployment = requireEnv(\"AZURE_OPENAI_DEPLOYMENT\");\n\n // Dynamic import to avoid loading unused provider SDKs\n const { createAzure } = require(\"@ai-sdk/azure\");\n const azure = createAzure({ resourceName, apiKey });\n return azure(deployment);\n}\n\nfunction createOpenAIModel(): LanguageModel {\n const apiKey = requireEnv(\"OPENAI_API_KEY\");\n const model = process.env.OPENAI_MODEL ?? \"gpt-4o\";\n\n const { createOpenAI } = require(\"@ai-sdk/openai\");\n const openai = createOpenAI({ apiKey });\n return openai(model);\n}\n\nfunction createAnthropicModel(): LanguageModel {\n const apiKey = requireEnv(\"ANTHROPIC_API_KEY\");\n const model = process.env.ANTHROPIC_MODEL ?? \"claude-sonnet-4-6\";\n\n const { createAnthropic } = require(\"@ai-sdk/anthropic\");\n const anthropic = createAnthropic({ apiKey });\n return anthropic(model);\n}\n\nfunction requireEnv(name: string): string {\n const val = process.env[name];\n if (!val) {\n throw new Error(`Required environment variable ${name} is not set.`);\n }\n return val;\n}\n\nexport function getModelId(provider: Provider): string {\n switch (provider) {\n case \"azure\":\n return process.env.AZURE_OPENAI_DEPLOYMENT ?? \"unknown\";\n case \"openai\":\n return process.env.OPENAI_MODEL ?? \"gpt-4o\";\n case \"anthropic\":\n return process.env.ANTHROPIC_MODEL ?? \"claude-sonnet-4-6\";\n }\n}\n","import { existsSync } from 'fs';\nimport { resolve } from 'path';\nimport { pathToFileURL } from 'url';\n\nexport type Provider = 'azure' | 'openai' | 'anthropic';\nexport type JSDocMode = 'context' | 'exact';\n\nexport interface OpenAPIGenConfig {\n\tprovider: Provider;\n\toutput: {\n\t\tspecPath: string;\n\t\tscalarDocs?: boolean;\n\t\tscalarPath?: string;\n\t};\n\topenapi: {\n\t\ttitle: string;\n\t\tversion: string;\n\t\tdescription?: string;\n\t\tservers?: Array<{ url: string; description?: string }>;\n\t};\n\tjsdocMode?: JSDocMode;\n\tcache?: boolean;\n\tcacheDir?: string;\n\tinclude?: string[];\n\texclude?: string[];\n\t/**\n\t * Path(s) to .env files to load before running. Defaults to ['.env', '.env.local'].\n\t * Set to false to disable automatic .env loading.\n\t */\n\tenvFile?: string | string[] | false;\n}\n\nexport interface ResolvedConfig extends Omit<Required<OpenAPIGenConfig>, 'envFile'> {\n\toutput: Required<OpenAPIGenConfig['output']>;\n\topenapi: Required<OpenAPIGenConfig['openapi']>;\n\tenvFile: string[] | false;\n}\n\nconst defaults: Omit<ResolvedConfig, 'provider' | 'output' | 'openapi'> = {\n\tjsdocMode: 'context',\n\tcache: true,\n\tcacheDir: '.openapi-cache',\n\tinclude: ['src/app/api/**/route.ts'],\n\texclude: [],\n\tenvFile: ['.env', '.env.local'],\n};\n\nexport function resolveConfig(config: OpenAPIGenConfig): ResolvedConfig {\n\tlet envFile: string[] | false;\n\tif (config.envFile === false) {\n\t\tenvFile = false;\n\t} else if (typeof config.envFile === 'string') {\n\t\tenvFile = [config.envFile];\n\t} else {\n\t\tenvFile = config.envFile ?? (defaults.envFile as string[]);\n\t}\n\n\treturn {\n\t\t...defaults,\n\t\t...config,\n\t\tenvFile,\n\t\toutput: {\n\t\t\tscalarDocs: false,\n\t\t\tscalarPath: 'src/app/api/docs/route.ts',\n\t\t\t...config.output,\n\t\t},\n\t\topenapi: {\n\t\t\tdescription: '',\n\t\t\tservers: [],\n\t\t\t...config.openapi,\n\t\t},\n\t};\n}\n\nexport async function loadConfig(configPath?: string): Promise<ResolvedConfig> {\n\tconst searchPaths = configPath\n\t\t? [configPath]\n\t\t: [\n\t\t\t\t'openapi-gen.config.ts',\n\t\t\t\t'openapi-gen.config.js',\n\t\t\t\t'openapi-gen.config.mjs',\n\t\t\t\t'openapi-gen.config.cjs',\n\t\t\t];\n\n\tfor (const p of searchPaths) {\n\t\tconst abs = resolve(process.cwd(), p);\n\t\tif (existsSync(abs)) {\n\t\t\tconst mod = await importConfig(abs);\n\t\t\tconst config: OpenAPIGenConfig = mod.default ?? mod;\n\t\t\treturn resolveConfig(config);\n\t\t}\n\t}\n\n\tthrow new Error('No openapi-gen.config.ts found. Create one at your project root.');\n}\n\nasync function importConfig(\n\tfilePath: string,\n): Promise<{ default?: OpenAPIGenConfig } & OpenAPIGenConfig> {\n\t// For .ts files, try to use tsx/ts-node if available, else fall back to require\n\tif (filePath.endsWith('.ts')) {\n\t\treturn importTypeScriptConfig(filePath);\n\t}\n\tconst url = pathToFileURL(filePath).href;\n\treturn import(url);\n}\n\nasync function importTypeScriptConfig(\n\tfilePath: string,\n): Promise<{ default?: OpenAPIGenConfig } & OpenAPIGenConfig> {\n\t// Use tsx (bundled as a dependency) to register CJS TypeScript hooks\n\t// eslint-disable-next-line @typescript-eslint/no-require-imports\n\trequire('tsx/cjs');\n\t// eslint-disable-next-line @typescript-eslint/no-require-imports\n\treturn require(filePath);\n}\n","import { existsSync, mkdirSync, writeFileSync } from 'fs';\nimport { dirname, join, resolve } from 'path';\n\nimport type { AnalyzedRoute } from './analyzer.js';\nimport type { ResolvedConfig } from './config.js';\n\nexport interface OpenAPISpec {\n\topenapi: '3.1.0';\n\tinfo: {\n\t\ttitle: string;\n\t\tversion: string;\n\t\tdescription?: string;\n\t};\n\tservers?: Array<{ url: string; description?: string }>;\n\tpaths: Record<string, unknown>;\n}\n\nexport function assembleSpec(config: ResolvedConfig, routes: AnalyzedRoute[]): OpenAPISpec {\n\tconst paths: Record<string, unknown> = {};\n\n\tfor (const route of routes) {\n\t\tif (Object.keys(route.pathItem).length > 0) {\n\t\t\tpaths[route.urlPath] = route.pathItem;\n\t\t}\n\t}\n\n\tconst spec: OpenAPISpec = {\n\t\topenapi: '3.1.0',\n\t\tinfo: {\n\t\t\ttitle: config.openapi.title,\n\t\t\tversion: config.openapi.version,\n\t\t\t...(config.openapi.description ? { description: config.openapi.description } : {}),\n\t\t},\n\t\tpaths,\n\t};\n\n\tif (config.openapi.servers && config.openapi.servers.length > 0) {\n\t\tspec.servers = config.openapi.servers;\n\t}\n\n\treturn spec;\n}\n\nexport function writeOutputFiles(\n\tconfig: ResolvedConfig,\n\tspec: OpenAPISpec,\n\tcwd: string = process.cwd(),\n): void {\n\twriteSpecFiles(config, spec, cwd);\n\n\tif (config.output.scalarDocs) {\n\t\twriteScalarRoute(config, cwd);\n\t}\n}\n\nfunction writeSpecFiles(config: ResolvedConfig, spec: OpenAPISpec, cwd: string): void {\n\tconst specRoutePath = resolve(cwd, config.output.specPath);\n\tconst specDir = dirname(specRoutePath);\n\n\tensureDir(specDir);\n\n\t// Write spec.json co-located with the route\n\tconst specJsonPath = join(specDir, 'spec.json');\n\twriteFileSync(specJsonPath, JSON.stringify(spec, null, 2), 'utf8');\n\n\t// Write the Next.js route that serves the spec\n\tconst routeContent = `import spec from './spec.json';\n\nexport const dynamic = 'force-static';\n\nexport function GET() {\n return Response.json(spec);\n}\n`;\n\twriteFileSync(specRoutePath, routeContent, 'utf8');\n}\n\nfunction writeScalarRoute(config: ResolvedConfig, cwd: string): void {\n\tconst scalarRoutePath = resolve(cwd, config.output.scalarPath);\n\tensureDir(dirname(scalarRoutePath));\n\n\t// Derive the spec URL from the specPath\n\tconst specUrl = filePathToApiUrl(config.output.specPath);\n\n\tconst routeContent = `export const dynamic = 'force-static';\n\nexport function GET() {\n return new Response(\n \\`<!doctype html>\n<html>\n <head>\n <title>API Docs</title>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n </head>\n <body>\n <script\n id=\"api-reference\"\n data-url=\"${specUrl}\"\n ></script>\n <script src=\"https://cdn.jsdelivr.net/npm/@scalar/api-reference\"></script>\n </body>\n</html>\\`,\n { headers: { 'Content-Type': 'text/html' } }\n );\n}\n`;\n\twriteFileSync(scalarRoutePath, routeContent, 'utf8');\n}\n\n/**\n * Convert a file path like src/app/api/openapi.json/route.ts to /api/openapi.json\n */\nfunction filePathToApiUrl(filePath: string): string {\n\tlet path = filePath.replace(/\\\\/g, '/');\n\tpath = path.replace(/^(src\\/)?app\\//, '');\n\tpath = path.replace(/\\/route\\.(ts|tsx|js|jsx)$/, '');\n\tif (!path.startsWith('/')) path = `/${path}`;\n\treturn path;\n}\n\nfunction ensureDir(dir: string): void {\n\tif (!existsSync(dir)) {\n\t\tmkdirSync(dir, { recursive: true });\n\t}\n}\n","import { readFileSync } from 'fs';\nimport { relative } from 'path';\n\nexport interface RouteInfo {\n\tfilePath: string;\n\trelativePath: string;\n\turlPath: string;\n\tsourceCode: string;\n\tjsdocComments: string[];\n\thasExactJsdoc: boolean;\n\texactPathItem?: Record<string, unknown>;\n}\n\nexport async function scanRoutes(\n\tinclude: string[],\n\texclude: string[],\n\tcwd: string = process.cwd(),\n): Promise<RouteInfo[]> {\n\t// Lazy import so that importing this module does not eagerly load fast-glob.\n\t// This keeps the module lightweight and allows Vitest to mock it cleanly.\n\tconst { default: fg } = await import('fast-glob');\n\tconst files = await fg(include, {\n\t\tcwd,\n\t\tignore: exclude,\n\t\tabsolute: true,\n\t});\n\n\treturn files.map((filePath) => parseRoute(filePath, cwd));\n}\n\nfunction parseRoute(filePath: string, cwd: string): RouteInfo {\n\tconst relativePath = relative(cwd, filePath);\n\tconst sourceCode = readFileSync(filePath, 'utf8');\n\tconst urlPath = filePathToUrlPath(relativePath);\n\tconst { jsdocComments, hasExactJsdoc, exactPathItem } = extractJsdoc(sourceCode);\n\n\treturn {\n\t\tfilePath,\n\t\trelativePath,\n\t\turlPath,\n\t\tsourceCode,\n\t\tjsdocComments,\n\t\thasExactJsdoc,\n\t\texactPathItem,\n\t};\n}\n\n/**\n * Convert a Next.js route file path to an OpenAPI URL path.\n * e.g. src/app/api/users/[id]/route.ts -> /api/users/{id}\n */\nexport function filePathToUrlPath(filePath: string): string {\n\t// Normalize separators\n\tlet path = filePath.replace(/\\\\/g, '/');\n\n\t// Remove leading src/ or app/ prefixes\n\tpath = path.replace(/^(src\\/)?app\\//, '');\n\n\t// Remove trailing /route.ts or /route.js\n\tpath = path.replace(/\\/route\\.(ts|tsx|js|jsx)$/, '');\n\n\t// Convert Next.js dynamic segments [param] to OpenAPI {param}\n\tpath = path.replace(/\\[([^\\]]+)\\]/g, (_, param) => {\n\t\t// Handle catch-all [...param] and optional [[...param]]\n\t\tif (param.startsWith('...')) {\n\t\t\treturn `{${param.slice(3)}}`;\n\t\t}\n\t\treturn `{${param}}`;\n\t});\n\n\t// Ensure leading slash\n\tif (!path.startsWith('/')) {\n\t\tpath = `/${path}`;\n\t}\n\n\treturn path;\n}\n\ninterface JsdocResult {\n\tjsdocComments: string[];\n\thasExactJsdoc: boolean;\n\texactPathItem?: Record<string, unknown>;\n}\n\nfunction extractJsdoc(sourceCode: string): JsdocResult {\n\t// Match all JSDoc comment blocks /** ... */\n\tconst jsdocRegex = /\\/\\*\\*([\\s\\S]*?)\\*\\//g;\n\tconst jsdocComments: string[] = [];\n\tlet hasExactJsdoc = false;\n\tlet exactPathItem: Record<string, unknown> | undefined;\n\n\tconst match: RegExpExecArray | null = jsdocRegex.exec(sourceCode);\n\twhile (match !== null) {\n\t\tconst comment = match[0];\n\t\tjsdocComments.push(comment);\n\n\t\t// Check for @openapi-exact tag\n\t\tif (/@openapi-exact/.test(comment)) {\n\t\t\thasExactJsdoc = true;\n\t\t\t// Try to extract the JSON from @openapi tag\n\t\t\tconst openapiMatch = comment.match(/@openapi\\s+([\\s\\S]*?)(?=\\s*\\*\\/|\\s*\\*\\s*@)/);\n\t\t\tif (openapiMatch) {\n\t\t\t\ttry {\n\t\t\t\t\t// Clean up JSDoc asterisks from the JSON\n\t\t\t\t\tconst jsonStr = openapiMatch[1]\n\t\t\t\t\t\t.split('\\n')\n\t\t\t\t\t\t.map((line) => line.replace(/^\\s*\\*\\s?/, ''))\n\t\t\t\t\t\t.join('\\n')\n\t\t\t\t\t\t.trim();\n\t\t\t\t\texactPathItem = JSON.parse(jsonStr);\n\t\t\t\t} catch {\n\t\t\t\t\t// If JSON parse fails, fall through to LLM\n\t\t\t\t\thasExactJsdoc = false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { jsdocComments, hasExactJsdoc, exactPathItem };\n}\n","export type { JSDocMode, OpenAPIGenConfig, Provider, ResolvedConfig } from './config.js';\n\nexport { analyzeRoutes } from './analyzer.js';\nexport { loadConfig, resolveConfig } from './config.js';\nexport { assembleSpec, writeOutputFiles } from './generator.js';\nexport { filePathToUrlPath, scanRoutes } from './scanner.js';\n\nimport { resolve } from 'node:path';\nimport type { OpenAPIGenConfig } from './config.js';\n\nimport { analyzeRoutes } from './analyzer.js';\nimport { loadConfig } from './config.js';\nimport { assembleSpec, writeOutputFiles } from './generator.js';\nimport { scanRoutes } from './scanner.js';\n\nexport interface GenerateOptions {\n\tconfig?: string;\n\tprovider?: OpenAPIGenConfig['provider'];\n\tcache?: boolean;\n\tcwd?: string;\n}\n\nexport interface GenerateResult {\n\troutesAnalyzed: number;\n\troutesFromCache: number;\n\troutesSkippedLLM: number;\n\tspecPath: string;\n}\n\nexport async function generate(options: GenerateOptions = {}): Promise<GenerateResult> {\n\tconst cwd = options.cwd ?? process.cwd();\n\tconst config = await loadConfig(options.config);\n\n\t// Allow CLI overrides\n\tif (options.provider) config.provider = options.provider;\n\tif (options.cache === false) config.cache = false;\n\n\t// Load .env files before provider env vars are read\n\tif (config.envFile !== false) {\n\t\tconst { config: dotenvConfig } = await import('dotenv');\n\t\tfor (const file of config.envFile) {\n\t\t\tdotenvConfig({ path: resolve(cwd, file), override: false });\n\t\t}\n\t}\n\n\tconsole.log(`[openapi-ai-generator] Scanning routes...`);\n\tconst routes = await scanRoutes(config.include, config.exclude, cwd);\n\tconsole.log(`[openapi-ai-generator] Found ${routes.length} route(s)`);\n\n\tconsole.log(`[openapi-ai-generator] Analyzing routes with provider: ${config.provider}`);\n\tconst analyzed = await analyzeRoutes(routes, {\n\t\tprovider: config.provider,\n\t\tjsdocMode: config.jsdocMode,\n\t\tcache: config.cache,\n\t\tcacheDir: config.cacheDir,\n\t});\n\n\tconst fromCache = analyzed.filter((r) => r.fromCache).length;\n\tconst skippedLLM = analyzed.filter((r) => r.skippedLLM).length;\n\tconsole.log(\n\t\t`[openapi-ai-generator] ${analyzed.length} routes analyzed (${fromCache} from cache, ${skippedLLM - fromCache} exact JSDoc)`,\n\t);\n\n\tconst spec = assembleSpec(config, analyzed);\n\twriteOutputFiles(config, spec, cwd);\n\n\tconsole.log(`[openapi-ai-generator] Spec written to ${config.output.specPath}`);\n\tif (config.output.scalarDocs) {\n\t\tconsole.log(`[openapi-ai-generator] Scalar docs written to ${config.output.scalarPath}`);\n\t}\n\n\treturn {\n\t\troutesAnalyzed: analyzed.length,\n\t\troutesFromCache: fromCache,\n\t\troutesSkippedLLM: skippedLLM,\n\t\tspecPath: config.output.specPath,\n\t};\n}\n"],"mappings":"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","names":["import_fs","import_path","import_fs","import_path","import_fs","import_path"]}
|
package/dist/index.d.mts
CHANGED
|
@@ -21,10 +21,16 @@ interface OpenAPIGenConfig {
|
|
|
21
21
|
cacheDir?: string;
|
|
22
22
|
include?: string[];
|
|
23
23
|
exclude?: string[];
|
|
24
|
+
/**
|
|
25
|
+
* Path(s) to .env files to load before running. Defaults to ['.env', '.env.local'].
|
|
26
|
+
* Set to false to disable automatic .env loading.
|
|
27
|
+
*/
|
|
28
|
+
envFile?: string | string[] | false;
|
|
24
29
|
}
|
|
25
|
-
interface ResolvedConfig extends Required<OpenAPIGenConfig> {
|
|
30
|
+
interface ResolvedConfig extends Omit<Required<OpenAPIGenConfig>, 'envFile'> {
|
|
26
31
|
output: Required<OpenAPIGenConfig['output']>;
|
|
27
32
|
openapi: Required<OpenAPIGenConfig['openapi']>;
|
|
33
|
+
envFile: string[] | false;
|
|
28
34
|
}
|
|
29
35
|
declare function resolveConfig(config: OpenAPIGenConfig): ResolvedConfig;
|
|
30
36
|
declare function loadConfig(configPath?: string): Promise<ResolvedConfig>;
|
package/dist/index.d.ts
CHANGED
|
@@ -21,10 +21,16 @@ interface OpenAPIGenConfig {
|
|
|
21
21
|
cacheDir?: string;
|
|
22
22
|
include?: string[];
|
|
23
23
|
exclude?: string[];
|
|
24
|
+
/**
|
|
25
|
+
* Path(s) to .env files to load before running. Defaults to ['.env', '.env.local'].
|
|
26
|
+
* Set to false to disable automatic .env loading.
|
|
27
|
+
*/
|
|
28
|
+
envFile?: string | string[] | false;
|
|
24
29
|
}
|
|
25
|
-
interface ResolvedConfig extends Required<OpenAPIGenConfig> {
|
|
30
|
+
interface ResolvedConfig extends Omit<Required<OpenAPIGenConfig>, 'envFile'> {
|
|
26
31
|
output: Required<OpenAPIGenConfig['output']>;
|
|
27
32
|
openapi: Required<OpenAPIGenConfig['openapi']>;
|
|
33
|
+
envFile: string[] | false;
|
|
28
34
|
}
|
|
29
35
|
declare function resolveConfig(config: OpenAPIGenConfig): ResolvedConfig;
|
|
30
36
|
declare function loadConfig(configPath?: string): Promise<ResolvedConfig>;
|
package/dist/index.js
CHANGED
|
@@ -64,10 +64,11 @@ function createModel(provider) {
|
|
|
64
64
|
}
|
|
65
65
|
function createAzureModel() {
|
|
66
66
|
const endpoint = requireEnv("AZURE_OPENAI_ENDPOINT");
|
|
67
|
+
const resourceName = endpoint?.match(/https?:\/\/([^.]+)/)?.[1];
|
|
67
68
|
const apiKey = requireEnv("AZURE_OPENAI_API_KEY");
|
|
68
69
|
const deployment = requireEnv("AZURE_OPENAI_DEPLOYMENT");
|
|
69
70
|
const { createAzure } = __require("@ai-sdk/azure");
|
|
70
|
-
const azure = createAzure({
|
|
71
|
+
const azure = createAzure({ resourceName, apiKey });
|
|
71
72
|
return azure(deployment);
|
|
72
73
|
}
|
|
73
74
|
function createOpenAIModel() {
|
|
@@ -249,12 +250,22 @@ var defaults = {
|
|
|
249
250
|
cache: true,
|
|
250
251
|
cacheDir: ".openapi-cache",
|
|
251
252
|
include: ["src/app/api/**/route.ts"],
|
|
252
|
-
exclude: []
|
|
253
|
+
exclude: [],
|
|
254
|
+
envFile: [".env", ".env.local"]
|
|
253
255
|
};
|
|
254
256
|
function resolveConfig(config) {
|
|
257
|
+
let envFile;
|
|
258
|
+
if (config.envFile === false) {
|
|
259
|
+
envFile = false;
|
|
260
|
+
} else if (typeof config.envFile === "string") {
|
|
261
|
+
envFile = [config.envFile];
|
|
262
|
+
} else {
|
|
263
|
+
envFile = config.envFile ?? defaults.envFile;
|
|
264
|
+
}
|
|
255
265
|
return {
|
|
256
266
|
...defaults,
|
|
257
267
|
...config,
|
|
268
|
+
envFile,
|
|
258
269
|
output: {
|
|
259
270
|
scalarDocs: false,
|
|
260
271
|
scalarPath: "src/app/api/docs/route.ts",
|
|
@@ -292,20 +303,8 @@ async function importConfig(filePath) {
|
|
|
292
303
|
return import(url$1);
|
|
293
304
|
}
|
|
294
305
|
async function importTypeScriptConfig(filePath) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
const url$1 = url.pathToFileURL(filePath).href;
|
|
298
|
-
return await import(url$1);
|
|
299
|
-
} catch {
|
|
300
|
-
try {
|
|
301
|
-
__require("ts-node/register");
|
|
302
|
-
return __require(filePath);
|
|
303
|
-
} catch {
|
|
304
|
-
throw new Error(
|
|
305
|
-
`Cannot load TypeScript config file: ${filePath}. Install tsx or ts-node, or use a .js config file.`
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
306
|
+
__require("tsx/cjs");
|
|
307
|
+
return __require(filePath);
|
|
309
308
|
}
|
|
310
309
|
function assembleSpec(config, routes) {
|
|
311
310
|
const paths = {};
|
|
@@ -454,13 +453,17 @@ function extractJsdoc(sourceCode) {
|
|
|
454
453
|
}
|
|
455
454
|
return { jsdocComments, hasExactJsdoc, exactPathItem };
|
|
456
455
|
}
|
|
457
|
-
|
|
458
|
-
// src/index.ts
|
|
459
456
|
async function generate(options = {}) {
|
|
460
457
|
const cwd = options.cwd ?? process.cwd();
|
|
461
458
|
const config = await loadConfig(options.config);
|
|
462
459
|
if (options.provider) config.provider = options.provider;
|
|
463
460
|
if (options.cache === false) config.cache = false;
|
|
461
|
+
if (config.envFile !== false) {
|
|
462
|
+
const { config: dotenvConfig } = await import('dotenv');
|
|
463
|
+
for (const file of config.envFile) {
|
|
464
|
+
dotenvConfig({ path: path.resolve(cwd, file), override: false });
|
|
465
|
+
}
|
|
466
|
+
}
|
|
464
467
|
console.log(`[openapi-ai-generator] Scanning routes...`);
|
|
465
468
|
const routes = await scanRoutes(config.include, config.exclude, cwd);
|
|
466
469
|
console.log(`[openapi-ai-generator] Found ${routes.length} route(s)`);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cache.ts","../src/providers/index.ts","../src/analyzer.ts","../src/config.ts","../src/generator.ts","../src/scanner.ts","../src/index.ts"],"names":["createHash","existsSync","mkdirSync","join","readFileSync","writeFileSync","generateText","resolve","url","pathToFileURL","dirname","relative"],"mappings":";;;;;;;;;;;;;;AAUO,SAAS,WAAA,CAAY,OAAA,EAAiB,QAAA,EAAkB,OAAA,EAAyB;AACvF,EAAA,OAAOA,iBAAA,CAAW,QAAQ,CAAA,CAAE,MAAA,CAAO,OAAO,CAAA,CAAE,MAAA,CAAO,QAAQ,CAAA,CAAE,MAAA,CAAO,OAAO,CAAA,CAAE,OAAO,KAAK,CAAA;AAC1F;AAEO,IAAM,aAAN,MAAiB;AAAA,EACN,QAAA;AAAA,EAEjB,YAAY,QAAA,EAAkB;AAC7B,IAAA,IAAA,CAAK,QAAA,GAAW,QAAA;AAAA,EACjB;AAAA,EAEQ,SAAA,GAAkB;AACzB,IAAA,IAAI,CAACC,aAAA,CAAW,IAAA,CAAK,QAAQ,CAAA,EAAG;AAC/B,MAAAC,YAAA,CAAU,IAAA,CAAK,QAAA,EAAU,EAAE,SAAA,EAAW,MAAM,CAAA;AAAA,IAC7C;AAAA,EACD;AAAA,EAEA,IAAI,IAAA,EAA8C;AACjD,IAAA,MAAM,WAAWC,SAAA,CAAK,IAAA,CAAK,QAAA,EAAU,CAAA,EAAG,IAAI,CAAA,KAAA,CAAO,CAAA;AACnD,IAAA,IAAI,CAACF,aAAA,CAAW,QAAQ,CAAA,EAAG,OAAO,IAAA;AAClC,IAAA,IAAI;AACH,MAAA,MAAM,QAAoB,IAAA,CAAK,KAAA,CAAMG,eAAA,CAAa,QAAA,EAAU,MAAM,CAAC,CAAA;AACnE,MAAA,OAAO,KAAA,CAAM,QAAA;AAAA,IACd,CAAA,CAAA,MAAQ;AACP,MAAA,OAAO,IAAA;AAAA,IACR;AAAA,EACD;AAAA,EAEA,GAAA,CAAI,MAAc,QAAA,EAAyC;AAC1D,IAAA,IAAA,CAAK,SAAA,EAAU;AACf,IAAA,MAAM,KAAA,GAAoB;AAAA,MACzB,IAAA;AAAA,MACA,QAAA;AAAA,MACA,QAAA,EAAA,iBAAU,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,KAClC;AACA,IAAA,MAAM,WAAWD,SAAA,CAAK,IAAA,CAAK,QAAA,EAAU,CAAA,EAAG,IAAI,CAAA,KAAA,CAAO,CAAA;AACnD,IAAAE,gBAAA,CAAc,UAAU,IAAA,CAAK,SAAA,CAAU,OAAO,IAAA,EAAM,CAAC,GAAG,MAAM,CAAA;AAAA,EAC/D;AACD,CAAA;;;AC5CO,SAAS,YAAY,QAAA,EAAmC;AAC9D,EAAA,QAAQ,QAAA;AAAU,IACjB,KAAK,OAAA;AACJ,MAAA,OAAO,gBAAA,EAAiB;AAAA,IACzB,KAAK,QAAA;AACJ,MAAA,OAAO,iBAAA,EAAkB;AAAA,IAC1B,KAAK,WAAA;AACJ,MAAA,OAAO,oBAAA,EAAqB;AAAA,IAC7B,SAAS;AACR,MAAA,MAAM,WAAA,GAAqB,QAAA;AAC3B,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,kBAAA,EAAqB,WAAW,CAAA,CAAE,CAAA;AAAA,IACnD;AAAA;AAEF;AAEA,SAAS,gBAAA,GAAkC;AAC1C,EAAA,MAAM,QAAA,GAAW,WAAW,uBAAuB,CAAA;AACnD,EAAA,MAAM,MAAA,GAAS,WAAW,sBAAsB,CAAA;AAChD,EAAA,MAAM,UAAA,GAAa,WAAW,yBAAyB,CAAA;AAGvD,EAAA,MAAM,EAAE,WAAA,EAAY,GAAI,SAAA,CAAQ,eAAe,CAAA;AAC/C,EAAA,MAAM,KAAA,GAAQ,WAAA,CAAY,EAAE,QAAA,EAAU,QAAQ,CAAA;AAC9C,EAAA,OAAO,MAAM,UAAU,CAAA;AACxB;AAEA,SAAS,iBAAA,GAAmC;AAC3C,EAAA,MAAM,MAAA,GAAS,WAAW,gBAAgB,CAAA;AAC1C,EAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,GAAA,CAAI,YAAA,IAAgB,QAAA;AAE1C,EAAA,MAAM,EAAE,YAAA,EAAa,GAAI,SAAA,CAAQ,gBAAgB,CAAA;AACjD,EAAA,MAAM,MAAA,GAAS,YAAA,CAAa,EAAE,MAAA,EAAQ,CAAA;AACtC,EAAA,OAAO,OAAO,KAAK,CAAA;AACpB;AAEA,SAAS,oBAAA,GAAsC;AAC9C,EAAA,MAAM,MAAA,GAAS,WAAW,mBAAmB,CAAA;AAC7C,EAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,GAAA,CAAI,eAAA,IAAmB,mBAAA;AAE7C,EAAA,MAAM,EAAE,eAAA,EAAgB,GAAI,SAAA,CAAQ,mBAAmB,CAAA;AACvD,EAAA,MAAM,SAAA,GAAY,eAAA,CAAgB,EAAE,MAAA,EAAQ,CAAA;AAC5C,EAAA,OAAO,UAAU,KAAK,CAAA;AACvB;AAEA,SAAS,WAAW,IAAA,EAAsB;AACzC,EAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,GAAA,CAAI,IAAI,CAAA;AAC5B,EAAA,IAAI,CAAC,GAAA,EAAK;AACT,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,8BAAA,EAAiC,IAAI,CAAA,YAAA,CAAc,CAAA;AAAA,EACpE;AACA,EAAA,OAAO,GAAA;AACR;AAEO,SAAS,WAAW,QAAA,EAA4B;AACtD,EAAA,QAAQ,QAAA;AAAU,IACjB,KAAK,OAAA;AACJ,MAAA,OAAO,OAAA,CAAQ,IAAI,uBAAA,IAA2B,SAAA;AAAA,IAC/C,KAAK,QAAA;AACJ,MAAA,OAAO,OAAA,CAAQ,IAAI,YAAA,IAAgB,QAAA;AAAA,IACpC,KAAK,WAAA;AACJ,MAAA,OAAO,OAAA,CAAQ,IAAI,eAAA,IAAmB,mBAAA;AAAA;AAEzC;;;AC1CA,eAAsB,aAAA,CACrB,QACA,OAAA,EAC2B;AAC3B,EAAA,MAAM,OAAA,GAAU,UAAA,CAAW,OAAA,CAAQ,QAAQ,CAAA;AAC3C,EAAA,MAAM,QAAQ,OAAA,CAAQ,KAAA,GAAQ,IAAI,UAAA,CAAW,OAAA,CAAQ,QAAQ,CAAA,GAAI,IAAA;AAGjE,EAAA,IAAI,KAAA,GAA8B,IAAA;AAClC,EAAA,MAAM,WAAW,MAAqB;AACrC,IAAA,IAAI,CAAC,KAAA,EAAO,KAAA,GAAQ,WAAA,CAAY,QAAQ,QAAQ,CAAA;AAChD,IAAA,OAAO,KAAA;AAAA,EACR,CAAA;AAEA,EAAA,MAAM,UAA2B,EAAC;AAElC,EAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC3B,IAAA,MAAM,SAAS,MAAM,YAAA,CAAa,OAAO,OAAA,EAAS,OAAA,EAAS,OAAO,QAAQ,CAAA;AAC1E,IAAA,OAAA,CAAQ,KAAK,MAAM,CAAA;AAAA,EACpB;AAEA,EAAA,OAAO,OAAA;AACR;AAEA,eAAe,YAAA,CACd,KAAA,EACA,OAAA,EACA,OAAA,EACA,OACA,QAAA,EACyB;AAEzB,EAAA,IAAI,KAAA,CAAM,aAAA,IAAiB,KAAA,CAAM,aAAA,EAAe;AAC/C,IAAA,OAAO;AAAA,MACN,SAAS,KAAA,CAAM,OAAA;AAAA,MACf,UAAU,KAAA,CAAM,aAAA;AAAA,MAChB,SAAA,EAAW,KAAA;AAAA,MACX,UAAA,EAAY;AAAA,KACb;AAAA,EACD;AAEA,EAAA,IAAI,OAAA,CAAQ,cAAc,OAAA,EAAS;AAElC,IAAA,IAAI,KAAA,CAAM,aAAA,IAAiB,KAAA,CAAM,aAAA,EAAe;AAC/C,MAAA,OAAO;AAAA,QACN,SAAS,KAAA,CAAM,OAAA;AAAA,QACf,UAAU,KAAA,CAAM,aAAA;AAAA,QAChB,SAAA,EAAW,KAAA;AAAA,QACX,UAAA,EAAY;AAAA,OACb;AAAA,IACD;AAAA,EACD;AAGA,EAAA,MAAM,OAAO,WAAA,CAAY,KAAA,CAAM,UAAA,EAAY,OAAA,CAAQ,UAAU,OAAO,CAAA;AAGpE,EAAA,IAAI,KAAA,EAAO;AACV,IAAA,MAAM,MAAA,GAAS,KAAA,CAAM,GAAA,CAAI,IAAI,CAAA;AAC7B,IAAA,IAAI,MAAA,EAAQ;AACX,MAAA,OAAO;AAAA,QACN,SAAS,KAAA,CAAM,OAAA;AAAA,QACf,QAAA,EAAU,MAAA;AAAA,QACV,SAAA,EAAW,IAAA;AAAA,QACX,UAAA,EAAY;AAAA,OACb;AAAA,IACD;AAAA,EACD;AAGA,EAAA,IAAI,QAAA;AACJ,EAAA,IAAI;AACH,IAAA,QAAA,GAAW,MAAM,OAAA,CAAQ,KAAA,EAAO,OAAA,CAAQ,SAAA,EAAW,UAAU,CAAA;AAAA,EAC9D,SAAS,GAAA,EAAK;AACb,IAAA,MAAM,UAAU,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,OAAO,GAAG,CAAA;AAC/D,IAAA,MAAM,eAAA,GACL,OAAA,CAAQ,QAAA,CAAS,0BAA0B,CAAA,IAC3C,QAAQ,QAAA,CAAS,gBAAgB,CAAA,IAChC,GAAA,EAA6B,MAAA,KAAW,GAAA;AAE1C,IAAA,IAAI,eAAA,EAAiB;AACpB,MAAA,OAAA,CAAQ,IAAA;AAAA,QACP,CAAA,6CAAA,EAAgD,MAAM,OAAO,CAAA,wEAAA;AAAA,OAE9D;AACA,MAAA,OAAO;AAAA,QACN,SAAS,KAAA,CAAM,OAAA;AAAA,QACf,UAAU,EAAC;AAAA,QACX,SAAA,EAAW,KAAA;AAAA,QACX,UAAA,EAAY;AAAA,OACb;AAAA,IACD;AACA,IAAA,MAAM,GAAA;AAAA,EACP;AAGA,EAAA,IAAI,KAAA,EAAO;AACV,IAAA,KAAA,CAAM,GAAA,CAAI,MAAM,QAAQ,CAAA;AAAA,EACzB;AAEA,EAAA,OAAO;AAAA,IACN,SAAS,KAAA,CAAM,OAAA;AAAA,IACf,QAAA;AAAA,IACA,SAAA,EAAW,KAAA;AAAA,IACX,UAAA,EAAY;AAAA,GACb;AACD;AAEA,SAAS,WAAA,CAAY,OAAkB,SAAA,EAA8B;AACpE,EAAA,MAAM,YAAA,GACL,MAAM,aAAA,CAAc,MAAA,GAAS,IAC1B,CAAA,uBAAA,EAA0B,SAAA,KAAc,SAAA,GAAY,oBAAA,GAAuB,gBAAgB,CAAA;AAAA,EAAO,KAAA,CAAM,aAAA,CAAc,IAAA,CAAK,MAAM,CAAC,CAAA,CAAA,GAClI,0BAAA;AAEJ,EAAA,OAAO,CAAA;;AAAA;;AAAA;;AAAA;;AAAA,QAAA,EAQE,MAAM,YAAY;AAAA,YAAA,EACd,MAAM,OAAO;;AAAA;;AAAA;AAAA,EAKzB,MAAM,UAAU;AAAA;;AAAA,EAGhB,YAAY;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA,wJAAA,CAAA;AAiBd;AAEA,eAAe,OAAA,CACd,KAAA,EACA,SAAA,EACA,KAAA,EACmC;AACnC,EAAA,MAAM,MAAA,GAAS,WAAA,CAAY,KAAA,EAAO,SAAS,CAAA;AAE3C,EAAA,MAAM,EAAE,IAAA,EAAK,GAAI,MAAMC,eAAA,CAAa;AAAA,IACnC,KAAA;AAAA,IACA,MAAA;AAAA,IACA,WAAA,EAAa;AAAA,GACb,CAAA;AAED,EAAA,OAAO,aAAA,CAAc,IAAA,EAAM,KAAA,CAAM,OAAO,CAAA;AACzC;AAEA,SAAS,aAAA,CAAc,MAAc,OAAA,EAA0C;AAE9E,EAAA,IAAI,IAAA,GAAO,KAAK,IAAA,EAAK;AACrB,EAAA,IAAI,IAAA,CAAK,UAAA,CAAW,KAAK,CAAA,EAAG;AAC3B,IAAA,IAAA,GAAO,IAAA,CACL,QAAQ,mBAAA,EAAqB,EAAE,EAC/B,OAAA,CAAQ,SAAA,EAAW,EAAE,CAAA,CACrB,IAAA,EAAK;AAAA,EACR;AAEA,EAAA,IAAI;AACH,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AAC9B,IAAA,IAAI,OAAO,WAAW,QAAA,IAAY,KAAA,CAAM,QAAQ,MAAM,CAAA,IAAK,WAAW,IAAA,EAAM;AAC3E,MAAA,MAAM,IAAI,MAAM,+BAA+B,CAAA;AAAA,IAChD;AACA,IAAA,OAAO,MAAA;AAAA,EACR,SAAS,GAAA,EAAK;AACb,IAAA,OAAA,CAAQ,IAAA;AAAA,MACP,6CAA6C,OAAO,CAAA,uBAAA,CAAA;AAAA,MACpD;AAAA,KACD;AACA,IAAA,OAAO,EAAC;AAAA,EACT;AACD;ACpLA,IAAM,QAAA,GAAoE;AAAA,EACzE,SAAA,EAAW,SAAA;AAAA,EACX,KAAA,EAAO,IAAA;AAAA,EACP,QAAA,EAAU,gBAAA;AAAA,EACV,OAAA,EAAS,CAAC,yBAAyB,CAAA;AAAA,EACnC,SAAS;AACV,CAAA;AAEO,SAAS,cAAc,MAAA,EAA0C;AACvE,EAAA,OAAO;AAAA,IACN,GAAG,QAAA;AAAA,IACH,GAAG,MAAA;AAAA,IACH,MAAA,EAAQ;AAAA,MACP,UAAA,EAAY,KAAA;AAAA,MACZ,UAAA,EAAY,2BAAA;AAAA,MACZ,GAAG,MAAA,CAAO;AAAA,KACX;AAAA,IACA,OAAA,EAAS;AAAA,MACR,WAAA,EAAa,EAAA;AAAA,MACb,SAAS,EAAC;AAAA,MACV,GAAG,MAAA,CAAO;AAAA;AACX,GACD;AACD;AAEA,eAAsB,WAAW,UAAA,EAA8C;AAC9E,EAAA,MAAM,WAAA,GAAc,UAAA,GACjB,CAAC,UAAU,CAAA,GACX;AAAA,IACA,uBAAA;AAAA,IACA,uBAAA;AAAA,IACA,wBAAA;AAAA,IACA;AAAA,GACD;AAEF,EAAA,KAAA,MAAW,KAAK,WAAA,EAAa;AAC5B,IAAA,MAAM,GAAA,GAAMC,YAAA,CAAQ,OAAA,CAAQ,GAAA,IAAO,CAAC,CAAA;AACpC,IAAA,IAAIN,aAAAA,CAAW,GAAG,CAAA,EAAG;AACpB,MAAA,MAAM,GAAA,GAAM,MAAM,YAAA,CAAa,GAAG,CAAA;AAClC,MAAA,MAAM,MAAA,GAA2B,IAAI,OAAA,IAAW,GAAA;AAChD,MAAA,OAAO,cAAc,MAAM,CAAA;AAAA,IAC5B;AAAA,EACD;AAEA,EAAA,MAAM,IAAI,MAAM,kEAAkE,CAAA;AACnF;AAEA,eAAe,aACd,QAAA,EAC6D;AAE7D,EAAA,IAAI,QAAA,CAAS,QAAA,CAAS,KAAK,CAAA,EAAG;AAC7B,IAAA,OAAO,uBAAuB,QAAQ,CAAA;AAAA,EACvC;AACA,EAAA,MAAMO,KAAA,GAAMC,iBAAA,CAAc,QAAQ,CAAA,CAAE,IAAA;AACpC,EAAA,OAAO,OAAOD,KAAA,CAAA;AACf;AAEA,eAAe,uBACd,QAAA,EAC6D;AAE7D,EAAA,IAAI;AAEH,IAAA,MAAM,OAAO,QAAQ,CAAA;AAErB,IAAA,MAAMA,KAAA,GAAMC,iBAAA,CAAc,QAAQ,CAAA,CAAE,IAAA;AACpC,IAAA,OAAO,MAAM,OAAOD,KAAA,CAAA;AAAA,EACrB,CAAA,CAAA,MAAQ;AAEP,IAAA,IAAI;AAEH,MAAA,SAAA,CAAQ,kBAAkB,CAAA;AAE1B,MAAA,OAAO,UAAQ,QAAQ,CAAA;AAAA,IACxB,CAAA,CAAA,MAAQ;AACP,MAAA,MAAM,IAAI,KAAA;AAAA,QACT,uCAAuC,QAAQ,CAAA,mDAAA;AAAA,OAEhD;AAAA,IACD;AAAA,EACD;AACD;ACjGO,SAAS,YAAA,CAAa,QAAwB,MAAA,EAAsC;AAC1F,EAAA,MAAM,QAAiC,EAAC;AAExC,EAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC3B,IAAA,IAAI,OAAO,IAAA,CAAK,KAAA,CAAM,QAAQ,CAAA,CAAE,SAAS,CAAA,EAAG;AAC3C,MAAA,KAAA,CAAM,KAAA,CAAM,OAAO,CAAA,GAAI,KAAA,CAAM,QAAA;AAAA,IAC9B;AAAA,EACD;AAEA,EAAA,MAAM,IAAA,GAAoB;AAAA,IACzB,OAAA,EAAS,OAAA;AAAA,IACT,IAAA,EAAM;AAAA,MACL,KAAA,EAAO,OAAO,OAAA,CAAQ,KAAA;AAAA,MACtB,OAAA,EAAS,OAAO,OAAA,CAAQ,OAAA;AAAA,MACxB,GAAI,MAAA,CAAO,OAAA,CAAQ,WAAA,GAAc,EAAE,aAAa,MAAA,CAAO,OAAA,CAAQ,WAAA,EAAY,GAAI;AAAC,KACjF;AAAA,IACA;AAAA,GACD;AAEA,EAAA,IAAI,OAAO,OAAA,CAAQ,OAAA,IAAW,OAAO,OAAA,CAAQ,OAAA,CAAQ,SAAS,CAAA,EAAG;AAChE,IAAA,IAAA,CAAK,OAAA,GAAU,OAAO,OAAA,CAAQ,OAAA;AAAA,EAC/B;AAEA,EAAA,OAAO,IAAA;AACR;AAEO,SAAS,iBACf,MAAA,EACA,IAAA,EACA,GAAA,GAAc,OAAA,CAAQ,KAAI,EACnB;AACP,EAAA,cAAA,CAAe,MAAA,EAAQ,MAAM,GAAG,CAAA;AAEhC,EAAA,IAAI,MAAA,CAAO,OAAO,UAAA,EAAY;AAC7B,IAAA,gBAAA,CAAiB,QAAQ,GAAG,CAAA;AAAA,EAC7B;AACD;AAEA,SAAS,cAAA,CAAe,MAAA,EAAwB,IAAA,EAAmB,GAAA,EAAmB;AACrF,EAAA,MAAM,aAAA,GAAgBD,YAAAA,CAAQ,GAAA,EAAK,MAAA,CAAO,OAAO,QAAQ,CAAA;AACzD,EAAA,MAAM,OAAA,GAAUG,aAAQ,aAAa,CAAA;AAErC,EAAA,SAAA,CAAU,OAAO,CAAA;AAGjB,EAAA,MAAM,YAAA,GAAeP,SAAAA,CAAK,OAAA,EAAS,WAAW,CAAA;AAC9C,EAAAE,gBAAAA,CAAc,cAAc,IAAA,CAAK,SAAA,CAAU,MAAM,IAAA,EAAM,CAAC,GAAG,MAAM,CAAA;AAGjE,EAAA,MAAM,YAAA,GAAe,CAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA,CAAA;AAQrB,EAAAA,gBAAAA,CAAc,aAAA,EAAe,YAAA,EAAc,MAAM,CAAA;AAClD;AAEA,SAAS,gBAAA,CAAiB,QAAwB,GAAA,EAAmB;AACpE,EAAA,MAAM,eAAA,GAAkBE,YAAAA,CAAQ,GAAA,EAAK,MAAA,CAAO,OAAO,UAAU,CAAA;AAC7D,EAAA,SAAA,CAAUG,YAAA,CAAQ,eAAe,CAAC,CAAA;AAGlC,EAAA,MAAM,OAAA,GAAU,gBAAA,CAAiB,MAAA,CAAO,MAAA,CAAO,QAAQ,CAAA;AAEvD,EAAA,MAAM,YAAA,GAAe,CAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gBAAA,EAcJ,OAAO,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AASxB,EAAAL,gBAAAA,CAAc,eAAA,EAAiB,YAAA,EAAc,MAAM,CAAA;AACpD;AAKA,SAAS,iBAAiB,QAAA,EAA0B;AACnD,EAAA,IAAI,IAAA,GAAO,QAAA,CAAS,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA;AACtC,EAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,gBAAA,EAAkB,EAAE,CAAA;AACxC,EAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,2BAAA,EAA6B,EAAE,CAAA;AACnD,EAAA,IAAI,CAAC,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,EAAG,IAAA,GAAO,IAAI,IAAI,CAAA,CAAA;AAC1C,EAAA,OAAO,IAAA;AACR;AAEA,SAAS,UAAU,GAAA,EAAmB;AACrC,EAAA,IAAI,CAACJ,aAAAA,CAAW,GAAG,CAAA,EAAG;AACrB,IAAAC,YAAAA,CAAU,GAAA,EAAK,EAAE,SAAA,EAAW,MAAM,CAAA;AAAA,EACnC;AACD;AChHA,eAAsB,WACrB,OAAA,EACA,OAAA,EACA,GAAA,GAAc,OAAA,CAAQ,KAAI,EACH;AAGvB,EAAA,MAAM,EAAE,OAAA,EAAS,EAAA,EAAG,GAAI,MAAM,OAAO,WAAW,CAAA;AAChD,EAAA,MAAM,KAAA,GAAQ,MAAM,EAAA,CAAG,OAAA,EAAS;AAAA,IAC/B,GAAA;AAAA,IACA,MAAA,EAAQ,OAAA;AAAA,IACR,QAAA,EAAU;AAAA,GACV,CAAA;AAED,EAAA,OAAO,MAAM,GAAA,CAAI,CAAC,aAAa,UAAA,CAAW,QAAA,EAAU,GAAG,CAAC,CAAA;AACzD;AAEA,SAAS,UAAA,CAAW,UAAkB,GAAA,EAAwB;AAC7D,EAAA,MAAM,YAAA,GAAeS,aAAA,CAAS,GAAA,EAAK,QAAQ,CAAA;AAC3C,EAAA,MAAM,UAAA,GAAaP,eAAAA,CAAa,QAAA,EAAU,MAAM,CAAA;AAChD,EAAA,MAAM,OAAA,GAAU,kBAAkB,YAAY,CAAA;AAC9C,EAAA,MAAM,EAAE,aAAA,EAAe,aAAA,EAAe,aAAA,EAAc,GAAI,aAAa,UAAU,CAAA;AAE/E,EAAA,OAAO;AAAA,IACN,QAAA;AAAA,IACA,YAAA;AAAA,IACA,OAAA;AAAA,IACA,UAAA;AAAA,IACA,aAAA;AAAA,IACA,aAAA;AAAA,IACA;AAAA,GACD;AACD;AAMO,SAAS,kBAAkB,QAAA,EAA0B;AAE3D,EAAA,IAAI,IAAA,GAAO,QAAA,CAAS,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA;AAGtC,EAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,gBAAA,EAAkB,EAAE,CAAA;AAGxC,EAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,2BAAA,EAA6B,EAAE,CAAA;AAGnD,EAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,eAAA,EAAiB,CAAC,GAAG,KAAA,KAAU;AAElD,IAAA,IAAI,KAAA,CAAM,UAAA,CAAW,KAAK,CAAA,EAAG;AAC5B,MAAA,OAAO,CAAA,CAAA,EAAI,KAAA,CAAM,KAAA,CAAM,CAAC,CAAC,CAAA,CAAA,CAAA;AAAA,IAC1B;AACA,IAAA,OAAO,IAAI,KAAK,CAAA,CAAA,CAAA;AAAA,EACjB,CAAC,CAAA;AAGD,EAAA,IAAI,CAAC,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,EAAG;AAC1B,IAAA,IAAA,GAAO,IAAI,IAAI,CAAA,CAAA;AAAA,EAChB;AAEA,EAAA,OAAO,IAAA;AACR;AAQA,SAAS,aAAa,UAAA,EAAiC;AAEtD,EAAA,MAAM,UAAA,GAAa,uBAAA;AACnB,EAAA,MAAM,gBAA0B,EAAC;AACjC,EAAA,IAAI,aAAA,GAAgB,KAAA;AACpB,EAAA,IAAI,aAAA;AAEJ,EAAA,MAAM,KAAA,GAAgC,UAAA,CAAW,IAAA,CAAK,UAAU,CAAA;AAChE,EAAA,OAAO,UAAU,IAAA,EAAM;AACtB,IAAA,MAAM,OAAA,GAAU,MAAM,CAAC,CAAA;AACvB,IAAA,aAAA,CAAc,KAAK,OAAO,CAAA;AAG1B,IAAA,IAAI,gBAAA,CAAiB,IAAA,CAAK,OAAO,CAAA,EAAG;AACnC,MAAA,aAAA,GAAgB,IAAA;AAEhB,MAAA,MAAM,YAAA,GAAe,OAAA,CAAQ,KAAA,CAAM,4CAA4C,CAAA;AAC/E,MAAA,IAAI,YAAA,EAAc;AACjB,QAAA,IAAI;AAEH,UAAA,MAAM,UAAU,YAAA,CAAa,CAAC,EAC5B,KAAA,CAAM,IAAI,EACV,GAAA,CAAI,CAAC,SAAS,IAAA,CAAK,OAAA,CAAQ,aAAa,EAAE,CAAC,EAC3C,IAAA,CAAK,IAAI,EACT,IAAA,EAAK;AACP,UAAA,aAAA,GAAgB,IAAA,CAAK,MAAM,OAAO,CAAA;AAAA,QACnC,CAAA,CAAA,MAAQ;AAEP,UAAA,aAAA,GAAgB,KAAA;AAAA,QACjB;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAEA,EAAA,OAAO,EAAE,aAAA,EAAe,aAAA,EAAe,aAAA,EAAc;AACtD;;;AC3FA,eAAsB,QAAA,CAAS,OAAA,GAA2B,EAAC,EAA4B;AACtF,EAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,GAAA,IAAO,OAAA,CAAQ,GAAA,EAAI;AACvC,EAAA,MAAM,MAAA,GAAS,MAAM,UAAA,CAAW,OAAA,CAAQ,MAAM,CAAA;AAG9C,EAAA,IAAI,OAAA,CAAQ,QAAA,EAAU,MAAA,CAAO,QAAA,GAAW,OAAA,CAAQ,QAAA;AAChD,EAAA,IAAI,OAAA,CAAQ,KAAA,KAAU,KAAA,EAAO,MAAA,CAAO,KAAA,GAAQ,KAAA;AAE5C,EAAA,OAAA,CAAQ,IAAI,CAAA,yCAAA,CAA2C,CAAA;AACvD,EAAA,MAAM,SAAS,MAAM,UAAA,CAAW,OAAO,OAAA,EAAS,MAAA,CAAO,SAAS,GAAG,CAAA;AACnE,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,6BAAA,EAAgC,MAAA,CAAO,MAAM,CAAA,SAAA,CAAW,CAAA;AAEpE,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,uDAAA,EAA0D,MAAA,CAAO,QAAQ,CAAA,CAAE,CAAA;AACvF,EAAA,MAAM,QAAA,GAAW,MAAM,aAAA,CAAc,MAAA,EAAQ;AAAA,IAC5C,UAAU,MAAA,CAAO,QAAA;AAAA,IACjB,WAAW,MAAA,CAAO,SAAA;AAAA,IAClB,OAAO,MAAA,CAAO,KAAA;AAAA,IACd,UAAU,MAAA,CAAO;AAAA,GACjB,CAAA;AAED,EAAA,MAAM,YAAY,QAAA,CAAS,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,SAAS,CAAA,CAAE,MAAA;AACtD,EAAA,MAAM,aAAa,QAAA,CAAS,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,UAAU,CAAA,CAAE,MAAA;AACxD,EAAA,OAAA,CAAQ,GAAA;AAAA,IACP,0BAA0B,QAAA,CAAS,MAAM,qBAAqB,SAAS,CAAA,aAAA,EAAgB,aAAa,SAAS,CAAA,aAAA;AAAA,GAC9G;AAEA,EAAA,MAAM,IAAA,GAAO,YAAA,CAAa,MAAA,EAAQ,QAAQ,CAAA;AAC1C,EAAA,gBAAA,CAAiB,MAAA,EAAQ,MAAM,GAAG,CAAA;AAElC,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,uCAAA,EAA0C,MAAA,CAAO,MAAA,CAAO,QAAQ,CAAA,CAAE,CAAA;AAC9E,EAAA,IAAI,MAAA,CAAO,OAAO,UAAA,EAAY;AAC7B,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,8CAAA,EAAiD,MAAA,CAAO,MAAA,CAAO,UAAU,CAAA,CAAE,CAAA;AAAA,EACxF;AAEA,EAAA,OAAO;AAAA,IACN,gBAAgB,QAAA,CAAS,MAAA;AAAA,IACzB,eAAA,EAAiB,SAAA;AAAA,IACjB,gBAAA,EAAkB,UAAA;AAAA,IAClB,QAAA,EAAU,OAAO,MAAA,CAAO;AAAA,GACzB;AACD","file":"index.js","sourcesContent":["import { createHash } from 'crypto';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';\nimport { join } from 'path';\n\nexport interface CacheEntry {\n\thash: string;\n\tpathItem: Record<string, unknown>;\n\tcachedAt: string;\n}\n\nexport function computeHash(content: string, provider: string, modelId: string): string {\n\treturn createHash('sha256').update(content).update(provider).update(modelId).digest('hex');\n}\n\nexport class RouteCache {\n\tprivate readonly cacheDir: string;\n\n\tconstructor(cacheDir: string) {\n\t\tthis.cacheDir = cacheDir;\n\t}\n\n\tprivate ensureDir(): void {\n\t\tif (!existsSync(this.cacheDir)) {\n\t\t\tmkdirSync(this.cacheDir, { recursive: true });\n\t\t}\n\t}\n\n\tget(hash: string): Record<string, unknown> | null {\n\t\tconst filePath = join(this.cacheDir, `${hash}.json`);\n\t\tif (!existsSync(filePath)) return null;\n\t\ttry {\n\t\t\tconst entry: CacheEntry = JSON.parse(readFileSync(filePath, 'utf8'));\n\t\t\treturn entry.pathItem;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tset(hash: string, pathItem: Record<string, unknown>): void {\n\t\tthis.ensureDir();\n\t\tconst entry: CacheEntry = {\n\t\t\thash,\n\t\t\tpathItem,\n\t\t\tcachedAt: new Date().toISOString(),\n\t\t};\n\t\tconst filePath = join(this.cacheDir, `${hash}.json`);\n\t\twriteFileSync(filePath, JSON.stringify(entry, null, 2), 'utf8');\n\t}\n}\n","import type { LanguageModel } from 'ai';\n\nimport type { Provider } from '../config.js';\n\nexport function createModel(provider: Provider): LanguageModel {\n\tswitch (provider) {\n\t\tcase 'azure':\n\t\t\treturn createAzureModel();\n\t\tcase 'openai':\n\t\t\treturn createOpenAIModel();\n\t\tcase 'anthropic':\n\t\t\treturn createAnthropicModel();\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = provider;\n\t\t\tthrow new Error(`Unknown provider: ${_exhaustive}`);\n\t\t}\n\t}\n}\n\nfunction createAzureModel(): LanguageModel {\n\tconst endpoint = requireEnv('AZURE_OPENAI_ENDPOINT');\n\tconst apiKey = requireEnv('AZURE_OPENAI_API_KEY');\n\tconst deployment = requireEnv('AZURE_OPENAI_DEPLOYMENT');\n\n\t// Dynamic import to avoid loading unused provider SDKs\n\tconst { createAzure } = require('@ai-sdk/azure');\n\tconst azure = createAzure({ endpoint, apiKey });\n\treturn azure(deployment);\n}\n\nfunction createOpenAIModel(): LanguageModel {\n\tconst apiKey = requireEnv('OPENAI_API_KEY');\n\tconst model = process.env.OPENAI_MODEL ?? 'gpt-4o';\n\n\tconst { createOpenAI } = require('@ai-sdk/openai');\n\tconst openai = createOpenAI({ apiKey });\n\treturn openai(model);\n}\n\nfunction createAnthropicModel(): LanguageModel {\n\tconst apiKey = requireEnv('ANTHROPIC_API_KEY');\n\tconst model = process.env.ANTHROPIC_MODEL ?? 'claude-sonnet-4-6';\n\n\tconst { createAnthropic } = require('@ai-sdk/anthropic');\n\tconst anthropic = createAnthropic({ apiKey });\n\treturn anthropic(model);\n}\n\nfunction requireEnv(name: string): string {\n\tconst val = process.env[name];\n\tif (!val) {\n\t\tthrow new Error(`Required environment variable ${name} is not set.`);\n\t}\n\treturn val;\n}\n\nexport function getModelId(provider: Provider): string {\n\tswitch (provider) {\n\t\tcase 'azure':\n\t\t\treturn process.env.AZURE_OPENAI_DEPLOYMENT ?? 'unknown';\n\t\tcase 'openai':\n\t\t\treturn process.env.OPENAI_MODEL ?? 'gpt-4o';\n\t\tcase 'anthropic':\n\t\t\treturn process.env.ANTHROPIC_MODEL ?? 'claude-sonnet-4-6';\n\t}\n}\n","import type { LanguageModel } from 'ai';\nimport { generateText } from 'ai';\n\nimport type { JSDocMode, Provider } from './config.js';\nimport type { RouteInfo } from './scanner.js';\n\nimport { computeHash, RouteCache } from './cache.js';\nimport { createModel, getModelId } from './providers/index.js';\n\nexport interface AnalyzeOptions {\n\tprovider: Provider;\n\tjsdocMode: JSDocMode;\n\tcache: boolean;\n\tcacheDir: string;\n}\n\nexport interface AnalyzedRoute {\n\turlPath: string;\n\tpathItem: Record<string, unknown>;\n\tfromCache: boolean;\n\tskippedLLM: boolean;\n}\n\nexport async function analyzeRoutes(\n\troutes: RouteInfo[],\n\toptions: AnalyzeOptions,\n): Promise<AnalyzedRoute[]> {\n\tconst modelId = getModelId(options.provider);\n\tconst cache = options.cache ? new RouteCache(options.cacheDir) : null;\n\n\t// Lazy-init the model only when we actually need it\n\tlet model: LanguageModel | null = null;\n\tconst getModel = (): LanguageModel => {\n\t\tif (!model) model = createModel(options.provider);\n\t\treturn model;\n\t};\n\n\tconst results: AnalyzedRoute[] = [];\n\n\tfor (const route of routes) {\n\t\tconst result = await analyzeRoute(route, options, modelId, cache, getModel);\n\t\tresults.push(result);\n\t}\n\n\treturn results;\n}\n\nasync function analyzeRoute(\n\troute: RouteInfo,\n\toptions: AnalyzeOptions,\n\tmodelId: string,\n\tcache: RouteCache | null,\n\tgetModel: () => LanguageModel,\n): Promise<AnalyzedRoute> {\n\t// If @openapi-exact is present or jsdocMode is 'exact', skip LLM\n\tif (route.hasExactJsdoc && route.exactPathItem) {\n\t\treturn {\n\t\t\turlPath: route.urlPath,\n\t\t\tpathItem: route.exactPathItem,\n\t\t\tfromCache: false,\n\t\t\tskippedLLM: true,\n\t\t};\n\t}\n\n\tif (options.jsdocMode === 'exact') {\n\t\t// In exact mode, if there's a @openapi tag, use it; otherwise still use LLM\n\t\tif (route.hasExactJsdoc && route.exactPathItem) {\n\t\t\treturn {\n\t\t\t\turlPath: route.urlPath,\n\t\t\t\tpathItem: route.exactPathItem,\n\t\t\t\tfromCache: false,\n\t\t\t\tskippedLLM: true,\n\t\t\t};\n\t\t}\n\t}\n\n\t// Compute cache hash\n\tconst hash = computeHash(route.sourceCode, options.provider, modelId);\n\n\t// Check cache\n\tif (cache) {\n\t\tconst cached = cache.get(hash);\n\t\tif (cached) {\n\t\t\treturn {\n\t\t\t\turlPath: route.urlPath,\n\t\t\t\tpathItem: cached,\n\t\t\t\tfromCache: true,\n\t\t\t\tskippedLLM: true,\n\t\t\t};\n\t\t}\n\t}\n\n\t// Call LLM\n\tlet pathItem: Record<string, unknown>;\n\ttry {\n\t\tpathItem = await callLLM(route, options.jsdocMode, getModel());\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tconst isContentFilter =\n\t\t\tmessage.includes('content filtering policy') ||\n\t\t\tmessage.includes('content_filter') ||\n\t\t\t(err as { status?: number })?.status === 400;\n\n\t\tif (isContentFilter) {\n\t\t\tconsole.warn(\n\t\t\t\t`Warning: Content filter blocked response for ${route.urlPath}. ` +\n\t\t\t\t\t`Skipping route. Use @openapi-exact JSDoc to provide the spec manually.`,\n\t\t\t);\n\t\t\treturn {\n\t\t\t\turlPath: route.urlPath,\n\t\t\t\tpathItem: {},\n\t\t\t\tfromCache: false,\n\t\t\t\tskippedLLM: false,\n\t\t\t};\n\t\t}\n\t\tthrow err;\n\t}\n\n\t// Store in cache\n\tif (cache) {\n\t\tcache.set(hash, pathItem);\n\t}\n\n\treturn {\n\t\turlPath: route.urlPath,\n\t\tpathItem,\n\t\tfromCache: false,\n\t\tskippedLLM: false,\n\t};\n}\n\nfunction buildPrompt(route: RouteInfo, jsdocMode: JSDocMode): string {\n\tconst jsDocSection =\n\t\troute.jsdocComments.length > 0\n\t\t\t? `JSDoc COMMENTS (use as ${jsdocMode === 'context' ? 'additional context' : 'primary source'}):\\n${route.jsdocComments.join('\\n\\n')}`\n\t\t\t: 'No JSDoc comments found.';\n\n\treturn `You are a technical documentation tool that reads existing source code and produces OpenAPI 3.1 documentation data. You do not write or execute code — you only read and describe it.\n\n## Task\n\nRead the Next.js API route source file below and produce a JSON object that documents its HTTP endpoints according to the OpenAPI 3.1 PathItem schema.\n\n## Route metadata\n\n- File: ${route.relativePath}\n- URL path: ${route.urlPath}\n\n## Source file contents\n\n\\`\\`\\`typescript\n${route.sourceCode}\n\\`\\`\\`\n\n${jsDocSection}\n\n## Instructions\n\nFor each exported function named GET, POST, PUT, PATCH, or DELETE, document:\n- operationId: a unique camelCase identifier\n- summary: a short one-line description\n- description: a fuller explanation of what the endpoint does\n- parameters: path params from URL segments like {id}, and query params from searchParams usage\n- requestBody: schema inferred from request.json() calls and TypeScript types (POST/PUT/PATCH only)\n- responses: per status code, inferred from NextResponse.json() calls and return type annotations\n- tags: inferred from the URL path segments\n- security: noted if the code checks for auth tokens, session cookies, or middleware guards\n\n## Output format\n\nReturn a single raw JSON object matching the OpenAPI 3.1 PathItem schema. No explanation, no markdown fences, no extra text — only the JSON object.`;\n}\n\nasync function callLLM(\n\troute: RouteInfo,\n\tjsdocMode: JSDocMode,\n\tmodel: LanguageModel,\n): Promise<Record<string, unknown>> {\n\tconst prompt = buildPrompt(route, jsdocMode);\n\n\tconst { text } = await generateText({\n\t\tmodel,\n\t\tprompt,\n\t\ttemperature: 0,\n\t});\n\n\treturn parsePathItem(text, route.urlPath);\n}\n\nfunction parsePathItem(text: string, urlPath: string): Record<string, unknown> {\n\t// Strip any accidental markdown code fences\n\tlet json = text.trim();\n\tif (json.startsWith('```')) {\n\t\tjson = json\n\t\t\t.replace(/^```(?:json)?\\s*/i, '')\n\t\t\t.replace(/\\s*```$/, '')\n\t\t\t.trim();\n\t}\n\n\ttry {\n\t\tconst parsed = JSON.parse(json);\n\t\tif (typeof parsed !== 'object' || Array.isArray(parsed) || parsed === null) {\n\t\t\tthrow new Error('Response is not a JSON object');\n\t\t}\n\t\treturn parsed;\n\t} catch (err) {\n\t\tconsole.warn(\n\t\t\t`Warning: Failed to parse LLM response for ${urlPath}. Using empty PathItem.`,\n\t\t\terr,\n\t\t);\n\t\treturn {};\n\t}\n}\n","import { existsSync } from 'fs';\nimport { resolve } from 'path';\nimport { pathToFileURL } from 'url';\n\nexport type Provider = 'azure' | 'openai' | 'anthropic';\nexport type JSDocMode = 'context' | 'exact';\n\nexport interface OpenAPIGenConfig {\n\tprovider: Provider;\n\toutput: {\n\t\tspecPath: string;\n\t\tscalarDocs?: boolean;\n\t\tscalarPath?: string;\n\t};\n\topenapi: {\n\t\ttitle: string;\n\t\tversion: string;\n\t\tdescription?: string;\n\t\tservers?: Array<{ url: string; description?: string }>;\n\t};\n\tjsdocMode?: JSDocMode;\n\tcache?: boolean;\n\tcacheDir?: string;\n\tinclude?: string[];\n\texclude?: string[];\n}\n\nexport interface ResolvedConfig extends Required<OpenAPIGenConfig> {\n\toutput: Required<OpenAPIGenConfig['output']>;\n\topenapi: Required<OpenAPIGenConfig['openapi']>;\n}\n\nconst defaults: Omit<ResolvedConfig, 'provider' | 'output' | 'openapi'> = {\n\tjsdocMode: 'context',\n\tcache: true,\n\tcacheDir: '.openapi-cache',\n\tinclude: ['src/app/api/**/route.ts'],\n\texclude: [],\n};\n\nexport function resolveConfig(config: OpenAPIGenConfig): ResolvedConfig {\n\treturn {\n\t\t...defaults,\n\t\t...config,\n\t\toutput: {\n\t\t\tscalarDocs: false,\n\t\t\tscalarPath: 'src/app/api/docs/route.ts',\n\t\t\t...config.output,\n\t\t},\n\t\topenapi: {\n\t\t\tdescription: '',\n\t\t\tservers: [],\n\t\t\t...config.openapi,\n\t\t},\n\t};\n}\n\nexport async function loadConfig(configPath?: string): Promise<ResolvedConfig> {\n\tconst searchPaths = configPath\n\t\t? [configPath]\n\t\t: [\n\t\t\t\t'openapi-gen.config.ts',\n\t\t\t\t'openapi-gen.config.js',\n\t\t\t\t'openapi-gen.config.mjs',\n\t\t\t\t'openapi-gen.config.cjs',\n\t\t\t];\n\n\tfor (const p of searchPaths) {\n\t\tconst abs = resolve(process.cwd(), p);\n\t\tif (existsSync(abs)) {\n\t\t\tconst mod = await importConfig(abs);\n\t\t\tconst config: OpenAPIGenConfig = mod.default ?? mod;\n\t\t\treturn resolveConfig(config);\n\t\t}\n\t}\n\n\tthrow new Error('No openapi-gen.config.ts found. Create one at your project root.');\n}\n\nasync function importConfig(\n\tfilePath: string,\n): Promise<{ default?: OpenAPIGenConfig } & OpenAPIGenConfig> {\n\t// For .ts files, try to use tsx/ts-node if available, else fall back to require\n\tif (filePath.endsWith('.ts')) {\n\t\treturn importTypeScriptConfig(filePath);\n\t}\n\tconst url = pathToFileURL(filePath).href;\n\treturn import(url);\n}\n\nasync function importTypeScriptConfig(\n\tfilePath: string,\n): Promise<{ default?: OpenAPIGenConfig } & OpenAPIGenConfig> {\n\t// Try dynamic import with tsx register if available\n\ttry {\n\t\t// Check if tsx is available\n\t\tawait import('module');\n\t\t// Use tsx/ts-node loader\n\t\tconst url = pathToFileURL(filePath).href;\n\t\treturn await import(url);\n\t} catch {\n\t\t// Fall back: try require with ts-node\n\t\ttry {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-require-imports\n\t\t\trequire('ts-node/register');\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-require-imports\n\t\t\treturn require(filePath);\n\t\t} catch {\n\t\t\tthrow new Error(\n\t\t\t\t`Cannot load TypeScript config file: ${filePath}. ` +\n\t\t\t\t\t'Install tsx or ts-node, or use a .js config file.',\n\t\t\t);\n\t\t}\n\t}\n}\n","import { existsSync, mkdirSync, writeFileSync } from 'fs';\nimport { dirname, join, resolve } from 'path';\n\nimport type { AnalyzedRoute } from './analyzer.js';\nimport type { ResolvedConfig } from './config.js';\n\nexport interface OpenAPISpec {\n\topenapi: '3.1.0';\n\tinfo: {\n\t\ttitle: string;\n\t\tversion: string;\n\t\tdescription?: string;\n\t};\n\tservers?: Array<{ url: string; description?: string }>;\n\tpaths: Record<string, unknown>;\n}\n\nexport function assembleSpec(config: ResolvedConfig, routes: AnalyzedRoute[]): OpenAPISpec {\n\tconst paths: Record<string, unknown> = {};\n\n\tfor (const route of routes) {\n\t\tif (Object.keys(route.pathItem).length > 0) {\n\t\t\tpaths[route.urlPath] = route.pathItem;\n\t\t}\n\t}\n\n\tconst spec: OpenAPISpec = {\n\t\topenapi: '3.1.0',\n\t\tinfo: {\n\t\t\ttitle: config.openapi.title,\n\t\t\tversion: config.openapi.version,\n\t\t\t...(config.openapi.description ? { description: config.openapi.description } : {}),\n\t\t},\n\t\tpaths,\n\t};\n\n\tif (config.openapi.servers && config.openapi.servers.length > 0) {\n\t\tspec.servers = config.openapi.servers;\n\t}\n\n\treturn spec;\n}\n\nexport function writeOutputFiles(\n\tconfig: ResolvedConfig,\n\tspec: OpenAPISpec,\n\tcwd: string = process.cwd(),\n): void {\n\twriteSpecFiles(config, spec, cwd);\n\n\tif (config.output.scalarDocs) {\n\t\twriteScalarRoute(config, cwd);\n\t}\n}\n\nfunction writeSpecFiles(config: ResolvedConfig, spec: OpenAPISpec, cwd: string): void {\n\tconst specRoutePath = resolve(cwd, config.output.specPath);\n\tconst specDir = dirname(specRoutePath);\n\n\tensureDir(specDir);\n\n\t// Write spec.json co-located with the route\n\tconst specJsonPath = join(specDir, 'spec.json');\n\twriteFileSync(specJsonPath, JSON.stringify(spec, null, 2), 'utf8');\n\n\t// Write the Next.js route that serves the spec\n\tconst routeContent = `import spec from './spec.json';\n\nexport const dynamic = 'force-static';\n\nexport function GET() {\n return Response.json(spec);\n}\n`;\n\twriteFileSync(specRoutePath, routeContent, 'utf8');\n}\n\nfunction writeScalarRoute(config: ResolvedConfig, cwd: string): void {\n\tconst scalarRoutePath = resolve(cwd, config.output.scalarPath);\n\tensureDir(dirname(scalarRoutePath));\n\n\t// Derive the spec URL from the specPath\n\tconst specUrl = filePathToApiUrl(config.output.specPath);\n\n\tconst routeContent = `export const dynamic = 'force-static';\n\nexport function GET() {\n return new Response(\n \\`<!doctype html>\n<html>\n <head>\n <title>API Docs</title>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n </head>\n <body>\n <script\n id=\"api-reference\"\n data-url=\"${specUrl}\"\n ></script>\n <script src=\"https://cdn.jsdelivr.net/npm/@scalar/api-reference\"></script>\n </body>\n</html>\\`,\n { headers: { 'Content-Type': 'text/html' } }\n );\n}\n`;\n\twriteFileSync(scalarRoutePath, routeContent, 'utf8');\n}\n\n/**\n * Convert a file path like src/app/api/openapi.json/route.ts to /api/openapi.json\n */\nfunction filePathToApiUrl(filePath: string): string {\n\tlet path = filePath.replace(/\\\\/g, '/');\n\tpath = path.replace(/^(src\\/)?app\\//, '');\n\tpath = path.replace(/\\/route\\.(ts|tsx|js|jsx)$/, '');\n\tif (!path.startsWith('/')) path = `/${path}`;\n\treturn path;\n}\n\nfunction ensureDir(dir: string): void {\n\tif (!existsSync(dir)) {\n\t\tmkdirSync(dir, { recursive: true });\n\t}\n}\n","import { readFileSync } from 'fs';\nimport { relative } from 'path';\n\nexport interface RouteInfo {\n\tfilePath: string;\n\trelativePath: string;\n\turlPath: string;\n\tsourceCode: string;\n\tjsdocComments: string[];\n\thasExactJsdoc: boolean;\n\texactPathItem?: Record<string, unknown>;\n}\n\nexport async function scanRoutes(\n\tinclude: string[],\n\texclude: string[],\n\tcwd: string = process.cwd(),\n): Promise<RouteInfo[]> {\n\t// Lazy import so that importing this module does not eagerly load fast-glob.\n\t// This keeps the module lightweight and allows Vitest to mock it cleanly.\n\tconst { default: fg } = await import('fast-glob');\n\tconst files = await fg(include, {\n\t\tcwd,\n\t\tignore: exclude,\n\t\tabsolute: true,\n\t});\n\n\treturn files.map((filePath) => parseRoute(filePath, cwd));\n}\n\nfunction parseRoute(filePath: string, cwd: string): RouteInfo {\n\tconst relativePath = relative(cwd, filePath);\n\tconst sourceCode = readFileSync(filePath, 'utf8');\n\tconst urlPath = filePathToUrlPath(relativePath);\n\tconst { jsdocComments, hasExactJsdoc, exactPathItem } = extractJsdoc(sourceCode);\n\n\treturn {\n\t\tfilePath,\n\t\trelativePath,\n\t\turlPath,\n\t\tsourceCode,\n\t\tjsdocComments,\n\t\thasExactJsdoc,\n\t\texactPathItem,\n\t};\n}\n\n/**\n * Convert a Next.js route file path to an OpenAPI URL path.\n * e.g. src/app/api/users/[id]/route.ts -> /api/users/{id}\n */\nexport function filePathToUrlPath(filePath: string): string {\n\t// Normalize separators\n\tlet path = filePath.replace(/\\\\/g, '/');\n\n\t// Remove leading src/ or app/ prefixes\n\tpath = path.replace(/^(src\\/)?app\\//, '');\n\n\t// Remove trailing /route.ts or /route.js\n\tpath = path.replace(/\\/route\\.(ts|tsx|js|jsx)$/, '');\n\n\t// Convert Next.js dynamic segments [param] to OpenAPI {param}\n\tpath = path.replace(/\\[([^\\]]+)\\]/g, (_, param) => {\n\t\t// Handle catch-all [...param] and optional [[...param]]\n\t\tif (param.startsWith('...')) {\n\t\t\treturn `{${param.slice(3)}}`;\n\t\t}\n\t\treturn `{${param}}`;\n\t});\n\n\t// Ensure leading slash\n\tif (!path.startsWith('/')) {\n\t\tpath = `/${path}`;\n\t}\n\n\treturn path;\n}\n\ninterface JsdocResult {\n\tjsdocComments: string[];\n\thasExactJsdoc: boolean;\n\texactPathItem?: Record<string, unknown>;\n}\n\nfunction extractJsdoc(sourceCode: string): JsdocResult {\n\t// Match all JSDoc comment blocks /** ... */\n\tconst jsdocRegex = /\\/\\*\\*([\\s\\S]*?)\\*\\//g;\n\tconst jsdocComments: string[] = [];\n\tlet hasExactJsdoc = false;\n\tlet exactPathItem: Record<string, unknown> | undefined;\n\n\tconst match: RegExpExecArray | null = jsdocRegex.exec(sourceCode);\n\twhile (match !== null) {\n\t\tconst comment = match[0];\n\t\tjsdocComments.push(comment);\n\n\t\t// Check for @openapi-exact tag\n\t\tif (/@openapi-exact/.test(comment)) {\n\t\t\thasExactJsdoc = true;\n\t\t\t// Try to extract the JSON from @openapi tag\n\t\t\tconst openapiMatch = comment.match(/@openapi\\s+([\\s\\S]*?)(?=\\s*\\*\\/|\\s*\\*\\s*@)/);\n\t\t\tif (openapiMatch) {\n\t\t\t\ttry {\n\t\t\t\t\t// Clean up JSDoc asterisks from the JSON\n\t\t\t\t\tconst jsonStr = openapiMatch[1]\n\t\t\t\t\t\t.split('\\n')\n\t\t\t\t\t\t.map((line) => line.replace(/^\\s*\\*\\s?/, ''))\n\t\t\t\t\t\t.join('\\n')\n\t\t\t\t\t\t.trim();\n\t\t\t\t\texactPathItem = JSON.parse(jsonStr);\n\t\t\t\t} catch {\n\t\t\t\t\t// If JSON parse fails, fall through to LLM\n\t\t\t\t\thasExactJsdoc = false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { jsdocComments, hasExactJsdoc, exactPathItem };\n}\n","export type { JSDocMode, OpenAPIGenConfig, Provider, ResolvedConfig } from './config.js';\n\nexport { analyzeRoutes } from './analyzer.js';\nexport { loadConfig, resolveConfig } from './config.js';\nexport { assembleSpec, writeOutputFiles } from './generator.js';\nexport { filePathToUrlPath, scanRoutes } from './scanner.js';\n\nimport type { OpenAPIGenConfig } from './config.js';\n\nimport { analyzeRoutes } from './analyzer.js';\nimport { loadConfig } from './config.js';\nimport { assembleSpec, writeOutputFiles } from './generator.js';\nimport { scanRoutes } from './scanner.js';\n\nexport interface GenerateOptions {\n\tconfig?: string;\n\tprovider?: OpenAPIGenConfig['provider'];\n\tcache?: boolean;\n\tcwd?: string;\n}\n\nexport interface GenerateResult {\n\troutesAnalyzed: number;\n\troutesFromCache: number;\n\troutesSkippedLLM: number;\n\tspecPath: string;\n}\n\nexport async function generate(options: GenerateOptions = {}): Promise<GenerateResult> {\n\tconst cwd = options.cwd ?? process.cwd();\n\tconst config = await loadConfig(options.config);\n\n\t// Allow CLI overrides\n\tif (options.provider) config.provider = options.provider;\n\tif (options.cache === false) config.cache = false;\n\n\tconsole.log(`[openapi-ai-generator] Scanning routes...`);\n\tconst routes = await scanRoutes(config.include, config.exclude, cwd);\n\tconsole.log(`[openapi-ai-generator] Found ${routes.length} route(s)`);\n\n\tconsole.log(`[openapi-ai-generator] Analyzing routes with provider: ${config.provider}`);\n\tconst analyzed = await analyzeRoutes(routes, {\n\t\tprovider: config.provider,\n\t\tjsdocMode: config.jsdocMode,\n\t\tcache: config.cache,\n\t\tcacheDir: config.cacheDir,\n\t});\n\n\tconst fromCache = analyzed.filter((r) => r.fromCache).length;\n\tconst skippedLLM = analyzed.filter((r) => r.skippedLLM).length;\n\tconsole.log(\n\t\t`[openapi-ai-generator] ${analyzed.length} routes analyzed (${fromCache} from cache, ${skippedLLM - fromCache} exact JSDoc)`,\n\t);\n\n\tconst spec = assembleSpec(config, analyzed);\n\twriteOutputFiles(config, spec, cwd);\n\n\tconsole.log(`[openapi-ai-generator] Spec written to ${config.output.specPath}`);\n\tif (config.output.scalarDocs) {\n\t\tconsole.log(`[openapi-ai-generator] Scalar docs written to ${config.output.scalarPath}`);\n\t}\n\n\treturn {\n\t\troutesAnalyzed: analyzed.length,\n\t\troutesFromCache: fromCache,\n\t\troutesSkippedLLM: skippedLLM,\n\t\tspecPath: config.output.specPath,\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/cache.ts","../src/providers/index.ts","../src/analyzer.ts","../src/config.ts","../src/generator.ts","../src/scanner.ts","../src/index.ts"],"names":["createHash","existsSync","mkdirSync","join","readFileSync","writeFileSync","generateText","resolve","url","pathToFileURL","dirname","relative"],"mappings":"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","file":"index.js","sourcesContent":["import { createHash } from 'crypto';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';\nimport { join } from 'path';\n\nexport interface CacheEntry {\n\thash: string;\n\tpathItem: Record<string, unknown>;\n\tcachedAt: string;\n}\n\nexport function computeHash(content: string, provider: string, modelId: string): string {\n\treturn createHash('sha256').update(content).update(provider).update(modelId).digest('hex');\n}\n\nexport class RouteCache {\n\tprivate readonly cacheDir: string;\n\n\tconstructor(cacheDir: string) {\n\t\tthis.cacheDir = cacheDir;\n\t}\n\n\tprivate ensureDir(): void {\n\t\tif (!existsSync(this.cacheDir)) {\n\t\t\tmkdirSync(this.cacheDir, { recursive: true });\n\t\t}\n\t}\n\n\tget(hash: string): Record<string, unknown> | null {\n\t\tconst filePath = join(this.cacheDir, `${hash}.json`);\n\t\tif (!existsSync(filePath)) return null;\n\t\ttry {\n\t\t\tconst entry: CacheEntry = JSON.parse(readFileSync(filePath, 'utf8'));\n\t\t\treturn entry.pathItem;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tset(hash: string, pathItem: Record<string, unknown>): void {\n\t\tthis.ensureDir();\n\t\tconst entry: CacheEntry = {\n\t\t\thash,\n\t\t\tpathItem,\n\t\t\tcachedAt: new Date().toISOString(),\n\t\t};\n\t\tconst filePath = join(this.cacheDir, `${hash}.json`);\n\t\twriteFileSync(filePath, JSON.stringify(entry, null, 2), 'utf8');\n\t}\n}\n","import type { LanguageModel } from \"ai\";\n\nimport type { Provider } from \"../config.js\";\n\nexport function createModel(provider: Provider): LanguageModel {\n switch (provider) {\n case \"azure\":\n return createAzureModel();\n case \"openai\":\n return createOpenAIModel();\n case \"anthropic\":\n return createAnthropicModel();\n default: {\n const _exhaustive: never = provider;\n throw new Error(`Unknown provider: ${_exhaustive}`);\n }\n }\n}\n\nfunction createAzureModel(): LanguageModel {\n const endpoint = requireEnv(\"AZURE_OPENAI_ENDPOINT\");\n const resourceName = endpoint?.match(/https?:\\/\\/([^.]+)/)?.[1];\n const apiKey = requireEnv(\"AZURE_OPENAI_API_KEY\");\n const deployment = requireEnv(\"AZURE_OPENAI_DEPLOYMENT\");\n\n // Dynamic import to avoid loading unused provider SDKs\n const { createAzure } = require(\"@ai-sdk/azure\");\n const azure = createAzure({ resourceName, apiKey });\n return azure(deployment);\n}\n\nfunction createOpenAIModel(): LanguageModel {\n const apiKey = requireEnv(\"OPENAI_API_KEY\");\n const model = process.env.OPENAI_MODEL ?? \"gpt-4o\";\n\n const { createOpenAI } = require(\"@ai-sdk/openai\");\n const openai = createOpenAI({ apiKey });\n return openai(model);\n}\n\nfunction createAnthropicModel(): LanguageModel {\n const apiKey = requireEnv(\"ANTHROPIC_API_KEY\");\n const model = process.env.ANTHROPIC_MODEL ?? \"claude-sonnet-4-6\";\n\n const { createAnthropic } = require(\"@ai-sdk/anthropic\");\n const anthropic = createAnthropic({ apiKey });\n return anthropic(model);\n}\n\nfunction requireEnv(name: string): string {\n const val = process.env[name];\n if (!val) {\n throw new Error(`Required environment variable ${name} is not set.`);\n }\n return val;\n}\n\nexport function getModelId(provider: Provider): string {\n switch (provider) {\n case \"azure\":\n return process.env.AZURE_OPENAI_DEPLOYMENT ?? \"unknown\";\n case \"openai\":\n return process.env.OPENAI_MODEL ?? \"gpt-4o\";\n case \"anthropic\":\n return process.env.ANTHROPIC_MODEL ?? \"claude-sonnet-4-6\";\n }\n}\n","import type { LanguageModel } from \"ai\";\nimport { generateText } from \"ai\";\n\nimport type { JSDocMode, Provider } from \"./config.js\";\nimport type { RouteInfo } from \"./scanner.js\";\n\nimport { computeHash, RouteCache } from \"./cache.js\";\nimport { createModel, getModelId } from \"./providers/index.js\";\n\nexport interface AnalyzeOptions {\n provider: Provider;\n jsdocMode: JSDocMode;\n cache: boolean;\n cacheDir: string;\n}\n\nexport interface AnalyzedRoute {\n urlPath: string;\n pathItem: Record<string, unknown>;\n fromCache: boolean;\n skippedLLM: boolean;\n}\n\nexport async function analyzeRoutes(\n routes: RouteInfo[],\n options: AnalyzeOptions,\n): Promise<AnalyzedRoute[]> {\n const modelId = getModelId(options.provider);\n const cache = options.cache ? new RouteCache(options.cacheDir) : null;\n\n // Lazy-init the model only when we actually need it\n let model: LanguageModel | null = null;\n const getModel = (): LanguageModel => {\n if (!model) model = createModel(options.provider);\n return model;\n };\n\n const results: AnalyzedRoute[] = [];\n\n for (const route of routes) {\n const result = await analyzeRoute(route, options, modelId, cache, getModel);\n results.push(result);\n }\n\n return results;\n}\n\nasync function analyzeRoute(\n route: RouteInfo,\n options: AnalyzeOptions,\n modelId: string,\n cache: RouteCache | null,\n getModel: () => LanguageModel,\n): Promise<AnalyzedRoute> {\n // If @openapi-exact is present or jsdocMode is 'exact', skip LLM\n if (route.hasExactJsdoc && route.exactPathItem) {\n return {\n urlPath: route.urlPath,\n pathItem: route.exactPathItem,\n fromCache: false,\n skippedLLM: true,\n };\n }\n\n if (options.jsdocMode === \"exact\") {\n // In exact mode, if there's a @openapi tag, use it; otherwise still use LLM\n if (route.hasExactJsdoc && route.exactPathItem) {\n return {\n urlPath: route.urlPath,\n pathItem: route.exactPathItem,\n fromCache: false,\n skippedLLM: true,\n };\n }\n }\n\n // Compute cache hash\n const hash = computeHash(route.sourceCode, options.provider, modelId);\n\n // Check cache\n if (cache) {\n const cached = cache.get(hash);\n if (cached) {\n return {\n urlPath: route.urlPath,\n pathItem: cached,\n fromCache: true,\n skippedLLM: true,\n };\n }\n }\n\n // Call LLM\n let pathItem: Record<string, unknown>;\n try {\n pathItem = await callLLM(route, options.jsdocMode, getModel());\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const isContentFilter =\n message.includes(\"content filtering policy\") ||\n message.includes(\"content_filter\") ||\n (err as { status?: number })?.status === 400;\n\n if (isContentFilter) {\n console.warn(\n `Warning: Content filter blocked response for ${route.urlPath}. ` +\n `Skipping route. Use @openapi-exact JSDoc to provide the spec manually.`,\n );\n return {\n urlPath: route.urlPath,\n pathItem: {},\n fromCache: false,\n skippedLLM: false,\n };\n }\n throw err;\n }\n\n // Store in cache\n if (cache) {\n cache.set(hash, pathItem);\n }\n\n return {\n urlPath: route.urlPath,\n pathItem,\n fromCache: false,\n skippedLLM: false,\n };\n}\n\nfunction buildPrompt(route: RouteInfo, jsdocMode: JSDocMode): string {\n const jsDocSection =\n route.jsdocComments.length > 0\n ? `JSDoc COMMENTS (use as ${jsdocMode === \"context\" ? \"additional context\" : \"primary source\"}):\\n${route.jsdocComments.join(\"\\n\\n\")}`\n : \"No JSDoc comments found.\";\n\n return `You are a technical documentation tool that reads existing source code and produces OpenAPI 3.1 documentation data. You do not write or execute code — you only read and describe it.\n\n## Task\n\nRead the Next.js API route source file below and produce a JSON object that documents its HTTP endpoints according to the OpenAPI 3.1 PathItem schema.\n\n## Route metadata\n\n- File: ${route.relativePath}\n- URL path: ${route.urlPath}\n\n## Source file contents\n\n\\`\\`\\`typescript\n${route.sourceCode}\n\\`\\`\\`\n\n${jsDocSection}\n\n## Instructions\n\nFor each exported function named GET, POST, PUT, PATCH, or DELETE, document:\n- operationId: a unique camelCase identifier\n- summary: a short one-line description\n- description: a fuller explanation of what the endpoint does\n- parameters: path params from URL segments like {id}, and query params from searchParams usage\n- requestBody: schema inferred from request.json() calls and TypeScript types (POST/PUT/PATCH only)\n- responses: per status code, inferred from NextResponse.json() calls and return type annotations\n- tags: inferred from the URL path segments\n- security: noted if the code checks for auth tokens, session cookies, or middleware guards\n\n## Output format\n\nReturn a single raw JSON object matching the OpenAPI 3.1 PathItem schema. No explanation, no markdown fences, no extra text — only the JSON object.`;\n}\n\nasync function callLLM(\n route: RouteInfo,\n jsdocMode: JSDocMode,\n model: LanguageModel,\n): Promise<Record<string, unknown>> {\n const prompt = buildPrompt(route, jsdocMode);\n\n const { text } = await generateText({\n model,\n prompt,\n temperature: 0,\n });\n\n return parsePathItem(text, route.urlPath);\n}\n\nfunction parsePathItem(text: string, urlPath: string): Record<string, unknown> {\n // Strip any accidental markdown code fences\n let json = text.trim();\n if (json.startsWith(\"```\")) {\n json = json\n .replace(/^```(?:json)?\\s*/i, \"\")\n .replace(/\\s*```$/, \"\")\n .trim();\n }\n\n try {\n const parsed = JSON.parse(json);\n if (\n typeof parsed !== \"object\" ||\n Array.isArray(parsed) ||\n parsed === null\n ) {\n throw new Error(\"Response is not a JSON object\");\n }\n return parsed;\n } catch (err) {\n console.warn(\n `Warning: Failed to parse LLM response for ${urlPath}. Using empty PathItem.`,\n err,\n );\n return {};\n }\n}\n","import { existsSync } from 'fs';\nimport { resolve } from 'path';\nimport { pathToFileURL } from 'url';\n\nexport type Provider = 'azure' | 'openai' | 'anthropic';\nexport type JSDocMode = 'context' | 'exact';\n\nexport interface OpenAPIGenConfig {\n\tprovider: Provider;\n\toutput: {\n\t\tspecPath: string;\n\t\tscalarDocs?: boolean;\n\t\tscalarPath?: string;\n\t};\n\topenapi: {\n\t\ttitle: string;\n\t\tversion: string;\n\t\tdescription?: string;\n\t\tservers?: Array<{ url: string; description?: string }>;\n\t};\n\tjsdocMode?: JSDocMode;\n\tcache?: boolean;\n\tcacheDir?: string;\n\tinclude?: string[];\n\texclude?: string[];\n\t/**\n\t * Path(s) to .env files to load before running. Defaults to ['.env', '.env.local'].\n\t * Set to false to disable automatic .env loading.\n\t */\n\tenvFile?: string | string[] | false;\n}\n\nexport interface ResolvedConfig extends Omit<Required<OpenAPIGenConfig>, 'envFile'> {\n\toutput: Required<OpenAPIGenConfig['output']>;\n\topenapi: Required<OpenAPIGenConfig['openapi']>;\n\tenvFile: string[] | false;\n}\n\nconst defaults: Omit<ResolvedConfig, 'provider' | 'output' | 'openapi'> = {\n\tjsdocMode: 'context',\n\tcache: true,\n\tcacheDir: '.openapi-cache',\n\tinclude: ['src/app/api/**/route.ts'],\n\texclude: [],\n\tenvFile: ['.env', '.env.local'],\n};\n\nexport function resolveConfig(config: OpenAPIGenConfig): ResolvedConfig {\n\tlet envFile: string[] | false;\n\tif (config.envFile === false) {\n\t\tenvFile = false;\n\t} else if (typeof config.envFile === 'string') {\n\t\tenvFile = [config.envFile];\n\t} else {\n\t\tenvFile = config.envFile ?? (defaults.envFile as string[]);\n\t}\n\n\treturn {\n\t\t...defaults,\n\t\t...config,\n\t\tenvFile,\n\t\toutput: {\n\t\t\tscalarDocs: false,\n\t\t\tscalarPath: 'src/app/api/docs/route.ts',\n\t\t\t...config.output,\n\t\t},\n\t\topenapi: {\n\t\t\tdescription: '',\n\t\t\tservers: [],\n\t\t\t...config.openapi,\n\t\t},\n\t};\n}\n\nexport async function loadConfig(configPath?: string): Promise<ResolvedConfig> {\n\tconst searchPaths = configPath\n\t\t? [configPath]\n\t\t: [\n\t\t\t\t'openapi-gen.config.ts',\n\t\t\t\t'openapi-gen.config.js',\n\t\t\t\t'openapi-gen.config.mjs',\n\t\t\t\t'openapi-gen.config.cjs',\n\t\t\t];\n\n\tfor (const p of searchPaths) {\n\t\tconst abs = resolve(process.cwd(), p);\n\t\tif (existsSync(abs)) {\n\t\t\tconst mod = await importConfig(abs);\n\t\t\tconst config: OpenAPIGenConfig = mod.default ?? mod;\n\t\t\treturn resolveConfig(config);\n\t\t}\n\t}\n\n\tthrow new Error('No openapi-gen.config.ts found. Create one at your project root.');\n}\n\nasync function importConfig(\n\tfilePath: string,\n): Promise<{ default?: OpenAPIGenConfig } & OpenAPIGenConfig> {\n\t// For .ts files, try to use tsx/ts-node if available, else fall back to require\n\tif (filePath.endsWith('.ts')) {\n\t\treturn importTypeScriptConfig(filePath);\n\t}\n\tconst url = pathToFileURL(filePath).href;\n\treturn import(url);\n}\n\nasync function importTypeScriptConfig(\n\tfilePath: string,\n): Promise<{ default?: OpenAPIGenConfig } & OpenAPIGenConfig> {\n\t// Use tsx (bundled as a dependency) to register CJS TypeScript hooks\n\t// eslint-disable-next-line @typescript-eslint/no-require-imports\n\trequire('tsx/cjs');\n\t// eslint-disable-next-line @typescript-eslint/no-require-imports\n\treturn require(filePath);\n}\n","import { existsSync, mkdirSync, writeFileSync } from 'fs';\nimport { dirname, join, resolve } from 'path';\n\nimport type { AnalyzedRoute } from './analyzer.js';\nimport type { ResolvedConfig } from './config.js';\n\nexport interface OpenAPISpec {\n\topenapi: '3.1.0';\n\tinfo: {\n\t\ttitle: string;\n\t\tversion: string;\n\t\tdescription?: string;\n\t};\n\tservers?: Array<{ url: string; description?: string }>;\n\tpaths: Record<string, unknown>;\n}\n\nexport function assembleSpec(config: ResolvedConfig, routes: AnalyzedRoute[]): OpenAPISpec {\n\tconst paths: Record<string, unknown> = {};\n\n\tfor (const route of routes) {\n\t\tif (Object.keys(route.pathItem).length > 0) {\n\t\t\tpaths[route.urlPath] = route.pathItem;\n\t\t}\n\t}\n\n\tconst spec: OpenAPISpec = {\n\t\topenapi: '3.1.0',\n\t\tinfo: {\n\t\t\ttitle: config.openapi.title,\n\t\t\tversion: config.openapi.version,\n\t\t\t...(config.openapi.description ? { description: config.openapi.description } : {}),\n\t\t},\n\t\tpaths,\n\t};\n\n\tif (config.openapi.servers && config.openapi.servers.length > 0) {\n\t\tspec.servers = config.openapi.servers;\n\t}\n\n\treturn spec;\n}\n\nexport function writeOutputFiles(\n\tconfig: ResolvedConfig,\n\tspec: OpenAPISpec,\n\tcwd: string = process.cwd(),\n): void {\n\twriteSpecFiles(config, spec, cwd);\n\n\tif (config.output.scalarDocs) {\n\t\twriteScalarRoute(config, cwd);\n\t}\n}\n\nfunction writeSpecFiles(config: ResolvedConfig, spec: OpenAPISpec, cwd: string): void {\n\tconst specRoutePath = resolve(cwd, config.output.specPath);\n\tconst specDir = dirname(specRoutePath);\n\n\tensureDir(specDir);\n\n\t// Write spec.json co-located with the route\n\tconst specJsonPath = join(specDir, 'spec.json');\n\twriteFileSync(specJsonPath, JSON.stringify(spec, null, 2), 'utf8');\n\n\t// Write the Next.js route that serves the spec\n\tconst routeContent = `import spec from './spec.json';\n\nexport const dynamic = 'force-static';\n\nexport function GET() {\n return Response.json(spec);\n}\n`;\n\twriteFileSync(specRoutePath, routeContent, 'utf8');\n}\n\nfunction writeScalarRoute(config: ResolvedConfig, cwd: string): void {\n\tconst scalarRoutePath = resolve(cwd, config.output.scalarPath);\n\tensureDir(dirname(scalarRoutePath));\n\n\t// Derive the spec URL from the specPath\n\tconst specUrl = filePathToApiUrl(config.output.specPath);\n\n\tconst routeContent = `export const dynamic = 'force-static';\n\nexport function GET() {\n return new Response(\n \\`<!doctype html>\n<html>\n <head>\n <title>API Docs</title>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n </head>\n <body>\n <script\n id=\"api-reference\"\n data-url=\"${specUrl}\"\n ></script>\n <script src=\"https://cdn.jsdelivr.net/npm/@scalar/api-reference\"></script>\n </body>\n</html>\\`,\n { headers: { 'Content-Type': 'text/html' } }\n );\n}\n`;\n\twriteFileSync(scalarRoutePath, routeContent, 'utf8');\n}\n\n/**\n * Convert a file path like src/app/api/openapi.json/route.ts to /api/openapi.json\n */\nfunction filePathToApiUrl(filePath: string): string {\n\tlet path = filePath.replace(/\\\\/g, '/');\n\tpath = path.replace(/^(src\\/)?app\\//, '');\n\tpath = path.replace(/\\/route\\.(ts|tsx|js|jsx)$/, '');\n\tif (!path.startsWith('/')) path = `/${path}`;\n\treturn path;\n}\n\nfunction ensureDir(dir: string): void {\n\tif (!existsSync(dir)) {\n\t\tmkdirSync(dir, { recursive: true });\n\t}\n}\n","import { readFileSync } from 'fs';\nimport { relative } from 'path';\n\nexport interface RouteInfo {\n\tfilePath: string;\n\trelativePath: string;\n\turlPath: string;\n\tsourceCode: string;\n\tjsdocComments: string[];\n\thasExactJsdoc: boolean;\n\texactPathItem?: Record<string, unknown>;\n}\n\nexport async function scanRoutes(\n\tinclude: string[],\n\texclude: string[],\n\tcwd: string = process.cwd(),\n): Promise<RouteInfo[]> {\n\t// Lazy import so that importing this module does not eagerly load fast-glob.\n\t// This keeps the module lightweight and allows Vitest to mock it cleanly.\n\tconst { default: fg } = await import('fast-glob');\n\tconst files = await fg(include, {\n\t\tcwd,\n\t\tignore: exclude,\n\t\tabsolute: true,\n\t});\n\n\treturn files.map((filePath) => parseRoute(filePath, cwd));\n}\n\nfunction parseRoute(filePath: string, cwd: string): RouteInfo {\n\tconst relativePath = relative(cwd, filePath);\n\tconst sourceCode = readFileSync(filePath, 'utf8');\n\tconst urlPath = filePathToUrlPath(relativePath);\n\tconst { jsdocComments, hasExactJsdoc, exactPathItem } = extractJsdoc(sourceCode);\n\n\treturn {\n\t\tfilePath,\n\t\trelativePath,\n\t\turlPath,\n\t\tsourceCode,\n\t\tjsdocComments,\n\t\thasExactJsdoc,\n\t\texactPathItem,\n\t};\n}\n\n/**\n * Convert a Next.js route file path to an OpenAPI URL path.\n * e.g. src/app/api/users/[id]/route.ts -> /api/users/{id}\n */\nexport function filePathToUrlPath(filePath: string): string {\n\t// Normalize separators\n\tlet path = filePath.replace(/\\\\/g, '/');\n\n\t// Remove leading src/ or app/ prefixes\n\tpath = path.replace(/^(src\\/)?app\\//, '');\n\n\t// Remove trailing /route.ts or /route.js\n\tpath = path.replace(/\\/route\\.(ts|tsx|js|jsx)$/, '');\n\n\t// Convert Next.js dynamic segments [param] to OpenAPI {param}\n\tpath = path.replace(/\\[([^\\]]+)\\]/g, (_, param) => {\n\t\t// Handle catch-all [...param] and optional [[...param]]\n\t\tif (param.startsWith('...')) {\n\t\t\treturn `{${param.slice(3)}}`;\n\t\t}\n\t\treturn `{${param}}`;\n\t});\n\n\t// Ensure leading slash\n\tif (!path.startsWith('/')) {\n\t\tpath = `/${path}`;\n\t}\n\n\treturn path;\n}\n\ninterface JsdocResult {\n\tjsdocComments: string[];\n\thasExactJsdoc: boolean;\n\texactPathItem?: Record<string, unknown>;\n}\n\nfunction extractJsdoc(sourceCode: string): JsdocResult {\n\t// Match all JSDoc comment blocks /** ... */\n\tconst jsdocRegex = /\\/\\*\\*([\\s\\S]*?)\\*\\//g;\n\tconst jsdocComments: string[] = [];\n\tlet hasExactJsdoc = false;\n\tlet exactPathItem: Record<string, unknown> | undefined;\n\n\tconst match: RegExpExecArray | null = jsdocRegex.exec(sourceCode);\n\twhile (match !== null) {\n\t\tconst comment = match[0];\n\t\tjsdocComments.push(comment);\n\n\t\t// Check for @openapi-exact tag\n\t\tif (/@openapi-exact/.test(comment)) {\n\t\t\thasExactJsdoc = true;\n\t\t\t// Try to extract the JSON from @openapi tag\n\t\t\tconst openapiMatch = comment.match(/@openapi\\s+([\\s\\S]*?)(?=\\s*\\*\\/|\\s*\\*\\s*@)/);\n\t\t\tif (openapiMatch) {\n\t\t\t\ttry {\n\t\t\t\t\t// Clean up JSDoc asterisks from the JSON\n\t\t\t\t\tconst jsonStr = openapiMatch[1]\n\t\t\t\t\t\t.split('\\n')\n\t\t\t\t\t\t.map((line) => line.replace(/^\\s*\\*\\s?/, ''))\n\t\t\t\t\t\t.join('\\n')\n\t\t\t\t\t\t.trim();\n\t\t\t\t\texactPathItem = JSON.parse(jsonStr);\n\t\t\t\t} catch {\n\t\t\t\t\t// If JSON parse fails, fall through to LLM\n\t\t\t\t\thasExactJsdoc = false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { jsdocComments, hasExactJsdoc, exactPathItem };\n}\n","export type { JSDocMode, OpenAPIGenConfig, Provider, ResolvedConfig } from './config.js';\n\nexport { analyzeRoutes } from './analyzer.js';\nexport { loadConfig, resolveConfig } from './config.js';\nexport { assembleSpec, writeOutputFiles } from './generator.js';\nexport { filePathToUrlPath, scanRoutes } from './scanner.js';\n\nimport { resolve } from 'node:path';\nimport type { OpenAPIGenConfig } from './config.js';\n\nimport { analyzeRoutes } from './analyzer.js';\nimport { loadConfig } from './config.js';\nimport { assembleSpec, writeOutputFiles } from './generator.js';\nimport { scanRoutes } from './scanner.js';\n\nexport interface GenerateOptions {\n\tconfig?: string;\n\tprovider?: OpenAPIGenConfig['provider'];\n\tcache?: boolean;\n\tcwd?: string;\n}\n\nexport interface GenerateResult {\n\troutesAnalyzed: number;\n\troutesFromCache: number;\n\troutesSkippedLLM: number;\n\tspecPath: string;\n}\n\nexport async function generate(options: GenerateOptions = {}): Promise<GenerateResult> {\n\tconst cwd = options.cwd ?? process.cwd();\n\tconst config = await loadConfig(options.config);\n\n\t// Allow CLI overrides\n\tif (options.provider) config.provider = options.provider;\n\tif (options.cache === false) config.cache = false;\n\n\t// Load .env files before provider env vars are read\n\tif (config.envFile !== false) {\n\t\tconst { config: dotenvConfig } = await import('dotenv');\n\t\tfor (const file of config.envFile) {\n\t\t\tdotenvConfig({ path: resolve(cwd, file), override: false });\n\t\t}\n\t}\n\n\tconsole.log(`[openapi-ai-generator] Scanning routes...`);\n\tconst routes = await scanRoutes(config.include, config.exclude, cwd);\n\tconsole.log(`[openapi-ai-generator] Found ${routes.length} route(s)`);\n\n\tconsole.log(`[openapi-ai-generator] Analyzing routes with provider: ${config.provider}`);\n\tconst analyzed = await analyzeRoutes(routes, {\n\t\tprovider: config.provider,\n\t\tjsdocMode: config.jsdocMode,\n\t\tcache: config.cache,\n\t\tcacheDir: config.cacheDir,\n\t});\n\n\tconst fromCache = analyzed.filter((r) => r.fromCache).length;\n\tconst skippedLLM = analyzed.filter((r) => r.skippedLLM).length;\n\tconsole.log(\n\t\t`[openapi-ai-generator] ${analyzed.length} routes analyzed (${fromCache} from cache, ${skippedLLM - fromCache} exact JSDoc)`,\n\t);\n\n\tconst spec = assembleSpec(config, analyzed);\n\twriteOutputFiles(config, spec, cwd);\n\n\tconsole.log(`[openapi-ai-generator] Spec written to ${config.output.specPath}`);\n\tif (config.output.scalarDocs) {\n\t\tconsole.log(`[openapi-ai-generator] Scalar docs written to ${config.output.scalarPath}`);\n\t}\n\n\treturn {\n\t\troutesAnalyzed: analyzed.length,\n\t\troutesFromCache: fromCache,\n\t\troutesSkippedLLM: skippedLLM,\n\t\tspecPath: config.output.specPath,\n\t};\n}\n"]}
|