openapi-ai-generator 0.1.0

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/plugin.js ADDED
@@ -0,0 +1,572 @@
1
+ 'use strict';
2
+
3
+ var fs = require('fs');
4
+ var path = require('path');
5
+ var url = require('url');
6
+ var glob = require('glob');
7
+ var crypto = require('crypto');
8
+ var ai = require('ai');
9
+
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropNames = Object.getOwnPropertyNames;
12
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
13
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
14
+ }) : x)(function(x) {
15
+ if (typeof require !== "undefined") return require.apply(this, arguments);
16
+ throw Error('Dynamic require of "' + x + '" is not supported');
17
+ });
18
+ var __esm = (fn, res) => function __init() {
19
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
20
+ };
21
+ var __export = (target, all) => {
22
+ for (var name in all)
23
+ __defProp(target, name, { get: all[name], enumerable: true });
24
+ };
25
+ function resolveConfig(config) {
26
+ return {
27
+ ...defaults,
28
+ ...config,
29
+ output: {
30
+ scalarDocs: false,
31
+ scalarPath: "src/app/api/docs/route.ts",
32
+ ...config.output
33
+ },
34
+ openapi: {
35
+ description: "",
36
+ servers: [],
37
+ ...config.openapi
38
+ }
39
+ };
40
+ }
41
+ async function loadConfig(configPath) {
42
+ const searchPaths = configPath ? [configPath] : [
43
+ "openapi-gen.config.ts",
44
+ "openapi-gen.config.js",
45
+ "openapi-gen.config.mjs",
46
+ "openapi-gen.config.cjs"
47
+ ];
48
+ for (const p of searchPaths) {
49
+ const abs = path.resolve(process.cwd(), p);
50
+ if (fs.existsSync(abs)) {
51
+ const mod = await importConfig(abs);
52
+ const config = mod.default ?? mod;
53
+ return resolveConfig(config);
54
+ }
55
+ }
56
+ throw new Error(
57
+ "No openapi-gen.config.ts found. Create one at your project root."
58
+ );
59
+ }
60
+ async function importConfig(filePath) {
61
+ if (filePath.endsWith(".ts")) {
62
+ return importTypeScriptConfig(filePath);
63
+ }
64
+ const url$1 = url.pathToFileURL(filePath).href;
65
+ return import(url$1);
66
+ }
67
+ async function importTypeScriptConfig(filePath) {
68
+ try {
69
+ const { register } = await import('module');
70
+ const url$1 = url.pathToFileURL(filePath).href;
71
+ return await import(url$1);
72
+ } catch {
73
+ try {
74
+ __require("ts-node/register");
75
+ return __require(filePath);
76
+ } catch {
77
+ throw new Error(
78
+ `Cannot load TypeScript config file: ${filePath}. Install tsx or ts-node, or use a .js config file.`
79
+ );
80
+ }
81
+ }
82
+ }
83
+ var defaults;
84
+ var init_config = __esm({
85
+ "src/config.ts"() {
86
+ defaults = {
87
+ jsdocMode: "context",
88
+ cache: true,
89
+ cacheDir: ".openapi-cache",
90
+ include: ["src/app/api/**/route.ts"],
91
+ exclude: []
92
+ };
93
+ }
94
+ });
95
+ async function scanRoutes(include, exclude, cwd = process.cwd()) {
96
+ const files = await glob.glob(include, {
97
+ cwd,
98
+ ignore: exclude,
99
+ absolute: true
100
+ });
101
+ return files.map((filePath) => parseRoute(filePath, cwd));
102
+ }
103
+ function parseRoute(filePath, cwd) {
104
+ const relativePath = path.relative(cwd, filePath);
105
+ const sourceCode = fs.readFileSync(filePath, "utf8");
106
+ const urlPath = filePathToUrlPath(relativePath);
107
+ const { jsdocComments, hasExactJsdoc, exactPathItem } = extractJsdoc(sourceCode);
108
+ return {
109
+ filePath,
110
+ relativePath,
111
+ urlPath,
112
+ sourceCode,
113
+ jsdocComments,
114
+ hasExactJsdoc,
115
+ exactPathItem
116
+ };
117
+ }
118
+ function filePathToUrlPath(filePath) {
119
+ let path = filePath.replace(/\\/g, "/");
120
+ path = path.replace(/^(src\/)?app\//, "");
121
+ path = path.replace(/\/route\.(ts|tsx|js|jsx)$/, "");
122
+ path = path.replace(/\[([^\]]+)\]/g, (_, param) => {
123
+ if (param.startsWith("...")) {
124
+ return `{${param.slice(3)}}`;
125
+ }
126
+ return `{${param}}`;
127
+ });
128
+ if (!path.startsWith("/")) {
129
+ path = "/" + path;
130
+ }
131
+ return path;
132
+ }
133
+ function extractJsdoc(sourceCode) {
134
+ const jsdocRegex = /\/\*\*([\s\S]*?)\*\//g;
135
+ const jsdocComments = [];
136
+ let hasExactJsdoc = false;
137
+ let exactPathItem;
138
+ let match;
139
+ while ((match = jsdocRegex.exec(sourceCode)) !== null) {
140
+ const comment = match[0];
141
+ jsdocComments.push(comment);
142
+ if (/@openapi-exact/.test(comment)) {
143
+ hasExactJsdoc = true;
144
+ const openapiMatch = comment.match(/@openapi\s+([\s\S]*?)(?=\s*\*\/|\s*\*\s*@)/);
145
+ if (openapiMatch) {
146
+ try {
147
+ const jsonStr = openapiMatch[1].split("\n").map((line) => line.replace(/^\s*\*\s?/, "")).join("\n").trim();
148
+ exactPathItem = JSON.parse(jsonStr);
149
+ } catch {
150
+ hasExactJsdoc = false;
151
+ }
152
+ }
153
+ }
154
+ }
155
+ return { jsdocComments, hasExactJsdoc, exactPathItem };
156
+ }
157
+ var init_scanner = __esm({
158
+ "src/scanner.ts"() {
159
+ }
160
+ });
161
+ function computeHash(content, provider, modelId) {
162
+ return crypto.createHash("sha256").update(content).update(provider).update(modelId).digest("hex");
163
+ }
164
+ var RouteCache;
165
+ var init_cache = __esm({
166
+ "src/cache.ts"() {
167
+ RouteCache = class {
168
+ cacheDir;
169
+ constructor(cacheDir) {
170
+ this.cacheDir = cacheDir;
171
+ }
172
+ ensureDir() {
173
+ if (!fs.existsSync(this.cacheDir)) {
174
+ fs.mkdirSync(this.cacheDir, { recursive: true });
175
+ }
176
+ }
177
+ get(hash) {
178
+ const filePath = path.join(this.cacheDir, `${hash}.json`);
179
+ if (!fs.existsSync(filePath)) return null;
180
+ try {
181
+ const entry = JSON.parse(fs.readFileSync(filePath, "utf8"));
182
+ return entry.pathItem;
183
+ } catch {
184
+ return null;
185
+ }
186
+ }
187
+ set(hash, pathItem) {
188
+ this.ensureDir();
189
+ const entry = {
190
+ hash,
191
+ pathItem,
192
+ cachedAt: (/* @__PURE__ */ new Date()).toISOString()
193
+ };
194
+ const filePath = path.join(this.cacheDir, `${hash}.json`);
195
+ fs.writeFileSync(filePath, JSON.stringify(entry, null, 2), "utf8");
196
+ }
197
+ };
198
+ }
199
+ });
200
+
201
+ // src/providers/index.ts
202
+ function createModel(provider) {
203
+ switch (provider) {
204
+ case "azure":
205
+ return createAzureModel();
206
+ case "openai":
207
+ return createOpenAIModel();
208
+ case "anthropic":
209
+ return createAnthropicModel();
210
+ default: {
211
+ const _exhaustive = provider;
212
+ throw new Error(`Unknown provider: ${_exhaustive}`);
213
+ }
214
+ }
215
+ }
216
+ function createAzureModel() {
217
+ const endpoint = requireEnv("AZURE_OPENAI_ENDPOINT");
218
+ const apiKey = requireEnv("AZURE_OPENAI_API_KEY");
219
+ const deployment = requireEnv("AZURE_OPENAI_DEPLOYMENT");
220
+ const { createAzure } = __require("@ai-sdk/azure");
221
+ const azure = createAzure({ endpoint, apiKey });
222
+ return azure(deployment);
223
+ }
224
+ function createOpenAIModel() {
225
+ const apiKey = requireEnv("OPENAI_API_KEY");
226
+ const model = process.env.OPENAI_MODEL ?? "gpt-4o";
227
+ const { createOpenAI } = __require("@ai-sdk/openai");
228
+ const openai = createOpenAI({ apiKey });
229
+ return openai(model);
230
+ }
231
+ function createAnthropicModel() {
232
+ const apiKey = requireEnv("ANTHROPIC_API_KEY");
233
+ const model = process.env.ANTHROPIC_MODEL ?? "claude-sonnet-4-6";
234
+ const { createAnthropic } = __require("@ai-sdk/anthropic");
235
+ const anthropic = createAnthropic({ apiKey });
236
+ return anthropic(model);
237
+ }
238
+ function requireEnv(name) {
239
+ const val = process.env[name];
240
+ if (!val) {
241
+ throw new Error(`Required environment variable ${name} is not set.`);
242
+ }
243
+ return val;
244
+ }
245
+ function getModelId(provider) {
246
+ switch (provider) {
247
+ case "azure":
248
+ return process.env.AZURE_OPENAI_DEPLOYMENT ?? "unknown";
249
+ case "openai":
250
+ return process.env.OPENAI_MODEL ?? "gpt-4o";
251
+ case "anthropic":
252
+ return process.env.ANTHROPIC_MODEL ?? "claude-sonnet-4-6";
253
+ }
254
+ }
255
+ var init_providers = __esm({
256
+ "src/providers/index.ts"() {
257
+ }
258
+ });
259
+ async function analyzeRoutes(routes, options) {
260
+ const modelId = getModelId(options.provider);
261
+ const cache = options.cache ? new RouteCache(options.cacheDir) : null;
262
+ let model = null;
263
+ const getModel = () => {
264
+ if (!model) model = createModel(options.provider);
265
+ return model;
266
+ };
267
+ const results = [];
268
+ for (const route of routes) {
269
+ const result = await analyzeRoute(route, options, modelId, cache, getModel);
270
+ results.push(result);
271
+ }
272
+ return results;
273
+ }
274
+ async function analyzeRoute(route, options, modelId, cache, getModel) {
275
+ if (route.hasExactJsdoc && route.exactPathItem) {
276
+ return {
277
+ urlPath: route.urlPath,
278
+ pathItem: route.exactPathItem,
279
+ fromCache: false,
280
+ skippedLLM: true
281
+ };
282
+ }
283
+ if (options.jsdocMode === "exact") {
284
+ if (route.hasExactJsdoc && route.exactPathItem) {
285
+ return {
286
+ urlPath: route.urlPath,
287
+ pathItem: route.exactPathItem,
288
+ fromCache: false,
289
+ skippedLLM: true
290
+ };
291
+ }
292
+ }
293
+ const hash = computeHash(route.sourceCode, options.provider, modelId);
294
+ if (cache) {
295
+ const cached = cache.get(hash);
296
+ if (cached) {
297
+ return {
298
+ urlPath: route.urlPath,
299
+ pathItem: cached,
300
+ fromCache: true,
301
+ skippedLLM: true
302
+ };
303
+ }
304
+ }
305
+ const pathItem = await callLLM(route, options.jsdocMode, getModel());
306
+ if (cache) {
307
+ cache.set(hash, pathItem);
308
+ }
309
+ return {
310
+ urlPath: route.urlPath,
311
+ pathItem,
312
+ fromCache: false,
313
+ skippedLLM: false
314
+ };
315
+ }
316
+ function buildPrompt(route, jsdocMode) {
317
+ const jsDocSection = route.jsdocComments.length > 0 ? `JSDoc COMMENTS (use as ${jsdocMode === "context" ? "additional context" : "primary source"}):
318
+ ${route.jsdocComments.join("\n\n")}` : "No JSDoc comments found.";
319
+ return `You are an OpenAPI 3.1 specification generator. Analyze the following Next.js API route and extract a complete OpenAPI PathItem object.
320
+
321
+ FILE PATH: ${route.relativePath}
322
+ INFERRED URL PATH: ${route.urlPath}
323
+
324
+ SOURCE CODE:
325
+ \`\`\`typescript
326
+ ${route.sourceCode}
327
+ \`\`\`
328
+
329
+ ${jsDocSection}
330
+
331
+ Extract the following for EACH exported HTTP method handler (GET, POST, PUT, PATCH, DELETE):
332
+ - operationId (camelCase, unique)
333
+ - summary (short description)
334
+ - description (detailed description)
335
+ - path parameters (from URL segments like [id])
336
+ - query parameters (from NextRequest.nextUrl.searchParams usage)
337
+ - request body schema (from request.json() usage and TypeScript types)
338
+ - response schemas (per status code, from NextResponse.json() calls and return types)
339
+ - tags (infer from path segments)
340
+ - security requirements (if auth middleware or token checks are present)
341
+
342
+ 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.`;
343
+ }
344
+ async function callLLM(route, jsdocMode, model) {
345
+ const prompt = buildPrompt(route, jsdocMode);
346
+ const { text } = await ai.generateText({
347
+ model,
348
+ prompt,
349
+ temperature: 0
350
+ });
351
+ return parsePathItem(text, route.urlPath);
352
+ }
353
+ function parsePathItem(text, urlPath) {
354
+ let json = text.trim();
355
+ if (json.startsWith("```")) {
356
+ json = json.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, "").trim();
357
+ }
358
+ try {
359
+ const parsed = JSON.parse(json);
360
+ if (typeof parsed !== "object" || Array.isArray(parsed) || parsed === null) {
361
+ throw new Error("Response is not a JSON object");
362
+ }
363
+ return parsed;
364
+ } catch (err) {
365
+ console.warn(
366
+ `Warning: Failed to parse LLM response for ${urlPath}. Using empty PathItem.`,
367
+ err
368
+ );
369
+ return {};
370
+ }
371
+ }
372
+ var init_analyzer = __esm({
373
+ "src/analyzer.ts"() {
374
+ init_cache();
375
+ init_providers();
376
+ }
377
+ });
378
+ function assembleSpec(config, routes) {
379
+ const paths = {};
380
+ for (const route of routes) {
381
+ if (Object.keys(route.pathItem).length > 0) {
382
+ paths[route.urlPath] = route.pathItem;
383
+ }
384
+ }
385
+ const spec = {
386
+ openapi: "3.1.0",
387
+ info: {
388
+ title: config.openapi.title,
389
+ version: config.openapi.version,
390
+ ...config.openapi.description ? { description: config.openapi.description } : {}
391
+ },
392
+ paths
393
+ };
394
+ if (config.openapi.servers && config.openapi.servers.length > 0) {
395
+ spec.servers = config.openapi.servers;
396
+ }
397
+ return spec;
398
+ }
399
+ function writeOutputFiles(config, spec, cwd = process.cwd()) {
400
+ writeSpecFiles(config, spec, cwd);
401
+ if (config.output.scalarDocs) {
402
+ writeScalarRoute(config, cwd);
403
+ }
404
+ }
405
+ function writeSpecFiles(config, spec, cwd) {
406
+ const specRoutePath = path.resolve(cwd, config.output.specPath);
407
+ const specDir = path.dirname(specRoutePath);
408
+ ensureDir(specDir);
409
+ const specJsonPath = path.join(specDir, "spec.json");
410
+ fs.writeFileSync(specJsonPath, JSON.stringify(spec, null, 2), "utf8");
411
+ const routeContent = `import spec from './spec.json';
412
+
413
+ export const dynamic = 'force-static';
414
+
415
+ export function GET() {
416
+ return Response.json(spec);
417
+ }
418
+ `;
419
+ fs.writeFileSync(specRoutePath, routeContent, "utf8");
420
+ }
421
+ function writeScalarRoute(config, cwd) {
422
+ const scalarRoutePath = path.resolve(cwd, config.output.scalarPath);
423
+ ensureDir(path.dirname(scalarRoutePath));
424
+ const specUrl = filePathToApiUrl(config.output.specPath);
425
+ const routeContent = `export const dynamic = 'force-static';
426
+
427
+ export function GET() {
428
+ return new Response(
429
+ \`<!doctype html>
430
+ <html>
431
+ <head>
432
+ <title>API Docs</title>
433
+ <meta charset="utf-8" />
434
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
435
+ </head>
436
+ <body>
437
+ <script
438
+ id="api-reference"
439
+ data-url="${specUrl}"
440
+ ></script>
441
+ <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
442
+ </body>
443
+ </html>\`,
444
+ { headers: { 'Content-Type': 'text/html' } }
445
+ );
446
+ }
447
+ `;
448
+ fs.writeFileSync(scalarRoutePath, routeContent, "utf8");
449
+ }
450
+ function filePathToApiUrl(filePath) {
451
+ let path = filePath.replace(/\\/g, "/");
452
+ path = path.replace(/^(src\/)?app\//, "");
453
+ path = path.replace(/\/route\.(ts|tsx|js|jsx)$/, "");
454
+ if (!path.startsWith("/")) path = "/" + path;
455
+ return path;
456
+ }
457
+ function ensureDir(dir) {
458
+ if (!fs.existsSync(dir)) {
459
+ fs.mkdirSync(dir, { recursive: true });
460
+ }
461
+ }
462
+ var init_generator = __esm({
463
+ "src/generator.ts"() {
464
+ }
465
+ });
466
+
467
+ // src/index.ts
468
+ var src_exports = {};
469
+ __export(src_exports, {
470
+ analyzeRoutes: () => analyzeRoutes,
471
+ assembleSpec: () => assembleSpec,
472
+ filePathToUrlPath: () => filePathToUrlPath,
473
+ generate: () => generate,
474
+ loadConfig: () => loadConfig,
475
+ resolveConfig: () => resolveConfig,
476
+ scanRoutes: () => scanRoutes,
477
+ writeOutputFiles: () => writeOutputFiles
478
+ });
479
+ async function generate(options = {}) {
480
+ const cwd = options.cwd ?? process.cwd();
481
+ const config = await loadConfig(options.config);
482
+ if (options.provider) config.provider = options.provider;
483
+ if (options.cache === false) config.cache = false;
484
+ console.log(`[openapi-ai-generator] Scanning routes...`);
485
+ const routes = await scanRoutes(config.include, config.exclude, cwd);
486
+ console.log(`[openapi-ai-generator] Found ${routes.length} route(s)`);
487
+ console.log(`[openapi-ai-generator] Analyzing routes with provider: ${config.provider}`);
488
+ const analyzed = await analyzeRoutes(routes, {
489
+ provider: config.provider,
490
+ jsdocMode: config.jsdocMode,
491
+ cache: config.cache,
492
+ cacheDir: config.cacheDir
493
+ });
494
+ const fromCache = analyzed.filter((r) => r.fromCache).length;
495
+ const skippedLLM = analyzed.filter((r) => r.skippedLLM).length;
496
+ console.log(
497
+ `[openapi-ai-generator] ${analyzed.length} routes analyzed (${fromCache} from cache, ${skippedLLM - fromCache} exact JSDoc)`
498
+ );
499
+ const spec = assembleSpec(config, analyzed);
500
+ writeOutputFiles(config, spec, cwd);
501
+ console.log(`[openapi-ai-generator] Spec written to ${config.output.specPath}`);
502
+ if (config.output.scalarDocs) {
503
+ console.log(`[openapi-ai-generator] Scalar docs written to ${config.output.scalarPath}`);
504
+ }
505
+ return {
506
+ routesAnalyzed: analyzed.length,
507
+ routesFromCache: fromCache,
508
+ routesSkippedLLM: skippedLLM,
509
+ specPath: config.output.specPath
510
+ };
511
+ }
512
+ var init_src = __esm({
513
+ "src/index.ts"() {
514
+ init_config();
515
+ init_scanner();
516
+ init_analyzer();
517
+ init_generator();
518
+ init_config();
519
+ init_scanner();
520
+ init_analyzer();
521
+ init_generator();
522
+ }
523
+ });
524
+
525
+ // src/plugin.ts
526
+ function withOpenAPIGen(nextConfig = {}, options = {}) {
527
+ return {
528
+ ...nextConfig,
529
+ webpack(config, context) {
530
+ const existingPlugins = config.plugins ?? [];
531
+ config.plugins = [
532
+ ...existingPlugins,
533
+ new OpenAPIGenWebpackPlugin(options)
534
+ ];
535
+ const existingWebpack = nextConfig.webpack;
536
+ if (existingWebpack) {
537
+ return existingWebpack(config, context);
538
+ }
539
+ return config;
540
+ }
541
+ };
542
+ }
543
+ var OpenAPIGenWebpackPlugin = class {
544
+ options;
545
+ hasRun = false;
546
+ constructor(options) {
547
+ this.options = options;
548
+ }
549
+ apply(compiler) {
550
+ compiler.hooks.beforeRun.tapAsync(
551
+ "OpenAPIGenPlugin",
552
+ async (_compiler, callback) => {
553
+ if (this.hasRun) {
554
+ callback();
555
+ return;
556
+ }
557
+ this.hasRun = true;
558
+ try {
559
+ const { generate: generate2 } = await Promise.resolve().then(() => (init_src(), src_exports));
560
+ await generate2(this.options);
561
+ callback();
562
+ } catch (err) {
563
+ callback(err instanceof Error ? err : new Error(String(err)));
564
+ }
565
+ }
566
+ );
567
+ }
568
+ };
569
+
570
+ exports.withOpenAPIGen = withOpenAPIGen;
571
+ //# sourceMappingURL=plugin.js.map
572
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config.ts","../src/scanner.ts","../src/cache.ts","../src/providers/index.ts","../src/analyzer.ts","../src/generator.ts","../src/index.ts","../src/plugin.ts"],"names":["resolve","existsSync","url","pathToFileURL","glob","relative","readFileSync","createHash","mkdirSync","join","writeFileSync","generateText","dirname","generate"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAwCO,SAAS,cAAc,MAAA,EAA0C;AACtE,EAAA,OAAO;AAAA,IACL,GAAG,QAAA;AAAA,IACH,GAAG,MAAA;AAAA,IACH,MAAA,EAAQ;AAAA,MACN,UAAA,EAAY,KAAA;AAAA,MACZ,UAAA,EAAY,2BAAA;AAAA,MACZ,GAAG,MAAA,CAAO;AAAA,KACZ;AAAA,IACA,OAAA,EAAS;AAAA,MACP,WAAA,EAAa,EAAA;AAAA,MACb,SAAS,EAAC;AAAA,MACV,GAAG,MAAA,CAAO;AAAA;AACZ,GACF;AACF;AAEA,eAAsB,WAAW,UAAA,EAA8C;AAC7E,EAAA,MAAM,WAAA,GAAc,UAAA,GAChB,CAAC,UAAU,CAAA,GACX;AAAA,IACE,uBAAA;AAAA,IACA,uBAAA;AAAA,IACA,wBAAA;AAAA,IACA;AAAA,GACF;AAEJ,EAAA,KAAA,MAAW,KAAK,WAAA,EAAa;AAC3B,IAAA,MAAM,GAAA,GAAMA,YAAA,CAAQ,OAAA,CAAQ,GAAA,IAAO,CAAC,CAAA;AACpC,IAAA,IAAIC,aAAA,CAAW,GAAG,CAAA,EAAG;AACnB,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,IAC7B;AAAA,EACF;AAEA,EAAA,MAAM,IAAI,KAAA;AAAA,IACR;AAAA,GACF;AACF;AAEA,eAAe,aAAa,QAAA,EAA8E;AAExG,EAAA,IAAI,QAAA,CAAS,QAAA,CAAS,KAAK,CAAA,EAAG;AAC5B,IAAA,OAAO,uBAAuB,QAAQ,CAAA;AAAA,EACxC;AACA,EAAA,MAAMC,KAAA,GAAMC,iBAAA,CAAc,QAAQ,CAAA,CAAE,IAAA;AACpC,EAAA,OAAO,OAAOD,KAAA,CAAA;AAChB;AAEA,eAAe,uBAAuB,QAAA,EAA8E;AAElH,EAAA,IAAI;AAEF,IAAA,MAAM,EAAE,QAAA,EAAS,GAAI,MAAM,OAAO,QAAQ,CAAA;AAE1C,IAAA,MAAMA,KAAA,GAAMC,iBAAA,CAAc,QAAQ,CAAA,CAAE,IAAA;AACpC,IAAA,OAAO,MAAM,OAAOD,KAAA,CAAA;AAAA,EACtB,CAAA,CAAA,MAAQ;AAEN,IAAA,IAAI;AAEF,MAAA,SAAA,CAAQ,kBAAkB,CAAA;AAE1B,MAAA,OAAO,UAAQ,QAAQ,CAAA;AAAA,IACzB,CAAA,CAAA,MAAQ;AACN,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,uCAAuC,QAAQ,CAAA,mDAAA;AAAA,OAEjD;AAAA,IACF;AAAA,EACF;AACF;AAhHA,IAgCM,QAAA;AAhCN,IAAA,WAAA,GAAA,KAAA,CAAA;AAAA,EAAA,eAAA,GAAA;AAgCA,IAAM,QAAA,GAAoE;AAAA,MACxE,SAAA,EAAW,SAAA;AAAA,MACX,KAAA,EAAO,IAAA;AAAA,MACP,QAAA,EAAU,gBAAA;AAAA,MACV,OAAA,EAAS,CAAC,yBAAyB,CAAA;AAAA,MACnC,SAAS;AAAC,KACZ;AAAA,EAAA;AAAA,CAAA,CAAA;ACxBA,eAAsB,WACpB,OAAA,EACA,OAAA,EACA,GAAA,GAAc,OAAA,CAAQ,KAAI,EACJ;AACtB,EAAA,MAAM,KAAA,GAAQ,MAAME,SAAA,CAAK,OAAA,EAAS;AAAA,IAChC,GAAA;AAAA,IACA,MAAA,EAAQ,OAAA;AAAA,IACR,QAAA,EAAU;AAAA,GACX,CAAA;AAED,EAAA,OAAO,MAAM,GAAA,CAAI,CAAC,aAAa,UAAA,CAAW,QAAA,EAAU,GAAG,CAAC,CAAA;AAC1D;AAEA,SAAS,UAAA,CAAW,UAAkB,GAAA,EAAwB;AAC5D,EAAA,MAAM,YAAA,GAAeC,aAAA,CAAS,GAAA,EAAK,QAAQ,CAAA;AAC3C,EAAA,MAAM,UAAA,GAAaC,eAAA,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,IACL,QAAA;AAAA,IACA,YAAA;AAAA,IACA,OAAA;AAAA,IACA,UAAA;AAAA,IACA,aAAA;AAAA,IACA,aAAA;AAAA,IACA;AAAA,GACF;AACF;AAMO,SAAS,kBAAkB,QAAA,EAA0B;AAE1D,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;AAEjD,IAAA,IAAI,KAAA,CAAM,UAAA,CAAW,KAAK,CAAA,EAAG;AAC3B,MAAA,OAAO,CAAA,CAAA,EAAI,KAAA,CAAM,KAAA,CAAM,CAAC,CAAC,CAAA,CAAA,CAAA;AAAA,IAC3B;AACA,IAAA,OAAO,IAAI,KAAK,CAAA,CAAA,CAAA;AAAA,EAClB,CAAC,CAAA;AAGD,EAAA,IAAI,CAAC,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,EAAG;AACzB,IAAA,IAAA,GAAO,GAAA,GAAM,IAAA;AAAA,EACf;AAEA,EAAA,OAAO,IAAA;AACT;AAQA,SAAS,aAAa,UAAA,EAAiC;AAErD,EAAA,MAAM,UAAA,GAAa,uBAAA;AACnB,EAAA,MAAM,gBAA0B,EAAC;AACjC,EAAA,IAAI,aAAA,GAAgB,KAAA;AACpB,EAAA,IAAI,aAAA;AAEJ,EAAA,IAAI,KAAA;AACJ,EAAA,OAAA,CAAQ,KAAA,GAAQ,UAAA,CAAW,IAAA,CAAK,UAAU,OAAO,IAAA,EAAM;AACrD,IAAA,MAAM,OAAA,GAAU,MAAM,CAAC,CAAA;AACvB,IAAA,aAAA,CAAc,KAAK,OAAO,CAAA;AAG1B,IAAA,IAAI,gBAAA,CAAiB,IAAA,CAAK,OAAO,CAAA,EAAG;AAClC,MAAA,aAAA,GAAgB,IAAA;AAEhB,MAAA,MAAM,YAAA,GAAe,OAAA,CAAQ,KAAA,CAAM,4CAA4C,CAAA;AAC/E,MAAA,IAAI,YAAA,EAAc;AAChB,QAAA,IAAI;AAEF,UAAA,MAAM,UAAU,YAAA,CAAa,CAAC,EAC3B,KAAA,CAAM,IAAI,EACV,GAAA,CAAI,CAAC,SAAS,IAAA,CAAK,OAAA,CAAQ,aAAa,EAAE,CAAC,EAC3C,IAAA,CAAK,IAAI,EACT,IAAA,EAAK;AACR,UAAA,aAAA,GAAgB,IAAA,CAAK,MAAM,OAAO,CAAA;AAAA,QACpC,CAAA,CAAA,MAAQ;AAEN,UAAA,aAAA,GAAgB,KAAA;AAAA,QAClB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO,EAAE,aAAA,EAAe,aAAA,EAAe,aAAA,EAAc;AACvD;AArHA,IAAA,YAAA,GAAA,KAAA,CAAA;AAAA,EAAA,gBAAA,GAAA;AAAA,EAAA;AAAA,CAAA,CAAA;ACUO,SAAS,WAAA,CAAY,OAAA,EAAiB,QAAA,EAAkB,OAAA,EAAyB;AACtF,EAAA,OAAOC,iBAAA,CAAW,QAAQ,CAAA,CACvB,MAAA,CAAO,OAAO,CAAA,CACd,MAAA,CAAO,QAAQ,CAAA,CACf,MAAA,CAAO,OAAO,CAAA,CACd,OAAO,KAAK,CAAA;AACjB;AAhBA,IAkBa,UAAA;AAlBb,IAAA,UAAA,GAAA,KAAA,CAAA;AAAA,EAAA,cAAA,GAAA;AAkBO,IAAM,aAAN,MAAiB;AAAA,MACL,QAAA;AAAA,MAEjB,YAAY,QAAA,EAAkB;AAC5B,QAAA,IAAA,CAAK,QAAA,GAAW,QAAA;AAAA,MAClB;AAAA,MAEQ,SAAA,GAAkB;AACxB,QAAA,IAAI,CAACN,aAAAA,CAAW,IAAA,CAAK,QAAQ,CAAA,EAAG;AAC9B,UAAAO,YAAA,CAAU,IAAA,CAAK,QAAA,EAAU,EAAE,SAAA,EAAW,MAAM,CAAA;AAAA,QAC9C;AAAA,MACF;AAAA,MAEA,IAAI,IAAA,EAA8C;AAChD,QAAA,MAAM,WAAWC,SAAA,CAAK,IAAA,CAAK,QAAA,EAAU,CAAA,EAAG,IAAI,CAAA,KAAA,CAAO,CAAA;AACnD,QAAA,IAAI,CAACR,aAAAA,CAAW,QAAQ,CAAA,EAAG,OAAO,IAAA;AAClC,QAAA,IAAI;AACF,UAAA,MAAM,QAAoB,IAAA,CAAK,KAAA,CAAMK,eAAAA,CAAa,QAAA,EAAU,MAAM,CAAC,CAAA;AACnE,UAAA,OAAO,KAAA,CAAM,QAAA;AAAA,QACf,CAAA,CAAA,MAAQ;AACN,UAAA,OAAO,IAAA;AAAA,QACT;AAAA,MACF;AAAA,MAEA,GAAA,CAAI,MAAc,QAAA,EAAyC;AACzD,QAAA,IAAA,CAAK,SAAA,EAAU;AACf,QAAA,MAAM,KAAA,GAAoB;AAAA,UACxB,IAAA;AAAA,UACA,QAAA;AAAA,UACA,QAAA,EAAA,iBAAU,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,SACnC;AACA,QAAA,MAAM,WAAWG,SAAA,CAAK,IAAA,CAAK,QAAA,EAAU,CAAA,EAAG,IAAI,CAAA,KAAA,CAAO,CAAA;AACnD,QAAAC,gBAAA,CAAc,UAAU,IAAA,CAAK,SAAA,CAAU,OAAO,IAAA,EAAM,CAAC,GAAG,MAAM,CAAA;AAAA,MAChE;AAAA,KACF;AAAA,EAAA;AAAA,CAAA,CAAA;;;ACjDO,SAAS,YAAY,QAAA,EAAmC;AAC7D,EAAA,QAAQ,QAAA;AAAU,IAChB,KAAK,OAAA;AACH,MAAA,OAAO,gBAAA,EAAiB;AAAA,IAC1B,KAAK,QAAA;AACH,MAAA,OAAO,iBAAA,EAAkB;AAAA,IAC3B,KAAK,WAAA;AACH,MAAA,OAAO,oBAAA,EAAqB;AAAA,IAC9B,SAAS;AACP,MAAA,MAAM,WAAA,GAAqB,QAAA;AAC3B,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,kBAAA,EAAqB,WAAW,CAAA,CAAE,CAAA;AAAA,IACpD;AAAA;AAEJ;AAEA,SAAS,gBAAA,GAAkC;AACzC,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;AACzB;AAEA,SAAS,iBAAA,GAAmC;AAC1C,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;AACrB;AAEA,SAAS,oBAAA,GAAsC;AAC7C,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;AACxB;AAEA,SAAS,WAAW,IAAA,EAAsB;AACxC,EAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,GAAA,CAAI,IAAI,CAAA;AAC5B,EAAA,IAAI,CAAC,GAAA,EAAK;AACR,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,8BAAA,EAAiC,IAAI,CAAA,YAAA,CAAc,CAAA;AAAA,EACrE;AACA,EAAA,OAAO,GAAA;AACT;AAEO,SAAS,WAAW,QAAA,EAA4B;AACrD,EAAA,QAAQ,QAAA;AAAU,IAChB,KAAK,OAAA;AACH,MAAA,OAAO,OAAA,CAAQ,IAAI,uBAAA,IAA2B,SAAA;AAAA,IAChD,KAAK,QAAA;AACH,MAAA,OAAO,OAAA,CAAQ,IAAI,YAAA,IAAgB,QAAA;AAAA,IACrC,KAAK,WAAA;AACH,MAAA,OAAO,OAAA,CAAQ,IAAI,eAAA,IAAmB,mBAAA;AAAA;AAE5C;AAhEA,IAAA,cAAA,GAAA,KAAA,CAAA;AAAA,EAAA,wBAAA,GAAA;AAAA,EAAA;AAAA,CAAA,CAAA;ACsBA,eAAsB,aAAA,CACpB,QACA,OAAA,EAC0B;AAC1B,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;AACpC,IAAA,IAAI,CAAC,KAAA,EAAO,KAAA,GAAQ,WAAA,CAAY,QAAQ,QAAQ,CAAA;AAChD,IAAA,OAAO,KAAA;AAAA,EACT,CAAA;AAEA,EAAA,MAAM,UAA2B,EAAC;AAElC,EAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,IAAA,MAAM,SAAS,MAAM,YAAA,CAAa,OAAO,OAAA,EAAS,OAAA,EAAS,OAAO,QAAQ,CAAA;AAC1E,IAAA,OAAA,CAAQ,KAAK,MAAM,CAAA;AAAA,EACrB;AAEA,EAAA,OAAO,OAAA;AACT;AAEA,eAAe,YAAA,CACb,KAAA,EACA,OAAA,EACA,OAAA,EACA,OACA,QAAA,EACwB;AAExB,EAAA,IAAI,KAAA,CAAM,aAAA,IAAiB,KAAA,CAAM,aAAA,EAAe;AAC9C,IAAA,OAAO;AAAA,MACL,SAAS,KAAA,CAAM,OAAA;AAAA,MACf,UAAU,KAAA,CAAM,aAAA;AAAA,MAChB,SAAA,EAAW,KAAA;AAAA,MACX,UAAA,EAAY;AAAA,KACd;AAAA,EACF;AAEA,EAAA,IAAI,OAAA,CAAQ,cAAc,OAAA,EAAS;AAEjC,IAAA,IAAI,KAAA,CAAM,aAAA,IAAiB,KAAA,CAAM,aAAA,EAAe;AAC9C,MAAA,OAAO;AAAA,QACL,SAAS,KAAA,CAAM,OAAA;AAAA,QACf,UAAU,KAAA,CAAM,aAAA;AAAA,QAChB,SAAA,EAAW,KAAA;AAAA,QACX,UAAA,EAAY;AAAA,OACd;AAAA,IACF;AAAA,EACF;AAGA,EAAA,MAAM,OAAO,WAAA,CAAY,KAAA,CAAM,UAAA,EAAY,OAAA,CAAQ,UAAU,OAAO,CAAA;AAGpE,EAAA,IAAI,KAAA,EAAO;AACT,IAAA,MAAM,MAAA,GAAS,KAAA,CAAM,GAAA,CAAI,IAAI,CAAA;AAC7B,IAAA,IAAI,MAAA,EAAQ;AACV,MAAA,OAAO;AAAA,QACL,SAAS,KAAA,CAAM,OAAA;AAAA,QACf,QAAA,EAAU,MAAA;AAAA,QACV,SAAA,EAAW,IAAA;AAAA,QACX,UAAA,EAAY;AAAA,OACd;AAAA,IACF;AAAA,EACF;AAGA,EAAA,MAAM,WAAW,MAAM,OAAA,CAAQ,OAAO,OAAA,CAAQ,SAAA,EAAW,UAAU,CAAA;AAGnE,EAAA,IAAI,KAAA,EAAO;AACT,IAAA,KAAA,CAAM,GAAA,CAAI,MAAM,QAAQ,CAAA;AAAA,EAC1B;AAEA,EAAA,OAAO;AAAA,IACL,SAAS,KAAA,CAAM,OAAA;AAAA,IACf,QAAA;AAAA,IACA,SAAA,EAAW,KAAA;AAAA,IACX,UAAA,EAAY;AAAA,GACd;AACF;AAEA,SAAS,WAAA,CAAY,OAAkB,SAAA,EAA8B;AACnE,EAAA,MAAM,YAAA,GACJ,MAAM,aAAA,CAAc,MAAA,GAAS,IACzB,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;AAEN,EAAA,OAAO,CAAA;;AAAA,WAAA,EAEI,MAAM,YAAY;AAAA,mBAAA,EACV,MAAM,OAAO;;AAAA;AAAA;AAAA,EAIhC,MAAM,UAAU;AAAA;;AAAA,EAGhB,YAAY;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA,gJAAA,CAAA;AAcd;AAEA,eAAe,OAAA,CACb,KAAA,EACA,SAAA,EACA,KAAA,EACkC;AAClC,EAAA,MAAM,MAAA,GAAS,WAAA,CAAY,KAAA,EAAO,SAAS,CAAA;AAE3C,EAAA,MAAM,EAAE,IAAA,EAAK,GAAI,MAAMC,eAAA,CAAa;AAAA,IAClC,KAAA;AAAA,IACA,MAAA;AAAA,IACA,WAAA,EAAa;AAAA,GACd,CAAA;AAED,EAAA,OAAO,aAAA,CAAc,IAAA,EAAM,KAAA,CAAM,OAAO,CAAA;AAC1C;AAEA,SAAS,aAAA,CACP,MACA,OAAA,EACyB;AAEzB,EAAA,IAAI,IAAA,GAAO,KAAK,IAAA,EAAK;AACrB,EAAA,IAAI,IAAA,CAAK,UAAA,CAAW,KAAK,CAAA,EAAG;AAC1B,IAAA,IAAA,GAAO,IAAA,CAAK,QAAQ,mBAAA,EAAqB,EAAE,EAAE,OAAA,CAAQ,SAAA,EAAW,EAAE,CAAA,CAAE,IAAA,EAAK;AAAA,EAC3E;AAEA,EAAA,IAAI;AACF,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;AAC1E,MAAA,MAAM,IAAI,MAAM,+BAA+B,CAAA;AAAA,IACjD;AACA,IAAA,OAAO,MAAA;AAAA,EACT,SAAS,GAAA,EAAK;AACZ,IAAA,OAAA,CAAQ,IAAA;AAAA,MACN,6CAA6C,OAAO,CAAA,uBAAA,CAAA;AAAA,MACpD;AAAA,KACF;AACA,IAAA,OAAO,EAAC;AAAA,EACV;AACF;AAlLA,IAAA,aAAA,GAAA,KAAA,CAAA;AAAA,EAAA,iBAAA,GAAA;AAIA,IAAA,UAAA,EAAA;AAEA,IAAA,cAAA,EAAA;AAAA,EAAA;AAAA,CAAA,CAAA;ACUO,SAAS,YAAA,CACd,QACA,MAAA,EACa;AACb,EAAA,MAAM,QAAiC,EAAC;AAExC,EAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,IAAA,IAAI,OAAO,IAAA,CAAK,KAAA,CAAM,QAAQ,CAAA,CAAE,SAAS,CAAA,EAAG;AAC1C,MAAA,KAAA,CAAM,KAAA,CAAM,OAAO,CAAA,GAAI,KAAA,CAAM,QAAA;AAAA,IAC/B;AAAA,EACF;AAEA,EAAA,MAAM,IAAA,GAAoB;AAAA,IACxB,OAAA,EAAS,OAAA;AAAA,IACT,IAAA,EAAM;AAAA,MACJ,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,KAClF;AAAA,IACA;AAAA,GACF;AAEA,EAAA,IAAI,OAAO,OAAA,CAAQ,OAAA,IAAW,OAAO,OAAA,CAAQ,OAAA,CAAQ,SAAS,CAAA,EAAG;AAC/D,IAAA,IAAA,CAAK,OAAA,GAAU,OAAO,OAAA,CAAQ,OAAA;AAAA,EAChC;AAEA,EAAA,OAAO,IAAA;AACT;AAEO,SAAS,iBACd,MAAA,EACA,IAAA,EACA,GAAA,GAAc,OAAA,CAAQ,KAAI,EACpB;AACN,EAAA,cAAA,CAAe,MAAA,EAAQ,MAAM,GAAG,CAAA;AAEhC,EAAA,IAAI,MAAA,CAAO,OAAO,UAAA,EAAY;AAC5B,IAAA,gBAAA,CAAiB,QAAQ,GAAG,CAAA;AAAA,EAC9B;AACF;AAEA,SAAS,cAAA,CACP,MAAA,EACA,IAAA,EACA,GAAA,EACM;AACN,EAAA,MAAM,aAAA,GAAgBX,YAAAA,CAAQ,GAAA,EAAK,MAAA,CAAO,OAAO,QAAQ,CAAA;AACzD,EAAA,MAAM,OAAA,GAAUY,aAAQ,aAAa,CAAA;AAErC,EAAA,SAAA,CAAU,OAAO,CAAA;AAGjB,EAAA,MAAM,YAAA,GAAeH,SAAAA,CAAK,OAAA,EAAS,WAAW,CAAA;AAC9C,EAAAC,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;AACnD;AAEA,SAAS,gBAAA,CAAiB,QAAwB,GAAA,EAAmB;AACnE,EAAA,MAAM,eAAA,GAAkBV,YAAAA,CAAQ,GAAA,EAAK,MAAA,CAAO,OAAO,UAAU,CAAA;AAC7D,EAAA,SAAA,CAAUY,YAAAA,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,EAcL,OAAO,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AASvB,EAAAF,gBAAAA,CAAc,eAAA,EAAiB,YAAA,EAAc,MAAM,CAAA;AACrD;AAKA,SAAS,iBAAiB,QAAA,EAA0B;AAClD,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,SAAU,GAAA,GAAM,IAAA;AACxC,EAAA,OAAO,IAAA;AACT;AAEA,SAAS,UAAU,GAAA,EAAmB;AACpC,EAAA,IAAI,CAACT,aAAAA,CAAW,GAAG,CAAA,EAAG;AACpB,IAAAO,YAAAA,CAAU,GAAA,EAAK,EAAE,SAAA,EAAW,MAAM,CAAA;AAAA,EACpC;AACF;AAnIA,IAAA,cAAA,GAAA,KAAA,CAAA;AAAA,EAAA,kBAAA,GAAA;AAAA,EAAA;AAAA,CAAA,CAAA;;;ACAA,IAAA,WAAA,GAAA,EAAA;AAAA,QAAA,CAAA,WAAA,EAAA;AAAA,EAAA,aAAA,EAAA,MAAA,aAAA;AAAA,EAAA,YAAA,EAAA,MAAA,YAAA;AAAA,EAAA,iBAAA,EAAA,MAAA,iBAAA;AAAA,EAAA,QAAA,EAAA,MAAA,QAAA;AAAA,EAAA,UAAA,EAAA,MAAA,UAAA;AAAA,EAAA,aAAA,EAAA,MAAA,aAAA;AAAA,EAAA,UAAA,EAAA,MAAA,UAAA;AAAA,EAAA,gBAAA,EAAA,MAAA;AAAA,CAAA,CAAA;AA0BA,eAAsB,QAAA,CAAS,OAAA,GAA2B,EAAC,EAA4B;AACrF,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,IAC3C,UAAU,MAAA,CAAO,QAAA;AAAA,IACjB,WAAW,MAAA,CAAO,SAAA;AAAA,IAClB,OAAO,MAAA,CAAO,KAAA;AAAA,IACd,UAAU,MAAA,CAAO;AAAA,GAClB,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,IACN,0BAA0B,QAAA,CAAS,MAAM,qBAAqB,SAAS,CAAA,aAAA,EAAgB,aAAa,SAAS,CAAA,aAAA;AAAA,GAC/G;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;AAC5B,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,8CAAA,EAAiD,MAAA,CAAO,MAAA,CAAO,UAAU,CAAA,CAAE,CAAA;AAAA,EACzF;AAEA,EAAA,OAAO;AAAA,IACL,gBAAgB,QAAA,CAAS,MAAA;AAAA,IACzB,eAAA,EAAiB,SAAA;AAAA,IACjB,gBAAA,EAAkB,UAAA;AAAA,IAClB,QAAA,EAAU,OAAO,MAAA,CAAO;AAAA,GAC1B;AACF;AAlEA,IAAA,QAAA,GAAA,KAAA,CAAA;AAAA,EAAA,cAAA,GAAA;AACA,IAAA,WAAA,EAAA;AACA,IAAA,YAAA,EAAA;AACA,IAAA,aAAA,EAAA;AACA,IAAA,cAAA,EAAA;AAEA,IAAA,WAAA,EAAA;AACA,IAAA,YAAA,EAAA;AACA,IAAA,aAAA,EAAA;AACA,IAAA,cAAA,EAAA;AAAA,EAAA;AAAA,CAAA,CAAA;;;ACWO,SAAS,eACd,UAAA,GAAyB,EAAC,EAC1B,OAAA,GAA2B,EAAC,EAChB;AACZ,EAAA,OAAO;AAAA,IACL,GAAG,UAAA;AAAA,IACH,OAAA,CACE,QACA,OAAA,EACA;AAEA,MAAA,MAAM,eAAA,GAAmB,MAAA,CAAO,OAAA,IAAyB,EAAC;AAC1D,MAAA,MAAA,CAAO,OAAA,GAAU;AAAA,QACf,GAAG,eAAA;AAAA,QACH,IAAI,wBAAwB,OAAO;AAAA,OACrC;AAGA,MAAA,MAAM,kBAAkB,UAAA,CAAW,OAAA;AAInC,MAAA,IAAI,eAAA,EAAiB;AACnB,QAAA,OAAO,eAAA,CAAgB,QAAQ,OAAO,CAAA;AAAA,MACxC;AAEA,MAAA,OAAO,MAAA;AAAA,IACT;AAAA,GACF;AACF;AAEA,IAAM,0BAAN,MAA8B;AAAA,EACX,OAAA;AAAA,EACT,MAAA,GAAS,KAAA;AAAA,EAEjB,YAAY,OAAA,EAA0B;AACpC,IAAA,IAAA,CAAK,OAAA,GAAU,OAAA;AAAA,EACjB;AAAA,EAEA,MAAM,QAAA,EAAiC;AACrC,IAAA,QAAA,CAAS,MAAM,SAAA,CAAU,QAAA;AAAA,MACvB,kBAAA;AAAA,MACA,OAAO,WAAW,QAAA,KAAa;AAE7B,QAAA,IAAI,KAAK,MAAA,EAAQ;AACf,UAAA,QAAA,EAAS;AACT,UAAA;AAAA,QACF;AACA,QAAA,IAAA,CAAK,MAAA,GAAS,IAAA;AAEd,QAAA,IAAI;AACF,UAAA,MAAM,EAAE,QAAA,EAAAK,SAAAA,EAAS,GAAI,MAAM,OAAA,CAAA,OAAA,EAAA,CAAA,IAAA,CAAA,OAAA,QAAA,EAAA,EAAA,WAAA,CAAA,CAAA;AAC3B,UAAA,MAAMA,SAAAA,CAAS,KAAK,OAAO,CAAA;AAC3B,UAAA,QAAA,EAAS;AAAA,QACX,SAAS,GAAA,EAAK;AACZ,UAAA,QAAA,CAAS,GAAA,YAAe,QAAQ,GAAA,GAAM,IAAI,MAAM,MAAA,CAAO,GAAG,CAAC,CAAC,CAAA;AAAA,QAC9D;AAAA,MACF;AAAA,KACF;AAAA,EACF;AACF,CAAA","file":"plugin.js","sourcesContent":["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 { 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 { 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 { 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","import type { GenerateOptions } from './index.js';\n\ntype NextConfig = Record<string, unknown>;\n\ninterface WebpackCompiler {\n hooks: {\n beforeRun: {\n tapAsync: (name: string, fn: (compiler: WebpackCompiler, callback: (err?: Error) => void) => void) => void;\n };\n };\n}\n\n/**\n * Next.js config plugin that runs openapi-ai-generator before each build.\n *\n * @example\n * // next.config.ts\n * import { withOpenAPIGen } from 'openapi-ai-generator/plugin';\n * export default withOpenAPIGen(nextConfig);\n */\nexport function withOpenAPIGen(\n nextConfig: NextConfig = {},\n options: GenerateOptions = {}\n): NextConfig {\n return {\n ...nextConfig,\n webpack(\n config: { plugins?: unknown[] } & Record<string, unknown>,\n context: Record<string, unknown>\n ) {\n // Add webpack plugin that runs before build\n const existingPlugins = (config.plugins as unknown[]) ?? [];\n config.plugins = [\n ...existingPlugins,\n new OpenAPIGenWebpackPlugin(options),\n ];\n\n // Chain existing webpack config\n const existingWebpack = nextConfig.webpack as\n | ((cfg: typeof config, ctx: typeof context) => typeof config)\n | undefined;\n\n if (existingWebpack) {\n return existingWebpack(config, context);\n }\n\n return config;\n },\n };\n}\n\nclass OpenAPIGenWebpackPlugin {\n private readonly options: GenerateOptions;\n private hasRun = false;\n\n constructor(options: GenerateOptions) {\n this.options = options;\n }\n\n apply(compiler: WebpackCompiler): void {\n compiler.hooks.beforeRun.tapAsync(\n 'OpenAPIGenPlugin',\n async (_compiler, callback) => {\n // Only run once per build (not for each chunk compilation)\n if (this.hasRun) {\n callback();\n return;\n }\n this.hasRun = true;\n\n try {\n const { generate } = await import('./index.js');\n await generate(this.options);\n callback();\n } catch (err) {\n callback(err instanceof Error ? err : new Error(String(err)));\n }\n }\n );\n }\n}\n"]}