te.js 2.0.3 → 2.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.
Files changed (68) hide show
  1. package/README.md +197 -187
  2. package/auto-docs/analysis/handler-analyzer.js +58 -58
  3. package/auto-docs/analysis/source-resolver.js +101 -101
  4. package/auto-docs/constants.js +37 -37
  5. package/auto-docs/docs-llm/index.js +7 -0
  6. package/auto-docs/{llm → docs-llm}/prompts.js +222 -222
  7. package/auto-docs/{llm → docs-llm}/provider.js +132 -187
  8. package/auto-docs/index.js +146 -146
  9. package/auto-docs/openapi/endpoint-processor.js +277 -277
  10. package/auto-docs/openapi/generator.js +107 -107
  11. package/auto-docs/openapi/level3.js +131 -131
  12. package/auto-docs/openapi/spec-builders.js +244 -244
  13. package/auto-docs/ui/docs-ui.js +186 -186
  14. package/auto-docs/utils/logger.js +17 -17
  15. package/auto-docs/utils/strip-usage.js +10 -10
  16. package/cli/docs-command.js +315 -315
  17. package/cli/fly-command.js +71 -71
  18. package/cli/index.js +56 -56
  19. package/database/index.js +165 -165
  20. package/database/mongodb.js +146 -146
  21. package/database/redis.js +201 -201
  22. package/docs/README.md +36 -36
  23. package/docs/ammo.md +362 -362
  24. package/docs/api-reference.md +490 -489
  25. package/docs/auto-docs.md +216 -215
  26. package/docs/cli.md +152 -152
  27. package/docs/configuration.md +275 -233
  28. package/docs/database.md +390 -391
  29. package/docs/error-handling.md +438 -417
  30. package/docs/file-uploads.md +333 -334
  31. package/docs/getting-started.md +214 -215
  32. package/docs/middleware.md +355 -356
  33. package/docs/rate-limiting.md +393 -394
  34. package/docs/routing.md +302 -302
  35. package/package.json +62 -62
  36. package/rate-limit/algorithms/fixed-window.js +141 -141
  37. package/rate-limit/algorithms/sliding-window.js +147 -147
  38. package/rate-limit/algorithms/token-bucket.js +115 -115
  39. package/rate-limit/base.js +165 -165
  40. package/rate-limit/index.js +147 -147
  41. package/rate-limit/storage/base.js +104 -104
  42. package/rate-limit/storage/memory.js +101 -101
  43. package/rate-limit/storage/redis.js +88 -88
  44. package/server/ammo/body-parser.js +220 -220
  45. package/server/ammo/dispatch-helper.js +103 -103
  46. package/server/ammo/enhancer.js +57 -57
  47. package/server/ammo.js +454 -356
  48. package/server/endpoint.js +97 -74
  49. package/server/error.js +9 -9
  50. package/server/errors/code-context.js +125 -0
  51. package/server/errors/llm-error-service.js +140 -0
  52. package/server/files/helper.js +33 -33
  53. package/server/files/uploader.js +143 -143
  54. package/server/handler.js +158 -113
  55. package/server/target.js +185 -175
  56. package/server/targets/middleware-validator.js +22 -22
  57. package/server/targets/path-validator.js +21 -21
  58. package/server/targets/registry.js +160 -160
  59. package/server/targets/shoot-validator.js +21 -21
  60. package/te.js +428 -363
  61. package/utils/auto-register.js +17 -17
  62. package/utils/configuration.js +64 -64
  63. package/utils/errors-llm-config.js +84 -0
  64. package/utils/request-logger.js +43 -43
  65. package/utils/status-codes.js +82 -82
  66. package/utils/tejas-entrypoint-html.js +18 -18
  67. package/auto-docs/llm/index.js +0 -6
  68. package/auto-docs/llm/parse.js +0 -88
@@ -1,187 +1,132 @@
1
- /**
2
- * LLM provider for auto-documentation: single OpenAI-compatible implementation.
3
- * Works with OpenAI, OpenRouter, Ollama (OpenAI-compatible endpoint), Azure, etc.
4
- * Uses fetch() only — no provider-specific npm dependencies.
5
- */
6
-
7
- import { extractJSON, extractJSONArray, reconcileOrderedTags } from './parse.js';
8
- import {
9
- buildSummarizeGroupPrompt,
10
- buildEnhanceEndpointPrompt,
11
- buildEnhanceEndpointPerMethodPrompt,
12
- buildReorderTagsPrompt,
13
- buildOverviewPrompt,
14
- } from './prompts.js';
15
-
16
- const DEFAULT_BASE_URL = 'https://api.openai.com/v1';
17
- const DEFAULT_MODEL = 'gpt-4o-mini';
18
-
19
- /**
20
- * OpenAI-compatible LLM provider. POSTs to {baseURL}/chat/completions.
21
- */
22
- class LLMProvider {
23
- constructor(options = {}) {
24
- this.baseURL = (options.baseURL ?? DEFAULT_BASE_URL).replace(/\/$/, '');
25
- this.model = options.model ?? DEFAULT_MODEL;
26
- this.apiKey = options.apiKey ?? process.env.OPENAI_API_KEY;
27
- this.options = options;
28
- }
29
-
30
- /**
31
- * Send a prompt to the LLM and return the raw text response.
32
- * @param {string} prompt
33
- * @returns {Promise<string>}
34
- */
35
- async analyze(prompt) {
36
- const url = `${this.baseURL}/chat/completions`;
37
- const headers = {
38
- 'Content-Type': 'application/json',
39
- ...(this.apiKey && { Authorization: `Bearer ${this.apiKey}` }),
40
- };
41
- const body = {
42
- model: this.model,
43
- messages: [{ role: 'user', content: prompt }],
44
- };
45
-
46
- const res = await fetch(url, {
47
- method: 'POST',
48
- headers,
49
- body: JSON.stringify(body),
50
- });
51
-
52
- if (!res.ok) {
53
- const text = await res.text();
54
- throw new Error(`LLM request failed (${res.status}): ${text.slice(0, 300)}`);
55
- }
56
-
57
- const data = await res.json();
58
- const content = data.choices?.[0]?.message?.content ?? '';
59
- const text = typeof content === 'string' ? content : JSON.stringify(content);
60
- const rawUsage = data.usage;
61
- const usage = {
62
- prompt_tokens: rawUsage?.prompt_tokens ?? 0,
63
- completion_tokens: rawUsage?.completion_tokens ?? 0,
64
- total_tokens: rawUsage?.total_tokens ?? (rawUsage?.prompt_tokens ?? 0) + (rawUsage?.completion_tokens ?? 0),
65
- };
66
- return { content: text, usage };
67
- }
68
-
69
- /**
70
- * Summarize what a target file (group) does from its endpoints and handler context.
71
- * @param {string} groupId - Group id (e.g. target file path without .target.js)
72
- * @param {Array<object>} endpointsInfo - List of { path, methods, summary?, description?, handlerSource?, dependencySources? }
73
- * @param {string} [dependencySources] - Optional full context (target + dependencies) for Level 3
74
- * @returns {Promise<{ name: string, description: string }>} Tag name and description for OpenAPI
75
- */
76
- async summarizeTargetGroup(groupId, endpointsInfo, dependencySources = '') {
77
- if (!endpointsInfo?.length) {
78
- return { name: groupId, description: '', _usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 } };
79
- }
80
- const prompt = buildSummarizeGroupPrompt(groupId, endpointsInfo, dependencySources);
81
- const { content: raw, usage } = await this.analyze(prompt);
82
- const json = extractJSON(raw);
83
- if (!json)
84
- return {
85
- name: groupId.split('/').pop() || groupId,
86
- description: '',
87
- _usage: usage,
88
- };
89
- return {
90
- name: json.name || groupId.split('/').pop() || groupId,
91
- description: (json.description || '').trim(),
92
- _usage: usage,
93
- };
94
- }
95
-
96
- /**
97
- * Build enhanced OpenAPI-style metadata for an endpoint from handler info.
98
- * @param {object} endpointInfo - { path, methods, metadata?, handlerSource?, dependencySources? }
99
- * @returns {Promise<object>} Enhanced metadata (summary, description, request?, response?)
100
- */
101
- async enhanceEndpointDocs(endpointInfo) {
102
- const { path } = endpointInfo;
103
- const prompt = buildEnhanceEndpointPrompt(endpointInfo);
104
- const { content: raw, usage } = await this.analyze(prompt);
105
- const json = extractJSON(raw);
106
- if (!json) return { summary: path, description: '', _usage: usage };
107
- return {
108
- summary: json.summary || path,
109
- description: json.description || '',
110
- ...(json.request && { request: json.request }),
111
- ...(json.response && { response: json.response }),
112
- _usage: usage,
113
- };
114
- }
115
-
116
- /**
117
- * Build method-specific OpenAPI metadata so each HTTP method gets its own summary, description, request, and response.
118
- * Returns an object keyed by lowercase method (get, put, post, delete, patch, head, options). If the LLM
119
- * returns a flat shape or omits methods, the caller should fall back to shared metadata.
120
- *
121
- * @param {object} endpointInfo - { path, methods, metadata?, handlerSource?, dependencySources? }
122
- * @returns {Promise<object>} Method-keyed metadata, e.g. { get: { summary, description?, response }, put: { summary, request?, response }, ... }
123
- */
124
- async enhanceEndpointDocsPerMethod(endpointInfo) {
125
- const prompt = buildEnhanceEndpointPerMethodPrompt(endpointInfo);
126
- const { content: raw, usage } = await this.analyze(prompt);
127
- const json = extractJSON(raw);
128
- if (!json || typeof json !== 'object') return { _usage: usage, _fallback: true };
129
- return { ...json, _usage: usage };
130
- }
131
-
132
- /**
133
- * Return tag names ordered by importance (most important first) for use in OpenAPI spec.tags.
134
- * @param {object} spec - OpenAPI 3 spec with spec.tags (array of { name, description? })
135
- * @returns {Promise<{ orderedTagNames: string[], _usage?: object }>}
136
- */
137
- async reorderTagsByImportance(spec) {
138
- const tags = spec?.tags ?? [];
139
- if (!tags.length) return { orderedTagNames: [], _usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 } };
140
- const prompt = buildReorderTagsPrompt(spec);
141
- const { content: raw, usage } = await this.analyze(prompt);
142
- const parsed = extractJSON(raw) ?? extractJSONArray(raw);
143
- const orderedTagNames = Array.isArray(parsed)
144
- ? parsed.filter((n) => typeof n === 'string').map((n) => String(n).trim()).filter(Boolean)
145
- : tags.map((t) => t.name);
146
- const orderedTags = reconcileOrderedTags(orderedTagNames, tags);
147
- return {
148
- orderedTagNames: orderedTags.map((t) => t.name),
149
- _usage: usage,
150
- _orderedTags: orderedTags,
151
- };
152
- }
153
-
154
- /**
155
- * Generate a comprehensive project/API overview page in Markdown.
156
- * @param {object} spec - OpenAPI 3 spec (after reorder) with info, tags, paths
157
- * @param {object} [options] - { title?, version?, description? } (defaults from spec.info)
158
- * @returns {Promise<{ markdown: string, _usage?: object }>}
159
- */
160
- async generateOverviewPage(spec, options = {}) {
161
- const prompt = buildOverviewPrompt(spec, options);
162
- const { content: raw, usage } = await this.analyze(prompt);
163
- const markdown = typeof raw === 'string' ? raw.trim() : '';
164
- return { markdown, _usage: usage };
165
- }
166
- }
167
-
168
- /**
169
- * Create an LLM provider from config.
170
- * Single OpenAI-compatible setup: works with OpenAI, OpenRouter, Ollama (compat), Azure, etc.
171
- *
172
- * @param {object} config - { baseURL?, apiKey?, model? }
173
- * - baseURL: e.g. 'https://api.openai.com/v1' | 'https://openrouter.ai/api/v1' | 'http://localhost:11434/v1'
174
- * - apiKey: optional for local (e.g. Ollama); use OPENAI_API_KEY or OPENROUTER_API_KEY
175
- * - model: e.g. 'gpt-4o-mini' | 'openai/gpt-4o-mini' (OpenRouter)
176
- * @returns {LLMProvider}
177
- */
178
- function createProvider(config) {
179
- if (!config || typeof config !== 'object') {
180
- return new LLMProvider({});
181
- }
182
- return new LLMProvider(config);
183
- }
184
-
185
- export { LLMProvider, createProvider };
186
- export { extractJSON, extractJSONArray } from './parse.js';
187
- export default LLMProvider;
1
+ /**
2
+ * LLM provider for auto-documentation: extends shared lib/llm client with doc-specific methods.
3
+ * Single OpenAI-compatible implementation; works with OpenAI, OpenRouter, Ollama, Azure, etc.
4
+ */
5
+
6
+ import { LLMProvider as BaseLLMProvider, extractJSON, extractJSONArray, reconcileOrderedTags } from '../../lib/llm/index.js';
7
+ import {
8
+ buildSummarizeGroupPrompt,
9
+ buildEnhanceEndpointPrompt,
10
+ buildEnhanceEndpointPerMethodPrompt,
11
+ buildReorderTagsPrompt,
12
+ buildOverviewPrompt,
13
+ } from './prompts.js';
14
+
15
+ /**
16
+ * Docs-specific LLM provider: base analyze() from lib/llm plus summarizeTargetGroup, enhanceEndpointDocs, etc.
17
+ */
18
+ class DocsLLMProvider extends BaseLLMProvider {
19
+ /**
20
+ * Summarize what a target file (group) does from its endpoints and handler context.
21
+ * @param {string} groupId - Group id (e.g. target file path without .target.js)
22
+ * @param {Array<object>} endpointsInfo - List of { path, methods, summary?, description?, handlerSource?, dependencySources? }
23
+ * @param {string} [dependencySources] - Optional full context (target + dependencies) for Level 3
24
+ * @returns {Promise<{ name: string, description: string }>} Tag name and description for OpenAPI
25
+ */
26
+ async summarizeTargetGroup(groupId, endpointsInfo, dependencySources = '') {
27
+ if (!endpointsInfo?.length) {
28
+ return { name: groupId, description: '', _usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 } };
29
+ }
30
+ const prompt = buildSummarizeGroupPrompt(groupId, endpointsInfo, dependencySources);
31
+ const { content: raw, usage } = await this.analyze(prompt);
32
+ const json = extractJSON(raw);
33
+ if (!json)
34
+ return {
35
+ name: groupId.split('/').pop() || groupId,
36
+ description: '',
37
+ _usage: usage,
38
+ };
39
+ return {
40
+ name: json.name || groupId.split('/').pop() || groupId,
41
+ description: (json.description || '').trim(),
42
+ _usage: usage,
43
+ };
44
+ }
45
+
46
+ /**
47
+ * Build enhanced OpenAPI-style metadata for an endpoint from handler info.
48
+ * @param {object} endpointInfo - { path, methods, metadata?, handlerSource?, dependencySources? }
49
+ * @returns {Promise<object>} Enhanced metadata (summary, description, request?, response?)
50
+ */
51
+ async enhanceEndpointDocs(endpointInfo) {
52
+ const { path } = endpointInfo;
53
+ const prompt = buildEnhanceEndpointPrompt(endpointInfo);
54
+ const { content: raw, usage } = await this.analyze(prompt);
55
+ const json = extractJSON(raw);
56
+ if (!json) return { summary: path, description: '', _usage: usage };
57
+ return {
58
+ summary: json.summary || path,
59
+ description: json.description || '',
60
+ ...(json.request && { request: json.request }),
61
+ ...(json.response && { response: json.response }),
62
+ _usage: usage,
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Build method-specific OpenAPI metadata so each HTTP method gets its own summary, description, request, and response.
68
+ * Returns an object keyed by lowercase method (get, put, post, delete, patch, head, options). If the LLM
69
+ * returns a flat shape or omits methods, the caller should fall back to shared metadata.
70
+ *
71
+ * @param {object} endpointInfo - { path, methods, metadata?, handlerSource?, dependencySources? }
72
+ * @returns {Promise<object>} Method-keyed metadata, e.g. { get: { summary, description?, response }, put: { summary, request?, response }, ... }
73
+ */
74
+ async enhanceEndpointDocsPerMethod(endpointInfo) {
75
+ const prompt = buildEnhanceEndpointPerMethodPrompt(endpointInfo);
76
+ const { content: raw, usage } = await this.analyze(prompt);
77
+ const json = extractJSON(raw);
78
+ if (!json || typeof json !== 'object') return { _usage: usage, _fallback: true };
79
+ return { ...json, _usage: usage };
80
+ }
81
+
82
+ /**
83
+ * Return tag names ordered by importance (most important first) for use in OpenAPI spec.tags.
84
+ * @param {object} spec - OpenAPI 3 spec with spec.tags (array of { name, description? })
85
+ * @returns {Promise<{ orderedTagNames: string[], _usage?: object }>}
86
+ */
87
+ async reorderTagsByImportance(spec) {
88
+ const tags = spec?.tags ?? [];
89
+ if (!tags.length) return { orderedTagNames: [], _usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 } };
90
+ const prompt = buildReorderTagsPrompt(spec);
91
+ const { content: raw, usage } = await this.analyze(prompt);
92
+ const parsed = extractJSON(raw) ?? extractJSONArray(raw);
93
+ const orderedTagNames = Array.isArray(parsed)
94
+ ? parsed.filter((n) => typeof n === 'string').map((n) => String(n).trim()).filter(Boolean)
95
+ : tags.map((t) => t.name);
96
+ const orderedTags = reconcileOrderedTags(orderedTagNames, tags);
97
+ return {
98
+ orderedTagNames: orderedTags.map((t) => t.name),
99
+ _usage: usage,
100
+ _orderedTags: orderedTags,
101
+ };
102
+ }
103
+
104
+ /**
105
+ * Generate a comprehensive project/API overview page in Markdown.
106
+ * @param {object} spec - OpenAPI 3 spec (after reorder) with info, tags, paths
107
+ * @param {object} [options] - { title?, version?, description? } (defaults from spec.info)
108
+ * @returns {Promise<{ markdown: string, _usage?: object }>}
109
+ */
110
+ async generateOverviewPage(spec, options = {}) {
111
+ const prompt = buildOverviewPrompt(spec, options);
112
+ const { content: raw, usage } = await this.analyze(prompt);
113
+ const markdown = typeof raw === 'string' ? raw.trim() : '';
114
+ return { markdown, _usage: usage };
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Create a docs-specific LLM provider from config (same config shape as lib/llm).
120
+ * @param {object} config - { baseURL?, apiKey?, model? }
121
+ * @returns {DocsLLMProvider}
122
+ */
123
+ function createProvider(config) {
124
+ if (!config || typeof config !== 'object') {
125
+ return new DocsLLMProvider({});
126
+ }
127
+ return new DocsLLMProvider(config);
128
+ }
129
+
130
+ export { DocsLLMProvider as LLMProvider, createProvider };
131
+ export { extractJSON, extractJSONArray, reconcileOrderedTags } from '../../lib/llm/index.js';
132
+ export default DocsLLMProvider;
@@ -1,146 +1,146 @@
1
- /**
2
- * Auto-docs orchestrator.
3
- *
4
- * Module layout:
5
- * - index.js (this file) — generateDocs(registry, options): orchestration, config, spec write, level-3 dispatch
6
- * - openapi/generator.js — build OpenAPI spec from registry (levels 1–2: handler analysis, LLM enhancement)
7
- * - openapi/level3.js — level-3 pipeline: reorder tags by importance, generate and write overview page
8
- * - analysis/handler-analyzer.js — detect HTTP methods from handler source
9
- * - analysis/source-resolver.js — resolve target file and dependencies (for level 2 context)
10
- * - llm/ — LLM provider (enhanceEndpointDocs, summarizeTargetGroup, reorderTagsByImportance, generateOverviewPage)
11
- * - ui/docs-ui.js — build Scalar docs HTML, registerDocRoutes
12
- */
13
-
14
- import { writeFile } from 'node:fs/promises';
15
- import TejLogger from 'tej-logger';
16
- import { createProvider } from './llm/index.js';
17
- import { generateOpenAPISpec } from './openapi/generator.js';
18
- import { runLevel3 } from './openapi/level3.js';
19
- import targetRegistry from '../server/targets/registry.js';
20
-
21
- const logger = new TejLogger('Tejas.AutoDocs');
22
-
23
- /**
24
- * Validate llm config, warn if no API key, create and return LLM provider.
25
- * @param {object} llmConfig - options.llm
26
- * @returns {object} LLM provider instance
27
- */
28
- function validateAndCreateLlm(llmConfig) {
29
- if (!llmConfig || typeof llmConfig !== 'object') {
30
- throw new Error(
31
- 'Documentation generation requires an LLM. Provide options.llm with { baseURL?, apiKey?, model? }.',
32
- );
33
- }
34
- const hasApiKey = llmConfig.apiKey || process.env.OPENAI_API_KEY;
35
- if (!hasApiKey) {
36
- logger.warn(
37
- 'No API key set. Provide options.llm.apiKey or OPENAI_API_KEY. Local providers (e.g. Ollama) may work without a key.',
38
- );
39
- }
40
- return createProvider(llmConfig);
41
- }
42
-
43
- /**
44
- * Log start summary when verbose (endpoints count, model, title, output file, building message).
45
- * @param {object} options - { info?, outputPath? }
46
- * @param {number} targetCount
47
- * @param {object} log - logger
48
- * @param {boolean} verbose
49
- */
50
- function logStartSummary(options, targetCount, log, verbose) {
51
- if (!verbose) return;
52
- const { info, outputPath } = options;
53
- log.info('OpenAPI documentation generation started.');
54
- log.info(` Endpoints in registry: ${targetCount}`);
55
- log.info(` LLM model: ${options.llm?.model ?? 'default'}`);
56
- if (info?.title) log.info(` API title: ${info.title}`);
57
- if (outputPath) log.info(` Output file: ${outputPath}`);
58
- if (targetCount === 0) {
59
- log.warn('No endpoints in registry; OpenAPI spec will be minimal.');
60
- } else {
61
- log.info('Building OpenAPI spec (analyzing handlers, LLM enhancement)...');
62
- }
63
- }
64
-
65
- /**
66
- * Log result summary when verbose (paths count, tags).
67
- * @param {object} spec - OpenAPI spec
68
- * @param {object} log - logger
69
- * @param {boolean} verbose
70
- */
71
- function logResultSummary(spec, log, verbose) {
72
- if (!verbose) return;
73
- const pathCount = spec.paths ? Object.keys(spec.paths).length : 0;
74
- const tagList = Array.isArray(spec.tags) ? spec.tags.map((t) => t.name).join(', ') : '';
75
- log.info(` Paths: ${pathCount}`);
76
- log.info(` Tags (groups): ${spec.tags?.length ?? 0} [ ${tagList || '—'}]`);
77
- }
78
-
79
- /**
80
- * Write spec JSON to outputPath if path is set. Logs when verbose.
81
- * @param {object} spec - OpenAPI spec
82
- * @param {string|undefined} outputPath
83
- * @param {object} log - logger
84
- * @param {boolean} verbose
85
- */
86
- async function writeSpecIfNeeded(spec, outputPath, log, verbose) {
87
- if (!outputPath || typeof outputPath !== 'string') return;
88
- if (verbose) log.info(`Writing spec to ${outputPath}...`);
89
- await writeFile(outputPath, JSON.stringify(spec, null, 2), 'utf8');
90
- if (verbose) log.info(`OpenAPI spec written to ${outputPath}.`);
91
- }
92
-
93
- /**
94
- * Generate OpenAPI 3.0 spec from the target registry using an LLM.
95
- *
96
- * @param {object} [registry] - Target registry with .targets (default: app registry)
97
- * @param {object} [options] - llm (required), info, servers, outputPath, level, dirTargets, overviewPath, verbose
98
- * @returns {Promise<object>} OpenAPI 3.0 spec object
99
- */
100
- export async function generateDocs(registry = targetRegistry, options = {}) {
101
- const {
102
- llm: llmConfig,
103
- info,
104
- servers,
105
- outputPath,
106
- level,
107
- dirTargets,
108
- overviewPath: overviewPathOption,
109
- verbose = false,
110
- } = options;
111
- const targets = registry?.targets ?? [];
112
-
113
- const llm = validateAndCreateLlm(llmConfig);
114
- logStartSummary({ ...options, llm: llmConfig }, targets.length, logger, verbose);
115
-
116
- const spec = await generateOpenAPISpec(registry, {
117
- llm,
118
- info,
119
- servers,
120
- level,
121
- dirTargets,
122
- verbose,
123
- logger,
124
- });
125
-
126
- logResultSummary(spec, logger, verbose);
127
- await writeSpecIfNeeded(spec, outputPath, logger, verbose);
128
-
129
- if (level === 3 && llm) {
130
- await runLevel3(spec, {
131
- outputPath,
132
- overviewPath: overviewPathOption,
133
- info,
134
- verbose,
135
- logger,
136
- }, llm);
137
- }
138
-
139
- if (verbose) logger.info('OpenAPI documentation generation completed.');
140
- return spec;
141
- }
142
-
143
- export { generateOpenAPISpec } from './openapi/generator.js';
144
- export { createProvider } from './llm/index.js';
145
- export { buildDocsPage } from './ui/docs-ui.js';
146
- export { analyzeHandler, detectMethods } from './analysis/handler-analyzer.js';
1
+ /**
2
+ * Auto-docs orchestrator.
3
+ *
4
+ * Module layout:
5
+ * - index.js (this file) — generateDocs(registry, options): orchestration, config, spec write, level-3 dispatch
6
+ * - openapi/generator.js — build OpenAPI spec from registry (levels 1–2: handler analysis, LLM enhancement)
7
+ * - openapi/level3.js — level-3 pipeline: reorder tags by importance, generate and write overview page
8
+ * - analysis/handler-analyzer.js — detect HTTP methods from handler source
9
+ * - analysis/source-resolver.js — resolve target file and dependencies (for level 2 context)
10
+ * - docs-llm/ — Docs-specific LLM provider (enhanceEndpointDocs, summarizeTargetGroup, reorderTagsByImportance, generateOverviewPage)
11
+ * - ui/docs-ui.js — build Scalar docs HTML, registerDocRoutes
12
+ */
13
+
14
+ import { writeFile } from 'node:fs/promises';
15
+ import TejLogger from 'tej-logger';
16
+ import { createProvider } from './docs-llm/index.js';
17
+ import { generateOpenAPISpec } from './openapi/generator.js';
18
+ import { runLevel3 } from './openapi/level3.js';
19
+ import targetRegistry from '../server/targets/registry.js';
20
+
21
+ const logger = new TejLogger('Tejas.AutoDocs');
22
+
23
+ /**
24
+ * Validate llm config, warn if no API key, create and return LLM provider.
25
+ * @param {object} llmConfig - options.llm
26
+ * @returns {object} LLM provider instance
27
+ */
28
+ function validateAndCreateLlm(llmConfig) {
29
+ if (!llmConfig || typeof llmConfig !== 'object') {
30
+ throw new Error(
31
+ 'Documentation generation requires an LLM. Provide options.llm with { baseURL?, apiKey?, model? }.',
32
+ );
33
+ }
34
+ const hasApiKey = llmConfig.apiKey || process.env.OPENAI_API_KEY;
35
+ if (!hasApiKey) {
36
+ logger.warn(
37
+ 'No API key set. Provide options.llm.apiKey or OPENAI_API_KEY. Local providers (e.g. Ollama) may work without a key.',
38
+ );
39
+ }
40
+ return createProvider(llmConfig);
41
+ }
42
+
43
+ /**
44
+ * Log start summary when verbose (endpoints count, model, title, output file, building message).
45
+ * @param {object} options - { info?, outputPath? }
46
+ * @param {number} targetCount
47
+ * @param {object} log - logger
48
+ * @param {boolean} verbose
49
+ */
50
+ function logStartSummary(options, targetCount, log, verbose) {
51
+ if (!verbose) return;
52
+ const { info, outputPath } = options;
53
+ log.info('OpenAPI documentation generation started.');
54
+ log.info(` Endpoints in registry: ${targetCount}`);
55
+ log.info(` LLM model: ${options.llm?.model ?? 'default'}`);
56
+ if (info?.title) log.info(` API title: ${info.title}`);
57
+ if (outputPath) log.info(` Output file: ${outputPath}`);
58
+ if (targetCount === 0) {
59
+ log.warn('No endpoints in registry; OpenAPI spec will be minimal.');
60
+ } else {
61
+ log.info('Building OpenAPI spec (analyzing handlers, LLM enhancement)...');
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Log result summary when verbose (paths count, tags).
67
+ * @param {object} spec - OpenAPI spec
68
+ * @param {object} log - logger
69
+ * @param {boolean} verbose
70
+ */
71
+ function logResultSummary(spec, log, verbose) {
72
+ if (!verbose) return;
73
+ const pathCount = spec.paths ? Object.keys(spec.paths).length : 0;
74
+ const tagList = Array.isArray(spec.tags) ? spec.tags.map((t) => t.name).join(', ') : '';
75
+ log.info(` Paths: ${pathCount}`);
76
+ log.info(` Tags (groups): ${spec.tags?.length ?? 0} [ ${tagList || '—'}]`);
77
+ }
78
+
79
+ /**
80
+ * Write spec JSON to outputPath if path is set. Logs when verbose.
81
+ * @param {object} spec - OpenAPI spec
82
+ * @param {string|undefined} outputPath
83
+ * @param {object} log - logger
84
+ * @param {boolean} verbose
85
+ */
86
+ async function writeSpecIfNeeded(spec, outputPath, log, verbose) {
87
+ if (!outputPath || typeof outputPath !== 'string') return;
88
+ if (verbose) log.info(`Writing spec to ${outputPath}...`);
89
+ await writeFile(outputPath, JSON.stringify(spec, null, 2), 'utf8');
90
+ if (verbose) log.info(`OpenAPI spec written to ${outputPath}.`);
91
+ }
92
+
93
+ /**
94
+ * Generate OpenAPI 3.0 spec from the target registry using an LLM.
95
+ *
96
+ * @param {object} [registry] - Target registry with .targets (default: app registry)
97
+ * @param {object} [options] - llm (required), info, servers, outputPath, level, dirTargets, overviewPath, verbose
98
+ * @returns {Promise<object>} OpenAPI 3.0 spec object
99
+ */
100
+ export async function generateDocs(registry = targetRegistry, options = {}) {
101
+ const {
102
+ llm: llmConfig,
103
+ info,
104
+ servers,
105
+ outputPath,
106
+ level,
107
+ dirTargets,
108
+ overviewPath: overviewPathOption,
109
+ verbose = false,
110
+ } = options;
111
+ const targets = registry?.targets ?? [];
112
+
113
+ const llm = validateAndCreateLlm(llmConfig);
114
+ logStartSummary({ ...options, llm: llmConfig }, targets.length, logger, verbose);
115
+
116
+ const spec = await generateOpenAPISpec(registry, {
117
+ llm,
118
+ info,
119
+ servers,
120
+ level,
121
+ dirTargets,
122
+ verbose,
123
+ logger,
124
+ });
125
+
126
+ logResultSummary(spec, logger, verbose);
127
+ await writeSpecIfNeeded(spec, outputPath, logger, verbose);
128
+
129
+ if (level === 3 && llm) {
130
+ await runLevel3(spec, {
131
+ outputPath,
132
+ overviewPath: overviewPathOption,
133
+ info,
134
+ verbose,
135
+ logger,
136
+ }, llm);
137
+ }
138
+
139
+ if (verbose) logger.info('OpenAPI documentation generation completed.');
140
+ return spec;
141
+ }
142
+
143
+ export { generateOpenAPISpec } from './openapi/generator.js';
144
+ export { createProvider } from './docs-llm/index.js';
145
+ export { buildDocsPage } from './ui/docs-ui.js';
146
+ export { analyzeHandler, detectMethods } from './analysis/handler-analyzer.js';