@redpanda-data/docs-extensions-and-macros 4.4.2 → 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 +808 -151
- package/cli-utils/antora-utils.js +127 -0
- 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 +91 -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 +6 -5
- package/tools/fetch-from-github.js +2 -2
- 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,16 +1,38 @@
|
|
|
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');
|
|
7
|
+
const yaml = require('yaml');
|
|
6
8
|
const fs = require('fs');
|
|
9
|
+
const handlebars = require('handlebars');
|
|
10
|
+
const { determineDocsBranch } = require('../cli-utils/self-managed-docs-branch.js');
|
|
11
|
+
const fetchFromGithub = require('../tools/fetch-from-github.js');
|
|
12
|
+
const { urlToXref } = require('../cli-utils/convert-doc-links.js');
|
|
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');
|
|
7
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Searches upward from a starting directory to locate the repository root.
|
|
23
|
+
*
|
|
24
|
+
* 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.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} [start] - The directory to begin the search from. Defaults to the current working directory.
|
|
27
|
+
* @returns {string} The absolute path to the repository root directory.
|
|
28
|
+
*/
|
|
8
29
|
function findRepoRoot(start = process.cwd()) {
|
|
9
30
|
let dir = start;
|
|
10
31
|
while (dir !== path.parse(dir).root) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
32
|
+
if (
|
|
33
|
+
fs.existsSync(path.join(dir, '.git')) ||
|
|
34
|
+
fs.existsSync(path.join(dir, 'package.json'))
|
|
35
|
+
) {
|
|
14
36
|
return dir;
|
|
15
37
|
}
|
|
16
38
|
dir = path.dirname(dir);
|
|
@@ -21,107 +43,233 @@ function findRepoRoot(start = process.cwd()) {
|
|
|
21
43
|
|
|
22
44
|
// --------------------------------------------------------------------
|
|
23
45
|
// Dependency check functions
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Prints an error message to stderr and exits the process with a non-zero status.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} msg - The error message to display before exiting.
|
|
51
|
+
*/
|
|
52
|
+
function fail(msg) {
|
|
53
|
+
console.error(`❌ ${msg}`);
|
|
54
|
+
process.exit(1);
|
|
34
55
|
}
|
|
35
56
|
|
|
36
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Ensures that a specified command-line tool is installed and operational.
|
|
59
|
+
*
|
|
60
|
+
* 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.
|
|
61
|
+
*
|
|
62
|
+
* @param {string} cmd - The name of the tool to check (e.g., 'docker', 'helm-docs').
|
|
63
|
+
* @param {object} [opts] - Optional settings.
|
|
64
|
+
* @param {string} [opts.versionFlag='--version'] - The flag used to test the tool's execution.
|
|
65
|
+
* @param {string} [opts.help] - An optional hint or installation instruction shown on failure.
|
|
66
|
+
*/
|
|
67
|
+
function requireTool(cmd, { versionFlag = '--version', help = '' } = {}) {
|
|
37
68
|
try {
|
|
38
|
-
execSync(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return false;
|
|
69
|
+
execSync(`${cmd} ${versionFlag}`, { stdio: 'ignore' });
|
|
70
|
+
} catch {
|
|
71
|
+
const hint = help ? `\n→ ${help}` : '';
|
|
72
|
+
fail(`'${cmd}' is required but not found.${hint}`);
|
|
43
73
|
}
|
|
44
74
|
}
|
|
45
75
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Ensures that a command-line tool is installed by checking if it responds to a specified flag.
|
|
78
|
+
*
|
|
79
|
+
* @param {string} cmd - The name of the command-line tool to check.
|
|
80
|
+
* @param {string} [help] - Optional help text to display if the tool is not found.
|
|
81
|
+
* @param {string} [versionFlag='--version'] - The flag to use when checking if the tool is installed.
|
|
82
|
+
*
|
|
83
|
+
* @throws {Error} If the specified command is not found or does not respond to the specified flag.
|
|
84
|
+
*/
|
|
85
|
+
function requireCmd(cmd, help, versionFlag = '--version') {
|
|
86
|
+
requireTool(cmd, { versionFlag, help });
|
|
51
87
|
}
|
|
52
88
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
let found = false;
|
|
89
|
+
// --------------------------------------------------------------------
|
|
90
|
+
// Special validators
|
|
56
91
|
|
|
57
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Ensures that Python with a minimum required version is installed and available in the system PATH.
|
|
94
|
+
*
|
|
95
|
+
* 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.
|
|
96
|
+
*
|
|
97
|
+
* @param {number} [minMajor=3] - Minimum required major version of Python.
|
|
98
|
+
* @param {number} [minMinor=10] - Minimum required minor version of Python.
|
|
99
|
+
*/
|
|
100
|
+
function requirePython(minMajor = 3, minMinor = 10) {
|
|
101
|
+
const candidates = ['python3', 'python'];
|
|
102
|
+
for (const p of candidates) {
|
|
58
103
|
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);
|
|
104
|
+
const out = execSync(`${p} --version`, { encoding: 'utf8' }).trim();
|
|
105
|
+
const [maj, min] = out.split(' ')[1].split('.').map(Number);
|
|
106
|
+
if (maj > minMajor || (maj === minMajor && min >= minMinor)) {
|
|
107
|
+
return; // success
|
|
72
108
|
}
|
|
73
109
|
} catch {
|
|
74
|
-
|
|
110
|
+
/* ignore & try next */
|
|
75
111
|
}
|
|
76
112
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
113
|
+
fail(
|
|
114
|
+
`Python ${minMajor}.${minMinor}+ not found or too old.
|
|
115
|
+
→ Install from your package manager or https://python.org`
|
|
116
|
+
);
|
|
81
117
|
}
|
|
82
118
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function checkDocker() {
|
|
93
|
-
checkDependency('docker', '--version', 'Docker', 'https://docs.docker.com/get-docker/');
|
|
119
|
+
/**
|
|
120
|
+
* Ensures that the Docker CLI is installed and the Docker daemon is running.
|
|
121
|
+
*
|
|
122
|
+
* @throws {Error} If Docker is not installed or the Docker daemon is not running.
|
|
123
|
+
*/
|
|
124
|
+
function requireDockerDaemon() {
|
|
125
|
+
requireTool('docker', { help: 'https://docs.docker.com/get-docker/' });
|
|
94
126
|
try {
|
|
95
127
|
execSync('docker info', { stdio: 'ignore' });
|
|
96
|
-
} catch
|
|
97
|
-
|
|
98
|
-
process.exit(1);
|
|
128
|
+
} catch {
|
|
129
|
+
fail('Docker daemon does not appear to be running. Please start Docker.');
|
|
99
130
|
}
|
|
100
131
|
}
|
|
101
132
|
|
|
133
|
+
// --------------------------------------------------------------------
|
|
134
|
+
// Grouped checks
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Ensures that required dependencies for generating CRD documentation are installed.
|
|
138
|
+
*
|
|
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.
|
|
140
|
+
*/
|
|
141
|
+
function verifyCrdDependencies() {
|
|
142
|
+
requireCmd('git', 'Install Git: https://git-scm.com/downloads');
|
|
143
|
+
requireCmd(
|
|
144
|
+
'crd-ref-docs',
|
|
145
|
+
`
|
|
146
|
+
The 'crd-ref-docs' command is required but was not found.
|
|
147
|
+
|
|
148
|
+
To install it, follow these steps (for macOS):
|
|
149
|
+
|
|
150
|
+
1. Determine your architecture:
|
|
151
|
+
Run: \`uname -m\`
|
|
152
|
+
|
|
153
|
+
2. Download and install:
|
|
154
|
+
|
|
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/
|
|
160
|
+
|
|
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/
|
|
166
|
+
|
|
167
|
+
For more details, visit: https://github.com/elastic/crd-ref-docs
|
|
168
|
+
`.trim()
|
|
169
|
+
);
|
|
170
|
+
requireCmd(
|
|
171
|
+
'go',
|
|
172
|
+
`
|
|
173
|
+
The 'go' command (Golang) is required but was not found.
|
|
174
|
+
|
|
175
|
+
To install it on macOS:
|
|
176
|
+
|
|
177
|
+
Option 1: Install via Homebrew (recommended):
|
|
178
|
+
brew install go
|
|
179
|
+
|
|
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.
|
|
184
|
+
|
|
185
|
+
After installation, verify it works:
|
|
186
|
+
go version
|
|
187
|
+
|
|
188
|
+
For more details, see: https://go.dev/doc/install
|
|
189
|
+
`.trim(),
|
|
190
|
+
'version'
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Ensures that all required tools for Helm documentation generation are installed.
|
|
196
|
+
*
|
|
197
|
+
* Checks for the presence of `helm-docs`, `pandoc`, and `git`, exiting the process with an error if any are missing.
|
|
198
|
+
*/
|
|
199
|
+
function verifyHelmDependencies() {
|
|
200
|
+
requireCmd(
|
|
201
|
+
'helm-docs',
|
|
202
|
+
`
|
|
203
|
+
The 'helm-docs' command is required but was not found.
|
|
204
|
+
|
|
205
|
+
To install it, follow these steps (for macOS):
|
|
206
|
+
|
|
207
|
+
1. Determine your architecture:
|
|
208
|
+
Run: \`uname -m\`
|
|
209
|
+
|
|
210
|
+
2. Download and install:
|
|
211
|
+
|
|
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/
|
|
217
|
+
|
|
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/
|
|
223
|
+
|
|
224
|
+
Alternatively, if you use Homebrew:
|
|
225
|
+
brew install norwoodj/tap/helm-docs
|
|
226
|
+
|
|
227
|
+
For more details, visit: https://github.com/norwoodj/helm-docs
|
|
228
|
+
`.trim()
|
|
229
|
+
);
|
|
230
|
+
requireCmd('pandoc', 'brew install pandoc or https://pandoc.org');
|
|
231
|
+
requireCmd('git', 'Install Git: https://git-scm.com/downloads');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Ensures all dependencies required for generating property documentation are installed.
|
|
236
|
+
*
|
|
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.
|
|
238
|
+
*/
|
|
102
239
|
function verifyPropertyDependencies() {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
240
|
+
requireCmd('make', 'Your OS package manager');
|
|
241
|
+
requirePython();
|
|
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
|
+
}
|
|
250
|
+
}
|
|
106
251
|
}
|
|
107
252
|
|
|
253
|
+
/**
|
|
254
|
+
* Ensures all required dependencies for generating Redpanda metrics documentation are installed.
|
|
255
|
+
*
|
|
256
|
+
* Verifies that Python 3.10+, `curl`, and `tar` are available, and that the Docker daemon is running.
|
|
257
|
+
*
|
|
258
|
+
* @throws {Error} If any required dependency is missing or the Docker daemon is not running.
|
|
259
|
+
*/
|
|
108
260
|
function verifyMetricsDependencies() {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
checkDocker();
|
|
261
|
+
requirePython();
|
|
262
|
+
requireCmd('curl');
|
|
263
|
+
requireCmd('tar');
|
|
264
|
+
requireDockerDaemon();
|
|
115
265
|
}
|
|
266
|
+
|
|
116
267
|
// --------------------------------------------------------------------
|
|
117
268
|
// Main CLI Definition
|
|
118
269
|
// --------------------------------------------------------------------
|
|
119
270
|
const programCli = new Command();
|
|
120
271
|
|
|
121
|
-
programCli
|
|
122
|
-
.name('doc-tools')
|
|
123
|
-
.description('Redpanda Document Automation CLI')
|
|
124
|
-
.version('1.0.1');
|
|
272
|
+
programCli.name('doc-tools').description('Redpanda Document Automation CLI').version('1.1.0');
|
|
125
273
|
|
|
126
274
|
// Top-level commands.
|
|
127
275
|
programCli
|
|
@@ -142,7 +290,7 @@ programCli
|
|
|
142
290
|
try {
|
|
143
291
|
await require('../tools/get-redpanda-version.js')(options);
|
|
144
292
|
} catch (err) {
|
|
145
|
-
console.error(err);
|
|
293
|
+
console.error(`❌ ${err.message}`);
|
|
146
294
|
process.exit(1);
|
|
147
295
|
}
|
|
148
296
|
});
|
|
@@ -156,41 +304,103 @@ programCli
|
|
|
156
304
|
try {
|
|
157
305
|
await require('../tools/get-console-version.js')(options);
|
|
158
306
|
} catch (err) {
|
|
159
|
-
console.error(err);
|
|
307
|
+
console.error(`❌ ${err.message}`);
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
programCli
|
|
313
|
+
.command('link-readme')
|
|
314
|
+
.description('Symlink a README.adoc into docs/modules/<module>/pages/')
|
|
315
|
+
.requiredOption('-s, --subdir <subdir>', 'Relative path to the lab project subdirectory')
|
|
316
|
+
.requiredOption('-t, --target <filename>', 'Name of the target AsciiDoc file in pages/')
|
|
317
|
+
.action((options) => {
|
|
318
|
+
const repoRoot = findRepoRoot();
|
|
319
|
+
const normalized = options.subdir.replace(/\/+$/, '');
|
|
320
|
+
const moduleName = normalized.split('/')[0];
|
|
321
|
+
|
|
322
|
+
const projectDir = path.join(repoRoot, normalized);
|
|
323
|
+
const pagesDir = path.join(repoRoot, 'docs', 'modules', moduleName, 'pages');
|
|
324
|
+
const sourceFile = path.join(projectDir, 'README.adoc');
|
|
325
|
+
const destLink = path.join(pagesDir, options.target);
|
|
326
|
+
|
|
327
|
+
if (!fs.existsSync(projectDir)) {
|
|
328
|
+
console.error(`❌ Project directory not found: ${projectDir}`);
|
|
329
|
+
process.exit(1);
|
|
330
|
+
}
|
|
331
|
+
if (!fs.existsSync(sourceFile)) {
|
|
332
|
+
console.error(`❌ README.adoc not found in ${projectDir}`);
|
|
333
|
+
process.exit(1);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
fs.mkdirSync(pagesDir, { recursive: true });
|
|
337
|
+
const relPath = path.relative(pagesDir, sourceFile);
|
|
338
|
+
|
|
339
|
+
try {
|
|
340
|
+
if (fs.existsSync(destLink)) {
|
|
341
|
+
const stat = fs.lstatSync(destLink);
|
|
342
|
+
if (stat.isSymbolicLink()) fs.unlinkSync(destLink);
|
|
343
|
+
else fail(`Destination already exists and is not a symlink: ${destLink}`);
|
|
344
|
+
}
|
|
345
|
+
fs.symlinkSync(relPath, destLink);
|
|
346
|
+
console.log(`✅ Linked ${relPath} → ${destLink}`);
|
|
347
|
+
} catch (err) {
|
|
348
|
+
fail(`Failed to create symlink: ${err.message}`);
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
programCli
|
|
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}`);
|
|
160
372
|
process.exit(1);
|
|
161
373
|
}
|
|
162
374
|
});
|
|
163
375
|
|
|
164
376
|
// Create an "automation" subcommand group.
|
|
165
|
-
const automation = new Command('generate')
|
|
166
|
-
.description('Run docs automations (properties, metrics, and rpk docs generation)');
|
|
377
|
+
const automation = new Command('generate').description('Run docs automations');
|
|
167
378
|
|
|
168
379
|
// --------------------------------------------------------------------
|
|
169
|
-
// Automation
|
|
380
|
+
// Automation subcommands
|
|
170
381
|
// --------------------------------------------------------------------
|
|
171
382
|
|
|
172
383
|
// Common options for both automation tasks.
|
|
173
384
|
const commonOptions = {
|
|
174
|
-
tag: 'latest',
|
|
175
385
|
dockerRepo: 'redpanda',
|
|
176
386
|
consoleTag: 'latest',
|
|
177
|
-
consoleDockerRepo: 'console'
|
|
387
|
+
consoleDockerRepo: 'console',
|
|
178
388
|
};
|
|
179
389
|
|
|
180
390
|
function runClusterDocs(mode, tag, options) {
|
|
181
391
|
const script = path.join(__dirname, '../cli-utils/generate-cluster-docs.sh');
|
|
182
|
-
const args
|
|
183
|
-
console.log(
|
|
184
|
-
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 });
|
|
185
395
|
if (r.status !== 0) process.exit(r.status);
|
|
186
396
|
}
|
|
187
397
|
|
|
188
398
|
// helper to diff two autogenerated directories
|
|
189
399
|
function diffDirs(kind, oldTag, newTag) {
|
|
190
|
-
const oldDir
|
|
191
|
-
const newDir
|
|
400
|
+
const oldDir = path.join('autogenerated', oldTag, kind);
|
|
401
|
+
const newDir = path.join('autogenerated', newTag, kind);
|
|
192
402
|
const diffDir = path.join('autogenerated', 'diffs', kind, `${oldTag}_to_${newTag}`);
|
|
193
|
-
const patch
|
|
403
|
+
const patch = path.join(diffDir, 'changes.patch');
|
|
194
404
|
|
|
195
405
|
if (!fs.existsSync(oldDir)) {
|
|
196
406
|
console.error(`❌ Cannot diff: missing ${oldDir}`);
|
|
@@ -215,11 +425,23 @@ function diffDirs(kind, oldTag, newTag) {
|
|
|
215
425
|
|
|
216
426
|
automation
|
|
217
427
|
.command('metrics-docs')
|
|
218
|
-
.description('
|
|
219
|
-
.
|
|
220
|
-
.option(
|
|
221
|
-
|
|
222
|
-
|
|
428
|
+
.description('Generate JSON and AsciiDoc documentation for Redpanda metrics')
|
|
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
|
+
)
|
|
223
445
|
.option('--diff <oldTag>', 'Also diff autogenerated metrics from <oldTag> → <tag>')
|
|
224
446
|
.action((options) => {
|
|
225
447
|
verifyMetricsDependencies();
|
|
@@ -245,21 +467,216 @@ automation
|
|
|
245
467
|
process.exit(0);
|
|
246
468
|
});
|
|
247
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
|
+
|
|
248
662
|
automation
|
|
249
663
|
.command('property-docs')
|
|
250
|
-
.description('
|
|
251
|
-
.option('--tag <tag>', 'Git tag or branch to extract from
|
|
664
|
+
.description('Generate JSON and AsciiDoc documentation for Redpanda configuration properties')
|
|
665
|
+
.option('--tag <tag>', 'Git tag or branch to extract from', 'dev')
|
|
252
666
|
.option('--diff <oldTag>', 'Also diff autogenerated properties from <oldTag> → <tag>')
|
|
253
667
|
.action((options) => {
|
|
254
668
|
verifyPropertyDependencies();
|
|
255
669
|
|
|
256
670
|
const newTag = options.tag;
|
|
257
671
|
const oldTag = options.diff;
|
|
258
|
-
const cwd
|
|
259
|
-
const make
|
|
672
|
+
const cwd = path.resolve(__dirname, '../tools/property-extractor');
|
|
673
|
+
const make = (tag) => {
|
|
260
674
|
console.log(`⏳ Building property docs for ${tag}…`);
|
|
261
675
|
const r = spawnSync('make', ['build', `TAG=${tag}`], { cwd, stdio: 'inherit' });
|
|
262
|
-
if (r.error
|
|
676
|
+
if (r.error) {
|
|
677
|
+
console.error(`❌ ${r.error.message}`);
|
|
678
|
+
process.exit(1);
|
|
679
|
+
}
|
|
263
680
|
if (r.status !== 0) process.exit(r.status);
|
|
264
681
|
};
|
|
265
682
|
|
|
@@ -279,11 +696,23 @@ automation
|
|
|
279
696
|
|
|
280
697
|
automation
|
|
281
698
|
.command('rpk-docs')
|
|
282
|
-
.description('Generate documentation for rpk commands')
|
|
283
|
-
.
|
|
284
|
-
.option(
|
|
285
|
-
|
|
286
|
-
|
|
699
|
+
.description('Generate AsciiDoc documentation for rpk CLI commands')
|
|
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
|
+
)
|
|
287
716
|
.option('--diff <oldTag>', 'Also diff autogenerated rpk docs from <oldTag> → <tag>')
|
|
288
717
|
.action((options) => {
|
|
289
718
|
verifyMetricsDependencies();
|
|
@@ -309,70 +738,298 @@ automation
|
|
|
309
738
|
process.exit(0);
|
|
310
739
|
});
|
|
311
740
|
|
|
312
|
-
|
|
313
|
-
.command('
|
|
314
|
-
.description(
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
.
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
741
|
+
automation
|
|
742
|
+
.command('helm-spec')
|
|
743
|
+
.description(
|
|
744
|
+
`Generate AsciiDoc documentation for one or more Helm charts (supports local dirs or GitHub URLs)`
|
|
745
|
+
)
|
|
746
|
+
.option(
|
|
747
|
+
'--chart-dir <dir|url>',
|
|
748
|
+
'Chart directory (contains Chart.yaml) or a root containing multiple charts, or a GitHub URL',
|
|
749
|
+
'https://github.com/redpanda-data/redpanda-operator/charts'
|
|
750
|
+
)
|
|
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();
|
|
321
757
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
const destLink = path.join(pagesDir, options.target);
|
|
758
|
+
// Prepare chart-root (local or GitHub)
|
|
759
|
+
let root = opts.chartDir;
|
|
760
|
+
let tmpClone = null;
|
|
326
761
|
|
|
327
|
-
if (
|
|
328
|
-
|
|
762
|
+
if (/^https?:\/\/github\.com\//.test(root)) {
|
|
763
|
+
if (!opts.tag) {
|
|
764
|
+
console.error('❌ When using a GitHub URL you must pass --tag');
|
|
765
|
+
process.exit(1);
|
|
766
|
+
}
|
|
767
|
+
const u = new URL(root);
|
|
768
|
+
const parts = u.pathname.replace(/\.git$/, '').split('/').filter(Boolean);
|
|
769
|
+
if (parts.length < 2) {
|
|
770
|
+
console.error(`❌ Invalid GitHub URL: ${root}`);
|
|
771
|
+
process.exit(1);
|
|
772
|
+
}
|
|
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;
|
|
784
|
+
if (!ok) {
|
|
785
|
+
console.error(`❌ ${ref} not found on ${repoUrl}`);
|
|
786
|
+
process.exit(1);
|
|
787
|
+
}
|
|
788
|
+
|
|
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);
|
|
798
|
+
}
|
|
799
|
+
root = sub.length ? path.join(tmpClone, sub.join('/')) : tmpClone;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// Discover charts
|
|
803
|
+
if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) {
|
|
804
|
+
console.error(`❌ Chart root not found: ${root}`);
|
|
329
805
|
process.exit(1);
|
|
330
806
|
}
|
|
331
|
-
|
|
332
|
-
|
|
807
|
+
let charts = [];
|
|
808
|
+
if (fs.existsSync(path.join(root, 'Chart.yaml'))) {
|
|
809
|
+
charts = [root];
|
|
810
|
+
} else {
|
|
811
|
+
charts = fs
|
|
812
|
+
.readdirSync(root)
|
|
813
|
+
.map((n) => path.join(root, n))
|
|
814
|
+
.filter((p) => fs.existsSync(path.join(p, 'Chart.yaml')));
|
|
815
|
+
}
|
|
816
|
+
if (charts.length === 0) {
|
|
817
|
+
console.error(`❌ No charts found under: ${root}`);
|
|
333
818
|
process.exit(1);
|
|
334
819
|
}
|
|
335
820
|
|
|
336
|
-
|
|
337
|
-
const
|
|
821
|
+
// Ensure output-dir exists
|
|
822
|
+
const outDir = path.resolve(opts.outputDir);
|
|
823
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
338
824
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
825
|
+
// Process each chart
|
|
826
|
+
for (const chartPath of charts) {
|
|
827
|
+
const name = path.basename(chartPath);
|
|
828
|
+
console.log(`⏳ Processing chart "${name}"…`);
|
|
829
|
+
|
|
830
|
+
// Regenerate README.md
|
|
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);
|
|
834
|
+
|
|
835
|
+
// Convert Markdown → AsciiDoc
|
|
836
|
+
const md = path.join(chartPath, opts.readme);
|
|
837
|
+
if (!fs.existsSync(md)) {
|
|
838
|
+
console.error(`❌ README not found: ${md}`);
|
|
839
|
+
process.exit(1);
|
|
840
|
+
}
|
|
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);
|
|
846
|
+
|
|
847
|
+
// Post-process tweaks
|
|
848
|
+
let doc = fs.readFileSync(outFile, 'utf8');
|
|
849
|
+
const xrefRe = /https:\/\/docs\.redpanda\.com[^\s\]\[\)"]+(?:\[[^\]]*\])?/g;
|
|
850
|
+
doc = doc
|
|
851
|
+
.replace(/(\[\d+\])\]\./g, '$1\\].')
|
|
852
|
+
.replace(/^== # (.*)$/gm, '= $1')
|
|
853
|
+
.replace(/^== description: (.*)$/gm, ':description: $1')
|
|
854
|
+
.replace(xrefRe, (match) => {
|
|
855
|
+
let urlPart = match;
|
|
856
|
+
let bracketPart = '';
|
|
857
|
+
const m = match.match(/^([^\[]+)(\[[^\]]*\])$/);
|
|
858
|
+
if (m) {
|
|
859
|
+
urlPart = m[1];
|
|
860
|
+
bracketPart = m[2];
|
|
861
|
+
}
|
|
862
|
+
if (urlPart.endsWith('#')) {
|
|
863
|
+
return match;
|
|
864
|
+
}
|
|
865
|
+
try {
|
|
866
|
+
const xref = urlToXref(urlPart);
|
|
867
|
+
return bracketPart ? `${xref}${bracketPart}` : `${xref}[]`;
|
|
868
|
+
} catch (err) {
|
|
869
|
+
console.warn(`⚠️ urlToXref failed on ${urlPart}: ${err.message}`);
|
|
870
|
+
return match;
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
fs.writeFileSync(outFile, doc, 'utf8');
|
|
874
|
+
|
|
875
|
+
console.log(`✅ Wrote ${outFile}`);
|
|
345
876
|
}
|
|
877
|
+
|
|
878
|
+
// Cleanup
|
|
879
|
+
if (tmpClone) fs.rmSync(tmpClone, { recursive: true, force: true });
|
|
346
880
|
});
|
|
347
881
|
|
|
348
|
-
|
|
349
|
-
.command('
|
|
350
|
-
.description('
|
|
351
|
-
.requiredOption('-
|
|
352
|
-
.
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
.
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
882
|
+
automation
|
|
883
|
+
.command('crd-spec')
|
|
884
|
+
.description('Generate Asciidoc documentation for Kubernetes CRD references')
|
|
885
|
+
.requiredOption('-t, --tag <operatorTag>', 'Operator release tag or branch, such as operator/v25.1.2')
|
|
886
|
+
.option(
|
|
887
|
+
'-s, --source-path <src>',
|
|
888
|
+
'CRD Go types dir or GitHub URL',
|
|
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) => {
|
|
895
|
+
verifyCrdDependencies();
|
|
896
|
+
|
|
897
|
+
// Fetch upstream config
|
|
898
|
+
const configTmp = fs.mkdtempSync(path.join(os.tmpdir(), 'crd-config-'));
|
|
899
|
+
console.log(`⏳ Fetching crd-ref-docs-config.yaml from redpanda-operator@main…`);
|
|
360
900
|
await fetchFromGithub(
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
901
|
+
'redpanda-data',
|
|
902
|
+
'redpanda-operator',
|
|
903
|
+
'operator/crd-ref-docs-config.yaml',
|
|
904
|
+
configTmp,
|
|
905
|
+
'crd-ref-docs-config.yaml'
|
|
366
906
|
);
|
|
367
|
-
|
|
368
|
-
console.error('❌', err.message);
|
|
369
|
-
process.exit(1);
|
|
370
|
-
}
|
|
371
|
-
});
|
|
907
|
+
const configPath = path.join(configTmp, 'crd-ref-docs-config.yaml');
|
|
372
908
|
|
|
909
|
+
// Detect docs repo context
|
|
910
|
+
const repoRoot = findRepoRoot();
|
|
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'));
|
|
915
|
+
let docsBranch = null;
|
|
373
916
|
|
|
374
|
-
|
|
375
|
-
|
|
917
|
+
if (!inDocs) {
|
|
918
|
+
console.warn('⚠️ Not inside redpanda-data/docs; skipping branch suggestion.');
|
|
919
|
+
} else {
|
|
920
|
+
try {
|
|
921
|
+
docsBranch = await determineDocsBranch(opts.tag);
|
|
922
|
+
console.log(`✅ Detected docs repo; you should commit to branch '${docsBranch}'.`);
|
|
923
|
+
} catch (err) {
|
|
924
|
+
console.error(`❌ Unable to determine docs branch: ${err.message}`);
|
|
925
|
+
process.exit(1);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
376
928
|
|
|
377
|
-
|
|
929
|
+
// Validate templates
|
|
930
|
+
if (!fs.existsSync(opts.templatesDir)) {
|
|
931
|
+
console.error(`❌ Templates directory not found: ${opts.templatesDir}`);
|
|
932
|
+
process.exit(1);
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// Prepare source (local folder or GitHub URL)
|
|
936
|
+
let localSrc = opts.sourcePath;
|
|
937
|
+
let tmpSrc;
|
|
938
|
+
if (/^https?:\/\/github\.com\//.test(opts.sourcePath)) {
|
|
939
|
+
const u = new URL(opts.sourcePath);
|
|
940
|
+
const parts = u.pathname.split('/').filter(Boolean);
|
|
941
|
+
if (parts.length < 2) {
|
|
942
|
+
console.error(`❌ Invalid GitHub URL: ${opts.sourcePath}`);
|
|
943
|
+
process.exit(1);
|
|
944
|
+
}
|
|
945
|
+
const [owner, repo, ...subpathParts] = parts;
|
|
946
|
+
const repoUrl = `https://${u.host}/${owner}/${repo}`;
|
|
947
|
+
const subpath = subpathParts.join('/');
|
|
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;
|
|
953
|
+
if (!ok) {
|
|
954
|
+
console.error(`❌ Tag or branch "${opts.tag}" not found on ${repoUrl}`);
|
|
955
|
+
process.exit(1);
|
|
956
|
+
}
|
|
957
|
+
tmpSrc = fs.mkdtempSync(path.join(os.tmpdir(), 'crd-src-'));
|
|
958
|
+
console.log(`⏳ Cloning ${repoUrl}@${opts.tag} → ${tmpSrc}`);
|
|
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);
|
|
966
|
+
}
|
|
967
|
+
localSrc = subpath ? path.join(tmpSrc, subpath) : tmpSrc;
|
|
968
|
+
if (!fs.existsSync(localSrc)) {
|
|
969
|
+
console.error(`❌ Subdirectory not found in repo: ${subpath}`);
|
|
970
|
+
process.exit(1);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// Ensure output directory exists
|
|
975
|
+
const outputDir = path.dirname(opts.output);
|
|
976
|
+
if (!fs.existsSync(outputDir)) {
|
|
977
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
978
|
+
}
|
|
378
979
|
|
|
980
|
+
// Run crd-ref-docs
|
|
981
|
+
const args = [
|
|
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,
|
|
994
|
+
];
|
|
995
|
+
console.log(`⏳ Running crd-ref-docs ${args.join(' ')}`);
|
|
996
|
+
if (spawnSync('crd-ref-docs', args, { stdio: 'inherit' }).status !== 0) {
|
|
997
|
+
console.error(`❌ crd-ref-docs failed`);
|
|
998
|
+
process.exit(1);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
let doc = fs.readFileSync(opts.output, 'utf8');
|
|
1002
|
+
const xrefRe = /https:\/\/docs\.redpanda\.com[^\s\]\[\)"]+(?:\[[^\]]*\])?/g;
|
|
1003
|
+
doc = doc.replace(xrefRe, (match) => {
|
|
1004
|
+
let urlPart = match;
|
|
1005
|
+
let bracketPart = '';
|
|
1006
|
+
const m = match.match(/^([^\[]+)(\[[^\]]*\])$/);
|
|
1007
|
+
if (m) {
|
|
1008
|
+
urlPart = m[1];
|
|
1009
|
+
bracketPart = m[2];
|
|
1010
|
+
}
|
|
1011
|
+
if (urlPart.endsWith('#')) {
|
|
1012
|
+
return match;
|
|
1013
|
+
}
|
|
1014
|
+
try {
|
|
1015
|
+
const xref = urlToXref(urlPart);
|
|
1016
|
+
return bracketPart ? `${xref}${bracketPart}` : `${xref}[]`;
|
|
1017
|
+
} catch (err) {
|
|
1018
|
+
console.warn(`⚠️ urlToXref failed on ${urlPart}: ${err.message}`);
|
|
1019
|
+
return match;
|
|
1020
|
+
}
|
|
1021
|
+
});
|
|
1022
|
+
fs.writeFileSync(opts.output, doc, 'utf8');
|
|
1023
|
+
|
|
1024
|
+
// Cleanup
|
|
1025
|
+
if (tmpSrc) fs.rmSync(tmpSrc, { recursive: true, force: true });
|
|
1026
|
+
fs.rmSync(configTmp, { recursive: true, force: true });
|
|
1027
|
+
|
|
1028
|
+
console.log(`✅ CRD docs generated at ${opts.output}`);
|
|
1029
|
+
if (inDocs) {
|
|
1030
|
+
console.log(`ℹ️ Don't forget to commit your changes on branch '${docsBranch}'.`);
|
|
1031
|
+
}
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
programCli.addCommand(automation);
|
|
1035
|
+
programCli.parse(process.argv);
|