@redpanda-data/docs-extensions-and-macros 4.4.1 → 4.5.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/bin/doc-tools.js +585 -131
- package/cli-utils/convert-doc-links.js +58 -0
- package/cli-utils/generate-cluster-docs.sh +1 -1
- package/cli-utils/self-managed-docs-branch.js +90 -0
- package/cli-utils/start-cluster.sh +12 -2
- package/docker-compose/25.1/bootstrap.yml +67 -0
- package/docker-compose/25.1/docker-compose.yml +414 -0
- package/docker-compose/25.1/generate-profiles.yaml +77 -0
- package/docker-compose/25.1/rpk-profile.yaml +24 -0
- package/docker-compose/25.1/transactions-schema.json +37 -0
- package/docker-compose/25.1/transactions.md +46 -0
- package/docker-compose/25.1/transform/README.adoc +73 -0
- package/docker-compose/25.1/transform/go.mod +5 -0
- package/docker-compose/25.1/transform/go.sum +2 -0
- package/docker-compose/25.1/transform/regex.wasm +0 -0
- package/docker-compose/25.1/transform/transform.go +122 -0
- package/docker-compose/25.1/transform/transform.yaml +33 -0
- package/docker-compose/bootstrap.yml +0 -12
- package/package.json +1 -1
- package/tools/fetch-from-github.js +2 -2
package/bin/doc-tools.js
CHANGED
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const { execSync, spawnSync } = require('child_process');
|
|
4
|
+
const os = require('os');
|
|
4
5
|
const { Command } = require('commander');
|
|
5
6
|
const path = require('path');
|
|
6
7
|
const fs = require('fs');
|
|
7
|
-
|
|
8
|
+
const {determineDocsBranch} = require('../cli-utils/self-managed-docs-branch.js')
|
|
9
|
+
const fetchFromGithub = require('../tools/fetch-from-github.js');
|
|
10
|
+
const { urlToXref } = require('../cli-utils/convert-doc-links.js');
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Searches upward from a starting directory to locate the repository root.
|
|
15
|
+
*
|
|
16
|
+
* Traverses parent directories from the specified start path, returning the first directory containing either a `.git` folder or a `package.json` file. Exits the process with an error if no such directory is found.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} [start] - The directory to begin the search from. Defaults to the current working directory.
|
|
19
|
+
* @returns {string} The absolute path to the repository root directory.
|
|
20
|
+
*/
|
|
8
21
|
function findRepoRoot(start = process.cwd()) {
|
|
9
22
|
let dir = start;
|
|
10
23
|
while (dir !== path.parse(dir).root) {
|
|
@@ -21,97 +34,221 @@ function findRepoRoot(start = process.cwd()) {
|
|
|
21
34
|
|
|
22
35
|
// --------------------------------------------------------------------
|
|
23
36
|
// Dependency check functions
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
process.exit(1);
|
|
33
|
-
}
|
|
37
|
+
/**
|
|
38
|
+
* Prints an error message to stderr and exits the process with a non-zero status.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} msg - The error message to display before exiting.
|
|
41
|
+
*/
|
|
42
|
+
function fail(msg) {
|
|
43
|
+
console.error(`❌ ${msg}`);
|
|
44
|
+
process.exit(1);
|
|
34
45
|
}
|
|
35
46
|
|
|
36
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Ensures that a specified command-line tool is installed and operational.
|
|
49
|
+
*
|
|
50
|
+
* Attempts to execute the tool with a version flag to verify its presence. If the tool is missing or fails to run, the process exits with an error message and optional installation hint.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} cmd - The name of the tool to check (e.g., 'docker', 'helm-docs').
|
|
53
|
+
* @param {object} [opts] - Optional settings.
|
|
54
|
+
* @param {string} [opts.versionFlag='--version'] - The flag used to test the tool's execution.
|
|
55
|
+
* @param {string} [opts.help] - An optional hint or installation instruction shown on failure.
|
|
56
|
+
*/
|
|
57
|
+
function requireTool(cmd, { versionFlag = '--version', help = '' } = {}) {
|
|
37
58
|
try {
|
|
38
|
-
execSync(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return false;
|
|
59
|
+
execSync(`${cmd} ${versionFlag}`, { stdio: 'ignore' });
|
|
60
|
+
} catch {
|
|
61
|
+
const hint = help ? `\n→ ${help}` : '';
|
|
62
|
+
fail(`'${cmd}' is required but not found.${hint}`);
|
|
43
63
|
}
|
|
44
64
|
}
|
|
45
65
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Ensures that a command-line tool is installed by checking if it responds to a specified flag.
|
|
68
|
+
*
|
|
69
|
+
* @param {string} cmd - The name of the command-line tool to check.
|
|
70
|
+
* @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.
|
|
72
|
+
*
|
|
73
|
+
* @throws {Error} If the specified command is not found or does not respond to the specified flag.
|
|
74
|
+
*/
|
|
75
|
+
function requireCmd(cmd, help, versionFlag = '--version') {
|
|
76
|
+
requireTool(cmd, { versionFlag, help });
|
|
51
77
|
}
|
|
52
78
|
|
|
53
|
-
function checkPython() {
|
|
54
|
-
const candidates = ['python3', 'python'];
|
|
55
|
-
let found = false;
|
|
56
79
|
|
|
57
|
-
|
|
80
|
+
// --------------------------------------------------------------------
|
|
81
|
+
// Special validators
|
|
82
|
+
/**
|
|
83
|
+
* Ensures that Python with a minimum required version is installed and available in the system PATH.
|
|
84
|
+
*
|
|
85
|
+
* Checks for either `python3` or `python` executables and verifies that the version is at least the specified minimum (default: 3.10). Exits the process with an error message if the requirement is not met.
|
|
86
|
+
*
|
|
87
|
+
* @param {number} [minMajor=3] - Minimum required major version of Python.
|
|
88
|
+
* @param {number} [minMinor=10] - Minimum required minor version of Python.
|
|
89
|
+
*/
|
|
90
|
+
|
|
91
|
+
function requirePython(minMajor = 3, minMinor = 10) {
|
|
92
|
+
const candidates = ['python3', 'python'];
|
|
93
|
+
for (const p of candidates) {
|
|
58
94
|
try {
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
// versionOutput looks like "Python 3.x.y"
|
|
64
|
-
const versionString = versionOutput.split(' ')[1];
|
|
65
|
-
const [major, minor] = versionString.split('.').map(Number);
|
|
66
|
-
if (major > 3 || (major === 3 && minor >= 10)) {
|
|
67
|
-
found = true;
|
|
68
|
-
break;
|
|
69
|
-
} else {
|
|
70
|
-
console.error(`Error: Python 3.10 or higher is required. Detected version: ${versionString}`);
|
|
71
|
-
process.exit(1);
|
|
95
|
+
const out = execSync(`${p} --version`, { encoding: 'utf8' }).trim();
|
|
96
|
+
const [maj, min] = out.split(' ')[1].split('.').map(Number);
|
|
97
|
+
if (maj > minMajor || (maj === minMajor && min >= minMinor)) {
|
|
98
|
+
return; // success
|
|
72
99
|
}
|
|
73
100
|
} catch {
|
|
74
|
-
|
|
101
|
+
/* ignore & try next */
|
|
75
102
|
}
|
|
76
103
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
process.exit(1);
|
|
80
|
-
}
|
|
104
|
+
fail(`Python ${minMajor}.${minMinor}+ not found or too old.
|
|
105
|
+
→ Install from your package manager or https://python.org`);
|
|
81
106
|
}
|
|
82
107
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Ensures that the Docker CLI is installed and the Docker daemon is running.
|
|
110
|
+
*
|
|
111
|
+
* @throws {Error} If Docker is not installed or the Docker daemon is not running.
|
|
112
|
+
*/
|
|
113
|
+
function requireDockerDaemon() {
|
|
114
|
+
requireTool('docker', { help: 'https://docs.docker.com/get-docker/' });
|
|
115
|
+
try {
|
|
116
|
+
execSync('docker info', { stdio: 'ignore' });
|
|
117
|
+
} catch {
|
|
118
|
+
fail('Docker daemon does not appear to be running. Please start Docker.');
|
|
89
119
|
}
|
|
90
120
|
}
|
|
91
121
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
122
|
+
// --------------------------------------------------------------------
|
|
123
|
+
// Grouped checks
|
|
124
|
+
/**
|
|
125
|
+
* Ensures that required dependencies for generating CRD documentation are installed.
|
|
126
|
+
*
|
|
127
|
+
* 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
|
+
*/
|
|
129
|
+
|
|
130
|
+
function verifyCrdDependencies() {
|
|
131
|
+
requireCmd('git', 'Install Git: https://git-scm.com/downloads');
|
|
132
|
+
requireCmd(
|
|
133
|
+
'crd-ref-docs',
|
|
134
|
+
`
|
|
135
|
+
The 'crd-ref-docs' command is required but was not found.
|
|
136
|
+
|
|
137
|
+
To install it, follow these steps (for macOS):
|
|
138
|
+
|
|
139
|
+
1. Determine your architecture:
|
|
140
|
+
Run: \`uname -m\`
|
|
141
|
+
|
|
142
|
+
2. Download and install:
|
|
143
|
+
|
|
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/
|
|
149
|
+
|
|
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/
|
|
155
|
+
|
|
156
|
+
For more details, visit: https://github.com/elastic/crd-ref-docs
|
|
157
|
+
`.trim()
|
|
158
|
+
);
|
|
159
|
+
requireCmd(
|
|
160
|
+
'go',
|
|
161
|
+
`
|
|
162
|
+
The 'go' command (Golang) is required but was not found.
|
|
163
|
+
|
|
164
|
+
To install it on macOS:
|
|
165
|
+
|
|
166
|
+
Option 1: Install via Homebrew (recommended):
|
|
167
|
+
brew install go
|
|
168
|
+
|
|
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.
|
|
173
|
+
|
|
174
|
+
After installation, verify it works:
|
|
175
|
+
go version
|
|
176
|
+
|
|
177
|
+
For more details, see: https://go.dev/doc/install
|
|
178
|
+
`.trim(),
|
|
179
|
+
'version'
|
|
180
|
+
);
|
|
181
|
+
|
|
99
182
|
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Ensures that all required tools for Helm documentation generation are installed.
|
|
186
|
+
*
|
|
187
|
+
* Checks for the presence of `helm-docs`, `pandoc`, and `git`, exiting the process with an error if any are missing.
|
|
188
|
+
*/
|
|
189
|
+
function verifyHelmDependencies() {
|
|
190
|
+
requireCmd(
|
|
191
|
+
'helm-docs',
|
|
192
|
+
`
|
|
193
|
+
The 'helm-docs' command is required but was not found.
|
|
194
|
+
|
|
195
|
+
To install it, follow these steps (for macOS):
|
|
196
|
+
|
|
197
|
+
1. Determine your architecture:
|
|
198
|
+
Run: \`uname -m\`
|
|
199
|
+
|
|
200
|
+
2. Download and install:
|
|
201
|
+
|
|
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/
|
|
207
|
+
|
|
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/
|
|
213
|
+
|
|
214
|
+
Alternatively, if you use Homebrew:
|
|
215
|
+
brew install norwoodj/tap/helm-docs
|
|
216
|
+
|
|
217
|
+
For more details, visit: https://github.com/norwoodj/helm-docs
|
|
218
|
+
`.trim()
|
|
219
|
+
);
|
|
220
|
+
requireCmd('pandoc', 'brew install pandoc or https://pandoc.org');
|
|
221
|
+
requireCmd('git', 'install Git: https://git-scm.com/downloads');
|
|
100
222
|
}
|
|
101
223
|
|
|
224
|
+
/**
|
|
225
|
+
* Ensures all dependencies required for generating property documentation are installed.
|
|
226
|
+
*
|
|
227
|
+
* 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
|
+
*/
|
|
102
229
|
function verifyPropertyDependencies() {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
230
|
+
requireCmd('make', 'your OS package manager');
|
|
231
|
+
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.'); }
|
|
237
|
+
}
|
|
106
238
|
}
|
|
107
239
|
|
|
240
|
+
/**
|
|
241
|
+
* Ensures all required dependencies for generating Redpanda metrics documentation are installed.
|
|
242
|
+
*
|
|
243
|
+
* Verifies that Python 3.10+, `curl`, and `tar` are available, and that the Docker daemon is running.
|
|
244
|
+
*
|
|
245
|
+
* @throws {Error} If any required dependency is missing or the Docker daemon is not running.
|
|
246
|
+
*/
|
|
108
247
|
function verifyMetricsDependencies() {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
checkDocker();
|
|
248
|
+
requirePython();
|
|
249
|
+
requireCmd('curl');
|
|
250
|
+
requireCmd('tar');
|
|
251
|
+
requireDockerDaemon();
|
|
115
252
|
}
|
|
116
253
|
// --------------------------------------------------------------------
|
|
117
254
|
// Main CLI Definition
|
|
@@ -121,7 +258,7 @@ const programCli = new Command();
|
|
|
121
258
|
programCli
|
|
122
259
|
.name('doc-tools')
|
|
123
260
|
.description('Redpanda Document Automation CLI')
|
|
124
|
-
.version('1.0
|
|
261
|
+
.version('1.1.0');
|
|
125
262
|
|
|
126
263
|
// Top-level commands.
|
|
127
264
|
programCli
|
|
@@ -161,17 +298,79 @@ programCli
|
|
|
161
298
|
}
|
|
162
299
|
});
|
|
163
300
|
|
|
301
|
+
programCli
|
|
302
|
+
.command('link-readme')
|
|
303
|
+
.description('Symlink a README.adoc into docs/modules/<module>/pages/')
|
|
304
|
+
.requiredOption('-s, --subdir <subdir>', 'Relative path to the lab project subdirectory')
|
|
305
|
+
.requiredOption('-t, --target <filename>', 'Name of the target AsciiDoc file in pages/')
|
|
306
|
+
.action((options) => {
|
|
307
|
+
const repoRoot = findRepoRoot();
|
|
308
|
+
const normalized = options.subdir.replace(/\/+$/, '');
|
|
309
|
+
const moduleName = normalized.split('/')[0];
|
|
310
|
+
|
|
311
|
+
const projectDir = path.join(repoRoot, normalized);
|
|
312
|
+
const pagesDir = path.join(repoRoot, 'docs', 'modules', moduleName, 'pages');
|
|
313
|
+
const sourceFile = path.join(projectDir, 'README.adoc');
|
|
314
|
+
const destLink = path.join(pagesDir, options.target);
|
|
315
|
+
|
|
316
|
+
if (!fs.existsSync(projectDir)) {
|
|
317
|
+
console.error(`❌ Project directory not found: ${projectDir}`);
|
|
318
|
+
process.exit(1);
|
|
319
|
+
}
|
|
320
|
+
if (!fs.existsSync(sourceFile)) {
|
|
321
|
+
console.error(`❌ README.adoc not found in ${projectDir}`);
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
fs.mkdirSync(pagesDir, { recursive: true });
|
|
326
|
+
const relPath = path.relative(pagesDir, sourceFile);
|
|
327
|
+
|
|
328
|
+
try {
|
|
329
|
+
if (fs.existsSync(destLink)) {
|
|
330
|
+
const stat = fs.lstatSync(destLink);
|
|
331
|
+
if (stat.isSymbolicLink()) fs.unlinkSync(destLink);
|
|
332
|
+
else fail(`Destination already exists and is not a symlink: ${destLink}`);
|
|
333
|
+
}
|
|
334
|
+
fs.symlinkSync(relPath, destLink);
|
|
335
|
+
console.log(`✔️ Linked ${relPath} → ${destLink}`);
|
|
336
|
+
} catch (err) {
|
|
337
|
+
fail(`Failed to create symlink: ${err.message}`);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
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
|
+
});
|
|
363
|
+
|
|
164
364
|
// Create an "automation" subcommand group.
|
|
165
365
|
const automation = new Command('generate')
|
|
166
|
-
.description('Run docs automations (properties, metrics,
|
|
366
|
+
.description('Run docs automations (properties, metrics, rpk docs, and Kubernetes)');
|
|
167
367
|
|
|
168
368
|
// --------------------------------------------------------------------
|
|
169
|
-
// Automation
|
|
369
|
+
// Automation subcommands
|
|
170
370
|
// --------------------------------------------------------------------
|
|
171
371
|
|
|
172
372
|
// Common options for both automation tasks.
|
|
173
373
|
const commonOptions = {
|
|
174
|
-
tag: 'latest',
|
|
175
374
|
dockerRepo: 'redpanda',
|
|
176
375
|
consoleTag: 'latest',
|
|
177
376
|
consoleDockerRepo: 'console'
|
|
@@ -215,11 +414,12 @@ function diffDirs(kind, oldTag, newTag) {
|
|
|
215
414
|
|
|
216
415
|
automation
|
|
217
416
|
.command('metrics-docs')
|
|
218
|
-
.description('
|
|
219
|
-
.
|
|
220
|
-
|
|
221
|
-
.option('--
|
|
222
|
-
.option('--console-
|
|
417
|
+
.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)
|
|
223
423
|
.option('--diff <oldTag>', 'Also diff autogenerated metrics from <oldTag> → <tag>')
|
|
224
424
|
.action((options) => {
|
|
225
425
|
verifyMetricsDependencies();
|
|
@@ -247,8 +447,8 @@ automation
|
|
|
247
447
|
|
|
248
448
|
automation
|
|
249
449
|
.command('property-docs')
|
|
250
|
-
.description('
|
|
251
|
-
.option('--tag <tag>', 'Git tag or branch to extract from
|
|
450
|
+
.description('Generate JSON and AsciiDoc documentation for Redpanda configuration properties')
|
|
451
|
+
.option('--tag <tag>', 'Git tag or branch to extract from', 'dev')
|
|
252
452
|
.option('--diff <oldTag>', 'Also diff autogenerated properties from <oldTag> → <tag>')
|
|
253
453
|
.action((options) => {
|
|
254
454
|
verifyPropertyDependencies();
|
|
@@ -279,11 +479,12 @@ automation
|
|
|
279
479
|
|
|
280
480
|
automation
|
|
281
481
|
.command('rpk-docs')
|
|
282
|
-
.description('Generate documentation for rpk commands')
|
|
283
|
-
.
|
|
284
|
-
|
|
285
|
-
.option('--
|
|
286
|
-
.option('--console-
|
|
482
|
+
.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)
|
|
287
488
|
.option('--diff <oldTag>', 'Also diff autogenerated rpk docs from <oldTag> → <tag>')
|
|
288
489
|
.action((options) => {
|
|
289
490
|
verifyMetricsDependencies();
|
|
@@ -309,64 +510,317 @@ automation
|
|
|
309
510
|
process.exit(0);
|
|
310
511
|
});
|
|
311
512
|
|
|
312
|
-
|
|
313
|
-
.command('
|
|
314
|
-
.description(
|
|
315
|
-
.
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
513
|
+
automation
|
|
514
|
+
.command('helm-spec')
|
|
515
|
+
.description(`Generate AsciiDoc documentation for one or more Helm charts (supports local dirs or GitHub URLs)`)
|
|
516
|
+
.option(
|
|
517
|
+
'--chart-dir <dir|url>',
|
|
518
|
+
'Chart directory (contains Chart.yaml) or a root containing multiple charts, or a GitHub URL',
|
|
519
|
+
'https://github.com/redpanda-data/redpanda-operator/charts'
|
|
520
|
+
)
|
|
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()
|
|
540
|
+
|
|
541
|
+
// Prepare chart-root (local or GitHub) ───────────────────────
|
|
542
|
+
let root = opts.chartDir
|
|
543
|
+
let tmpClone = null
|
|
544
|
+
|
|
545
|
+
if (/^https?:\/\/github\.com\//.test(root)) {
|
|
546
|
+
if (!opts.tag) {
|
|
547
|
+
console.error('❌ When using a GitHub URL you must pass --tag')
|
|
548
|
+
process.exit(1)
|
|
549
|
+
}
|
|
550
|
+
const u = new URL(root)
|
|
551
|
+
const parts = u.pathname.replace(/\.git$/, '').split('/').filter(Boolean)
|
|
552
|
+
if (parts.length < 2) {
|
|
553
|
+
console.error(`❌ Invalid GitHub URL: ${root}`)
|
|
554
|
+
process.exit(1)
|
|
555
|
+
}
|
|
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
|
|
565
|
+
if (!ok) {
|
|
566
|
+
console.error(`❌ ${ref} not found on ${repoUrl}`)
|
|
567
|
+
process.exit(1)
|
|
568
|
+
}
|
|
319
569
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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)
|
|
578
|
+
}
|
|
579
|
+
root = sub.length ? path.join(tmpClone, sub.join('/')) : tmpClone
|
|
580
|
+
}
|
|
324
581
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
582
|
+
// Discover charts ─────────────────────────────────────────────
|
|
583
|
+
if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) {
|
|
584
|
+
console.error(`❌ Chart root not found: ${root}`)
|
|
585
|
+
process.exit(1)
|
|
328
586
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
587
|
+
// single-chart?
|
|
588
|
+
let charts = []
|
|
589
|
+
if (fs.existsSync(path.join(root,'Chart.yaml'))) {
|
|
590
|
+
charts = [root]
|
|
591
|
+
} else {
|
|
592
|
+
charts = fs.readdirSync(root)
|
|
593
|
+
.map(n => path.join(root,n))
|
|
594
|
+
.filter(p => fs.existsSync(path.join(p,'Chart.yaml')))
|
|
595
|
+
}
|
|
596
|
+
if (charts.length === 0) {
|
|
597
|
+
console.error(`❌ No charts found under: ${root}`)
|
|
598
|
+
process.exit(1)
|
|
332
599
|
}
|
|
333
600
|
|
|
334
|
-
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
console.
|
|
342
|
-
|
|
601
|
+
// Ensure output-dir exists ────────────────────────────────────
|
|
602
|
+
const outDir = path.resolve(opts.outputDir)
|
|
603
|
+
fs.mkdirSync(outDir, { recursive: true })
|
|
604
|
+
|
|
605
|
+
// Process each chart ─────────────────────────────────────────
|
|
606
|
+
for (const chartPath of charts) {
|
|
607
|
+
const name = path.basename(chartPath)
|
|
608
|
+
console.log(`\n🔨 Processing chart "${name}"…`)
|
|
609
|
+
|
|
610
|
+
// 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)
|
|
614
|
+
|
|
615
|
+
// Convert Markdown → AsciiDoc
|
|
616
|
+
const md = path.join(chartPath, opts.readme)
|
|
617
|
+
if (!fs.existsSync(md)) {
|
|
618
|
+
console.error(`❌ README not found: ${md}`)
|
|
619
|
+
process.exit(1)
|
|
620
|
+
}
|
|
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)
|
|
626
|
+
|
|
627
|
+
// Post-process tweaks
|
|
628
|
+
let doc = fs.readFileSync(outFile, 'utf8')
|
|
629
|
+
const xrefRe = /https:\/\/docs\.redpanda\.com[^\s\]\[\)"]+(?:\[[^\]]*\])?/g;
|
|
630
|
+
doc = doc
|
|
631
|
+
.replace(/(\[\d+\])\]\./g, '$1\\].')
|
|
632
|
+
.replace(/^== # (.*)$/gm, '= $1')
|
|
633
|
+
.replace(/^== description: (.*)$/gm, ':description: $1')
|
|
634
|
+
.replace(xrefRe, match => {
|
|
635
|
+
// split off any [bracketed text]
|
|
636
|
+
let urlPart = match;
|
|
637
|
+
let bracketPart = '';
|
|
638
|
+
const m = match.match(/^([^\[]+)(\[[^\]]*\])$/);
|
|
639
|
+
if (m) {
|
|
640
|
+
urlPart = m[1]; // the pure URL
|
|
641
|
+
bracketPart = m[2]; // the “[text]”
|
|
642
|
+
}
|
|
643
|
+
// if it ends in “#” we leave it alone
|
|
644
|
+
if (urlPart.endsWith('#')) {
|
|
645
|
+
return match;
|
|
646
|
+
}
|
|
647
|
+
try {
|
|
648
|
+
const xref = urlToXref(urlPart);
|
|
649
|
+
// re-attach the bracket text, or append empty [] for bare URLs
|
|
650
|
+
return bracketPart
|
|
651
|
+
? `${xref}${bracketPart}`
|
|
652
|
+
: `${xref}[]`;
|
|
653
|
+
} catch (err) {
|
|
654
|
+
console.warn(`⚠️ urlToXref failed on ${urlPart}: ${err.message}`);
|
|
655
|
+
return match;
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
fs.writeFileSync(outFile, doc, 'utf8')
|
|
659
|
+
|
|
660
|
+
console.log(`✅ Wrote ${outFile}`)
|
|
343
661
|
}
|
|
344
|
-
});
|
|
345
662
|
|
|
346
|
-
|
|
347
|
-
.
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
.
|
|
352
|
-
.
|
|
353
|
-
.
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
663
|
+
// Cleanup ───────────────────────────────────────────────────
|
|
664
|
+
if (tmpClone) fs.rmSync(tmpClone, { recursive: true, force: true })
|
|
665
|
+
})
|
|
666
|
+
|
|
667
|
+
automation
|
|
668
|
+
.command('crd-spec')
|
|
669
|
+
.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>',
|
|
673
|
+
'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 => {
|
|
685
|
+
verifyCrdDependencies();
|
|
686
|
+
|
|
687
|
+
// Fetch upstream config ──────────────────────────────────────────
|
|
688
|
+
const configTmp = fs.mkdtempSync(path.join(os.tmpdir(), 'crd-config-'));
|
|
689
|
+
console.log('🔧 Fetching crd-ref-docs-config.yaml from redpanda-operator@main…');
|
|
358
690
|
await fetchFromGithub(
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
691
|
+
'redpanda-data',
|
|
692
|
+
'redpanda-operator',
|
|
693
|
+
'operator/crd-ref-docs-config.yaml',
|
|
694
|
+
configTmp,
|
|
695
|
+
'crd-ref-docs-config.yaml'
|
|
364
696
|
);
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
697
|
+
const configPath = path.join(configTmp, 'crd-ref-docs-config.yaml');
|
|
698
|
+
|
|
699
|
+
// Detect docs repo context ───────────────────────────────────────
|
|
700
|
+
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'));
|
|
704
|
+
let docsBranch = null;
|
|
705
|
+
|
|
706
|
+
if (!inDocs) {
|
|
707
|
+
console.warn('⚠️ Not inside redpanda-data/docs; skipping branch suggestion.');
|
|
708
|
+
} else {
|
|
709
|
+
try {
|
|
710
|
+
docsBranch = await determineDocsBranch(opts.tag);
|
|
711
|
+
console.log(`Detected docs repo; you should commit to branch '${docsBranch}'.`);
|
|
712
|
+
} catch (err) {
|
|
713
|
+
console.error(`❌ Unable to determine docs branch: ${err.message}`);
|
|
714
|
+
process.exit(1);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// Validate templates ─────────────────────────────────────────────
|
|
719
|
+
if (!fs.existsSync(opts.templatesDir)) {
|
|
720
|
+
console.error(`❌ Templates directory not found: ${opts.templatesDir}`);
|
|
721
|
+
process.exit(1);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// Prepare source (local folder or GitHub URL) ───────────────────
|
|
725
|
+
let localSrc = opts.sourcePath;
|
|
726
|
+
let tmpSrc;
|
|
727
|
+
if (/^https?:\/\/github\.com\//.test(opts.sourcePath)) {
|
|
728
|
+
const u = new URL(opts.sourcePath);
|
|
729
|
+
const parts = u.pathname.split('/').filter(Boolean);
|
|
730
|
+
if (parts.length < 2) {
|
|
731
|
+
console.error(`❌ Invalid GitHub URL: ${opts.sourcePath}`);
|
|
732
|
+
process.exit(1);
|
|
733
|
+
}
|
|
734
|
+
const [owner, repo, ...subpathParts] = parts;
|
|
735
|
+
const repoUrl = `https://${u.host}/${owner}/${repo}`;
|
|
736
|
+
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;
|
|
743
|
+
if (!ok) {
|
|
744
|
+
console.error(`❌ Tag or branch "${opts.tag}" not found on ${repoUrl}`);
|
|
745
|
+
process.exit(1);
|
|
746
|
+
}
|
|
747
|
+
// Clone
|
|
748
|
+
tmpSrc = fs.mkdtempSync(path.join(os.tmpdir(), 'crd-src-'));
|
|
749
|
+
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);
|
|
752
|
+
}
|
|
753
|
+
// Point at subfolder if any
|
|
754
|
+
localSrc = subpath ? path.join(tmpSrc, subpath) : tmpSrc;
|
|
755
|
+
if (!fs.existsSync(localSrc)) {
|
|
756
|
+
console.error(`❌ Subdirectory not found in repo: ${subpath}`); process.exit(1);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// Ensure output directory exists ────────────────────────────────
|
|
761
|
+
const outputDir = path.dirname(opts.output);
|
|
762
|
+
if (!fs.existsSync(outputDir)) {
|
|
763
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Run crd-ref-docs ───────────────────────────────────────────────
|
|
767
|
+
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
|
|
774
|
+
];
|
|
775
|
+
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);
|
|
778
|
+
}
|
|
779
|
+
|
|
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.
|
|
785
|
+
const xrefRe = /https:\/\/docs\.redpanda\.com[^\s\]\[\)"]+(?:\[[^\]]*\])?/g;
|
|
786
|
+
|
|
787
|
+
doc = doc.replace(xrefRe, match => {
|
|
788
|
+
// split off any [bracketed text]
|
|
789
|
+
let urlPart = match;
|
|
790
|
+
let bracketPart = '';
|
|
791
|
+
const m = match.match(/^([^\[]+)(\[[^\]]*\])$/);
|
|
792
|
+
if (m) {
|
|
793
|
+
urlPart = m[1]; // the pure URL
|
|
794
|
+
bracketPart = m[2]; // the “[text]”
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// if it ends in “#” we leave it alone
|
|
798
|
+
if (urlPart.endsWith('#')) {
|
|
799
|
+
return match;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
try {
|
|
803
|
+
const xref = urlToXref(urlPart);
|
|
804
|
+
// re-attach the bracket text, or append empty [] for bare URLs
|
|
805
|
+
return bracketPart
|
|
806
|
+
? `${xref}${bracketPart}`
|
|
807
|
+
: `${xref}[]`;
|
|
808
|
+
} catch (err) {
|
|
809
|
+
console.warn(`⚠️ urlToXref failed on ${urlPart}: ${err.message}`);
|
|
810
|
+
return match;
|
|
811
|
+
}
|
|
812
|
+
});
|
|
813
|
+
fs.writeFileSync(opts.output, doc, 'utf8')
|
|
814
|
+
|
|
815
|
+
// Cleanup ────────────────────────────────────────────────────────
|
|
816
|
+
if (tmpSrc) fs.rmSync(tmpSrc, { recursive: true, force: true });
|
|
817
|
+
fs.rmSync(configTmp, { recursive: true, force: true });
|
|
818
|
+
|
|
819
|
+
console.log(`✅ CRD docs generated at ${opts.output}`);
|
|
820
|
+
if (inDocs) {
|
|
821
|
+
console.log(`➡️ Don't forget to commit your changes on branch '${docsBranch}'.`);
|
|
822
|
+
}
|
|
823
|
+
});
|
|
370
824
|
|
|
371
825
|
|
|
372
826
|
// Attach the automation group to the main program.
|