@redpanda-data/docs-extensions-and-macros 5.3.6 → 5.3.7
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/package.json +1 -1
- package/tools/redpanda-connect/generate-rpcn-connector-docs.js +23 -1
- package/tools/redpanda-connect/metadata-utils.js +59 -0
- package/tools/redpanda-connect/pr-summary-formatter.js +21 -0
- package/tools/redpanda-connect/rpcn-connector-docs-handler.js +9 -2
- package/tools/rpk-docs/generate-plugin-stubs.js +9 -3
package/package.json
CHANGED
|
@@ -5,6 +5,7 @@ const path = require('path');
|
|
|
5
5
|
const handlebars = require('handlebars');
|
|
6
6
|
const yaml = require('yaml');
|
|
7
7
|
const helpers = require('./helpers');
|
|
8
|
+
const { lostMetadataSections } = require('./metadata-utils.js');
|
|
8
9
|
|
|
9
10
|
// Register each helper under handlebars, verifying that it’s a function
|
|
10
11
|
Object.entries(helpers).forEach(([name, fn]) => {
|
|
@@ -376,6 +377,7 @@ async function generateRpcnConnectorDocs(options) {
|
|
|
376
377
|
}
|
|
377
378
|
|
|
378
379
|
let partialsWritten = 0;
|
|
380
|
+
const lostSectionWarnings = [];
|
|
379
381
|
let draftsWritten = 0;
|
|
380
382
|
const partialFiles = [];
|
|
381
383
|
const draftFiles = [];
|
|
@@ -434,6 +436,25 @@ async function generateRpcnConnectorDocs(options) {
|
|
|
434
436
|
const mPath = path.join(metadataOutRoot, typeDir, `${name}.adoc`);
|
|
435
437
|
if (metadataOut.trim()) {
|
|
436
438
|
fs.mkdirSync(path.dirname(mPath), { recursive: true });
|
|
439
|
+
if (fs.existsSync(mPath)) {
|
|
440
|
+
// Regeneration is authoritative, but a section vanishing from a
|
|
441
|
+
// published partial is usually content loss, not cleanup: the
|
|
442
|
+
// section either sits outside the upstream description's
|
|
443
|
+
// `== Metadata` block or was hand-migrated from a page, and
|
|
444
|
+
// overwriting drops it without a trace. Warn so the auto-docs PR
|
|
445
|
+
// reviewer can move the content to the connector page or restore
|
|
446
|
+
// it upstream before merging.
|
|
447
|
+
const lost = lostMetadataSections(fs.readFileSync(mPath, 'utf8'), metadataOut);
|
|
448
|
+
if (lost.length) {
|
|
449
|
+
lostSectionWarnings.push({ partial: path.relative(process.cwd(), mPath), sections: lost });
|
|
450
|
+
console.warn(
|
|
451
|
+
`Warning: regenerated metadata partial ${path.relative(process.cwd(), mPath)} ` +
|
|
452
|
+
`drops previously published section(s): ${lost.map((h) => `"${h}"`).join(', ')}. ` +
|
|
453
|
+
'If this content is still wanted, move it to the connector page or restore it ' +
|
|
454
|
+
"in the upstream description's Metadata section."
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
437
458
|
fs.writeFileSync(mPath, metadataOut);
|
|
438
459
|
if (!writeFullDrafts) {
|
|
439
460
|
partialsWritten++;
|
|
@@ -657,7 +678,8 @@ async function generateRpcnConnectorDocs(options) {
|
|
|
657
678
|
partialsWritten,
|
|
658
679
|
draftsWritten,
|
|
659
680
|
partialFiles,
|
|
660
|
-
draftFiles
|
|
681
|
+
draftFiles,
|
|
682
|
+
lostSectionWarnings
|
|
661
683
|
};
|
|
662
684
|
}
|
|
663
685
|
|
|
@@ -111,10 +111,69 @@ function descriptionWithMetadataInclude (item) {
|
|
|
111
111
|
return description.slice(0, found.start) + metadataIncludeLine(item) + description.slice(found.end);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
// Markdown-style fence delimiter (``` or ~~~, possibly with a language tag).
|
|
115
|
+
// Metadata blocks can carry fenced examples alongside AsciiDoc ---- blocks.
|
|
116
|
+
const FENCE_DELIMITER = /^(`{3,}|~{3,})/;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Collect the section heading titles in an AsciiDoc block, ignoring lines
|
|
120
|
+
* inside `----` literal blocks and ```/~~~ fenced blocks. Titles are returned
|
|
121
|
+
* without their `=` markers so callers can compare sections across heading
|
|
122
|
+
* levels (the same section may be `==` in a connector description but `===`
|
|
123
|
+
* in a partial migrated from a page).
|
|
124
|
+
* @param {string} text
|
|
125
|
+
* @returns {string[]}
|
|
126
|
+
*/
|
|
127
|
+
function sectionHeadings (text) {
|
|
128
|
+
if (!text || typeof text !== 'string') return [];
|
|
129
|
+
const headings = [];
|
|
130
|
+
let inBlock = false;
|
|
131
|
+
let fence = null;
|
|
132
|
+
for (const line of text.split('\n')) {
|
|
133
|
+
// Layered state: while inside one delimiter kind, the only thing that
|
|
134
|
+
// matters is its own closer. A fence-like line inside a ---- literal
|
|
135
|
+
// block (or a ---- line inside a fence) is content, not a delimiter —
|
|
136
|
+
// treating it as one leaks the state and swallows every later heading.
|
|
137
|
+
if (inBlock) {
|
|
138
|
+
if (BLOCK_DELIMITER.test(line)) inBlock = false;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (fence) {
|
|
142
|
+
const closer = line.match(FENCE_DELIMITER);
|
|
143
|
+
if (closer && closer[1][0] === fence) fence = null;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const fenceMatch = line.match(FENCE_DELIMITER);
|
|
147
|
+
if (fenceMatch) { fence = fenceMatch[1][0]; continue; }
|
|
148
|
+
if (BLOCK_DELIMITER.test(line)) { inBlock = true; continue; }
|
|
149
|
+
const m = line.match(/^=+\s+(\S.*)$/);
|
|
150
|
+
if (m) headings.push(m[1].trim());
|
|
151
|
+
}
|
|
152
|
+
return headings;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Report the section headings present in a previously generated metadata
|
|
157
|
+
* partial that are missing from its regenerated replacement. Regeneration is
|
|
158
|
+
* authoritative, but published content silently disappearing is how docs lose
|
|
159
|
+
* examples: a section that lives outside the upstream description's
|
|
160
|
+
* `== Metadata` block (or was hand-migrated from a page) is dropped without a
|
|
161
|
+
* trace on the next run. Callers use this to warn before overwriting.
|
|
162
|
+
* @param {string} oldContent existing partial on disk
|
|
163
|
+
* @param {string} newContent regenerated partial about to be written
|
|
164
|
+
* @returns {string[]} heading titles present in oldContent but not newContent
|
|
165
|
+
*/
|
|
166
|
+
function lostMetadataSections (oldContent, newContent) {
|
|
167
|
+
const newHeadings = new Set(sectionHeadings(newContent));
|
|
168
|
+
return sectionHeadings(oldContent).filter((h) => !newHeadings.has(h));
|
|
169
|
+
}
|
|
170
|
+
|
|
114
171
|
module.exports = {
|
|
115
172
|
locateMetadata,
|
|
116
173
|
extractMetadata,
|
|
117
174
|
typeDirFor,
|
|
118
175
|
metadataIncludeLine,
|
|
119
176
|
descriptionWithMetadataInclude,
|
|
177
|
+
sectionHeadings,
|
|
178
|
+
lostMetadataSections,
|
|
120
179
|
};
|
|
@@ -122,6 +122,25 @@ function renderBloblangChanges(data, includeVersion = false) {
|
|
|
122
122
|
* @param {array} draftedConnectors - Array of newly drafted connectors
|
|
123
123
|
* @returns {string} Formatted summary
|
|
124
124
|
*/
|
|
125
|
+
/**
|
|
126
|
+
* Render the content-loss warning block that leads the PR summary. These
|
|
127
|
+
* warnings exist to stop a merge, so they go at the top of the summary body
|
|
128
|
+
* rather than into the collapsed workflow log where nobody reads them.
|
|
129
|
+
* @param {Array<{partial: string, sections: string[]}>} warnings
|
|
130
|
+
* @returns {string[]} lines (empty when there is nothing to warn about)
|
|
131
|
+
*/
|
|
132
|
+
function renderLostSectionWarnings(warnings) {
|
|
133
|
+
if (!Array.isArray(warnings) || warnings.length === 0) return [];
|
|
134
|
+
const lines = [];
|
|
135
|
+
lines.push('> [!WARNING]');
|
|
136
|
+
lines.push('> **This update deletes previously published metadata sections.** Review before merging: move the content to the connector page or restore it in the upstream description.');
|
|
137
|
+
for (const w of warnings) {
|
|
138
|
+
lines.push(`> - \`${w.partial}\` drops: ${w.sections.map((h) => `"${h}"`).join(', ')}`);
|
|
139
|
+
}
|
|
140
|
+
lines.push('');
|
|
141
|
+
return lines;
|
|
142
|
+
}
|
|
143
|
+
|
|
125
144
|
function generateMultiVersionPRSummary(masterDiff, binaryAnalysis = null, draftedConnectors = null) {
|
|
126
145
|
const lines = [];
|
|
127
146
|
|
|
@@ -133,6 +152,7 @@ function generateMultiVersionPRSummary(masterDiff, binaryAnalysis = null, drafte
|
|
|
133
152
|
|
|
134
153
|
lines.push('<!-- PR_SUMMARY_START -->');
|
|
135
154
|
lines.push('');
|
|
155
|
+
lines.push(...renderLostSectionWarnings(masterDiff?.lostSectionWarnings));
|
|
136
156
|
lines.push('## Redpanda Connect Documentation Update');
|
|
137
157
|
lines.push('');
|
|
138
158
|
lines.push(`**Multi-Release Update:** ${startVersion} → ${endVersion}`);
|
|
@@ -538,6 +558,7 @@ function generatePRSummary(diffData, binaryAnalysis = null, draftedConnectors =
|
|
|
538
558
|
// Header with delimiters for GitHub Action parsing
|
|
539
559
|
lines.push('<!-- PR_SUMMARY_START -->');
|
|
540
560
|
lines.push('');
|
|
561
|
+
lines.push(...renderLostSectionWarnings(diffData.lostSectionWarnings));
|
|
541
562
|
|
|
542
563
|
// Detect if this is a master diff
|
|
543
564
|
if (!isMultiVersion && diffData.releases && diffData.totalSummary) {
|
|
@@ -1130,6 +1130,7 @@ async function handleRpcnConnectorDocs (options) {
|
|
|
1130
1130
|
|
|
1131
1131
|
console.log('Generating connector partials...')
|
|
1132
1132
|
let partialsWritten, partialFiles
|
|
1133
|
+
const lostSectionWarnings = []
|
|
1133
1134
|
|
|
1134
1135
|
try {
|
|
1135
1136
|
const result = await generateRpcnConnectorDocs({
|
|
@@ -1147,6 +1148,7 @@ async function handleRpcnConnectorDocs (options) {
|
|
|
1147
1148
|
})
|
|
1148
1149
|
partialsWritten = result.partialsWritten
|
|
1149
1150
|
partialFiles = result.partialFiles
|
|
1151
|
+
lostSectionWarnings.push(...(result.lostSectionWarnings || []))
|
|
1150
1152
|
} catch (err) {
|
|
1151
1153
|
console.error(`Error: Failed to generate partials: ${err.message}`)
|
|
1152
1154
|
process.exit(1)
|
|
@@ -1940,6 +1942,7 @@ async function handleRpcnConnectorDocs (options) {
|
|
|
1940
1942
|
fs.unlinkSync(tempDataPath)
|
|
1941
1943
|
draftsWritten = draftResult.draftsWritten
|
|
1942
1944
|
draftFiles = draftResult.draftFiles
|
|
1945
|
+
lostSectionWarnings.push(...(draftResult.lostSectionWarnings || []))
|
|
1943
1946
|
}
|
|
1944
1947
|
} catch (err) {
|
|
1945
1948
|
console.error(`Error: Could not draft missing: ${err.message}`)
|
|
@@ -1987,8 +1990,12 @@ async function handleRpcnConnectorDocs (options) {
|
|
|
1987
1990
|
// Generate PR summary
|
|
1988
1991
|
try {
|
|
1989
1992
|
const { printPRSummary } = require('./pr-summary-formatter.js')
|
|
1990
|
-
// Use master diff if available, otherwise use single diff
|
|
1991
|
-
|
|
1993
|
+
// Use master diff if available, otherwise use single diff. Content-loss
|
|
1994
|
+
// warnings ride the diff object so they lead the PR summary body instead
|
|
1995
|
+
// of dying in the collapsed workflow log.
|
|
1996
|
+
const summaryDiff = masterDiff || diffJson
|
|
1997
|
+
if (lostSectionWarnings.length) summaryDiff.lostSectionWarnings = lostSectionWarnings
|
|
1998
|
+
printPRSummary(summaryDiff, binaryAnalysis, draftFiles, masterDiff ? true : false)
|
|
1992
1999
|
} catch (err) {
|
|
1993
2000
|
console.error(`Warning: Failed to generate PR summary: ${err.message}`)
|
|
1994
2001
|
}
|
|
@@ -23,7 +23,7 @@ const { spawnSync } = require('child_process')
|
|
|
23
23
|
* authoritative: dashified filenames cannot be reversed unambiguously
|
|
24
24
|
* (rpk-ai-llm-provider could be `llm provider` or `llm-provider`).
|
|
25
25
|
* @param {string} partialsDir - Directory of generated .adoc partials
|
|
26
|
-
* @returns {Array<{file: string, title: string}>} Sorted by command path
|
|
26
|
+
* @returns {Array<{file: string, title: string, description: string|undefined}>} Sorted by command path
|
|
27
27
|
*/
|
|
28
28
|
function readPartialTitles(partialsDir) {
|
|
29
29
|
const partials = []
|
|
@@ -35,7 +35,11 @@ function readPartialTitles(partialsDir) {
|
|
|
35
35
|
console.warn(`Warning: no title line in ${file}; skipping`)
|
|
36
36
|
continue
|
|
37
37
|
}
|
|
38
|
-
|
|
38
|
+
// The partial repeats :description: inside its single-source tag, but
|
|
39
|
+
// Antora resolves page metadata with a header-only parse that never sees
|
|
40
|
+
// the include — the stub must carry the description in its own header.
|
|
41
|
+
const description = (content.match(/^:description:[ \t]*(.+)$/m) || [])[1]
|
|
42
|
+
partials.push({ file, title: match[1].trim(), description: description && description.trim() })
|
|
39
43
|
}
|
|
40
44
|
// Hierarchical order: sort by command words so parents precede children
|
|
41
45
|
partials.sort((a, b) => {
|
|
@@ -114,12 +118,14 @@ function inferIncludePrefix(stubDir, plugin) {
|
|
|
114
118
|
* @param {Object} params
|
|
115
119
|
* @param {string} params.title - Command path (e.g. "rpk ai auth login")
|
|
116
120
|
* @param {string} params.file - Partial filename
|
|
121
|
+
* @param {string} [params.description] - Meta description from the partial header
|
|
117
122
|
* @param {string} params.includePrefix - Antora resource prefix
|
|
118
123
|
* @param {Array<string>} params.attributes - Page attribute lines
|
|
119
124
|
* @returns {string}
|
|
120
125
|
*/
|
|
121
|
-
function renderStub({ title, file, includePrefix, attributes }) {
|
|
126
|
+
function renderStub({ title, file, description, includePrefix, attributes }) {
|
|
122
127
|
const lines = [`= ${title}`]
|
|
128
|
+
if (description) lines.push(`:description: ${description}`)
|
|
123
129
|
for (const attr of attributes) lines.push(attr)
|
|
124
130
|
lines.push('')
|
|
125
131
|
lines.push(`include::${includePrefix}${file}[tag=single-source]`)
|