@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.
- package/README.adoc +0 -163
- package/bin/doc-tools.js +491 -286
- package/cli-utils/antora-utils.js +127 -0
- package/cli-utils/self-managed-docs-branch.js +2 -1
- package/package.json +6 -5
- package/tools/redpanda-connect/generate-rpcn-connector-docs.js +205 -0
- package/tools/redpanda-connect/helpers/advancedConfig.js +17 -0
- package/tools/redpanda-connect/helpers/buildConfigYaml.js +53 -0
- package/tools/redpanda-connect/helpers/commonConfig.js +31 -0
- package/tools/redpanda-connect/helpers/eq.js +10 -0
- package/tools/redpanda-connect/helpers/index.js +19 -0
- package/tools/redpanda-connect/helpers/isObject.js +1 -0
- package/tools/redpanda-connect/helpers/join.js +6 -0
- package/tools/redpanda-connect/helpers/ne.js +10 -0
- package/tools/redpanda-connect/helpers/or.js +4 -0
- package/tools/redpanda-connect/helpers/renderConnectExamples.js +37 -0
- package/tools/redpanda-connect/helpers/renderConnectFields.js +146 -0
- package/tools/redpanda-connect/helpers/renderLeafField.js +64 -0
- package/tools/redpanda-connect/helpers/renderObjectField.js +41 -0
- package/tools/redpanda-connect/helpers/renderYamlList.js +24 -0
- package/tools/redpanda-connect/helpers/toYaml.js +11 -0
- package/tools/redpanda-connect/helpers/uppercase.js +9 -0
- package/tools/redpanda-connect/parse-csv-connectors.js +63 -0
- package/tools/redpanda-connect/report-delta.js +152 -0
- package/tools/redpanda-connect/templates/connector.hbs +20 -0
- package/tools/redpanda-connect/templates/examples-partials.hbs +7 -0
- package/tools/redpanda-connect/templates/fields-partials.hbs +13 -0
- package/tools/redpanda-connect/templates/intro.hbs +35 -0
- package/macros/data-template.js +0 -591
package/bin/doc-tools.js
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const { execSync, spawnSync } = require('child_process');
|
|
4
|
-
const os
|
|
4
|
+
const os = require('os');
|
|
5
5
|
const { Command } = require('commander');
|
|
6
6
|
const path = require('path');
|
|
7
|
+
const yaml = require('yaml');
|
|
7
8
|
const fs = require('fs');
|
|
8
|
-
const
|
|
9
|
+
const handlebars = require('handlebars');
|
|
10
|
+
const { determineDocsBranch } = require('../cli-utils/self-managed-docs-branch.js');
|
|
9
11
|
const fetchFromGithub = require('../tools/fetch-from-github.js');
|
|
10
12
|
const { urlToXref } = require('../cli-utils/convert-doc-links.js');
|
|
11
|
-
|
|
13
|
+
const { generateRpcnConnectorDocs } = require('../tools/redpanda-connect/generate-rpcn-connector-docs.js');
|
|
14
|
+
const parseCSVConnectors = require('../tools/redpanda-connect/parse-csv-connectors.js');
|
|
15
|
+
const { getAntoraValue, setAntoraValue } = require('../cli-utils/antora-utils');
|
|
16
|
+
const {
|
|
17
|
+
getRpkConnectVersion,
|
|
18
|
+
printDeltaReport
|
|
19
|
+
} = require('../tools/redpanda-connect/report-delta');
|
|
12
20
|
|
|
13
21
|
/**
|
|
14
22
|
* Searches upward from a starting directory to locate the repository root.
|
|
@@ -21,9 +29,10 @@ const { urlToXref } = require('../cli-utils/convert-doc-links.js');
|
|
|
21
29
|
function findRepoRoot(start = process.cwd()) {
|
|
22
30
|
let dir = start;
|
|
23
31
|
while (dir !== path.parse(dir).root) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
if (
|
|
33
|
+
fs.existsSync(path.join(dir, '.git')) ||
|
|
34
|
+
fs.existsSync(path.join(dir, 'package.json'))
|
|
35
|
+
) {
|
|
27
36
|
return dir;
|
|
28
37
|
}
|
|
29
38
|
dir = path.dirname(dir);
|
|
@@ -34,6 +43,7 @@ function findRepoRoot(start = process.cwd()) {
|
|
|
34
43
|
|
|
35
44
|
// --------------------------------------------------------------------
|
|
36
45
|
// Dependency check functions
|
|
46
|
+
|
|
37
47
|
/**
|
|
38
48
|
* Prints an error message to stderr and exits the process with a non-zero status.
|
|
39
49
|
*
|
|
@@ -68,7 +78,7 @@ function requireTool(cmd, { versionFlag = '--version', help = '' } = {}) {
|
|
|
68
78
|
*
|
|
69
79
|
* @param {string} cmd - The name of the command-line tool to check.
|
|
70
80
|
* @param {string} [help] - Optional help text to display if the tool is not found.
|
|
71
|
-
* @param {string} [versionFlag='--
|
|
81
|
+
* @param {string} [versionFlag='--version'] - The flag to use when checking if the tool is installed.
|
|
72
82
|
*
|
|
73
83
|
* @throws {Error} If the specified command is not found or does not respond to the specified flag.
|
|
74
84
|
*/
|
|
@@ -76,9 +86,9 @@ function requireCmd(cmd, help, versionFlag = '--version') {
|
|
|
76
86
|
requireTool(cmd, { versionFlag, help });
|
|
77
87
|
}
|
|
78
88
|
|
|
79
|
-
|
|
80
89
|
// --------------------------------------------------------------------
|
|
81
90
|
// Special validators
|
|
91
|
+
|
|
82
92
|
/**
|
|
83
93
|
* Ensures that Python with a minimum required version is installed and available in the system PATH.
|
|
84
94
|
*
|
|
@@ -87,7 +97,6 @@ function requireCmd(cmd, help, versionFlag = '--version') {
|
|
|
87
97
|
* @param {number} [minMajor=3] - Minimum required major version of Python.
|
|
88
98
|
* @param {number} [minMinor=10] - Minimum required minor version of Python.
|
|
89
99
|
*/
|
|
90
|
-
|
|
91
100
|
function requirePython(minMajor = 3, minMinor = 10) {
|
|
92
101
|
const candidates = ['python3', 'python'];
|
|
93
102
|
for (const p of candidates) {
|
|
@@ -101,8 +110,10 @@ function requirePython(minMajor = 3, minMinor = 10) {
|
|
|
101
110
|
/* ignore & try next */
|
|
102
111
|
}
|
|
103
112
|
}
|
|
104
|
-
fail(
|
|
105
|
-
|
|
113
|
+
fail(
|
|
114
|
+
`Python ${minMajor}.${minMinor}+ not found or too old.
|
|
115
|
+
→ Install from your package manager or https://python.org`
|
|
116
|
+
);
|
|
106
117
|
}
|
|
107
118
|
|
|
108
119
|
/**
|
|
@@ -121,65 +132,64 @@ function requireDockerDaemon() {
|
|
|
121
132
|
|
|
122
133
|
// --------------------------------------------------------------------
|
|
123
134
|
// Grouped checks
|
|
135
|
+
|
|
124
136
|
/**
|
|
125
137
|
* Ensures that required dependencies for generating CRD documentation are installed.
|
|
126
138
|
*
|
|
127
139
|
* Verifies the presence of the {@link git} and {@link crd-ref-docs} command-line tools, exiting the process with an error message if either is missing.
|
|
128
140
|
*/
|
|
129
|
-
|
|
130
141
|
function verifyCrdDependencies() {
|
|
131
|
-
requireCmd('git',
|
|
142
|
+
requireCmd('git', 'Install Git: https://git-scm.com/downloads');
|
|
132
143
|
requireCmd(
|
|
133
144
|
'crd-ref-docs',
|
|
134
145
|
`
|
|
135
|
-
|
|
146
|
+
The 'crd-ref-docs' command is required but was not found.
|
|
136
147
|
|
|
137
|
-
|
|
148
|
+
To install it, follow these steps (for macOS):
|
|
138
149
|
|
|
139
|
-
|
|
140
|
-
|
|
150
|
+
1. Determine your architecture:
|
|
151
|
+
Run: \`uname -m\`
|
|
141
152
|
|
|
142
|
-
|
|
153
|
+
2. Download and install:
|
|
143
154
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
155
|
+
- For Apple Silicon (M1/M2/M3):
|
|
156
|
+
curl -fLO https://github.com/elastic/crd-ref-docs/releases/download/v0.1.0/crd-ref-docs_0.1.0_Darwin_arm64.tar.gz
|
|
157
|
+
tar -xzf crd-ref-docs_0.1.0_Darwin_arm64.tar.gz
|
|
158
|
+
chmod +x crd-ref-docs
|
|
159
|
+
sudo mv crd-ref-docs /usr/local/bin/
|
|
149
160
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
161
|
+
- For Intel (x86_64):
|
|
162
|
+
curl -fLO https://github.com/elastic/crd-ref-docs/releases/download/v0.1.0/crd-ref-docs_0.1.0_Darwin_x86_64.tar.gz
|
|
163
|
+
tar -xzf crd-ref-docs_0.1.0_Darwin_x86_64.tar.gz
|
|
164
|
+
chmod +x crd-ref-docs
|
|
165
|
+
sudo mv crd-ref-docs /usr/local/bin/
|
|
155
166
|
|
|
156
|
-
|
|
167
|
+
For more details, visit: https://github.com/elastic/crd-ref-docs
|
|
157
168
|
`.trim()
|
|
158
169
|
);
|
|
159
170
|
requireCmd(
|
|
160
171
|
'go',
|
|
161
172
|
`
|
|
162
|
-
|
|
173
|
+
The 'go' command (Golang) is required but was not found.
|
|
163
174
|
|
|
164
|
-
|
|
175
|
+
To install it on macOS:
|
|
165
176
|
|
|
166
|
-
|
|
167
|
-
|
|
177
|
+
Option 1: Install via Homebrew (recommended):
|
|
178
|
+
brew install go
|
|
168
179
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
180
|
+
Option 2: Download directly from the official site:
|
|
181
|
+
1. Visit: https://go.dev/dl/
|
|
182
|
+
2. Download the appropriate installer for macOS.
|
|
183
|
+
3. Run the installer and follow the instructions.
|
|
173
184
|
|
|
174
|
-
|
|
175
|
-
|
|
185
|
+
After installation, verify it works:
|
|
186
|
+
go version
|
|
176
187
|
|
|
177
|
-
|
|
188
|
+
For more details, see: https://go.dev/doc/install
|
|
178
189
|
`.trim(),
|
|
179
|
-
|
|
190
|
+
'version'
|
|
180
191
|
);
|
|
181
|
-
|
|
182
|
-
}
|
|
192
|
+
}
|
|
183
193
|
|
|
184
194
|
/**
|
|
185
195
|
* Ensures that all required tools for Helm documentation generation are installed.
|
|
@@ -190,35 +200,35 @@ function verifyHelmDependencies() {
|
|
|
190
200
|
requireCmd(
|
|
191
201
|
'helm-docs',
|
|
192
202
|
`
|
|
193
|
-
|
|
203
|
+
The 'helm-docs' command is required but was not found.
|
|
194
204
|
|
|
195
|
-
|
|
205
|
+
To install it, follow these steps (for macOS):
|
|
196
206
|
|
|
197
|
-
|
|
198
|
-
|
|
207
|
+
1. Determine your architecture:
|
|
208
|
+
Run: \`uname -m\`
|
|
199
209
|
|
|
200
|
-
|
|
210
|
+
2. Download and install:
|
|
201
211
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
212
|
+
- For Apple Silicon (M1/M2/M3):
|
|
213
|
+
curl -fLO https://github.com/norwoodj/helm-docs/releases/download/v1.11.0/helm-docs_1.11.0_Darwin_arm64.tar.gz
|
|
214
|
+
tar -xzf helm-docs_1.11.0_Darwin_arm64.tar.gz
|
|
215
|
+
chmod +x helm-docs
|
|
216
|
+
sudo mv helm-docs /usr/local/bin/
|
|
207
217
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
218
|
+
- For Intel (x86_64):
|
|
219
|
+
curl -fLO https://github.com/norwoodj/helm-docs/releases/download/v1.11.0/helm-docs_1.11.0_Darwin_x86_64.tar.gz
|
|
220
|
+
tar -xzf helm-docs_1.11.0_Darwin_x86_64.tar.gz
|
|
221
|
+
chmod +x helm-docs
|
|
222
|
+
sudo mv helm-docs /usr/local/bin/
|
|
213
223
|
|
|
214
|
-
|
|
215
|
-
|
|
224
|
+
Alternatively, if you use Homebrew:
|
|
225
|
+
brew install norwoodj/tap/helm-docs
|
|
216
226
|
|
|
217
|
-
|
|
227
|
+
For more details, visit: https://github.com/norwoodj/helm-docs
|
|
218
228
|
`.trim()
|
|
219
229
|
);
|
|
220
|
-
|
|
221
|
-
requireCmd('git',
|
|
230
|
+
requireCmd('pandoc', 'brew install pandoc or https://pandoc.org');
|
|
231
|
+
requireCmd('git', 'Install Git: https://git-scm.com/downloads');
|
|
222
232
|
}
|
|
223
233
|
|
|
224
234
|
/**
|
|
@@ -227,13 +237,16 @@ function verifyHelmDependencies() {
|
|
|
227
237
|
* Checks for the presence of `make`, Python 3.10 or newer, and at least one C++ compiler (`gcc` or `clang`). Exits the process with an error message if any dependency is missing.
|
|
228
238
|
*/
|
|
229
239
|
function verifyPropertyDependencies() {
|
|
230
|
-
requireCmd('make',
|
|
240
|
+
requireCmd('make', 'Your OS package manager');
|
|
231
241
|
requirePython();
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
catch {
|
|
235
|
-
try {
|
|
236
|
-
|
|
242
|
+
try {
|
|
243
|
+
execSync('gcc --version', { stdio: 'ignore' });
|
|
244
|
+
} catch {
|
|
245
|
+
try {
|
|
246
|
+
execSync('clang --version', { stdio: 'ignore' });
|
|
247
|
+
} catch {
|
|
248
|
+
fail('A C++ compiler (gcc or clang) is required.');
|
|
249
|
+
}
|
|
237
250
|
}
|
|
238
251
|
}
|
|
239
252
|
|
|
@@ -250,15 +263,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
|
-
|
|
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
|
|
323
|
+
const pagesDir = path.join(repoRoot, 'docs', 'modules', moduleName, 'pages');
|
|
313
324
|
const sourceFile = path.join(projectDir, 'README.adoc');
|
|
314
|
-
const destLink
|
|
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(
|
|
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
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
|
382
|
-
console.log(
|
|
383
|
-
const r = spawnSync('bash', [
|
|
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
|
|
390
|
-
const newDir
|
|
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
|
|
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
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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
|
|
459
|
-
const make
|
|
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
|
|
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
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
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(
|
|
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
|
-
|
|
523
|
-
.option(
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
)
|
|
528
|
-
.option(
|
|
529
|
-
'--output-dir <dir>',
|
|
530
|
-
'Where to write all generated AsciiDoc files',
|
|
531
|
-
'modules/reference/pages'
|
|
532
|
-
)
|
|
533
|
-
.option(
|
|
534
|
-
'--output-suffix <suffix>',
|
|
535
|
-
'Suffix to append to each chart name (including extension)',
|
|
536
|
-
'-helm-spec.adoc'
|
|
537
|
-
)
|
|
538
|
-
.action(opts => {
|
|
539
|
-
verifyHelmDependencies()
|
|
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
|
|
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
|
|
559
|
-
|
|
560
|
-
console.log(
|
|
561
|
-
const ok =
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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 (
|
|
573
|
-
'clone','--depth','1','--branch',ref,
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
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
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
charts = [root]
|
|
807
|
+
let charts = [];
|
|
808
|
+
if (fs.existsSync(path.join(root, 'Chart.yaml'))) {
|
|
809
|
+
charts = [root];
|
|
591
810
|
} else {
|
|
592
|
-
charts = fs
|
|
593
|
-
.
|
|
594
|
-
.
|
|
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(
|
|
827
|
+
const name = path.basename(chartPath);
|
|
828
|
+
console.log(`⏳ Processing chart "${name}"…`);
|
|
609
829
|
|
|
610
830
|
// Regenerate README.md
|
|
611
|
-
console.log(
|
|
612
|
-
let r = spawnSync('helm-docs', { cwd: chartPath, stdio: 'inherit' })
|
|
613
|
-
if (r.status !== 0) process.exit(r.status)
|
|
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(
|
|
623
|
-
fs.mkdirSync(path.dirname(outFile), { recursive: true })
|
|
624
|
-
r = spawnSync('pandoc', [
|
|
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];
|
|
641
|
-
bracketPart = m[2];
|
|
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
|
-
|
|
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
|
-
|
|
672
|
-
|
|
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
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
.option('--
|
|
679
|
-
|
|
680
|
-
'.github/crd-config/templates/asciidoctor/operator')
|
|
681
|
-
.option('--output <file>',
|
|
682
|
-
'Where to write the generated AsciiDoc file',
|
|
683
|
-
'modules/reference/pages/k-crd.adoc')
|
|
684
|
-
.action(async opts => {
|
|
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(
|
|
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
|
|
702
|
-
const inDocs
|
|
703
|
-
|
|
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('⚠️
|
|
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(
|
|
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
|
|
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
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
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 (
|
|
751
|
-
|
|
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}`);
|
|
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',
|
|
769
|
-
|
|
770
|
-
'--
|
|
771
|
-
|
|
772
|
-
'--
|
|
773
|
-
|
|
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(
|
|
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];
|
|
794
|
-
bracketPart = m[2];
|
|
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
|
-
|
|
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)
|
|
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(
|
|
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
|
-
|