@redpanda-data/docs-extensions-and-macros 5.0.3 → 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/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
|
@@ -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}`)
|