@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
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Extract custom content from existing rpk docs and generate override suggestions
|
|
8
|
+
*
|
|
9
|
+
* This tool scans existing rpk documentation files, identifies editorial content
|
|
10
|
+
* that would be lost during auto-generation, and produces override entries
|
|
11
|
+
* that preserve this content.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Parse an AsciiDoc file and extract its structure
|
|
16
|
+
* @param {string} content - File content
|
|
17
|
+
* @returns {Object} Parsed structure
|
|
18
|
+
*/
|
|
19
|
+
function parseAsciiDoc(content) {
|
|
20
|
+
const result = {
|
|
21
|
+
title: '',
|
|
22
|
+
attributes: {},
|
|
23
|
+
description: '',
|
|
24
|
+
includesAfterHeader: [],
|
|
25
|
+
includesAfterDescription: [],
|
|
26
|
+
usageSection: '',
|
|
27
|
+
flagsSection: '',
|
|
28
|
+
customSections: [],
|
|
29
|
+
conditionalBlocks: [],
|
|
30
|
+
contentAfterFlags: '',
|
|
31
|
+
seeAlso: [],
|
|
32
|
+
rawContent: content
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const lines = content.split('\n')
|
|
36
|
+
let currentSection = 'header'
|
|
37
|
+
let sectionContent = []
|
|
38
|
+
let inConditional = null
|
|
39
|
+
let conditionalContent = []
|
|
40
|
+
let conditionalDepth = 0
|
|
41
|
+
|
|
42
|
+
for (let i = 0; i < lines.length; i++) {
|
|
43
|
+
const line = lines[i]
|
|
44
|
+
const trimmedLine = line.trim()
|
|
45
|
+
|
|
46
|
+
// Extract title
|
|
47
|
+
if (trimmedLine.startsWith('= ') && !result.title) {
|
|
48
|
+
result.title = trimmedLine.substring(2).trim()
|
|
49
|
+
continue
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Extract attributes
|
|
53
|
+
if (trimmedLine.startsWith(':') && trimmedLine.includes(':') && currentSection === 'header') {
|
|
54
|
+
const match = trimmedLine.match(/^:([^:]+):\s*(.*)$/)
|
|
55
|
+
if (match) {
|
|
56
|
+
result.attributes[match[1]] = match[2]
|
|
57
|
+
}
|
|
58
|
+
continue
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Track conditional blocks (ifdef/ifndef)
|
|
62
|
+
if (trimmedLine.startsWith('ifdef::') || trimmedLine.startsWith('ifndef::')) {
|
|
63
|
+
if (conditionalDepth === 0) {
|
|
64
|
+
inConditional = {
|
|
65
|
+
type: trimmedLine.startsWith('ifdef::') ? 'ifdef' : 'ifndef',
|
|
66
|
+
condition: trimmedLine.match(/::(.*)\[\]/)?.[1] || '',
|
|
67
|
+
content: [],
|
|
68
|
+
startLine: i
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
conditionalDepth++
|
|
72
|
+
if (conditionalDepth > 1) {
|
|
73
|
+
inConditional.content.push(line)
|
|
74
|
+
}
|
|
75
|
+
continue
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (trimmedLine === 'endif::[]') {
|
|
79
|
+
conditionalDepth--
|
|
80
|
+
if (conditionalDepth === 0 && inConditional) {
|
|
81
|
+
inConditional.endLine = i
|
|
82
|
+
result.conditionalBlocks.push(inConditional)
|
|
83
|
+
inConditional = null
|
|
84
|
+
} else if (conditionalDepth > 0 && inConditional) {
|
|
85
|
+
inConditional.content.push(line)
|
|
86
|
+
}
|
|
87
|
+
continue
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (inConditional && conditionalDepth > 0) {
|
|
91
|
+
inConditional.content.push(line)
|
|
92
|
+
continue
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Extract includes
|
|
96
|
+
if (trimmedLine.startsWith('include::')) {
|
|
97
|
+
const includePath = trimmedLine.match(/include::([^\[]+)/)?.[1]
|
|
98
|
+
if (includePath) {
|
|
99
|
+
if (currentSection === 'header' || currentSection === 'description') {
|
|
100
|
+
result.includesAfterHeader.push(includePath)
|
|
101
|
+
} else {
|
|
102
|
+
result.includesAfterDescription.push(includePath)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
continue
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Detect section transitions
|
|
109
|
+
if (trimmedLine === '== Usage') {
|
|
110
|
+
if (currentSection === 'header' || currentSection === 'description') {
|
|
111
|
+
result.description = sectionContent.join('\n').trim()
|
|
112
|
+
}
|
|
113
|
+
currentSection = 'usage'
|
|
114
|
+
sectionContent = []
|
|
115
|
+
continue
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (trimmedLine === '== Flags') {
|
|
119
|
+
if (currentSection === 'usage') {
|
|
120
|
+
result.usageSection = sectionContent.join('\n').trim()
|
|
121
|
+
}
|
|
122
|
+
currentSection = 'flags'
|
|
123
|
+
sectionContent = []
|
|
124
|
+
continue
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (trimmedLine === '== Global flags') {
|
|
128
|
+
if (currentSection === 'flags') {
|
|
129
|
+
result.flagsSection = sectionContent.join('\n').trim()
|
|
130
|
+
}
|
|
131
|
+
currentSection = 'globalflags'
|
|
132
|
+
sectionContent = []
|
|
133
|
+
continue
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Detect custom sections (== or === headers after flags)
|
|
137
|
+
if ((trimmedLine.startsWith('== ') || trimmedLine.startsWith('=== ')) &&
|
|
138
|
+
(currentSection === 'flags' || currentSection === 'globalflags' || currentSection === 'custom')) {
|
|
139
|
+
|
|
140
|
+
// Save previous section
|
|
141
|
+
if (currentSection === 'flags') {
|
|
142
|
+
result.flagsSection = sectionContent.join('\n').trim()
|
|
143
|
+
} else if (sectionContent.length > 0) {
|
|
144
|
+
const lastCustom = result.customSections[result.customSections.length - 1]
|
|
145
|
+
if (lastCustom) {
|
|
146
|
+
lastCustom.content = sectionContent.join('\n').trim()
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Start new custom section
|
|
151
|
+
const level = trimmedLine.startsWith('=== ') ? 3 : 2
|
|
152
|
+
const title = trimmedLine.substring(level + 1).trim()
|
|
153
|
+
|
|
154
|
+
// Check if it's a "See also" or "Related topics" section
|
|
155
|
+
if (title.toLowerCase() === 'see also' || title.toLowerCase() === 'related topics') {
|
|
156
|
+
currentSection = 'seealso'
|
|
157
|
+
} else {
|
|
158
|
+
result.customSections.push({
|
|
159
|
+
title,
|
|
160
|
+
level,
|
|
161
|
+
content: ''
|
|
162
|
+
})
|
|
163
|
+
currentSection = 'custom'
|
|
164
|
+
}
|
|
165
|
+
sectionContent = []
|
|
166
|
+
continue
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Collect content
|
|
170
|
+
sectionContent.push(line)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Handle remaining content
|
|
174
|
+
if (currentSection === 'seealso') {
|
|
175
|
+
// Extract see also links
|
|
176
|
+
const seeAlsoContent = sectionContent.join('\n')
|
|
177
|
+
const xrefMatches = seeAlsoContent.matchAll(/xref:([^\[]+)\[([^\]]*)\]/g)
|
|
178
|
+
for (const match of xrefMatches) {
|
|
179
|
+
result.seeAlso.push(`xref:${match[1]}[${match[2]}]`)
|
|
180
|
+
}
|
|
181
|
+
} else if (currentSection === 'custom' && result.customSections.length > 0) {
|
|
182
|
+
result.customSections[result.customSections.length - 1].content = sectionContent.join('\n').trim()
|
|
183
|
+
} else if (currentSection === 'flags') {
|
|
184
|
+
result.flagsSection = sectionContent.join('\n').trim()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return result
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Determine the command path from a file path
|
|
192
|
+
* @param {string} filePath - Path to the .adoc file
|
|
193
|
+
* @param {string} baseDir - Base rpk docs directory
|
|
194
|
+
* @returns {string} Command path (e.g., "rpk topic create")
|
|
195
|
+
*/
|
|
196
|
+
function filePathToCommandPath(filePath, baseDir) {
|
|
197
|
+
const relativePath = path.relative(baseDir, filePath)
|
|
198
|
+
const withoutExt = relativePath.replace(/\.adoc$/, '')
|
|
199
|
+
|
|
200
|
+
// Convert path separators and dashes to spaces
|
|
201
|
+
// e.g., "rpk-topic/rpk-topic-create" -> "rpk topic create"
|
|
202
|
+
const parts = withoutExt.split(path.sep)
|
|
203
|
+
const lastPart = parts[parts.length - 1]
|
|
204
|
+
|
|
205
|
+
return lastPart.replace(/-/g, ' ')
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Clean up extracted content by removing markers and noise
|
|
210
|
+
* @param {string} content
|
|
211
|
+
* @returns {string}
|
|
212
|
+
*/
|
|
213
|
+
function cleanContent(content) {
|
|
214
|
+
if (!content) return ''
|
|
215
|
+
return content
|
|
216
|
+
// Remove single-source tags
|
|
217
|
+
.replace(/\/\/\s*tag::single-source\[\]\s*/g, '')
|
|
218
|
+
.replace(/\/\/\s*end::single-source\[\]\s*/g, '')
|
|
219
|
+
// Remove trailing endif markers that got captured
|
|
220
|
+
.replace(/\s*endif::\[\]\s*$/g, '')
|
|
221
|
+
// Clean up multiple blank lines
|
|
222
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
223
|
+
.trim()
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Check if content is meaningful (not just whitespace or comments)
|
|
228
|
+
* @param {string} content
|
|
229
|
+
* @returns {boolean}
|
|
230
|
+
*/
|
|
231
|
+
function isMeaningfulContent(content) {
|
|
232
|
+
if (!content) return false
|
|
233
|
+
const cleaned = cleanContent(content)
|
|
234
|
+
if (!cleaned) return false
|
|
235
|
+
return true
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Extract override suggestions from a parsed document
|
|
240
|
+
* @param {Object} parsed - Parsed AsciiDoc structure
|
|
241
|
+
* @param {string} commandPath - Command path
|
|
242
|
+
* @returns {Object|null} Override suggestion or null if no custom content
|
|
243
|
+
*/
|
|
244
|
+
function extractOverrideSuggestion(parsed, commandPath) {
|
|
245
|
+
const override = {}
|
|
246
|
+
let hasCustomContent = false
|
|
247
|
+
|
|
248
|
+
// Check for custom description (more than just the auto-generated short desc)
|
|
249
|
+
if (parsed.description && parsed.description.length > 100) {
|
|
250
|
+
// Check if description has multiple paragraphs or detailed content
|
|
251
|
+
const paragraphs = parsed.description.split(/\n\n+/).filter(p => p.trim())
|
|
252
|
+
if (paragraphs.length > 1) {
|
|
253
|
+
override.appendToDescription = cleanContent(paragraphs.slice(1).join('\n\n'))
|
|
254
|
+
hasCustomContent = true
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Check for includes
|
|
259
|
+
if (parsed.includesAfterHeader.length > 0) {
|
|
260
|
+
override.includes = override.includes || {}
|
|
261
|
+
override.includes.after_header = parsed.includesAfterHeader
|
|
262
|
+
hasCustomContent = true
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Check for unsupported-os attribute - convert to a note
|
|
266
|
+
if (parsed.attributes['unsupported-os']) {
|
|
267
|
+
const unsupportedOs = parsed.attributes['unsupported-os']
|
|
268
|
+
override.notes = override.notes || {}
|
|
269
|
+
override.notes.after_header = `This command is not supported on ${unsupportedOs}.`
|
|
270
|
+
hasCustomContent = true
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Check for custom sections
|
|
274
|
+
if (parsed.customSections.length > 0) {
|
|
275
|
+
override.customSections = {}
|
|
276
|
+
for (const section of parsed.customSections) {
|
|
277
|
+
const cleanedContent = cleanContent(section.content)
|
|
278
|
+
if (isMeaningfulContent(cleanedContent)) {
|
|
279
|
+
const key = section.title.toLowerCase().replace(/\s+/g, '-')
|
|
280
|
+
override.customSections[key] = {
|
|
281
|
+
title: section.title,
|
|
282
|
+
content: cleanedContent,
|
|
283
|
+
position: 'after_flags'
|
|
284
|
+
}
|
|
285
|
+
hasCustomContent = true
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (Object.keys(override.customSections).length === 0) {
|
|
289
|
+
delete override.customSections
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Check for see also links
|
|
294
|
+
if (parsed.seeAlso.length > 0) {
|
|
295
|
+
override.seeAlso = parsed.seeAlso
|
|
296
|
+
hasCustomContent = true
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Check for conditional blocks (cloud/self-hosted specific content)
|
|
300
|
+
for (const block of parsed.conditionalBlocks) {
|
|
301
|
+
const content = cleanContent(block.content.join('\n'))
|
|
302
|
+
if (isMeaningfulContent(content)) {
|
|
303
|
+
if (block.type === 'ifdef' && block.condition === 'env-cloud') {
|
|
304
|
+
override.cloudContent = override.cloudContent || {}
|
|
305
|
+
override.cloudContent.after_flags = content
|
|
306
|
+
hasCustomContent = true
|
|
307
|
+
} else if (block.type === 'ifndef' && block.condition === 'env-cloud') {
|
|
308
|
+
override.selfHostedContent = override.selfHostedContent || {}
|
|
309
|
+
override.selfHostedContent.after_flags = content
|
|
310
|
+
hasCustomContent = true
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return hasCustomContent ? override : null
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Scan a directory of rpk docs and extract override suggestions
|
|
320
|
+
* @param {string} docsDir - Path to rpk docs directory
|
|
321
|
+
* @param {Object} existingOverrides - Current overrides to merge with
|
|
322
|
+
* @returns {Object} Results with suggestions and report
|
|
323
|
+
*/
|
|
324
|
+
function extractOverridesFromDocs(docsDir, existingOverrides = {}) {
|
|
325
|
+
const results = {
|
|
326
|
+
suggestions: {},
|
|
327
|
+
report: {
|
|
328
|
+
totalFiles: 0,
|
|
329
|
+
filesWithCustomContent: 0,
|
|
330
|
+
newOverrides: 0,
|
|
331
|
+
updatedOverrides: 0,
|
|
332
|
+
details: []
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const existingCommands = existingOverrides.commands || {}
|
|
337
|
+
|
|
338
|
+
function processDirectory(dir) {
|
|
339
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
340
|
+
|
|
341
|
+
for (const entry of entries) {
|
|
342
|
+
const fullPath = path.join(dir, entry.name)
|
|
343
|
+
|
|
344
|
+
if (entry.isDirectory()) {
|
|
345
|
+
processDirectory(fullPath)
|
|
346
|
+
} else if (entry.isFile() && entry.name.endsWith('.adoc')) {
|
|
347
|
+
results.report.totalFiles++
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
const content = fs.readFileSync(fullPath, 'utf8')
|
|
351
|
+
const parsed = parseAsciiDoc(content)
|
|
352
|
+
const commandPath = filePathToCommandPath(fullPath, docsDir)
|
|
353
|
+
|
|
354
|
+
const suggestion = extractOverrideSuggestion(parsed, commandPath)
|
|
355
|
+
|
|
356
|
+
if (suggestion) {
|
|
357
|
+
results.report.filesWithCustomContent++
|
|
358
|
+
|
|
359
|
+
const existing = existingCommands[commandPath]
|
|
360
|
+
if (existing) {
|
|
361
|
+
// Merge with existing, only add new fields
|
|
362
|
+
const merged = { ...existing }
|
|
363
|
+
let hasNewContent = false
|
|
364
|
+
|
|
365
|
+
for (const [key, value] of Object.entries(suggestion)) {
|
|
366
|
+
if (!existing[key]) {
|
|
367
|
+
merged[key] = value
|
|
368
|
+
hasNewContent = true
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (hasNewContent) {
|
|
373
|
+
results.suggestions[commandPath] = merged
|
|
374
|
+
results.report.updatedOverrides++
|
|
375
|
+
results.report.details.push({
|
|
376
|
+
file: fullPath,
|
|
377
|
+
commandPath,
|
|
378
|
+
action: 'update',
|
|
379
|
+
newFields: Object.keys(suggestion).filter(k => !existing[k])
|
|
380
|
+
})
|
|
381
|
+
}
|
|
382
|
+
} else {
|
|
383
|
+
results.suggestions[commandPath] = suggestion
|
|
384
|
+
results.report.newOverrides++
|
|
385
|
+
results.report.details.push({
|
|
386
|
+
file: fullPath,
|
|
387
|
+
commandPath,
|
|
388
|
+
action: 'new',
|
|
389
|
+
fields: Object.keys(suggestion)
|
|
390
|
+
})
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
} catch (err) {
|
|
394
|
+
console.error(`Error processing ${fullPath}: ${err.message}`)
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
processDirectory(docsDir)
|
|
401
|
+
return results
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Generate a migration report
|
|
406
|
+
* @param {Object} results - Results from extractOverridesFromDocs
|
|
407
|
+
* @returns {string} Markdown report
|
|
408
|
+
*/
|
|
409
|
+
function generateReport(results) {
|
|
410
|
+
const lines = [
|
|
411
|
+
'# RPK Docs Override Migration Report',
|
|
412
|
+
'',
|
|
413
|
+
'## Summary',
|
|
414
|
+
'',
|
|
415
|
+
`- **Total files scanned:** ${results.report.totalFiles}`,
|
|
416
|
+
`- **Files with custom content:** ${results.report.filesWithCustomContent}`,
|
|
417
|
+
`- **New overrides to add:** ${results.report.newOverrides}`,
|
|
418
|
+
`- **Existing overrides to update:** ${results.report.updatedOverrides}`,
|
|
419
|
+
''
|
|
420
|
+
]
|
|
421
|
+
|
|
422
|
+
if (results.report.details.length > 0) {
|
|
423
|
+
lines.push('## Details', '')
|
|
424
|
+
|
|
425
|
+
const newOverrides = results.report.details.filter(d => d.action === 'new')
|
|
426
|
+
const updates = results.report.details.filter(d => d.action === 'update')
|
|
427
|
+
|
|
428
|
+
if (newOverrides.length > 0) {
|
|
429
|
+
lines.push('### New Overrides', '')
|
|
430
|
+
for (const detail of newOverrides) {
|
|
431
|
+
lines.push(`- **${detail.commandPath}**`)
|
|
432
|
+
lines.push(` - File: \`${detail.file}\``)
|
|
433
|
+
lines.push(` - Content types: ${detail.fields.join(', ')}`)
|
|
434
|
+
lines.push('')
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (updates.length > 0) {
|
|
439
|
+
lines.push('### Updates to Existing Overrides', '')
|
|
440
|
+
for (const detail of updates) {
|
|
441
|
+
lines.push(`- **${detail.commandPath}**`)
|
|
442
|
+
lines.push(` - File: \`${detail.file}\``)
|
|
443
|
+
lines.push(` - New fields: ${detail.newFields.join(', ')}`)
|
|
444
|
+
lines.push('')
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return lines.join('\n')
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Main extraction function
|
|
454
|
+
* @param {string} docsDir - Path to rpk docs directory
|
|
455
|
+
* @param {string} overridesPath - Path to existing overrides JSON
|
|
456
|
+
* @param {Object} options - Options
|
|
457
|
+
* @returns {Object} Results
|
|
458
|
+
*/
|
|
459
|
+
function extractOverrides(docsDir, overridesPath, options = {}) {
|
|
460
|
+
// Load existing overrides
|
|
461
|
+
let existingOverrides = {}
|
|
462
|
+
if (overridesPath && fs.existsSync(overridesPath)) {
|
|
463
|
+
try {
|
|
464
|
+
existingOverrides = JSON.parse(fs.readFileSync(overridesPath, 'utf8'))
|
|
465
|
+
} catch (err) {
|
|
466
|
+
console.warn(`Warning: Could not load existing overrides: ${err.message}`)
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Extract suggestions
|
|
471
|
+
const results = extractOverridesFromDocs(docsDir, existingOverrides)
|
|
472
|
+
|
|
473
|
+
// Generate outputs
|
|
474
|
+
const report = generateReport(results)
|
|
475
|
+
|
|
476
|
+
// Build merged overrides
|
|
477
|
+
const mergedOverrides = {
|
|
478
|
+
...existingOverrides,
|
|
479
|
+
commands: {
|
|
480
|
+
...(existingOverrides.commands || {}),
|
|
481
|
+
...results.suggestions
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return {
|
|
486
|
+
suggestions: results.suggestions,
|
|
487
|
+
report,
|
|
488
|
+
mergedOverrides,
|
|
489
|
+
stats: results.report
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// CLI interface
|
|
494
|
+
if (require.main === module) {
|
|
495
|
+
const args = process.argv.slice(2)
|
|
496
|
+
|
|
497
|
+
if (args.length < 1) {
|
|
498
|
+
console.log('Usage: node extract-overrides.js <docs-dir> [overrides-path] [--output-json <path>] [--output-report <path>]')
|
|
499
|
+
console.log('')
|
|
500
|
+
console.log('Arguments:')
|
|
501
|
+
console.log(' docs-dir Path to rpk docs directory (e.g., ../docs/modules/reference/pages/rpk)')
|
|
502
|
+
console.log(' overrides-path Path to existing rpk-overrides.json (optional)')
|
|
503
|
+
console.log('')
|
|
504
|
+
console.log('Options:')
|
|
505
|
+
console.log(' --output-json <path> Write merged overrides to file')
|
|
506
|
+
console.log(' --output-report <path> Write migration report to file')
|
|
507
|
+
console.log(' --dry-run Print report without writing files')
|
|
508
|
+
process.exit(1)
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const docsDir = args[0]
|
|
512
|
+
let overridesPath = null
|
|
513
|
+
let outputJson = null
|
|
514
|
+
let outputReport = null
|
|
515
|
+
let dryRun = false
|
|
516
|
+
|
|
517
|
+
for (let i = 1; i < args.length; i++) {
|
|
518
|
+
if (args[i] === '--output-json' && args[i + 1]) {
|
|
519
|
+
outputJson = args[++i]
|
|
520
|
+
} else if (args[i] === '--output-report' && args[i + 1]) {
|
|
521
|
+
outputReport = args[++i]
|
|
522
|
+
} else if (args[i] === '--dry-run') {
|
|
523
|
+
dryRun = true
|
|
524
|
+
} else if (!args[i].startsWith('--')) {
|
|
525
|
+
overridesPath = args[i]
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (!fs.existsSync(docsDir)) {
|
|
530
|
+
console.error(`Error: Docs directory not found: ${docsDir}`)
|
|
531
|
+
process.exit(1)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
console.log(`Extracting overrides from: ${docsDir}`)
|
|
535
|
+
if (overridesPath) {
|
|
536
|
+
console.log(`Using existing overrides: ${overridesPath}`)
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const results = extractOverrides(docsDir, overridesPath)
|
|
540
|
+
|
|
541
|
+
console.log('')
|
|
542
|
+
console.log(results.report)
|
|
543
|
+
|
|
544
|
+
if (!dryRun) {
|
|
545
|
+
if (outputJson) {
|
|
546
|
+
fs.writeFileSync(outputJson, JSON.stringify(results.mergedOverrides, null, 2))
|
|
547
|
+
console.log(`\nWrote merged overrides to: ${outputJson}`)
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (outputReport) {
|
|
551
|
+
fs.writeFileSync(outputReport, results.report)
|
|
552
|
+
console.log(`Wrote report to: ${outputReport}`)
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
console.log(`\nFound ${Object.keys(results.suggestions).length} commands with custom content to migrate`)
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
module.exports = {
|
|
560
|
+
extractOverrides,
|
|
561
|
+
extractOverridesFromDocs,
|
|
562
|
+
parseAsciiDoc,
|
|
563
|
+
extractOverrideSuggestion,
|
|
564
|
+
generateReport,
|
|
565
|
+
filePathToCommandPath
|
|
566
|
+
}
|