@redpanda-data/docs-extensions-and-macros 4.5.0 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/README.adoc +0 -163
  2. package/bin/doc-tools.js +491 -286
  3. package/cli-utils/antora-utils.js +127 -0
  4. package/cli-utils/self-managed-docs-branch.js +2 -1
  5. package/package.json +6 -5
  6. package/tools/redpanda-connect/generate-rpcn-connector-docs.js +205 -0
  7. package/tools/redpanda-connect/helpers/advancedConfig.js +17 -0
  8. package/tools/redpanda-connect/helpers/buildConfigYaml.js +53 -0
  9. package/tools/redpanda-connect/helpers/commonConfig.js +31 -0
  10. package/tools/redpanda-connect/helpers/eq.js +10 -0
  11. package/tools/redpanda-connect/helpers/index.js +19 -0
  12. package/tools/redpanda-connect/helpers/isObject.js +1 -0
  13. package/tools/redpanda-connect/helpers/join.js +6 -0
  14. package/tools/redpanda-connect/helpers/ne.js +10 -0
  15. package/tools/redpanda-connect/helpers/or.js +4 -0
  16. package/tools/redpanda-connect/helpers/renderConnectExamples.js +37 -0
  17. package/tools/redpanda-connect/helpers/renderConnectFields.js +146 -0
  18. package/tools/redpanda-connect/helpers/renderLeafField.js +64 -0
  19. package/tools/redpanda-connect/helpers/renderObjectField.js +41 -0
  20. package/tools/redpanda-connect/helpers/renderYamlList.js +24 -0
  21. package/tools/redpanda-connect/helpers/toYaml.js +11 -0
  22. package/tools/redpanda-connect/helpers/uppercase.js +9 -0
  23. package/tools/redpanda-connect/parse-csv-connectors.js +63 -0
  24. package/tools/redpanda-connect/report-delta.js +152 -0
  25. package/tools/redpanda-connect/templates/connector.hbs +20 -0
  26. package/tools/redpanda-connect/templates/examples-partials.hbs +7 -0
  27. package/tools/redpanda-connect/templates/fields-partials.hbs +13 -0
  28. package/tools/redpanda-connect/templates/intro.hbs +35 -0
  29. 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,13 @@ 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
 
258
- programCli
259
- .name('doc-tools')
260
- .description('Redpanda Document Automation CLI')
261
- .version('1.1.0');
272
+ programCli.name('doc-tools').description('Redpanda Document Automation CLI').version('1.1.0');
262
273
 
263
274
  // Top-level commands.
264
275
  programCli
@@ -279,7 +290,7 @@ programCli
279
290
  try {
280
291
  await require('../tools/get-redpanda-version.js')(options);
281
292
  } catch (err) {
282
- console.error(err);
293
+ console.error(`❌ ${err.message}`);
283
294
  process.exit(1);
284
295
  }
285
296
  });
@@ -293,12 +304,12 @@ programCli
293
304
  try {
294
305
  await require('../tools/get-console-version.js')(options);
295
306
  } catch (err) {
296
- console.error(err);
307
+ console.error(`❌ ${err.message}`);
297
308
  process.exit(1);
298
309
  }
299
310
  });
300
311
 
301
- programCli
312
+ programCli
302
313
  .command('link-readme')
303
314
  .description('Symlink a README.adoc into docs/modules/<module>/pages/')
304
315
  .requiredOption('-s, --subdir <subdir>', 'Relative path to the lab project subdirectory')
@@ -309,9 +320,9 @@ programCli
309
320
  const moduleName = normalized.split('/')[0];
310
321
 
311
322
  const projectDir = path.join(repoRoot, normalized);
312
- const pagesDir = path.join(repoRoot, 'docs', 'modules', moduleName, 'pages');
323
+ const pagesDir = path.join(repoRoot, 'docs', 'modules', moduleName, 'pages');
313
324
  const sourceFile = path.join(projectDir, 'README.adoc');
314
- const destLink = path.join(pagesDir, options.target);
325
+ const destLink = path.join(pagesDir, options.target);
315
326
 
316
327
  if (!fs.existsSync(projectDir)) {
317
328
  console.error(`❌ Project directory not found: ${projectDir}`);
@@ -332,38 +343,38 @@ programCli
332
343
  else fail(`Destination already exists and is not a symlink: ${destLink}`);
333
344
  }
334
345
  fs.symlinkSync(relPath, destLink);
335
- console.log(`✔️ Linked ${relPath} → ${destLink}`);
346
+ console.log(`✅ Linked ${relPath} → ${destLink}`);
336
347
  } catch (err) {
337
348
  fail(`Failed to create symlink: ${err.message}`);
338
349
  }
339
350
  });
340
351
 
341
352
  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
- });
353
+ .command('fetch')
354
+ .description('Fetch a file or directory from GitHub and save it locally')
355
+ .requiredOption('-o, --owner <owner>', 'GitHub repo owner or org')
356
+ .requiredOption('-r, --repo <repo>', 'GitHub repo name')
357
+ .requiredOption('-p, --remote-path <path>', 'Path in the repo to fetch')
358
+ .requiredOption('-d, --save-dir <dir>', 'Local directory to save into')
359
+ .option('-f, --filename <name>', 'Custom filename to save as')
360
+ .action(async (options) => {
361
+ try {
362
+ await fetchFromGithub(
363
+ options.owner,
364
+ options.repo,
365
+ options.remotePath,
366
+ options.saveDir,
367
+ options.filename
368
+ );
369
+ console.log(`✅ Fetched to ${options.saveDir}`);
370
+ } catch (err) {
371
+ console.error(`❌ ${err.message}`);
372
+ process.exit(1);
373
+ }
374
+ });
363
375
 
364
376
  // Create an "automation" subcommand group.
365
- const automation = new Command('generate')
366
- .description('Run docs automations (properties, metrics, rpk docs, and Kubernetes)');
377
+ const automation = new Command('generate').description('Run docs automations');
367
378
 
368
379
  // --------------------------------------------------------------------
369
380
  // Automation subcommands
@@ -373,23 +384,23 @@ const automation = new Command('generate')
373
384
  const commonOptions = {
374
385
  dockerRepo: 'redpanda',
375
386
  consoleTag: 'latest',
376
- consoleDockerRepo: 'console'
387
+ consoleDockerRepo: 'console',
377
388
  };
378
389
 
379
390
  function runClusterDocs(mode, tag, options) {
380
391
  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 });
392
+ const args = [mode, tag, options.dockerRepo, options.consoleTag, options.consoleDockerRepo];
393
+ console.log(`⏳ Running ${script} with arguments: ${args.join(' ')}`);
394
+ const r = spawnSync('bash', [script, ...args], { stdio: 'inherit', shell: true });
384
395
  if (r.status !== 0) process.exit(r.status);
385
396
  }
386
397
 
387
398
  // helper to diff two autogenerated directories
388
399
  function diffDirs(kind, oldTag, newTag) {
389
- const oldDir = path.join('autogenerated', oldTag, kind);
390
- const newDir = path.join('autogenerated', newTag, kind);
400
+ const oldDir = path.join('autogenerated', oldTag, kind);
401
+ const newDir = path.join('autogenerated', newTag, kind);
391
402
  const diffDir = path.join('autogenerated', 'diffs', kind, `${oldTag}_to_${newTag}`);
392
- const patch = path.join(diffDir, 'changes.patch');
403
+ const patch = path.join(diffDir, 'changes.patch');
393
404
 
394
405
  if (!fs.existsSync(oldDir)) {
395
406
  console.error(`❌ Cannot diff: missing ${oldDir}`);
@@ -415,11 +426,22 @@ function diffDirs(kind, oldTag, newTag) {
415
426
  automation
416
427
  .command('metrics-docs')
417
428
  .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)
429
+ .requiredOption('-t, --tag <tag>', 'Redpanda version to use when starting Redpanda in Docker')
430
+ .option(
431
+ '--docker-repo <repo>',
432
+ 'Docker repository to use when starting Redpanda in Docker',
433
+ commonOptions.dockerRepo
434
+ )
435
+ .option(
436
+ '--console-tag <tag>',
437
+ 'Redpanda Console version to use when starting Redpanda Console in Docker',
438
+ commonOptions.consoleTag
439
+ )
440
+ .option(
441
+ '--console-docker-repo <repo>',
442
+ 'Docker repository to use when starting Redpanda Console in Docker',
443
+ commonOptions.consoleDockerRepo
444
+ )
423
445
  .option('--diff <oldTag>', 'Also diff autogenerated metrics from <oldTag> → <tag>')
424
446
  .action((options) => {
425
447
  verifyMetricsDependencies();
@@ -445,6 +467,198 @@ automation
445
467
  process.exit(0);
446
468
  });
447
469
 
470
+ automation
471
+ .command('rpcn-connector-docs')
472
+ .description('Generate RPCN connector docs and diff changes since the last version')
473
+ .option('-d, --data-dir <path>', 'Directory where versioned connect JSON files live', path.resolve(process.cwd(), 'docs-data'))
474
+ .option('--old-data <path>', 'Optional override for old data file (for diff)')
475
+ .option('-f, --fetch-connectors', 'Fetch latest connector data using rpk')
476
+ .option('-m, --draft-missing', 'Generate full-doc drafts for connectors missing in output')
477
+ .option('--csv <path>', 'Path to connector metadata CSV file', 'internal/plugins/info.csv')
478
+ .option('--template-main <path>', 'Main Handlebars template', path.resolve(__dirname, '../tools/redpanda-connect/templates/connector.hbs'))
479
+ .option('--template-intro <path>', 'Intro section partial template', path.resolve(__dirname, '../tools/redpanda-connect/templates/intro.hbs'))
480
+ .option('--template-fields <path>', 'Fields section partial template', path.resolve(__dirname, '../tools/redpanda-connect/templates/fields-partials.hbs'))
481
+ .option('--template-examples <path>', 'Examples section partial template', path.resolve(__dirname, '../tools/redpanda-connect/templates/examples-partials.hbs'))
482
+ .option('--overrides <path>', 'Optional JSON file with overrides')
483
+ .action(async (options) => {
484
+ let success = true;
485
+ const dataDir = path.resolve(process.cwd(), options.dataDir);
486
+ fs.mkdirSync(dataDir, { recursive: true });
487
+
488
+ const timestamp = new Date().toISOString();
489
+
490
+ let newVersion;
491
+ let dataFile;
492
+ if (options.fetchConnectors) {
493
+ try {
494
+ execSync('rpk --version', { stdio: 'ignore' });
495
+ newVersion = getRpkConnectVersion();
496
+ const tmpFile = path.join(dataDir, `connect-${newVersion}.tmp.json`);
497
+ const finalFile = path.join(dataDir, `connect-${newVersion}.json`);
498
+
499
+ const fd = fs.openSync(tmpFile, 'w');
500
+ const r = spawnSync('rpk', ['connect', 'list', '--format', 'json-full'], { stdio: ['ignore', fd, 'inherit'] });
501
+ fs.closeSync(fd);
502
+
503
+ const rawJson = fs.readFileSync(tmpFile, 'utf8');
504
+ const parsed = JSON.parse(rawJson);
505
+ fs.writeFileSync(finalFile, JSON.stringify(parsed, null, 2));
506
+ fs.unlinkSync(tmpFile);
507
+ dataFile = finalFile;
508
+ console.log(`✅ Fetched and saved: ${finalFile}`);
509
+ } catch (err) {
510
+ console.error(`❌ Failed to fetch connectors: ${err.message}`);
511
+ success = false;
512
+ }
513
+ } else {
514
+ const candidates = fs.readdirSync(dataDir).filter(f => /^connect-\d+\.\d+\.\d+\.json$/.test(f));
515
+ if (candidates.length === 0) {
516
+ console.error('❌ No connect-<version>.json found. Use --fetch-connectors.');
517
+ process.exit(1);
518
+ }
519
+ candidates.sort();
520
+ dataFile = path.join(dataDir, candidates[candidates.length - 1]);
521
+ newVersion = candidates[candidates.length - 1].match(/connect-(\d+\.\d+\.\d+)\.json/)[1];
522
+ }
523
+
524
+ console.log('⏳ Generating connector partials...');
525
+ let partialsWritten, partialFiles, draftsWritten, draftFiles;
526
+
527
+ try {
528
+ const result = await generateRpcnConnectorDocs({
529
+ data: dataFile,
530
+ overrides: options.overrides,
531
+ template: options.templateMain,
532
+ templateIntro: options.templateIntro,
533
+ templateFields: options.templateFields,
534
+ templateExamples: options.templateExamples,
535
+ writeFullDrafts: false
536
+ });
537
+ partialsWritten = result.partialsWritten;
538
+ partialFiles = result.partialFiles;
539
+ } catch (err) {
540
+ console.error(`❌ Failed to generate partials: ${err.message}`);
541
+ success = false;
542
+ }
543
+
544
+ if (options.draftMissing) {
545
+ console.log('⏳ Drafting missing connectors...');
546
+ try {
547
+ const connectorList = await parseCSVConnectors(options.csv, console);
548
+ const validConnectors = connectorList.filter(row => row.name && row.type);
549
+
550
+ const pagesRoot = path.resolve(process.cwd(), 'modules/components/pages');
551
+ const allMissing = validConnectors.filter(({ name, type }) => {
552
+ if (!name || !type) {
553
+ console.warn(`⚠️ Skipping invalid connector entry:`, { name, type });
554
+ return false;
555
+ }
556
+ const expected = path.join(pagesRoot, `${type}s`, `${name}.adoc`);
557
+ return !fs.existsSync(expected);
558
+ });
559
+
560
+ const missingConnectors = allMissing.filter(({ name }) => !name.includes('sql_driver'));
561
+
562
+ if (missingConnectors.length === 0) {
563
+ console.log('✅ All connectors (excluding sql_drivers) already have docs—nothing to draft.');
564
+ } else {
565
+ console.log(`⏳ Docs missing for ${missingConnectors.length} connectors:`);
566
+ missingConnectors.forEach(({ name, type }) => {
567
+ console.log(` • ${type}/${name}`);
568
+ });
569
+ console.log('');
570
+
571
+ const rawData = fs.readFileSync(dataFile, 'utf8');
572
+ const dataObj = JSON.parse(rawData);
573
+
574
+ const filteredDataObj = {};
575
+ for (const [key, arr] of Object.entries(dataObj)) {
576
+ if (!Array.isArray(arr)) {
577
+ filteredDataObj[key] = arr;
578
+ continue;
579
+ }
580
+ filteredDataObj[key] = arr.filter(component =>
581
+ missingConnectors.some(m => m.name === component.name && `${m.type}s` === key)
582
+ );
583
+ }
584
+
585
+ const tempDataPath = path.join(dataDir, '._filtered_connect_data.json');
586
+ fs.writeFileSync(tempDataPath, JSON.stringify(filteredDataObj, null, 2), 'utf8');
587
+
588
+ const draftResult = await generateRpcnConnectorDocs({
589
+ data: tempDataPath,
590
+ overrides: options.overrides,
591
+ template: options.templateMain,
592
+ templateFields: options.templateFields,
593
+ templateExamples: options.templateExamples,
594
+ templateIntro: options.templateIntro,
595
+ writeFullDrafts: true
596
+ });
597
+
598
+ fs.unlinkSync(tempDataPath);
599
+ draftsWritten = draftResult.draftsWritten;
600
+ draftFiles = draftResult.draftFiles;
601
+ }
602
+ } catch (err) {
603
+ console.error(`❌ Could not draft missing: ${err.message}`);
604
+ success = false;
605
+ }
606
+ }
607
+
608
+ let oldIndex = {};
609
+ if (options.oldData && fs.existsSync(options.oldData)) {
610
+ oldIndex = JSON.parse(fs.readFileSync(options.oldData, 'utf8'));
611
+ } else {
612
+ const oldVersion = getAntoraValue('asciidoc.attributes.latest-connect-version');
613
+ if (oldVersion) {
614
+ const oldPath = path.join(dataDir, `connect-${oldVersion}.json`);
615
+ if (fs.existsSync(oldPath)) {
616
+ oldIndex = JSON.parse(fs.readFileSync(oldPath, 'utf8'));
617
+ }
618
+ }
619
+ }
620
+
621
+ const newIndex = JSON.parse(fs.readFileSync(dataFile, 'utf8'));
622
+ printDeltaReport(oldIndex, newIndex);
623
+
624
+ function logCollapsed(label, filesArray, maxToShow = 10) {
625
+ console.log(` • ${label}: ${filesArray.length} total`);
626
+ const sample = filesArray.slice(0, maxToShow);
627
+ sample.forEach(fp => console.log(` – ${fp}`));
628
+ const remaining = filesArray.length - sample.length;
629
+ if (remaining > 0) {
630
+ console.log(` … plus ${remaining} more`);
631
+ }
632
+ console.log('');
633
+ }
634
+
635
+ const wrote = setAntoraValue('asciidoc.attributes.latest-connect-version', newVersion);
636
+ if (wrote) {
637
+ console.log(`✅ Updated Antora version: ${newVersion}`);
638
+ }
639
+
640
+ console.log('📊 Generation Report:');
641
+ console.log(` • Partial files: ${partialsWritten}`);
642
+ // Split “partials” into fields vs examples by checking the path substring.
643
+ const fieldsPartials = partialFiles.filter(fp => fp.includes('/fields/'));
644
+ const examplesPartials = partialFiles.filter(fp => fp.includes('/examples/'));
645
+
646
+ // Show only up to 10 of each
647
+ logCollapsed('Fields partials', fieldsPartials, 10);
648
+ logCollapsed('Examples partials', examplesPartials, 10);
649
+
650
+ if (options.draftMissing) {
651
+ console.log(` • Full drafts: ${draftsWritten}`);
652
+ logCollapsed('Draft files', draftFiles, 5);
653
+ }
654
+
655
+ console.log('\n📄 Summary:');
656
+ console.log(` • Run time: ${timestamp}`);
657
+ console.log(` • Version used: ${newVersion}`);
658
+
659
+ process.exit(success ? 0 : 1);
660
+ });
661
+
448
662
  automation
449
663
  .command('property-docs')
450
664
  .description('Generate JSON and AsciiDoc documentation for Redpanda configuration properties')
@@ -455,11 +669,14 @@ automation
455
669
 
456
670
  const newTag = options.tag;
457
671
  const oldTag = options.diff;
458
- const cwd = path.resolve(__dirname, '../tools/property-extractor');
459
- const make = (tag) => {
672
+ const cwd = path.resolve(__dirname, '../tools/property-extractor');
673
+ const make = (tag) => {
460
674
  console.log(`⏳ Building property docs for ${tag}…`);
461
675
  const r = spawnSync('make', ['build', `TAG=${tag}`], { cwd, stdio: 'inherit' });
462
- if (r.error ) { console.error(r.error); process.exit(1); }
676
+ if (r.error) {
677
+ console.error(`❌ ${r.error.message}`);
678
+ process.exit(1);
679
+ }
463
680
  if (r.status !== 0) process.exit(r.status);
464
681
  };
465
682
 
@@ -480,11 +697,22 @@ automation
480
697
  automation
481
698
  .command('rpk-docs')
482
699
  .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)
700
+ .requiredOption('-t, --tag <tag>', 'Redpanda version to use when starting Redpanda in Docker')
701
+ .option(
702
+ '--docker-repo <repo>',
703
+ 'Docker repository to use when starting Redpanda in Docker',
704
+ commonOptions.dockerRepo
705
+ )
706
+ .option(
707
+ '--console-tag <tag>',
708
+ 'Redpanda Console version to use when starting Redpanda Console in Docker',
709
+ commonOptions.consoleTag
710
+ )
711
+ .option(
712
+ '--console-docker-repo <repo>',
713
+ 'Docker repository to use when starting Redpanda Console in Docker',
714
+ commonOptions.consoleDockerRepo
715
+ )
488
716
  .option('--diff <oldTag>', 'Also diff autogenerated rpk docs from <oldTag> → <tag>')
489
717
  .action((options) => {
490
718
  verifyMetricsDependencies();
@@ -512,181 +740,163 @@ automation
512
740
 
513
741
  automation
514
742
  .command('helm-spec')
515
- .description(`Generate AsciiDoc documentation for one or more Helm charts (supports local dirs or GitHub URLs)`)
743
+ .description(
744
+ `Generate AsciiDoc documentation for one or more Helm charts (supports local dirs or GitHub URLs)`
745
+ )
516
746
  .option(
517
747
  '--chart-dir <dir|url>',
518
748
  'Chart directory (contains Chart.yaml) or a root containing multiple charts, or a GitHub URL',
519
749
  'https://github.com/redpanda-data/redpanda-operator/charts'
520
750
  )
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()
751
+ .requiredOption('-t, --tag <tag>', 'Branch or tag to clone when using a GitHub URL for the chart-dir')
752
+ .option('--readme <file>', 'Relative README.md path inside each chart dir', 'README.md')
753
+ .option('--output-dir <dir>', 'Where to write all generated AsciiDoc files', 'modules/reference/pages')
754
+ .option('--output-suffix <suffix>', 'Suffix to append to each chart name (including extension)', '-helm-spec.adoc')
755
+ .action((opts) => {
756
+ verifyHelmDependencies();
540
757
 
541
- // Prepare chart-root (local or GitHub) ───────────────────────
542
- let root = opts.chartDir
543
- let tmpClone = null
758
+ // Prepare chart-root (local or GitHub)
759
+ let root = opts.chartDir;
760
+ let tmpClone = null;
544
761
 
545
762
  if (/^https?:\/\/github\.com\//.test(root)) {
546
763
  if (!opts.tag) {
547
- console.error('❌ When using a GitHub URL you must pass --tag')
548
- process.exit(1)
764
+ console.error('❌ When using a GitHub URL you must pass --tag');
765
+ process.exit(1);
549
766
  }
550
- const u = new URL(root)
551
- const parts = u.pathname.replace(/\.git$/, '').split('/').filter(Boolean)
767
+ const u = new URL(root);
768
+ const parts = u.pathname.replace(/\.git$/, '').split('/').filter(Boolean);
552
769
  if (parts.length < 2) {
553
- console.error(`❌ Invalid GitHub URL: ${root}`)
554
- process.exit(1)
770
+ console.error(`❌ Invalid GitHub URL: ${root}`);
771
+ process.exit(1);
555
772
  }
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
773
+ const [owner, repo, ...sub] = parts;
774
+ const repoUrl = `https://${u.host}/${owner}/${repo}.git`;
775
+ const ref = opts.tag;
776
+
777
+ console.log(`⏳ Verifying ${repoUrl}@${ref}…`);
778
+ const ok =
779
+ spawnSync(
780
+ 'git',
781
+ ['ls-remote', '--exit-code', repoUrl, `refs/heads/${ref}`, `refs/tags/${ref}`],
782
+ { stdio: 'ignore' }
783
+ ).status === 0;
565
784
  if (!ok) {
566
- console.error(`❌ ${ref} not found on ${repoUrl}`)
567
- process.exit(1)
785
+ console.error(`❌ ${ref} not found on ${repoUrl}`);
786
+ process.exit(1);
568
787
  }
569
788
 
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)
789
+ tmpClone = fs.mkdtempSync(path.join(os.tmpdir(), 'helm-'));
790
+ console.log(`⏳ Cloning ${repoUrl}@${ref} → ${tmpClone}`);
791
+ if (
792
+ spawnSync('git', ['clone', '--depth', '1', '--branch', ref, repoUrl, tmpClone], {
793
+ stdio: 'inherit',
794
+ }).status !== 0
795
+ ) {
796
+ console.error('❌ git clone failed');
797
+ process.exit(1);
578
798
  }
579
- root = sub.length ? path.join(tmpClone, sub.join('/')) : tmpClone
799
+ root = sub.length ? path.join(tmpClone, sub.join('/')) : tmpClone;
580
800
  }
581
801
 
582
- // Discover charts ─────────────────────────────────────────────
802
+ // Discover charts
583
803
  if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) {
584
- console.error(`❌ Chart root not found: ${root}`)
585
- process.exit(1)
804
+ console.error(`❌ Chart root not found: ${root}`);
805
+ process.exit(1);
586
806
  }
587
- // single-chart?
588
- let charts = []
589
- if (fs.existsSync(path.join(root,'Chart.yaml'))) {
590
- charts = [root]
807
+ let charts = [];
808
+ if (fs.existsSync(path.join(root, 'Chart.yaml'))) {
809
+ charts = [root];
591
810
  } else {
592
- charts = fs.readdirSync(root)
593
- .map(n => path.join(root,n))
594
- .filter(p => fs.existsSync(path.join(p,'Chart.yaml')))
811
+ charts = fs
812
+ .readdirSync(root)
813
+ .map((n) => path.join(root, n))
814
+ .filter((p) => fs.existsSync(path.join(p, 'Chart.yaml')));
595
815
  }
596
816
  if (charts.length === 0) {
597
- console.error(`❌ No charts found under: ${root}`)
598
- process.exit(1)
817
+ console.error(`❌ No charts found under: ${root}`);
818
+ process.exit(1);
599
819
  }
600
820
 
601
- // Ensure output-dir exists ────────────────────────────────────
602
- const outDir = path.resolve(opts.outputDir)
603
- fs.mkdirSync(outDir, { recursive: true })
821
+ // Ensure output-dir exists
822
+ const outDir = path.resolve(opts.outputDir);
823
+ fs.mkdirSync(outDir, { recursive: true });
604
824
 
605
- // Process each chart ─────────────────────────────────────────
825
+ // Process each chart
606
826
  for (const chartPath of charts) {
607
- const name = path.basename(chartPath)
608
- console.log(`\n🔨 Processing chart "${name}"…`)
827
+ const name = path.basename(chartPath);
828
+ console.log(`⏳ Processing chart "${name}"…`);
609
829
 
610
830
  // 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)
831
+ console.log(`⏳ helm-docs in ${chartPath}`);
832
+ let r = spawnSync('helm-docs', { cwd: chartPath, stdio: 'inherit' });
833
+ if (r.status !== 0) process.exit(r.status);
614
834
 
615
835
  // Convert Markdown → AsciiDoc
616
- const md = path.join(chartPath, opts.readme)
836
+ const md = path.join(chartPath, opts.readme);
617
837
  if (!fs.existsSync(md)) {
618
- console.error(`❌ README not found: ${md}`)
619
- process.exit(1)
838
+ console.error(`❌ README not found: ${md}`);
839
+ process.exit(1);
620
840
  }
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)
841
+ const outFile = path.join(outDir, `k-${name}${opts.outputSuffix}`);
842
+ console.log(`⏳ pandoc ${md} → ${outFile}`);
843
+ fs.mkdirSync(path.dirname(outFile), { recursive: true });
844
+ r = spawnSync('pandoc', [md, '-t', 'asciidoc', '-o', outFile], { stdio: 'inherit' });
845
+ if (r.status !== 0) process.exit(r.status);
626
846
 
627
847
  // Post-process tweaks
628
- let doc = fs.readFileSync(outFile, 'utf8')
848
+ let doc = fs.readFileSync(outFile, 'utf8');
629
849
  const xrefRe = /https:\/\/docs\.redpanda\.com[^\s\]\[\)"]+(?:\[[^\]]*\])?/g;
630
850
  doc = doc
631
851
  .replace(/(\[\d+\])\]\./g, '$1\\].')
632
852
  .replace(/^== # (.*)$/gm, '= $1')
633
853
  .replace(/^== description: (.*)$/gm, ':description: $1')
634
- .replace(xrefRe, match => {
635
- // split off any [bracketed text]
854
+ .replace(xrefRe, (match) => {
636
855
  let urlPart = match;
637
856
  let bracketPart = '';
638
857
  const m = match.match(/^([^\[]+)(\[[^\]]*\])$/);
639
858
  if (m) {
640
- urlPart = m[1]; // the pure URL
641
- bracketPart = m[2]; // the “[text]”
859
+ urlPart = m[1];
860
+ bracketPart = m[2];
642
861
  }
643
- // if it ends in “#” we leave it alone
644
862
  if (urlPart.endsWith('#')) {
645
863
  return match;
646
864
  }
647
865
  try {
648
866
  const xref = urlToXref(urlPart);
649
- // re-attach the bracket text, or append empty [] for bare URLs
650
- return bracketPart
651
- ? `${xref}${bracketPart}`
652
- : `${xref}[]`;
867
+ return bracketPart ? `${xref}${bracketPart}` : `${xref}[]`;
653
868
  } catch (err) {
654
869
  console.warn(`⚠️ urlToXref failed on ${urlPart}: ${err.message}`);
655
870
  return match;
656
871
  }
657
872
  });
658
- fs.writeFileSync(outFile, doc, 'utf8')
873
+ fs.writeFileSync(outFile, doc, 'utf8');
659
874
 
660
- console.log(`✅ Wrote ${outFile}`)
875
+ console.log(`✅ Wrote ${outFile}`);
661
876
  }
662
877
 
663
- // Cleanup ───────────────────────────────────────────────────
664
- if (tmpClone) fs.rmSync(tmpClone, { recursive: true, force: true })
665
- })
878
+ // Cleanup
879
+ if (tmpClone) fs.rmSync(tmpClone, { recursive: true, force: true });
880
+ });
666
881
 
667
882
  automation
668
883
  .command('crd-spec')
669
884
  .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>',
885
+ .requiredOption('-t, --tag <operatorTag>', 'Operator release tag or branch, such as operator/v25.1.2')
886
+ .option(
887
+ '-s, --source-path <src>',
673
888
  '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 => {
889
+ 'https://github.com/redpanda-data/redpanda-operator/operator/api/redpanda/v1alpha2'
890
+ )
891
+ .option('-d, --depth <n>', 'How many levels deep', '10')
892
+ .option('--templates-dir <dir>', 'Asciidoctor templates dir', '.github/crd-config/templates/asciidoctor/operator')
893
+ .option('--output <file>', 'Where to write the generated AsciiDoc file', 'modules/reference/pages/k-crd.adoc')
894
+ .action(async (opts) => {
685
895
  verifyCrdDependencies();
686
896
 
687
- // Fetch upstream config ──────────────────────────────────────────
897
+ // Fetch upstream config
688
898
  const configTmp = fs.mkdtempSync(path.join(os.tmpdir(), 'crd-config-'));
689
- console.log('🔧 Fetching crd-ref-docs-config.yaml from redpanda-operator@main…');
899
+ console.log(`⏳ Fetching crd-ref-docs-config.yaml from redpanda-operator@main…`);
690
900
  await fetchFromGithub(
691
901
  'redpanda-data',
692
902
  'redpanda-operator',
@@ -696,36 +906,37 @@ automation
696
906
  );
697
907
  const configPath = path.join(configTmp, 'crd-ref-docs-config.yaml');
698
908
 
699
- // Detect docs repo context ───────────────────────────────────────
909
+ // Detect docs repo context
700
910
  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'));
911
+ const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
912
+ const inDocs =
913
+ pkg.name === 'redpanda-docs-playbook' ||
914
+ (pkg.repository && pkg.repository.url.includes('redpanda-data/docs'));
704
915
  let docsBranch = null;
705
916
 
706
917
  if (!inDocs) {
707
- console.warn('⚠️ Not inside redpanda-data/docs; skipping branch suggestion.');
918
+ console.warn('⚠️ Not inside redpanda-data/docs; skipping branch suggestion.');
708
919
  } else {
709
920
  try {
710
921
  docsBranch = await determineDocsBranch(opts.tag);
711
- console.log(`Detected docs repo; you should commit to branch '${docsBranch}'.`);
922
+ console.log(`✅ Detected docs repo; you should commit to branch '${docsBranch}'.`);
712
923
  } catch (err) {
713
924
  console.error(`❌ Unable to determine docs branch: ${err.message}`);
714
925
  process.exit(1);
715
926
  }
716
927
  }
717
928
 
718
- // Validate templates ─────────────────────────────────────────────
929
+ // Validate templates
719
930
  if (!fs.existsSync(opts.templatesDir)) {
720
931
  console.error(`❌ Templates directory not found: ${opts.templatesDir}`);
721
932
  process.exit(1);
722
933
  }
723
934
 
724
- // Prepare source (local folder or GitHub URL) ───────────────────
935
+ // Prepare source (local folder or GitHub URL)
725
936
  let localSrc = opts.sourcePath;
726
937
  let tmpSrc;
727
938
  if (/^https?:\/\/github\.com\//.test(opts.sourcePath)) {
728
- const u = new URL(opts.sourcePath);
939
+ const u = new URL(opts.sourcePath);
729
940
  const parts = u.pathname.split('/').filter(Boolean);
730
941
  if (parts.length < 2) {
731
942
  console.error(`❌ Invalid GitHub URL: ${opts.sourcePath}`);
@@ -734,97 +945,91 @@ automation
734
945
  const [owner, repo, ...subpathParts] = parts;
735
946
  const repoUrl = `https://${u.host}/${owner}/${repo}`;
736
947
  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;
948
+ console.log(`⏳ Verifying "${opts.tag}" in ${repoUrl}…`);
949
+ const ok =
950
+ spawnSync('git', ['ls-remote', '--exit-code', repoUrl, `refs/tags/${opts.tag}`, `refs/heads/${opts.tag}`], {
951
+ stdio: 'ignore',
952
+ }).status === 0;
743
953
  if (!ok) {
744
954
  console.error(`❌ Tag or branch "${opts.tag}" not found on ${repoUrl}`);
745
955
  process.exit(1);
746
956
  }
747
- // Clone
748
957
  tmpSrc = fs.mkdtempSync(path.join(os.tmpdir(), 'crd-src-'));
749
958
  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);
959
+ if (
960
+ spawnSync('git', ['clone', '--depth', '1', '--branch', opts.tag, repoUrl, tmpSrc], {
961
+ stdio: 'inherit',
962
+ }).status !== 0
963
+ ) {
964
+ console.error(`❌ git clone failed`);
965
+ process.exit(1);
752
966
  }
753
- // Point at subfolder if any
754
967
  localSrc = subpath ? path.join(tmpSrc, subpath) : tmpSrc;
755
968
  if (!fs.existsSync(localSrc)) {
756
- console.error(`❌ Subdirectory not found in repo: ${subpath}`); process.exit(1);
969
+ console.error(`❌ Subdirectory not found in repo: ${subpath}`);
970
+ process.exit(1);
757
971
  }
758
972
  }
759
973
 
760
- // Ensure output directory exists ────────────────────────────────
974
+ // Ensure output directory exists
761
975
  const outputDir = path.dirname(opts.output);
762
976
  if (!fs.existsSync(outputDir)) {
763
977
  fs.mkdirSync(outputDir, { recursive: true });
764
978
  }
765
979
 
766
- // Run crd-ref-docs ───────────────────────────────────────────────
980
+ // Run crd-ref-docs
767
981
  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
982
+ '--source-path',
983
+ localSrc,
984
+ '--max-depth',
985
+ opts.depth,
986
+ '--templates-dir',
987
+ opts.templatesDir,
988
+ '--config',
989
+ configPath,
990
+ '--renderer',
991
+ 'asciidoctor',
992
+ '--output-path',
993
+ opts.output,
774
994
  ];
775
995
  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);
996
+ if (spawnSync('crd-ref-docs', args, { stdio: 'inherit' }).status !== 0) {
997
+ console.error(`❌ crd-ref-docs failed`);
998
+ process.exit(1);
778
999
  }
779
1000
 
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.
1001
+ let doc = fs.readFileSync(opts.output, 'utf8');
785
1002
  const xrefRe = /https:\/\/docs\.redpanda\.com[^\s\]\[\)"]+(?:\[[^\]]*\])?/g;
786
-
787
- doc = doc.replace(xrefRe, match => {
788
- // split off any [bracketed text]
1003
+ doc = doc.replace(xrefRe, (match) => {
789
1004
  let urlPart = match;
790
1005
  let bracketPart = '';
791
1006
  const m = match.match(/^([^\[]+)(\[[^\]]*\])$/);
792
1007
  if (m) {
793
- urlPart = m[1]; // the pure URL
794
- bracketPart = m[2]; // the “[text]”
1008
+ urlPart = m[1];
1009
+ bracketPart = m[2];
795
1010
  }
796
-
797
- // if it ends in “#” we leave it alone
798
1011
  if (urlPart.endsWith('#')) {
799
1012
  return match;
800
1013
  }
801
-
802
1014
  try {
803
1015
  const xref = urlToXref(urlPart);
804
- // re-attach the bracket text, or append empty [] for bare URLs
805
- return bracketPart
806
- ? `${xref}${bracketPart}`
807
- : `${xref}[]`;
1016
+ return bracketPart ? `${xref}${bracketPart}` : `${xref}[]`;
808
1017
  } catch (err) {
809
1018
  console.warn(`⚠️ urlToXref failed on ${urlPart}: ${err.message}`);
810
1019
  return match;
811
1020
  }
812
1021
  });
813
- fs.writeFileSync(opts.output, doc, 'utf8')
1022
+ fs.writeFileSync(opts.output, doc, 'utf8');
814
1023
 
815
- // Cleanup ────────────────────────────────────────────────────────
816
- if (tmpSrc) fs.rmSync(tmpSrc, { recursive: true, force: true });
1024
+ // Cleanup
1025
+ if (tmpSrc) fs.rmSync(tmpSrc, { recursive: true, force: true });
817
1026
  fs.rmSync(configTmp, { recursive: true, force: true });
818
1027
 
819
1028
  console.log(`✅ CRD docs generated at ${opts.output}`);
820
1029
  if (inDocs) {
821
- console.log(`➡️ Don't forget to commit your changes on branch '${docsBranch}'.`);
1030
+ console.log(`ℹ️ Don't forget to commit your changes on branch '${docsBranch}'.`);
822
1031
  }
823
1032
  });
824
1033
 
825
-
826
- // Attach the automation group to the main program.
827
1034
  programCli.addCommand(automation);
828
-
829
1035
  programCli.parse(process.argv);
830
-