@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.
@@ -0,0 +1,295 @@
1
+ /**
2
+ * Scan rpk Go source code for hidden and deprecated commands
3
+ *
4
+ * Extracts commands marked with Hidden: true or Deprecated: "message"
5
+ * to ensure they're included in documentation with proper deprecation notices.
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ /**
12
+ * Scan a Go file for cobra command definitions with deprecation/hidden status
13
+ * @param {string} filePath - Path to Go file
14
+ * @returns {Object|null} Command metadata if deprecated/hidden, null otherwise
15
+ */
16
+ function scanGoFile(filePath) {
17
+ const content = fs.readFileSync(filePath, 'utf8');
18
+
19
+ // Look for NewCommand function that returns *cobra.Command
20
+ const newCommandMatch = content.match(/func NewCommand\([^)]*\)\s*\*cobra\.Command\s*\{/);
21
+ if (!newCommandMatch) {
22
+ return null;
23
+ }
24
+
25
+ // Extract the cobra.Command struct definition
26
+ // Handle nested braces and multiline strings
27
+ let cmdBlock = '';
28
+ let braceCount = 0;
29
+ let startIndex = content.indexOf('&cobra.Command{');
30
+
31
+ if (startIndex === -1) {
32
+ return null;
33
+ }
34
+
35
+ startIndex += '&cobra.Command{'.length;
36
+ for (let i = startIndex; i < content.length; i++) {
37
+ const char = content[i];
38
+ if (char === '{') braceCount++;
39
+ if (char === '}') {
40
+ if (braceCount === 0) break;
41
+ braceCount--;
42
+ }
43
+ cmdBlock += char;
44
+ }
45
+
46
+ // Extract fields - handle multiline strings with backticks or quotes
47
+ const useMatch = cmdBlock.match(/Use:\s*"([^"]+)"/);
48
+ const shortMatch = cmdBlock.match(/Short:\s*"([^"]+)"/);
49
+ const longMatch = cmdBlock.match(/Long:\s*`([^`]+)`|Long:\s*"([^"]+)"/s);
50
+ const hiddenMatch = cmdBlock.match(/Hidden:\s*true/);
51
+ const deprecatedMatch = cmdBlock.match(/Deprecated:\s*"([^"]+(?:[^"\\]|\\.)*)"/s);
52
+
53
+ // Only return if hidden or deprecated
54
+ if (!hiddenMatch && !deprecatedMatch) {
55
+ return null;
56
+ }
57
+
58
+ const result = {
59
+ use: useMatch ? useMatch[1] : null,
60
+ short: shortMatch ? shortMatch[1] : null,
61
+ long: longMatch ? (longMatch[1] || longMatch[2]) : null,
62
+ hidden: !!hiddenMatch,
63
+ deprecated: !!deprecatedMatch,
64
+ deprecatedMessage: deprecatedMatch ? deprecatedMatch[1].replace(/\n\s*/g, ' ').trim() : null
65
+ };
66
+
67
+ return result;
68
+ }
69
+
70
+ /**
71
+ * Build command path from directory structure
72
+ * @param {string} filePath - Path to Go file (relative to rpk root)
73
+ * @param {string} rpkRoot - Path to rpk source root
74
+ * @returns {string} Command path (e.g., "rpk redpanda admin")
75
+ */
76
+ function buildCommandPath(filePath, rpkRoot) {
77
+ const relativePath = path.relative(rpkRoot, filePath);
78
+ const parts = relativePath.split(path.sep);
79
+
80
+ // Remove filename, and filter out structural directories
81
+ // Structure is either:
82
+ // pkg/cli/<group>/<command>/file.go
83
+ // cmd/rpk/<command>/file.go
84
+ const commandParts = [];
85
+ let inCommandPath = false;
86
+
87
+ for (const part of parts) {
88
+ if (part.endsWith('.go')) continue;
89
+
90
+ // Start collecting after 'cli' or after 'rpk' in cmd
91
+ if (part === 'cli' || (part === 'rpk' && parts.includes('cmd'))) {
92
+ inCommandPath = true;
93
+ continue;
94
+ }
95
+
96
+ if (inCommandPath) {
97
+ commandParts.push(part);
98
+ }
99
+ }
100
+
101
+ // Build path: rpk + directory hierarchy
102
+ return 'rpk ' + commandParts.join(' ');
103
+ }
104
+
105
+ /**
106
+ * Recursively scan directory for deprecated commands
107
+ * @param {string} dir - Directory to scan
108
+ * @param {string} rpkRoot - Path to rpk source root
109
+ * @param {Array} results - Accumulator for results
110
+ */
111
+ function scanDirectory(dir, rpkRoot, results = []) {
112
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
113
+
114
+ for (const entry of entries) {
115
+ const fullPath = path.join(dir, entry.name);
116
+
117
+ if (entry.isDirectory()) {
118
+ scanDirectory(fullPath, rpkRoot, results);
119
+ } else if (entry.isFile() && entry.name.endsWith('.go')) {
120
+ const metadata = scanGoFile(fullPath);
121
+ if (metadata) {
122
+ const commandPath = buildCommandPath(fullPath, rpkRoot);
123
+ results.push({
124
+ commandPath,
125
+ filePath: path.relative(rpkRoot, fullPath),
126
+ ...metadata
127
+ });
128
+ }
129
+ }
130
+ }
131
+
132
+ return results;
133
+ }
134
+
135
+ /**
136
+ * Extract deprecation info and convert to override format
137
+ * @param {Object} cmdMetadata - Command metadata from source scan
138
+ * @returns {Object} Override object for rpk-overrides.json
139
+ */
140
+ function buildOverrideFromDeprecation(cmdMetadata) {
141
+ const override = {
142
+ deprecated: true,
143
+ _note: `Hidden: ${cmdMetadata.hidden}, found by scanning Go source`
144
+ };
145
+
146
+ if (cmdMetadata.deprecatedMessage) {
147
+ // Parse the deprecation message to extract replacement info
148
+ const message = cmdMetadata.deprecatedMessage;
149
+
150
+ // Common patterns:
151
+ // "use `rpk cluster` subcommands; see ..."
152
+ // "use `rpk cluster brokers` instead"
153
+
154
+ if (message.includes('use `') && message.includes('`')) {
155
+ // Extract the replacement command
156
+ const cmdMatch = message.match(/use `([^`]+)`/);
157
+ if (cmdMatch) {
158
+ const replacementCmd = cmdMatch[1];
159
+
160
+ // Try to build xref
161
+ if (replacementCmd.startsWith('rpk ')) {
162
+ const parts = replacementCmd.split(' ');
163
+ const xrefPath = parts.slice(1).join('/');
164
+ const filename = parts.slice(1).join('-');
165
+ override.replacement = `Use xref:reference:rpk/rpk-${xrefPath}/rpk-${filename}.adoc[\`${replacementCmd}\`] instead.`;
166
+ } else {
167
+ override.replacement = `Use \`${replacementCmd}\` instead.`;
168
+ }
169
+ }
170
+
171
+ // Add full deprecation message if it has additional context
172
+ if (message.length > 50 || message.includes(';')) {
173
+ override.deprecatedMessage = message;
174
+ }
175
+ } else {
176
+ override.deprecatedMessage = message;
177
+ }
178
+ }
179
+
180
+ return override;
181
+ }
182
+
183
+ /**
184
+ * Scan rpk source for deprecated/hidden commands
185
+ * @param {string} sourcePath - Path to rpk source (src/go/rpk)
186
+ * @returns {Object} Map of command paths to override metadata
187
+ */
188
+ function scanDeprecatedCommands(sourcePath) {
189
+ console.log('Scanning Go source for deprecated/hidden commands...');
190
+
191
+ // Scan both cmd/rpk (old structure) and pkg/cli (current structure)
192
+ const dirsToScan = [
193
+ path.join(sourcePath, 'cmd', 'rpk'),
194
+ path.join(sourcePath, 'pkg', 'cli')
195
+ ];
196
+
197
+ const results = [];
198
+
199
+ for (const dir of dirsToScan) {
200
+ if (fs.existsSync(dir)) {
201
+ console.log(`Scanning ${dir}...`);
202
+ scanDirectory(dir, sourcePath, results);
203
+ }
204
+ }
205
+
206
+ console.log(`Found ${results.length} deprecated/hidden command(s):`);
207
+
208
+ const overrides = {};
209
+ for (const cmd of results) {
210
+ console.log(` - ${cmd.commandPath}`);
211
+ if (cmd.deprecatedMessage) {
212
+ console.log(` Deprecated: ${cmd.deprecatedMessage.substring(0, 80)}${cmd.deprecatedMessage.length > 80 ? '...' : ''}`);
213
+ }
214
+ if (cmd.hidden) {
215
+ console.log(` Hidden: true`);
216
+ }
217
+
218
+ overrides[cmd.commandPath] = buildOverrideFromDeprecation(cmd);
219
+ }
220
+
221
+ return overrides;
222
+ }
223
+
224
+ /**
225
+ * Merge deprecated commands into existing tree
226
+ * @param {Object} tree - Command tree from rpk --print-tree
227
+ * @param {Object} deprecatedOverrides - Overrides from source scanning
228
+ * @param {Object} existingOverrides - Existing overrides.json data
229
+ * @returns {Object} Tree with deprecated commands added
230
+ */
231
+ function mergeDeprecatedCommands(tree, deprecatedOverrides, existingOverrides) {
232
+ // For now, just return the overrides to be merged into overrides.json
233
+ // The actual command data will come from the old docs or manual creation
234
+
235
+ console.log('\nMerging deprecated commands into overrides...');
236
+
237
+ if (!existingOverrides.commands) {
238
+ existingOverrides.commands = {};
239
+ }
240
+
241
+ let added = 0;
242
+ let updated = 0;
243
+
244
+ for (const [cmdPath, metadata] of Object.entries(deprecatedOverrides)) {
245
+ if (!existingOverrides.commands[cmdPath]) {
246
+ existingOverrides.commands[cmdPath] = metadata;
247
+ added++;
248
+ } else {
249
+ // Merge, keeping existing content but adding deprecation metadata
250
+ existingOverrides.commands[cmdPath] = {
251
+ ...existingOverrides.commands[cmdPath],
252
+ deprecated: metadata.deprecated,
253
+ replacement: metadata.replacement || existingOverrides.commands[cmdPath].replacement,
254
+ deprecatedMessage: metadata.deprecatedMessage || existingOverrides.commands[cmdPath].deprecatedMessage,
255
+ _note: metadata._note
256
+ };
257
+ updated++;
258
+ }
259
+ }
260
+
261
+ console.log(`Added ${added} new deprecated commands`);
262
+ console.log(`Updated ${updated} existing commands with deprecation info`);
263
+
264
+ return existingOverrides;
265
+ }
266
+
267
+ module.exports = {
268
+ scanDeprecatedCommands,
269
+ mergeDeprecatedCommands,
270
+ scanGoFile,
271
+ buildCommandPath
272
+ };
273
+
274
+ // CLI usage
275
+ if (require.main === module) {
276
+ const sourcePath = process.argv[2];
277
+ const overridesPath = process.argv[3];
278
+
279
+ if (!sourcePath) {
280
+ console.error('Usage: node scan-deprecated-commands.js <rpk-source-path> [overrides-json-path]');
281
+ process.exit(1);
282
+ }
283
+
284
+ const deprecated = scanDeprecatedCommands(sourcePath);
285
+
286
+ if (overridesPath) {
287
+ const existingOverrides = JSON.parse(fs.readFileSync(overridesPath, 'utf8'));
288
+ const updated = mergeDeprecatedCommands(null, deprecated, existingOverrides);
289
+ fs.writeFileSync(overridesPath, JSON.stringify(updated, null, 2));
290
+ console.log(`\nUpdated overrides file: ${overridesPath}`);
291
+ } else {
292
+ console.log('\nDeprecated commands (JSON):');
293
+ console.log(JSON.stringify(deprecated, null, 2));
294
+ }
295
+ }