@redpanda-data/docs-extensions-and-macros 4.12.6 → 4.13.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/README.adoc +33 -1064
- package/bin/doc-tools-mcp.js +720 -0
- package/bin/doc-tools.js +1050 -50
- package/bin/mcp-tools/antora.js +153 -0
- package/bin/mcp-tools/cache.js +89 -0
- package/bin/mcp-tools/cloud-regions.js +127 -0
- package/bin/mcp-tools/content-review.js +196 -0
- package/bin/mcp-tools/crd-docs.js +153 -0
- package/bin/mcp-tools/frontmatter.js +138 -0
- package/bin/mcp-tools/generated-docs-review.js +887 -0
- package/bin/mcp-tools/helm-docs.js +152 -0
- package/bin/mcp-tools/index.js +245 -0
- package/bin/mcp-tools/job-queue.js +468 -0
- package/bin/mcp-tools/mcp-validation.js +266 -0
- package/bin/mcp-tools/metrics-docs.js +146 -0
- package/bin/mcp-tools/openapi.js +174 -0
- package/bin/mcp-tools/prompt-discovery.js +283 -0
- package/bin/mcp-tools/property-docs.js +157 -0
- package/bin/mcp-tools/rpcn-docs.js +113 -0
- package/bin/mcp-tools/rpk-docs.js +141 -0
- package/bin/mcp-tools/telemetry.js +211 -0
- package/bin/mcp-tools/utils.js +131 -0
- package/bin/mcp-tools/versions.js +168 -0
- package/cli-utils/convert-doc-links.js +1 -1
- package/cli-utils/github-token.js +58 -0
- package/cli-utils/self-managed-docs-branch.js +2 -2
- package/cli-utils/setup-mcp.js +313 -0
- package/docker-compose/25.1/transactions.md +1 -1
- package/docker-compose/transactions.md +1 -1
- package/extensions/DEVELOPMENT.adoc +464 -0
- package/extensions/README.adoc +124 -0
- package/extensions/REFERENCE.adoc +768 -0
- package/extensions/USER_GUIDE.adoc +339 -0
- package/extensions/generate-rp-connect-info.js +3 -4
- package/extensions/version-fetcher/get-latest-console-version.js +38 -27
- package/extensions/version-fetcher/get-latest-redpanda-version.js +65 -54
- package/extensions/version-fetcher/retry-util.js +88 -0
- package/extensions/version-fetcher/set-latest-version.js +6 -3
- package/macros/DEVELOPMENT.adoc +377 -0
- package/macros/README.adoc +105 -0
- package/macros/REFERENCE.adoc +222 -0
- package/macros/USER_GUIDE.adoc +220 -0
- package/macros/rp-connect-components.js +6 -6
- package/package.json +12 -3
- package/tools/bundle-openapi.js +20 -10
- package/tools/cloud-regions/generate-cloud-regions.js +1 -1
- package/tools/fetch-from-github.js +18 -4
- package/tools/gen-rpk-ascii.py +3 -1
- package/tools/generate-cli-docs.js +325 -0
- package/tools/get-console-version.js +4 -2
- package/tools/get-redpanda-version.js +4 -2
- package/tools/metrics/metrics.py +19 -7
- package/tools/property-extractor/Makefile +7 -1
- package/tools/property-extractor/cloud_config.py +4 -4
- package/tools/property-extractor/constant_resolver.py +11 -11
- package/tools/property-extractor/property_extractor.py +18 -16
- package/tools/property-extractor/topic_property_extractor.py +2 -2
- package/tools/property-extractor/transformers.py +7 -7
- package/tools/property-extractor/type_definition_extractor.py +4 -4
- package/tools/redpanda-connect/README.adoc +1 -1
- package/tools/redpanda-connect/generate-rpcn-connector-docs.js +5 -3
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Configuration Validation
|
|
3
|
+
*
|
|
4
|
+
* Validates prompts, resources, and overall MCP server configuration.
|
|
5
|
+
* Used at startup and by the validate-mcp CLI command.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { parsePromptFile } = require('./frontmatter');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Validate that a prompt name is safe (no path traversal)
|
|
14
|
+
* @param {string} name - Prompt name to validate
|
|
15
|
+
* @throws {Error} If name is invalid
|
|
16
|
+
*/
|
|
17
|
+
function validatePromptName(name) {
|
|
18
|
+
if (!name || typeof name !== 'string') {
|
|
19
|
+
throw new Error('Prompt name must be a non-empty string');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Only allow alphanumeric, hyphens, and underscores
|
|
23
|
+
if (!/^[a-z0-9_-]+$/i.test(name)) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`Invalid prompt name: ${name}. Use only letters, numbers, hyphens, and underscores.`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return name;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Validate that required arguments are provided
|
|
34
|
+
* @param {string} promptName - Name of the prompt
|
|
35
|
+
* @param {Object} providedArgs - Arguments provided by user
|
|
36
|
+
* @param {Array} schema - Argument schema from prompt metadata
|
|
37
|
+
* @throws {Error} If validation fails
|
|
38
|
+
*/
|
|
39
|
+
function validatePromptArguments(promptName, providedArgs, schema) {
|
|
40
|
+
if (!schema || schema.length === 0) {
|
|
41
|
+
return; // No validation needed
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const errors = [];
|
|
45
|
+
|
|
46
|
+
// Check required arguments
|
|
47
|
+
schema
|
|
48
|
+
.filter(arg => arg.required)
|
|
49
|
+
.forEach(arg => {
|
|
50
|
+
if (!providedArgs || !providedArgs[arg.name]) {
|
|
51
|
+
errors.push(`Missing required argument: ${arg.name}`);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (errors.length > 0) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Invalid arguments for prompt "${promptName}":\n${errors.join('\n')}`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Validate all resources are accessible
|
|
64
|
+
* @param {Array} resources - Resource definitions
|
|
65
|
+
* @param {Object} resourceMap - Resource file mappings
|
|
66
|
+
* @param {string} baseDir - Base directory for resources
|
|
67
|
+
* @returns {{ errors: string[], warnings: string[] }}
|
|
68
|
+
*/
|
|
69
|
+
function validateResources(resources, resourceMap, baseDir) {
|
|
70
|
+
const errors = [];
|
|
71
|
+
const warnings = [];
|
|
72
|
+
|
|
73
|
+
for (const resource of resources) {
|
|
74
|
+
// Check mapping exists
|
|
75
|
+
const mapping = resourceMap[resource.uri];
|
|
76
|
+
if (!mapping) {
|
|
77
|
+
errors.push(`Resource ${resource.uri} has no file mapping in resourceMap`);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Check file exists
|
|
82
|
+
const filePath = path.join(baseDir, 'mcp', 'team-standards', mapping.file);
|
|
83
|
+
if (!fs.existsSync(filePath)) {
|
|
84
|
+
errors.push(`Resource file missing: ${mapping.file} for ${resource.uri}`);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Check file is readable
|
|
89
|
+
try {
|
|
90
|
+
fs.readFileSync(filePath, 'utf8');
|
|
91
|
+
} catch (err) {
|
|
92
|
+
errors.push(`Resource file not readable: ${mapping.file} - ${err.message}`);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Validate resource metadata
|
|
97
|
+
if (!resource.name || resource.name.length < 3) {
|
|
98
|
+
warnings.push(`Resource ${resource.uri} has a very short name`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!resource.description || resource.description.length < 10) {
|
|
102
|
+
warnings.push(`Resource ${resource.uri} has a very short description`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Check for versioning
|
|
106
|
+
if (!resource.version) {
|
|
107
|
+
warnings.push(`Resource ${resource.uri} missing version metadata`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!resource.lastUpdated) {
|
|
111
|
+
warnings.push(`Resource ${resource.uri} missing lastUpdated metadata`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return { errors, warnings };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Validate all prompts are loadable
|
|
120
|
+
* @param {Array} prompts - Discovered prompts
|
|
121
|
+
* @returns {{ errors: string[], warnings: string[] }}
|
|
122
|
+
*/
|
|
123
|
+
function validatePrompts(prompts) {
|
|
124
|
+
const errors = [];
|
|
125
|
+
const warnings = [];
|
|
126
|
+
|
|
127
|
+
for (const prompt of prompts) {
|
|
128
|
+
// Check basic metadata
|
|
129
|
+
if (!prompt.description || prompt.description.length < 10) {
|
|
130
|
+
warnings.push(`Prompt "${prompt.name}" has a very short description`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!prompt.content || prompt.content.trim().length < 100) {
|
|
134
|
+
warnings.push(`Prompt "${prompt.name}" has very little content (< 100 chars)`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Check for version
|
|
138
|
+
if (!prompt.version) {
|
|
139
|
+
warnings.push(`Prompt "${prompt.name}" missing version metadata`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Validate argument format if specified
|
|
143
|
+
if (prompt.argumentFormat) {
|
|
144
|
+
const validFormats = ['content-append', 'structured'];
|
|
145
|
+
if (!validFormats.includes(prompt.argumentFormat)) {
|
|
146
|
+
errors.push(
|
|
147
|
+
`Prompt "${prompt.name}" has invalid argumentFormat: ${prompt.argumentFormat}`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Check for argument schema consistency
|
|
153
|
+
if (prompt.arguments && prompt.arguments.length > 0) {
|
|
154
|
+
if (!prompt.argumentFormat) {
|
|
155
|
+
warnings.push(
|
|
156
|
+
`Prompt "${prompt.name}" has arguments but no argumentFormat specified`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Check for duplicate argument names
|
|
161
|
+
const argNames = new Set();
|
|
162
|
+
for (const arg of prompt.arguments) {
|
|
163
|
+
if (argNames.has(arg.name)) {
|
|
164
|
+
errors.push(`Prompt "${prompt.name}" has duplicate argument: ${arg.name}`);
|
|
165
|
+
}
|
|
166
|
+
argNames.add(arg.name);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return { errors, warnings };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Validate entire MCP configuration
|
|
176
|
+
* @param {Object} config - Configuration object
|
|
177
|
+
* @param {Array} config.resources - Resources
|
|
178
|
+
* @param {Object} config.resourceMap - Resource mappings
|
|
179
|
+
* @param {Array} config.prompts - Prompts
|
|
180
|
+
* @param {string} config.baseDir - Base directory
|
|
181
|
+
* @returns {{ valid: boolean, errors: string[], warnings: string[] }}
|
|
182
|
+
*/
|
|
183
|
+
function validateMcpConfiguration(config) {
|
|
184
|
+
const allErrors = [];
|
|
185
|
+
const allWarnings = [];
|
|
186
|
+
|
|
187
|
+
// Validate resources
|
|
188
|
+
const resourceValidation = validateResources(
|
|
189
|
+
config.resources,
|
|
190
|
+
config.resourceMap,
|
|
191
|
+
config.baseDir
|
|
192
|
+
);
|
|
193
|
+
allErrors.push(...resourceValidation.errors);
|
|
194
|
+
allWarnings.push(...resourceValidation.warnings);
|
|
195
|
+
|
|
196
|
+
// Validate prompts
|
|
197
|
+
const promptValidation = validatePrompts(config.prompts);
|
|
198
|
+
allErrors.push(...promptValidation.errors);
|
|
199
|
+
allWarnings.push(...promptValidation.warnings);
|
|
200
|
+
|
|
201
|
+
// Check for name collisions
|
|
202
|
+
const promptNames = new Set();
|
|
203
|
+
for (const prompt of config.prompts) {
|
|
204
|
+
if (promptNames.has(prompt.name)) {
|
|
205
|
+
allErrors.push(`Duplicate prompt name: ${prompt.name}`);
|
|
206
|
+
}
|
|
207
|
+
promptNames.add(prompt.name);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
valid: allErrors.length === 0,
|
|
212
|
+
errors: allErrors,
|
|
213
|
+
warnings: allWarnings
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Format validation results for display
|
|
219
|
+
* @param {{ valid: boolean, errors: string[], warnings: string[] }} results
|
|
220
|
+
* @returns {string} Formatted output
|
|
221
|
+
*/
|
|
222
|
+
function formatValidationResults(results, config) {
|
|
223
|
+
const lines = [];
|
|
224
|
+
|
|
225
|
+
lines.push('MCP Configuration Validation');
|
|
226
|
+
lines.push('='.repeat(60));
|
|
227
|
+
lines.push('');
|
|
228
|
+
|
|
229
|
+
// Summary
|
|
230
|
+
lines.push(`Prompts found: ${config.prompts.length}`);
|
|
231
|
+
lines.push(`Resources found: ${config.resources.length}`);
|
|
232
|
+
lines.push('');
|
|
233
|
+
|
|
234
|
+
// Warnings
|
|
235
|
+
if (results.warnings.length > 0) {
|
|
236
|
+
lines.push('Warnings:');
|
|
237
|
+
results.warnings.forEach(w => lines.push(` ⚠ ${w}`));
|
|
238
|
+
lines.push('');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Errors
|
|
242
|
+
if (results.errors.length > 0) {
|
|
243
|
+
lines.push('Errors:');
|
|
244
|
+
results.errors.forEach(e => lines.push(` ✗ ${e}`));
|
|
245
|
+
lines.push('');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Result
|
|
249
|
+
if (results.valid) {
|
|
250
|
+
lines.push('✓ Validation passed');
|
|
251
|
+
} else {
|
|
252
|
+
lines.push('✗ Validation failed');
|
|
253
|
+
lines.push(` ${results.errors.length} error(s) must be fixed`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return lines.join('\n');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
module.exports = {
|
|
260
|
+
validatePromptName,
|
|
261
|
+
validatePromptArguments,
|
|
262
|
+
validateResources,
|
|
263
|
+
validatePrompts,
|
|
264
|
+
validateMcpConfiguration,
|
|
265
|
+
formatValidationResults
|
|
266
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tools - Metrics Documentation Generation
|
|
3
|
+
*
|
|
4
|
+
* OPTIMIZATION: This tool calls CLI, doesn't use LLM directly.
|
|
5
|
+
* - No model recommendation (CLI tool)
|
|
6
|
+
* - Cost comes from doc-tools CLI execution
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { spawnSync } = require('child_process');
|
|
10
|
+
const { findRepoRoot, getDocToolsCommand, MAX_EXEC_BUFFER_SIZE, DEFAULT_COMMAND_TIMEOUT } = require('./utils');
|
|
11
|
+
const { getAntoraStructure } = require('./antora');
|
|
12
|
+
const { createJob } = require('./job-queue');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Generate Redpanda metrics documentation
|
|
16
|
+
*
|
|
17
|
+
* Use tags for released content (GA or beta), branches for in-progress content.
|
|
18
|
+
* Defaults to branch "dev" if neither tag nor branch is provided.
|
|
19
|
+
*
|
|
20
|
+
* @param {Object} args - Arguments
|
|
21
|
+
* @param {string} [args.tag] - Git tag for released content (for example, "v25.3.1")
|
|
22
|
+
* @param {string} [args.branch] - Branch name for in-progress content (for example, "dev", "main")
|
|
23
|
+
* @param {boolean} [args.background] - Run as background job
|
|
24
|
+
* @returns {Object} Generation results
|
|
25
|
+
*/
|
|
26
|
+
function generateMetricsDocs(args) {
|
|
27
|
+
const repoRoot = findRepoRoot();
|
|
28
|
+
const structure = getAntoraStructure(repoRoot);
|
|
29
|
+
|
|
30
|
+
if (!structure.hasDocTools) {
|
|
31
|
+
return {
|
|
32
|
+
success: false,
|
|
33
|
+
error: 'doc-tools not found in this repository',
|
|
34
|
+
suggestion: 'Navigate to the docs-extensions-and-macros repository'
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Validate that tag and branch are mutually exclusive
|
|
39
|
+
if (args.tag && args.branch) {
|
|
40
|
+
return {
|
|
41
|
+
success: false,
|
|
42
|
+
error: 'Cannot specify both tag and branch',
|
|
43
|
+
suggestion: 'Use either --tag or --branch, not both'
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Default to 'dev' branch if neither provided
|
|
48
|
+
const gitRef = args.tag || args.branch || 'dev';
|
|
49
|
+
const refType = args.tag ? 'tag' : 'branch';
|
|
50
|
+
|
|
51
|
+
// Normalize version (add 'v' prefix if tag and not present)
|
|
52
|
+
let version = gitRef;
|
|
53
|
+
if (args.tag && !version.startsWith('v')) {
|
|
54
|
+
version = `v${version}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Validate version string to prevent command injection
|
|
58
|
+
const versionRegex = /^[0-9A-Za-z._\/-]+$/;
|
|
59
|
+
if (!versionRegex.test(version)) {
|
|
60
|
+
return {
|
|
61
|
+
success: false,
|
|
62
|
+
error: 'Invalid version format',
|
|
63
|
+
suggestion: 'Version must contain only alphanumeric characters, dots, underscores, slashes, and hyphens (for example, "v25.3.1", "v25.3.1-rc1", "dev", "main")'
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Get doc-tools command (handles both local and installed)
|
|
68
|
+
const docTools = getDocToolsCommand(repoRoot);
|
|
69
|
+
|
|
70
|
+
// Build command arguments
|
|
71
|
+
const baseArgs = ['generate', 'metrics-docs'];
|
|
72
|
+
|
|
73
|
+
if (args.tag) {
|
|
74
|
+
baseArgs.push('--tag');
|
|
75
|
+
baseArgs.push(version);
|
|
76
|
+
} else {
|
|
77
|
+
baseArgs.push('--branch');
|
|
78
|
+
baseArgs.push(version);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// If background mode, create job and return immediately
|
|
82
|
+
if (args.background) {
|
|
83
|
+
const cmdArgs = [docTools.program, ...docTools.getArgs(baseArgs)];
|
|
84
|
+
const jobId = createJob('generate_metrics_docs', cmdArgs, {
|
|
85
|
+
cwd: repoRoot.root
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
success: true,
|
|
90
|
+
background: true,
|
|
91
|
+
job_id: jobId,
|
|
92
|
+
message: `Metrics docs generation started in background. Use get_job_status with job_id: ${jobId} to check progress.`,
|
|
93
|
+
[refType]: version
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Otherwise run synchronously
|
|
98
|
+
try {
|
|
99
|
+
const result = spawnSync(docTools.program, docTools.getArgs(baseArgs), {
|
|
100
|
+
cwd: repoRoot.root,
|
|
101
|
+
encoding: 'utf8',
|
|
102
|
+
stdio: 'pipe',
|
|
103
|
+
maxBuffer: MAX_EXEC_BUFFER_SIZE,
|
|
104
|
+
timeout: DEFAULT_COMMAND_TIMEOUT
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Check for spawn errors
|
|
108
|
+
if (result.error) {
|
|
109
|
+
throw new Error(`Failed to execute command: ${result.error.message}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Check for non-zero exit codes
|
|
113
|
+
if (result.status !== 0) {
|
|
114
|
+
const errorMsg = result.stderr || `Command failed with exit code ${result.status}`;
|
|
115
|
+
throw new Error(errorMsg);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const output = result.stdout;
|
|
119
|
+
|
|
120
|
+
const metricsCountMatch = output.match(/(\d+) metrics/i);
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
success: true,
|
|
124
|
+
[refType]: version,
|
|
125
|
+
files_generated: [
|
|
126
|
+
'modules/reference/pages/public-metrics-reference.adoc'
|
|
127
|
+
],
|
|
128
|
+
metrics_count: metricsCountMatch ? parseInt(metricsCountMatch[1]) : null,
|
|
129
|
+
output: output.trim(),
|
|
130
|
+
summary: `Generated metrics documentation for Redpanda ${refType} ${version}`
|
|
131
|
+
};
|
|
132
|
+
} catch (err) {
|
|
133
|
+
return {
|
|
134
|
+
success: false,
|
|
135
|
+
error: err.message,
|
|
136
|
+
stdout: err.stdout || '',
|
|
137
|
+
stderr: err.stderr || '',
|
|
138
|
+
exitCode: err.status,
|
|
139
|
+
suggestion: 'Check that the version exists in the Redpanda repository'
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = {
|
|
145
|
+
generateMetricsDocs
|
|
146
|
+
};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tools - OpenAPI Bundle Generation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { spawnSync } = require('child_process');
|
|
6
|
+
const { findRepoRoot, MAX_EXEC_BUFFER_SIZE, normalizeVersion, DEFAULT_COMMAND_TIMEOUT } = require('./utils');
|
|
7
|
+
const { getAntoraStructure } = require('./antora');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generate bundled OpenAPI documentation
|
|
11
|
+
*
|
|
12
|
+
* Use tags for released content (GA or beta), branches for in-progress content.
|
|
13
|
+
* Requires either --tag or --branch to be specified.
|
|
14
|
+
*
|
|
15
|
+
* @param {Object} args - Arguments
|
|
16
|
+
* @param {string} [args.tag] - Git tag for released content (for example, "v24.3.2" or "24.3.2")
|
|
17
|
+
* @param {string} [args.branch] - Branch name for in-progress content (for example, "dev", "main")
|
|
18
|
+
* @param {string} [args.repo] - Repository URL
|
|
19
|
+
* @param {string} [args.surface] - Which API surface(s) to bundle: 'admin', 'connect', or 'both'
|
|
20
|
+
* @param {string} [args.out_admin] - Output path for admin API
|
|
21
|
+
* @param {string} [args.out_connect] - Output path for connect API
|
|
22
|
+
* @param {string} [args.admin_major] - Admin API major version
|
|
23
|
+
* @param {boolean} [args.use_admin_major_version] - Use admin major version for info.version
|
|
24
|
+
* @param {boolean} [args.quiet] - Suppress logs
|
|
25
|
+
* @returns {Object} Generation results
|
|
26
|
+
*/
|
|
27
|
+
function generateBundleOpenApi(args) {
|
|
28
|
+
const repoRoot = findRepoRoot();
|
|
29
|
+
const structure = getAntoraStructure(repoRoot);
|
|
30
|
+
|
|
31
|
+
if (!structure.hasDocTools) {
|
|
32
|
+
return {
|
|
33
|
+
success: false,
|
|
34
|
+
error: 'doc-tools not found in this repository',
|
|
35
|
+
suggestion: 'Navigate to the docs-extensions-and-macros repository'
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Validate that either tag or branch is provided (but not both)
|
|
40
|
+
if (!args.tag && !args.branch) {
|
|
41
|
+
return {
|
|
42
|
+
success: false,
|
|
43
|
+
error: 'Either tag or branch is required',
|
|
44
|
+
suggestion: 'Provide --tag "v24.3.2" or --branch "dev"'
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (args.tag && args.branch) {
|
|
49
|
+
return {
|
|
50
|
+
success: false,
|
|
51
|
+
error: 'Cannot specify both tag and branch',
|
|
52
|
+
suggestion: 'Use either --tag or --branch, not both'
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const gitRef = args.tag || args.branch;
|
|
58
|
+
const refType = args.tag ? 'tag' : 'branch';
|
|
59
|
+
|
|
60
|
+
// Normalize version (add 'v' prefix if tag and not present and not branch-like names)
|
|
61
|
+
let version = gitRef;
|
|
62
|
+
if (args.tag && !version.startsWith('v') && version !== 'dev' && version !== 'main') {
|
|
63
|
+
version = `v${version}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Build command arguments array (no shell interpolation)
|
|
67
|
+
const cmdArgs = ['doc-tools', 'generate', 'bundle-openapi'];
|
|
68
|
+
|
|
69
|
+
// Add flags only when present, each as separate array entries
|
|
70
|
+
if (args.tag) {
|
|
71
|
+
cmdArgs.push('--tag');
|
|
72
|
+
cmdArgs.push(version);
|
|
73
|
+
} else {
|
|
74
|
+
cmdArgs.push('--branch');
|
|
75
|
+
cmdArgs.push(version);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (args.repo) {
|
|
79
|
+
cmdArgs.push('--repo');
|
|
80
|
+
cmdArgs.push(args.repo);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (args.surface) {
|
|
84
|
+
cmdArgs.push('--surface');
|
|
85
|
+
cmdArgs.push(args.surface);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (args.out_admin) {
|
|
89
|
+
cmdArgs.push('--out-admin');
|
|
90
|
+
cmdArgs.push(args.out_admin);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (args.out_connect) {
|
|
94
|
+
cmdArgs.push('--out-connect');
|
|
95
|
+
cmdArgs.push(args.out_connect);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (args.admin_major) {
|
|
99
|
+
cmdArgs.push('--admin-major');
|
|
100
|
+
cmdArgs.push(args.admin_major);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (args.use_admin_major_version) {
|
|
104
|
+
cmdArgs.push('--use-admin-major-version');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (args.quiet) {
|
|
108
|
+
cmdArgs.push('--quiet');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const result = spawnSync('npx', cmdArgs, {
|
|
112
|
+
cwd: repoRoot.root,
|
|
113
|
+
encoding: 'utf8',
|
|
114
|
+
stdio: 'pipe',
|
|
115
|
+
maxBuffer: MAX_EXEC_BUFFER_SIZE,
|
|
116
|
+
timeout: DEFAULT_COMMAND_TIMEOUT
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Check for spawn errors
|
|
120
|
+
if (result.error) {
|
|
121
|
+
const err = new Error(`Failed to execute command: ${result.error.message}`);
|
|
122
|
+
err.stdout = result.stdout || '';
|
|
123
|
+
err.stderr = result.stderr || '';
|
|
124
|
+
err.status = result.status;
|
|
125
|
+
throw err;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Check for non-zero exit codes
|
|
129
|
+
if (result.status !== 0) {
|
|
130
|
+
const errorMsg = result.stderr || `Command failed with exit code ${result.status}`;
|
|
131
|
+
const err = new Error(errorMsg);
|
|
132
|
+
err.stdout = result.stdout || '';
|
|
133
|
+
err.stderr = result.stderr || '';
|
|
134
|
+
err.status = result.status;
|
|
135
|
+
throw err;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const output = result.stdout;
|
|
139
|
+
|
|
140
|
+
// Determine which files were generated based on surface
|
|
141
|
+
const filesGenerated = [];
|
|
142
|
+
const surface = args.surface || 'both';
|
|
143
|
+
|
|
144
|
+
if (surface === 'admin' || surface === 'both') {
|
|
145
|
+
filesGenerated.push(args.out_admin || 'admin/redpanda-admin-api.yaml');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (surface === 'connect' || surface === 'both') {
|
|
149
|
+
filesGenerated.push(args.out_connect || 'connect/redpanda-connect-api.yaml');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
success: true,
|
|
154
|
+
[refType]: version,
|
|
155
|
+
surface,
|
|
156
|
+
files_generated: filesGenerated,
|
|
157
|
+
output: output.trim(),
|
|
158
|
+
summary: `Bundled OpenAPI documentation for ${surface} API(s) at ${refType} ${version}`
|
|
159
|
+
};
|
|
160
|
+
} catch (err) {
|
|
161
|
+
return {
|
|
162
|
+
success: false,
|
|
163
|
+
error: err.message,
|
|
164
|
+
stdout: err.stdout || '',
|
|
165
|
+
stderr: err.stderr || '',
|
|
166
|
+
exitCode: err.status,
|
|
167
|
+
suggestion: 'Check that the tag exists in the Redpanda repository'
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = {
|
|
173
|
+
generateBundleOpenApi
|
|
174
|
+
};
|