@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.
Files changed (61) hide show
  1. package/README.adoc +33 -1064
  2. package/bin/doc-tools-mcp.js +720 -0
  3. package/bin/doc-tools.js +1050 -50
  4. package/bin/mcp-tools/antora.js +153 -0
  5. package/bin/mcp-tools/cache.js +89 -0
  6. package/bin/mcp-tools/cloud-regions.js +127 -0
  7. package/bin/mcp-tools/content-review.js +196 -0
  8. package/bin/mcp-tools/crd-docs.js +153 -0
  9. package/bin/mcp-tools/frontmatter.js +138 -0
  10. package/bin/mcp-tools/generated-docs-review.js +887 -0
  11. package/bin/mcp-tools/helm-docs.js +152 -0
  12. package/bin/mcp-tools/index.js +245 -0
  13. package/bin/mcp-tools/job-queue.js +468 -0
  14. package/bin/mcp-tools/mcp-validation.js +266 -0
  15. package/bin/mcp-tools/metrics-docs.js +146 -0
  16. package/bin/mcp-tools/openapi.js +174 -0
  17. package/bin/mcp-tools/prompt-discovery.js +283 -0
  18. package/bin/mcp-tools/property-docs.js +157 -0
  19. package/bin/mcp-tools/rpcn-docs.js +113 -0
  20. package/bin/mcp-tools/rpk-docs.js +141 -0
  21. package/bin/mcp-tools/telemetry.js +211 -0
  22. package/bin/mcp-tools/utils.js +131 -0
  23. package/bin/mcp-tools/versions.js +168 -0
  24. package/cli-utils/convert-doc-links.js +1 -1
  25. package/cli-utils/github-token.js +58 -0
  26. package/cli-utils/self-managed-docs-branch.js +2 -2
  27. package/cli-utils/setup-mcp.js +313 -0
  28. package/docker-compose/25.1/transactions.md +1 -1
  29. package/docker-compose/transactions.md +1 -1
  30. package/extensions/DEVELOPMENT.adoc +464 -0
  31. package/extensions/README.adoc +124 -0
  32. package/extensions/REFERENCE.adoc +768 -0
  33. package/extensions/USER_GUIDE.adoc +339 -0
  34. package/extensions/generate-rp-connect-info.js +3 -4
  35. package/extensions/version-fetcher/get-latest-console-version.js +38 -27
  36. package/extensions/version-fetcher/get-latest-redpanda-version.js +65 -54
  37. package/extensions/version-fetcher/retry-util.js +88 -0
  38. package/extensions/version-fetcher/set-latest-version.js +6 -3
  39. package/macros/DEVELOPMENT.adoc +377 -0
  40. package/macros/README.adoc +105 -0
  41. package/macros/REFERENCE.adoc +222 -0
  42. package/macros/USER_GUIDE.adoc +220 -0
  43. package/macros/rp-connect-components.js +6 -6
  44. package/package.json +12 -3
  45. package/tools/bundle-openapi.js +20 -10
  46. package/tools/cloud-regions/generate-cloud-regions.js +1 -1
  47. package/tools/fetch-from-github.js +18 -4
  48. package/tools/gen-rpk-ascii.py +3 -1
  49. package/tools/generate-cli-docs.js +325 -0
  50. package/tools/get-console-version.js +4 -2
  51. package/tools/get-redpanda-version.js +4 -2
  52. package/tools/metrics/metrics.py +19 -7
  53. package/tools/property-extractor/Makefile +7 -1
  54. package/tools/property-extractor/cloud_config.py +4 -4
  55. package/tools/property-extractor/constant_resolver.py +11 -11
  56. package/tools/property-extractor/property_extractor.py +18 -16
  57. package/tools/property-extractor/topic_property_extractor.py +2 -2
  58. package/tools/property-extractor/transformers.py +7 -7
  59. package/tools/property-extractor/type_definition_extractor.py +4 -4
  60. package/tools/redpanda-connect/README.adoc +1 -1
  61. 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
+ };