@redpanda-data/docs-extensions-and-macros 5.0.4 → 5.1.1
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/algolia-indexer/generate-index.js +81 -26
- 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
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Handlebars helpers for rpk documentation generation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Convert string to dash-separated format
|
|
9
|
+
* @param {string} str - Input string
|
|
10
|
+
* @returns {string} Dashified string
|
|
11
|
+
*/
|
|
12
|
+
function dashify(str) {
|
|
13
|
+
if (!str) return ''
|
|
14
|
+
return str.replace(/\s+/g, '-')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Ensure string ends with a period
|
|
19
|
+
* @param {string} str - Input string
|
|
20
|
+
* @returns {string} String ending with period
|
|
21
|
+
*/
|
|
22
|
+
function ensurePeriod(str) {
|
|
23
|
+
if (!str) return ''
|
|
24
|
+
const trimmed = str.trim()
|
|
25
|
+
if (!trimmed) return ''
|
|
26
|
+
if (/[.!?]$/.test(trimmed)) return trimmed
|
|
27
|
+
return trimmed + '.'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Cap description to two sentences
|
|
32
|
+
* @param {string} str - Full description
|
|
33
|
+
* @returns {string} Shortened description
|
|
34
|
+
*/
|
|
35
|
+
function shortDescription(str) {
|
|
36
|
+
if (!str) return ''
|
|
37
|
+
if (typeof str !== 'string') return String(str)
|
|
38
|
+
|
|
39
|
+
// Normalize newlines to spaces for inline use
|
|
40
|
+
const singleLine = str.replace(/\s*\n+\s*/g, ' ').trim()
|
|
41
|
+
|
|
42
|
+
const abbrevs = ['e.g.', 'i.e.', 'etc.', 'vs.']
|
|
43
|
+
let normalized = singleLine
|
|
44
|
+
const placeholders = []
|
|
45
|
+
|
|
46
|
+
for (const abbrev of abbrevs) {
|
|
47
|
+
const regex = new RegExp(abbrev.replace(/\./g, '\\.'), 'gi')
|
|
48
|
+
normalized = normalized.replace(regex, match => {
|
|
49
|
+
const ph = `__ABBREV${placeholders.length}__`
|
|
50
|
+
placeholders.push({ ph, original: match })
|
|
51
|
+
return ph
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const sentences = normalized.match(/[^.!?]+[.!?]+(?:\s|$)/g)
|
|
56
|
+
if (!sentences || sentences.length === 0) {
|
|
57
|
+
let result = normalized
|
|
58
|
+
placeholders.forEach(({ ph, original }) => {
|
|
59
|
+
result = result.replace(ph, original)
|
|
60
|
+
})
|
|
61
|
+
return result.trim()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let result = sentences.slice(0, 2).join('')
|
|
65
|
+
placeholders.forEach(({ ph, original }) => {
|
|
66
|
+
result = result.replace(ph, original)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
return result.trim()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get flag type display value
|
|
74
|
+
* @param {Object} flag - Flag object
|
|
75
|
+
* @returns {string} Type string
|
|
76
|
+
*/
|
|
77
|
+
function flagType(flag) {
|
|
78
|
+
if (!flag) return '-'
|
|
79
|
+
return flag.type || '-'
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Check equality
|
|
84
|
+
* @param {*} a - First value
|
|
85
|
+
* @param {*} b - Second value
|
|
86
|
+
* @returns {boolean}
|
|
87
|
+
*/
|
|
88
|
+
function eq(a, b) {
|
|
89
|
+
return a === b
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Check inequality
|
|
94
|
+
* @param {*} a - First value
|
|
95
|
+
* @param {*} b - Second value
|
|
96
|
+
* @returns {boolean}
|
|
97
|
+
*/
|
|
98
|
+
function ne(a, b) {
|
|
99
|
+
return a !== b
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Logical OR (for any number of arguments)
|
|
104
|
+
* @param {...*} args - Values to OR (last arg is Handlebars options)
|
|
105
|
+
* @returns {boolean}
|
|
106
|
+
*/
|
|
107
|
+
function or(...args) {
|
|
108
|
+
// Remove Handlebars options object (last argument)
|
|
109
|
+
const values = args.slice(0, -1)
|
|
110
|
+
return values.some(Boolean)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Logical AND
|
|
115
|
+
* @param {...*} args - Values to AND (last arg is Handlebars options)
|
|
116
|
+
* @returns {boolean}
|
|
117
|
+
*/
|
|
118
|
+
function and(...args) {
|
|
119
|
+
const values = args.slice(0, -1)
|
|
120
|
+
return values.every(Boolean)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Logical NOT
|
|
125
|
+
* @param {*} value - Value to negate
|
|
126
|
+
* @returns {boolean}
|
|
127
|
+
*/
|
|
128
|
+
function not(value) {
|
|
129
|
+
return !value
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Join array elements
|
|
134
|
+
* @param {Array} arr - Array to join
|
|
135
|
+
* @param {string} sep - Separator
|
|
136
|
+
* @returns {string}
|
|
137
|
+
*/
|
|
138
|
+
function join(arr, sep) {
|
|
139
|
+
if (!Array.isArray(arr)) return arr
|
|
140
|
+
return arr.join(sep)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get array length
|
|
145
|
+
* @param {Array} arr - Array
|
|
146
|
+
* @returns {number}
|
|
147
|
+
*/
|
|
148
|
+
function length(arr) {
|
|
149
|
+
if (!arr) return 0
|
|
150
|
+
if (Array.isArray(arr)) return arr.length
|
|
151
|
+
if (typeof arr === 'string') return arr.length
|
|
152
|
+
return 0
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Check if value is greater than
|
|
157
|
+
* @param {number} a - First value
|
|
158
|
+
* @param {number} b - Second value
|
|
159
|
+
* @returns {boolean}
|
|
160
|
+
*/
|
|
161
|
+
function gt(a, b) {
|
|
162
|
+
return a > b
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Check if value is less than
|
|
167
|
+
* @param {number} a - First value
|
|
168
|
+
* @param {number} b - Second value
|
|
169
|
+
* @returns {boolean}
|
|
170
|
+
*/
|
|
171
|
+
function lt(a, b) {
|
|
172
|
+
return a < b
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Check if value is in array
|
|
177
|
+
* @param {*} value - Value to find
|
|
178
|
+
* @param {Array} arr - Array to search
|
|
179
|
+
* @returns {boolean}
|
|
180
|
+
*/
|
|
181
|
+
function includes(value, arr) {
|
|
182
|
+
if (!Array.isArray(arr)) return false
|
|
183
|
+
return arr.includes(value)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Convert to uppercase
|
|
188
|
+
* @param {string} str - Input string
|
|
189
|
+
* @returns {string}
|
|
190
|
+
*/
|
|
191
|
+
function uppercase(str) {
|
|
192
|
+
if (!str) return ''
|
|
193
|
+
return str.toUpperCase()
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Convert to lowercase
|
|
198
|
+
* @param {string} str - Input string
|
|
199
|
+
* @returns {string}
|
|
200
|
+
*/
|
|
201
|
+
function lowercase(str) {
|
|
202
|
+
if (!str) return ''
|
|
203
|
+
return str.toLowerCase()
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Capitalize first letter
|
|
208
|
+
* @param {string} str - Input string
|
|
209
|
+
* @returns {string}
|
|
210
|
+
*/
|
|
211
|
+
function capitalize(str) {
|
|
212
|
+
if (!str) return ''
|
|
213
|
+
return str.charAt(0).toUpperCase() + str.slice(1)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Convert to sentence case (capitalize first letter of each sentence)
|
|
218
|
+
* @param {string} str - Input string
|
|
219
|
+
* @returns {string}
|
|
220
|
+
*/
|
|
221
|
+
function toSentenceCase(str) {
|
|
222
|
+
if (!str) return ''
|
|
223
|
+
return str.replace(/(^\s*\w|[.!?]\s*\w)/g, c => c.toUpperCase())
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Format flag name with dashes
|
|
228
|
+
* @param {Object} flag - Flag object
|
|
229
|
+
* @returns {string} Formatted flag name
|
|
230
|
+
*/
|
|
231
|
+
function formatFlagName(flag) {
|
|
232
|
+
if (!flag) return ''
|
|
233
|
+
if (flag.shorthand) {
|
|
234
|
+
return `-${flag.shorthand}, --${flag.name}`
|
|
235
|
+
}
|
|
236
|
+
return `--${flag.name}`
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Format default value for display
|
|
241
|
+
* @param {*} value - Default value
|
|
242
|
+
* @param {string} type - Value type
|
|
243
|
+
* @returns {string}
|
|
244
|
+
*/
|
|
245
|
+
function formatDefault(value, type) {
|
|
246
|
+
if (value === undefined || value === null) return ''
|
|
247
|
+
if (value === '') return '""'
|
|
248
|
+
if (type === 'bool' || type === 'boolean') {
|
|
249
|
+
return value ? 'true' : 'false'
|
|
250
|
+
}
|
|
251
|
+
if (Array.isArray(value)) {
|
|
252
|
+
if (value.length === 0) return '[]'
|
|
253
|
+
return `[${value.join(', ')}]`
|
|
254
|
+
}
|
|
255
|
+
return String(value)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Check if flag has a default value worth showing
|
|
260
|
+
* @param {Object} flag - Flag object
|
|
261
|
+
* @returns {boolean}
|
|
262
|
+
*/
|
|
263
|
+
function hasDefaultValue(flag) {
|
|
264
|
+
if (!flag) return false
|
|
265
|
+
const value = flag.default
|
|
266
|
+
if (value === undefined || value === null) return false
|
|
267
|
+
if (value === '' || value === false) return false
|
|
268
|
+
if (Array.isArray(value) && value.length === 0) return false
|
|
269
|
+
return true
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Get parent command path
|
|
274
|
+
* @param {string} commandPath - Full command path
|
|
275
|
+
* @returns {string} Parent path
|
|
276
|
+
*/
|
|
277
|
+
function parentPath(commandPath) {
|
|
278
|
+
if (!commandPath) return ''
|
|
279
|
+
const parts = commandPath.split(' ')
|
|
280
|
+
if (parts.length <= 1) return ''
|
|
281
|
+
return parts.slice(0, -1).join(' ')
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Check if command is a plugin command
|
|
286
|
+
* @param {string} commandPath - Full command path
|
|
287
|
+
* @param {Object} pluginVersions - Plugin versions map
|
|
288
|
+
* @returns {boolean}
|
|
289
|
+
*/
|
|
290
|
+
function isPluginCommand(commandPath, pluginVersions) {
|
|
291
|
+
if (!commandPath || !pluginVersions) return false
|
|
292
|
+
const parts = commandPath.split(' ')
|
|
293
|
+
if (parts.length < 2) return false
|
|
294
|
+
return !!pluginVersions[parts[1]]
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Convert ALL CAPS section name to title case for display
|
|
299
|
+
* @param {string} name - ALL CAPS section name
|
|
300
|
+
* @returns {string} Title case name
|
|
301
|
+
*/
|
|
302
|
+
function sectionTitle(name) {
|
|
303
|
+
if (!name) return ''
|
|
304
|
+
|
|
305
|
+
// Acronyms that should remain ALL CAPS
|
|
306
|
+
const acronyms = new Set([
|
|
307
|
+
'id', 'ip', 'api', 'url', 'uri', 'cpu', 'gpu', 'ram', 'io',
|
|
308
|
+
'tls', 'ssl', 'mtls', 'sasl', 'oauth', 'oidc', 'jwt',
|
|
309
|
+
'json', 'yaml', 'xml', 'csv', 'http', 'https', 'grpc', 'rpc',
|
|
310
|
+
'aws', 'gcp', 'azure', 's3', 'eos'
|
|
311
|
+
])
|
|
312
|
+
|
|
313
|
+
// Product names that should be title cased
|
|
314
|
+
const properNouns = new Set([
|
|
315
|
+
'redpanda', 'kafka', 'kubernetes', 'schema', 'registry'
|
|
316
|
+
])
|
|
317
|
+
|
|
318
|
+
// Split on spaces/hyphens but keep & as its own token
|
|
319
|
+
// "PRODUCER ID & EPOCH" -> ["producer", "id", "&", "epoch"]
|
|
320
|
+
const words = name.toLowerCase().split(/[\s-]+/)
|
|
321
|
+
|
|
322
|
+
return words.map((word, index) => {
|
|
323
|
+
// Preserve ampersand as-is
|
|
324
|
+
if (word === '&') {
|
|
325
|
+
return '&'
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Handle words with slashes (e.g., "enabled/disabled")
|
|
329
|
+
if (word.includes('/')) {
|
|
330
|
+
const parts = word.split('/')
|
|
331
|
+
return parts.map((part, partIndex) => {
|
|
332
|
+
if (acronyms.has(part)) {
|
|
333
|
+
return part.toUpperCase()
|
|
334
|
+
}
|
|
335
|
+
if (index === 0 && partIndex === 0) {
|
|
336
|
+
return part.charAt(0).toUpperCase() + part.slice(1)
|
|
337
|
+
}
|
|
338
|
+
if (properNouns.has(part)) {
|
|
339
|
+
return part.charAt(0).toUpperCase() + part.slice(1)
|
|
340
|
+
}
|
|
341
|
+
return part
|
|
342
|
+
}).join('/')
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Acronyms: ALL CAPS
|
|
346
|
+
if (acronyms.has(word)) {
|
|
347
|
+
return word.toUpperCase()
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// First word: always capitalize
|
|
351
|
+
if (index === 0) {
|
|
352
|
+
return word.charAt(0).toUpperCase() + word.slice(1)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Proper nouns: capitalize
|
|
356
|
+
if (properNouns.has(word)) {
|
|
357
|
+
return word.charAt(0).toUpperCase() + word.slice(1)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// All other words in title: capitalize (standard title case)
|
|
361
|
+
return word.charAt(0).toUpperCase() + word.slice(1)
|
|
362
|
+
}).join(' ')
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Add two numbers
|
|
367
|
+
* @param {number} a - First number
|
|
368
|
+
* @param {number} b - Second number
|
|
369
|
+
* @returns {number} Sum
|
|
370
|
+
*/
|
|
371
|
+
function add(a, b) {
|
|
372
|
+
return (a || 0) + (b || 0)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Return first truthy value or default
|
|
377
|
+
* @param {*} value - Value to check
|
|
378
|
+
* @param {*} defaultValue - Default if value is falsy
|
|
379
|
+
* @returns {*} Value or default
|
|
380
|
+
*/
|
|
381
|
+
function defaultHelper(value, defaultValue) {
|
|
382
|
+
return value !== undefined && value !== null ? value : defaultValue
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Repeat a string n times (used for generating heading levels)
|
|
387
|
+
* @param {number} n - Number of repetitions
|
|
388
|
+
* @param {Object} options - Handlebars options
|
|
389
|
+
* @returns {string} Repeated content
|
|
390
|
+
*/
|
|
391
|
+
function repeat(n, options) {
|
|
392
|
+
if (!n || n <= 0) return ''
|
|
393
|
+
let result = ''
|
|
394
|
+
for (let i = 0; i < n; i++) {
|
|
395
|
+
result += options.fn(this)
|
|
396
|
+
}
|
|
397
|
+
return result
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Escape pipe characters for use in AsciiDoc table cells
|
|
402
|
+
* @param {string} str - Input string
|
|
403
|
+
* @returns {string} String with escaped pipes
|
|
404
|
+
*/
|
|
405
|
+
function escapePipes(str) {
|
|
406
|
+
if (!str) return ''
|
|
407
|
+
// Escape literal pipe characters that would break AsciiDoc tables
|
|
408
|
+
// Use {vbar} which is an AsciiDoc attribute for the pipe character
|
|
409
|
+
return str.replace(/\|/g, '{vbar}')
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Wrap description in pass:q[] if it contains inline AsciiDoc formatting
|
|
414
|
+
* This is needed for :description: attributes to properly render backticks
|
|
415
|
+
* @param {string} str - Input string
|
|
416
|
+
* @returns {string} String wrapped in pass:q[] if contains formatting
|
|
417
|
+
*/
|
|
418
|
+
function wrapDescriptionPassthrough(str) {
|
|
419
|
+
if (!str) return ''
|
|
420
|
+
// Check if contains backticks (inline code) or other formatting markers
|
|
421
|
+
if (/`/.test(str)) {
|
|
422
|
+
return `pass:q[${str}]`
|
|
423
|
+
}
|
|
424
|
+
return str
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
module.exports = {
|
|
428
|
+
dashify,
|
|
429
|
+
ensurePeriod,
|
|
430
|
+
shortDescription,
|
|
431
|
+
flagType,
|
|
432
|
+
eq,
|
|
433
|
+
ne,
|
|
434
|
+
or,
|
|
435
|
+
and,
|
|
436
|
+
not,
|
|
437
|
+
join,
|
|
438
|
+
length,
|
|
439
|
+
gt,
|
|
440
|
+
lt,
|
|
441
|
+
includes,
|
|
442
|
+
uppercase,
|
|
443
|
+
lowercase,
|
|
444
|
+
capitalize,
|
|
445
|
+
toSentenceCase,
|
|
446
|
+
formatFlagName,
|
|
447
|
+
formatDefault,
|
|
448
|
+
hasDefaultValue,
|
|
449
|
+
parentPath,
|
|
450
|
+
isPluginCommand,
|
|
451
|
+
sectionTitle,
|
|
452
|
+
add,
|
|
453
|
+
repeat,
|
|
454
|
+
default: defaultHelper,
|
|
455
|
+
escapePipes,
|
|
456
|
+
wrapDescriptionPassthrough,
|
|
457
|
+
isObject: (value) => typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
458
|
+
}
|