@redpanda-data/docs-extensions-and-macros 4.5.0 → 4.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.adoc +0 -163
- package/bin/doc-tools.js +492 -283
- package/cli-utils/antora-utils.js +127 -0
- package/cli-utils/generate-cluster-docs.sh +41 -29
- package/cli-utils/self-managed-docs-branch.js +2 -1
- package/cli-utils/start-cluster.sh +70 -30
- package/extensions/generate-rp-connect-info.js +14 -9
- package/package.json +6 -5
- package/tools/redpanda-connect/generate-rpcn-connector-docs.js +233 -0
- package/tools/redpanda-connect/helpers/advancedConfig.js +17 -0
- package/tools/redpanda-connect/helpers/buildConfigYaml.js +53 -0
- package/tools/redpanda-connect/helpers/commonConfig.js +31 -0
- package/tools/redpanda-connect/helpers/eq.js +10 -0
- package/tools/redpanda-connect/helpers/index.js +19 -0
- package/tools/redpanda-connect/helpers/isObject.js +1 -0
- package/tools/redpanda-connect/helpers/join.js +6 -0
- package/tools/redpanda-connect/helpers/ne.js +10 -0
- package/tools/redpanda-connect/helpers/or.js +4 -0
- package/tools/redpanda-connect/helpers/renderConnectExamples.js +37 -0
- package/tools/redpanda-connect/helpers/renderConnectFields.js +148 -0
- package/tools/redpanda-connect/helpers/renderLeafField.js +64 -0
- package/tools/redpanda-connect/helpers/renderObjectField.js +41 -0
- package/tools/redpanda-connect/helpers/renderYamlList.js +24 -0
- package/tools/redpanda-connect/helpers/toYaml.js +11 -0
- package/tools/redpanda-connect/helpers/uppercase.js +9 -0
- package/tools/redpanda-connect/parse-csv-connectors.js +63 -0
- package/tools/redpanda-connect/report-delta.js +152 -0
- package/tools/redpanda-connect/templates/connector.hbs +20 -0
- package/tools/redpanda-connect/templates/examples-partials.hbs +7 -0
- package/tools/redpanda-connect/templates/fields-partials.hbs +13 -0
- package/tools/redpanda-connect/templates/intro.hbs +33 -0
- package/macros/data-template.js +0 -591
package/bin/doc-tools.js
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const { execSync, spawnSync } = require('child_process');
|
|
4
|
-
const os
|
|
4
|
+
const os = require('os');
|
|
5
5
|
const { Command } = require('commander');
|
|
6
6
|
const path = require('path');
|
|
7
|
+
const yaml = require('yaml');
|
|
7
8
|
const fs = require('fs');
|
|
8
|
-
const
|
|
9
|
+
const handlebars = require('handlebars');
|
|
10
|
+
const { determineDocsBranch } = require('../cli-utils/self-managed-docs-branch.js');
|
|
9
11
|
const fetchFromGithub = require('../tools/fetch-from-github.js');
|
|
10
12
|
const { urlToXref } = require('../cli-utils/convert-doc-links.js');
|
|
11
|
-
|
|
13
|
+
const { generateRpcnConnectorDocs } = require('../tools/redpanda-connect/generate-rpcn-connector-docs.js');
|
|
14
|
+
const parseCSVConnectors = require('../tools/redpanda-connect/parse-csv-connectors.js');
|
|
15
|
+
const { getAntoraValue, setAntoraValue } = require('../cli-utils/antora-utils');
|
|
16
|
+
const {
|
|
17
|
+
getRpkConnectVersion,
|
|
18
|
+
printDeltaReport
|
|
19
|
+
} = require('../tools/redpanda-connect/report-delta');
|
|
12
20
|
|
|
13
21
|
/**
|
|
14
22
|
* Searches upward from a starting directory to locate the repository root.
|
|
@@ -21,9 +29,10 @@ const { urlToXref } = require('../cli-utils/convert-doc-links.js');
|
|
|
21
29
|
function findRepoRoot(start = process.cwd()) {
|
|
22
30
|
let dir = start;
|
|
23
31
|
while (dir !== path.parse(dir).root) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
if (
|
|
33
|
+
fs.existsSync(path.join(dir, '.git')) ||
|
|
34
|
+
fs.existsSync(path.join(dir, 'package.json'))
|
|
35
|
+
) {
|
|
27
36
|
return dir;
|
|
28
37
|
}
|
|
29
38
|
dir = path.dirname(dir);
|
|
@@ -34,6 +43,7 @@ function findRepoRoot(start = process.cwd()) {
|
|
|
34
43
|
|
|
35
44
|
// --------------------------------------------------------------------
|
|
36
45
|
// Dependency check functions
|
|
46
|
+
|
|
37
47
|
/**
|
|
38
48
|
* Prints an error message to stderr and exits the process with a non-zero status.
|
|
39
49
|
*
|
|
@@ -68,7 +78,7 @@ function requireTool(cmd, { versionFlag = '--version', help = '' } = {}) {
|
|
|
68
78
|
*
|
|
69
79
|
* @param {string} cmd - The name of the command-line tool to check.
|
|
70
80
|
* @param {string} [help] - Optional help text to display if the tool is not found.
|
|
71
|
-
* @param {string} [versionFlag='--
|
|
81
|
+
* @param {string} [versionFlag='--version'] - The flag to use when checking if the tool is installed.
|
|
72
82
|
*
|
|
73
83
|
* @throws {Error} If the specified command is not found or does not respond to the specified flag.
|
|
74
84
|
*/
|
|
@@ -76,9 +86,9 @@ function requireCmd(cmd, help, versionFlag = '--version') {
|
|
|
76
86
|
requireTool(cmd, { versionFlag, help });
|
|
77
87
|
}
|
|
78
88
|
|
|
79
|
-
|
|
80
89
|
// --------------------------------------------------------------------
|
|
81
90
|
// Special validators
|
|
91
|
+
|
|
82
92
|
/**
|
|
83
93
|
* Ensures that Python with a minimum required version is installed and available in the system PATH.
|
|
84
94
|
*
|
|
@@ -87,7 +97,6 @@ function requireCmd(cmd, help, versionFlag = '--version') {
|
|
|
87
97
|
* @param {number} [minMajor=3] - Minimum required major version of Python.
|
|
88
98
|
* @param {number} [minMinor=10] - Minimum required minor version of Python.
|
|
89
99
|
*/
|
|
90
|
-
|
|
91
100
|
function requirePython(minMajor = 3, minMinor = 10) {
|
|
92
101
|
const candidates = ['python3', 'python'];
|
|
93
102
|
for (const p of candidates) {
|
|
@@ -101,8 +110,10 @@ function requirePython(minMajor = 3, minMinor = 10) {
|
|
|
101
110
|
/* ignore & try next */
|
|
102
111
|
}
|
|
103
112
|
}
|
|
104
|
-
fail(
|
|
105
|
-
|
|
113
|
+
fail(
|
|
114
|
+
`Python ${minMajor}.${minMinor}+ not found or too old.
|
|
115
|
+
→ Install from your package manager or https://python.org`
|
|
116
|
+
);
|
|
106
117
|
}
|
|
107
118
|
|
|
108
119
|
/**
|
|
@@ -121,65 +132,64 @@ function requireDockerDaemon() {
|
|
|
121
132
|
|
|
122
133
|
// --------------------------------------------------------------------
|
|
123
134
|
// Grouped checks
|
|
135
|
+
|
|
124
136
|
/**
|
|
125
137
|
* Ensures that required dependencies for generating CRD documentation are installed.
|
|
126
138
|
*
|
|
127
139
|
* 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.
|
|
128
140
|
*/
|
|
129
|
-
|
|
130
141
|
function verifyCrdDependencies() {
|
|
131
|
-
requireCmd('git',
|
|
142
|
+
requireCmd('git', 'Install Git: https://git-scm.com/downloads');
|
|
132
143
|
requireCmd(
|
|
133
144
|
'crd-ref-docs',
|
|
134
145
|
`
|
|
135
|
-
|
|
146
|
+
The 'crd-ref-docs' command is required but was not found.
|
|
136
147
|
|
|
137
|
-
|
|
148
|
+
To install it, follow these steps (for macOS):
|
|
138
149
|
|
|
139
|
-
|
|
140
|
-
|
|
150
|
+
1. Determine your architecture:
|
|
151
|
+
Run: \`uname -m\`
|
|
141
152
|
|
|
142
|
-
|
|
153
|
+
2. Download and install:
|
|
143
154
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
155
|
+
- For Apple Silicon (M1/M2/M3):
|
|
156
|
+
curl -fLO https://github.com/elastic/crd-ref-docs/releases/download/v0.1.0/crd-ref-docs_0.1.0_Darwin_arm64.tar.gz
|
|
157
|
+
tar -xzf crd-ref-docs_0.1.0_Darwin_arm64.tar.gz
|
|
158
|
+
chmod +x crd-ref-docs
|
|
159
|
+
sudo mv crd-ref-docs /usr/local/bin/
|
|
149
160
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
161
|
+
- For Intel (x86_64):
|
|
162
|
+
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
|
|
163
|
+
tar -xzf crd-ref-docs_0.1.0_Darwin_x86_64.tar.gz
|
|
164
|
+
chmod +x crd-ref-docs
|
|
165
|
+
sudo mv crd-ref-docs /usr/local/bin/
|
|
155
166
|
|
|
156
|
-
|
|
167
|
+
For more details, visit: https://github.com/elastic/crd-ref-docs
|
|
157
168
|
`.trim()
|
|
158
169
|
);
|
|
159
170
|
requireCmd(
|
|
160
171
|
'go',
|
|
161
172
|
`
|
|
162
|
-
|
|
173
|
+
The 'go' command (Golang) is required but was not found.
|
|
163
174
|
|
|
164
|
-
|
|
175
|
+
To install it on macOS:
|
|
165
176
|
|
|
166
|
-
|
|
167
|
-
|
|
177
|
+
Option 1: Install via Homebrew (recommended):
|
|
178
|
+
brew install go
|
|
168
179
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
180
|
+
Option 2: Download directly from the official site:
|
|
181
|
+
1. Visit: https://go.dev/dl/
|
|
182
|
+
2. Download the appropriate installer for macOS.
|
|
183
|
+
3. Run the installer and follow the instructions.
|
|
173
184
|
|
|
174
|
-
|
|
175
|
-
|
|
185
|
+
After installation, verify it works:
|
|
186
|
+
go version
|
|
176
187
|
|
|
177
|
-
|
|
188
|
+
For more details, see: https://go.dev/doc/install
|
|
178
189
|
`.trim(),
|
|
179
|
-
|
|
190
|
+
'version'
|
|
180
191
|
);
|
|
181
|
-
|
|
182
|
-
}
|
|
192
|
+
}
|
|
183
193
|
|
|
184
194
|
/**
|
|
185
195
|
* Ensures that all required tools for Helm documentation generation are installed.
|
|
@@ -190,35 +200,35 @@ function verifyHelmDependencies() {
|
|
|
190
200
|
requireCmd(
|
|
191
201
|
'helm-docs',
|
|
192
202
|
`
|
|
193
|
-
|
|
203
|
+
The 'helm-docs' command is required but was not found.
|
|
194
204
|
|
|
195
|
-
|
|
205
|
+
To install it, follow these steps (for macOS):
|
|
196
206
|
|
|
197
|
-
|
|
198
|
-
|
|
207
|
+
1. Determine your architecture:
|
|
208
|
+
Run: \`uname -m\`
|
|
199
209
|
|
|
200
|
-
|
|
210
|
+
2. Download and install:
|
|
201
211
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
212
|
+
- For Apple Silicon (M1/M2/M3):
|
|
213
|
+
curl -fLO https://github.com/norwoodj/helm-docs/releases/download/v1.11.0/helm-docs_1.11.0_Darwin_arm64.tar.gz
|
|
214
|
+
tar -xzf helm-docs_1.11.0_Darwin_arm64.tar.gz
|
|
215
|
+
chmod +x helm-docs
|
|
216
|
+
sudo mv helm-docs /usr/local/bin/
|
|
207
217
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
218
|
+
- For Intel (x86_64):
|
|
219
|
+
curl -fLO https://github.com/norwoodj/helm-docs/releases/download/v1.11.0/helm-docs_1.11.0_Darwin_x86_64.tar.gz
|
|
220
|
+
tar -xzf helm-docs_1.11.0_Darwin_x86_64.tar.gz
|
|
221
|
+
chmod +x helm-docs
|
|
222
|
+
sudo mv helm-docs /usr/local/bin/
|
|
213
223
|
|
|
214
|
-
|
|
215
|
-
|
|
224
|
+
Alternatively, if you use Homebrew:
|
|
225
|
+
brew install norwoodj/tap/helm-docs
|
|
216
226
|
|
|
217
|
-
|
|
227
|
+
For more details, visit: https://github.com/norwoodj/helm-docs
|
|
218
228
|
`.trim()
|
|
219
229
|
);
|
|
220
|
-
|
|
221
|
-
requireCmd('git',
|
|
230
|
+
requireCmd('pandoc', 'brew install pandoc or https://pandoc.org');
|
|
231
|
+
requireCmd('git', 'Install Git: https://git-scm.com/downloads');
|
|
222
232
|
}
|
|
223
233
|
|
|
224
234
|
/**
|
|
@@ -227,13 +237,16 @@ function verifyHelmDependencies() {
|
|
|
227
237
|
* Checks for the presence of `make`, Python 3.10 or newer, and at least one C++ compiler (`gcc` or `clang`). Exits the process with an error message if any dependency is missing.
|
|
228
238
|
*/
|
|
229
239
|
function verifyPropertyDependencies() {
|
|
230
|
-
requireCmd('make',
|
|
240
|
+
requireCmd('make', 'Your OS package manager');
|
|
231
241
|
requirePython();
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
catch {
|
|
235
|
-
try {
|
|
236
|
-
|
|
242
|
+
try {
|
|
243
|
+
execSync('gcc --version', { stdio: 'ignore' });
|
|
244
|
+
} catch {
|
|
245
|
+
try {
|
|
246
|
+
execSync('clang --version', { stdio: 'ignore' });
|
|
247
|
+
} catch {
|
|
248
|
+
fail('A C++ compiler (gcc or clang) is required.');
|
|
249
|
+
}
|
|
237
250
|
}
|
|
238
251
|
}
|
|
239
252
|
|
|
@@ -250,15 +263,17 @@ function verifyMetricsDependencies() {
|
|
|
250
263
|
requireCmd('tar');
|
|
251
264
|
requireDockerDaemon();
|
|
252
265
|
}
|
|
266
|
+
|
|
253
267
|
// --------------------------------------------------------------------
|
|
254
268
|
// Main CLI Definition
|
|
255
269
|
// --------------------------------------------------------------------
|
|
256
270
|
const programCli = new Command();
|
|
257
271
|
|
|
272
|
+
const pkg = require('../package.json');
|
|
258
273
|
programCli
|
|
259
274
|
.name('doc-tools')
|
|
260
275
|
.description('Redpanda Document Automation CLI')
|
|
261
|
-
.version(
|
|
276
|
+
.version(pkg.version);
|
|
262
277
|
|
|
263
278
|
// Top-level commands.
|
|
264
279
|
programCli
|
|
@@ -279,7 +294,7 @@ programCli
|
|
|
279
294
|
try {
|
|
280
295
|
await require('../tools/get-redpanda-version.js')(options);
|
|
281
296
|
} catch (err) {
|
|
282
|
-
console.error(err);
|
|
297
|
+
console.error(`❌ ${err.message}`);
|
|
283
298
|
process.exit(1);
|
|
284
299
|
}
|
|
285
300
|
});
|
|
@@ -293,12 +308,12 @@ programCli
|
|
|
293
308
|
try {
|
|
294
309
|
await require('../tools/get-console-version.js')(options);
|
|
295
310
|
} catch (err) {
|
|
296
|
-
console.error(err);
|
|
311
|
+
console.error(`❌ ${err.message}`);
|
|
297
312
|
process.exit(1);
|
|
298
313
|
}
|
|
299
314
|
});
|
|
300
315
|
|
|
301
|
-
|
|
316
|
+
programCli
|
|
302
317
|
.command('link-readme')
|
|
303
318
|
.description('Symlink a README.adoc into docs/modules/<module>/pages/')
|
|
304
319
|
.requiredOption('-s, --subdir <subdir>', 'Relative path to the lab project subdirectory')
|
|
@@ -309,9 +324,9 @@ programCli
|
|
|
309
324
|
const moduleName = normalized.split('/')[0];
|
|
310
325
|
|
|
311
326
|
const projectDir = path.join(repoRoot, normalized);
|
|
312
|
-
const pagesDir
|
|
327
|
+
const pagesDir = path.join(repoRoot, 'docs', 'modules', moduleName, 'pages');
|
|
313
328
|
const sourceFile = path.join(projectDir, 'README.adoc');
|
|
314
|
-
const destLink
|
|
329
|
+
const destLink = path.join(pagesDir, options.target);
|
|
315
330
|
|
|
316
331
|
if (!fs.existsSync(projectDir)) {
|
|
317
332
|
console.error(`❌ Project directory not found: ${projectDir}`);
|
|
@@ -332,38 +347,38 @@ programCli
|
|
|
332
347
|
else fail(`Destination already exists and is not a symlink: ${destLink}`);
|
|
333
348
|
}
|
|
334
349
|
fs.symlinkSync(relPath, destLink);
|
|
335
|
-
console.log(
|
|
350
|
+
console.log(`✅ Linked ${relPath} → ${destLink}`);
|
|
336
351
|
} catch (err) {
|
|
337
352
|
fail(`Failed to create symlink: ${err.message}`);
|
|
338
353
|
}
|
|
339
354
|
});
|
|
340
355
|
|
|
341
356
|
programCli
|
|
342
|
-
.command('fetch')
|
|
343
|
-
.description('Fetch a file or directory from GitHub and save it locally')
|
|
344
|
-
.requiredOption('-o, --owner <owner>', 'GitHub repo owner or org')
|
|
345
|
-
.requiredOption('-r, --repo <repo>', 'GitHub repo name')
|
|
346
|
-
.requiredOption('-p, --remote-path <path>', 'Path in the repo to fetch')
|
|
347
|
-
.requiredOption('-d, --save-dir <dir>', 'Local directory to save into')
|
|
348
|
-
.option('-f, --filename <name>', 'Custom filename to save as')
|
|
349
|
-
.action(async (options) => {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
}
|
|
357
|
+
.command('fetch')
|
|
358
|
+
.description('Fetch a file or directory from GitHub and save it locally')
|
|
359
|
+
.requiredOption('-o, --owner <owner>', 'GitHub repo owner or org')
|
|
360
|
+
.requiredOption('-r, --repo <repo>', 'GitHub repo name')
|
|
361
|
+
.requiredOption('-p, --remote-path <path>', 'Path in the repo to fetch')
|
|
362
|
+
.requiredOption('-d, --save-dir <dir>', 'Local directory to save into')
|
|
363
|
+
.option('-f, --filename <name>', 'Custom filename to save as')
|
|
364
|
+
.action(async (options) => {
|
|
365
|
+
try {
|
|
366
|
+
await fetchFromGithub(
|
|
367
|
+
options.owner,
|
|
368
|
+
options.repo,
|
|
369
|
+
options.remotePath,
|
|
370
|
+
options.saveDir,
|
|
371
|
+
options.filename
|
|
372
|
+
);
|
|
373
|
+
console.log(`✅ Fetched to ${options.saveDir}`);
|
|
374
|
+
} catch (err) {
|
|
375
|
+
console.error(`❌ ${err.message}`);
|
|
376
|
+
process.exit(1);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
363
379
|
|
|
364
380
|
// Create an "automation" subcommand group.
|
|
365
|
-
const automation = new Command('generate')
|
|
366
|
-
.description('Run docs automations (properties, metrics, rpk docs, and Kubernetes)');
|
|
381
|
+
const automation = new Command('generate').description('Run docs automations');
|
|
367
382
|
|
|
368
383
|
// --------------------------------------------------------------------
|
|
369
384
|
// Automation subcommands
|
|
@@ -373,23 +388,23 @@ const automation = new Command('generate')
|
|
|
373
388
|
const commonOptions = {
|
|
374
389
|
dockerRepo: 'redpanda',
|
|
375
390
|
consoleTag: 'latest',
|
|
376
|
-
consoleDockerRepo: 'console'
|
|
391
|
+
consoleDockerRepo: 'console',
|
|
377
392
|
};
|
|
378
393
|
|
|
379
394
|
function runClusterDocs(mode, tag, options) {
|
|
380
395
|
const script = path.join(__dirname, '../cli-utils/generate-cluster-docs.sh');
|
|
381
|
-
const args
|
|
382
|
-
console.log(
|
|
383
|
-
const r = spawnSync('bash', [
|
|
396
|
+
const args = [mode, tag, options.dockerRepo, options.consoleTag, options.consoleDockerRepo];
|
|
397
|
+
console.log(`⏳ Running ${script} with arguments: ${args.join(' ')}`);
|
|
398
|
+
const r = spawnSync('bash', [script, ...args], { stdio: 'inherit', shell: true });
|
|
384
399
|
if (r.status !== 0) process.exit(r.status);
|
|
385
400
|
}
|
|
386
401
|
|
|
387
402
|
// helper to diff two autogenerated directories
|
|
388
403
|
function diffDirs(kind, oldTag, newTag) {
|
|
389
|
-
const oldDir
|
|
390
|
-
const newDir
|
|
404
|
+
const oldDir = path.join('autogenerated', oldTag, kind);
|
|
405
|
+
const newDir = path.join('autogenerated', newTag, kind);
|
|
391
406
|
const diffDir = path.join('autogenerated', 'diffs', kind, `${oldTag}_to_${newTag}`);
|
|
392
|
-
const patch
|
|
407
|
+
const patch = path.join(diffDir, 'changes.patch');
|
|
393
408
|
|
|
394
409
|
if (!fs.existsSync(oldDir)) {
|
|
395
410
|
console.error(`❌ Cannot diff: missing ${oldDir}`);
|
|
@@ -415,11 +430,22 @@ function diffDirs(kind, oldTag, newTag) {
|
|
|
415
430
|
automation
|
|
416
431
|
.command('metrics-docs')
|
|
417
432
|
.description('Generate JSON and AsciiDoc documentation for Redpanda metrics')
|
|
418
|
-
.requiredOption('-t, --tag <tag>',
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
433
|
+
.requiredOption('-t, --tag <tag>', 'Redpanda version to use when starting Redpanda in Docker')
|
|
434
|
+
.option(
|
|
435
|
+
'--docker-repo <repo>',
|
|
436
|
+
'Docker repository to use when starting Redpanda in Docker',
|
|
437
|
+
commonOptions.dockerRepo
|
|
438
|
+
)
|
|
439
|
+
.option(
|
|
440
|
+
'--console-tag <tag>',
|
|
441
|
+
'Redpanda Console version to use when starting Redpanda Console in Docker',
|
|
442
|
+
commonOptions.consoleTag
|
|
443
|
+
)
|
|
444
|
+
.option(
|
|
445
|
+
'--console-docker-repo <repo>',
|
|
446
|
+
'Docker repository to use when starting Redpanda Console in Docker',
|
|
447
|
+
commonOptions.consoleDockerRepo
|
|
448
|
+
)
|
|
423
449
|
.option('--diff <oldTag>', 'Also diff autogenerated metrics from <oldTag> → <tag>')
|
|
424
450
|
.action((options) => {
|
|
425
451
|
verifyMetricsDependencies();
|
|
@@ -445,6 +471,198 @@ automation
|
|
|
445
471
|
process.exit(0);
|
|
446
472
|
});
|
|
447
473
|
|
|
474
|
+
automation
|
|
475
|
+
.command('rpcn-connector-docs')
|
|
476
|
+
.description('Generate RPCN connector docs and diff changes since the last version')
|
|
477
|
+
.option('-d, --data-dir <path>', 'Directory where versioned connect JSON files live', path.resolve(process.cwd(), 'docs-data'))
|
|
478
|
+
.option('--old-data <path>', 'Optional override for old data file (for diff)')
|
|
479
|
+
.option('-f, --fetch-connectors', 'Fetch latest connector data using rpk')
|
|
480
|
+
.option('-m, --draft-missing', 'Generate full-doc drafts for connectors missing in output')
|
|
481
|
+
.option('--csv <path>', 'Path to connector metadata CSV file', 'internal/plugins/info.csv')
|
|
482
|
+
.option('--template-main <path>', 'Main Handlebars template', path.resolve(__dirname, '../tools/redpanda-connect/templates/connector.hbs'))
|
|
483
|
+
.option('--template-intro <path>', 'Intro section partial template', path.resolve(__dirname, '../tools/redpanda-connect/templates/intro.hbs'))
|
|
484
|
+
.option('--template-fields <path>', 'Fields section partial template', path.resolve(__dirname, '../tools/redpanda-connect/templates/fields-partials.hbs'))
|
|
485
|
+
.option('--template-examples <path>', 'Examples section partial template', path.resolve(__dirname, '../tools/redpanda-connect/templates/examples-partials.hbs'))
|
|
486
|
+
.option('--overrides <path>', 'Optional JSON file with overrides')
|
|
487
|
+
.action(async (options) => {
|
|
488
|
+
let success = true;
|
|
489
|
+
const dataDir = path.resolve(process.cwd(), options.dataDir);
|
|
490
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
491
|
+
|
|
492
|
+
const timestamp = new Date().toISOString();
|
|
493
|
+
|
|
494
|
+
let newVersion;
|
|
495
|
+
let dataFile;
|
|
496
|
+
if (options.fetchConnectors) {
|
|
497
|
+
try {
|
|
498
|
+
execSync('rpk --version', { stdio: 'ignore' });
|
|
499
|
+
newVersion = getRpkConnectVersion();
|
|
500
|
+
const tmpFile = path.join(dataDir, `connect-${newVersion}.tmp.json`);
|
|
501
|
+
const finalFile = path.join(dataDir, `connect-${newVersion}.json`);
|
|
502
|
+
|
|
503
|
+
const fd = fs.openSync(tmpFile, 'w');
|
|
504
|
+
const r = spawnSync('rpk', ['connect', 'list', '--format', 'json-full'], { stdio: ['ignore', fd, 'inherit'] });
|
|
505
|
+
fs.closeSync(fd);
|
|
506
|
+
|
|
507
|
+
const rawJson = fs.readFileSync(tmpFile, 'utf8');
|
|
508
|
+
const parsed = JSON.parse(rawJson);
|
|
509
|
+
fs.writeFileSync(finalFile, JSON.stringify(parsed, null, 2));
|
|
510
|
+
fs.unlinkSync(tmpFile);
|
|
511
|
+
dataFile = finalFile;
|
|
512
|
+
console.log(`✅ Fetched and saved: ${finalFile}`);
|
|
513
|
+
} catch (err) {
|
|
514
|
+
console.error(`❌ Failed to fetch connectors: ${err.message}`);
|
|
515
|
+
success = false;
|
|
516
|
+
}
|
|
517
|
+
} else {
|
|
518
|
+
const candidates = fs.readdirSync(dataDir).filter(f => /^connect-\d+\.\d+\.\d+\.json$/.test(f));
|
|
519
|
+
if (candidates.length === 0) {
|
|
520
|
+
console.error('❌ No connect-<version>.json found. Use --fetch-connectors.');
|
|
521
|
+
process.exit(1);
|
|
522
|
+
}
|
|
523
|
+
candidates.sort();
|
|
524
|
+
dataFile = path.join(dataDir, candidates[candidates.length - 1]);
|
|
525
|
+
newVersion = candidates[candidates.length - 1].match(/connect-(\d+\.\d+\.\d+)\.json/)[1];
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
console.log('⏳ Generating connector partials...');
|
|
529
|
+
let partialsWritten, partialFiles, draftsWritten, draftFiles;
|
|
530
|
+
|
|
531
|
+
try {
|
|
532
|
+
const result = await generateRpcnConnectorDocs({
|
|
533
|
+
data: dataFile,
|
|
534
|
+
overrides: options.overrides,
|
|
535
|
+
template: options.templateMain,
|
|
536
|
+
templateIntro: options.templateIntro,
|
|
537
|
+
templateFields: options.templateFields,
|
|
538
|
+
templateExamples: options.templateExamples,
|
|
539
|
+
writeFullDrafts: false
|
|
540
|
+
});
|
|
541
|
+
partialsWritten = result.partialsWritten;
|
|
542
|
+
partialFiles = result.partialFiles;
|
|
543
|
+
} catch (err) {
|
|
544
|
+
console.error(`❌ Failed to generate partials: ${err.message}`);
|
|
545
|
+
success = false;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (options.draftMissing) {
|
|
549
|
+
console.log('⏳ Drafting missing connectors...');
|
|
550
|
+
try {
|
|
551
|
+
const connectorList = await parseCSVConnectors(options.csv, console);
|
|
552
|
+
const validConnectors = connectorList.filter(row => row.name && row.type);
|
|
553
|
+
|
|
554
|
+
const pagesRoot = path.resolve(process.cwd(), 'modules/components/pages');
|
|
555
|
+
const allMissing = validConnectors.filter(({ name, type }) => {
|
|
556
|
+
if (!name || !type) {
|
|
557
|
+
console.warn(`⚠️ Skipping invalid connector entry:`, { name, type });
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
560
|
+
const expected = path.join(pagesRoot, `${type}s`, `${name}.adoc`);
|
|
561
|
+
return !fs.existsSync(expected);
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
const missingConnectors = allMissing.filter(({ name }) => !name.includes('sql_driver'));
|
|
565
|
+
|
|
566
|
+
if (missingConnectors.length === 0) {
|
|
567
|
+
console.log('✅ All connectors (excluding sql_drivers) already have docs—nothing to draft.');
|
|
568
|
+
} else {
|
|
569
|
+
console.log(`⏳ Docs missing for ${missingConnectors.length} connectors:`);
|
|
570
|
+
missingConnectors.forEach(({ name, type }) => {
|
|
571
|
+
console.log(` • ${type}/${name}`);
|
|
572
|
+
});
|
|
573
|
+
console.log('');
|
|
574
|
+
|
|
575
|
+
const rawData = fs.readFileSync(dataFile, 'utf8');
|
|
576
|
+
const dataObj = JSON.parse(rawData);
|
|
577
|
+
|
|
578
|
+
const filteredDataObj = {};
|
|
579
|
+
for (const [key, arr] of Object.entries(dataObj)) {
|
|
580
|
+
if (!Array.isArray(arr)) {
|
|
581
|
+
filteredDataObj[key] = arr;
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
filteredDataObj[key] = arr.filter(component =>
|
|
585
|
+
missingConnectors.some(m => m.name === component.name && `${m.type}s` === key)
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const tempDataPath = path.join(dataDir, '._filtered_connect_data.json');
|
|
590
|
+
fs.writeFileSync(tempDataPath, JSON.stringify(filteredDataObj, null, 2), 'utf8');
|
|
591
|
+
|
|
592
|
+
const draftResult = await generateRpcnConnectorDocs({
|
|
593
|
+
data: tempDataPath,
|
|
594
|
+
overrides: options.overrides,
|
|
595
|
+
template: options.templateMain,
|
|
596
|
+
templateFields: options.templateFields,
|
|
597
|
+
templateExamples: options.templateExamples,
|
|
598
|
+
templateIntro: options.templateIntro,
|
|
599
|
+
writeFullDrafts: true
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
fs.unlinkSync(tempDataPath);
|
|
603
|
+
draftsWritten = draftResult.draftsWritten;
|
|
604
|
+
draftFiles = draftResult.draftFiles;
|
|
605
|
+
}
|
|
606
|
+
} catch (err) {
|
|
607
|
+
console.error(`❌ Could not draft missing: ${err.message}`);
|
|
608
|
+
success = false;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
let oldIndex = {};
|
|
613
|
+
if (options.oldData && fs.existsSync(options.oldData)) {
|
|
614
|
+
oldIndex = JSON.parse(fs.readFileSync(options.oldData, 'utf8'));
|
|
615
|
+
} else {
|
|
616
|
+
const oldVersion = getAntoraValue('asciidoc.attributes.latest-connect-version');
|
|
617
|
+
if (oldVersion) {
|
|
618
|
+
const oldPath = path.join(dataDir, `connect-${oldVersion}.json`);
|
|
619
|
+
if (fs.existsSync(oldPath)) {
|
|
620
|
+
oldIndex = JSON.parse(fs.readFileSync(oldPath, 'utf8'));
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const newIndex = JSON.parse(fs.readFileSync(dataFile, 'utf8'));
|
|
626
|
+
printDeltaReport(oldIndex, newIndex);
|
|
627
|
+
|
|
628
|
+
function logCollapsed(label, filesArray, maxToShow = 10) {
|
|
629
|
+
console.log(` • ${label}: ${filesArray.length} total`);
|
|
630
|
+
const sample = filesArray.slice(0, maxToShow);
|
|
631
|
+
sample.forEach(fp => console.log(` – ${fp}`));
|
|
632
|
+
const remaining = filesArray.length - sample.length;
|
|
633
|
+
if (remaining > 0) {
|
|
634
|
+
console.log(` … plus ${remaining} more`);
|
|
635
|
+
}
|
|
636
|
+
console.log('');
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const wrote = setAntoraValue('asciidoc.attributes.latest-connect-version', newVersion);
|
|
640
|
+
if (wrote) {
|
|
641
|
+
console.log(`✅ Updated Antora version: ${newVersion}`);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
console.log('📊 Generation Report:');
|
|
645
|
+
console.log(` • Partial files: ${partialsWritten}`);
|
|
646
|
+
// Split “partials” into fields vs examples by checking the path substring.
|
|
647
|
+
const fieldsPartials = partialFiles.filter(fp => fp.includes('/fields/'));
|
|
648
|
+
const examplesPartials = partialFiles.filter(fp => fp.includes('/examples/'));
|
|
649
|
+
|
|
650
|
+
// Show only up to 10 of each
|
|
651
|
+
logCollapsed('Fields partials', fieldsPartials, 10);
|
|
652
|
+
logCollapsed('Examples partials', examplesPartials, 10);
|
|
653
|
+
|
|
654
|
+
if (options.draftMissing) {
|
|
655
|
+
console.log(` • Full drafts: ${draftsWritten}`);
|
|
656
|
+
logCollapsed('Draft files', draftFiles, 5);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
console.log('\n📄 Summary:');
|
|
660
|
+
console.log(` • Run time: ${timestamp}`);
|
|
661
|
+
console.log(` • Version used: ${newVersion}`);
|
|
662
|
+
|
|
663
|
+
process.exit(success ? 0 : 1);
|
|
664
|
+
});
|
|
665
|
+
|
|
448
666
|
automation
|
|
449
667
|
.command('property-docs')
|
|
450
668
|
.description('Generate JSON and AsciiDoc documentation for Redpanda configuration properties')
|
|
@@ -455,11 +673,14 @@ automation
|
|
|
455
673
|
|
|
456
674
|
const newTag = options.tag;
|
|
457
675
|
const oldTag = options.diff;
|
|
458
|
-
const cwd
|
|
459
|
-
const make
|
|
676
|
+
const cwd = path.resolve(__dirname, '../tools/property-extractor');
|
|
677
|
+
const make = (tag) => {
|
|
460
678
|
console.log(`⏳ Building property docs for ${tag}…`);
|
|
461
679
|
const r = spawnSync('make', ['build', `TAG=${tag}`], { cwd, stdio: 'inherit' });
|
|
462
|
-
if (r.error
|
|
680
|
+
if (r.error) {
|
|
681
|
+
console.error(`❌ ${r.error.message}`);
|
|
682
|
+
process.exit(1);
|
|
683
|
+
}
|
|
463
684
|
if (r.status !== 0) process.exit(r.status);
|
|
464
685
|
};
|
|
465
686
|
|
|
@@ -480,11 +701,22 @@ automation
|
|
|
480
701
|
automation
|
|
481
702
|
.command('rpk-docs')
|
|
482
703
|
.description('Generate AsciiDoc documentation for rpk CLI commands')
|
|
483
|
-
.requiredOption('-t, --tag <tag>',
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
704
|
+
.requiredOption('-t, --tag <tag>', 'Redpanda version to use when starting Redpanda in Docker')
|
|
705
|
+
.option(
|
|
706
|
+
'--docker-repo <repo>',
|
|
707
|
+
'Docker repository to use when starting Redpanda in Docker',
|
|
708
|
+
commonOptions.dockerRepo
|
|
709
|
+
)
|
|
710
|
+
.option(
|
|
711
|
+
'--console-tag <tag>',
|
|
712
|
+
'Redpanda Console version to use when starting Redpanda Console in Docker',
|
|
713
|
+
commonOptions.consoleTag
|
|
714
|
+
)
|
|
715
|
+
.option(
|
|
716
|
+
'--console-docker-repo <repo>',
|
|
717
|
+
'Docker repository to use when starting Redpanda Console in Docker',
|
|
718
|
+
commonOptions.consoleDockerRepo
|
|
719
|
+
)
|
|
488
720
|
.option('--diff <oldTag>', 'Also diff autogenerated rpk docs from <oldTag> → <tag>')
|
|
489
721
|
.action((options) => {
|
|
490
722
|
verifyMetricsDependencies();
|
|
@@ -512,181 +744,163 @@ automation
|
|
|
512
744
|
|
|
513
745
|
automation
|
|
514
746
|
.command('helm-spec')
|
|
515
|
-
.description(
|
|
747
|
+
.description(
|
|
748
|
+
`Generate AsciiDoc documentation for one or more Helm charts (supports local dirs or GitHub URLs)`
|
|
749
|
+
)
|
|
516
750
|
.option(
|
|
517
751
|
'--chart-dir <dir|url>',
|
|
518
752
|
'Chart directory (contains Chart.yaml) or a root containing multiple charts, or a GitHub URL',
|
|
519
753
|
'https://github.com/redpanda-data/redpanda-operator/charts'
|
|
520
754
|
)
|
|
521
|
-
.requiredOption('-t, --tag <tag>',
|
|
522
|
-
|
|
523
|
-
.option(
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
)
|
|
528
|
-
.option(
|
|
529
|
-
'--output-dir <dir>',
|
|
530
|
-
'Where to write all generated AsciiDoc files',
|
|
531
|
-
'modules/reference/pages'
|
|
532
|
-
)
|
|
533
|
-
.option(
|
|
534
|
-
'--output-suffix <suffix>',
|
|
535
|
-
'Suffix to append to each chart name (including extension)',
|
|
536
|
-
'-helm-spec.adoc'
|
|
537
|
-
)
|
|
538
|
-
.action(opts => {
|
|
539
|
-
verifyHelmDependencies()
|
|
755
|
+
.requiredOption('-t, --tag <tag>', 'Branch or tag to clone when using a GitHub URL for the chart-dir')
|
|
756
|
+
.option('--readme <file>', 'Relative README.md path inside each chart dir', 'README.md')
|
|
757
|
+
.option('--output-dir <dir>', 'Where to write all generated AsciiDoc files', 'modules/reference/pages')
|
|
758
|
+
.option('--output-suffix <suffix>', 'Suffix to append to each chart name (including extension)', '-helm-spec.adoc')
|
|
759
|
+
.action((opts) => {
|
|
760
|
+
verifyHelmDependencies();
|
|
540
761
|
|
|
541
|
-
// Prepare chart-root (local or GitHub)
|
|
542
|
-
let root = opts.chartDir
|
|
543
|
-
let tmpClone = null
|
|
762
|
+
// Prepare chart-root (local or GitHub)
|
|
763
|
+
let root = opts.chartDir;
|
|
764
|
+
let tmpClone = null;
|
|
544
765
|
|
|
545
766
|
if (/^https?:\/\/github\.com\//.test(root)) {
|
|
546
767
|
if (!opts.tag) {
|
|
547
|
-
console.error('❌ When using a GitHub URL you must pass --tag')
|
|
548
|
-
process.exit(1)
|
|
768
|
+
console.error('❌ When using a GitHub URL you must pass --tag');
|
|
769
|
+
process.exit(1);
|
|
549
770
|
}
|
|
550
|
-
const u
|
|
551
|
-
const parts = u.pathname.replace(/\.git$/, '').split('/').filter(Boolean)
|
|
771
|
+
const u = new URL(root);
|
|
772
|
+
const parts = u.pathname.replace(/\.git$/, '').split('/').filter(Boolean);
|
|
552
773
|
if (parts.length < 2) {
|
|
553
|
-
console.error(`❌ Invalid GitHub URL: ${root}`)
|
|
554
|
-
process.exit(1)
|
|
774
|
+
console.error(`❌ Invalid GitHub URL: ${root}`);
|
|
775
|
+
process.exit(1);
|
|
555
776
|
}
|
|
556
|
-
const [owner, repo, ...sub] = parts
|
|
557
|
-
const repoUrl = `https://${u.host}/${owner}/${repo}.git
|
|
558
|
-
const ref
|
|
559
|
-
|
|
560
|
-
console.log(
|
|
561
|
-
const ok =
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
777
|
+
const [owner, repo, ...sub] = parts;
|
|
778
|
+
const repoUrl = `https://${u.host}/${owner}/${repo}.git`;
|
|
779
|
+
const ref = opts.tag;
|
|
780
|
+
|
|
781
|
+
console.log(`⏳ Verifying ${repoUrl}@${ref}…`);
|
|
782
|
+
const ok =
|
|
783
|
+
spawnSync(
|
|
784
|
+
'git',
|
|
785
|
+
['ls-remote', '--exit-code', repoUrl, `refs/heads/${ref}`, `refs/tags/${ref}`],
|
|
786
|
+
{ stdio: 'ignore' }
|
|
787
|
+
).status === 0;
|
|
565
788
|
if (!ok) {
|
|
566
|
-
console.error(`❌ ${ref} not found on ${repoUrl}`)
|
|
567
|
-
process.exit(1)
|
|
789
|
+
console.error(`❌ ${ref} not found on ${repoUrl}`);
|
|
790
|
+
process.exit(1);
|
|
568
791
|
}
|
|
569
792
|
|
|
570
|
-
tmpClone = fs.mkdtempSync(path.join(os.tmpdir(), 'helm-'))
|
|
571
|
-
console.log(`⏳ Cloning ${repoUrl}@${ref} → ${tmpClone}`)
|
|
572
|
-
if (
|
|
573
|
-
'clone','--depth','1','--branch',ref,
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
793
|
+
tmpClone = fs.mkdtempSync(path.join(os.tmpdir(), 'helm-'));
|
|
794
|
+
console.log(`⏳ Cloning ${repoUrl}@${ref} → ${tmpClone}`);
|
|
795
|
+
if (
|
|
796
|
+
spawnSync('git', ['clone', '--depth', '1', '--branch', ref, repoUrl, tmpClone], {
|
|
797
|
+
stdio: 'inherit',
|
|
798
|
+
}).status !== 0
|
|
799
|
+
) {
|
|
800
|
+
console.error('❌ git clone failed');
|
|
801
|
+
process.exit(1);
|
|
578
802
|
}
|
|
579
|
-
root = sub.length ? path.join(tmpClone, sub.join('/')) : tmpClone
|
|
803
|
+
root = sub.length ? path.join(tmpClone, sub.join('/')) : tmpClone;
|
|
580
804
|
}
|
|
581
805
|
|
|
582
|
-
// Discover charts
|
|
806
|
+
// Discover charts
|
|
583
807
|
if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) {
|
|
584
|
-
console.error(`❌ Chart root not found: ${root}`)
|
|
585
|
-
process.exit(1)
|
|
808
|
+
console.error(`❌ Chart root not found: ${root}`);
|
|
809
|
+
process.exit(1);
|
|
586
810
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
charts = [root]
|
|
811
|
+
let charts = [];
|
|
812
|
+
if (fs.existsSync(path.join(root, 'Chart.yaml'))) {
|
|
813
|
+
charts = [root];
|
|
591
814
|
} else {
|
|
592
|
-
charts = fs
|
|
593
|
-
.
|
|
594
|
-
.
|
|
815
|
+
charts = fs
|
|
816
|
+
.readdirSync(root)
|
|
817
|
+
.map((n) => path.join(root, n))
|
|
818
|
+
.filter((p) => fs.existsSync(path.join(p, 'Chart.yaml')));
|
|
595
819
|
}
|
|
596
820
|
if (charts.length === 0) {
|
|
597
|
-
console.error(`❌ No charts found under: ${root}`)
|
|
598
|
-
process.exit(1)
|
|
821
|
+
console.error(`❌ No charts found under: ${root}`);
|
|
822
|
+
process.exit(1);
|
|
599
823
|
}
|
|
600
824
|
|
|
601
|
-
// Ensure output-dir exists
|
|
602
|
-
const outDir = path.resolve(opts.outputDir)
|
|
603
|
-
fs.mkdirSync(outDir, { recursive: true })
|
|
825
|
+
// Ensure output-dir exists
|
|
826
|
+
const outDir = path.resolve(opts.outputDir);
|
|
827
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
604
828
|
|
|
605
|
-
// Process each chart
|
|
829
|
+
// Process each chart
|
|
606
830
|
for (const chartPath of charts) {
|
|
607
|
-
const name = path.basename(chartPath)
|
|
608
|
-
console.log(
|
|
831
|
+
const name = path.basename(chartPath);
|
|
832
|
+
console.log(`⏳ Processing chart "${name}"…`);
|
|
609
833
|
|
|
610
834
|
// Regenerate README.md
|
|
611
|
-
console.log(
|
|
612
|
-
let r = spawnSync('helm-docs', { cwd: chartPath, stdio: 'inherit' })
|
|
613
|
-
if (r.status !== 0) process.exit(r.status)
|
|
835
|
+
console.log(`⏳ helm-docs in ${chartPath}`);
|
|
836
|
+
let r = spawnSync('helm-docs', { cwd: chartPath, stdio: 'inherit' });
|
|
837
|
+
if (r.status !== 0) process.exit(r.status);
|
|
614
838
|
|
|
615
839
|
// Convert Markdown → AsciiDoc
|
|
616
|
-
const md = path.join(chartPath, opts.readme)
|
|
840
|
+
const md = path.join(chartPath, opts.readme);
|
|
617
841
|
if (!fs.existsSync(md)) {
|
|
618
|
-
console.error(`❌ README not found: ${md}`)
|
|
619
|
-
process.exit(1)
|
|
842
|
+
console.error(`❌ README not found: ${md}`);
|
|
843
|
+
process.exit(1);
|
|
620
844
|
}
|
|
621
|
-
const outFile = path.join(outDir, `k-${name}${opts.outputSuffix}`)
|
|
622
|
-
console.log(
|
|
623
|
-
fs.mkdirSync(path.dirname(outFile), { recursive: true })
|
|
624
|
-
r = spawnSync('pandoc', [
|
|
625
|
-
if (r.status !== 0) process.exit(r.status)
|
|
845
|
+
const outFile = path.join(outDir, `k-${name}${opts.outputSuffix}`);
|
|
846
|
+
console.log(`⏳ pandoc ${md} → ${outFile}`);
|
|
847
|
+
fs.mkdirSync(path.dirname(outFile), { recursive: true });
|
|
848
|
+
r = spawnSync('pandoc', [md, '-t', 'asciidoc', '-o', outFile], { stdio: 'inherit' });
|
|
849
|
+
if (r.status !== 0) process.exit(r.status);
|
|
626
850
|
|
|
627
851
|
// Post-process tweaks
|
|
628
|
-
let doc = fs.readFileSync(outFile, 'utf8')
|
|
852
|
+
let doc = fs.readFileSync(outFile, 'utf8');
|
|
629
853
|
const xrefRe = /https:\/\/docs\.redpanda\.com[^\s\]\[\)"]+(?:\[[^\]]*\])?/g;
|
|
630
854
|
doc = doc
|
|
631
855
|
.replace(/(\[\d+\])\]\./g, '$1\\].')
|
|
632
856
|
.replace(/^== # (.*)$/gm, '= $1')
|
|
633
857
|
.replace(/^== description: (.*)$/gm, ':description: $1')
|
|
634
|
-
.replace(xrefRe, match => {
|
|
635
|
-
// split off any [bracketed text]
|
|
858
|
+
.replace(xrefRe, (match) => {
|
|
636
859
|
let urlPart = match;
|
|
637
860
|
let bracketPart = '';
|
|
638
861
|
const m = match.match(/^([^\[]+)(\[[^\]]*\])$/);
|
|
639
862
|
if (m) {
|
|
640
|
-
urlPart = m[1];
|
|
641
|
-
bracketPart = m[2];
|
|
863
|
+
urlPart = m[1];
|
|
864
|
+
bracketPart = m[2];
|
|
642
865
|
}
|
|
643
|
-
// if it ends in “#” we leave it alone
|
|
644
866
|
if (urlPart.endsWith('#')) {
|
|
645
867
|
return match;
|
|
646
868
|
}
|
|
647
869
|
try {
|
|
648
870
|
const xref = urlToXref(urlPart);
|
|
649
|
-
|
|
650
|
-
return bracketPart
|
|
651
|
-
? `${xref}${bracketPart}`
|
|
652
|
-
: `${xref}[]`;
|
|
871
|
+
return bracketPart ? `${xref}${bracketPart}` : `${xref}[]`;
|
|
653
872
|
} catch (err) {
|
|
654
873
|
console.warn(`⚠️ urlToXref failed on ${urlPart}: ${err.message}`);
|
|
655
874
|
return match;
|
|
656
875
|
}
|
|
657
876
|
});
|
|
658
|
-
fs.writeFileSync(outFile, doc, 'utf8')
|
|
877
|
+
fs.writeFileSync(outFile, doc, 'utf8');
|
|
659
878
|
|
|
660
|
-
console.log(`✅ Wrote ${outFile}`)
|
|
879
|
+
console.log(`✅ Wrote ${outFile}`);
|
|
661
880
|
}
|
|
662
881
|
|
|
663
|
-
// Cleanup
|
|
664
|
-
if (tmpClone) fs.rmSync(tmpClone, { recursive: true, force: true })
|
|
665
|
-
})
|
|
882
|
+
// Cleanup
|
|
883
|
+
if (tmpClone) fs.rmSync(tmpClone, { recursive: true, force: true });
|
|
884
|
+
});
|
|
666
885
|
|
|
667
886
|
automation
|
|
668
887
|
.command('crd-spec')
|
|
669
888
|
.description('Generate Asciidoc documentation for Kubernetes CRD references')
|
|
670
|
-
.requiredOption('-t, --tag <operatorTag>',
|
|
671
|
-
|
|
672
|
-
|
|
889
|
+
.requiredOption('-t, --tag <operatorTag>', 'Operator release tag or branch, such as operator/v25.1.2')
|
|
890
|
+
.option(
|
|
891
|
+
'-s, --source-path <src>',
|
|
673
892
|
'CRD Go types dir or GitHub URL',
|
|
674
|
-
'https://github.com/redpanda-data/redpanda-operator/operator/api/redpanda/v1alpha2'
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
.option('--
|
|
679
|
-
|
|
680
|
-
'.github/crd-config/templates/asciidoctor/operator')
|
|
681
|
-
.option('--output <file>',
|
|
682
|
-
'Where to write the generated AsciiDoc file',
|
|
683
|
-
'modules/reference/pages/k-crd.adoc')
|
|
684
|
-
.action(async opts => {
|
|
893
|
+
'https://github.com/redpanda-data/redpanda-operator/operator/api/redpanda/v1alpha2'
|
|
894
|
+
)
|
|
895
|
+
.option('-d, --depth <n>', 'How many levels deep', '10')
|
|
896
|
+
.option('--templates-dir <dir>', 'Asciidoctor templates dir', '.github/crd-config/templates/asciidoctor/operator')
|
|
897
|
+
.option('--output <file>', 'Where to write the generated AsciiDoc file', 'modules/reference/pages/k-crd.adoc')
|
|
898
|
+
.action(async (opts) => {
|
|
685
899
|
verifyCrdDependencies();
|
|
686
900
|
|
|
687
|
-
// Fetch upstream config
|
|
901
|
+
// Fetch upstream config
|
|
688
902
|
const configTmp = fs.mkdtempSync(path.join(os.tmpdir(), 'crd-config-'));
|
|
689
|
-
console.log(
|
|
903
|
+
console.log(`⏳ Fetching crd-ref-docs-config.yaml from redpanda-operator@main…`);
|
|
690
904
|
await fetchFromGithub(
|
|
691
905
|
'redpanda-data',
|
|
692
906
|
'redpanda-operator',
|
|
@@ -696,36 +910,37 @@ automation
|
|
|
696
910
|
);
|
|
697
911
|
const configPath = path.join(configTmp, 'crd-ref-docs-config.yaml');
|
|
698
912
|
|
|
699
|
-
// Detect docs repo context
|
|
913
|
+
// Detect docs repo context
|
|
700
914
|
const repoRoot = findRepoRoot();
|
|
701
|
-
const pkg
|
|
702
|
-
const inDocs
|
|
703
|
-
|
|
915
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
|
|
916
|
+
const inDocs =
|
|
917
|
+
pkg.name === 'redpanda-docs-playbook' ||
|
|
918
|
+
(pkg.repository && pkg.repository.url.includes('redpanda-data/docs'));
|
|
704
919
|
let docsBranch = null;
|
|
705
920
|
|
|
706
921
|
if (!inDocs) {
|
|
707
|
-
console.warn('⚠️
|
|
922
|
+
console.warn('⚠️ Not inside redpanda-data/docs; skipping branch suggestion.');
|
|
708
923
|
} else {
|
|
709
924
|
try {
|
|
710
925
|
docsBranch = await determineDocsBranch(opts.tag);
|
|
711
|
-
console.log(
|
|
926
|
+
console.log(`✅ Detected docs repo; you should commit to branch '${docsBranch}'.`);
|
|
712
927
|
} catch (err) {
|
|
713
928
|
console.error(`❌ Unable to determine docs branch: ${err.message}`);
|
|
714
929
|
process.exit(1);
|
|
715
930
|
}
|
|
716
931
|
}
|
|
717
932
|
|
|
718
|
-
// Validate templates
|
|
933
|
+
// Validate templates
|
|
719
934
|
if (!fs.existsSync(opts.templatesDir)) {
|
|
720
935
|
console.error(`❌ Templates directory not found: ${opts.templatesDir}`);
|
|
721
936
|
process.exit(1);
|
|
722
937
|
}
|
|
723
938
|
|
|
724
|
-
// Prepare source (local folder or GitHub URL)
|
|
939
|
+
// Prepare source (local folder or GitHub URL)
|
|
725
940
|
let localSrc = opts.sourcePath;
|
|
726
941
|
let tmpSrc;
|
|
727
942
|
if (/^https?:\/\/github\.com\//.test(opts.sourcePath)) {
|
|
728
|
-
const u
|
|
943
|
+
const u = new URL(opts.sourcePath);
|
|
729
944
|
const parts = u.pathname.split('/').filter(Boolean);
|
|
730
945
|
if (parts.length < 2) {
|
|
731
946
|
console.error(`❌ Invalid GitHub URL: ${opts.sourcePath}`);
|
|
@@ -734,97 +949,91 @@ automation
|
|
|
734
949
|
const [owner, repo, ...subpathParts] = parts;
|
|
735
950
|
const repoUrl = `https://${u.host}/${owner}/${repo}`;
|
|
736
951
|
const subpath = subpathParts.join('/');
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
], { stdio:'ignore' }).status === 0;
|
|
952
|
+
console.log(`⏳ Verifying "${opts.tag}" in ${repoUrl}…`);
|
|
953
|
+
const ok =
|
|
954
|
+
spawnSync('git', ['ls-remote', '--exit-code', repoUrl, `refs/tags/${opts.tag}`, `refs/heads/${opts.tag}`], {
|
|
955
|
+
stdio: 'ignore',
|
|
956
|
+
}).status === 0;
|
|
743
957
|
if (!ok) {
|
|
744
958
|
console.error(`❌ Tag or branch "${opts.tag}" not found on ${repoUrl}`);
|
|
745
959
|
process.exit(1);
|
|
746
960
|
}
|
|
747
|
-
// Clone
|
|
748
961
|
tmpSrc = fs.mkdtempSync(path.join(os.tmpdir(), 'crd-src-'));
|
|
749
962
|
console.log(`⏳ Cloning ${repoUrl}@${opts.tag} → ${tmpSrc}`);
|
|
750
|
-
if (
|
|
751
|
-
|
|
963
|
+
if (
|
|
964
|
+
spawnSync('git', ['clone', '--depth', '1', '--branch', opts.tag, repoUrl, tmpSrc], {
|
|
965
|
+
stdio: 'inherit',
|
|
966
|
+
}).status !== 0
|
|
967
|
+
) {
|
|
968
|
+
console.error(`❌ git clone failed`);
|
|
969
|
+
process.exit(1);
|
|
752
970
|
}
|
|
753
|
-
// Point at subfolder if any
|
|
754
971
|
localSrc = subpath ? path.join(tmpSrc, subpath) : tmpSrc;
|
|
755
972
|
if (!fs.existsSync(localSrc)) {
|
|
756
|
-
console.error(`❌ Subdirectory not found in repo: ${subpath}`);
|
|
973
|
+
console.error(`❌ Subdirectory not found in repo: ${subpath}`);
|
|
974
|
+
process.exit(1);
|
|
757
975
|
}
|
|
758
976
|
}
|
|
759
977
|
|
|
760
|
-
// Ensure output directory exists
|
|
978
|
+
// Ensure output directory exists
|
|
761
979
|
const outputDir = path.dirname(opts.output);
|
|
762
980
|
if (!fs.existsSync(outputDir)) {
|
|
763
981
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
764
982
|
}
|
|
765
983
|
|
|
766
|
-
// Run crd-ref-docs
|
|
984
|
+
// Run crd-ref-docs
|
|
767
985
|
const args = [
|
|
768
|
-
'--source-path',
|
|
769
|
-
|
|
770
|
-
'--
|
|
771
|
-
|
|
772
|
-
'--
|
|
773
|
-
|
|
986
|
+
'--source-path',
|
|
987
|
+
localSrc,
|
|
988
|
+
'--max-depth',
|
|
989
|
+
opts.depth,
|
|
990
|
+
'--templates-dir',
|
|
991
|
+
opts.templatesDir,
|
|
992
|
+
'--config',
|
|
993
|
+
configPath,
|
|
994
|
+
'--renderer',
|
|
995
|
+
'asciidoctor',
|
|
996
|
+
'--output-path',
|
|
997
|
+
opts.output,
|
|
774
998
|
];
|
|
775
999
|
console.log(`⏳ Running crd-ref-docs ${args.join(' ')}`);
|
|
776
|
-
if (spawnSync('crd-ref-docs', args, { stdio:'inherit' }).status !== 0) {
|
|
777
|
-
console.error(
|
|
1000
|
+
if (spawnSync('crd-ref-docs', args, { stdio: 'inherit' }).status !== 0) {
|
|
1001
|
+
console.error(`❌ crd-ref-docs failed`);
|
|
1002
|
+
process.exit(1);
|
|
778
1003
|
}
|
|
779
1004
|
|
|
780
|
-
let doc = fs.readFileSync(opts.output, 'utf8')
|
|
781
|
-
// this regex matches:
|
|
782
|
-
// - a docs.redpanda.com URL
|
|
783
|
-
// - optionally followed immediately by [some text]
|
|
784
|
-
// It will not include trailing ] in the URL match itself.
|
|
1005
|
+
let doc = fs.readFileSync(opts.output, 'utf8');
|
|
785
1006
|
const xrefRe = /https:\/\/docs\.redpanda\.com[^\s\]\[\)"]+(?:\[[^\]]*\])?/g;
|
|
786
|
-
|
|
787
|
-
doc = doc.replace(xrefRe, match => {
|
|
788
|
-
// split off any [bracketed text]
|
|
1007
|
+
doc = doc.replace(xrefRe, (match) => {
|
|
789
1008
|
let urlPart = match;
|
|
790
1009
|
let bracketPart = '';
|
|
791
1010
|
const m = match.match(/^([^\[]+)(\[[^\]]*\])$/);
|
|
792
1011
|
if (m) {
|
|
793
|
-
urlPart = m[1];
|
|
794
|
-
bracketPart = m[2];
|
|
1012
|
+
urlPart = m[1];
|
|
1013
|
+
bracketPart = m[2];
|
|
795
1014
|
}
|
|
796
|
-
|
|
797
|
-
// if it ends in “#” we leave it alone
|
|
798
1015
|
if (urlPart.endsWith('#')) {
|
|
799
1016
|
return match;
|
|
800
1017
|
}
|
|
801
|
-
|
|
802
1018
|
try {
|
|
803
1019
|
const xref = urlToXref(urlPart);
|
|
804
|
-
|
|
805
|
-
return bracketPart
|
|
806
|
-
? `${xref}${bracketPart}`
|
|
807
|
-
: `${xref}[]`;
|
|
1020
|
+
return bracketPart ? `${xref}${bracketPart}` : `${xref}[]`;
|
|
808
1021
|
} catch (err) {
|
|
809
1022
|
console.warn(`⚠️ urlToXref failed on ${urlPart}: ${err.message}`);
|
|
810
1023
|
return match;
|
|
811
1024
|
}
|
|
812
1025
|
});
|
|
813
|
-
fs.writeFileSync(opts.output, doc, 'utf8')
|
|
1026
|
+
fs.writeFileSync(opts.output, doc, 'utf8');
|
|
814
1027
|
|
|
815
|
-
// Cleanup
|
|
816
|
-
if (tmpSrc)
|
|
1028
|
+
// Cleanup
|
|
1029
|
+
if (tmpSrc) fs.rmSync(tmpSrc, { recursive: true, force: true });
|
|
817
1030
|
fs.rmSync(configTmp, { recursive: true, force: true });
|
|
818
1031
|
|
|
819
1032
|
console.log(`✅ CRD docs generated at ${opts.output}`);
|
|
820
1033
|
if (inDocs) {
|
|
821
|
-
console.log(
|
|
1034
|
+
console.log(`ℹ️ Don't forget to commit your changes on branch '${docsBranch}'.`);
|
|
822
1035
|
}
|
|
823
1036
|
});
|
|
824
1037
|
|
|
825
|
-
|
|
826
|
-
// Attach the automation group to the main program.
|
|
827
1038
|
programCli.addCommand(automation);
|
|
828
|
-
|
|
829
1039
|
programCli.parse(process.argv);
|
|
830
|
-
|