@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.
@@ -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}`)