openapi-ai-generator 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -26,150 +26,13 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  // src/cli.ts
27
27
  var import_commander = require("commander");
28
28
 
29
- // src/config.ts
30
- var import_fs = require("fs");
31
- var import_path = require("path");
32
- var import_url = require("url");
33
- var defaults = {
34
- jsdocMode: "context",
35
- cache: true,
36
- cacheDir: ".openapi-cache",
37
- include: ["src/app/api/**/route.ts"],
38
- exclude: []
39
- };
40
- function resolveConfig(config) {
41
- return {
42
- ...defaults,
43
- ...config,
44
- output: {
45
- scalarDocs: false,
46
- scalarPath: "src/app/api/docs/route.ts",
47
- ...config.output
48
- },
49
- openapi: {
50
- description: "",
51
- servers: [],
52
- ...config.openapi
53
- }
54
- };
55
- }
56
- async function loadConfig(configPath) {
57
- const searchPaths = configPath ? [configPath] : [
58
- "openapi-gen.config.ts",
59
- "openapi-gen.config.js",
60
- "openapi-gen.config.mjs",
61
- "openapi-gen.config.cjs"
62
- ];
63
- for (const p of searchPaths) {
64
- const abs = (0, import_path.resolve)(process.cwd(), p);
65
- if ((0, import_fs.existsSync)(abs)) {
66
- const mod = await importConfig(abs);
67
- const config = mod.default ?? mod;
68
- return resolveConfig(config);
69
- }
70
- }
71
- throw new Error(
72
- "No openapi-gen.config.ts found. Create one at your project root."
73
- );
74
- }
75
- async function importConfig(filePath) {
76
- if (filePath.endsWith(".ts")) {
77
- return importTypeScriptConfig(filePath);
78
- }
79
- const url = (0, import_url.pathToFileURL)(filePath).href;
80
- return import(url);
81
- }
82
- async function importTypeScriptConfig(filePath) {
83
- try {
84
- const { register } = await import("module");
85
- const url = (0, import_url.pathToFileURL)(filePath).href;
86
- return await import(url);
87
- } catch {
88
- try {
89
- require("ts-node/register");
90
- return require(filePath);
91
- } catch {
92
- throw new Error(
93
- `Cannot load TypeScript config file: ${filePath}. Install tsx or ts-node, or use a .js config file.`
94
- );
95
- }
96
- }
97
- }
98
-
99
- // src/scanner.ts
100
- var import_fs2 = require("fs");
101
- var import_glob = require("glob");
102
- var import_path2 = require("path");
103
- async function scanRoutes(include, exclude, cwd = process.cwd()) {
104
- const files = await (0, import_glob.glob)(include, {
105
- cwd,
106
- ignore: exclude,
107
- absolute: true
108
- });
109
- return files.map((filePath) => parseRoute(filePath, cwd));
110
- }
111
- function parseRoute(filePath, cwd) {
112
- const relativePath = (0, import_path2.relative)(cwd, filePath);
113
- const sourceCode = (0, import_fs2.readFileSync)(filePath, "utf8");
114
- const urlPath = filePathToUrlPath(relativePath);
115
- const { jsdocComments, hasExactJsdoc, exactPathItem } = extractJsdoc(sourceCode);
116
- return {
117
- filePath,
118
- relativePath,
119
- urlPath,
120
- sourceCode,
121
- jsdocComments,
122
- hasExactJsdoc,
123
- exactPathItem
124
- };
125
- }
126
- function filePathToUrlPath(filePath) {
127
- let path = filePath.replace(/\\/g, "/");
128
- path = path.replace(/^(src\/)?app\//, "");
129
- path = path.replace(/\/route\.(ts|tsx|js|jsx)$/, "");
130
- path = path.replace(/\[([^\]]+)\]/g, (_, param) => {
131
- if (param.startsWith("...")) {
132
- return `{${param.slice(3)}}`;
133
- }
134
- return `{${param}}`;
135
- });
136
- if (!path.startsWith("/")) {
137
- path = "/" + path;
138
- }
139
- return path;
140
- }
141
- function extractJsdoc(sourceCode) {
142
- const jsdocRegex = /\/\*\*([\s\S]*?)\*\//g;
143
- const jsdocComments = [];
144
- let hasExactJsdoc = false;
145
- let exactPathItem;
146
- let match;
147
- while ((match = jsdocRegex.exec(sourceCode)) !== null) {
148
- const comment = match[0];
149
- jsdocComments.push(comment);
150
- if (/@openapi-exact/.test(comment)) {
151
- hasExactJsdoc = true;
152
- const openapiMatch = comment.match(/@openapi\s+([\s\S]*?)(?=\s*\*\/|\s*\*\s*@)/);
153
- if (openapiMatch) {
154
- try {
155
- const jsonStr = openapiMatch[1].split("\n").map((line) => line.replace(/^\s*\*\s?/, "")).join("\n").trim();
156
- exactPathItem = JSON.parse(jsonStr);
157
- } catch {
158
- hasExactJsdoc = false;
159
- }
160
- }
161
- }
162
- }
163
- return { jsdocComments, hasExactJsdoc, exactPathItem };
164
- }
165
-
166
29
  // src/analyzer.ts
167
30
  var import_ai = require("ai");
168
31
 
169
32
  // src/cache.ts
170
33
  var import_crypto = require("crypto");
171
- var import_fs3 = require("fs");
172
- var import_path3 = require("path");
34
+ var import_fs = require("fs");
35
+ var import_path = require("path");
173
36
  function computeHash(content, provider, modelId) {
174
37
  return (0, import_crypto.createHash)("sha256").update(content).update(provider).update(modelId).digest("hex");
175
38
  }
@@ -179,15 +42,15 @@ var RouteCache = class {
179
42
  this.cacheDir = cacheDir;
180
43
  }
181
44
  ensureDir() {
182
- if (!(0, import_fs3.existsSync)(this.cacheDir)) {
183
- (0, import_fs3.mkdirSync)(this.cacheDir, { recursive: true });
45
+ if (!(0, import_fs.existsSync)(this.cacheDir)) {
46
+ (0, import_fs.mkdirSync)(this.cacheDir, { recursive: true });
184
47
  }
185
48
  }
186
49
  get(hash) {
187
- const filePath = (0, import_path3.join)(this.cacheDir, `${hash}.json`);
188
- if (!(0, import_fs3.existsSync)(filePath)) return null;
50
+ const filePath = (0, import_path.join)(this.cacheDir, `${hash}.json`);
51
+ if (!(0, import_fs.existsSync)(filePath)) return null;
189
52
  try {
190
- const entry = JSON.parse((0, import_fs3.readFileSync)(filePath, "utf8"));
53
+ const entry = JSON.parse((0, import_fs.readFileSync)(filePath, "utf8"));
191
54
  return entry.pathItem;
192
55
  } catch {
193
56
  return null;
@@ -200,8 +63,8 @@ var RouteCache = class {
200
63
  pathItem,
201
64
  cachedAt: (/* @__PURE__ */ new Date()).toISOString()
202
65
  };
203
- const filePath = (0, import_path3.join)(this.cacheDir, `${hash}.json`);
204
- (0, import_fs3.writeFileSync)(filePath, JSON.stringify(entry, null, 2), "utf8");
66
+ const filePath = (0, import_path.join)(this.cacheDir, `${hash}.json`);
67
+ (0, import_fs.writeFileSync)(filePath, JSON.stringify(entry, null, 2), "utf8");
205
68
  }
206
69
  };
207
70
 
@@ -307,7 +170,25 @@ async function analyzeRoute(route, options, modelId, cache, getModel) {
307
170
  };
308
171
  }
309
172
  }
310
- const pathItem = await callLLM(route, options.jsdocMode, getModel());
173
+ let pathItem;
174
+ try {
175
+ pathItem = await callLLM(route, options.jsdocMode, getModel());
176
+ } catch (err) {
177
+ const message = err instanceof Error ? err.message : String(err);
178
+ const isContentFilter = message.includes("content filtering policy") || message.includes("content_filter") || err?.status === 400;
179
+ if (isContentFilter) {
180
+ console.warn(
181
+ `Warning: Content filter blocked response for ${route.urlPath}. Skipping route. Use @openapi-exact JSDoc to provide the spec manually.`
182
+ );
183
+ return {
184
+ urlPath: route.urlPath,
185
+ pathItem: {},
186
+ fromCache: false,
187
+ skippedLLM: false
188
+ };
189
+ }
190
+ throw err;
191
+ }
311
192
  if (cache) {
312
193
  cache.set(hash, pathItem);
313
194
  }
@@ -321,30 +202,40 @@ async function analyzeRoute(route, options, modelId, cache, getModel) {
321
202
  function buildPrompt(route, jsdocMode) {
322
203
  const jsDocSection = route.jsdocComments.length > 0 ? `JSDoc COMMENTS (use as ${jsdocMode === "context" ? "additional context" : "primary source"}):
323
204
  ${route.jsdocComments.join("\n\n")}` : "No JSDoc comments found.";
324
- return `You are an OpenAPI 3.1 specification generator. Analyze the following Next.js API route and extract a complete OpenAPI PathItem object.
205
+ 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 \u2014 you only read and describe it.
206
+
207
+ ## Task
208
+
209
+ Read 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.
210
+
211
+ ## Route metadata
212
+
213
+ - File: ${route.relativePath}
214
+ - URL path: ${route.urlPath}
325
215
 
326
- FILE PATH: ${route.relativePath}
327
- INFERRED URL PATH: ${route.urlPath}
216
+ ## Source file contents
328
217
 
329
- SOURCE CODE:
330
218
  \`\`\`typescript
331
219
  ${route.sourceCode}
332
220
  \`\`\`
333
221
 
334
222
  ${jsDocSection}
335
223
 
336
- Extract the following for EACH exported HTTP method handler (GET, POST, PUT, PATCH, DELETE):
337
- - operationId (camelCase, unique)
338
- - summary (short description)
339
- - description (detailed description)
340
- - path parameters (from URL segments like [id])
341
- - query parameters (from NextRequest.nextUrl.searchParams usage)
342
- - request body schema (from request.json() usage and TypeScript types)
343
- - response schemas (per status code, from NextResponse.json() calls and return types)
344
- - tags (infer from path segments)
345
- - security requirements (if auth middleware or token checks are present)
224
+ ## Instructions
346
225
 
347
- Return ONLY a valid JSON object matching the OpenAPI 3.1 PathItem schema. No explanation, no markdown, no code blocks. Just the raw JSON object.`;
226
+ For each exported function named GET, POST, PUT, PATCH, or DELETE, document:
227
+ - operationId: a unique camelCase identifier
228
+ - summary: a short one-line description
229
+ - description: a fuller explanation of what the endpoint does
230
+ - parameters: path params from URL segments like {id}, and query params from searchParams usage
231
+ - requestBody: schema inferred from request.json() calls and TypeScript types (POST/PUT/PATCH only)
232
+ - responses: per status code, inferred from NextResponse.json() calls and return type annotations
233
+ - tags: inferred from the URL path segments
234
+ - security: noted if the code checks for auth tokens, session cookies, or middleware guards
235
+
236
+ ## Output format
237
+
238
+ Return a single raw JSON object matching the OpenAPI 3.1 PathItem schema. No explanation, no markdown fences, no extra text \u2014 only the JSON object.`;
348
239
  }
349
240
  async function callLLM(route, jsdocMode, model) {
350
241
  const prompt = buildPrompt(route, jsdocMode);
@@ -375,9 +266,77 @@ function parsePathItem(text, urlPath) {
375
266
  }
376
267
  }
377
268
 
269
+ // src/config.ts
270
+ var import_fs2 = require("fs");
271
+ var import_path2 = require("path");
272
+ var import_url = require("url");
273
+ var defaults = {
274
+ jsdocMode: "context",
275
+ cache: true,
276
+ cacheDir: ".openapi-cache",
277
+ include: ["src/app/api/**/route.ts"],
278
+ exclude: []
279
+ };
280
+ function resolveConfig(config) {
281
+ return {
282
+ ...defaults,
283
+ ...config,
284
+ output: {
285
+ scalarDocs: false,
286
+ scalarPath: "src/app/api/docs/route.ts",
287
+ ...config.output
288
+ },
289
+ openapi: {
290
+ description: "",
291
+ servers: [],
292
+ ...config.openapi
293
+ }
294
+ };
295
+ }
296
+ async function loadConfig(configPath) {
297
+ const searchPaths = configPath ? [configPath] : [
298
+ "openapi-gen.config.ts",
299
+ "openapi-gen.config.js",
300
+ "openapi-gen.config.mjs",
301
+ "openapi-gen.config.cjs"
302
+ ];
303
+ for (const p of searchPaths) {
304
+ const abs = (0, import_path2.resolve)(process.cwd(), p);
305
+ if ((0, import_fs2.existsSync)(abs)) {
306
+ const mod = await importConfig(abs);
307
+ const config = mod.default ?? mod;
308
+ return resolveConfig(config);
309
+ }
310
+ }
311
+ throw new Error("No openapi-gen.config.ts found. Create one at your project root.");
312
+ }
313
+ async function importConfig(filePath) {
314
+ if (filePath.endsWith(".ts")) {
315
+ return importTypeScriptConfig(filePath);
316
+ }
317
+ const url = (0, import_url.pathToFileURL)(filePath).href;
318
+ return import(url);
319
+ }
320
+ async function importTypeScriptConfig(filePath) {
321
+ try {
322
+ await import("module");
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
+ }
335
+ }
336
+
378
337
  // src/generator.ts
379
- var import_fs4 = require("fs");
380
- var import_path4 = require("path");
338
+ var import_fs3 = require("fs");
339
+ var import_path3 = require("path");
381
340
  function assembleSpec(config, routes) {
382
341
  const paths = {};
383
342
  for (const route of routes) {
@@ -406,11 +365,11 @@ function writeOutputFiles(config, spec, cwd = process.cwd()) {
406
365
  }
407
366
  }
408
367
  function writeSpecFiles(config, spec, cwd) {
409
- const specRoutePath = (0, import_path4.resolve)(cwd, config.output.specPath);
410
- const specDir = (0, import_path4.dirname)(specRoutePath);
368
+ const specRoutePath = (0, import_path3.resolve)(cwd, config.output.specPath);
369
+ const specDir = (0, import_path3.dirname)(specRoutePath);
411
370
  ensureDir(specDir);
412
- const specJsonPath = (0, import_path4.join)(specDir, "spec.json");
413
- (0, import_fs4.writeFileSync)(specJsonPath, JSON.stringify(spec, null, 2), "utf8");
371
+ const specJsonPath = (0, import_path3.join)(specDir, "spec.json");
372
+ (0, import_fs3.writeFileSync)(specJsonPath, JSON.stringify(spec, null, 2), "utf8");
414
373
  const routeContent = `import spec from './spec.json';
415
374
 
416
375
  export const dynamic = 'force-static';
@@ -419,11 +378,11 @@ export function GET() {
419
378
  return Response.json(spec);
420
379
  }
421
380
  `;
422
- (0, import_fs4.writeFileSync)(specRoutePath, routeContent, "utf8");
381
+ (0, import_fs3.writeFileSync)(specRoutePath, routeContent, "utf8");
423
382
  }
424
383
  function writeScalarRoute(config, cwd) {
425
- const scalarRoutePath = (0, import_path4.resolve)(cwd, config.output.scalarPath);
426
- ensureDir((0, import_path4.dirname)(scalarRoutePath));
384
+ const scalarRoutePath = (0, import_path3.resolve)(cwd, config.output.scalarPath);
385
+ ensureDir((0, import_path3.dirname)(scalarRoutePath));
427
386
  const specUrl = filePathToApiUrl(config.output.specPath);
428
387
  const routeContent = `export const dynamic = 'force-static';
429
388
 
@@ -448,19 +407,86 @@ export function GET() {
448
407
  );
449
408
  }
450
409
  `;
451
- (0, import_fs4.writeFileSync)(scalarRoutePath, routeContent, "utf8");
410
+ (0, import_fs3.writeFileSync)(scalarRoutePath, routeContent, "utf8");
452
411
  }
453
412
  function filePathToApiUrl(filePath) {
454
413
  let path = filePath.replace(/\\/g, "/");
455
414
  path = path.replace(/^(src\/)?app\//, "");
456
415
  path = path.replace(/\/route\.(ts|tsx|js|jsx)$/, "");
457
- if (!path.startsWith("/")) path = "/" + path;
416
+ if (!path.startsWith("/")) path = `/${path}`;
458
417
  return path;
459
418
  }
460
419
  function ensureDir(dir) {
461
- if (!(0, import_fs4.existsSync)(dir)) {
462
- (0, import_fs4.mkdirSync)(dir, { recursive: true });
420
+ if (!(0, import_fs3.existsSync)(dir)) {
421
+ (0, import_fs3.mkdirSync)(dir, { recursive: true });
422
+ }
423
+ }
424
+
425
+ // src/scanner.ts
426
+ var import_fs4 = require("fs");
427
+ var import_path4 = require("path");
428
+ async function scanRoutes(include, exclude, cwd = process.cwd()) {
429
+ const { default: fg } = await import("fast-glob");
430
+ const files = await fg(include, {
431
+ cwd,
432
+ ignore: exclude,
433
+ absolute: true
434
+ });
435
+ return files.map((filePath) => parseRoute(filePath, cwd));
436
+ }
437
+ function parseRoute(filePath, cwd) {
438
+ const relativePath = (0, import_path4.relative)(cwd, filePath);
439
+ const sourceCode = (0, import_fs4.readFileSync)(filePath, "utf8");
440
+ const urlPath = filePathToUrlPath(relativePath);
441
+ const { jsdocComments, hasExactJsdoc, exactPathItem } = extractJsdoc(sourceCode);
442
+ return {
443
+ filePath,
444
+ relativePath,
445
+ urlPath,
446
+ sourceCode,
447
+ jsdocComments,
448
+ hasExactJsdoc,
449
+ exactPathItem
450
+ };
451
+ }
452
+ function filePathToUrlPath(filePath) {
453
+ let path = filePath.replace(/\\/g, "/");
454
+ path = path.replace(/^(src\/)?app\//, "");
455
+ path = path.replace(/\/route\.(ts|tsx|js|jsx)$/, "");
456
+ path = path.replace(/\[([^\]]+)\]/g, (_, param) => {
457
+ if (param.startsWith("...")) {
458
+ return `{${param.slice(3)}}`;
459
+ }
460
+ return `{${param}}`;
461
+ });
462
+ if (!path.startsWith("/")) {
463
+ path = `/${path}`;
464
+ }
465
+ return path;
466
+ }
467
+ function extractJsdoc(sourceCode) {
468
+ const jsdocRegex = /\/\*\*([\s\S]*?)\*\//g;
469
+ const jsdocComments = [];
470
+ let hasExactJsdoc = false;
471
+ let exactPathItem;
472
+ const match = jsdocRegex.exec(sourceCode);
473
+ while (match !== null) {
474
+ const comment = match[0];
475
+ jsdocComments.push(comment);
476
+ if (/@openapi-exact/.test(comment)) {
477
+ hasExactJsdoc = true;
478
+ const openapiMatch = comment.match(/@openapi\s+([\s\S]*?)(?=\s*\*\/|\s*\*\s*@)/);
479
+ if (openapiMatch) {
480
+ try {
481
+ const jsonStr = openapiMatch[1].split("\n").map((line) => line.replace(/^\s*\*\s?/, "")).join("\n").trim();
482
+ exactPathItem = JSON.parse(jsonStr);
483
+ } catch {
484
+ hasExactJsdoc = false;
485
+ }
486
+ }
487
+ }
463
488
  }
489
+ return { jsdocComments, hasExactJsdoc, exactPathItem };
464
490
  }
465
491
 
466
492
  // src/index.ts
@@ -501,10 +527,7 @@ async function generate(options = {}) {
501
527
  // src/cli.ts
502
528
  var program = new import_commander.Command();
503
529
  program.name("openapi-ai-generator").description("Generate OpenAPI 3.1 specs from Next.js API routes using AI").version("0.1.0");
504
- program.command("generate").description("Scan Next.js API routes and generate an OpenAPI spec").option("-c, --config <path>", "Path to config file (default: openapi-gen.config.ts)").option(
505
- "-p, --provider <provider>",
506
- "Override provider (azure | openai | anthropic)"
507
- ).option("--no-cache", "Disable caching (always re-analyze all routes)").action(async (opts) => {
530
+ program.command("generate").description("Scan Next.js API routes and generate an OpenAPI spec").option("-c, --config <path>", "Path to config file (default: openapi-gen.config.ts)").option("-p, --provider <provider>", "Override provider (azure | openai | anthropic)").option("--no-cache", "Disable caching (always re-analyze all routes)").action(async (opts) => {
508
531
  try {
509
532
  const result = await generate({
510
533
  config: opts.config,
@@ -515,7 +538,9 @@ program.command("generate").description("Scan Next.js API routes and generate an
515
538
  \u2713 OpenAPI spec generated successfully`);
516
539
  console.log(` Routes analyzed: ${result.routesAnalyzed}`);
517
540
  console.log(` From cache: ${result.routesFromCache}`);
518
- console.log(` LLM calls made: ${result.routesAnalyzed - result.routesFromCache - (result.routesSkippedLLM - result.routesFromCache)}`);
541
+ console.log(
542
+ ` LLM calls made: ${result.routesAnalyzed - result.routesFromCache - (result.routesSkippedLLM - result.routesFromCache)}`
543
+ );
519
544
  console.log(` Spec written to: ${result.specPath}`);
520
545
  } catch (err) {
521
546
  console.error("[openapi-ai-generator] Error:", err instanceof Error ? err.message : err);
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/config.ts","../src/scanner.ts","../src/analyzer.ts","../src/cache.ts","../src/providers/index.ts","../src/generator.ts","../src/index.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { generate } from './index.js';\nimport type { Provider } from './config.js';\n\nconst program = new Command();\n\nprogram\n .name('openapi-ai-generator')\n .description('Generate OpenAPI 3.1 specs from Next.js API routes using AI')\n .version('0.1.0');\n\nprogram\n .command('generate')\n .description('Scan Next.js API routes and generate an OpenAPI spec')\n .option('-c, --config <path>', 'Path to config file (default: openapi-gen.config.ts)')\n .option(\n '-p, --provider <provider>',\n 'Override provider (azure | openai | anthropic)'\n )\n .option('--no-cache', 'Disable caching (always re-analyze all routes)')\n .action(async (opts: { config?: string; provider?: string; cache: boolean }) => {\n try {\n const result = await generate({\n config: opts.config,\n provider: opts.provider as Provider | undefined,\n cache: opts.cache,\n });\n\n console.log(`\\n✓ OpenAPI spec generated successfully`);\n console.log(` Routes analyzed: ${result.routesAnalyzed}`);\n console.log(` From cache: ${result.routesFromCache}`);\n console.log(` LLM calls made: ${result.routesAnalyzed - result.routesFromCache - (result.routesSkippedLLM - result.routesFromCache)}`);\n console.log(` Spec written to: ${result.specPath}`);\n } catch (err) {\n console.error('[openapi-ai-generator] Error:', err instanceof Error ? err.message : err);\n process.exit(1);\n }\n });\n\nprogram.parse();\n","import { existsSync } from 'fs';\nimport { resolve, dirname } from 'path';\nimport { pathToFileURL } from 'url';\n\nexport type Provider = 'azure' | 'openai' | 'anthropic';\nexport type JSDocMode = 'context' | 'exact';\n\nexport interface OpenAPIGenConfig {\n provider: Provider;\n output: {\n specPath: string;\n scalarDocs?: boolean;\n scalarPath?: string;\n };\n openapi: {\n title: string;\n version: string;\n description?: string;\n servers?: Array<{ url: string; description?: string }>;\n };\n jsdocMode?: JSDocMode;\n cache?: boolean;\n cacheDir?: string;\n include?: string[];\n exclude?: string[];\n}\n\nexport interface ResolvedConfig extends Required<OpenAPIGenConfig> {\n output: Required<OpenAPIGenConfig['output']>;\n openapi: Required<OpenAPIGenConfig['openapi']>;\n}\n\nconst defaults: Omit<ResolvedConfig, 'provider' | 'output' | 'openapi'> = {\n jsdocMode: 'context',\n cache: true,\n cacheDir: '.openapi-cache',\n include: ['src/app/api/**/route.ts'],\n exclude: [],\n};\n\nexport function resolveConfig(config: OpenAPIGenConfig): ResolvedConfig {\n return {\n ...defaults,\n ...config,\n output: {\n scalarDocs: false,\n scalarPath: 'src/app/api/docs/route.ts',\n ...config.output,\n },\n openapi: {\n description: '',\n servers: [],\n ...config.openapi,\n },\n };\n}\n\nexport async function loadConfig(configPath?: string): Promise<ResolvedConfig> {\n const searchPaths = configPath\n ? [configPath]\n : [\n 'openapi-gen.config.ts',\n 'openapi-gen.config.js',\n 'openapi-gen.config.mjs',\n 'openapi-gen.config.cjs',\n ];\n\n for (const p of searchPaths) {\n const abs = resolve(process.cwd(), p);\n if (existsSync(abs)) {\n const mod = await importConfig(abs);\n const config: OpenAPIGenConfig = mod.default ?? mod;\n return resolveConfig(config);\n }\n }\n\n throw new Error(\n 'No openapi-gen.config.ts found. Create one at your project root.'\n );\n}\n\nasync function importConfig(filePath: string): Promise<{ default?: OpenAPIGenConfig } & OpenAPIGenConfig> {\n // For .ts files, try to use tsx/ts-node if available, else fall back to require\n if (filePath.endsWith('.ts')) {\n return importTypeScriptConfig(filePath);\n }\n const url = pathToFileURL(filePath).href;\n return import(url);\n}\n\nasync function importTypeScriptConfig(filePath: string): Promise<{ default?: OpenAPIGenConfig } & OpenAPIGenConfig> {\n // Try dynamic import with tsx register if available\n try {\n // Check if tsx is available\n const { register } = await import('module');\n // Use tsx/ts-node loader\n const url = pathToFileURL(filePath).href;\n return await import(url);\n } catch {\n // Fall back: try require with ts-node\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n require('ts-node/register');\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n return require(filePath);\n } catch {\n throw new Error(\n `Cannot load TypeScript config file: ${filePath}. ` +\n 'Install tsx or ts-node, or use a .js config file.'\n );\n }\n }\n}\n","import { readFileSync } from 'fs';\nimport { glob } from 'glob';\nimport { resolve, relative } from 'path';\n\nexport interface RouteInfo {\n filePath: string;\n relativePath: string;\n urlPath: string;\n sourceCode: string;\n jsdocComments: string[];\n hasExactJsdoc: boolean;\n exactPathItem?: Record<string, unknown>;\n}\n\nexport async function scanRoutes(\n include: string[],\n exclude: string[],\n cwd: string = process.cwd()\n): Promise<RouteInfo[]> {\n const files = await glob(include, {\n cwd,\n ignore: exclude,\n absolute: true,\n });\n\n return files.map((filePath) => parseRoute(filePath, cwd));\n}\n\nfunction parseRoute(filePath: string, cwd: string): RouteInfo {\n const relativePath = relative(cwd, filePath);\n const sourceCode = readFileSync(filePath, 'utf8');\n const urlPath = filePathToUrlPath(relativePath);\n const { jsdocComments, hasExactJsdoc, exactPathItem } = extractJsdoc(sourceCode);\n\n return {\n filePath,\n relativePath,\n urlPath,\n sourceCode,\n jsdocComments,\n hasExactJsdoc,\n exactPathItem,\n };\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 // Normalize separators\n let path = filePath.replace(/\\\\/g, '/');\n\n // Remove leading src/ or app/ prefixes\n path = path.replace(/^(src\\/)?app\\//, '');\n\n // Remove trailing /route.ts or /route.js\n path = path.replace(/\\/route\\.(ts|tsx|js|jsx)$/, '');\n\n // Convert Next.js dynamic segments [param] to OpenAPI {param}\n path = path.replace(/\\[([^\\]]+)\\]/g, (_, param) => {\n // Handle catch-all [...param] and optional [[...param]]\n if (param.startsWith('...')) {\n return `{${param.slice(3)}}`;\n }\n return `{${param}}`;\n });\n\n // Ensure leading slash\n if (!path.startsWith('/')) {\n path = '/' + path;\n }\n\n return path;\n}\n\ninterface JsdocResult {\n jsdocComments: string[];\n hasExactJsdoc: boolean;\n exactPathItem?: Record<string, unknown>;\n}\n\nfunction extractJsdoc(sourceCode: string): JsdocResult {\n // Match all JSDoc comment blocks /** ... */\n const jsdocRegex = /\\/\\*\\*([\\s\\S]*?)\\*\\//g;\n const jsdocComments: string[] = [];\n let hasExactJsdoc = false;\n let exactPathItem: Record<string, unknown> | undefined;\n\n let match: RegExpExecArray | null;\n while ((match = jsdocRegex.exec(sourceCode)) !== null) {\n const comment = match[0];\n jsdocComments.push(comment);\n\n // Check for @openapi-exact tag\n if (/@openapi-exact/.test(comment)) {\n hasExactJsdoc = true;\n // Try to extract the JSON from @openapi tag\n const openapiMatch = comment.match(/@openapi\\s+([\\s\\S]*?)(?=\\s*\\*\\/|\\s*\\*\\s*@)/);\n if (openapiMatch) {\n try {\n // Clean up JSDoc asterisks from the JSON\n const jsonStr = openapiMatch[1]\n .split('\\n')\n .map((line) => line.replace(/^\\s*\\*\\s?/, ''))\n .join('\\n')\n .trim();\n exactPathItem = JSON.parse(jsonStr);\n } catch {\n // If JSON parse fails, fall through to LLM\n hasExactJsdoc = false;\n }\n }\n }\n }\n\n return { jsdocComments, hasExactJsdoc, exactPathItem };\n}\n","import { generateText } from 'ai';\nimport type { LanguageModel } from 'ai';\nimport type { RouteInfo } from './scanner.js';\nimport type { JSDocMode } from './config.js';\nimport { RouteCache, computeHash } from './cache.js';\nimport type { Provider } from './config.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 const pathItem = await callLLM(route, options.jsdocMode, getModel());\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 an OpenAPI 3.1 specification generator. Analyze the following Next.js API route and extract a complete OpenAPI PathItem object.\n\nFILE PATH: ${route.relativePath}\nINFERRED URL PATH: ${route.urlPath}\n\nSOURCE CODE:\n\\`\\`\\`typescript\n${route.sourceCode}\n\\`\\`\\`\n\n${jsDocSection}\n\nExtract the following for EACH exported HTTP method handler (GET, POST, PUT, PATCH, DELETE):\n- operationId (camelCase, unique)\n- summary (short description)\n- description (detailed description)\n- path parameters (from URL segments like [id])\n- query parameters (from NextRequest.nextUrl.searchParams usage)\n- request body schema (from request.json() usage and TypeScript types)\n- response schemas (per status code, from NextResponse.json() calls and return types)\n- tags (infer from path segments)\n- security requirements (if auth middleware or token checks are present)\n\nReturn ONLY a valid JSON object matching the OpenAPI 3.1 PathItem schema. No explanation, no markdown, no code blocks. Just the raw 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(\n text: string,\n urlPath: string\n): Record<string, unknown> {\n // Strip any accidental markdown code fences\n let json = text.trim();\n if (json.startsWith('```')) {\n json = json.replace(/^```(?:json)?\\s*/i, '').replace(/\\s*```$/, '').trim();\n }\n\n try {\n const parsed = JSON.parse(json);\n if (typeof parsed !== 'object' || Array.isArray(parsed) || parsed === null) {\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 hash: string;\n pathItem: Record<string, unknown>;\n cachedAt: string;\n}\n\nexport function computeHash(content: string, provider: string, modelId: string): string {\n return createHash('sha256')\n .update(content)\n .update(provider)\n .update(modelId)\n .digest('hex');\n}\n\nexport class RouteCache {\n private readonly cacheDir: string;\n\n constructor(cacheDir: string) {\n this.cacheDir = cacheDir;\n }\n\n private ensureDir(): void {\n if (!existsSync(this.cacheDir)) {\n mkdirSync(this.cacheDir, { recursive: true });\n }\n }\n\n get(hash: string): Record<string, unknown> | null {\n const filePath = join(this.cacheDir, `${hash}.json`);\n if (!existsSync(filePath)) return null;\n try {\n const entry: CacheEntry = JSON.parse(readFileSync(filePath, 'utf8'));\n return entry.pathItem;\n } catch {\n return null;\n }\n }\n\n set(hash: string, pathItem: Record<string, unknown>): void {\n this.ensureDir();\n const entry: CacheEntry = {\n hash,\n pathItem,\n cachedAt: new Date().toISOString(),\n };\n const filePath = join(this.cacheDir, `${hash}.json`);\n writeFileSync(filePath, JSON.stringify(entry, null, 2), 'utf8');\n }\n}\n","import type { Provider } from '../config.js';\nimport type { LanguageModel } from 'ai';\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 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({ endpoint, 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, mkdirSync, writeFileSync } from 'fs';\nimport { dirname, join, resolve } from 'path';\nimport type { ResolvedConfig } from './config.js';\nimport type { AnalyzedRoute } from './analyzer.js';\n\nexport interface OpenAPISpec {\n openapi: '3.1.0';\n info: {\n title: string;\n version: string;\n description?: string;\n };\n servers?: Array<{ url: string; description?: string }>;\n paths: Record<string, unknown>;\n}\n\nexport function assembleSpec(\n config: ResolvedConfig,\n routes: AnalyzedRoute[]\n): OpenAPISpec {\n const paths: Record<string, unknown> = {};\n\n for (const route of routes) {\n if (Object.keys(route.pathItem).length > 0) {\n paths[route.urlPath] = route.pathItem;\n }\n }\n\n const spec: OpenAPISpec = {\n openapi: '3.1.0',\n info: {\n title: config.openapi.title,\n version: config.openapi.version,\n ...(config.openapi.description ? { description: config.openapi.description } : {}),\n },\n paths,\n };\n\n if (config.openapi.servers && config.openapi.servers.length > 0) {\n spec.servers = config.openapi.servers;\n }\n\n return spec;\n}\n\nexport function writeOutputFiles(\n config: ResolvedConfig,\n spec: OpenAPISpec,\n cwd: string = process.cwd()\n): void {\n writeSpecFiles(config, spec, cwd);\n\n if (config.output.scalarDocs) {\n writeScalarRoute(config, cwd);\n }\n}\n\nfunction writeSpecFiles(\n config: ResolvedConfig,\n spec: OpenAPISpec,\n cwd: string\n): void {\n const specRoutePath = resolve(cwd, config.output.specPath);\n const specDir = dirname(specRoutePath);\n\n ensureDir(specDir);\n\n // Write spec.json co-located with the route\n const specJsonPath = join(specDir, 'spec.json');\n writeFileSync(specJsonPath, JSON.stringify(spec, null, 2), 'utf8');\n\n // Write the Next.js route that serves the spec\n const routeContent = `import spec from './spec.json';\n\nexport const dynamic = 'force-static';\n\nexport function GET() {\n return Response.json(spec);\n}\n`;\n writeFileSync(specRoutePath, routeContent, 'utf8');\n}\n\nfunction writeScalarRoute(config: ResolvedConfig, cwd: string): void {\n const scalarRoutePath = resolve(cwd, config.output.scalarPath);\n ensureDir(dirname(scalarRoutePath));\n\n // Derive the spec URL from the specPath\n const specUrl = filePathToApiUrl(config.output.specPath);\n\n const 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 writeFileSync(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 let path = filePath.replace(/\\\\/g, '/');\n path = path.replace(/^(src\\/)?app\\//, '');\n path = path.replace(/\\/route\\.(ts|tsx|js|jsx)$/, '');\n if (!path.startsWith('/')) path = '/' + path;\n return path;\n}\n\nfunction ensureDir(dir: string): void {\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n}\n","export type { OpenAPIGenConfig, ResolvedConfig, Provider, JSDocMode } from './config.js';\nexport { loadConfig, resolveConfig } from './config.js';\nexport { scanRoutes, filePathToUrlPath } from './scanner.js';\nexport { analyzeRoutes } from './analyzer.js';\nexport { assembleSpec, writeOutputFiles } from './generator.js';\n\nimport { loadConfig } from './config.js';\nimport { scanRoutes } from './scanner.js';\nimport { analyzeRoutes } from './analyzer.js';\nimport { assembleSpec, writeOutputFiles } from './generator.js';\nimport type { OpenAPIGenConfig } from './config.js';\n\nexport interface GenerateOptions {\n config?: string;\n provider?: OpenAPIGenConfig['provider'];\n cache?: boolean;\n cwd?: string;\n}\n\nexport interface GenerateResult {\n routesAnalyzed: number;\n routesFromCache: number;\n routesSkippedLLM: number;\n specPath: string;\n}\n\nexport async function generate(options: GenerateOptions = {}): Promise<GenerateResult> {\n const cwd = options.cwd ?? process.cwd();\n const config = await loadConfig(options.config);\n\n // Allow CLI overrides\n if (options.provider) config.provider = options.provider;\n if (options.cache === false) config.cache = false;\n\n console.log(`[openapi-ai-generator] Scanning routes...`);\n const routes = await scanRoutes(config.include, config.exclude, cwd);\n console.log(`[openapi-ai-generator] Found ${routes.length} route(s)`);\n\n console.log(`[openapi-ai-generator] Analyzing routes with provider: ${config.provider}`);\n const analyzed = await analyzeRoutes(routes, {\n provider: config.provider,\n jsdocMode: config.jsdocMode,\n cache: config.cache,\n cacheDir: config.cacheDir,\n });\n\n const fromCache = analyzed.filter((r) => r.fromCache).length;\n const skippedLLM = analyzed.filter((r) => r.skippedLLM).length;\n console.log(\n `[openapi-ai-generator] ${analyzed.length} routes analyzed (${fromCache} from cache, ${skippedLLM - fromCache} exact JSDoc)`\n );\n\n const spec = assembleSpec(config, analyzed);\n writeOutputFiles(config, spec, cwd);\n\n console.log(`[openapi-ai-generator] Spec written to ${config.output.specPath}`);\n if (config.output.scalarDocs) {\n console.log(`[openapi-ai-generator] Scalar docs written to ${config.output.scalarPath}`);\n }\n\n return {\n routesAnalyzed: analyzed.length,\n routesFromCache: fromCache,\n routesSkippedLLM: skippedLLM,\n specPath: config.output.specPath,\n };\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\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"]}