@redpanda-data/docs-extensions-and-macros 4.13.0 → 4.13.2
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/bin/doc-tools-mcp.js +15 -3
- package/bin/doc-tools.js +767 -2088
- package/bin/mcp-tools/property-docs.js +18 -0
- package/bin/mcp-tools/rpcn-docs.js +28 -3
- package/cli-utils/antora-utils.js +53 -2
- package/cli-utils/dependencies.js +313 -0
- package/cli-utils/diff-utils.js +273 -0
- package/cli-utils/doc-tools-utils.js +54 -0
- package/extensions/algolia-indexer/generate-index.js +134 -102
- package/extensions/algolia-indexer/index.js +70 -38
- package/extensions/collect-bloblang-samples.js +2 -1
- package/extensions/generate-rp-connect-categories.js +126 -67
- package/extensions/generate-rp-connect-info.js +291 -137
- package/macros/rp-connect-components.js +34 -5
- package/mcp/CLI_INTERFACE.adoc +384 -0
- package/mcp/COSTS.adoc +167 -0
- package/mcp/DEVELOPMENT.adoc +726 -0
- package/mcp/README.adoc +172 -0
- package/mcp/USER_GUIDE.adoc +1392 -0
- package/mcp/WRITER_EXTENSION_GUIDE.adoc +814 -0
- package/mcp/prompts/README.adoc +183 -0
- package/mcp/prompts/property-docs-guide.md +283 -0
- package/mcp/prompts/review-for-style.md +128 -0
- package/mcp/prompts/rpcn-connector-docs-guide.md +126 -0
- package/mcp/prompts/write-new-guide.md +222 -0
- package/mcp/team-standards/style-guide.md +321 -0
- package/mcp/templates/README.adoc +212 -0
- package/mcp/templates/prompt-review-template.md +80 -0
- package/mcp/templates/prompt-write-template.md +110 -0
- package/mcp/templates/resource-template.md +76 -0
- package/package.json +8 -5
- package/tools/add-commercial-names.js +207 -0
- package/tools/generate-cli-docs.js +6 -2
- package/tools/get-console-version.js +5 -0
- package/tools/get-redpanda-version.js +5 -0
- package/tools/property-extractor/compare-properties.js +3 -3
- package/tools/property-extractor/generate-handlebars-docs.js +14 -14
- package/tools/property-extractor/generate-pr-summary.js +46 -0
- package/tools/property-extractor/pr-summary-formatter.js +375 -0
- package/tools/redpanda-connect/README.adoc +403 -38
- package/tools/redpanda-connect/connector-binary-analyzer.js +588 -0
- package/tools/redpanda-connect/generate-rpcn-connector-docs.js +97 -34
- package/tools/redpanda-connect/parse-csv-connectors.js +1 -1
- package/tools/redpanda-connect/pr-summary-formatter.js +601 -0
- package/tools/redpanda-connect/report-delta.js +69 -2
- package/tools/redpanda-connect/rpcn-connector-docs-handler.js +1180 -0
- package/tools/redpanda-connect/templates/connector.hbs +38 -0
- package/tools/redpanda-connect/templates/intro.hbs +0 -20
- package/tools/redpanda-connect/update-nav.js +205 -0
package/bin/doc-tools.js
CHANGED
|
@@ -1,382 +1,51 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
const os = require('os');
|
|
5
|
-
const { Command, Option } = require('commander');
|
|
6
|
-
const path = require('path');
|
|
7
|
-
const fs = require('fs');
|
|
8
|
-
const { determineDocsBranch } = require('../cli-utils/self-managed-docs-branch.js');
|
|
9
|
-
const fetchFromGithub = require('../tools/fetch-from-github.js');
|
|
10
|
-
const { urlToXref } = require('../cli-utils/convert-doc-links.js');
|
|
11
|
-
const { generateRpcnConnectorDocs } = require('../tools/redpanda-connect/generate-rpcn-connector-docs.js');
|
|
12
|
-
const parseCSVConnectors = require('../tools/redpanda-connect/parse-csv-connectors.js');
|
|
13
|
-
const { getAntoraValue, setAntoraValue } = require('../cli-utils/antora-utils');
|
|
14
|
-
const {
|
|
15
|
-
getRpkConnectVersion,
|
|
16
|
-
printDeltaReport
|
|
17
|
-
} = require('../tools/redpanda-connect/report-delta');
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Searches upward from a starting directory to locate the repository root.
|
|
21
|
-
*
|
|
22
|
-
* Traverses parent directories from the specified start path, returning the first directory containing either a `.git` folder or a `package.json` file. Exits the process with an error if no such directory is found.
|
|
23
|
-
*
|
|
24
|
-
* @param {string} [start] - The directory to begin the search from. Defaults to the current working directory.
|
|
25
|
-
* @returns {string} The absolute path to the repository root directory.
|
|
26
|
-
*/
|
|
27
|
-
function findRepoRoot(start = process.cwd()) {
|
|
28
|
-
let dir = start;
|
|
29
|
-
while (dir !== path.parse(dir).root) {
|
|
30
|
-
if (
|
|
31
|
-
fs.existsSync(path.join(dir, '.git')) ||
|
|
32
|
-
fs.existsSync(path.join(dir, 'package.json'))
|
|
33
|
-
) {
|
|
34
|
-
return dir;
|
|
35
|
-
}
|
|
36
|
-
dir = path.dirname(dir);
|
|
37
|
-
}
|
|
38
|
-
console.error('❌ Could not find repo root (no .git or package.json in any parent)');
|
|
39
|
-
process.exit(1);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// --------------------------------------------------------------------
|
|
43
|
-
// Dependency check functions
|
|
3
|
+
'use strict'
|
|
44
4
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
function fail(msg) {
|
|
51
|
-
console.error(`❌ ${msg}`);
|
|
52
|
-
process.exit(1);
|
|
53
|
-
}
|
|
5
|
+
const { spawnSync } = require('child_process')
|
|
6
|
+
const os = require('os')
|
|
7
|
+
const { Command, Option } = require('commander')
|
|
8
|
+
const path = require('path')
|
|
9
|
+
const fs = require('fs')
|
|
54
10
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
* @param {string} [help] - Optional help text to display if the tool is not found.
|
|
79
|
-
* @param {string} [versionFlag='--version'] - The flag to use when checking if the tool is installed.
|
|
80
|
-
*
|
|
81
|
-
* @throws {Error} If the specified command is not found or does not respond to the specified flag.
|
|
82
|
-
*/
|
|
83
|
-
function requireCmd(cmd, help, versionFlag = '--version') {
|
|
84
|
-
requireTool(cmd, { versionFlag, help });
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// --------------------------------------------------------------------
|
|
88
|
-
// Special validators
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Ensures that Python with a minimum required version is installed and available in the system PATH.
|
|
92
|
-
*
|
|
93
|
-
* Checks for either `python3` or `python` executables and verifies that the version is at least the specified minimum (default: 3.10). Exits the process with an error message if the requirement is not met.
|
|
94
|
-
*
|
|
95
|
-
* @param {number} [minMajor=3] - Minimum required major version of Python.
|
|
96
|
-
* @param {number} [minMinor=10] - Minimum required minor version of Python.
|
|
97
|
-
*/
|
|
98
|
-
function requirePython(minMajor = 3, minMinor = 10) {
|
|
99
|
-
const candidates = ['python3', 'python', 'python3.12', 'python3.11', 'python3.10'];
|
|
100
|
-
for (const p of candidates) {
|
|
101
|
-
try {
|
|
102
|
-
const out = execSync(`${p} --version`, { encoding: 'utf8' }).trim();
|
|
103
|
-
const [maj, min] = out.split(' ')[1].split('.').map(Number);
|
|
104
|
-
if (maj > minMajor || (maj === minMajor && min >= minMinor)) {
|
|
105
|
-
return; // success
|
|
106
|
-
}
|
|
107
|
-
} catch {
|
|
108
|
-
/* ignore & try next */
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
fail(
|
|
112
|
-
`Python ${minMajor}.${minMinor}+ not found or too old.
|
|
113
|
-
|
|
114
|
-
**Quick Install (Recommended):**
|
|
115
|
-
Run the automated installer to set up all dependencies:
|
|
116
|
-
npm run install-test-dependencies
|
|
117
|
-
|
|
118
|
-
Or install manually from your package manager or https://python.org`
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Ensures that the Docker CLI is installed and the Docker daemon is running.
|
|
124
|
-
*
|
|
125
|
-
* @throws {Error} If Docker is not installed or the Docker daemon is not running.
|
|
126
|
-
*/
|
|
127
|
-
function requireDockerDaemon() {
|
|
128
|
-
requireTool('docker', { help: 'https://docs.docker.com/get-docker/' });
|
|
129
|
-
try {
|
|
130
|
-
execSync('docker info', { stdio: 'ignore' });
|
|
131
|
-
} catch {
|
|
132
|
-
fail(`Docker daemon does not appear to be running.
|
|
133
|
-
|
|
134
|
-
**Quick Install (Recommended):**
|
|
135
|
-
Run the automated installer to set up all dependencies:
|
|
136
|
-
npm run install-test-dependencies
|
|
137
|
-
|
|
138
|
-
Or install and start Docker manually: https://docs.docker.com/get-docker/`);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// --------------------------------------------------------------------
|
|
143
|
-
// Grouped checks
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Ensures that required dependencies for generating CRD documentation are installed.
|
|
147
|
-
*
|
|
148
|
-
* Verifies the presence of the {@link git} and {@link crd-ref-docs} command-line tools, exiting the process with an error message if either is missing.
|
|
149
|
-
*/
|
|
150
|
-
function verifyCrdDependencies() {
|
|
151
|
-
requireCmd('git', 'Install Git: https://git-scm.com/downloads');
|
|
152
|
-
requireCmd(
|
|
153
|
-
'crd-ref-docs',
|
|
154
|
-
`
|
|
155
|
-
The 'crd-ref-docs' command is required but was not found.
|
|
156
|
-
|
|
157
|
-
**Quick Install (Recommended):**
|
|
158
|
-
Run the automated installer to set up all dependencies:
|
|
159
|
-
npm run install-test-dependencies
|
|
160
|
-
|
|
161
|
-
Or install manually (for macOS):
|
|
162
|
-
|
|
163
|
-
1. Determine your architecture:
|
|
164
|
-
Run: \`uname -m\`
|
|
165
|
-
|
|
166
|
-
2. Download and install:
|
|
167
|
-
|
|
168
|
-
- For Apple Silicon (M1/M2/M3):
|
|
169
|
-
curl -fLO https://github.com/elastic/crd-ref-docs/releases/download/v0.1.0/crd-ref-docs_0.1.0_Darwin_arm64.tar.gz
|
|
170
|
-
tar -xzf crd-ref-docs_0.1.0_Darwin_arm64.tar.gz
|
|
171
|
-
chmod +x crd-ref-docs
|
|
172
|
-
sudo mv crd-ref-docs /usr/local/bin/
|
|
173
|
-
|
|
174
|
-
- For Intel (x86_64):
|
|
175
|
-
curl -fLO https://github.com/elastic/crd-ref-docs/releases/download/v0.1.0/crd-ref-docs_0.1.0_Darwin_x86_64.tar.gz
|
|
176
|
-
tar -xzf crd-ref-docs_0.1.0_Darwin_x86_64.tar.gz
|
|
177
|
-
chmod +x crd-ref-docs
|
|
178
|
-
sudo mv crd-ref-docs /usr/local/bin/
|
|
179
|
-
|
|
180
|
-
For more details, visit: https://github.com/elastic/crd-ref-docs
|
|
181
|
-
`.trim()
|
|
182
|
-
);
|
|
183
|
-
requireCmd(
|
|
184
|
-
'go',
|
|
185
|
-
`
|
|
186
|
-
The 'go' command (Golang) is required but was not found.
|
|
187
|
-
|
|
188
|
-
**Quick Install (Recommended):**
|
|
189
|
-
Run the automated installer to set up all dependencies:
|
|
190
|
-
npm run install-test-dependencies
|
|
191
|
-
|
|
192
|
-
Or install manually on macOS:
|
|
193
|
-
|
|
194
|
-
Option 1: Install via Homebrew (recommended):
|
|
195
|
-
brew install go
|
|
196
|
-
|
|
197
|
-
Option 2: Download directly from the official site:
|
|
198
|
-
1. Visit: https://go.dev/dl/
|
|
199
|
-
2. Download the appropriate installer for macOS.
|
|
200
|
-
3. Run the installer and follow the instructions.
|
|
201
|
-
|
|
202
|
-
After installation, verify it works:
|
|
203
|
-
go version
|
|
204
|
-
|
|
205
|
-
For more details, see: https://go.dev/doc/install
|
|
206
|
-
`.trim(),
|
|
207
|
-
'version'
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Ensures that all required tools for Helm documentation generation are installed.
|
|
213
|
-
*
|
|
214
|
-
* Checks for the presence of `helm-docs`, `pandoc`, and `git`, exiting the process with an error if any are missing.
|
|
215
|
-
*/
|
|
216
|
-
function verifyHelmDependencies() {
|
|
217
|
-
requireCmd(
|
|
218
|
-
'helm-docs',
|
|
219
|
-
`
|
|
220
|
-
The 'helm-docs' command is required but was not found.
|
|
221
|
-
|
|
222
|
-
**Quick Install (Recommended):**
|
|
223
|
-
Run the automated installer to set up all dependencies:
|
|
224
|
-
npm run install-test-dependencies
|
|
225
|
-
|
|
226
|
-
Or install manually (for macOS):
|
|
227
|
-
|
|
228
|
-
1. Determine your architecture:
|
|
229
|
-
Run: \`uname -m\`
|
|
230
|
-
|
|
231
|
-
2. Download and install:
|
|
232
|
-
|
|
233
|
-
- For Apple Silicon (M1/M2/M3):
|
|
234
|
-
curl -fLO https://github.com/norwoodj/helm-docs/releases/download/v1.11.0/helm-docs_1.11.0_Darwin_arm64.tar.gz
|
|
235
|
-
tar -xzf helm-docs_1.11.0_Darwin_arm64.tar.gz
|
|
236
|
-
chmod +x helm-docs
|
|
237
|
-
sudo mv helm-docs /usr/local/bin/
|
|
238
|
-
|
|
239
|
-
- For Intel (x86_64):
|
|
240
|
-
curl -fLO https://github.com/norwoodj/helm-docs/releases/download/v1.11.0/helm-docs_1.11.0_Darwin_x86_64.tar.gz
|
|
241
|
-
tar -xzf helm-docs_1.11.0_Darwin_x86_64.tar.gz
|
|
242
|
-
chmod +x helm-docs
|
|
243
|
-
sudo mv helm-docs /usr/local/bin/
|
|
244
|
-
|
|
245
|
-
Alternatively, if you use Homebrew:
|
|
246
|
-
brew install norwoodj/tap/helm-docs
|
|
247
|
-
|
|
248
|
-
For more details, visit: https://github.com/norwoodj/helm-docs
|
|
249
|
-
`.trim()
|
|
250
|
-
);
|
|
251
|
-
requireCmd('pandoc', 'brew install pandoc or https://pandoc.org');
|
|
252
|
-
requireCmd('git', 'Install Git: https://git-scm.com/downloads');
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Ensures all dependencies required for generating property documentation are installed.
|
|
257
|
-
*
|
|
258
|
-
* Checks for the presence of `make`, Python 3.10 or newer, Node.js, C++ compiler, and C++ standard library headers.
|
|
259
|
-
* Exits the process with an error message if any dependency is missing.
|
|
260
|
-
*/
|
|
261
|
-
function verifyPropertyDependencies() {
|
|
262
|
-
requireCmd('make', 'Your OS package manager');
|
|
263
|
-
requirePython();
|
|
264
|
-
|
|
265
|
-
// Check for Node.js (required for Handlebars templates)
|
|
266
|
-
requireCmd('node', 'https://nodejs.org/en/download/ or use your package manager (for example, brew install node)');
|
|
267
|
-
requireCmd('npm', 'Usually installed with Node.js');
|
|
268
|
-
|
|
269
|
-
// Check for C++ compiler
|
|
270
|
-
let cppCompiler = null;
|
|
271
|
-
try {
|
|
272
|
-
execSync('gcc --version', { stdio: 'ignore' });
|
|
273
|
-
cppCompiler = 'gcc';
|
|
274
|
-
} catch {
|
|
275
|
-
try {
|
|
276
|
-
execSync('clang --version', { stdio: 'ignore' });
|
|
277
|
-
cppCompiler = 'clang';
|
|
278
|
-
} catch {
|
|
279
|
-
fail(`A C++ compiler (gcc or clang) is required for tree-sitter compilation.
|
|
280
|
-
|
|
281
|
-
**Quick Install (Recommended):**
|
|
282
|
-
Run the automated installer to set up all dependencies:
|
|
283
|
-
npm run install-test-dependencies
|
|
284
|
-
|
|
285
|
-
Or install manually:
|
|
286
|
-
|
|
287
|
-
On macOS, install Xcode Command Line Tools:
|
|
288
|
-
xcode-select --install
|
|
289
|
-
|
|
290
|
-
On Linux (Ubuntu/Debian):
|
|
291
|
-
sudo apt update && sudo apt install build-essential
|
|
292
|
-
|
|
293
|
-
On Linux (CentOS/RHEL/Fedora):
|
|
294
|
-
sudo yum groupinstall "Development Tools"
|
|
295
|
-
# or on newer versions:
|
|
296
|
-
sudo dnf groupinstall "Development Tools"
|
|
297
|
-
|
|
298
|
-
After installation, verify with:
|
|
299
|
-
gcc --version
|
|
300
|
-
# or
|
|
301
|
-
clang --version`);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// Check for C++ standard library headers (critical for tree-sitter compilation)
|
|
306
|
-
let tempDir = null;
|
|
307
|
-
let compileCmd = null;
|
|
308
|
-
try {
|
|
309
|
-
const testProgram = '#include <functional>\nint main() { return 0; }';
|
|
310
|
-
tempDir = require('fs').mkdtempSync(require('path').join(require('os').tmpdir(), 'cpp-test-'));
|
|
311
|
-
const tempFile = require('path').join(tempDir, 'test.cpp');
|
|
312
|
-
require('fs').writeFileSync(tempFile, testProgram);
|
|
313
|
-
|
|
314
|
-
compileCmd = cppCompiler === 'gcc' ? 'gcc' : 'clang++';
|
|
315
|
-
execSync(`${compileCmd} -x c++ -fsyntax-only "${tempFile}"`, { stdio: 'ignore' });
|
|
316
|
-
require('fs').rmSync(tempDir, { recursive: true, force: true });
|
|
317
|
-
} catch {
|
|
318
|
-
// Clean up temp directory if it was created
|
|
319
|
-
if (tempDir) {
|
|
320
|
-
try {
|
|
321
|
-
require('fs').rmSync(tempDir, { recursive: true, force: true });
|
|
322
|
-
} catch {
|
|
323
|
-
// Ignore cleanup errors
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
fail(`C++ standard library headers are missing or incomplete.
|
|
327
|
-
|
|
328
|
-
**Quick Install (Recommended):**
|
|
329
|
-
Run the automated installer to set up all dependencies:
|
|
330
|
-
npm run install-test-dependencies
|
|
331
|
-
|
|
332
|
-
Or fix manually:
|
|
333
|
-
|
|
334
|
-
1. **Test if the issue exists**:
|
|
335
|
-
echo '#include <functional>' | ${compileCmd} -x c++ -fsyntax-only -
|
|
336
|
-
|
|
337
|
-
2. **If the test fails, try these fixes in order**:
|
|
338
|
-
**Fix 1**: Reset developer path
|
|
339
|
-
sudo xcode-select --reset
|
|
340
|
-
|
|
341
|
-
**Fix 2**: Force reinstall Command Line Tools
|
|
342
|
-
sudo rm -rf /Library/Developer/CommandLineTools
|
|
343
|
-
xcode-select --install
|
|
344
|
-
|
|
345
|
-
Complete the GUI installation dialog that appears.
|
|
346
|
-
|
|
347
|
-
3. **Verify the fix**:
|
|
348
|
-
echo '#include <functional>' | ${compileCmd} -x c++ -fsyntax-only -
|
|
349
|
-
If successful, you should see no output and the command should exit with code 0.
|
|
350
|
-
`);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* Ensures all required dependencies for generating Redpanda metrics documentation are installed.
|
|
356
|
-
*
|
|
357
|
-
* Verifies that Python 3.10+, `curl`, and `tar` are available, and that the Docker daemon is running.
|
|
358
|
-
*
|
|
359
|
-
* @throws {Error} If any required dependency is missing or the Docker daemon is not running.
|
|
360
|
-
*/
|
|
361
|
-
function verifyMetricsDependencies() {
|
|
362
|
-
requirePython();
|
|
363
|
-
requireCmd('curl');
|
|
364
|
-
requireCmd('tar');
|
|
365
|
-
requireDockerDaemon();
|
|
366
|
-
}
|
|
11
|
+
// Import extracted utility modules
|
|
12
|
+
const { findRepoRoot, fail, commonOptions } = require('../cli-utils/doc-tools-utils')
|
|
13
|
+
const {
|
|
14
|
+
requireTool,
|
|
15
|
+
requireCmd,
|
|
16
|
+
verifyCrdDependencies,
|
|
17
|
+
verifyHelmDependencies,
|
|
18
|
+
verifyPropertyDependencies,
|
|
19
|
+
verifyMetricsDependencies
|
|
20
|
+
} = require('../cli-utils/dependencies')
|
|
21
|
+
const {
|
|
22
|
+
runClusterDocs,
|
|
23
|
+
diffDirs,
|
|
24
|
+
generatePropertyComparisonReport,
|
|
25
|
+
updatePropertyOverridesWithVersion,
|
|
26
|
+
cleanupOldDiffs
|
|
27
|
+
} = require('../cli-utils/diff-utils')
|
|
28
|
+
|
|
29
|
+
// Import other utilities
|
|
30
|
+
const { determineDocsBranch } = require('../cli-utils/self-managed-docs-branch.js')
|
|
31
|
+
const fetchFromGithub = require('../tools/fetch-from-github.js')
|
|
32
|
+
const { urlToXref } = require('../cli-utils/convert-doc-links.js')
|
|
33
|
+
const { getAntoraValue, setAntoraValue } = require('../cli-utils/antora-utils')
|
|
367
34
|
|
|
368
35
|
// --------------------------------------------------------------------
|
|
369
36
|
// Main CLI Definition
|
|
370
37
|
// --------------------------------------------------------------------
|
|
371
|
-
const programCli = new Command()
|
|
38
|
+
const programCli = new Command()
|
|
372
39
|
|
|
373
|
-
const pkg = require('../package.json')
|
|
40
|
+
const pkg = require('../package.json')
|
|
374
41
|
programCli
|
|
375
42
|
.name('doc-tools')
|
|
376
43
|
.description('Redpanda Document Automation CLI')
|
|
377
|
-
.version(pkg.version)
|
|
44
|
+
.version(pkg.version)
|
|
378
45
|
|
|
379
|
-
//
|
|
46
|
+
// ====================================================================
|
|
47
|
+
// TOP-LEVEL COMMANDS
|
|
48
|
+
// ====================================================================
|
|
380
49
|
|
|
381
50
|
/**
|
|
382
51
|
* install-test-dependencies
|
|
@@ -407,10 +76,10 @@ programCli
|
|
|
407
76
|
.command('install-test-dependencies')
|
|
408
77
|
.description('Install packages for doc test workflows')
|
|
409
78
|
.action(() => {
|
|
410
|
-
const scriptPath = path.join(__dirname, '../cli-utils/install-test-dependencies.sh')
|
|
411
|
-
const result = spawnSync(scriptPath, { stdio: 'inherit', shell: true })
|
|
412
|
-
process.exit(result.status)
|
|
413
|
-
})
|
|
79
|
+
const scriptPath = path.join(__dirname, '../cli-utils/install-test-dependencies.sh')
|
|
80
|
+
const result = spawnSync(scriptPath, { stdio: 'inherit', shell: true })
|
|
81
|
+
process.exit(result.status)
|
|
82
|
+
})
|
|
414
83
|
|
|
415
84
|
/**
|
|
416
85
|
* get-redpanda-version
|
|
@@ -454,12 +123,12 @@ programCli
|
|
|
454
123
|
.option('--from-antora', 'Read prerelease flag from local antora.yml')
|
|
455
124
|
.action(async (options) => {
|
|
456
125
|
try {
|
|
457
|
-
await require('../tools/get-redpanda-version.js')(options)
|
|
126
|
+
await require('../tools/get-redpanda-version.js')(options)
|
|
458
127
|
} catch (err) {
|
|
459
|
-
console.error(
|
|
460
|
-
process.exit(1)
|
|
128
|
+
console.error(`Error: ${err.message}`)
|
|
129
|
+
process.exit(1)
|
|
461
130
|
}
|
|
462
|
-
})
|
|
131
|
+
})
|
|
463
132
|
|
|
464
133
|
/**
|
|
465
134
|
* get-console-version
|
|
@@ -502,12 +171,12 @@ programCli
|
|
|
502
171
|
.option('--from-antora', 'Read prerelease flag from local antora.yml')
|
|
503
172
|
.action(async (options) => {
|
|
504
173
|
try {
|
|
505
|
-
await require('../tools/get-console-version.js')(options)
|
|
174
|
+
await require('../tools/get-console-version.js')(options)
|
|
506
175
|
} catch (err) {
|
|
507
|
-
console.error(
|
|
508
|
-
process.exit(1)
|
|
176
|
+
console.error(`Error: ${err.message}`)
|
|
177
|
+
process.exit(1)
|
|
509
178
|
}
|
|
510
|
-
})
|
|
179
|
+
})
|
|
511
180
|
|
|
512
181
|
/**
|
|
513
182
|
* link-readme
|
|
@@ -527,8 +196,8 @@ programCli
|
|
|
527
196
|
*
|
|
528
197
|
* @example
|
|
529
198
|
* # Link a lab project README into documentation
|
|
530
|
-
* npx doc-tools link-readme
|
|
531
|
-
* --subdir labs/docker-compose
|
|
199
|
+
* npx doc-tools link-readme \
|
|
200
|
+
* --subdir labs/docker-compose \
|
|
532
201
|
* --target docker-compose-lab.adoc
|
|
533
202
|
*
|
|
534
203
|
* # Link multiple lab READMEs
|
|
@@ -549,39 +218,39 @@ programCli
|
|
|
549
218
|
.requiredOption('-s, --subdir <subdir>', 'Relative path to the lab project subdirectory')
|
|
550
219
|
.requiredOption('-t, --target <filename>', 'Name of the target AsciiDoc file in pages/')
|
|
551
220
|
.action((options) => {
|
|
552
|
-
const repoRoot = findRepoRoot()
|
|
553
|
-
const normalized = options.subdir.replace(/\/+$/, '')
|
|
554
|
-
const moduleName = normalized.split('/')[0]
|
|
221
|
+
const repoRoot = findRepoRoot()
|
|
222
|
+
const normalized = options.subdir.replace(/\/+$/, '')
|
|
223
|
+
const moduleName = normalized.split('/')[0]
|
|
555
224
|
|
|
556
|
-
const projectDir = path.join(repoRoot, normalized)
|
|
557
|
-
const pagesDir = path.join(repoRoot, 'docs', 'modules', moduleName, 'pages')
|
|
558
|
-
const sourceFile = path.join(projectDir, 'README.adoc')
|
|
559
|
-
const destLink = path.join(pagesDir, options.target)
|
|
225
|
+
const projectDir = path.join(repoRoot, normalized)
|
|
226
|
+
const pagesDir = path.join(repoRoot, 'docs', 'modules', moduleName, 'pages')
|
|
227
|
+
const sourceFile = path.join(projectDir, 'README.adoc')
|
|
228
|
+
const destLink = path.join(pagesDir, options.target)
|
|
560
229
|
|
|
561
230
|
if (!fs.existsSync(projectDir)) {
|
|
562
|
-
console.error(
|
|
563
|
-
process.exit(1)
|
|
231
|
+
console.error(`Error: Project directory not found: ${projectDir}`)
|
|
232
|
+
process.exit(1)
|
|
564
233
|
}
|
|
565
234
|
if (!fs.existsSync(sourceFile)) {
|
|
566
|
-
console.error(
|
|
567
|
-
process.exit(1)
|
|
235
|
+
console.error(`Error: README.adoc not found in ${projectDir}`)
|
|
236
|
+
process.exit(1)
|
|
568
237
|
}
|
|
569
238
|
|
|
570
|
-
fs.mkdirSync(pagesDir, { recursive: true })
|
|
571
|
-
const relPath = path.relative(pagesDir, sourceFile)
|
|
239
|
+
fs.mkdirSync(pagesDir, { recursive: true })
|
|
240
|
+
const relPath = path.relative(pagesDir, sourceFile)
|
|
572
241
|
|
|
573
242
|
try {
|
|
574
243
|
if (fs.existsSync(destLink)) {
|
|
575
|
-
const stat = fs.lstatSync(destLink)
|
|
576
|
-
if (stat.isSymbolicLink()) fs.unlinkSync(destLink)
|
|
577
|
-
else fail(`Destination already exists and is not a symlink: ${destLink}`)
|
|
244
|
+
const stat = fs.lstatSync(destLink)
|
|
245
|
+
if (stat.isSymbolicLink()) fs.unlinkSync(destLink)
|
|
246
|
+
else fail(`Destination already exists and is not a symlink: ${destLink}`)
|
|
578
247
|
}
|
|
579
|
-
fs.symlinkSync(relPath, destLink)
|
|
580
|
-
console.log(
|
|
248
|
+
fs.symlinkSync(relPath, destLink)
|
|
249
|
+
console.log(`Done: Linked ${relPath} → ${destLink}`)
|
|
581
250
|
} catch (err) {
|
|
582
|
-
fail(`Failed to create symlink: ${err.message}`)
|
|
251
|
+
fail(`Failed to create symlink: ${err.message}`)
|
|
583
252
|
}
|
|
584
|
-
})
|
|
253
|
+
})
|
|
585
254
|
|
|
586
255
|
/**
|
|
587
256
|
* fetch
|
|
@@ -600,25 +269,25 @@ programCli
|
|
|
600
269
|
*
|
|
601
270
|
* @example
|
|
602
271
|
* # Fetch a specific configuration file
|
|
603
|
-
* npx doc-tools fetch
|
|
604
|
-
* --owner redpanda-data
|
|
605
|
-
* --repo redpanda
|
|
606
|
-
* --remote-path docker/docker-compose.yml
|
|
272
|
+
* npx doc-tools fetch \
|
|
273
|
+
* --owner redpanda-data \
|
|
274
|
+
* --repo redpanda \
|
|
275
|
+
* --remote-path docker/docker-compose.yml \
|
|
607
276
|
* --save-dir examples/
|
|
608
277
|
*
|
|
609
278
|
* # Fetch an entire directory of examples
|
|
610
|
-
* npx doc-tools fetch
|
|
611
|
-
* -o redpanda-data
|
|
612
|
-
* -r connect-examples
|
|
613
|
-
* -p pipelines/mongodb
|
|
279
|
+
* npx doc-tools fetch \
|
|
280
|
+
* -o redpanda-data \
|
|
281
|
+
* -r connect-examples \
|
|
282
|
+
* -p pipelines/mongodb \
|
|
614
283
|
* -d docs/modules/examples/attachments/
|
|
615
284
|
*
|
|
616
285
|
* # Fetch with custom filename
|
|
617
|
-
* npx doc-tools fetch
|
|
618
|
-
* -o redpanda-data
|
|
619
|
-
* -r helm-charts
|
|
620
|
-
* -p charts/redpanda/values.yaml
|
|
621
|
-
* -d examples/
|
|
286
|
+
* npx doc-tools fetch \
|
|
287
|
+
* -o redpanda-data \
|
|
288
|
+
* -r helm-charts \
|
|
289
|
+
* -p charts/redpanda/values.yaml \
|
|
290
|
+
* -d examples/ \
|
|
622
291
|
* --filename redpanda-values-example.yaml
|
|
623
292
|
*
|
|
624
293
|
* @requirements
|
|
@@ -642,13 +311,13 @@ programCli
|
|
|
642
311
|
options.remotePath,
|
|
643
312
|
options.saveDir,
|
|
644
313
|
options.filename
|
|
645
|
-
)
|
|
646
|
-
console.log(
|
|
314
|
+
)
|
|
315
|
+
console.log(`Done: Fetched to ${options.saveDir}`)
|
|
647
316
|
} catch (err) {
|
|
648
|
-
console.error(
|
|
649
|
-
process.exit(1)
|
|
317
|
+
console.error(`Error: ${err.message}`)
|
|
318
|
+
process.exit(1)
|
|
650
319
|
}
|
|
651
|
-
})
|
|
320
|
+
})
|
|
652
321
|
|
|
653
322
|
/**
|
|
654
323
|
* setup-mcp
|
|
@@ -701,250 +370,287 @@ programCli
|
|
|
701
370
|
.option('--status', 'Show current MCP server configuration status', false)
|
|
702
371
|
.action(async (options) => {
|
|
703
372
|
try {
|
|
704
|
-
const { setupMCP, showStatus, printNextSteps } = require('../cli-utils/setup-mcp.js')
|
|
373
|
+
const { setupMCP, showStatus, printNextSteps } = require('../cli-utils/setup-mcp.js')
|
|
705
374
|
|
|
706
375
|
if (options.status) {
|
|
707
|
-
showStatus()
|
|
708
|
-
return
|
|
376
|
+
showStatus()
|
|
377
|
+
return
|
|
709
378
|
}
|
|
710
379
|
|
|
711
380
|
const result = await setupMCP({
|
|
712
381
|
force: options.force,
|
|
713
382
|
target: options.target,
|
|
714
383
|
local: options.local
|
|
715
|
-
})
|
|
384
|
+
})
|
|
716
385
|
|
|
717
386
|
if (result.success) {
|
|
718
|
-
printNextSteps(result)
|
|
719
|
-
process.exit(0)
|
|
387
|
+
printNextSteps(result)
|
|
388
|
+
process.exit(0)
|
|
720
389
|
} else {
|
|
721
|
-
console.error(
|
|
722
|
-
process.exit(1)
|
|
390
|
+
console.error(`Error: Setup failed: ${result.error}`)
|
|
391
|
+
process.exit(1)
|
|
723
392
|
}
|
|
724
393
|
} catch (err) {
|
|
725
|
-
console.error(
|
|
726
|
-
process.exit(1)
|
|
394
|
+
console.error(`Error: ${err.message}`)
|
|
395
|
+
process.exit(1)
|
|
727
396
|
}
|
|
728
|
-
})
|
|
729
|
-
|
|
730
|
-
// Create an "automation" subcommand group.
|
|
731
|
-
const automation = new Command('generate').description('Run docs automations');
|
|
732
|
-
|
|
733
|
-
// --------------------------------------------------------------------
|
|
734
|
-
// Automation subcommands
|
|
735
|
-
// --------------------------------------------------------------------
|
|
736
|
-
|
|
737
|
-
// Common options for both automation tasks.
|
|
738
|
-
const commonOptions = {
|
|
739
|
-
dockerRepo: 'redpanda',
|
|
740
|
-
consoleTag: 'latest',
|
|
741
|
-
consoleDockerRepo: 'console',
|
|
742
|
-
};
|
|
397
|
+
})
|
|
743
398
|
|
|
744
399
|
/**
|
|
745
|
-
*
|
|
746
|
-
*
|
|
747
|
-
*
|
|
748
|
-
*
|
|
749
|
-
*
|
|
750
|
-
*
|
|
751
|
-
*
|
|
752
|
-
* @
|
|
753
|
-
* @param {string} tag - Release tag or version to generate docs for.
|
|
754
|
-
* @param {Object} options - Runtime options.
|
|
755
|
-
* @param {string} options.dockerRepo - Docker repository used by the script.
|
|
756
|
-
* @param {string} options.consoleTag - Console image tag passed to the script.
|
|
757
|
-
* @param {string} options.consoleDockerRepo - Console Docker repository used by the script.
|
|
400
|
+
* @description Validate the MCP server configuration including prompts, resources,
|
|
401
|
+
* and metadata. Reports any issues with prompt definitions or missing files.
|
|
402
|
+
* @why Use this command to verify that all MCP prompts and resources are properly
|
|
403
|
+
* configured before deploying or after making changes to prompt files.
|
|
404
|
+
* @example
|
|
405
|
+
* # Validate MCP configuration
|
|
406
|
+
* npx doc-tools validate-mcp
|
|
407
|
+
* @requirements None.
|
|
758
408
|
*/
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
409
|
+
programCli
|
|
410
|
+
.command('validate-mcp')
|
|
411
|
+
.description('Validate MCP server configuration (prompts, resources, metadata)')
|
|
412
|
+
.action(() => {
|
|
413
|
+
const {
|
|
414
|
+
PromptCache,
|
|
415
|
+
loadAllPrompts
|
|
416
|
+
} = require('./mcp-tools/prompt-discovery')
|
|
417
|
+
const {
|
|
418
|
+
validateMcpConfiguration,
|
|
419
|
+
formatValidationResults
|
|
420
|
+
} = require('./mcp-tools/mcp-validation')
|
|
766
421
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
*
|
|
770
|
-
* @param {string} diffDir - Directory containing diff files
|
|
771
|
-
*/
|
|
772
|
-
function cleanupOldDiffs(diffDir) {
|
|
773
|
-
try {
|
|
774
|
-
console.log('Cleaning up old diff JSON files (keeping only 2 most recent)…');
|
|
422
|
+
const baseDir = findRepoRoot()
|
|
423
|
+
const promptCache = new PromptCache()
|
|
775
424
|
|
|
776
|
-
const
|
|
777
|
-
|
|
778
|
-
|
|
425
|
+
const resources = [
|
|
426
|
+
{
|
|
427
|
+
uri: 'redpanda://style-guide',
|
|
428
|
+
name: 'Redpanda Documentation Style Guide',
|
|
429
|
+
description: 'Complete style guide based on Google Developer Documentation Style Guide with Redpanda-specific guidelines',
|
|
430
|
+
mimeType: 'text/markdown',
|
|
431
|
+
version: '1.0.0',
|
|
432
|
+
lastUpdated: '2025-12-11'
|
|
433
|
+
}
|
|
434
|
+
]
|
|
435
|
+
|
|
436
|
+
const resourceMap = {
|
|
437
|
+
'redpanda://style-guide': { file: 'style-guide.md', mimeType: 'text/markdown' }
|
|
779
438
|
}
|
|
780
439
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
.
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
440
|
+
try {
|
|
441
|
+
console.log('Loading prompts...')
|
|
442
|
+
const prompts = loadAllPrompts(baseDir, promptCache)
|
|
443
|
+
console.log(`Found ${prompts.length} prompts`)
|
|
444
|
+
|
|
445
|
+
console.log('\nValidating configuration...')
|
|
446
|
+
const validation = validateMcpConfiguration({
|
|
447
|
+
resources,
|
|
448
|
+
resourceMap,
|
|
449
|
+
prompts,
|
|
450
|
+
baseDir
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
const output = formatValidationResults(validation, { resources, prompts })
|
|
454
|
+
console.log('\n' + output)
|
|
455
|
+
|
|
456
|
+
if (!validation.valid) {
|
|
457
|
+
process.exit(1)
|
|
458
|
+
}
|
|
459
|
+
} catch (err) {
|
|
460
|
+
console.error(`Error: Validation failed: ${err.message}`)
|
|
461
|
+
process.exit(1)
|
|
797
462
|
}
|
|
798
|
-
}
|
|
799
|
-
console.error(` Failed to cleanup old diff files: ${error.message}`);
|
|
800
|
-
}
|
|
801
|
-
}
|
|
463
|
+
})
|
|
802
464
|
|
|
803
465
|
/**
|
|
804
|
-
*
|
|
805
|
-
*
|
|
806
|
-
*
|
|
807
|
-
*
|
|
808
|
-
*
|
|
809
|
-
*
|
|
810
|
-
*
|
|
811
|
-
* If either input JSON is missing the function logs a message and returns without
|
|
812
|
-
* error. Any errors from the comparison tool are logged; the function does not
|
|
813
|
-
* throw.
|
|
466
|
+
* @description Preview an MCP prompt with sample arguments to see the final rendered
|
|
467
|
+
* output. Useful for testing and debugging prompt templates.
|
|
468
|
+
* @why Use this command when developing or modifying MCP prompts to verify the
|
|
469
|
+
* output format and argument substitution works correctly.
|
|
470
|
+
* @example
|
|
471
|
+
* # Preview a prompt with content argument
|
|
472
|
+
* npx doc-tools preview-prompt review-content --content "Sample text"
|
|
814
473
|
*
|
|
815
|
-
*
|
|
816
|
-
*
|
|
817
|
-
* @
|
|
474
|
+
* # Preview a prompt with topic and audience
|
|
475
|
+
* npx doc-tools preview-prompt write-docs --topic "Kafka" --audience "developers"
|
|
476
|
+
* @requirements None.
|
|
818
477
|
*/
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
console.
|
|
863
|
-
|
|
864
|
-
|
|
478
|
+
programCli
|
|
479
|
+
.command('preview-prompt')
|
|
480
|
+
.description('Preview a prompt with arguments to see the final output')
|
|
481
|
+
.argument('<prompt-name>', 'Name of the prompt to preview')
|
|
482
|
+
.option('--content <text>', 'Content argument (for review/check prompts)')
|
|
483
|
+
.option('--topic <text>', 'Topic argument (for write prompts)')
|
|
484
|
+
.option('--audience <text>', 'Audience argument (for write prompts)')
|
|
485
|
+
.action((promptName, options) => {
|
|
486
|
+
const {
|
|
487
|
+
PromptCache,
|
|
488
|
+
loadAllPrompts,
|
|
489
|
+
buildPromptWithArguments
|
|
490
|
+
} = require('./mcp-tools/prompt-discovery')
|
|
491
|
+
|
|
492
|
+
const baseDir = findRepoRoot()
|
|
493
|
+
const promptCache = new PromptCache()
|
|
494
|
+
|
|
495
|
+
try {
|
|
496
|
+
loadAllPrompts(baseDir, promptCache)
|
|
497
|
+
|
|
498
|
+
const prompt = promptCache.get(promptName)
|
|
499
|
+
if (!prompt) {
|
|
500
|
+
console.error(`Error: Prompt not found: ${promptName}`)
|
|
501
|
+
console.error(`\nAvailable prompts: ${promptCache.getNames().join(', ')}`)
|
|
502
|
+
process.exit(1)
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const args = {}
|
|
506
|
+
if (options.content) args.content = options.content
|
|
507
|
+
if (options.topic) args.topic = options.topic
|
|
508
|
+
if (options.audience) args.audience = options.audience
|
|
509
|
+
|
|
510
|
+
const promptText = buildPromptWithArguments(prompt, args)
|
|
511
|
+
|
|
512
|
+
console.log('='.repeat(70))
|
|
513
|
+
console.log(`PROMPT PREVIEW: ${promptName}`)
|
|
514
|
+
console.log('='.repeat(70))
|
|
515
|
+
console.log(`Description: ${prompt.description}`)
|
|
516
|
+
console.log(`Version: ${prompt.version}`)
|
|
517
|
+
if (prompt.arguments.length > 0) {
|
|
518
|
+
console.log(`Arguments: ${prompt.arguments.map(a => a.name).join(', ')}`)
|
|
519
|
+
}
|
|
520
|
+
console.log('='.repeat(70))
|
|
521
|
+
console.log('\n' + promptText)
|
|
522
|
+
console.log('\n' + '='.repeat(70))
|
|
523
|
+
} catch (err) {
|
|
524
|
+
console.error(`Error: Preview failed: ${err.message}`)
|
|
525
|
+
process.exit(1)
|
|
865
526
|
}
|
|
866
|
-
}
|
|
867
|
-
console.error(`❌ Error generating property comparison: ${error.message}`);
|
|
868
|
-
}
|
|
869
|
-
}
|
|
527
|
+
})
|
|
870
528
|
|
|
871
529
|
/**
|
|
872
|
-
*
|
|
873
|
-
*
|
|
874
|
-
*
|
|
875
|
-
*
|
|
876
|
-
*
|
|
877
|
-
*
|
|
878
|
-
*
|
|
879
|
-
*
|
|
880
|
-
*
|
|
881
|
-
*
|
|
882
|
-
* @
|
|
883
|
-
* @param {string} newTempDir - Path to the existing temporary directory containing the new output; must exist.
|
|
530
|
+
* @description Show MCP server version information including available prompts,
|
|
531
|
+
* resources, and optionally usage statistics from previous sessions.
|
|
532
|
+
* @why Use this command to see what MCP capabilities are available and to review
|
|
533
|
+
* usage patterns for optimization.
|
|
534
|
+
* @example
|
|
535
|
+
* # Show version and capabilities
|
|
536
|
+
* npx doc-tools mcp-version
|
|
537
|
+
*
|
|
538
|
+
* # Show with usage statistics
|
|
539
|
+
* npx doc-tools mcp-version --stats
|
|
540
|
+
* @requirements None.
|
|
884
541
|
*/
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
542
|
+
programCli
|
|
543
|
+
.command('mcp-version')
|
|
544
|
+
.description('Show MCP server version and configuration information')
|
|
545
|
+
.option('--stats', 'Show usage statistics if available', false)
|
|
546
|
+
.action((options) => {
|
|
547
|
+
const packageJson = require('../package.json')
|
|
548
|
+
const {
|
|
549
|
+
PromptCache,
|
|
550
|
+
loadAllPrompts
|
|
551
|
+
} = require('./mcp-tools/prompt-discovery')
|
|
552
|
+
|
|
553
|
+
const baseDir = findRepoRoot()
|
|
554
|
+
const promptCache = new PromptCache()
|
|
555
|
+
|
|
556
|
+
try {
|
|
557
|
+
const prompts = loadAllPrompts(baseDir, promptCache)
|
|
558
|
+
|
|
559
|
+
const resources = [
|
|
560
|
+
{
|
|
561
|
+
uri: 'redpanda://style-guide',
|
|
562
|
+
name: 'Redpanda Documentation Style Guide',
|
|
563
|
+
version: '1.0.0',
|
|
564
|
+
lastUpdated: '2025-12-11'
|
|
565
|
+
}
|
|
566
|
+
]
|
|
567
|
+
|
|
568
|
+
console.log('Redpanda Doc Tools MCP Server')
|
|
569
|
+
console.log('='.repeat(60))
|
|
570
|
+
console.log(`Server version: ${packageJson.version}`)
|
|
571
|
+
console.log(`Base directory: ${baseDir}`)
|
|
572
|
+
console.log('')
|
|
573
|
+
|
|
574
|
+
console.log(`Prompts (${prompts.length} available):`)
|
|
575
|
+
prompts.forEach(prompt => {
|
|
576
|
+
const args = prompt.arguments.length > 0
|
|
577
|
+
? ` [${prompt.arguments.map(a => a.name).join(', ')}]`
|
|
578
|
+
: ''
|
|
579
|
+
console.log(` - ${prompt.name} (v${prompt.version})${args}`)
|
|
580
|
+
console.log(` ${prompt.description}`)
|
|
581
|
+
})
|
|
582
|
+
console.log('')
|
|
583
|
+
|
|
584
|
+
console.log(`Resources (${resources.length} available):`)
|
|
585
|
+
resources.forEach(resource => {
|
|
586
|
+
console.log(` - ${resource.name} (v${resource.version})`)
|
|
587
|
+
console.log(` URI: ${resource.uri}`)
|
|
588
|
+
console.log(` Last updated: ${resource.lastUpdated}`)
|
|
589
|
+
})
|
|
590
|
+
console.log('')
|
|
591
|
+
|
|
592
|
+
if (options.stats) {
|
|
593
|
+
const statsPath = path.join(os.tmpdir(), 'mcp-usage-stats.json')
|
|
594
|
+
if (fs.existsSync(statsPath)) {
|
|
595
|
+
try {
|
|
596
|
+
const stats = JSON.parse(fs.readFileSync(statsPath, 'utf8'))
|
|
597
|
+
console.log('Usage Statistics:')
|
|
598
|
+
console.log('='.repeat(60))
|
|
599
|
+
|
|
600
|
+
if (stats.tools && Object.keys(stats.tools).length > 0) {
|
|
601
|
+
console.log('\nTool Usage:')
|
|
602
|
+
Object.entries(stats.tools)
|
|
603
|
+
.sort(([, a], [, b]) => b.count - a.count)
|
|
604
|
+
.forEach(([name, data]) => {
|
|
605
|
+
console.log(` ${name}:`)
|
|
606
|
+
console.log(` Invocations: ${data.count}`)
|
|
607
|
+
if (data.errors > 0) {
|
|
608
|
+
console.log(` Errors: ${data.errors}`)
|
|
609
|
+
}
|
|
610
|
+
})
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
if (stats.prompts && Object.keys(stats.prompts).length > 0) {
|
|
614
|
+
console.log('\nPrompt Usage:')
|
|
615
|
+
Object.entries(stats.prompts)
|
|
616
|
+
.sort(([, a], [, b]) => b - a)
|
|
617
|
+
.forEach(([name, count]) => {
|
|
618
|
+
console.log(` ${name}: ${count} invocations`)
|
|
619
|
+
})
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
if (stats.resources && Object.keys(stats.resources).length > 0) {
|
|
623
|
+
console.log('\nResource Access:')
|
|
624
|
+
Object.entries(stats.resources)
|
|
625
|
+
.sort(([, a], [, b]) => b - a)
|
|
626
|
+
.forEach(([uri, count]) => {
|
|
627
|
+
console.log(` ${uri}: ${count} reads`)
|
|
628
|
+
})
|
|
629
|
+
}
|
|
630
|
+
} catch (err) {
|
|
631
|
+
console.error('Failed to parse usage statistics:', err.message)
|
|
632
|
+
}
|
|
633
|
+
} else {
|
|
634
|
+
console.log('No usage statistics available yet.')
|
|
635
|
+
console.log('Statistics are exported when the MCP server shuts down.')
|
|
939
636
|
}
|
|
940
|
-
} else {
|
|
941
|
-
console.log(`ℹ️ Skipping cleanup of directory outside tmp/: ${dirPath}`);
|
|
942
637
|
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
638
|
+
|
|
639
|
+
console.log('')
|
|
640
|
+
console.log('For more information, see:')
|
|
641
|
+
console.log(' mcp/WRITER_EXTENSION_GUIDE.adoc')
|
|
642
|
+
console.log(' mcp/AI_CONSISTENCY_ARCHITECTURE.adoc')
|
|
643
|
+
} catch (err) {
|
|
644
|
+
console.error(`Error: Failed to retrieve version information: ${err.message}`)
|
|
645
|
+
process.exit(1)
|
|
646
|
+
}
|
|
647
|
+
})
|
|
648
|
+
|
|
649
|
+
// ====================================================================
|
|
650
|
+
// GENERATE SUBCOMMAND GROUP
|
|
651
|
+
// ====================================================================
|
|
652
|
+
|
|
653
|
+
const automation = new Command('generate').description('Run docs automations')
|
|
948
654
|
|
|
949
655
|
/**
|
|
950
656
|
* generate metrics-docs
|
|
@@ -968,13 +674,13 @@ function diffDirs(kind, oldTag, newTag, oldTempDir, newTempDir) {
|
|
|
968
674
|
* npx doc-tools generate metrics-docs --tag v25.3.1
|
|
969
675
|
*
|
|
970
676
|
* # Compare metrics between versions to see what changed
|
|
971
|
-
* npx doc-tools generate metrics-docs
|
|
972
|
-
* --tag v25.3.1
|
|
677
|
+
* npx doc-tools generate metrics-docs \
|
|
678
|
+
* --tag v25.3.1 \
|
|
973
679
|
* --diff v25.2.1
|
|
974
680
|
*
|
|
975
681
|
* # Use custom Docker repository
|
|
976
|
-
* npx doc-tools generate metrics-docs
|
|
977
|
-
* --tag v25.3.1
|
|
682
|
+
* npx doc-tools generate metrics-docs \
|
|
683
|
+
* --tag v25.3.1 \
|
|
978
684
|
* --docker-repo docker.redpanda.com/redpandadata/redpanda
|
|
979
685
|
*
|
|
980
686
|
* # Full workflow: document new release
|
|
@@ -992,66 +698,52 @@ automation
|
|
|
992
698
|
.description('Generate JSON and AsciiDoc documentation for Redpanda metrics. Defaults to branch "dev" if neither --tag nor --branch is specified.')
|
|
993
699
|
.option('-t, --tag <tag>', 'Git tag for released content (GA/beta)')
|
|
994
700
|
.option('-b, --branch <branch>', 'Branch name for in-progress content')
|
|
995
|
-
.option(
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
commonOptions.dockerRepo
|
|
999
|
-
)
|
|
1000
|
-
.option(
|
|
1001
|
-
'--console-tag <tag>',
|
|
1002
|
-
'Redpanda Console version to use when starting Redpanda Console in Docker',
|
|
1003
|
-
commonOptions.consoleTag
|
|
1004
|
-
)
|
|
1005
|
-
.option(
|
|
1006
|
-
'--console-docker-repo <repo>',
|
|
1007
|
-
'Docker repository to use when starting Redpanda Console in Docker',
|
|
1008
|
-
commonOptions.consoleDockerRepo
|
|
1009
|
-
)
|
|
701
|
+
.option('--docker-repo <repo>', 'Docker repository to use', commonOptions.dockerRepo)
|
|
702
|
+
.option('--console-tag <tag>', 'Redpanda Console version to use', commonOptions.consoleTag)
|
|
703
|
+
.option('--console-docker-repo <repo>', 'Docker repository for Console', commonOptions.consoleDockerRepo)
|
|
1010
704
|
.option('--diff <oldTag>', 'Also diff autogenerated metrics from <oldTag> → <tag>')
|
|
1011
705
|
.action((options) => {
|
|
1012
|
-
verifyMetricsDependencies()
|
|
706
|
+
verifyMetricsDependencies()
|
|
1013
707
|
|
|
1014
|
-
// Validate that tag and branch are mutually exclusive
|
|
1015
708
|
if (options.tag && options.branch) {
|
|
1016
|
-
console.error('
|
|
1017
|
-
process.exit(1)
|
|
709
|
+
console.error('Error: Cannot specify both --tag and --branch')
|
|
710
|
+
process.exit(1)
|
|
1018
711
|
}
|
|
1019
712
|
|
|
1020
|
-
|
|
1021
|
-
const
|
|
1022
|
-
const oldTag = options.diff;
|
|
713
|
+
const newTag = options.tag || options.branch || 'dev'
|
|
714
|
+
const oldTag = options.diff
|
|
1023
715
|
|
|
1024
716
|
if (oldTag) {
|
|
1025
|
-
const oldDir = path.join('autogenerated', oldTag, 'metrics')
|
|
717
|
+
const oldDir = path.join('autogenerated', oldTag, 'metrics')
|
|
1026
718
|
if (!fs.existsSync(oldDir)) {
|
|
1027
|
-
console.log(
|
|
1028
|
-
runClusterDocs('metrics', oldTag, options)
|
|
719
|
+
console.log(`Generating metrics docs for old tag ${oldTag}…`)
|
|
720
|
+
runClusterDocs('metrics', oldTag, options)
|
|
1029
721
|
}
|
|
1030
722
|
}
|
|
1031
723
|
|
|
1032
|
-
console.log(
|
|
1033
|
-
runClusterDocs('metrics', newTag, options)
|
|
724
|
+
console.log(`Generating metrics docs for new tag ${newTag}…`)
|
|
725
|
+
runClusterDocs('metrics', newTag, options)
|
|
1034
726
|
|
|
1035
727
|
if (oldTag) {
|
|
1036
|
-
diffDirs('metrics', oldTag, newTag)
|
|
728
|
+
diffDirs('metrics', oldTag, newTag)
|
|
1037
729
|
}
|
|
1038
730
|
|
|
1039
|
-
process.exit(0)
|
|
1040
|
-
})
|
|
731
|
+
process.exit(0)
|
|
732
|
+
})
|
|
1041
733
|
|
|
1042
734
|
/**
|
|
1043
735
|
* generate rpcn-connector-docs
|
|
1044
736
|
*
|
|
1045
737
|
* @description
|
|
1046
738
|
* Generates complete reference documentation for Redpanda Connect (formerly Benthos) connectors,
|
|
1047
|
-
* processors, and components.
|
|
1048
|
-
*
|
|
1049
|
-
*
|
|
1050
|
-
*
|
|
739
|
+
* processors, and components. Parses component templates and configuration schemas, reads
|
|
740
|
+
* connector metadata from CSV, and generates AsciiDoc documentation for each component. Supports
|
|
741
|
+
* diffing changes between versions and automatically updating what's new documentation. Can also
|
|
742
|
+
* generate Bloblang function documentation.
|
|
1051
743
|
*
|
|
1052
744
|
* @why
|
|
1053
745
|
* Redpanda Connect has hundreds of connectors (inputs, outputs, processors) with complex
|
|
1054
|
-
* configuration schemas. Each component's documentation lives in its
|
|
746
|
+
* configuration schemas. Each component's documentation lives in its source code as struct
|
|
1055
747
|
* tags and comments. Manual documentation is impossible to maintain. This automation extracts
|
|
1056
748
|
* documentation directly from code, ensuring accuracy and completeness. The diff capability
|
|
1057
749
|
* automatically identifies new connectors and changed configurations for release notes.
|
|
@@ -1066,30 +758,28 @@ automation
|
|
|
1066
758
|
* # Include Bloblang function documentation
|
|
1067
759
|
* npx doc-tools generate rpcn-connector-docs --include-bloblang
|
|
1068
760
|
*
|
|
1069
|
-
* #
|
|
1070
|
-
* npx doc-tools generate rpcn-connector-docs
|
|
1071
|
-
* --csv custom/connector-metadata.csv
|
|
761
|
+
* # Fetch latest connector data using rpk
|
|
762
|
+
* npx doc-tools generate rpcn-connector-docs --fetch-connectors
|
|
1072
763
|
*
|
|
1073
764
|
* # Full workflow with diff and what's new update
|
|
1074
|
-
* npx doc-tools generate rpcn-connector-docs
|
|
1075
|
-
* --update-whats-new
|
|
765
|
+
* npx doc-tools generate rpcn-connector-docs \
|
|
766
|
+
* --update-whats-new \
|
|
1076
767
|
* --include-bloblang
|
|
1077
768
|
*
|
|
1078
769
|
* @requirements
|
|
1079
|
-
* -
|
|
1080
|
-
* - Internet connection
|
|
770
|
+
* - rpk and rpk connect must be installed
|
|
771
|
+
* - Internet connection for fetching connector data
|
|
1081
772
|
* - Node.js for parsing and generation
|
|
1082
|
-
* - Sufficient disk space for repository clone (~500MB)
|
|
1083
773
|
*/
|
|
1084
|
-
|
|
774
|
+
automation
|
|
1085
775
|
.command('rpcn-connector-docs')
|
|
1086
776
|
.description('Generate RPCN connector docs and diff changes since the last version')
|
|
1087
777
|
.option('-d, --data-dir <path>', 'Directory where versioned connect JSON files live', path.resolve(process.cwd(), 'docs-data'))
|
|
1088
778
|
.option('--old-data <path>', 'Optional override for old data file (for diff)')
|
|
1089
779
|
.option('--update-whats-new', 'Update whats-new.adoc with new section from diff JSON')
|
|
1090
780
|
.option('-f, --fetch-connectors', 'Fetch latest connector data using rpk')
|
|
781
|
+
.option('--connect-version <version>', 'Connect version to fetch (requires --fetch-connectors)')
|
|
1091
782
|
.option('-m, --draft-missing', 'Generate full-doc drafts for connectors missing in output')
|
|
1092
|
-
.option('--csv <path>', 'Path to connector metadata CSV file', 'internal/plugins/info.csv')
|
|
1093
783
|
.option('--template-main <path>', 'Main Handlebars template', path.resolve(__dirname, '../tools/redpanda-connect/templates/connector.hbs'))
|
|
1094
784
|
.option('--template-intro <path>', 'Intro section partial template', path.resolve(__dirname, '../tools/redpanda-connect/templates/intro.hbs'))
|
|
1095
785
|
.option('--template-fields <path>', 'Fields section partial template', path.resolve(__dirname, '../tools/redpanda-connect/templates/fields-partials.hbs'))
|
|
@@ -1097,760 +787,22 @@ automation
|
|
|
1097
787
|
.option('--template-bloblang <path>', 'Custom Handlebars template for bloblang function/method partials')
|
|
1098
788
|
.option('--overrides <path>', 'Optional JSON file with overrides', 'docs-data/overrides.json')
|
|
1099
789
|
.option('--include-bloblang', 'Include Bloblang functions and methods in generation')
|
|
790
|
+
.option('--cloud-version <version>', 'Cloud binary version (default: auto-detect latest)')
|
|
791
|
+
.option('--cgo-version <version>', 'cgo binary version (default: same as cloud-version)')
|
|
1100
792
|
.action(async (options) => {
|
|
1101
793
|
requireTool('rpk', {
|
|
1102
794
|
versionFlag: '--version',
|
|
1103
795
|
help: 'rpk is not installed. Install rpk: https://docs.redpanda.com/current/get-started/rpk-install/'
|
|
1104
|
-
})
|
|
796
|
+
})
|
|
1105
797
|
|
|
1106
798
|
requireTool('rpk connect', {
|
|
1107
799
|
versionFlag: '--version',
|
|
1108
800
|
help: 'rpk connect is not installed. Run rpk connect install before continuing.'
|
|
1109
|
-
})
|
|
1110
|
-
|
|
1111
|
-
const dataDir = path.resolve(process.cwd(), options.dataDir);
|
|
1112
|
-
fs.mkdirSync(dataDir, { recursive: true });
|
|
1113
|
-
|
|
1114
|
-
const timestamp = new Date().toISOString();
|
|
1115
|
-
|
|
1116
|
-
let newVersion;
|
|
1117
|
-
let dataFile;
|
|
1118
|
-
if (options.fetchConnectors) {
|
|
1119
|
-
try {
|
|
1120
|
-
newVersion = getRpkConnectVersion();
|
|
1121
|
-
const tmpFile = path.join(dataDir, `connect-${newVersion}.tmp.json`);
|
|
1122
|
-
const finalFile = path.join(dataDir, `connect-${newVersion}.json`);
|
|
1123
|
-
|
|
1124
|
-
const fd = fs.openSync(tmpFile, 'w');
|
|
1125
|
-
const r = spawnSync('rpk', ['connect', 'list', '--format', 'json-full'], { stdio: ['ignore', fd, 'inherit'] });
|
|
1126
|
-
fs.closeSync(fd);
|
|
1127
|
-
|
|
1128
|
-
const rawJson = fs.readFileSync(tmpFile, 'utf8');
|
|
1129
|
-
const parsed = JSON.parse(rawJson);
|
|
1130
|
-
fs.writeFileSync(finalFile, JSON.stringify(parsed, null, 2));
|
|
1131
|
-
fs.unlinkSync(tmpFile);
|
|
1132
|
-
dataFile = finalFile;
|
|
1133
|
-
console.log(`✅ Fetched and saved: ${finalFile}`);
|
|
1134
|
-
|
|
1135
|
-
// Keep only 2 most recent versions in docs-data
|
|
1136
|
-
const dataFiles = fs.readdirSync(dataDir)
|
|
1137
|
-
.filter(f => /^connect-\d+\.\d+\.\d+\.json$/.test(f))
|
|
1138
|
-
.sort();
|
|
1139
|
-
|
|
1140
|
-
while (dataFiles.length > 2) {
|
|
1141
|
-
const oldestFile = dataFiles.shift();
|
|
1142
|
-
const oldestPath = path.join(dataDir, oldestFile);
|
|
1143
|
-
fs.unlinkSync(oldestPath);
|
|
1144
|
-
console.log(`🧹 Deleted old version from docs-data: ${oldestFile}`);
|
|
1145
|
-
}
|
|
1146
|
-
} catch (err) {
|
|
1147
|
-
console.error(`❌ Failed to fetch connectors: ${err.message}`);
|
|
1148
|
-
process.exit(1);
|
|
1149
|
-
}
|
|
1150
|
-
} else {
|
|
1151
|
-
const candidates = fs.readdirSync(dataDir).filter(f => /^connect-\d+\.\d+\.\d+\.json$/.test(f));
|
|
1152
|
-
if (candidates.length === 0) {
|
|
1153
|
-
console.error('❌ No connect-<version>.json found. Use --fetch-connectors.');
|
|
1154
|
-
process.exit(1);
|
|
1155
|
-
}
|
|
1156
|
-
candidates.sort();
|
|
1157
|
-
dataFile = path.join(dataDir, candidates[candidates.length - 1]);
|
|
1158
|
-
newVersion = candidates[candidates.length - 1].match(/connect-(\d+\.\d+\.\d+)\.json/)[1];
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
console.log('⏳ Generating connector partials...');
|
|
1162
|
-
let partialsWritten, partialFiles, draftsWritten, draftFiles;
|
|
1163
|
-
|
|
1164
|
-
try {
|
|
1165
|
-
const result = await generateRpcnConnectorDocs({
|
|
1166
|
-
data: dataFile,
|
|
1167
|
-
overrides: options.overrides,
|
|
1168
|
-
template: options.templateMain,
|
|
1169
|
-
templateIntro: options.templateIntro,
|
|
1170
|
-
templateFields: options.templateFields,
|
|
1171
|
-
templateExamples: options.templateExamples,
|
|
1172
|
-
templateBloblang: options.templateBloblang,
|
|
1173
|
-
writeFullDrafts: false,
|
|
1174
|
-
includeBloblang: !!options.includeBloblang
|
|
1175
|
-
});
|
|
1176
|
-
partialsWritten = result.partialsWritten;
|
|
1177
|
-
partialFiles = result.partialFiles;
|
|
1178
|
-
} catch (err) {
|
|
1179
|
-
console.error(`❌ Failed to generate partials: ${err.message}`);
|
|
1180
|
-
process.exit(1);
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
if (options.draftMissing) {
|
|
1184
|
-
console.log('⏳ Drafting missing connectors…');
|
|
1185
|
-
try {
|
|
1186
|
-
const connectorList = await parseCSVConnectors(options.csv, console);
|
|
1187
|
-
const validConnectors = connectorList.filter(r => r.name && r.type);
|
|
1188
|
-
|
|
1189
|
-
const roots = {
|
|
1190
|
-
pages: path.resolve(process.cwd(), 'modules/components/pages'),
|
|
1191
|
-
partials:path.resolve(process.cwd(), 'modules/components/partials/components'),
|
|
1192
|
-
};
|
|
1193
|
-
|
|
1194
|
-
// find any connector that has NO .adoc under pages/TYPEs or partials/TYPEs
|
|
1195
|
-
const allMissing = validConnectors.filter(({ name, type }) => {
|
|
1196
|
-
const relPath = path.join(`${type}s`, `${name}.adoc`);
|
|
1197
|
-
const existsInAny = Object.values(roots).some(root =>
|
|
1198
|
-
fs.existsSync(path.join(root, relPath))
|
|
1199
|
-
);
|
|
1200
|
-
return !existsInAny;
|
|
1201
|
-
});
|
|
1202
|
-
|
|
1203
|
-
// still skip sql_driver
|
|
1204
|
-
const missingConnectors = allMissing.filter(c => !c.name.includes('sql_driver'));
|
|
1205
|
-
|
|
1206
|
-
if (missingConnectors.length === 0) {
|
|
1207
|
-
console.log('✅ All connectors (excluding sql_drivers) already have docs—nothing to draft.');
|
|
1208
|
-
} else {
|
|
1209
|
-
console.log(`⏳ Docs missing for ${missingConnectors.length} connectors:`);
|
|
1210
|
-
missingConnectors.forEach(({ name, type }) => {
|
|
1211
|
-
console.log(` • ${type}/${name}`);
|
|
1212
|
-
});
|
|
1213
|
-
console.log('');
|
|
1214
|
-
|
|
1215
|
-
// build your filtered JSON as before…
|
|
1216
|
-
const rawData = fs.readFileSync(dataFile, 'utf8');
|
|
1217
|
-
const dataObj = JSON.parse(rawData);
|
|
1218
|
-
const filteredDataObj = {};
|
|
1219
|
-
|
|
1220
|
-
for (const [key, arr] of Object.entries(dataObj)) {
|
|
1221
|
-
if (!Array.isArray(arr)) {
|
|
1222
|
-
filteredDataObj[key] = arr;
|
|
1223
|
-
continue;
|
|
1224
|
-
}
|
|
1225
|
-
filteredDataObj[key] = arr.filter(component =>
|
|
1226
|
-
missingConnectors.some(
|
|
1227
|
-
m => m.name === component.name && `${m.type}s` === key
|
|
1228
|
-
)
|
|
1229
|
-
);
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
|
-
const tempDataPath = path.join(dataDir, '._filtered_connect_data.json');
|
|
1233
|
-
fs.writeFileSync(tempDataPath, JSON.stringify(filteredDataObj, null, 2), 'utf8');
|
|
1234
|
-
|
|
1235
|
-
const draftResult = await generateRpcnConnectorDocs({
|
|
1236
|
-
data: tempDataPath,
|
|
1237
|
-
overrides: options.overrides,
|
|
1238
|
-
template: options.templateMain,
|
|
1239
|
-
templateFields: options.templateFields,
|
|
1240
|
-
templateExamples:options.templateExamples,
|
|
1241
|
-
templateIntro: options.templateIntro,
|
|
1242
|
-
writeFullDrafts: true
|
|
1243
|
-
});
|
|
1244
|
-
|
|
1245
|
-
fs.unlinkSync(tempDataPath);
|
|
1246
|
-
draftsWritten = draftResult.draftsWritten;
|
|
1247
|
-
draftFiles = draftResult.draftFiles;
|
|
1248
|
-
}
|
|
1249
|
-
} catch (err) {
|
|
1250
|
-
console.error(`❌ Could not draft missing: ${err.message}`);
|
|
1251
|
-
process.exit(1);
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
let oldIndex = {};
|
|
1256
|
-
let oldVersion = null;
|
|
1257
|
-
if (options.oldData && fs.existsSync(options.oldData)) {
|
|
1258
|
-
oldIndex = JSON.parse(fs.readFileSync(options.oldData, 'utf8'));
|
|
1259
|
-
const m = options.oldData.match(/connect-([\d.]+)\.json$/);
|
|
1260
|
-
if (m) oldVersion = m[1];
|
|
1261
|
-
} else {
|
|
1262
|
-
oldVersion = getAntoraValue('asciidoc.attributes.latest-connect-version');
|
|
1263
|
-
if (oldVersion) {
|
|
1264
|
-
const oldPath = path.join(dataDir, `connect-${oldVersion}.json`);
|
|
1265
|
-
if (fs.existsSync(oldPath)) {
|
|
1266
|
-
oldIndex = JSON.parse(fs.readFileSync(oldPath, 'utf8'));
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1269
|
-
}
|
|
801
|
+
})
|
|
1270
802
|
|
|
1271
|
-
const
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
if (oldVersion && newVersion && oldVersion === newVersion) {
|
|
1275
|
-
console.log(`\n✓ Already at version ${newVersion}`);
|
|
1276
|
-
console.log(' No diff or version updates needed.\n');
|
|
1277
|
-
|
|
1278
|
-
console.log('📊 Generation Report:');
|
|
1279
|
-
console.log(` • Partial files: ${partialsWritten}`);
|
|
1280
|
-
const fieldsPartials = partialFiles.filter(fp => fp.includes('/fields/'));
|
|
1281
|
-
const examplesPartials = partialFiles.filter(fp => fp.includes('/examples/'));
|
|
1282
|
-
|
|
1283
|
-
console.log(` • Fields partials: ${fieldsPartials.length}`);
|
|
1284
|
-
console.log(` • Examples partials: ${examplesPartials.length}`);
|
|
1285
|
-
|
|
1286
|
-
if (options.draftMissing && draftsWritten) {
|
|
1287
|
-
console.log(` • Draft files: ${draftsWritten}`);
|
|
1288
|
-
}
|
|
1289
|
-
|
|
1290
|
-
process.exit(0);
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
// Publish merged version with overrides to modules/components/attachments
|
|
1294
|
-
if (options.overrides && fs.existsSync(options.overrides)) {
|
|
1295
|
-
try {
|
|
1296
|
-
const { mergeOverrides, resolveReferences } = require('../tools/redpanda-connect/generate-rpcn-connector-docs.js');
|
|
1297
|
-
|
|
1298
|
-
// Create a copy of newIndex to merge overrides into
|
|
1299
|
-
const mergedData = JSON.parse(JSON.stringify(newIndex));
|
|
1300
|
-
|
|
1301
|
-
// Read and apply overrides
|
|
1302
|
-
const ovRaw = fs.readFileSync(options.overrides, 'utf8');
|
|
1303
|
-
const ovObj = JSON.parse(ovRaw);
|
|
1304
|
-
const resolvedOverrides = resolveReferences(ovObj, ovObj);
|
|
1305
|
-
mergeOverrides(mergedData, resolvedOverrides);
|
|
1306
|
-
|
|
1307
|
-
// Publish to modules/components/attachments
|
|
1308
|
-
const attachmentsRoot = path.resolve(process.cwd(), 'modules/components/attachments');
|
|
1309
|
-
fs.mkdirSync(attachmentsRoot, { recursive: true });
|
|
1310
|
-
|
|
1311
|
-
// Delete older versions from modules/components/attachments
|
|
1312
|
-
const existingFiles = fs.readdirSync(attachmentsRoot)
|
|
1313
|
-
.filter(f => /^connect-\d+\.\d+\.\d+\.json$/.test(f))
|
|
1314
|
-
.sort();
|
|
1315
|
-
|
|
1316
|
-
for (const oldFile of existingFiles) {
|
|
1317
|
-
const oldFilePath = path.join(attachmentsRoot, oldFile);
|
|
1318
|
-
fs.unlinkSync(oldFilePath);
|
|
1319
|
-
console.log(`🧹 Deleted old version: ${oldFile}`);
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
// Save merged version to modules/components/attachments
|
|
1323
|
-
const destFile = path.join(attachmentsRoot, `connect-${newVersion}.json`);
|
|
1324
|
-
fs.writeFileSync(destFile, JSON.stringify(mergedData, null, 2), 'utf8');
|
|
1325
|
-
console.log(`✅ Published merged version to: ${path.relative(process.cwd(), destFile)}`);
|
|
1326
|
-
} catch (err) {
|
|
1327
|
-
console.error(`❌ Failed to publish merged version: ${err.message}`);
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
printDeltaReport(oldIndex, newIndex);
|
|
1332
|
-
|
|
1333
|
-
// Generate JSON diff file for whats-new.adoc
|
|
1334
|
-
const { generateConnectorDiffJson } = require('../tools/redpanda-connect/report-delta.js');
|
|
1335
|
-
const diffJson = generateConnectorDiffJson(
|
|
1336
|
-
oldIndex,
|
|
1337
|
-
newIndex,
|
|
1338
|
-
{
|
|
1339
|
-
oldVersion: oldVersion || '',
|
|
1340
|
-
newVersion,
|
|
1341
|
-
timestamp
|
|
1342
|
-
}
|
|
1343
|
-
);
|
|
1344
|
-
const diffPath = path.join(dataDir, `connect-diff-${(oldVersion || 'unknown')}_to_${newVersion}.json`);
|
|
1345
|
-
fs.writeFileSync(diffPath, JSON.stringify(diffJson, null, 2), 'utf8');
|
|
1346
|
-
console.log(`✅ Connector diff JSON written to: ${diffPath}`);
|
|
1347
|
-
|
|
1348
|
-
function logCollapsed(label, filesArray, maxToShow = 10) {
|
|
1349
|
-
console.log(` • ${label}: ${filesArray.length} total`);
|
|
1350
|
-
const sample = filesArray.slice(0, maxToShow);
|
|
1351
|
-
sample.forEach(fp => console.log(` – ${fp}`));
|
|
1352
|
-
const remaining = filesArray.length - sample.length;
|
|
1353
|
-
if (remaining > 0) {
|
|
1354
|
-
console.log(` … plus ${remaining} more`);
|
|
1355
|
-
}
|
|
1356
|
-
console.log('');
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
const wrote = setAntoraValue('asciidoc.attributes.latest-connect-version', newVersion);
|
|
1360
|
-
if (wrote) {
|
|
1361
|
-
console.log(`✅ Updated Antora version: ${newVersion}`);
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
console.log('📊 Generation Report:');
|
|
1365
|
-
console.log(` • Partial files: ${partialsWritten}`);
|
|
1366
|
-
// Split “partials” into fields vs examples by checking the path substring.
|
|
1367
|
-
const fieldsPartials = partialFiles.filter(fp => fp.includes('/fields/'));
|
|
1368
|
-
const examplesPartials = partialFiles.filter(fp => fp.includes('/examples/'));
|
|
1369
|
-
|
|
1370
|
-
// Show only up to 10 of each
|
|
1371
|
-
logCollapsed('Fields partials', fieldsPartials, 10);
|
|
1372
|
-
logCollapsed('Examples partials', examplesPartials, 10);
|
|
1373
|
-
|
|
1374
|
-
if (options.draftMissing) {
|
|
1375
|
-
console.log(` • Full drafts: ${draftsWritten}`);
|
|
1376
|
-
logCollapsed('Draft files', draftFiles, 5);
|
|
1377
|
-
}
|
|
1378
|
-
|
|
1379
|
-
// Optionally update whats-new.adoc
|
|
1380
|
-
if (options.updateWhatsNew) {
|
|
1381
|
-
// Helper function to cap description to two sentences
|
|
1382
|
-
const capToTwoSentences = (description) => {
|
|
1383
|
-
if (!description) return '';
|
|
1384
|
-
|
|
1385
|
-
// Helper to check if text contains problematic content
|
|
1386
|
-
const hasProblematicContent = (text) => {
|
|
1387
|
-
return /```[\s\S]*?```/.test(text) || // code blocks
|
|
1388
|
-
/`[^`]+`/.test(text) || // inline code
|
|
1389
|
-
/^[=#]+\s+.+$/m.test(text) || // headings
|
|
1390
|
-
/\n/.test(text); // newlines
|
|
1391
|
-
};
|
|
1392
|
-
|
|
1393
|
-
// Step 1: Replace common abbreviations and ellipses with placeholders
|
|
1394
|
-
const abbreviations = [
|
|
1395
|
-
/\bv\d+\.\d+(?:\.\d+)?/gi, // version numbers like v4.12 or v4.12.0 (must come before decimal)
|
|
1396
|
-
/\d+\.\d+/g, // decimal numbers
|
|
1397
|
-
/\be\.g\./gi, // e.g.
|
|
1398
|
-
/\bi\.e\./gi, // i.e.
|
|
1399
|
-
/\betc\./gi, // etc.
|
|
1400
|
-
/\bvs\./gi, // vs.
|
|
1401
|
-
/\bDr\./gi, // Dr.
|
|
1402
|
-
/\bMr\./gi, // Mr.
|
|
1403
|
-
/\bMs\./gi, // Ms.
|
|
1404
|
-
/\bMrs\./gi, // Mrs.
|
|
1405
|
-
/\bSt\./gi, // St.
|
|
1406
|
-
/\bNo\./gi // No.
|
|
1407
|
-
];
|
|
1408
|
-
|
|
1409
|
-
let normalized = description;
|
|
1410
|
-
const placeholders = [];
|
|
1411
|
-
|
|
1412
|
-
// Replace abbreviations with placeholders
|
|
1413
|
-
abbreviations.forEach((abbrevRegex, idx) => {
|
|
1414
|
-
normalized = normalized.replace(abbrevRegex, (match) => {
|
|
1415
|
-
const placeholder = `__ABBREV${idx}_${placeholders.length}__`;
|
|
1416
|
-
placeholders.push({ placeholder, original: match });
|
|
1417
|
-
return placeholder;
|
|
1418
|
-
});
|
|
1419
|
-
});
|
|
1420
|
-
|
|
1421
|
-
// Replace ellipses (three or more dots) with placeholder
|
|
1422
|
-
normalized = normalized.replace(/\.{3,}/g, (match) => {
|
|
1423
|
-
const placeholder = `__ELLIPSIS_${placeholders.length}__`;
|
|
1424
|
-
placeholders.push({ placeholder, original: match });
|
|
1425
|
-
return placeholder;
|
|
1426
|
-
});
|
|
1427
|
-
|
|
1428
|
-
// Step 2: Split sentences using the regex
|
|
1429
|
-
const sentenceRegex = /[^.!?]+[.!?]+(?:\s|$)/g;
|
|
1430
|
-
const sentences = normalized.match(sentenceRegex);
|
|
1431
|
-
|
|
1432
|
-
if (!sentences || sentences.length === 0) {
|
|
1433
|
-
// Restore placeholders and return original
|
|
1434
|
-
let result = normalized;
|
|
1435
|
-
placeholders.forEach(({ placeholder, original }) => {
|
|
1436
|
-
result = result.replace(placeholder, original);
|
|
1437
|
-
});
|
|
1438
|
-
return result;
|
|
1439
|
-
}
|
|
1440
|
-
|
|
1441
|
-
// Step 3: Determine how many sentences to include
|
|
1442
|
-
let maxSentences = 2;
|
|
1443
|
-
|
|
1444
|
-
// If we have at least 2 sentences, check if the second one has problematic content
|
|
1445
|
-
if (sentences.length >= 2) {
|
|
1446
|
-
// Restore placeholders in second sentence to check original content
|
|
1447
|
-
let secondSentence = sentences[1];
|
|
1448
|
-
placeholders.forEach(({ placeholder, original }) => {
|
|
1449
|
-
secondSentence = secondSentence.replace(new RegExp(placeholder, 'g'), original);
|
|
1450
|
-
});
|
|
1451
|
-
|
|
1452
|
-
// If second sentence has problematic content, only take first sentence
|
|
1453
|
-
if (hasProblematicContent(secondSentence)) {
|
|
1454
|
-
maxSentences = 1;
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
|
|
1458
|
-
let result = sentences.slice(0, maxSentences).join('');
|
|
1459
|
-
|
|
1460
|
-
// Step 4: Restore placeholders back to original text
|
|
1461
|
-
placeholders.forEach(({ placeholder, original }) => {
|
|
1462
|
-
result = result.replace(new RegExp(placeholder, 'g'), original);
|
|
1463
|
-
});
|
|
1464
|
-
|
|
1465
|
-
return result.trim();
|
|
1466
|
-
};
|
|
1467
|
-
|
|
1468
|
-
try {
|
|
1469
|
-
const whatsNewPath = path.join(findRepoRoot(), 'modules/get-started/pages/whats-new.adoc');
|
|
1470
|
-
if (!fs.existsSync(whatsNewPath)) {
|
|
1471
|
-
console.error(`❌ Unable to update release notes: 'whats-new.adoc' was not found at: ${whatsNewPath}\nPlease ensure this file exists and is tracked in your repository.`);
|
|
1472
|
-
return;
|
|
1473
|
-
}
|
|
1474
|
-
// Find the diff JSON file we just wrote
|
|
1475
|
-
const diffPath = path.join(dataDir, `connect-diff-${(oldVersion || 'unknown')}_to_${newVersion}.json`);
|
|
1476
|
-
if (!fs.existsSync(diffPath)) {
|
|
1477
|
-
console.error(`❌ Unable to update release notes: The connector diff JSON was not found at: ${diffPath}\nPlease ensure the diff was generated successfully before updating release notes.`);
|
|
1478
|
-
return;
|
|
1479
|
-
}
|
|
1480
|
-
let diff;
|
|
1481
|
-
try {
|
|
1482
|
-
diff = JSON.parse(fs.readFileSync(diffPath, 'utf8'));
|
|
1483
|
-
} catch (jsonErr) {
|
|
1484
|
-
console.error(`❌ Unable to parse connector diff JSON at ${diffPath}: ${jsonErr.message}\nPlease check the file for syntax errors or corruption.`);
|
|
1485
|
-
return;
|
|
1486
|
-
}
|
|
1487
|
-
let whatsNewContent;
|
|
1488
|
-
try {
|
|
1489
|
-
whatsNewContent = fs.readFileSync(whatsNewPath, 'utf8');
|
|
1490
|
-
} catch (readErr) {
|
|
1491
|
-
console.error(`❌ Unable to read whats-new.adoc at ${whatsNewPath}: ${readErr.message}\nPlease check file permissions and try again.`);
|
|
1492
|
-
return;
|
|
1493
|
-
}
|
|
1494
|
-
const whatsNew = whatsNewContent;
|
|
1495
|
-
// Regex to find section for this version
|
|
1496
|
-
const versionTitle = `== Version ${diff.comparison.newVersion}`;
|
|
1497
|
-
const versionRe = new RegExp(`^== Version ${diff.comparison.newVersion.replace(/[-.]/g, '\\$&')}(?:\\r?\\n|$)`, 'm');
|
|
1498
|
-
const match = versionRe.exec(whatsNew);
|
|
1499
|
-
let startIdx = match ? match.index : -1;
|
|
1500
|
-
let endIdx = -1;
|
|
1501
|
-
if (startIdx !== -1) {
|
|
1502
|
-
// Find the start of the next version section
|
|
1503
|
-
const rest = whatsNew.slice(startIdx + 1);
|
|
1504
|
-
const nextMatch = /^== Version /m.exec(rest);
|
|
1505
|
-
endIdx = nextMatch ? startIdx + 1 + nextMatch.index : whatsNew.length;
|
|
1506
|
-
}
|
|
1507
|
-
// Compose new section
|
|
1508
|
-
// Add link to full release notes for this connector version after version heading
|
|
1509
|
-
let releaseNotesLink = '';
|
|
1510
|
-
if (diff.comparison && diff.comparison.newVersion) {
|
|
1511
|
-
releaseNotesLink = `link:https://github.com/redpanda-data/connect/releases/tag/v${diff.comparison.newVersion}[See the full release notes^].\n\n`;
|
|
1512
|
-
}
|
|
1513
|
-
let section = `\n== Version ${diff.comparison.newVersion}\n\n${releaseNotesLink}`;
|
|
1514
|
-
|
|
1515
|
-
// Separate Bloblang components from regular components
|
|
1516
|
-
const bloblangComponents = [];
|
|
1517
|
-
const regularComponents = [];
|
|
1518
|
-
|
|
1519
|
-
if (diff.details.newComponents && diff.details.newComponents.length) {
|
|
1520
|
-
for (const comp of diff.details.newComponents) {
|
|
1521
|
-
if (comp.type === 'bloblang-functions' || comp.type === 'bloblang-methods') {
|
|
1522
|
-
bloblangComponents.push(comp);
|
|
1523
|
-
} else {
|
|
1524
|
-
regularComponents.push(comp);
|
|
1525
|
-
}
|
|
1526
|
-
}
|
|
1527
|
-
}
|
|
1528
|
-
|
|
1529
|
-
// Bloblang updates section
|
|
1530
|
-
if (bloblangComponents.length > 0) {
|
|
1531
|
-
section += '=== Bloblang updates\n\n';
|
|
1532
|
-
section += 'This release adds the following new Bloblang capabilities:\n\n';
|
|
1533
|
-
|
|
1534
|
-
// Group by type (functions vs methods)
|
|
1535
|
-
const byType = {};
|
|
1536
|
-
for (const comp of bloblangComponents) {
|
|
1537
|
-
if (!byType[comp.type]) byType[comp.type] = [];
|
|
1538
|
-
byType[comp.type].push(comp);
|
|
1539
|
-
}
|
|
1540
|
-
|
|
1541
|
-
for (const [type, comps] of Object.entries(byType)) {
|
|
1542
|
-
if (type === 'bloblang-functions') {
|
|
1543
|
-
section += '* Functions:\n';
|
|
1544
|
-
for (const comp of comps) {
|
|
1545
|
-
section += `** xref:guides:bloblang/functions.adoc#${comp.name}[\`${comp.name}\`]`;
|
|
1546
|
-
if (comp.status && comp.status !== 'stable') section += ` (${comp.status})`;
|
|
1547
|
-
if (comp.description) section += `: ${capToTwoSentences(comp.description)}`;
|
|
1548
|
-
section += '\n';
|
|
1549
|
-
}
|
|
1550
|
-
} else if (type === 'bloblang-methods') {
|
|
1551
|
-
section += '* Methods:\n';
|
|
1552
|
-
for (const comp of comps) {
|
|
1553
|
-
section += `** xref:guides:bloblang/methods.adoc#${comp.name}[\`${comp.name}\`]`;
|
|
1554
|
-
if (comp.status && comp.status !== 'stable') section += ` (${comp.status})`;
|
|
1555
|
-
if (comp.description) section += `: ${capToTwoSentences(comp.description)}`;
|
|
1556
|
-
section += '\n';
|
|
1557
|
-
}
|
|
1558
|
-
}
|
|
1559
|
-
}
|
|
1560
|
-
section += '\n';
|
|
1561
|
-
}
|
|
1562
|
-
|
|
1563
|
-
// Regular component updates section
|
|
1564
|
-
if (regularComponents.length > 0) {
|
|
1565
|
-
section += '=== Component updates\n\n';
|
|
1566
|
-
section += 'This release adds the following new components:\n\n';
|
|
1567
|
-
|
|
1568
|
-
section += '[cols="1m,1,1,3"]\n';
|
|
1569
|
-
section += '|===\n';
|
|
1570
|
-
section += '|Component |Type |Status |Description\n\n';
|
|
1571
|
-
|
|
1572
|
-
for (const comp of regularComponents) {
|
|
1573
|
-
const typeLabel = comp.type.charAt(0).toUpperCase() + comp.type.slice(1);
|
|
1574
|
-
const statusLabel = comp.status || '-';
|
|
1575
|
-
const desc = comp.description ? capToTwoSentences(comp.description) : '-';
|
|
1576
|
-
|
|
1577
|
-
section += `|xref:components:${comp.type}/${comp.name}.adoc[${comp.name}]\n`;
|
|
1578
|
-
section += `|${typeLabel}\n`;
|
|
1579
|
-
section += `|${statusLabel}\n`;
|
|
1580
|
-
section += `|${desc}\n\n`;
|
|
1581
|
-
}
|
|
1582
|
-
|
|
1583
|
-
section += '|===\n\n';
|
|
1584
|
-
}
|
|
1585
|
-
|
|
1586
|
-
// New fields (exclude Bloblang functions/methods)
|
|
1587
|
-
if (diff.details.newFields && diff.details.newFields.length) {
|
|
1588
|
-
// Filter out Bloblang components
|
|
1589
|
-
const regularFields = diff.details.newFields.filter(field => {
|
|
1590
|
-
const [type] = field.component.split(':');
|
|
1591
|
-
return type !== 'bloblang-functions' && type !== 'bloblang-methods';
|
|
1592
|
-
});
|
|
1593
|
-
|
|
1594
|
-
if (regularFields.length > 0) {
|
|
1595
|
-
section += '\n=== New field support\n\n';
|
|
1596
|
-
section += 'This release adds support for the following new fields:\n\n';
|
|
1597
|
-
|
|
1598
|
-
// Group by field name
|
|
1599
|
-
const byField = {};
|
|
1600
|
-
for (const field of regularFields) {
|
|
1601
|
-
const [type, compName] = field.component.split(':');
|
|
1602
|
-
if (!byField[field.field]) {
|
|
1603
|
-
byField[field.field] = {
|
|
1604
|
-
description: field.description,
|
|
1605
|
-
components: []
|
|
1606
|
-
};
|
|
1607
|
-
}
|
|
1608
|
-
byField[field.field].components.push({ type, name: compName });
|
|
1609
|
-
}
|
|
1610
|
-
|
|
1611
|
-
section += '[cols="1m,3,2a"]\n';
|
|
1612
|
-
section += '|===\n';
|
|
1613
|
-
section += '|Field |Description |Affected components\n\n';
|
|
1614
|
-
|
|
1615
|
-
for (const [fieldName, info] of Object.entries(byField)) {
|
|
1616
|
-
// Format component list - group by type
|
|
1617
|
-
const byType = {};
|
|
1618
|
-
for (const comp of info.components) {
|
|
1619
|
-
if (!byType[comp.type]) byType[comp.type] = [];
|
|
1620
|
-
byType[comp.type].push(comp.name);
|
|
1621
|
-
}
|
|
1622
|
-
|
|
1623
|
-
let componentList = '';
|
|
1624
|
-
for (const [type, names] of Object.entries(byType)) {
|
|
1625
|
-
if (componentList) componentList += '\n\n';
|
|
1626
|
-
|
|
1627
|
-
// Smart pluralization: don't add 's' if already plural
|
|
1628
|
-
const typeLabel = names.length === 1
|
|
1629
|
-
? type.charAt(0).toUpperCase() + type.slice(1)
|
|
1630
|
-
: type.charAt(0).toUpperCase() + type.slice(1) + (type.endsWith('s') ? '' : 's');
|
|
1631
|
-
|
|
1632
|
-
componentList += `*${typeLabel}:*\n\n`;
|
|
1633
|
-
names.forEach(name => {
|
|
1634
|
-
componentList += `* xref:components:${type}/${name}.adoc#${fieldName}[${name}]\n`;
|
|
1635
|
-
});
|
|
1636
|
-
}
|
|
1637
|
-
|
|
1638
|
-
const desc = info.description ? capToTwoSentences(info.description) : '-';
|
|
1639
|
-
|
|
1640
|
-
section += `|${fieldName}\n`;
|
|
1641
|
-
section += `|${desc}\n`;
|
|
1642
|
-
section += `|${componentList}\n\n`;
|
|
1643
|
-
}
|
|
1644
|
-
|
|
1645
|
-
section += '|===\n\n';
|
|
1646
|
-
}
|
|
1647
|
-
}
|
|
1648
|
-
|
|
1649
|
-
// Deprecated components
|
|
1650
|
-
if (diff.details.deprecatedComponents && diff.details.deprecatedComponents.length) {
|
|
1651
|
-
section += '\n=== Deprecations\n\n';
|
|
1652
|
-
section += 'The following components are now deprecated:\n\n';
|
|
1653
|
-
|
|
1654
|
-
section += '[cols="1m,1,3"]\n';
|
|
1655
|
-
section += '|===\n';
|
|
1656
|
-
section += '|Component |Type |Description\n\n';
|
|
1657
|
-
|
|
1658
|
-
for (const comp of diff.details.deprecatedComponents) {
|
|
1659
|
-
const typeLabel = comp.type.charAt(0).toUpperCase() + comp.type.slice(1);
|
|
1660
|
-
const desc = comp.description ? capToTwoSentences(comp.description) : '-';
|
|
1661
|
-
|
|
1662
|
-
if (comp.type === 'bloblang-functions') {
|
|
1663
|
-
section += `|xref:guides:bloblang/functions.adoc#${comp.name}[${comp.name}]\n`;
|
|
1664
|
-
} else if (comp.type === 'bloblang-methods') {
|
|
1665
|
-
section += `|xref:guides:bloblang/methods.adoc#${comp.name}[${comp.name}]\n`;
|
|
1666
|
-
} else {
|
|
1667
|
-
section += `|xref:components:${comp.type}/${comp.name}.adoc[${comp.name}]\n`;
|
|
1668
|
-
}
|
|
1669
|
-
section += `|${typeLabel}\n`;
|
|
1670
|
-
section += `|${desc}\n\n`;
|
|
1671
|
-
}
|
|
1672
|
-
|
|
1673
|
-
section += '|===\n\n';
|
|
1674
|
-
}
|
|
1675
|
-
|
|
1676
|
-
// Deprecated fields (exclude Bloblang functions/methods)
|
|
1677
|
-
if (diff.details.deprecatedFields && diff.details.deprecatedFields.length) {
|
|
1678
|
-
// Filter out Bloblang components
|
|
1679
|
-
const regularDeprecatedFields = diff.details.deprecatedFields.filter(field => {
|
|
1680
|
-
const [type] = field.component.split(':');
|
|
1681
|
-
return type !== 'bloblang-functions' && type !== 'bloblang-methods';
|
|
1682
|
-
});
|
|
1683
|
-
|
|
1684
|
-
if (regularDeprecatedFields.length > 0) {
|
|
1685
|
-
if (!diff.details.deprecatedComponents || diff.details.deprecatedComponents.length === 0) {
|
|
1686
|
-
section += '\n=== Deprecations\n\n';
|
|
1687
|
-
} else {
|
|
1688
|
-
section += '\n';
|
|
1689
|
-
}
|
|
1690
|
-
section += 'The following fields are now deprecated:\n\n';
|
|
1691
|
-
|
|
1692
|
-
// Group by field name
|
|
1693
|
-
const byField = {};
|
|
1694
|
-
for (const field of regularDeprecatedFields) {
|
|
1695
|
-
const [type, compName] = field.component.split(':');
|
|
1696
|
-
if (!byField[field.field]) {
|
|
1697
|
-
byField[field.field] = {
|
|
1698
|
-
description: field.description,
|
|
1699
|
-
components: []
|
|
1700
|
-
};
|
|
1701
|
-
}
|
|
1702
|
-
byField[field.field].components.push({ type, name: compName });
|
|
1703
|
-
}
|
|
1704
|
-
|
|
1705
|
-
section += '[cols="1m,3,2a"]\n';
|
|
1706
|
-
section += '|===\n';
|
|
1707
|
-
section += '|Field |Description |Affected components\n\n';
|
|
1708
|
-
|
|
1709
|
-
for (const [fieldName, info] of Object.entries(byField)) {
|
|
1710
|
-
// Format component list - group by type
|
|
1711
|
-
const byType = {};
|
|
1712
|
-
for (const comp of info.components) {
|
|
1713
|
-
if (!byType[comp.type]) byType[comp.type] = [];
|
|
1714
|
-
byType[comp.type].push(comp.name);
|
|
1715
|
-
}
|
|
1716
|
-
|
|
1717
|
-
let componentList = '';
|
|
1718
|
-
for (const [type, names] of Object.entries(byType)) {
|
|
1719
|
-
if (componentList) componentList += '\n\n';
|
|
1720
|
-
|
|
1721
|
-
// Smart pluralization: don't add 's' if already plural
|
|
1722
|
-
const typeLabel = names.length === 1
|
|
1723
|
-
? type.charAt(0).toUpperCase() + type.slice(1)
|
|
1724
|
-
: type.charAt(0).toUpperCase() + type.slice(1) + (type.endsWith('s') ? '' : 's');
|
|
1725
|
-
|
|
1726
|
-
componentList += `*${typeLabel}:*\n\n`;
|
|
1727
|
-
names.forEach(name => {
|
|
1728
|
-
componentList += `* xref:components:${type}/${name}.adoc#${fieldName}[${name}]\n`;
|
|
1729
|
-
});
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
|
-
const desc = info.description ? capToTwoSentences(info.description) : '-';
|
|
1733
|
-
|
|
1734
|
-
section += `|${fieldName}\n`;
|
|
1735
|
-
section += `|${desc}\n`;
|
|
1736
|
-
section += `|${componentList}\n\n`;
|
|
1737
|
-
}
|
|
1738
|
-
|
|
1739
|
-
section += '|===\n\n';
|
|
1740
|
-
}
|
|
1741
|
-
}
|
|
1742
|
-
|
|
1743
|
-
// Changed defaults (exclude Bloblang functions/methods)
|
|
1744
|
-
if (diff.details.changedDefaults && diff.details.changedDefaults.length) {
|
|
1745
|
-
// Filter out Bloblang components
|
|
1746
|
-
const regularChangedDefaults = diff.details.changedDefaults.filter(change => {
|
|
1747
|
-
const [type] = change.component.split(':');
|
|
1748
|
-
return type !== 'bloblang-functions' && type !== 'bloblang-methods';
|
|
1749
|
-
});
|
|
1750
|
-
|
|
1751
|
-
if (regularChangedDefaults.length > 0) {
|
|
1752
|
-
section += '\n=== Default value changes\n\n';
|
|
1753
|
-
section += 'This release includes the following default value changes:\n\n';
|
|
1754
|
-
|
|
1755
|
-
// Group by field name and default values to avoid overwriting different default changes
|
|
1756
|
-
const byFieldAndDefaults = {};
|
|
1757
|
-
for (const change of regularChangedDefaults) {
|
|
1758
|
-
const [type, compName] = change.component.split(':');
|
|
1759
|
-
const compositeKey = `${change.field}|${String(change.oldDefault)}|${String(change.newDefault)}`;
|
|
1760
|
-
if (!byFieldAndDefaults[compositeKey]) {
|
|
1761
|
-
byFieldAndDefaults[compositeKey] = {
|
|
1762
|
-
field: change.field,
|
|
1763
|
-
oldDefault: change.oldDefault,
|
|
1764
|
-
newDefault: change.newDefault,
|
|
1765
|
-
description: change.description,
|
|
1766
|
-
components: []
|
|
1767
|
-
};
|
|
1768
|
-
}
|
|
1769
|
-
byFieldAndDefaults[compositeKey].components.push({
|
|
1770
|
-
type,
|
|
1771
|
-
name: compName
|
|
1772
|
-
});
|
|
1773
|
-
}
|
|
1774
|
-
|
|
1775
|
-
// Create table
|
|
1776
|
-
section += '[cols="1m,1,1,3,2a"]\n';
|
|
1777
|
-
section += '|===\n';
|
|
1778
|
-
section += '|Field |Old default |New default |Description |Affected components\n\n';
|
|
1779
|
-
|
|
1780
|
-
for (const [compositeKey, info] of Object.entries(byFieldAndDefaults)) {
|
|
1781
|
-
// Format old and new defaults
|
|
1782
|
-
const formatDefault = (val) => {
|
|
1783
|
-
if (val === undefined || val === null) return 'none';
|
|
1784
|
-
if (typeof val === 'string') return val;
|
|
1785
|
-
if (typeof val === 'number' || typeof val === 'boolean') return String(val);
|
|
1786
|
-
return JSON.stringify(val);
|
|
1787
|
-
};
|
|
1788
|
-
|
|
1789
|
-
const oldVal = formatDefault(info.oldDefault);
|
|
1790
|
-
const newVal = formatDefault(info.newDefault);
|
|
1791
|
-
|
|
1792
|
-
// Get description
|
|
1793
|
-
const desc = info.description ? capToTwoSentences(info.description) : '-';
|
|
1794
|
-
|
|
1795
|
-
// Format component references - group by type
|
|
1796
|
-
const byType = {};
|
|
1797
|
-
for (const comp of info.components) {
|
|
1798
|
-
if (!byType[comp.type]) byType[comp.type] = [];
|
|
1799
|
-
byType[comp.type].push(comp.name);
|
|
1800
|
-
}
|
|
1801
|
-
|
|
1802
|
-
let componentList = '';
|
|
1803
|
-
for (const [type, names] of Object.entries(byType)) {
|
|
1804
|
-
if (componentList) componentList += '\n\n';
|
|
1805
|
-
|
|
1806
|
-
// Smart pluralization: don't add 's' if already plural
|
|
1807
|
-
const typeLabel = names.length === 1
|
|
1808
|
-
? type.charAt(0).toUpperCase() + type.slice(1)
|
|
1809
|
-
: type.charAt(0).toUpperCase() + type.slice(1) + (type.endsWith('s') ? '' : 's');
|
|
1810
|
-
|
|
1811
|
-
componentList += `*${typeLabel}:*\n\n`;
|
|
1812
|
-
|
|
1813
|
-
// List components, with links to the field anchor
|
|
1814
|
-
names.forEach(name => {
|
|
1815
|
-
componentList += `* xref:components:${type}/${name}.adoc#${info.field}[${name}]\n`;
|
|
1816
|
-
});
|
|
1817
|
-
}
|
|
1818
|
-
|
|
1819
|
-
section += `|${info.field}\n`;
|
|
1820
|
-
section += `|${oldVal}\n`;
|
|
1821
|
-
section += `|${newVal}\n`;
|
|
1822
|
-
section += `|${desc}\n`;
|
|
1823
|
-
section += `|${componentList}\n\n`;
|
|
1824
|
-
}
|
|
1825
|
-
|
|
1826
|
-
section += '|===\n\n';
|
|
1827
|
-
}
|
|
1828
|
-
}
|
|
1829
|
-
|
|
1830
|
-
let updated;
|
|
1831
|
-
if (startIdx !== -1) {
|
|
1832
|
-
// Replace the existing section
|
|
1833
|
-
updated = whatsNew.slice(0, startIdx) + section + '\n' + whatsNew.slice(endIdx);
|
|
1834
|
-
console.log(`♻️ whats-new.adoc: replaced section for Version ${diff.comparison.newVersion}`);
|
|
1835
|
-
} else {
|
|
1836
|
-
// Insert above first version heading
|
|
1837
|
-
const versionHeading = /^== Version /m;
|
|
1838
|
-
const firstMatch = versionHeading.exec(whatsNew);
|
|
1839
|
-
let insertIdx = firstMatch ? firstMatch.index : 0;
|
|
1840
|
-
updated = whatsNew.slice(0, insertIdx) + section + '\n' + whatsNew.slice(insertIdx);
|
|
1841
|
-
console.log(`✅ whats-new.adoc updated with Version ${diff.comparison.newVersion}`);
|
|
1842
|
-
}
|
|
1843
|
-
fs.writeFileSync(whatsNewPath, updated, 'utf8');
|
|
1844
|
-
} catch (err) {
|
|
1845
|
-
console.error(`❌ Failed to update whats-new.adoc: ${err.message}`);
|
|
1846
|
-
}
|
|
1847
|
-
}
|
|
1848
|
-
|
|
1849
|
-
console.log('\n📄 Summary:');
|
|
1850
|
-
console.log(` • Run time: ${timestamp}`);
|
|
1851
|
-
console.log(` • Version used: ${newVersion}`);
|
|
1852
|
-
process.exit(0);
|
|
1853
|
-
});
|
|
803
|
+
const { handleRpcnConnectorDocs } = require('../tools/redpanda-connect/rpcn-connector-docs-handler.js')
|
|
804
|
+
await handleRpcnConnectorDocs(options)
|
|
805
|
+
})
|
|
1854
806
|
|
|
1855
807
|
/**
|
|
1856
808
|
* generate property-docs
|
|
@@ -1878,26 +830,26 @@ automation
|
|
|
1878
830
|
*
|
|
1879
831
|
* # Include Cloud support tags (requires GitHub token)
|
|
1880
832
|
* export GITHUB_TOKEN=ghp_xxx
|
|
1881
|
-
* npx doc-tools generate property-docs
|
|
1882
|
-
* --tag v25.3.1
|
|
1883
|
-
* --generate-partials
|
|
833
|
+
* npx doc-tools generate property-docs \
|
|
834
|
+
* --tag v25.3.1 \
|
|
835
|
+
* --generate-partials \
|
|
1884
836
|
* --cloud-support
|
|
1885
837
|
*
|
|
1886
838
|
* # Compare properties between versions
|
|
1887
|
-
* npx doc-tools generate property-docs
|
|
1888
|
-
* --tag v25.3.1
|
|
839
|
+
* npx doc-tools generate property-docs \
|
|
840
|
+
* --tag v25.3.1 \
|
|
1889
841
|
* --diff v25.2.1
|
|
1890
842
|
*
|
|
1891
843
|
* # Use custom output directory
|
|
1892
|
-
* npx doc-tools generate property-docs
|
|
1893
|
-
* --tag v25.3.1
|
|
844
|
+
* npx doc-tools generate property-docs \
|
|
845
|
+
* --tag v25.3.1 \
|
|
1894
846
|
* --output-dir docs/modules/reference
|
|
1895
847
|
*
|
|
1896
848
|
* # Full workflow: document new release
|
|
1897
849
|
* VERSION=$(npx doc-tools get-redpanda-version)
|
|
1898
|
-
* npx doc-tools generate property-docs
|
|
1899
|
-
* --tag $VERSION
|
|
1900
|
-
* --generate-partials
|
|
850
|
+
* npx doc-tools generate property-docs \
|
|
851
|
+
* --tag $VERSION \
|
|
852
|
+
* --generate-partials \
|
|
1901
853
|
* --cloud-support
|
|
1902
854
|
*
|
|
1903
855
|
* @requirements
|
|
@@ -1911,144 +863,148 @@ automation
|
|
|
1911
863
|
.command('property-docs')
|
|
1912
864
|
.description(
|
|
1913
865
|
'Generate JSON and consolidated AsciiDoc partials for Redpanda configuration properties. ' +
|
|
1914
|
-
'
|
|
1915
|
-
'AsciiDoc partials (including deprecated properties). Defaults to branch "dev" if neither --tag nor --branch is specified.'
|
|
866
|
+
'Defaults to branch "dev" if neither --tag nor --branch is specified.'
|
|
1916
867
|
)
|
|
1917
868
|
.option('-t, --tag <tag>', 'Git tag for released content (GA/beta)')
|
|
1918
869
|
.option('-b, --branch <branch>', 'Branch name for in-progress content')
|
|
1919
870
|
.option('--diff <oldTag>', 'Also diff autogenerated properties from <oldTag> to current tag/branch')
|
|
1920
871
|
.option('--overrides <path>', 'Optional JSON file with property description overrides', 'docs-data/property-overrides.json')
|
|
1921
872
|
.option('--output-dir <dir>', 'Where to write all generated files', 'modules/reference')
|
|
1922
|
-
.option('--cloud-support', 'Add AsciiDoc tags to generated property docs to indicate which ones are supported in Redpanda Cloud. This data is fetched from the cloudv2 repository so requires a GitHub token with repo permissions. Set the token as an environment variable using GITHUB_TOKEN, GH_TOKEN, or REDPANDA_GITHUB_TOKEN')
|
|
873
|
+
.option('--cloud-support', 'Add AsciiDoc tags to generated property docs to indicate which ones are supported in Redpanda Cloud. This data is fetched from the cloudv2 repository so requires a GitHub token with repo permissions. Set the token as an environment variable using GITHUB_TOKEN, GH_TOKEN, or REDPANDA_GITHUB_TOKEN', true)
|
|
1923
874
|
.option('--template-property <path>', 'Custom Handlebars template for individual property sections')
|
|
1924
|
-
.option('--template-topic-property <path>', 'Custom Handlebars template for
|
|
875
|
+
.option('--template-topic-property <path>', 'Custom Handlebars template for topic property sections')
|
|
1925
876
|
.option('--template-topic-property-mappings <path>', 'Custom Handlebars template for topic property mappings table')
|
|
1926
877
|
.option('--template-deprecated <path>', 'Custom Handlebars template for deprecated properties page')
|
|
1927
878
|
.option('--template-deprecated-property <path>', 'Custom Handlebars template for individual deprecated property sections')
|
|
1928
|
-
.option('--generate-partials', 'Generate consolidated property partials
|
|
879
|
+
.option('--generate-partials', 'Generate consolidated property partials')
|
|
1929
880
|
.option('--partials-dir <path>', 'Directory for property partials (relative to output-dir)', 'partials')
|
|
1930
881
|
.action((options) => {
|
|
1931
|
-
verifyPropertyDependencies()
|
|
882
|
+
verifyPropertyDependencies()
|
|
1932
883
|
|
|
1933
|
-
// Validate that tag and branch are mutually exclusive
|
|
1934
884
|
if (options.tag && options.branch) {
|
|
1935
|
-
console.error('
|
|
1936
|
-
process.exit(1)
|
|
885
|
+
console.error('Error: Cannot specify both --tag and --branch')
|
|
886
|
+
process.exit(1)
|
|
1937
887
|
}
|
|
1938
888
|
|
|
1939
|
-
|
|
1940
|
-
const newTag = options.tag || options.branch || 'dev';
|
|
889
|
+
const newTag = options.tag || options.branch || 'dev'
|
|
1941
890
|
|
|
1942
|
-
// Validate cloud support dependencies if requested
|
|
1943
891
|
if (options.cloudSupport) {
|
|
1944
|
-
console.log('
|
|
1945
|
-
const { getGitHubToken } = require('../cli-utils/github-token')
|
|
1946
|
-
const token = getGitHubToken()
|
|
892
|
+
console.log('Validating cloud support dependencies...')
|
|
893
|
+
const { getGitHubToken } = require('../cli-utils/github-token')
|
|
894
|
+
const token = getGitHubToken()
|
|
1947
895
|
if (!token) {
|
|
1948
|
-
console.error('
|
|
1949
|
-
console.error(' Set
|
|
1950
|
-
console.error('
|
|
1951
|
-
|
|
1952
|
-
console.error(' 3. Set: export GITHUB_TOKEN=your_token_here');
|
|
1953
|
-
console.error(' Or: export GH_TOKEN=your_token_here');
|
|
1954
|
-
console.error(' Or: export REDPANDA_GITHUB_TOKEN=your_token_here');
|
|
1955
|
-
process.exit(1);
|
|
896
|
+
console.error('Error: Cloud support requires a GitHub token')
|
|
897
|
+
console.error(' Set: export GITHUB_TOKEN=your_token_here')
|
|
898
|
+
console.error(' Or disable cloud support with: --no-cloud-support')
|
|
899
|
+
process.exit(1)
|
|
1956
900
|
}
|
|
1957
|
-
console.log('
|
|
1958
|
-
if (process.env.VIRTUAL_ENV) {
|
|
1959
|
-
console.log(` Using virtual environment: ${process.env.VIRTUAL_ENV}`);
|
|
1960
|
-
}
|
|
1961
|
-
console.log(' Required packages: pyyaml, requests');
|
|
1962
|
-
console.log('✅ GitHub token validated');
|
|
901
|
+
console.log('Done: GitHub token validated')
|
|
1963
902
|
}
|
|
1964
|
-
let oldTag = options.diff;
|
|
1965
|
-
const overridesPath = options.overrides;
|
|
1966
|
-
const outputDir = options.outputDir;
|
|
1967
|
-
const cwd = path.resolve(__dirname, '../tools/property-extractor');
|
|
1968
903
|
|
|
1969
|
-
|
|
904
|
+
let oldTag = options.diff
|
|
905
|
+
|
|
1970
906
|
if (!oldTag) {
|
|
1971
|
-
oldTag = getAntoraValue('asciidoc.attributes.latest-redpanda-tag')
|
|
907
|
+
oldTag = getAntoraValue('asciidoc.attributes.latest-redpanda-tag')
|
|
1972
908
|
if (oldTag) {
|
|
1973
|
-
console.log(`Using latest-redpanda-tag from Antora attributes for --diff: ${oldTag}`)
|
|
1974
|
-
} else {
|
|
1975
|
-
console.log('No --diff provided and no latest-redpanda-tag found in Antora attributes. Skipping diff.');
|
|
909
|
+
console.log(`Using latest-redpanda-tag from Antora attributes for --diff: ${oldTag}`)
|
|
1976
910
|
}
|
|
1977
911
|
}
|
|
1978
912
|
|
|
913
|
+
const overridesPath = options.overrides
|
|
914
|
+
const outputDir = options.outputDir
|
|
915
|
+
const cwd = path.resolve(__dirname, '../tools/property-extractor')
|
|
916
|
+
|
|
1979
917
|
const make = (tag, overrides, templates = {}, outDir = 'modules/reference/') => {
|
|
1980
|
-
console.log(
|
|
1981
|
-
const args = ['build', `TAG=${tag}`]
|
|
1982
|
-
const env = { ...process.env }
|
|
1983
|
-
if (overrides)
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
if (
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
if (templates.
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
if (templates.topicProperty) {
|
|
1993
|
-
env.TEMPLATE_TOPIC_PROPERTY = path.resolve(templates.topicProperty);
|
|
1994
|
-
}
|
|
1995
|
-
if (templates.topicPropertyMappings) {
|
|
1996
|
-
env.TEMPLATE_TOPIC_PROPERTY_MAPPINGS = path.resolve(templates.topicPropertyMappings);
|
|
1997
|
-
}
|
|
1998
|
-
if (templates.deprecated) {
|
|
1999
|
-
env.TEMPLATE_DEPRECATED = path.resolve(templates.deprecated);
|
|
2000
|
-
}
|
|
2001
|
-
if (templates.deprecatedProperty) {
|
|
2002
|
-
env.TEMPLATE_DEPRECATED_PROPERTY = path.resolve(templates.deprecatedProperty);
|
|
2003
|
-
}
|
|
2004
|
-
env.OUTPUT_JSON_DIR = path.resolve(outDir, 'attachments');
|
|
2005
|
-
env.OUTPUT_AUTOGENERATED_DIR = path.resolve(outDir);
|
|
918
|
+
console.log(`Building property docs for ${tag}…`)
|
|
919
|
+
const args = ['build', `TAG=${tag}`]
|
|
920
|
+
const env = { ...process.env }
|
|
921
|
+
if (overrides) env.OVERRIDES = path.resolve(overrides)
|
|
922
|
+
if (options.cloudSupport) env.CLOUD_SUPPORT = '1'
|
|
923
|
+
if (templates.property) env.TEMPLATE_PROPERTY = path.resolve(templates.property)
|
|
924
|
+
if (templates.topicProperty) env.TEMPLATE_TOPIC_PROPERTY = path.resolve(templates.topicProperty)
|
|
925
|
+
if (templates.topicPropertyMappings) env.TEMPLATE_TOPIC_PROPERTY_MAPPINGS = path.resolve(templates.topicPropertyMappings)
|
|
926
|
+
if (templates.deprecated) env.TEMPLATE_DEPRECATED = path.resolve(templates.deprecated)
|
|
927
|
+
if (templates.deprecatedProperty) env.TEMPLATE_DEPRECATED_PROPERTY = path.resolve(templates.deprecatedProperty)
|
|
928
|
+
env.OUTPUT_JSON_DIR = path.resolve(outDir, 'attachments')
|
|
929
|
+
env.OUTPUT_AUTOGENERATED_DIR = path.resolve(outDir)
|
|
2006
930
|
if (options.generatePartials) {
|
|
2007
|
-
env.GENERATE_PARTIALS = '1'
|
|
2008
|
-
env.OUTPUT_PARTIALS_DIR = path.resolve(outDir, options.partialsDir || 'partials')
|
|
931
|
+
env.GENERATE_PARTIALS = '1'
|
|
932
|
+
env.OUTPUT_PARTIALS_DIR = path.resolve(outDir, options.partialsDir || 'partials')
|
|
2009
933
|
}
|
|
2010
|
-
const r = spawnSync('make', args, { cwd, stdio: 'inherit', env })
|
|
934
|
+
const r = spawnSync('make', args, { cwd, stdio: 'inherit', env })
|
|
2011
935
|
if (r.error) {
|
|
2012
|
-
console.error(
|
|
2013
|
-
process.exit(1)
|
|
936
|
+
console.error(`Error: ${r.error.message}`)
|
|
937
|
+
process.exit(1)
|
|
2014
938
|
}
|
|
2015
|
-
if (r.status !== 0) process.exit(r.status)
|
|
2016
|
-
}
|
|
939
|
+
if (r.status !== 0) process.exit(r.status)
|
|
940
|
+
}
|
|
2017
941
|
|
|
2018
942
|
const templates = {
|
|
2019
943
|
property: options.templateProperty,
|
|
2020
944
|
topicProperty: options.templateTopicProperty,
|
|
2021
945
|
topicPropertyMappings: options.templateTopicPropertyMappings,
|
|
2022
946
|
deprecated: options.templateDeprecated,
|
|
2023
|
-
deprecatedProperty: options.templateDeprecatedProperty
|
|
2024
|
-
}
|
|
947
|
+
deprecatedProperty: options.templateDeprecatedProperty
|
|
948
|
+
}
|
|
2025
949
|
|
|
2026
|
-
const tagsAreSame = oldTag && newTag && oldTag === newTag
|
|
950
|
+
const tagsAreSame = oldTag && newTag && oldTag === newTag
|
|
2027
951
|
if (oldTag && !tagsAreSame) {
|
|
2028
|
-
make(oldTag, overridesPath, templates, outputDir)
|
|
952
|
+
make(oldTag, overridesPath, templates, outputDir)
|
|
2029
953
|
}
|
|
2030
|
-
make(newTag, overridesPath, templates, outputDir)
|
|
954
|
+
make(newTag, overridesPath, templates, outputDir)
|
|
2031
955
|
if (oldTag && !tagsAreSame) {
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
956
|
+
const diffOutputDir = overridesPath ? path.dirname(path.resolve(overridesPath)) : outputDir
|
|
957
|
+
generatePropertyComparisonReport(oldTag, newTag, diffOutputDir)
|
|
958
|
+
|
|
959
|
+
try {
|
|
960
|
+
const diffReportPath = path.join(diffOutputDir, `redpanda-property-changes-${oldTag}-to-${newTag}.json`)
|
|
961
|
+
if (fs.existsSync(diffReportPath)) {
|
|
962
|
+
const diffData = JSON.parse(fs.readFileSync(diffReportPath, 'utf8'))
|
|
963
|
+
const { printPRSummary } = require('../tools/property-extractor/pr-summary-formatter')
|
|
964
|
+
printPRSummary(diffData)
|
|
965
|
+
|
|
966
|
+
if (overridesPath && fs.existsSync(overridesPath)) {
|
|
967
|
+
updatePropertyOverridesWithVersion(overridesPath, diffData, newTag)
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
} catch (err) {
|
|
971
|
+
console.warn(`Warning: Failed to generate PR summary: ${err.message}`)
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
cleanupOldDiffs(diffOutputDir)
|
|
2040
975
|
}
|
|
2041
976
|
|
|
2042
|
-
// If we used Antora's latest-redpanda-tag for diff, update it to the new tag
|
|
2043
977
|
if (!options.diff && !tagsAreSame) {
|
|
2044
|
-
const
|
|
2045
|
-
if (
|
|
2046
|
-
|
|
978
|
+
const tagSuccess = setAntoraValue('asciidoc.attributes.latest-redpanda-tag', newTag)
|
|
979
|
+
if (tagSuccess) console.log(`Done: Updated Antora latest-redpanda-tag to: ${newTag}`)
|
|
980
|
+
|
|
981
|
+
const versionWithoutV = newTag.startsWith('v') ? newTag.slice(1) : newTag
|
|
982
|
+
const versionSuccess = setAntoraValue('asciidoc.attributes.full-version', versionWithoutV)
|
|
983
|
+
if (versionSuccess) console.log(`Done: Updated Antora full-version to: ${versionWithoutV}`)
|
|
984
|
+
|
|
985
|
+
try {
|
|
986
|
+
const jsonDir = path.resolve(outputDir, 'attachments')
|
|
987
|
+
const propertyFiles = fs.readdirSync(jsonDir)
|
|
988
|
+
.filter(f => /^redpanda-properties-v[\d.]+\.json$/.test(f))
|
|
989
|
+
.sort()
|
|
990
|
+
|
|
991
|
+
const keepFile = `redpanda-properties-${newTag}.json`
|
|
992
|
+
const filesToDelete = propertyFiles.filter(f => f !== keepFile)
|
|
993
|
+
|
|
994
|
+
if (filesToDelete.length > 0) {
|
|
995
|
+
console.log('🧹 Cleaning up old property JSON files...')
|
|
996
|
+
filesToDelete.forEach(file => {
|
|
997
|
+
fs.unlinkSync(path.join(jsonDir, file))
|
|
998
|
+
console.log(` Deleted: ${file}`)
|
|
999
|
+
})
|
|
1000
|
+
}
|
|
1001
|
+
} catch (err) {
|
|
1002
|
+
console.warn(`Warning: Failed to cleanup old property JSON files: ${err.message}`)
|
|
2047
1003
|
}
|
|
2048
1004
|
}
|
|
2049
1005
|
|
|
2050
|
-
process.exit(0)
|
|
2051
|
-
})
|
|
1006
|
+
process.exit(0)
|
|
1007
|
+
})
|
|
2052
1008
|
|
|
2053
1009
|
/**
|
|
2054
1010
|
* generate rpk-docs
|
|
@@ -2072,13 +1028,13 @@ automation
|
|
|
2072
1028
|
* npx doc-tools generate rpk-docs --tag v25.3.1
|
|
2073
1029
|
*
|
|
2074
1030
|
* # Compare RPK commands between versions
|
|
2075
|
-
* npx doc-tools generate rpk-docs
|
|
2076
|
-
* --tag v25.3.1
|
|
1031
|
+
* npx doc-tools generate rpk-docs \
|
|
1032
|
+
* --tag v25.3.1 \
|
|
2077
1033
|
* --diff v25.2.1
|
|
2078
1034
|
*
|
|
2079
1035
|
* # Use custom Docker repository
|
|
2080
|
-
* npx doc-tools generate rpk-docs
|
|
2081
|
-
* --tag v25.3.1
|
|
1036
|
+
* npx doc-tools generate rpk-docs \
|
|
1037
|
+
* --tag v25.3.1 \
|
|
2082
1038
|
* --docker-repo docker.redpanda.com/redpandadata/redpanda
|
|
2083
1039
|
*
|
|
2084
1040
|
* # Full workflow: document new release
|
|
@@ -2095,52 +1051,38 @@ automation
|
|
|
2095
1051
|
.description('Generate AsciiDoc documentation for rpk CLI commands. Defaults to branch "dev" if neither --tag nor --branch is specified.')
|
|
2096
1052
|
.option('-t, --tag <tag>', 'Git tag for released content (GA/beta)')
|
|
2097
1053
|
.option('-b, --branch <branch>', 'Branch name for in-progress content')
|
|
2098
|
-
.option(
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
commonOptions.dockerRepo
|
|
2102
|
-
)
|
|
2103
|
-
.option(
|
|
2104
|
-
'--console-tag <tag>',
|
|
2105
|
-
'Redpanda Console version to use when starting Redpanda Console in Docker',
|
|
2106
|
-
commonOptions.consoleTag
|
|
2107
|
-
)
|
|
2108
|
-
.option(
|
|
2109
|
-
'--console-docker-repo <repo>',
|
|
2110
|
-
'Docker repository to use when starting Redpanda Console in Docker',
|
|
2111
|
-
commonOptions.consoleDockerRepo
|
|
2112
|
-
)
|
|
1054
|
+
.option('--docker-repo <repo>', 'Docker repository to use', commonOptions.dockerRepo)
|
|
1055
|
+
.option('--console-tag <tag>', 'Redpanda Console version to use', commonOptions.consoleTag)
|
|
1056
|
+
.option('--console-docker-repo <repo>', 'Docker repository for Console', commonOptions.consoleDockerRepo)
|
|
2113
1057
|
.option('--diff <oldTag>', 'Also diff autogenerated rpk docs from <oldTag> → <tag>')
|
|
2114
1058
|
.action((options) => {
|
|
2115
|
-
verifyMetricsDependencies()
|
|
1059
|
+
verifyMetricsDependencies()
|
|
2116
1060
|
|
|
2117
|
-
// Validate that tag and branch are mutually exclusive
|
|
2118
1061
|
if (options.tag && options.branch) {
|
|
2119
|
-
console.error('
|
|
2120
|
-
process.exit(1)
|
|
1062
|
+
console.error('Error: Cannot specify both --tag and --branch')
|
|
1063
|
+
process.exit(1)
|
|
2121
1064
|
}
|
|
2122
1065
|
|
|
2123
|
-
|
|
2124
|
-
const
|
|
2125
|
-
const oldTag = options.diff;
|
|
1066
|
+
const newTag = options.tag || options.branch || 'dev'
|
|
1067
|
+
const oldTag = options.diff
|
|
2126
1068
|
|
|
2127
1069
|
if (oldTag) {
|
|
2128
|
-
const oldDir = path.join('autogenerated', oldTag, 'rpk')
|
|
1070
|
+
const oldDir = path.join('autogenerated', oldTag, 'rpk')
|
|
2129
1071
|
if (!fs.existsSync(oldDir)) {
|
|
2130
|
-
console.log(
|
|
2131
|
-
runClusterDocs('rpk', oldTag, options)
|
|
1072
|
+
console.log(`Generating rpk docs for old tag ${oldTag}…`)
|
|
1073
|
+
runClusterDocs('rpk', oldTag, options)
|
|
2132
1074
|
}
|
|
2133
1075
|
}
|
|
2134
1076
|
|
|
2135
|
-
console.log(
|
|
2136
|
-
runClusterDocs('rpk', newTag, options)
|
|
1077
|
+
console.log(`Generating rpk docs for new tag ${newTag}…`)
|
|
1078
|
+
runClusterDocs('rpk', newTag, options)
|
|
2137
1079
|
|
|
2138
1080
|
if (oldTag) {
|
|
2139
|
-
diffDirs('rpk', oldTag, newTag)
|
|
1081
|
+
diffDirs('rpk', oldTag, newTag)
|
|
2140
1082
|
}
|
|
2141
1083
|
|
|
2142
|
-
process.exit(0)
|
|
2143
|
-
})
|
|
1084
|
+
process.exit(0)
|
|
1085
|
+
})
|
|
2144
1086
|
|
|
2145
1087
|
/**
|
|
2146
1088
|
* generate helm-spec
|
|
@@ -2161,170 +1103,148 @@ automation
|
|
|
2161
1103
|
*
|
|
2162
1104
|
* @example
|
|
2163
1105
|
* # Generate docs from GitHub repository
|
|
2164
|
-
* npx doc-tools generate helm-spec
|
|
2165
|
-
* --chart-dir https://github.com/redpanda-data/helm-charts
|
|
2166
|
-
* --tag v5.9.0
|
|
1106
|
+
* npx doc-tools generate helm-spec \
|
|
1107
|
+
* --chart-dir https://github.com/redpanda-data/helm-charts \
|
|
1108
|
+
* --tag v5.9.0 \
|
|
2167
1109
|
* --output-dir modules/deploy/pages
|
|
2168
1110
|
*
|
|
2169
1111
|
* # Generate docs from local chart directory
|
|
2170
|
-
* npx doc-tools generate helm-spec
|
|
2171
|
-
* --chart-dir ./charts/redpanda
|
|
1112
|
+
* npx doc-tools generate helm-spec \
|
|
1113
|
+
* --chart-dir ./charts/redpanda \
|
|
2172
1114
|
* --output-dir docs/modules/deploy/pages
|
|
2173
1115
|
*
|
|
2174
1116
|
* # Use custom README and output suffix
|
|
2175
|
-
* npx doc-tools generate helm-spec
|
|
2176
|
-
* --chart-dir https://github.com/redpanda-data/helm-charts
|
|
2177
|
-
* --tag v5.9.0
|
|
2178
|
-
* --readme docs/README.md
|
|
1117
|
+
* npx doc-tools generate helm-spec \
|
|
1118
|
+
* --chart-dir https://github.com/redpanda-data/helm-charts \
|
|
1119
|
+
* --tag v5.9.0 \
|
|
1120
|
+
* --readme docs/README.md \
|
|
2179
1121
|
* --output-suffix -values.adoc
|
|
2180
1122
|
*
|
|
2181
1123
|
* @requirements
|
|
2182
1124
|
* - For GitHub URLs: Git and internet connection
|
|
2183
1125
|
* - For local charts: Chart directory must contain Chart.yaml
|
|
2184
1126
|
* - README.md file in chart directory (optional but recommended)
|
|
1127
|
+
* - helm-docs and pandoc must be installed
|
|
2185
1128
|
*/
|
|
2186
1129
|
automation
|
|
2187
1130
|
.command('helm-spec')
|
|
2188
|
-
.description(
|
|
2189
|
-
|
|
2190
|
-
)
|
|
2191
|
-
.option(
|
|
2192
|
-
'--chart-dir <dir|url>',
|
|
2193
|
-
'Chart directory (contains Chart.yaml) or a root containing multiple charts, or a GitHub URL',
|
|
2194
|
-
'https://github.com/redpanda-data/redpanda-operator/charts'
|
|
2195
|
-
)
|
|
2196
|
-
.option('-t, --tag <tag>', 'Git tag for released content when using GitHub URL (auto-prepends "operator/" for redpanda-operator repository)')
|
|
2197
|
-
.option('-b, --branch <branch>', 'Branch name for in-progress content when using GitHub URL')
|
|
1131
|
+
.description('Generate AsciiDoc documentation for Helm charts. Requires either --tag or --branch for GitHub URLs.')
|
|
1132
|
+
.option('--chart-dir <dir|url>', 'Chart directory or GitHub URL', 'https://github.com/redpanda-data/redpanda-operator/charts')
|
|
1133
|
+
.option('-t, --tag <tag>', 'Git tag for released content')
|
|
1134
|
+
.option('-b, --branch <branch>', 'Branch name for in-progress content')
|
|
2198
1135
|
.option('--readme <file>', 'Relative README.md path inside each chart dir', 'README.md')
|
|
2199
|
-
.option('--output-dir <dir>', 'Where to write
|
|
2200
|
-
.option('--output-suffix <suffix>', 'Suffix to append to each chart name
|
|
1136
|
+
.option('--output-dir <dir>', 'Where to write generated AsciiDoc files', 'modules/reference/pages')
|
|
1137
|
+
.option('--output-suffix <suffix>', 'Suffix to append to each chart name', '-helm-spec.adoc')
|
|
2201
1138
|
.action((opts) => {
|
|
2202
|
-
verifyHelmDependencies()
|
|
1139
|
+
verifyHelmDependencies()
|
|
2203
1140
|
|
|
2204
|
-
|
|
2205
|
-
let
|
|
2206
|
-
let tmpClone = null;
|
|
1141
|
+
let root = opts.chartDir
|
|
1142
|
+
let tmpClone = null
|
|
2207
1143
|
|
|
2208
1144
|
if (/^https?:\/\/github\.com\//.test(root)) {
|
|
2209
|
-
// Validate tag/branch for GitHub URLs
|
|
2210
1145
|
if (!opts.tag && !opts.branch) {
|
|
2211
|
-
console.error('
|
|
2212
|
-
process.exit(1)
|
|
1146
|
+
console.error('Error: When using a GitHub URL you must pass either --tag or --branch')
|
|
1147
|
+
process.exit(1)
|
|
2213
1148
|
}
|
|
2214
1149
|
if (opts.tag && opts.branch) {
|
|
2215
|
-
console.error('
|
|
2216
|
-
process.exit(1)
|
|
1150
|
+
console.error('Error: Cannot specify both --tag and --branch')
|
|
1151
|
+
process.exit(1)
|
|
2217
1152
|
}
|
|
2218
1153
|
|
|
2219
|
-
let gitRef = opts.tag || opts.branch
|
|
1154
|
+
let gitRef = opts.tag || opts.branch
|
|
2220
1155
|
|
|
2221
|
-
// Normalize tag: add 'v' prefix if not present for tags
|
|
2222
1156
|
if (opts.tag && !gitRef.startsWith('v')) {
|
|
2223
|
-
gitRef = `v${gitRef}
|
|
2224
|
-
console.log(`ℹ️ Auto-prepending "v" to tag: ${gitRef}`)
|
|
1157
|
+
gitRef = `v${gitRef}`
|
|
1158
|
+
console.log(`ℹ️ Auto-prepending "v" to tag: ${gitRef}`)
|
|
2225
1159
|
}
|
|
2226
1160
|
|
|
2227
|
-
const u = new URL(root)
|
|
2228
|
-
const parts = u.pathname.replace(/\.git$/, '').split('/').filter(Boolean)
|
|
1161
|
+
const u = new URL(root)
|
|
1162
|
+
const parts = u.pathname.replace(/\.git$/, '').split('/').filter(Boolean)
|
|
2229
1163
|
if (parts.length < 2) {
|
|
2230
|
-
console.error(
|
|
2231
|
-
process.exit(1)
|
|
1164
|
+
console.error(`Error: Invalid GitHub URL: ${root}`)
|
|
1165
|
+
process.exit(1)
|
|
2232
1166
|
}
|
|
2233
|
-
const [owner, repo, ...sub] = parts
|
|
2234
|
-
const repoUrl = `https://${u.host}/${owner}/${repo}.git
|
|
1167
|
+
const [owner, repo, ...sub] = parts
|
|
1168
|
+
const repoUrl = `https://${u.host}/${owner}/${repo}.git`
|
|
2235
1169
|
|
|
2236
|
-
// Auto-prepend "operator/" for tags in redpanda-operator repository
|
|
2237
1170
|
if (opts.tag && owner === 'redpanda-data' && repo === 'redpanda-operator') {
|
|
2238
1171
|
if (!gitRef.startsWith('operator/')) {
|
|
2239
|
-
gitRef = `operator/${gitRef}
|
|
2240
|
-
console.log(`ℹ️ Auto-prepending "operator/" to tag: ${gitRef}`)
|
|
1172
|
+
gitRef = `operator/${gitRef}`
|
|
1173
|
+
console.log(`ℹ️ Auto-prepending "operator/" to tag: ${gitRef}`)
|
|
2241
1174
|
}
|
|
2242
1175
|
}
|
|
2243
1176
|
|
|
2244
|
-
console.log(
|
|
2245
|
-
const ok =
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
).status === 0;
|
|
1177
|
+
console.log(`Verifying ${repoUrl}@${gitRef}…`)
|
|
1178
|
+
const ok = spawnSync(
|
|
1179
|
+
'git',
|
|
1180
|
+
['ls-remote', '--exit-code', repoUrl, `refs/heads/${gitRef}`, `refs/tags/${gitRef}`],
|
|
1181
|
+
{ stdio: 'ignore' }
|
|
1182
|
+
).status === 0
|
|
2251
1183
|
if (!ok) {
|
|
2252
|
-
console.error(
|
|
2253
|
-
process.exit(1)
|
|
1184
|
+
console.error(`Error: ${gitRef} not found on ${repoUrl}`)
|
|
1185
|
+
process.exit(1)
|
|
2254
1186
|
}
|
|
2255
1187
|
|
|
2256
|
-
const { getAuthenticatedGitHubUrl, hasGitHubToken } = require('../cli-utils/github-token')
|
|
1188
|
+
const { getAuthenticatedGitHubUrl, hasGitHubToken } = require('../cli-utils/github-token')
|
|
2257
1189
|
|
|
2258
|
-
tmpClone = fs.mkdtempSync(path.join(os.tmpdir(), 'helm-'))
|
|
1190
|
+
tmpClone = fs.mkdtempSync(path.join(os.tmpdir(), 'helm-'))
|
|
2259
1191
|
|
|
2260
|
-
|
|
2261
|
-
let cloneUrl = repoUrl;
|
|
1192
|
+
let cloneUrl = repoUrl
|
|
2262
1193
|
if (hasGitHubToken() && repoUrl.includes('github.com')) {
|
|
2263
|
-
cloneUrl = getAuthenticatedGitHubUrl(repoUrl)
|
|
2264
|
-
console.log(
|
|
1194
|
+
cloneUrl = getAuthenticatedGitHubUrl(repoUrl)
|
|
1195
|
+
console.log(`Cloning ${repoUrl}@${gitRef} → ${tmpClone} (authenticated)`)
|
|
2265
1196
|
} else {
|
|
2266
|
-
console.log(
|
|
1197
|
+
console.log(`Cloning ${repoUrl}@${gitRef} → ${tmpClone}`)
|
|
2267
1198
|
}
|
|
2268
1199
|
|
|
2269
|
-
if (
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
}).status !== 0
|
|
2273
|
-
) {
|
|
2274
|
-
console.error('❌ git clone failed');
|
|
2275
|
-
process.exit(1);
|
|
1200
|
+
if (spawnSync('git', ['clone', '--depth', '1', '--branch', gitRef, cloneUrl, tmpClone], { stdio: 'inherit' }).status !== 0) {
|
|
1201
|
+
console.error('Error: git clone failed')
|
|
1202
|
+
process.exit(1)
|
|
2276
1203
|
}
|
|
2277
|
-
root = sub.length ? path.join(tmpClone, sub.join('/')) : tmpClone
|
|
1204
|
+
root = sub.length ? path.join(tmpClone, sub.join('/')) : tmpClone
|
|
2278
1205
|
}
|
|
2279
1206
|
|
|
2280
|
-
// Discover charts
|
|
2281
1207
|
if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) {
|
|
2282
|
-
console.error(
|
|
2283
|
-
process.exit(1)
|
|
1208
|
+
console.error(`Error: Chart root not found: ${root}`)
|
|
1209
|
+
process.exit(1)
|
|
2284
1210
|
}
|
|
2285
|
-
let charts = []
|
|
1211
|
+
let charts = []
|
|
2286
1212
|
if (fs.existsSync(path.join(root, 'Chart.yaml'))) {
|
|
2287
|
-
charts = [root]
|
|
1213
|
+
charts = [root]
|
|
2288
1214
|
} else {
|
|
2289
|
-
charts = fs
|
|
2290
|
-
.readdirSync(root)
|
|
1215
|
+
charts = fs.readdirSync(root)
|
|
2291
1216
|
.map((n) => path.join(root, n))
|
|
2292
|
-
.filter((p) => fs.existsSync(path.join(p, 'Chart.yaml')))
|
|
1217
|
+
.filter((p) => fs.existsSync(path.join(p, 'Chart.yaml')))
|
|
2293
1218
|
}
|
|
2294
1219
|
if (charts.length === 0) {
|
|
2295
|
-
console.error(
|
|
2296
|
-
process.exit(1)
|
|
1220
|
+
console.error(`Error: No charts found under: ${root}`)
|
|
1221
|
+
process.exit(1)
|
|
2297
1222
|
}
|
|
2298
1223
|
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
fs.mkdirSync(outDir, { recursive: true });
|
|
1224
|
+
const outDir = path.resolve(opts.outputDir)
|
|
1225
|
+
fs.mkdirSync(outDir, { recursive: true })
|
|
2302
1226
|
|
|
2303
|
-
// Process each chart
|
|
2304
1227
|
for (const chartPath of charts) {
|
|
2305
|
-
const name = path.basename(chartPath)
|
|
2306
|
-
console.log(
|
|
1228
|
+
const name = path.basename(chartPath)
|
|
1229
|
+
console.log(`Processing chart "${name}"…`)
|
|
2307
1230
|
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
if (r.status !== 0) process.exit(r.status);
|
|
1231
|
+
console.log(`helm-docs in ${chartPath}`)
|
|
1232
|
+
let r = spawnSync('helm-docs', { cwd: chartPath, stdio: 'inherit' })
|
|
1233
|
+
if (r.status !== 0) process.exit(r.status)
|
|
2312
1234
|
|
|
2313
|
-
|
|
2314
|
-
const md = path.join(chartPath, opts.readme);
|
|
1235
|
+
const md = path.join(chartPath, opts.readme)
|
|
2315
1236
|
if (!fs.existsSync(md)) {
|
|
2316
|
-
console.error(
|
|
2317
|
-
process.exit(1)
|
|
1237
|
+
console.error(`Error: README not found: ${md}`)
|
|
1238
|
+
process.exit(1)
|
|
2318
1239
|
}
|
|
2319
|
-
const outFile = path.join(outDir, `k-${name}${opts.outputSuffix}`)
|
|
2320
|
-
console.log(
|
|
2321
|
-
fs.mkdirSync(path.dirname(outFile), { recursive: true })
|
|
2322
|
-
r = spawnSync('pandoc', [md, '-t', 'asciidoc', '-o', outFile], { stdio: 'inherit' })
|
|
2323
|
-
if (r.status !== 0) process.exit(r.status)
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
const xrefRe = /https:\/\/docs\.redpanda\.com[^\s\]\[\)"]+(?:\[[^\]]*\])?/g;
|
|
1240
|
+
const outFile = path.join(outDir, `k-${name}${opts.outputSuffix}`)
|
|
1241
|
+
console.log(`pandoc ${md} → ${outFile}`)
|
|
1242
|
+
fs.mkdirSync(path.dirname(outFile), { recursive: true })
|
|
1243
|
+
r = spawnSync('pandoc', [md, '-t', 'asciidoc', '-o', outFile], { stdio: 'inherit' })
|
|
1244
|
+
if (r.status !== 0) process.exit(r.status)
|
|
1245
|
+
|
|
1246
|
+
let doc = fs.readFileSync(outFile, 'utf8')
|
|
1247
|
+
const xrefRe = /https:\/\/docs\.redpanda\.com[^\s\]\[\)"]+(?:\[[^\]]*\])?/g
|
|
2328
1248
|
doc = doc
|
|
2329
1249
|
.replace(/(\[\d+\])\]\./g, '$1\\].')
|
|
2330
1250
|
.replace(/(\[\d+\])\]\]/g, '$1\\]\\]')
|
|
@@ -2332,36 +1252,30 @@ automation
|
|
|
2332
1252
|
.replace(/^== # (.*)$/gm, '= $1')
|
|
2333
1253
|
.replace(/^== description: (.*)$/gm, ':description: $1')
|
|
2334
1254
|
.replace(xrefRe, (match) => {
|
|
2335
|
-
let urlPart = match
|
|
2336
|
-
let bracketPart = ''
|
|
2337
|
-
const m = match.match(/^([^\[]+)(\[[^\]]*\])$/)
|
|
1255
|
+
let urlPart = match
|
|
1256
|
+
let bracketPart = ''
|
|
1257
|
+
const m = match.match(/^([^\[]+)(\[[^\]]*\])$/)
|
|
2338
1258
|
if (m) {
|
|
2339
|
-
urlPart = m[1]
|
|
2340
|
-
bracketPart = m[2]
|
|
2341
|
-
}
|
|
2342
|
-
if (urlPart.endsWith('#')) {
|
|
2343
|
-
return match;
|
|
1259
|
+
urlPart = m[1]
|
|
1260
|
+
bracketPart = m[2]
|
|
2344
1261
|
}
|
|
1262
|
+
if (urlPart.endsWith('#')) return match
|
|
2345
1263
|
try {
|
|
2346
|
-
const xref = urlToXref(urlPart)
|
|
2347
|
-
return bracketPart ? `${xref}${bracketPart}` : `${xref}[]
|
|
1264
|
+
const xref = urlToXref(urlPart)
|
|
1265
|
+
return bracketPart ? `${xref}${bracketPart}` : `${xref}[]`
|
|
2348
1266
|
} catch (err) {
|
|
2349
|
-
console.warn(`⚠️ urlToXref failed on ${urlPart}: ${err.message}`)
|
|
2350
|
-
return match
|
|
1267
|
+
console.warn(`⚠️ urlToXref failed on ${urlPart}: ${err.message}`)
|
|
1268
|
+
return match
|
|
2351
1269
|
}
|
|
2352
|
-
})
|
|
2353
|
-
fs.writeFileSync(outFile, doc, 'utf8')
|
|
1270
|
+
})
|
|
1271
|
+
fs.writeFileSync(outFile, doc, 'utf8')
|
|
2354
1272
|
|
|
2355
|
-
console.log(
|
|
1273
|
+
console.log(`Done: Wrote ${outFile}`)
|
|
2356
1274
|
}
|
|
2357
1275
|
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
});
|
|
1276
|
+
if (tmpClone) fs.rmSync(tmpClone, { recursive: true, force: true })
|
|
1277
|
+
})
|
|
2361
1278
|
|
|
2362
|
-
/**
|
|
2363
|
-
* Generate Markdown table of cloud regions and tiers from master-data.yaml
|
|
2364
|
-
*/
|
|
2365
1279
|
/**
|
|
2366
1280
|
* generate cloud-regions
|
|
2367
1281
|
*
|
|
@@ -2394,7 +1308,7 @@ automation
|
|
|
2394
1308
|
*
|
|
2395
1309
|
* # Use custom output file
|
|
2396
1310
|
* export GITHUB_TOKEN=ghp_xxx
|
|
2397
|
-
* npx doc-tools generate cloud-regions
|
|
1311
|
+
* npx doc-tools generate cloud-regions \
|
|
2398
1312
|
* --output custom/path/regions.md
|
|
2399
1313
|
*
|
|
2400
1314
|
* # Use different branch for testing
|
|
@@ -2418,21 +1332,21 @@ automation
|
|
|
2418
1332
|
.option('--template <path>', 'Path to custom Handlebars template (relative to repo root)')
|
|
2419
1333
|
.option('--dry-run', 'Print output to stdout instead of writing file')
|
|
2420
1334
|
.action(async (options) => {
|
|
2421
|
-
const { generateCloudRegions } = require('../tools/cloud-regions/generate-cloud-regions.js')
|
|
2422
|
-
const { getGitHubToken } = require('../cli-utils/github-token')
|
|
1335
|
+
const { generateCloudRegions } = require('../tools/cloud-regions/generate-cloud-regions.js')
|
|
1336
|
+
const { getGitHubToken } = require('../cli-utils/github-token')
|
|
2423
1337
|
|
|
2424
1338
|
try {
|
|
2425
|
-
const token = getGitHubToken()
|
|
1339
|
+
const token = getGitHubToken()
|
|
2426
1340
|
if (!token) {
|
|
2427
|
-
throw new Error('GitHub token is required to fetch from private cloudv2-infra repo.
|
|
1341
|
+
throw new Error('GitHub token is required to fetch from private cloudv2-infra repo.')
|
|
2428
1342
|
}
|
|
2429
|
-
const fmt = (options.format || 'md').toLowerCase()
|
|
2430
|
-
let templatePath
|
|
1343
|
+
const fmt = (options.format || 'md').toLowerCase()
|
|
1344
|
+
let templatePath
|
|
2431
1345
|
if (options.template) {
|
|
2432
|
-
const repoRoot = findRepoRoot()
|
|
2433
|
-
templatePath = path.resolve(repoRoot, options.template)
|
|
1346
|
+
const repoRoot = findRepoRoot()
|
|
1347
|
+
templatePath = path.resolve(repoRoot, options.template)
|
|
2434
1348
|
if (!fs.existsSync(templatePath)) {
|
|
2435
|
-
throw new Error(`Custom template not found: ${templatePath}`)
|
|
1349
|
+
throw new Error(`Custom template not found: ${templatePath}`)
|
|
2436
1350
|
}
|
|
2437
1351
|
}
|
|
2438
1352
|
const out = await generateCloudRegions({
|
|
@@ -2442,24 +1356,23 @@ automation
|
|
|
2442
1356
|
ref: options.ref,
|
|
2443
1357
|
format: fmt,
|
|
2444
1358
|
token,
|
|
2445
|
-
template: templatePath
|
|
2446
|
-
})
|
|
1359
|
+
template: templatePath
|
|
1360
|
+
})
|
|
2447
1361
|
if (options.dryRun) {
|
|
2448
|
-
process.stdout.write(out)
|
|
2449
|
-
console.log(`\
|
|
1362
|
+
process.stdout.write(out)
|
|
1363
|
+
console.log(`\nDone: (dry-run) ${fmt === 'adoc' ? 'AsciiDoc' : 'Markdown'} output printed to stdout.`)
|
|
2450
1364
|
} else {
|
|
2451
|
-
|
|
2452
|
-
const
|
|
2453
|
-
|
|
2454
|
-
fs.
|
|
2455
|
-
|
|
2456
|
-
console.log(`✅ Wrote ${absOutput}`);
|
|
1365
|
+
const repoRoot = findRepoRoot()
|
|
1366
|
+
const absOutput = path.resolve(repoRoot, options.output)
|
|
1367
|
+
fs.mkdirSync(path.dirname(absOutput), { recursive: true })
|
|
1368
|
+
fs.writeFileSync(absOutput, out, 'utf8')
|
|
1369
|
+
console.log(`Done: Wrote ${absOutput}`)
|
|
2457
1370
|
}
|
|
2458
1371
|
} catch (err) {
|
|
2459
|
-
console.error(
|
|
2460
|
-
process.exit(1)
|
|
1372
|
+
console.error(`Error: Failed to generate cloud regions: ${err.message}`)
|
|
1373
|
+
process.exit(1)
|
|
2461
1374
|
}
|
|
2462
|
-
})
|
|
1375
|
+
})
|
|
2463
1376
|
|
|
2464
1377
|
/**
|
|
2465
1378
|
* generate crd-spec
|
|
@@ -2500,9 +1413,9 @@ automation
|
|
|
2500
1413
|
* npx doc-tools generate crd-spec --branch dev
|
|
2501
1414
|
*
|
|
2502
1415
|
* # Use custom templates and output location
|
|
2503
|
-
* npx doc-tools generate crd-spec
|
|
2504
|
-
* --tag operator/v2.2.6-25.3.1
|
|
2505
|
-
* --templates-dir custom/templates
|
|
1416
|
+
* npx doc-tools generate crd-spec \
|
|
1417
|
+
* --tag operator/v2.2.6-25.3.1 \
|
|
1418
|
+
* --templates-dir custom/templates \
|
|
2506
1419
|
* --output modules/reference/pages/operator-crd.adoc
|
|
2507
1420
|
*
|
|
2508
1421
|
* @requirements
|
|
@@ -2512,42 +1425,29 @@ automation
|
|
|
2512
1425
|
*/
|
|
2513
1426
|
automation
|
|
2514
1427
|
.command('crd-spec')
|
|
2515
|
-
.description('Generate Asciidoc documentation for Kubernetes CRD references. Requires either --tag or --branch
|
|
2516
|
-
.option('-t, --tag <operatorTag>', 'Operator release tag for GA/beta content
|
|
2517
|
-
.option('-b, --branch <branch>', 'Branch name for in-progress content
|
|
2518
|
-
.option(
|
|
2519
|
-
'-s, --source-path <src>',
|
|
2520
|
-
'CRD Go types dir or GitHub URL',
|
|
2521
|
-
'https://github.com/redpanda-data/redpanda-operator/operator/api/redpanda/v1alpha2'
|
|
2522
|
-
)
|
|
1428
|
+
.description('Generate Asciidoc documentation for Kubernetes CRD references. Requires either --tag or --branch.')
|
|
1429
|
+
.option('-t, --tag <operatorTag>', 'Operator release tag for GA/beta content')
|
|
1430
|
+
.option('-b, --branch <branch>', 'Branch name for in-progress content')
|
|
1431
|
+
.option('-s, --source-path <src>', 'CRD Go types dir or GitHub URL', 'https://github.com/redpanda-data/redpanda-operator/operator/api/redpanda/v1alpha2')
|
|
2523
1432
|
.option('-d, --depth <n>', 'How many levels deep', '10')
|
|
2524
1433
|
.option('--templates-dir <dir>', 'Asciidoctor templates dir', '.github/crd-config/templates/asciidoctor/operator')
|
|
2525
1434
|
.option('--output <file>', 'Where to write the generated AsciiDoc file', 'modules/reference/pages/k-crd.adoc')
|
|
2526
1435
|
.action(async (opts) => {
|
|
2527
|
-
verifyCrdDependencies()
|
|
1436
|
+
verifyCrdDependencies()
|
|
2528
1437
|
|
|
2529
|
-
// Validate that either --tag or --branch is provided (but not both)
|
|
2530
1438
|
if (!opts.tag && !opts.branch) {
|
|
2531
|
-
console.error('
|
|
2532
|
-
process.exit(1)
|
|
1439
|
+
console.error('Error: Either --tag or --branch must be specified')
|
|
1440
|
+
process.exit(1)
|
|
2533
1441
|
}
|
|
2534
1442
|
if (opts.tag && opts.branch) {
|
|
2535
|
-
console.error('
|
|
2536
|
-
process.exit(1)
|
|
1443
|
+
console.error('Error: Cannot specify both --tag and --branch')
|
|
1444
|
+
process.exit(1)
|
|
2537
1445
|
}
|
|
2538
1446
|
|
|
2539
|
-
|
|
2540
|
-
let configRef;
|
|
2541
|
-
if (opts.branch) {
|
|
2542
|
-
// Branch - use as-is
|
|
2543
|
-
configRef = opts.branch;
|
|
2544
|
-
} else {
|
|
2545
|
-
// Tag - auto-prepend operator/ if needed
|
|
2546
|
-
configRef = opts.tag.startsWith('operator/') ? opts.tag : `operator/${opts.tag}`;
|
|
2547
|
-
}
|
|
1447
|
+
let configRef = opts.branch || (opts.tag.startsWith('operator/') ? opts.tag : `operator/${opts.tag}`)
|
|
2548
1448
|
|
|
2549
|
-
const configTmp = fs.mkdtempSync(path.join(os.tmpdir(), 'crd-config-'))
|
|
2550
|
-
console.log(
|
|
1449
|
+
const configTmp = fs.mkdtempSync(path.join(os.tmpdir(), 'crd-config-'))
|
|
1450
|
+
console.log(`Fetching crd-ref-docs-config.yaml from redpanda-operator@${configRef}…`)
|
|
2551
1451
|
await fetchFromGithub(
|
|
2552
1452
|
'redpanda-data',
|
|
2553
1453
|
'redpanda-operator',
|
|
@@ -2555,144 +1455,120 @@ automation
|
|
|
2555
1455
|
configTmp,
|
|
2556
1456
|
'crd-ref-docs-config.yaml',
|
|
2557
1457
|
configRef
|
|
2558
|
-
)
|
|
2559
|
-
const configPath = path.join(configTmp, 'crd-ref-docs-config.yaml')
|
|
1458
|
+
)
|
|
1459
|
+
const configPath = path.join(configTmp, 'crd-ref-docs-config.yaml')
|
|
2560
1460
|
|
|
2561
|
-
|
|
2562
|
-
const
|
|
2563
|
-
const
|
|
2564
|
-
|
|
2565
|
-
pkg.name === 'redpanda-docs-playbook' ||
|
|
2566
|
-
(pkg.repository && pkg.repository.url.includes('redpanda-data/docs'));
|
|
2567
|
-
let docsBranch = null;
|
|
1461
|
+
const repoRoot = findRepoRoot()
|
|
1462
|
+
const pkgJson = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'))
|
|
1463
|
+
const inDocs = pkgJson.name === 'redpanda-docs-playbook' || (pkgJson.repository && pkgJson.repository.url.includes('redpanda-data/docs'))
|
|
1464
|
+
let docsBranch = null
|
|
2568
1465
|
|
|
2569
1466
|
if (!inDocs) {
|
|
2570
|
-
console.warn('⚠️ Not inside redpanda-data/docs; skipping branch suggestion.')
|
|
1467
|
+
console.warn('⚠️ Not inside redpanda-data/docs; skipping branch suggestion.')
|
|
2571
1468
|
} else {
|
|
2572
1469
|
try {
|
|
2573
|
-
docsBranch = await determineDocsBranch(configRef)
|
|
2574
|
-
console.log(
|
|
1470
|
+
docsBranch = await determineDocsBranch(configRef)
|
|
1471
|
+
console.log(`Done: Detected docs repo; you should commit to branch '${docsBranch}'.`)
|
|
2575
1472
|
} catch (err) {
|
|
2576
|
-
console.error(
|
|
2577
|
-
process.exit(1)
|
|
1473
|
+
console.error(`Error: Unable to determine docs branch: ${err.message}`)
|
|
1474
|
+
process.exit(1)
|
|
2578
1475
|
}
|
|
2579
1476
|
}
|
|
2580
1477
|
|
|
2581
|
-
// Validate templates
|
|
2582
1478
|
if (!fs.existsSync(opts.templatesDir)) {
|
|
2583
|
-
console.error(
|
|
2584
|
-
process.exit(1)
|
|
1479
|
+
console.error(`Error: Templates directory not found: ${opts.templatesDir}`)
|
|
1480
|
+
process.exit(1)
|
|
2585
1481
|
}
|
|
2586
1482
|
|
|
2587
|
-
|
|
2588
|
-
let
|
|
2589
|
-
let tmpSrc;
|
|
1483
|
+
let localSrc = opts.sourcePath
|
|
1484
|
+
let tmpSrc
|
|
2590
1485
|
if (/^https?:\/\/github\.com\//.test(opts.sourcePath)) {
|
|
2591
|
-
const u = new URL(opts.sourcePath)
|
|
2592
|
-
const parts = u.pathname.split('/').filter(Boolean)
|
|
1486
|
+
const u = new URL(opts.sourcePath)
|
|
1487
|
+
const parts = u.pathname.split('/').filter(Boolean)
|
|
2593
1488
|
if (parts.length < 2) {
|
|
2594
|
-
console.error(
|
|
2595
|
-
process.exit(1)
|
|
1489
|
+
console.error(`Error: Invalid GitHub URL: ${opts.sourcePath}`)
|
|
1490
|
+
process.exit(1)
|
|
2596
1491
|
}
|
|
2597
|
-
const [owner, repo, ...subpathParts] = parts
|
|
2598
|
-
const repoUrl = `https://${u.host}/${owner}/${repo}
|
|
2599
|
-
const subpath = subpathParts.join('/')
|
|
2600
|
-
console.log(
|
|
2601
|
-
const ok =
|
|
2602
|
-
spawnSync('git', ['ls-remote', '--exit-code', repoUrl, `refs/tags/${configRef}`, `refs/heads/${configRef}`], {
|
|
2603
|
-
stdio: 'ignore',
|
|
2604
|
-
}).status === 0;
|
|
1492
|
+
const [owner, repo, ...subpathParts] = parts
|
|
1493
|
+
const repoUrl = `https://${u.host}/${owner}/${repo}`
|
|
1494
|
+
const subpath = subpathParts.join('/')
|
|
1495
|
+
console.log(`Verifying "${configRef}" in ${repoUrl}…`)
|
|
1496
|
+
const ok = spawnSync('git', ['ls-remote', '--exit-code', repoUrl, `refs/tags/${configRef}`, `refs/heads/${configRef}`], { stdio: 'ignore' }).status === 0
|
|
2605
1497
|
if (!ok) {
|
|
2606
|
-
console.error(
|
|
2607
|
-
process.exit(1)
|
|
1498
|
+
console.error(`Error: Tag or branch "${configRef}" not found on ${repoUrl}`)
|
|
1499
|
+
process.exit(1)
|
|
2608
1500
|
}
|
|
2609
|
-
const { getAuthenticatedGitHubUrl, hasGitHubToken } = require('../cli-utils/github-token')
|
|
1501
|
+
const { getAuthenticatedGitHubUrl, hasGitHubToken } = require('../cli-utils/github-token')
|
|
2610
1502
|
|
|
2611
|
-
tmpSrc = fs.mkdtempSync(path.join(os.tmpdir(), 'crd-src-'))
|
|
1503
|
+
tmpSrc = fs.mkdtempSync(path.join(os.tmpdir(), 'crd-src-'))
|
|
2612
1504
|
|
|
2613
|
-
|
|
2614
|
-
let cloneUrl = repoUrl;
|
|
1505
|
+
let cloneUrl = repoUrl
|
|
2615
1506
|
if (hasGitHubToken() && repoUrl.includes('github.com')) {
|
|
2616
|
-
cloneUrl = getAuthenticatedGitHubUrl(repoUrl)
|
|
2617
|
-
console.log(
|
|
1507
|
+
cloneUrl = getAuthenticatedGitHubUrl(repoUrl)
|
|
1508
|
+
console.log(`Cloning ${repoUrl}@${configRef} → ${tmpSrc} (authenticated)`)
|
|
2618
1509
|
} else {
|
|
2619
|
-
console.log(
|
|
1510
|
+
console.log(`Cloning ${repoUrl}@${configRef} → ${tmpSrc}`)
|
|
2620
1511
|
}
|
|
2621
1512
|
|
|
2622
|
-
if (
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
}).status !== 0
|
|
2626
|
-
) {
|
|
2627
|
-
console.error(`❌ git clone failed`);
|
|
2628
|
-
process.exit(1);
|
|
1513
|
+
if (spawnSync('git', ['clone', '--depth', '1', '--branch', configRef, cloneUrl, tmpSrc], { stdio: 'inherit' }).status !== 0) {
|
|
1514
|
+
console.error('Error: git clone failed')
|
|
1515
|
+
process.exit(1)
|
|
2629
1516
|
}
|
|
2630
|
-
localSrc = subpath ? path.join(tmpSrc, subpath) : tmpSrc
|
|
1517
|
+
localSrc = subpath ? path.join(tmpSrc, subpath) : tmpSrc
|
|
2631
1518
|
if (!fs.existsSync(localSrc)) {
|
|
2632
|
-
console.error(
|
|
2633
|
-
process.exit(1)
|
|
1519
|
+
console.error(`Error: Subdirectory not found in repo: ${subpath}`)
|
|
1520
|
+
process.exit(1)
|
|
2634
1521
|
}
|
|
2635
1522
|
}
|
|
2636
1523
|
|
|
2637
|
-
|
|
2638
|
-
const outputDir = path.dirname(opts.output);
|
|
1524
|
+
const outputDir = path.dirname(opts.output)
|
|
2639
1525
|
if (!fs.existsSync(outputDir)) {
|
|
2640
|
-
fs.mkdirSync(outputDir, { recursive: true })
|
|
1526
|
+
fs.mkdirSync(outputDir, { recursive: true })
|
|
2641
1527
|
}
|
|
2642
1528
|
|
|
2643
|
-
// Run crd-ref-docs
|
|
2644
1529
|
const args = [
|
|
2645
|
-
'--source-path',
|
|
2646
|
-
|
|
2647
|
-
'--
|
|
2648
|
-
|
|
2649
|
-
'--
|
|
2650
|
-
opts.
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
'--renderer',
|
|
2654
|
-
'asciidoctor',
|
|
2655
|
-
'--output-path',
|
|
2656
|
-
opts.output,
|
|
2657
|
-
];
|
|
2658
|
-
console.log(`⏳ Running crd-ref-docs ${args.join(' ')}`);
|
|
1530
|
+
'--source-path', localSrc,
|
|
1531
|
+
'--max-depth', opts.depth,
|
|
1532
|
+
'--templates-dir', opts.templatesDir,
|
|
1533
|
+
'--config', configPath,
|
|
1534
|
+
'--renderer', 'asciidoctor',
|
|
1535
|
+
'--output-path', opts.output
|
|
1536
|
+
]
|
|
1537
|
+
console.log(`Running crd-ref-docs ${args.join(' ')}`)
|
|
2659
1538
|
if (spawnSync('crd-ref-docs', args, { stdio: 'inherit' }).status !== 0) {
|
|
2660
|
-
console.error(
|
|
2661
|
-
process.exit(1)
|
|
1539
|
+
console.error('Error: crd-ref-docs failed')
|
|
1540
|
+
process.exit(1)
|
|
2662
1541
|
}
|
|
2663
1542
|
|
|
2664
|
-
let doc = fs.readFileSync(opts.output, 'utf8')
|
|
2665
|
-
const xrefRe = /https:\/\/docs\.redpanda\.com[^\s\]\[\)"]+(?:\[[^\]]*\])?/g
|
|
1543
|
+
let doc = fs.readFileSync(opts.output, 'utf8')
|
|
1544
|
+
const xrefRe = /https:\/\/docs\.redpanda\.com[^\s\]\[\)"]+(?:\[[^\]]*\])?/g
|
|
2666
1545
|
doc = doc.replace(xrefRe, (match) => {
|
|
2667
|
-
let urlPart = match
|
|
2668
|
-
let bracketPart = ''
|
|
2669
|
-
const m = match.match(/^([^\[]+)(\[[^\]]*\])$/)
|
|
1546
|
+
let urlPart = match
|
|
1547
|
+
let bracketPart = ''
|
|
1548
|
+
const m = match.match(/^([^\[]+)(\[[^\]]*\])$/)
|
|
2670
1549
|
if (m) {
|
|
2671
|
-
urlPart = m[1]
|
|
2672
|
-
bracketPart = m[2]
|
|
2673
|
-
}
|
|
2674
|
-
if (urlPart.endsWith('#')) {
|
|
2675
|
-
return match;
|
|
1550
|
+
urlPart = m[1]
|
|
1551
|
+
bracketPart = m[2]
|
|
2676
1552
|
}
|
|
1553
|
+
if (urlPart.endsWith('#')) return match
|
|
2677
1554
|
try {
|
|
2678
|
-
const xref = urlToXref(urlPart)
|
|
2679
|
-
return bracketPart ? `${xref}${bracketPart}` : `${xref}[]
|
|
1555
|
+
const xref = urlToXref(urlPart)
|
|
1556
|
+
return bracketPart ? `${xref}${bracketPart}` : `${xref}[]`
|
|
2680
1557
|
} catch (err) {
|
|
2681
|
-
console.warn(`⚠️ urlToXref failed on ${urlPart}: ${err.message}`)
|
|
2682
|
-
return match
|
|
1558
|
+
console.warn(`⚠️ urlToXref failed on ${urlPart}: ${err.message}`)
|
|
1559
|
+
return match
|
|
2683
1560
|
}
|
|
2684
|
-
})
|
|
2685
|
-
fs.writeFileSync(opts.output, doc, 'utf8')
|
|
1561
|
+
})
|
|
1562
|
+
fs.writeFileSync(opts.output, doc, 'utf8')
|
|
2686
1563
|
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
fs.rmSync(configTmp, { recursive: true, force: true });
|
|
1564
|
+
if (tmpSrc) fs.rmSync(tmpSrc, { recursive: true, force: true })
|
|
1565
|
+
fs.rmSync(configTmp, { recursive: true, force: true })
|
|
2690
1566
|
|
|
2691
|
-
console.log(
|
|
1567
|
+
console.log(`Done: CRD docs generated at ${opts.output}`)
|
|
2692
1568
|
if (inDocs) {
|
|
2693
|
-
console.log(`ℹ️ Don't forget to commit your changes on branch '${docsBranch}'.`)
|
|
1569
|
+
console.log(`ℹ️ Don't forget to commit your changes on branch '${docsBranch}'.`)
|
|
2694
1570
|
}
|
|
2695
|
-
})
|
|
1571
|
+
})
|
|
2696
1572
|
|
|
2697
1573
|
/**
|
|
2698
1574
|
* generate bundle-openapi
|
|
@@ -2715,26 +1591,26 @@ automation
|
|
|
2715
1591
|
*
|
|
2716
1592
|
* @example
|
|
2717
1593
|
* # Bundle both Admin and Connect APIs
|
|
2718
|
-
* npx doc-tools generate bundle-openapi
|
|
2719
|
-
* --tag v25.3.1
|
|
1594
|
+
* npx doc-tools generate bundle-openapi \
|
|
1595
|
+
* --tag v25.3.1 \
|
|
2720
1596
|
* --surface both
|
|
2721
1597
|
*
|
|
2722
1598
|
* # Bundle only Admin API
|
|
2723
|
-
* npx doc-tools generate bundle-openapi
|
|
2724
|
-
* --tag v25.3.1
|
|
1599
|
+
* npx doc-tools generate bundle-openapi \
|
|
1600
|
+
* --tag v25.3.1 \
|
|
2725
1601
|
* --surface admin
|
|
2726
1602
|
*
|
|
2727
1603
|
* # Use custom output paths
|
|
2728
|
-
* npx doc-tools generate bundle-openapi
|
|
2729
|
-
* --tag v25.3.1
|
|
2730
|
-
* --surface both
|
|
2731
|
-
* --out-admin api/admin-api.yaml
|
|
1604
|
+
* npx doc-tools generate bundle-openapi \
|
|
1605
|
+
* --tag v25.3.1 \
|
|
1606
|
+
* --surface both \
|
|
1607
|
+
* --out-admin api/admin-api.yaml \
|
|
2732
1608
|
* --out-connect api/connect-api.yaml
|
|
2733
1609
|
*
|
|
2734
1610
|
* # Use major version for Admin API version field
|
|
2735
|
-
* npx doc-tools generate bundle-openapi
|
|
2736
|
-
* --tag v25.3.1
|
|
2737
|
-
* --surface admin
|
|
1611
|
+
* npx doc-tools generate bundle-openapi \
|
|
1612
|
+
* --tag v25.3.1 \
|
|
1613
|
+
* --surface admin \
|
|
2738
1614
|
* --use-admin-major-version
|
|
2739
1615
|
*
|
|
2740
1616
|
* # Full workflow: generate API specs for new release
|
|
@@ -2750,9 +1626,9 @@ automation
|
|
|
2750
1626
|
*/
|
|
2751
1627
|
automation
|
|
2752
1628
|
.command('bundle-openapi')
|
|
2753
|
-
.description('Bundle Redpanda OpenAPI fragments for admin and connect APIs
|
|
2754
|
-
.option('-t, --tag <tag>', 'Git tag for released content
|
|
2755
|
-
.option('-b, --branch <branch>', 'Branch name for in-progress content
|
|
1629
|
+
.description('Bundle Redpanda OpenAPI fragments for admin and connect APIs. Requires either --tag or --branch.')
|
|
1630
|
+
.option('-t, --tag <tag>', 'Git tag for released content')
|
|
1631
|
+
.option('-b, --branch <branch>', 'Branch name for in-progress content')
|
|
2756
1632
|
.option('--repo <url>', 'Repository URL', 'https://github.com/redpanda-data/redpanda.git')
|
|
2757
1633
|
.addOption(new Option('-s, --surface <surface>', 'Which API surface(s) to bundle').choices(['admin', 'connect', 'both']).makeOptionMandatory())
|
|
2758
1634
|
.option('--out-admin <path>', 'Output path for admin API', 'admin/redpanda-admin-api.yaml')
|
|
@@ -2761,32 +1637,28 @@ automation
|
|
|
2761
1637
|
.option('--use-admin-major-version', 'Use admin major version for info.version instead of git tag', false)
|
|
2762
1638
|
.option('--quiet', 'Suppress logs', false)
|
|
2763
1639
|
.action(async (options) => {
|
|
2764
|
-
// Validate that either tag or branch is provided (but not both)
|
|
2765
1640
|
if (!options.tag && !options.branch) {
|
|
2766
|
-
console.error('
|
|
2767
|
-
process.exit(1)
|
|
1641
|
+
console.error('Error: Either --tag or --branch must be specified')
|
|
1642
|
+
process.exit(1)
|
|
2768
1643
|
}
|
|
2769
1644
|
if (options.tag && options.branch) {
|
|
2770
|
-
console.error('
|
|
2771
|
-
process.exit(1)
|
|
1645
|
+
console.error('Error: Cannot specify both --tag and --branch')
|
|
1646
|
+
process.exit(1)
|
|
2772
1647
|
}
|
|
2773
1648
|
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
requireCmd('buf', 'buf should be automatically available after npm install');
|
|
2779
|
-
|
|
2780
|
-
// Check for OpenAPI bundler using the existing detectBundler function
|
|
1649
|
+
const gitRef = options.tag || options.branch
|
|
1650
|
+
requireCmd('git', 'Install Git: https://git-scm.com/downloads')
|
|
1651
|
+
requireCmd('buf', 'buf should be automatically available after npm install')
|
|
1652
|
+
|
|
2781
1653
|
try {
|
|
2782
|
-
const { detectBundler } = require('../tools/bundle-openapi.js')
|
|
2783
|
-
detectBundler(true)
|
|
1654
|
+
const { detectBundler } = require('../tools/bundle-openapi.js')
|
|
1655
|
+
detectBundler(true)
|
|
2784
1656
|
} catch (err) {
|
|
2785
|
-
fail(err.message)
|
|
1657
|
+
fail(err.message)
|
|
2786
1658
|
}
|
|
2787
1659
|
|
|
2788
1660
|
try {
|
|
2789
|
-
const { bundleOpenAPI } = require('../tools/bundle-openapi.js')
|
|
1661
|
+
const { bundleOpenAPI } = require('../tools/bundle-openapi.js')
|
|
2790
1662
|
await bundleOpenAPI({
|
|
2791
1663
|
tag: gitRef,
|
|
2792
1664
|
repo: options.repo,
|
|
@@ -2796,256 +1668,63 @@ automation
|
|
|
2796
1668
|
adminMajor: options.adminMajor,
|
|
2797
1669
|
useAdminMajorVersion: options.useAdminMajorVersion,
|
|
2798
1670
|
quiet: options.quiet
|
|
2799
|
-
})
|
|
1671
|
+
})
|
|
2800
1672
|
} catch (err) {
|
|
2801
|
-
console.error(
|
|
2802
|
-
process.exit(err.message.includes('Validation failed') ? 2 : 1)
|
|
1673
|
+
console.error(`Error: ${err.message}`)
|
|
1674
|
+
process.exit(err.message.includes('Validation failed') ? 2 : 1)
|
|
2803
1675
|
}
|
|
2804
|
-
})
|
|
1676
|
+
})
|
|
2805
1677
|
|
|
2806
1678
|
/**
|
|
2807
|
-
*
|
|
2808
|
-
*
|
|
2809
|
-
*
|
|
2810
|
-
*
|
|
2811
|
-
* - Valid frontmatter in all prompt files
|
|
2812
|
-
* - Accessible resource files
|
|
2813
|
-
* - Proper metadata and descriptions
|
|
2814
|
-
* - No naming conflicts
|
|
2815
|
-
*
|
|
1679
|
+
* @description Update the Redpanda Connect version attribute in antora.yml by fetching
|
|
1680
|
+
* the latest release tag from GitHub or using a specified version.
|
|
1681
|
+
* @why Use this command before generating Connect connector docs to ensure the version
|
|
1682
|
+
* attribute is current. It updates the latest-connect-version attribute automatically.
|
|
2816
1683
|
* @example
|
|
2817
|
-
*
|
|
2818
|
-
|
|
2819
|
-
programCli
|
|
2820
|
-
.command('validate-mcp')
|
|
2821
|
-
.description('Validate MCP server configuration (prompts, resources, metadata)')
|
|
2822
|
-
.action(() => {
|
|
2823
|
-
const {
|
|
2824
|
-
PromptCache,
|
|
2825
|
-
loadAllPrompts
|
|
2826
|
-
} = require('./mcp-tools/prompt-discovery');
|
|
2827
|
-
const {
|
|
2828
|
-
validateMcpConfiguration,
|
|
2829
|
-
formatValidationResults
|
|
2830
|
-
} = require('./mcp-tools/mcp-validation');
|
|
2831
|
-
|
|
2832
|
-
const baseDir = findRepoRoot();
|
|
2833
|
-
const promptCache = new PromptCache();
|
|
2834
|
-
|
|
2835
|
-
// Resources configuration
|
|
2836
|
-
const resources = [
|
|
2837
|
-
{
|
|
2838
|
-
uri: 'redpanda://style-guide',
|
|
2839
|
-
name: 'Redpanda Documentation Style Guide',
|
|
2840
|
-
description: 'Complete style guide based on Google Developer Documentation Style Guide with Redpanda-specific guidelines',
|
|
2841
|
-
mimeType: 'text/markdown',
|
|
2842
|
-
version: '1.0.0',
|
|
2843
|
-
lastUpdated: '2025-12-11'
|
|
2844
|
-
}
|
|
2845
|
-
];
|
|
2846
|
-
|
|
2847
|
-
const resourceMap = {
|
|
2848
|
-
'redpanda://style-guide': { file: 'style-guide.md', mimeType: 'text/markdown' }
|
|
2849
|
-
};
|
|
2850
|
-
|
|
2851
|
-
try {
|
|
2852
|
-
// Load prompts
|
|
2853
|
-
console.log('Loading prompts...');
|
|
2854
|
-
const prompts = loadAllPrompts(baseDir, promptCache);
|
|
2855
|
-
console.log(`Found ${prompts.length} prompts`);
|
|
2856
|
-
|
|
2857
|
-
// Validate configuration
|
|
2858
|
-
console.log('\nValidating configuration...');
|
|
2859
|
-
const validation = validateMcpConfiguration({
|
|
2860
|
-
resources,
|
|
2861
|
-
resourceMap,
|
|
2862
|
-
prompts,
|
|
2863
|
-
baseDir
|
|
2864
|
-
});
|
|
2865
|
-
|
|
2866
|
-
// Format and display results
|
|
2867
|
-
const output = formatValidationResults(validation, { resources, prompts });
|
|
2868
|
-
console.log('\n' + output);
|
|
2869
|
-
|
|
2870
|
-
if (!validation.valid) {
|
|
2871
|
-
process.exit(1);
|
|
2872
|
-
}
|
|
2873
|
-
} catch (err) {
|
|
2874
|
-
console.error(`❌ Validation failed: ${err.message}`);
|
|
2875
|
-
process.exit(1);
|
|
2876
|
-
}
|
|
2877
|
-
});
|
|
2878
|
-
|
|
2879
|
-
/**
|
|
2880
|
-
* Preview a prompt with arguments
|
|
1684
|
+
* # Update to the latest version from GitHub
|
|
1685
|
+
* npx doc-tools generate update-connect-version
|
|
2881
1686
|
*
|
|
2882
|
-
*
|
|
2883
|
-
*
|
|
2884
|
-
*
|
|
2885
|
-
* @example
|
|
2886
|
-
* npx doc-tools preview-prompt review-for-style --content "Sample content"
|
|
2887
|
-
* npx doc-tools preview-prompt write-new-guide --topic "Deploy Redpanda"
|
|
1687
|
+
* # Update to a specific version
|
|
1688
|
+
* npx doc-tools generate update-connect-version --connect-version 4.50.0
|
|
1689
|
+
* @requirements None (uses GitHub API).
|
|
2888
1690
|
*/
|
|
2889
|
-
|
|
2890
|
-
.command('
|
|
2891
|
-
.description('
|
|
2892
|
-
.
|
|
2893
|
-
.
|
|
2894
|
-
|
|
2895
|
-
.option('--audience <text>', 'Audience argument (for write prompts)')
|
|
2896
|
-
.action((promptName, options) => {
|
|
2897
|
-
const {
|
|
2898
|
-
PromptCache,
|
|
2899
|
-
loadAllPrompts,
|
|
2900
|
-
buildPromptWithArguments
|
|
2901
|
-
} = require('./mcp-tools/prompt-discovery');
|
|
2902
|
-
|
|
2903
|
-
const baseDir = findRepoRoot();
|
|
2904
|
-
const promptCache = new PromptCache();
|
|
1691
|
+
automation
|
|
1692
|
+
.command('update-connect-version')
|
|
1693
|
+
.description('Update the Redpanda Connect version in antora.yml')
|
|
1694
|
+
.option('-v, --connect-version <version>', 'Specific Connect version (default: fetch latest from GitHub)')
|
|
1695
|
+
.action(async (options) => {
|
|
1696
|
+
const GetLatestConnectTag = require('../extensions/version-fetcher/get-latest-connect')
|
|
2905
1697
|
|
|
2906
1698
|
try {
|
|
2907
|
-
|
|
2908
|
-
loadAllPrompts(baseDir, promptCache);
|
|
2909
|
-
|
|
2910
|
-
// Get the requested prompt
|
|
2911
|
-
const prompt = promptCache.get(promptName);
|
|
2912
|
-
if (!prompt) {
|
|
2913
|
-
console.error(`❌ Prompt not found: ${promptName}`);
|
|
2914
|
-
console.error(`\nAvailable prompts: ${promptCache.getNames().join(', ')}`);
|
|
2915
|
-
process.exit(1);
|
|
2916
|
-
}
|
|
1699
|
+
let version
|
|
2917
1700
|
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
const promptText = buildPromptWithArguments(prompt, args);
|
|
2926
|
-
|
|
2927
|
-
// Display preview
|
|
2928
|
-
console.log('='.repeat(70));
|
|
2929
|
-
console.log(`PROMPT PREVIEW: ${promptName}`);
|
|
2930
|
-
console.log('='.repeat(70));
|
|
2931
|
-
console.log(`Description: ${prompt.description}`);
|
|
2932
|
-
console.log(`Version: ${prompt.version}`);
|
|
2933
|
-
if (prompt.arguments.length > 0) {
|
|
2934
|
-
console.log(`Arguments: ${prompt.arguments.map(a => a.name).join(', ')}`);
|
|
1701
|
+
if (options.connectVersion) {
|
|
1702
|
+
version = options.connectVersion.replace(/^v/, '')
|
|
1703
|
+
console.log(`Updating to specified Connect version: ${version}`)
|
|
1704
|
+
} else {
|
|
1705
|
+
console.log('Fetching latest Connect version from GitHub...')
|
|
1706
|
+
version = await GetLatestConnectTag()
|
|
1707
|
+
console.log(`Latest Connect version: ${version}`)
|
|
2935
1708
|
}
|
|
2936
|
-
console.log('='.repeat(70));
|
|
2937
|
-
console.log('\n' + promptText);
|
|
2938
|
-
console.log('\n' + '='.repeat(70));
|
|
2939
|
-
} catch (err) {
|
|
2940
|
-
console.error(`❌ Preview failed: ${err.message}`);
|
|
2941
|
-
process.exit(1);
|
|
2942
|
-
}
|
|
2943
|
-
});
|
|
2944
|
-
|
|
2945
|
-
/**
|
|
2946
|
-
* Show MCP server version and statistics
|
|
2947
|
-
*
|
|
2948
|
-
* Displays version information for the MCP server, including:
|
|
2949
|
-
* - Server version
|
|
2950
|
-
* - Available prompts and their versions
|
|
2951
|
-
* - Available resources and their versions
|
|
2952
|
-
* - Usage statistics (if available)
|
|
2953
|
-
*
|
|
2954
|
-
* @example
|
|
2955
|
-
* npx doc-tools mcp-version
|
|
2956
|
-
* npx doc-tools mcp-version --stats # Show usage statistics if available
|
|
2957
|
-
*/
|
|
2958
|
-
programCli
|
|
2959
|
-
.command('mcp-version')
|
|
2960
|
-
.description('Show MCP server version and configuration information')
|
|
2961
|
-
.option('--stats', 'Show usage statistics if available', false)
|
|
2962
|
-
.action((options) => {
|
|
2963
|
-
const packageJson = require('../package.json');
|
|
2964
|
-
const {
|
|
2965
|
-
PromptCache,
|
|
2966
|
-
loadAllPrompts
|
|
2967
|
-
} = require('./mcp-tools/prompt-discovery');
|
|
2968
|
-
|
|
2969
|
-
const baseDir = findRepoRoot();
|
|
2970
|
-
const promptCache = new PromptCache();
|
|
2971
|
-
|
|
2972
|
-
try {
|
|
2973
|
-
// Load prompts
|
|
2974
|
-
const prompts = loadAllPrompts(baseDir, promptCache);
|
|
2975
|
-
|
|
2976
|
-
// Resources configuration
|
|
2977
|
-
const resources = [
|
|
2978
|
-
{
|
|
2979
|
-
uri: 'redpanda://style-guide',
|
|
2980
|
-
name: 'Redpanda Documentation Style Guide',
|
|
2981
|
-
version: '1.0.0',
|
|
2982
|
-
lastUpdated: '2025-12-11'
|
|
2983
|
-
}
|
|
2984
|
-
];
|
|
2985
1709
|
|
|
2986
|
-
|
|
2987
|
-
console.log('Redpanda Doc Tools MCP Server');
|
|
2988
|
-
console.log('='.repeat(60));
|
|
2989
|
-
console.log(`Server version: ${packageJson.version}`);
|
|
2990
|
-
console.log(`Base directory: ${baseDir}`);
|
|
2991
|
-
console.log('');
|
|
1710
|
+
const currentVersion = getAntoraValue('asciidoc.attributes.latest-connect-version')
|
|
2992
1711
|
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
const args = prompt.arguments.length > 0
|
|
2997
|
-
? ` [${prompt.arguments.map(a => a.name).join(', ')}]`
|
|
2998
|
-
: '';
|
|
2999
|
-
console.log(` - ${prompt.name} (v${prompt.version})${args}`);
|
|
3000
|
-
console.log(` ${prompt.description}`);
|
|
3001
|
-
});
|
|
3002
|
-
console.log('');
|
|
3003
|
-
|
|
3004
|
-
// Resources
|
|
3005
|
-
console.log(`Resources (${resources.length} available):`);
|
|
3006
|
-
resources.forEach(resource => {
|
|
3007
|
-
console.log(` - ${resource.uri}`);
|
|
3008
|
-
console.log(` ${resource.name} (v${resource.version})`);
|
|
3009
|
-
console.log(` Last updated: ${resource.lastUpdated}`);
|
|
3010
|
-
});
|
|
3011
|
-
console.log('');
|
|
3012
|
-
|
|
3013
|
-
// Check for usage statistics
|
|
3014
|
-
if (options.stats) {
|
|
3015
|
-
const statsPath = path.join(baseDir, 'mcp-usage-stats.json');
|
|
3016
|
-
if (fs.existsSync(statsPath)) {
|
|
3017
|
-
console.log('Usage Statistics:');
|
|
3018
|
-
console.log('='.repeat(60));
|
|
3019
|
-
const stats = JSON.parse(fs.readFileSync(statsPath, 'utf8'));
|
|
3020
|
-
console.log(`Exported at: ${stats.exportedAt}`);
|
|
3021
|
-
console.log(`Total prompt calls: ${stats.totalPromptCalls}`);
|
|
3022
|
-
console.log(`Total resource calls: ${stats.totalResourceCalls}`);
|
|
3023
|
-
console.log(`Total tool calls: ${stats.totalToolCalls}`);
|
|
3024
|
-
|
|
3025
|
-
if (stats.totalPromptCalls > 0) {
|
|
3026
|
-
console.log('\nMost used prompts:');
|
|
3027
|
-
Object.entries(stats.prompts)
|
|
3028
|
-
.sort((a, b) => b[1] - a[1])
|
|
3029
|
-
.slice(0, 5)
|
|
3030
|
-
.forEach(([name, count]) => {
|
|
3031
|
-
console.log(` ${name}: ${count} calls`);
|
|
3032
|
-
});
|
|
3033
|
-
}
|
|
3034
|
-
} else {
|
|
3035
|
-
console.log('No usage statistics available yet.');
|
|
3036
|
-
console.log('Statistics are exported when the MCP server shuts down.');
|
|
3037
|
-
}
|
|
1712
|
+
if (currentVersion === version) {
|
|
1713
|
+
console.log(`✓ Already at version ${version}`)
|
|
1714
|
+
return
|
|
3038
1715
|
}
|
|
3039
1716
|
|
|
3040
|
-
|
|
3041
|
-
console.log(
|
|
3042
|
-
console.log('
|
|
3043
|
-
console.log('
|
|
1717
|
+
setAntoraValue('asciidoc.attributes.latest-connect-version', version)
|
|
1718
|
+
console.log(`Done: Updated latest-connect-version from ${currentVersion} to ${version}`)
|
|
1719
|
+
console.log('')
|
|
1720
|
+
console.log('Next steps:')
|
|
1721
|
+
console.log(' 1. Run: npx doc-tools generate rpcn-connector-docs --fetch-connectors')
|
|
1722
|
+
console.log(' 2. Review and commit the changes')
|
|
3044
1723
|
} catch (err) {
|
|
3045
|
-
console.error(
|
|
3046
|
-
process.exit(1)
|
|
1724
|
+
console.error(`Error: Failed to update Connect version: ${err.message}`)
|
|
1725
|
+
process.exit(1)
|
|
3047
1726
|
}
|
|
3048
|
-
})
|
|
1727
|
+
})
|
|
3049
1728
|
|
|
3050
|
-
programCli.addCommand(automation)
|
|
3051
|
-
programCli.parse(process.argv)
|
|
1729
|
+
programCli.addCommand(automation)
|
|
1730
|
+
programCli.parse(process.argv)
|