@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.
Files changed (32) hide show
  1. package/README.adoc +0 -163
  2. package/bin/doc-tools.js +492 -283
  3. package/cli-utils/antora-utils.js +127 -0
  4. package/cli-utils/generate-cluster-docs.sh +41 -29
  5. package/cli-utils/self-managed-docs-branch.js +2 -1
  6. package/cli-utils/start-cluster.sh +70 -30
  7. package/extensions/generate-rp-connect-info.js +14 -9
  8. package/package.json +6 -5
  9. package/tools/redpanda-connect/generate-rpcn-connector-docs.js +233 -0
  10. package/tools/redpanda-connect/helpers/advancedConfig.js +17 -0
  11. package/tools/redpanda-connect/helpers/buildConfigYaml.js +53 -0
  12. package/tools/redpanda-connect/helpers/commonConfig.js +31 -0
  13. package/tools/redpanda-connect/helpers/eq.js +10 -0
  14. package/tools/redpanda-connect/helpers/index.js +19 -0
  15. package/tools/redpanda-connect/helpers/isObject.js +1 -0
  16. package/tools/redpanda-connect/helpers/join.js +6 -0
  17. package/tools/redpanda-connect/helpers/ne.js +10 -0
  18. package/tools/redpanda-connect/helpers/or.js +4 -0
  19. package/tools/redpanda-connect/helpers/renderConnectExamples.js +37 -0
  20. package/tools/redpanda-connect/helpers/renderConnectFields.js +148 -0
  21. package/tools/redpanda-connect/helpers/renderLeafField.js +64 -0
  22. package/tools/redpanda-connect/helpers/renderObjectField.js +41 -0
  23. package/tools/redpanda-connect/helpers/renderYamlList.js +24 -0
  24. package/tools/redpanda-connect/helpers/toYaml.js +11 -0
  25. package/tools/redpanda-connect/helpers/uppercase.js +9 -0
  26. package/tools/redpanda-connect/parse-csv-connectors.js +63 -0
  27. package/tools/redpanda-connect/report-delta.js +152 -0
  28. package/tools/redpanda-connect/templates/connector.hbs +20 -0
  29. package/tools/redpanda-connect/templates/examples-partials.hbs +7 -0
  30. package/tools/redpanda-connect/templates/fields-partials.hbs +13 -0
  31. package/tools/redpanda-connect/templates/intro.hbs +33 -0
  32. 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 = require('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 {determineDocsBranch} = require('../cli-utils/self-managed-docs-branch.js')
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
- // marker could be a .git folder or package.json or anything you choose
25
- if (fs.existsSync(path.join(dir, '.git')) ||
26
- fs.existsSync(path.join(dir, 'package.json'))) {
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='--help'] - The flag to use when checking if the tool is installed.
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(`Python ${minMajor}.${minMinor}+ not found or too old.
105
- Install from your package manager or https://python.org`);
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', 'Install Git: https://git-scm.com/downloads');
142
+ requireCmd('git', 'Install Git: https://git-scm.com/downloads');
132
143
  requireCmd(
133
144
  'crd-ref-docs',
134
145
  `
135
- The 'crd-ref-docs' command is required but was not found.
146
+ The 'crd-ref-docs' command is required but was not found.
136
147
 
137
- To install it, follow these steps (for macOS):
148
+ To install it, follow these steps (for macOS):
138
149
 
139
- 1. Determine your architecture:
140
- Run: \`uname -m\`
150
+ 1. Determine your architecture:
151
+ Run: \`uname -m\`
141
152
 
142
- 2. Download and install:
153
+ 2. Download and install:
143
154
 
144
- - For Apple Silicon (M1/M2/M3):
145
- curl -fLO https://github.com/elastic/crd-ref-docs/releases/download/v0.1.0/crd-ref-docs_0.1.0_Darwin_arm64.tar.gz
146
- tar -xzf crd-ref-docs_0.1.0_Darwin_arm64.tar.gz
147
- chmod +x crd-ref-docs
148
- sudo mv crd-ref-docs /usr/local/bin/
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
- - For Intel (x86_64):
151
- 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
152
- tar -xzf crd-ref-docs_0.1.0_Darwin_x86_64.tar.gz
153
- chmod +x crd-ref-docs
154
- sudo mv crd-ref-docs /usr/local/bin/
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
- For more details, visit: https://github.com/elastic/crd-ref-docs
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
- The 'go' command (Golang) is required but was not found.
173
+ The 'go' command (Golang) is required but was not found.
163
174
 
164
- To install it on macOS:
175
+ To install it on macOS:
165
176
 
166
- Option 1: Install via Homebrew (recommended):
167
- brew install go
177
+ Option 1: Install via Homebrew (recommended):
178
+ brew install go
168
179
 
169
- Option 2: Download directly from the official site:
170
- 1. Visit: https://go.dev/dl/
171
- 2. Download the appropriate installer for macOS.
172
- 3. Run the installer and follow the instructions.
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
- After installation, verify it works:
175
- go version
185
+ After installation, verify it works:
186
+ go version
176
187
 
177
- For more details, see: https://go.dev/doc/install
188
+ For more details, see: https://go.dev/doc/install
178
189
  `.trim(),
179
- 'version'
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
- The 'helm-docs' command is required but was not found.
203
+ The 'helm-docs' command is required but was not found.
194
204
 
195
- To install it, follow these steps (for macOS):
205
+ To install it, follow these steps (for macOS):
196
206
 
197
- 1. Determine your architecture:
198
- Run: \`uname -m\`
207
+ 1. Determine your architecture:
208
+ Run: \`uname -m\`
199
209
 
200
- 2. Download and install:
210
+ 2. Download and install:
201
211
 
202
- - For Apple Silicon (M1/M2/M3):
203
- curl -fLO https://github.com/norwoodj/helm-docs/releases/download/v1.11.0/helm-docs_1.11.0_Darwin_arm64.tar.gz
204
- tar -xzf helm-docs_1.11.0_Darwin_arm64.tar.gz
205
- chmod +x helm-docs
206
- sudo mv helm-docs /usr/local/bin/
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
- - For Intel (x86_64):
209
- curl -fLO https://github.com/norwoodj/helm-docs/releases/download/v1.11.0/helm-docs_1.11.0_Darwin_x86_64.tar.gz
210
- tar -xzf helm-docs_1.11.0_Darwin_x86_64.tar.gz
211
- chmod +x helm-docs
212
- sudo mv helm-docs /usr/local/bin/
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
- Alternatively, if you use Homebrew:
215
- brew install norwoodj/tap/helm-docs
224
+ Alternatively, if you use Homebrew:
225
+ brew install norwoodj/tap/helm-docs
216
226
 
217
- For more details, visit: https://github.com/norwoodj/helm-docs
227
+ For more details, visit: https://github.com/norwoodj/helm-docs
218
228
  `.trim()
219
229
  );
220
- requireCmd('pandoc', 'brew install pandoc or https://pandoc.org');
221
- requireCmd('git', 'install Git: https://git-scm.com/downloads');
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', 'your OS package manager');
240
+ requireCmd('make', 'Your OS package manager');
231
241
  requirePython();
232
- // at least one compiler:
233
- try { execSync('gcc --version', { stdio: 'ignore' }); }
234
- catch {
235
- try { execSync('clang --version', { stdio: 'ignore' }); }
236
- catch { fail('A C++ compiler (gcc or clang) is required.'); }
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('1.1.0');
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
- programCli
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 = path.join(repoRoot, 'docs', 'modules', moduleName, 'pages');
327
+ const pagesDir = path.join(repoRoot, 'docs', 'modules', moduleName, 'pages');
313
328
  const sourceFile = path.join(projectDir, 'README.adoc');
314
- const destLink = path.join(pagesDir, options.target);
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(`✔️ Linked ${relPath} → ${destLink}`);
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
- try {
351
- await fetchFromGithub(
352
- options.owner,
353
- options.repo,
354
- options.remotePath,
355
- options.saveDir,
356
- options.filename
357
- );
358
- } catch (err) {
359
- console.error('❌', err.message);
360
- process.exit(1);
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 = [ mode, tag, options.dockerRepo, options.consoleTag, options.consoleDockerRepo ];
382
- console.log(`Running ${script} with arguments: ${args.join(' ')}`);
383
- const r = spawnSync('bash', [ script, ...args ], { stdio: 'inherit', shell: true });
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 = path.join('autogenerated', oldTag, kind);
390
- const newDir = path.join('autogenerated', newTag, kind);
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 = path.join(diffDir, 'changes.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
- 'Redpanda version to use when starting Redpanda in Docker')
420
- .option('--docker-repo <repo>', 'Docker repository to use when starting Redpanda in Docker', commonOptions.dockerRepo)
421
- .option('--console-tag <tag>', 'Redpanda Console version to use when starting Redpanda Console in Docker', commonOptions.consoleTag)
422
- .option('--console-docker-repo <repo>', 'Docker repository to use when starting Redpanda Console in Docker', commonOptions.consoleDockerRepo)
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 = path.resolve(__dirname, '../tools/property-extractor');
459
- const make = (tag) => {
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 ) { console.error(r.error); process.exit(1); }
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
- 'Redpanda version to use when starting Redpanda in Docker')
485
- .option('--docker-repo <repo>', 'Docker repository to use when starting Redpanda in Docker', commonOptions.dockerRepo)
486
- .option('--console-tag <tag>', 'Redpanda Console version to use when starting Redpanda Console in Docker', commonOptions.consoleTag)
487
- .option('--console-docker-repo <repo>', 'Docker repository to use when starting Redpanda Console in Docker', commonOptions.consoleDockerRepo)
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(`Generate AsciiDoc documentation for one or more Helm charts (supports local dirs or GitHub URLs)`)
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
- 'Branch or tag to clone when using a GitHub URL for the chart-dir')
523
- .option(
524
- '--readme <file>',
525
- 'Relative README.md path inside each chart dir',
526
- 'README.md'
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 = new URL(root)
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 = opts.tag
559
-
560
- console.log(`🔎 Verifying ${repoUrl}@${ref}…`)
561
- const ok = spawnSync('git', [
562
- 'ls-remote','--exit-code', repoUrl,
563
- `refs/heads/${ref}`, `refs/tags/${ref}`
564
- ], { stdio:'ignore' }).status === 0
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 (spawnSync('git', [
573
- 'clone','--depth','1','--branch',ref,
574
- repoUrl, tmpClone
575
- ], { stdio:'inherit' }).status !== 0) {
576
- console.error('❌ git clone failed')
577
- process.exit(1)
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
- // single-chart?
588
- let charts = []
589
- if (fs.existsSync(path.join(root,'Chart.yaml'))) {
590
- charts = [root]
811
+ let charts = [];
812
+ if (fs.existsSync(path.join(root, 'Chart.yaml'))) {
813
+ charts = [root];
591
814
  } else {
592
- charts = fs.readdirSync(root)
593
- .map(n => path.join(root,n))
594
- .filter(p => fs.existsSync(path.join(p,'Chart.yaml')))
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(`\n🔨 Processing chart "${name}"…`)
831
+ const name = path.basename(chartPath);
832
+ console.log(`⏳ Processing chart "${name}"…`);
609
833
 
610
834
  // Regenerate README.md
611
- console.log(` ⏳ helm-docs in ${chartPath}`)
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(` ⏳ pandoc ${md} → ${outFile}`)
623
- fs.mkdirSync(path.dirname(outFile), { recursive: true })
624
- r = spawnSync('pandoc', [ md, '-t', 'asciidoc', '-o', outFile ], { stdio:'inherit' })
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]; // the pure URL
641
- bracketPart = m[2]; // the “[text]”
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
- // re-attach the bracket text, or append empty [] for bare URLs
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
- 'Operator release tag or branch, such as operator/v25.1.2')
672
- .option('-s, --source-path <src>',
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
- .option('-d, --depth <n>',
676
- 'How many levels deep',
677
- '10')
678
- .option('--templates-dir <dir>',
679
- 'Asciidoctor templates dir',
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('🔧 Fetching crd-ref-docs-config.yaml from redpanda-operator@main…');
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 = JSON.parse(fs.readFileSync(path.join(repoRoot,'package.json'),'utf8'));
702
- const inDocs = pkg.name === 'redpanda-docs-playbook'
703
- || (pkg.repository && pkg.repository.url.includes('redpanda-data/docs'));
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('⚠️ Not inside redpanda-data/docs; skipping branch suggestion.');
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(`Detected docs repo; you should commit to branch '${docsBranch}'.`);
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 = new URL(opts.sourcePath);
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
- // Verify tag/branch exists
738
- console.log(`🔎 Verifying "${opts.tag}" in ${repoUrl}…`);
739
- const ok = spawnSync('git', [
740
- 'ls-remote','--exit-code', repoUrl,
741
- `refs/tags/${opts.tag}`, `refs/heads/${opts.tag}`
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 (spawnSync('git', ['clone','--depth','1','--branch',opts.tag,repoUrl,tmpSrc],{stdio:'inherit'}).status !== 0) {
751
- console.error('git clone failed'); process.exit(1);
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}`); process.exit(1);
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', localSrc,
769
- '--max-depth', opts.depth,
770
- '--templates-dir', opts.templatesDir,
771
- '--config', configPath,
772
- '--renderer', 'asciidoctor',
773
- '--output-path', opts.output
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('❌ crd-ref-docs failed'); process.exit(1);
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]; // the pure URL
794
- bracketPart = m[2]; // the “[text]”
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
- // re-attach the bracket text, or append empty [] for bare URLs
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) fs.rmSync(tmpSrc, { recursive: true, force: true });
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(`➡️ Don't forget to commit your changes on branch '${docsBranch}'.`);
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
-