@redpanda-data/docs-extensions-and-macros 5.0.4 → 5.1.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-mcp.js +30 -2
- package/bin/doc-tools.js +260 -51
- package/bin/mcp-tools/rpk-docs.js +109 -39
- package/extensions/version-fetcher/set-latest-version.js +0 -131
- package/package.json +2 -2
- package/tools/rpk-docs/DEPRECATION-SCANNING.md +192 -0
- package/tools/rpk-docs/extract-conditional-content.js +169 -0
- package/tools/rpk-docs/extract-overrides.js +566 -0
- package/tools/rpk-docs/generate-rpk-docs.js +2433 -0
- package/tools/rpk-docs/helpers/index.js +458 -0
- package/tools/rpk-docs/report-delta.js +479 -0
- package/tools/rpk-docs/rpk-docs-handler.js +1650 -0
- package/tools/rpk-docs/scan-deprecated-commands.js +295 -0
- package/tools/rpk-docs/templates/command.hbs +576 -0
- package/tools/rpk-docs/templates/content-item.hbs +106 -0
- package/tools/rpk-docs/templates/subsection.hbs +37 -0
- package/tools/rpk-docs/validate-output.js +414 -0
- package/tools/rpk-docs/validate-overrides.js +624 -0
|
@@ -135,97 +135,12 @@ module.exports.register = function ({ config }) {
|
|
|
135
135
|
}
|
|
136
136
|
});
|
|
137
137
|
|
|
138
|
-
// Find latest available Redpanda properties JSON in content catalog
|
|
139
|
-
// This ensures we use a version that actually exists, even if latest-redpanda-tag
|
|
140
|
-
// points to a newer release that doesn't have docs/attachments yet
|
|
141
|
-
// Component name is 'streaming' (or 'ROOT' for backwards compatibility)
|
|
142
|
-
const propertiesFiles = contentCatalog.findBy({
|
|
143
|
-
family: 'attachment'
|
|
144
|
-
}).filter(f => {
|
|
145
|
-
if (!f?.src?.stem || !f?.src?.component) return false;
|
|
146
|
-
const isStreamingComponent = f.src.component === 'streaming' || f.src.component === 'ROOT';
|
|
147
|
-
const isReferenceModule = f.src.module === 'reference';
|
|
148
|
-
const isPropertiesFile = f.src.stem.startsWith('redpanda-properties-');
|
|
149
|
-
return isStreamingComponent && isReferenceModule && isPropertiesFile;
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
let availablePropertiesTag = null;
|
|
153
|
-
if (propertiesFiles.length > 0) {
|
|
154
|
-
const versions = propertiesFiles
|
|
155
|
-
.map(f => f.src.stem.replace('redpanda-properties-', ''))
|
|
156
|
-
// Only accept stable versions (no prerelease suffix like -backup, -rc1, etc.)
|
|
157
|
-
.filter(v => {
|
|
158
|
-
const normalized = v.replace(/^v/, '');
|
|
159
|
-
return semver.valid(normalized) && semver.prerelease(normalized) === null;
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
if (versions.length > 0) {
|
|
163
|
-
availablePropertiesTag = versions.sort((a, b) =>
|
|
164
|
-
semver.rcompare(a.replace(/^v/, ''), b.replace(/^v/, ''))
|
|
165
|
-
)[0];
|
|
166
|
-
|
|
167
|
-
// Set on all component versions
|
|
168
|
-
components.forEach(component => {
|
|
169
|
-
component.versions.forEach(({ asciidoc }) => {
|
|
170
|
-
asciidoc.attributes['available-properties-tag'] = availablePropertiesTag;
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
logger.debug(`Found ${propertiesFiles.length} properties JSON files, latest available: ${availablePropertiesTag}`);
|
|
175
|
-
}
|
|
176
|
-
} else {
|
|
177
|
-
logger.debug('No properties JSON files found in content catalog');
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Find latest available Connect JSON in content catalog
|
|
181
|
-
// Component name is 'connect' (or 'redpanda-connect' for backwards compatibility)
|
|
182
|
-
const connectFiles = contentCatalog.findBy({
|
|
183
|
-
family: 'attachment'
|
|
184
|
-
}).filter(f => {
|
|
185
|
-
if (!f?.src?.stem || !f?.src?.component) return false;
|
|
186
|
-
const isConnectComponent = f.src.component === 'connect' || f.src.component === 'redpanda-connect';
|
|
187
|
-
const isComponentsModule = f.src.module === 'components';
|
|
188
|
-
const isConnectFile = f.src.stem.startsWith('connect-');
|
|
189
|
-
return isConnectComponent && isComponentsModule && isConnectFile;
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
let availableConnectVersion = null;
|
|
193
|
-
if (connectFiles.length > 0) {
|
|
194
|
-
const versions = connectFiles
|
|
195
|
-
.map(f => f.src.stem.replace('connect-', ''))
|
|
196
|
-
// Only accept stable versions (no prerelease suffix)
|
|
197
|
-
.filter(v => semver.valid(v) && semver.prerelease(v) === null);
|
|
198
|
-
|
|
199
|
-
if (versions.length > 0) {
|
|
200
|
-
availableConnectVersion = versions.sort((a, b) =>
|
|
201
|
-
semver.rcompare(a, b)
|
|
202
|
-
)[0];
|
|
203
|
-
|
|
204
|
-
// Set on all component versions
|
|
205
|
-
components.forEach(component => {
|
|
206
|
-
component.versions.forEach(({ asciidoc }) => {
|
|
207
|
-
asciidoc.attributes['available-connect-version'] = availableConnectVersion;
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
logger.debug(`Found ${connectFiles.length} connect JSON files, latest available: ${availableConnectVersion}`);
|
|
212
|
-
}
|
|
213
|
-
} else {
|
|
214
|
-
logger.debug('No connect JSON files found in content catalog');
|
|
215
|
-
}
|
|
216
|
-
|
|
217
138
|
logger.info('Updated Redpanda documentation versions successfully:');
|
|
218
139
|
logger.info(`- Redpanda: ${latestVersions.redpanda.latestRedpandaRelease.version}${latestVersions.redpanda.latestRcRelease ? ', beta: ' + latestVersions.redpanda.latestRcRelease.version : ''}`);
|
|
219
140
|
logger.info(`- Connect: ${latestVersions.connect}`);
|
|
220
141
|
logger.info(`- Console: ${latestVersions.console.latestStableRelease}${latestVersions.console.latestBetaRelease ? ', beta: ' + latestVersions.console.latestBetaRelease : ''}`);
|
|
221
142
|
logger.info(`- Operator: ${latestVersions.operator?.latestStableRelease || 'unknown'}${latestVersions.operator?.latestBetaRelease ? ', beta: ' + latestVersions.operator.latestBetaRelease : ''}`);
|
|
222
143
|
logger.info(`- Helm chart: ${latestVersions.helmChart?.latestStableRelease || 'unknown'}${latestVersions.helmChart?.latestBetaRelease ? ', beta: ' + latestVersions.helmChart.latestBetaRelease : ''}`);
|
|
223
|
-
if (availablePropertiesTag) {
|
|
224
|
-
logger.info(`- Available properties JSON: ${availablePropertiesTag}`);
|
|
225
|
-
}
|
|
226
|
-
if (availableConnectVersion) {
|
|
227
|
-
logger.info(`- Available Connect JSON: ${availableConnectVersion}`);
|
|
228
|
-
}
|
|
229
144
|
} catch (error) {
|
|
230
145
|
logger.error(`Error updating versions: ${error}`);
|
|
231
146
|
}
|
|
@@ -259,50 +174,4 @@ module.exports.register = function ({ config }) {
|
|
|
259
174
|
}
|
|
260
175
|
});
|
|
261
176
|
}
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
// Export helper functions for testing
|
|
265
|
-
module.exports.filterPropertiesFiles = function(files) {
|
|
266
|
-
return files.filter(f => {
|
|
267
|
-
if (!f?.src?.stem || !f?.src?.component) return false;
|
|
268
|
-
const isStreamingComponent = f.src.component === 'streaming' || f.src.component === 'ROOT';
|
|
269
|
-
const isReferenceModule = f.src.module === 'reference';
|
|
270
|
-
const isPropertiesFile = f.src.stem.startsWith('redpanda-properties-');
|
|
271
|
-
return isStreamingComponent && isReferenceModule && isPropertiesFile;
|
|
272
|
-
});
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
module.exports.filterConnectFiles = function(files) {
|
|
276
|
-
return files.filter(f => {
|
|
277
|
-
if (!f?.src?.stem || !f?.src?.component) return false;
|
|
278
|
-
const isConnectComponent = f.src.component === 'connect' || f.src.component === 'redpanda-connect';
|
|
279
|
-
const isComponentsModule = f.src.module === 'components';
|
|
280
|
-
const isConnectFile = f.src.stem.startsWith('connect-');
|
|
281
|
-
return isConnectComponent && isComponentsModule && isConnectFile;
|
|
282
|
-
});
|
|
283
|
-
};
|
|
284
|
-
|
|
285
|
-
module.exports.extractLatestPropertiesVersion = function(files, semver) {
|
|
286
|
-
const versions = files
|
|
287
|
-
.map(f => f.src.stem.replace('redpanda-properties-', ''))
|
|
288
|
-
.filter(v => {
|
|
289
|
-
const normalized = v.replace(/^v/, '');
|
|
290
|
-
return semver.valid(normalized) && semver.prerelease(normalized) === null;
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
if (versions.length === 0) return null;
|
|
294
|
-
|
|
295
|
-
return versions.sort((a, b) =>
|
|
296
|
-
semver.rcompare(a.replace(/^v/, ''), b.replace(/^v/, ''))
|
|
297
|
-
)[0];
|
|
298
|
-
};
|
|
299
|
-
|
|
300
|
-
module.exports.extractLatestConnectVersion = function(files, semver) {
|
|
301
|
-
const versions = files
|
|
302
|
-
.map(f => f.src.stem.replace('connect-', ''))
|
|
303
|
-
.filter(v => semver.valid(v) && semver.prerelease(v) === null);
|
|
304
|
-
|
|
305
|
-
if (versions.length === 0) return null;
|
|
306
|
-
|
|
307
|
-
return versions.sort((a, b) => semver.rcompare(a, b))[0];
|
|
308
177
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redpanda-data/docs-extensions-and-macros",
|
|
3
|
-
"version": "5.0
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"description": "Antora extensions and macros developed for Redpanda documentation.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"antora",
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
"@octokit/plugin-retry": "^7.1.1",
|
|
110
110
|
"@octokit/rest": "^21.1.1",
|
|
111
111
|
"@redocly/cli": "^2.2.0",
|
|
112
|
-
"ajv": "^8.
|
|
112
|
+
"ajv": "^8.20.0",
|
|
113
113
|
"algoliasearch": "^4.17.0",
|
|
114
114
|
"chalk": "4.1.2",
|
|
115
115
|
"cheerio": "^1.1.2",
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Automatic Deprecation Detection for rpk Commands
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The rpk documentation automation now automatically scans Go source code to detect commands marked as `Hidden: true` or `Deprecated: "message"` in Cobra command definitions. This ensures deprecated commands are properly documented with warnings even though they don't appear in `rpk --print-tree` output.
|
|
6
|
+
|
|
7
|
+
## How It Works
|
|
8
|
+
|
|
9
|
+
### 1. Source Code Scanning
|
|
10
|
+
|
|
11
|
+
The `scan-deprecated-commands.js` module scans the rpk Go source code looking for:
|
|
12
|
+
|
|
13
|
+
```go
|
|
14
|
+
cmd := &cobra.Command{
|
|
15
|
+
Use: "admin",
|
|
16
|
+
Hidden: true,
|
|
17
|
+
Deprecated: "use `rpk cluster` subcommands; see...",
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
It extracts:
|
|
22
|
+
- Command name (`Use` field)
|
|
23
|
+
- Hidden status (`Hidden: true`)
|
|
24
|
+
- Deprecation message (`Deprecated: "..."`)
|
|
25
|
+
|
|
26
|
+
### 2. Automatic Override Generation
|
|
27
|
+
|
|
28
|
+
When detected, the scanner automatically generates overrides in `rpk-overrides.json`:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"commands": {
|
|
33
|
+
"rpk redpanda admin": {
|
|
34
|
+
"deprecated": true,
|
|
35
|
+
"deprecatedInVersion": "v26.x",
|
|
36
|
+
"replacement": "Use xref:reference:rpk/rpk-cluster/rpk-cluster.adoc[`rpk cluster`] instead.",
|
|
37
|
+
"deprecatedMessage": "use `rpk cluster` subcommands; see...",
|
|
38
|
+
"_note": "Hidden: true, found by scanning Go source"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 3. Template Rendering
|
|
45
|
+
|
|
46
|
+
The Handlebars template (`command.hbs`) automatically renders deprecation warnings:
|
|
47
|
+
|
|
48
|
+
```handlebars
|
|
49
|
+
{{#if deprecated}}
|
|
50
|
+
[CAUTION]
|
|
51
|
+
====
|
|
52
|
+
This command is deprecated{{#if deprecatedInVersion}} as of {{deprecatedInVersion}}{{/if}}.
|
|
53
|
+
{{#if replacement}} {{{replacement}}}{{/if}}
|
|
54
|
+
====
|
|
55
|
+
{{/if}}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
This generates:
|
|
59
|
+
|
|
60
|
+
```asciidoc
|
|
61
|
+
[CAUTION]
|
|
62
|
+
====
|
|
63
|
+
This command is deprecated as of v26.x. Use xref:reference:rpk/rpk-cluster/rpk-cluster.adoc[`rpk cluster`] instead.
|
|
64
|
+
====
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Integration
|
|
68
|
+
|
|
69
|
+
The deprecation scanner runs automatically during documentation generation:
|
|
70
|
+
|
|
71
|
+
1. **After building rpk** - Source code is available
|
|
72
|
+
2. **Before generating docs** - Overrides are updated first
|
|
73
|
+
3. **Writes to overrides.json** - Automatically merges detected deprecations
|
|
74
|
+
|
|
75
|
+
To trigger manually:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Scan source and update overrides
|
|
79
|
+
node tools/rpk-docs/scan-deprecated-commands.js \
|
|
80
|
+
/path/to/redpanda/src/go/rpk \
|
|
81
|
+
docs-data/rpk-overrides.json
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Currently Detected Commands
|
|
85
|
+
|
|
86
|
+
As of the latest scan (June 8, 2026):
|
|
87
|
+
|
|
88
|
+
1. `rpk benchmark` - Hidden (internal testing command)
|
|
89
|
+
2. `rpk cloud resourcegroup` - Hidden (internal/unreleased)
|
|
90
|
+
3. `rpk redpanda admin` - **Deprecated** in favor of `rpk cluster`
|
|
91
|
+
4. `rpk redpanda admin brokers` - **Deprecated** → use `rpk cluster brokers`
|
|
92
|
+
5. `rpk redpanda admin config` - **Deprecated** → use `rpk cluster config`
|
|
93
|
+
6. `rpk redpanda admin partitions` - **Deprecated** → use `rpk cluster info --detailed`
|
|
94
|
+
|
|
95
|
+
## Limitations
|
|
96
|
+
|
|
97
|
+
### Hidden Commands Not in Tree
|
|
98
|
+
|
|
99
|
+
Commands with `Hidden: true` don't appear in `rpk --print-tree` output, so:
|
|
100
|
+
- They won't be auto-generated from scratch
|
|
101
|
+
- Existing docs files are preserved
|
|
102
|
+
- Overrides contain metadata for future use
|
|
103
|
+
|
|
104
|
+
### Subcommand Detection
|
|
105
|
+
|
|
106
|
+
The scanner finds commands with their own `NewCommand()` function but doesn't detect subcommands added via `cmd.AddCommand()`. For example:
|
|
107
|
+
|
|
108
|
+
- ✅ Detects: `rpk redpanda admin` (has NewCommand in admin.go)
|
|
109
|
+
- ❌ Misses: `rpk redpanda admin brokers list` (subcommand of brokers)
|
|
110
|
+
|
|
111
|
+
**Workaround**: Manually add subcommand deprecations to overrides or enhance scanner to follow AddCommand calls.
|
|
112
|
+
|
|
113
|
+
## File Locations
|
|
114
|
+
|
|
115
|
+
- **Scanner**: `tools/rpk-docs/scan-deprecated-commands.js`
|
|
116
|
+
- **Integration**: `tools/rpk-docs/rpk-docs-handler.js` (Step 5)
|
|
117
|
+
- **Template**: `tools/rpk-docs/templates/command.hbs`
|
|
118
|
+
- **Overrides**: `docs-data/rpk-overrides.json`
|
|
119
|
+
|
|
120
|
+
## Future Enhancements
|
|
121
|
+
|
|
122
|
+
1. **Follow AddCommand chains** - Detect all subcommand deprecations
|
|
123
|
+
2. **Extract deprecation versions** - Parse version info from comments/git history
|
|
124
|
+
3. **Build xref links automatically** - Generate xrefs from deprecation messages
|
|
125
|
+
4. **Scan for removed commands** - Compare against previous versions to detect removals
|
|
126
|
+
|
|
127
|
+
## Example: rpk redpanda admin
|
|
128
|
+
|
|
129
|
+
### Source Code
|
|
130
|
+
|
|
131
|
+
```go
|
|
132
|
+
func NewCommand(fs afero.Fs, p *config.Params) *cobra.Command {
|
|
133
|
+
cmd := &cobra.Command{
|
|
134
|
+
Use: "admin",
|
|
135
|
+
Short: "Talk to the Redpanda admin listener",
|
|
136
|
+
Hidden: true,
|
|
137
|
+
Deprecated: "use `rpk cluster` subcommands; see `rpk cluster brokers`, `rpk cluster info --detailed`, `rpk cluster config list --node-id`, and `rpk cluster loggers`",
|
|
138
|
+
}
|
|
139
|
+
// ...
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Generated Override
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"rpk redpanda admin": {
|
|
148
|
+
"deprecated": true,
|
|
149
|
+
"replacement": "Use xref:reference:rpk/rpk-cluster/rpk-cluster.adoc[`rpk cluster`] instead.",
|
|
150
|
+
"deprecatedMessage": "use `rpk cluster` subcommands; see `rpk cluster brokers`, `rpk cluster info --detailed`, `rpk cluster config list --node-id`, and `rpk cluster loggers`",
|
|
151
|
+
"_note": "Hidden: true, found by scanning Go source"
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Generated Documentation
|
|
157
|
+
|
|
158
|
+
```asciidoc
|
|
159
|
+
= rpk redpanda admin
|
|
160
|
+
:unsupported-os: macOS, Windows
|
|
161
|
+
|
|
162
|
+
include::reference:partial$unsupported-os-rpk.adoc[]
|
|
163
|
+
|
|
164
|
+
[CAUTION]
|
|
165
|
+
====
|
|
166
|
+
This command is deprecated. Use xref:reference:rpk/rpk-cluster/rpk-cluster.adoc[`rpk cluster`] instead.
|
|
167
|
+
====
|
|
168
|
+
|
|
169
|
+
Talk to the Redpanda admin listener.
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Testing
|
|
173
|
+
|
|
174
|
+
Test the scanner:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# Test on local source
|
|
178
|
+
node tools/rpk-docs/scan-deprecated-commands.js ~/Documents/redpanda/src/go/rpk
|
|
179
|
+
|
|
180
|
+
# Test with override merging
|
|
181
|
+
node tools/rpk-docs/scan-deprecated-commands.js \
|
|
182
|
+
~/Documents/redpanda/src/go/rpk \
|
|
183
|
+
docs-data/rpk-overrides.json
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Test full automation:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
cd /path/to/docs
|
|
190
|
+
npx doc-tools generate rpk-docs --ref dev
|
|
191
|
+
# Check for "Scanning source for deprecated/hidden commands..." in output
|
|
192
|
+
```
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extract conditional blocks (ifdef/ifndef) and includes from original rpk docs
|
|
5
|
+
* and add them to rpk-overrides.json
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs')
|
|
9
|
+
const path = require('path')
|
|
10
|
+
const { spawnSync } = require('child_process')
|
|
11
|
+
|
|
12
|
+
const docsDir = process.argv[2] || '../docs'
|
|
13
|
+
const rpkDir = path.join(docsDir, 'modules/reference/pages/rpk')
|
|
14
|
+
const overridesPath = path.join(__dirname, '../../docs-data/rpk-overrides.json')
|
|
15
|
+
|
|
16
|
+
// Validate docsDir exists and is a directory
|
|
17
|
+
if (!fs.existsSync(docsDir) || !fs.statSync(docsDir).isDirectory()) {
|
|
18
|
+
console.error(`ERROR: Invalid docs directory: ${docsDir}`)
|
|
19
|
+
process.exit(1)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
console.log('Extracting conditional content from original rpk docs...')
|
|
23
|
+
console.log(` Docs directory: ${docsDir}`)
|
|
24
|
+
console.log(` Overrides file: ${overridesPath}`)
|
|
25
|
+
|
|
26
|
+
// Get list of modified files using spawnSync to avoid command injection
|
|
27
|
+
const gitDiffResult = spawnSync('git', ['diff', '--name-only', 'modules/reference/pages/rpk/'], {
|
|
28
|
+
cwd: docsDir,
|
|
29
|
+
encoding: 'utf8'
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
if (gitDiffResult.error) {
|
|
33
|
+
console.error(`ERROR: Failed to run git diff: ${gitDiffResult.error.message}`)
|
|
34
|
+
process.exit(1)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const modifiedFilesOutput = gitDiffResult.stdout
|
|
38
|
+
|
|
39
|
+
const modifiedFiles = modifiedFilesOutput
|
|
40
|
+
.trim()
|
|
41
|
+
.split('\n')
|
|
42
|
+
.filter(f => f.endsWith('.adoc'))
|
|
43
|
+
|
|
44
|
+
console.log(`\nFound ${modifiedFiles.length} modified files`)
|
|
45
|
+
|
|
46
|
+
// Load existing overrides
|
|
47
|
+
let overrides = {}
|
|
48
|
+
if (fs.existsSync(overridesPath)) {
|
|
49
|
+
overrides = JSON.parse(fs.readFileSync(overridesPath, 'utf8'))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let extractedCount = 0
|
|
53
|
+
let filesWithContent = 0
|
|
54
|
+
|
|
55
|
+
for (const relPath of modifiedFiles) {
|
|
56
|
+
// Get the command path from filename
|
|
57
|
+
// e.g., "rpk/rpk-cluster/rpk-cluster-config.adoc" → "rpk cluster config"
|
|
58
|
+
const filename = path.basename(relPath, '.adoc')
|
|
59
|
+
const commandPath = filename.replace(/-/g, ' ')
|
|
60
|
+
|
|
61
|
+
// Get the original content (before changes)
|
|
62
|
+
try {
|
|
63
|
+
const gitShowResult = spawnSync('git', ['show', `HEAD:${relPath}`], {
|
|
64
|
+
cwd: docsDir,
|
|
65
|
+
encoding: 'utf8'
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
if (gitShowResult.error || gitShowResult.status !== 0) {
|
|
69
|
+
throw new Error(gitShowResult.stderr || 'git show failed')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const originalContent = gitShowResult.stdout
|
|
73
|
+
|
|
74
|
+
// Extract conditional blocks and includes
|
|
75
|
+
const conditionals = []
|
|
76
|
+
const includes = []
|
|
77
|
+
|
|
78
|
+
const lines = originalContent.split('\n')
|
|
79
|
+
let inConditional = false
|
|
80
|
+
let conditionalStart = -1
|
|
81
|
+
let conditionalLines = []
|
|
82
|
+
let conditionalType = null
|
|
83
|
+
|
|
84
|
+
for (let i = 0; i < lines.length; i++) {
|
|
85
|
+
const line = lines[i]
|
|
86
|
+
|
|
87
|
+
// Check for conditional start
|
|
88
|
+
if (/^(ifdef|ifndef)::/.test(line)) {
|
|
89
|
+
inConditional = true
|
|
90
|
+
conditionalStart = i
|
|
91
|
+
conditionalLines = [line]
|
|
92
|
+
conditionalType = line
|
|
93
|
+
} else if (inConditional) {
|
|
94
|
+
conditionalLines.push(line)
|
|
95
|
+
if (/^endif::/.test(line)) {
|
|
96
|
+
// End of conditional block
|
|
97
|
+
conditionals.push({
|
|
98
|
+
startLine: conditionalStart,
|
|
99
|
+
endLine: i,
|
|
100
|
+
type: conditionalType,
|
|
101
|
+
content: conditionalLines.join('\n')
|
|
102
|
+
})
|
|
103
|
+
inConditional = false
|
|
104
|
+
conditionalLines = []
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check for includes
|
|
109
|
+
if (/^include::/.test(line)) {
|
|
110
|
+
includes.push({
|
|
111
|
+
line: i,
|
|
112
|
+
content: line
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Add to overrides if we found content
|
|
118
|
+
if (conditionals.length > 0 || includes.length > 0) {
|
|
119
|
+
filesWithContent++
|
|
120
|
+
|
|
121
|
+
if (!overrides[commandPath]) {
|
|
122
|
+
overrides[commandPath] = { content: [] }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!overrides[commandPath].content) {
|
|
126
|
+
overrides[commandPath].content = []
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Add conditionals
|
|
130
|
+
for (const cond of conditionals) {
|
|
131
|
+
const lines = cond.content.split('\n')
|
|
132
|
+
const isCloudOnly = cond.type.includes('ifdef::env-cloud')
|
|
133
|
+
const isSelfHostedOnly = cond.type.includes('ifndef::env-cloud')
|
|
134
|
+
|
|
135
|
+
// Extract the actual content (skip ifdef/endif lines)
|
|
136
|
+
const innerContent = lines.slice(1, -1).join('\n').trim()
|
|
137
|
+
|
|
138
|
+
if (innerContent) {
|
|
139
|
+
overrides[commandPath].content.push({
|
|
140
|
+
type: isCloudOnly ? 'cloud-only' : 'self-hosted',
|
|
141
|
+
position: 'after_description',
|
|
142
|
+
content: innerContent,
|
|
143
|
+
note: `Extracted from line ${cond.startLine + 1}`
|
|
144
|
+
})
|
|
145
|
+
extractedCount++
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Add includes
|
|
150
|
+
for (const inc of includes) {
|
|
151
|
+
overrides[commandPath].content.push({
|
|
152
|
+
type: 'include',
|
|
153
|
+
position: 'after_description',
|
|
154
|
+
path: inc.content,
|
|
155
|
+
note: `Extracted from line ${inc.line + 1}`
|
|
156
|
+
})
|
|
157
|
+
extractedCount++
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
} catch (err) {
|
|
161
|
+
console.warn(` ⚠ Could not read original ${relPath}: ${err.message}`)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
console.log(`\nExtracted ${extractedCount} content items from ${filesWithContent} files`)
|
|
166
|
+
|
|
167
|
+
// Save overrides
|
|
168
|
+
fs.writeFileSync(overridesPath, JSON.stringify(overrides, null, 2))
|
|
169
|
+
console.log(`\n✓ Updated ${overridesPath}`)
|