@redpanda-data/docs-extensions-and-macros 5.7.1 → 5.9.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.js +183 -0
- package/extensions/convert-to-markdown.js +115 -0
- package/extensions/property-page-index.js +44 -0
- package/extensions/validate-attributes.js +1 -1
- package/macros/PROPERTY_AND_ENTERPRISE_REFERENCES.adoc +90 -0
- package/macros/README.adoc +2 -0
- package/macros/REFERENCE.adoc +167 -0
- package/macros/badge.js +35 -13
- package/macros/enterprise.js +390 -0
- package/macros/prop.js +491 -0
- package/package.json +6 -2
- package/tools/enterprise-features/parsers.js +134 -0
- package/tools/enterprise-features/verify.js +244 -0
- package/tools/generate-cli-docs.js +19 -1
- package/tools/migrate-property-refs.js +206 -0
- package/tools/property-extractor/generate-handlebars-docs.js +28 -1
- package/tools/property-extractor/property_extractor.py +34 -0
- package/tools/redpanda-connect/generate-rpcn-connector-docs.js +108 -2
- package/tools/redpanda-connect/templates/bloblang-functions-overview.hbs +2 -0
- package/tools/redpanda-connect/templates/bloblang-methods-overview.hbs +2 -0
- package/tools/redpanda-connect/templates/connector.hbs +13 -1
- package/tools/rpk-docs/generate-plugin-stubs.js +56 -9
- package/tools/rpk-docs/templates/command.hbs +23 -5
|
@@ -55,7 +55,10 @@ function mergeOverrides(target, overrides) {
|
|
|
55
55
|
throw new Error('Target must be a valid object');
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
// 'summary' earns its place here the hard way: overrides.json has carried
|
|
59
|
+
// summary overrides (zmq4, ffi) that were silently dropped because the key
|
|
60
|
+
// fell through every branch below. Summaries feed page meta descriptions.
|
|
61
|
+
const scalarKeys = ['description', 'summary', 'type', 'annotated_field', 'version'];
|
|
59
62
|
|
|
60
63
|
for (const key in overrides) {
|
|
61
64
|
// === Handle annotated_options ===
|
|
@@ -674,17 +677,120 @@ async function generateRpcnConnectorDocs(options) {
|
|
|
674
677
|
}
|
|
675
678
|
}
|
|
676
679
|
|
|
680
|
+
// Self-heal published page headers: pages are one-time drafts, so pages
|
|
681
|
+
// created before the template emitted :description: never got one.
|
|
682
|
+
let descriptionBackfill = { backfilled: [], skippedNoSummary: [] };
|
|
683
|
+
if (!writeFullDrafts) {
|
|
684
|
+
descriptionBackfill = backfillPageDescriptions(dataObj);
|
|
685
|
+
}
|
|
686
|
+
|
|
677
687
|
return {
|
|
678
688
|
partialsWritten,
|
|
679
689
|
draftsWritten,
|
|
680
690
|
partialFiles,
|
|
681
691
|
draftFiles,
|
|
682
|
-
lostSectionWarnings
|
|
692
|
+
lostSectionWarnings,
|
|
693
|
+
descriptionBackfill
|
|
683
694
|
};
|
|
684
695
|
}
|
|
685
696
|
|
|
697
|
+
|
|
698
|
+
// Connector page families that have per-component pages under
|
|
699
|
+
// modules/components/pages/. Other data keys (config, bloblang-functions,
|
|
700
|
+
// bloblang-methods) have no per-component page to backfill.
|
|
701
|
+
const PAGE_TYPE_DIRS = new Set([
|
|
702
|
+
'inputs', 'outputs', 'processors', 'caches', 'buffers', 'metrics',
|
|
703
|
+
'tracers', 'scanners', 'rate-limits', 'rate_limits'
|
|
704
|
+
]);
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* Insert a tagged :description: into existing connector page headers that
|
|
708
|
+
* lack one, sourced from the component's summary in the connector data.
|
|
709
|
+
*
|
|
710
|
+
* Connector pages are one-time first drafts: pages drafted before the
|
|
711
|
+
* template emitted :description: never got one, and nothing rewrote them,
|
|
712
|
+
* so hundreds of published pages ship the generic site meta description
|
|
713
|
+
* even though ~97% of components carry a summary in the source data. This
|
|
714
|
+
* runs on every generation, so the page set self-heals and stays healed.
|
|
715
|
+
*
|
|
716
|
+
* The inserted block matches the connector template's shape: the meta tag
|
|
717
|
+
* region lets single-source stubs inherit the description as a header
|
|
718
|
+
* attribute (see the template comment in connector.hbs).
|
|
719
|
+
*
|
|
720
|
+
* @param {Object} connectorData - Parsed connector data (typed arrays)
|
|
721
|
+
* @param {Object} [options]
|
|
722
|
+
* @param {string} [options.pagesRoot] - Pages directory (default: modules/components/pages under cwd)
|
|
723
|
+
* @param {boolean} [options.dryRun] - Report without writing
|
|
724
|
+
* @returns {{backfilled: string[], skippedNoSummary: string[]}}
|
|
725
|
+
*/
|
|
726
|
+
function backfillPageDescriptions (connectorData, { pagesRoot, dryRun = false } = {}) {
|
|
727
|
+
const root = pagesRoot || path.resolve(process.cwd(), 'modules/components/pages');
|
|
728
|
+
const results = { backfilled: [], skippedNoSummary: [] };
|
|
729
|
+
if (!fs.existsSync(root)) return results;
|
|
730
|
+
|
|
731
|
+
for (const [dataKey, items] of Object.entries(connectorData)) {
|
|
732
|
+
if (!PAGE_TYPE_DIRS.has(dataKey) || !Array.isArray(items)) continue;
|
|
733
|
+
// The data uses 'rate-limits' while the pages directory is
|
|
734
|
+
// 'rate_limits'. Resolve whichever spelling exists on disk.
|
|
735
|
+
const typeDir = fs.existsSync(path.join(root, dataKey))
|
|
736
|
+
? dataKey
|
|
737
|
+
: dataKey.replace(/-/g, '_');
|
|
738
|
+
for (const item of items) {
|
|
739
|
+
if (!item || !item.name) continue;
|
|
740
|
+
const pagePath = path.join(root, typeDir, `${item.name}.adoc`);
|
|
741
|
+
if (!fs.existsSync(pagePath)) continue;
|
|
742
|
+
|
|
743
|
+
const content = fs.readFileSync(pagePath, 'utf8');
|
|
744
|
+
const lines = content.split('\n');
|
|
745
|
+
const headerEnd = lines.findIndex((l) => l.trim() === '');
|
|
746
|
+
const header = lines.slice(0, headerEnd === -1 ? lines.length : headerEnd);
|
|
747
|
+
if (header.some((l) => l.startsWith(':description:'))) continue;
|
|
748
|
+
|
|
749
|
+
// Meta descriptions are read as plain text by search results, link
|
|
750
|
+
// previews, and assistants, so flatten AsciiDoc markup mechanically:
|
|
751
|
+
// xrefs and links become their labels, inline code loses its
|
|
752
|
+
// backticks. No prose is edited.
|
|
753
|
+
const summary = (item.summary || '')
|
|
754
|
+
.replace(/(?:xref|link):[^\[\]]+\[([^\]]*)\]/g, '$1')
|
|
755
|
+
// Bare URL macros (no xref:/link: prefix) and internal xref shorthand
|
|
756
|
+
// also appear in source summaries and render literally if left in place.
|
|
757
|
+
.replace(/https?:\/\/[^\s\[\]]+\[([^\]]*)\]/g, '$1')
|
|
758
|
+
.replace(/<<[^,>]+,([^>]+)>>/g, '$1')
|
|
759
|
+
.replace(/<<([^>]+)>>/g, '$1')
|
|
760
|
+
// Strip the "open in new tab" caret that AsciiDoc URL macros carry
|
|
761
|
+
// inside the link text, e.g. [Open Telemetry collector^].
|
|
762
|
+
.replace(/\^/g, '')
|
|
763
|
+
.replace(/`([^`]*)`/g, '$1')
|
|
764
|
+
.replace(/\s+/g, ' ')
|
|
765
|
+
.trim();
|
|
766
|
+
if (!summary) {
|
|
767
|
+
results.skippedNoSummary.push(`${typeDir}/${item.name}`);
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
const insertAt = headerEnd === -1 ? lines.length : headerEnd;
|
|
772
|
+
lines.splice(insertAt, 0, '// tag::meta[]', `:description: ${summary}`, '// end::meta[]');
|
|
773
|
+
if (!dryRun) fs.writeFileSync(pagePath, lines.join('\n'), 'utf8');
|
|
774
|
+
results.backfilled.push(`${typeDir}/${item.name}`);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
if (results.backfilled.length) {
|
|
779
|
+
console.log(`Backfilled :description: into ${results.backfilled.length} connector page header(s) from source summaries`);
|
|
780
|
+
}
|
|
781
|
+
if (results.skippedNoSummary.length) {
|
|
782
|
+
console.warn(
|
|
783
|
+
`\u26a0 ${results.skippedNoSummary.length} connector page(s) lack both a header description and a source summary: ` +
|
|
784
|
+
results.skippedNoSummary.join(', ') +
|
|
785
|
+
'. Author these in the overrides file or upstream.'
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
return results;
|
|
789
|
+
}
|
|
790
|
+
|
|
686
791
|
module.exports = {
|
|
687
792
|
generateRpcnConnectorDocs,
|
|
793
|
+
backfillPageDescriptions,
|
|
688
794
|
mergeOverrides,
|
|
689
795
|
resolveReferences
|
|
690
796
|
};
|
|
@@ -3,7 +3,19 @@
|
|
|
3
3
|
:type: {{type}}
|
|
4
4
|
{{#if support}}:support: {{support}}{{/if}}
|
|
5
5
|
:categories: [{{join categories ", "}}]
|
|
6
|
-
|
|
6
|
+
{{#if summary}}
|
|
7
|
+
// tag::meta[]
|
|
8
|
+
:description: {{{summary}}}
|
|
9
|
+
// end::meta[]
|
|
10
|
+
{{else if description}}
|
|
11
|
+
// tag::meta[]
|
|
12
|
+
:description: {{description}}
|
|
13
|
+
// end::meta[]
|
|
14
|
+
{{/if}}
|
|
15
|
+
{{!-- No fallback line when neither exists: an emitted TODO would become
|
|
16
|
+
the attribute VALUE and stubs would inherit it into their meta tags.
|
|
17
|
+
Missing descriptions are the tracked editorial backlog
|
|
18
|
+
(cloud-docs .github/description-allowlist.txt). --}}
|
|
7
19
|
|
|
8
20
|
component_type_dropdown::[]
|
|
9
21
|
|
|
@@ -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, description: string|undefined}>} Sorted by command path
|
|
26
|
+
* @returns {Array<{file: string, title: string, description: string|undefined, hasMetaTag: boolean}>} Sorted by command path
|
|
27
27
|
*/
|
|
28
28
|
function readPartialTitles(partialsDir) {
|
|
29
29
|
const partials = []
|
|
@@ -35,11 +35,18 @@ function readPartialTitles(partialsDir) {
|
|
|
35
35
|
console.warn(`Warning: no title line in ${file}; skipping`)
|
|
36
36
|
continue
|
|
37
37
|
}
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
38
|
+
// Antora resolves page metadata with a header-only parse that never
|
|
39
|
+
// sees the body include, so the stub must carry the description in its
|
|
40
|
+
// own header. Partials generated with doc-tools >= 5.4 carry a nested
|
|
41
|
+
// meta tag region around :description: that stubs include dynamically;
|
|
42
|
+
// older partials fall back to a literal copy of the header description.
|
|
41
43
|
const description = (content.match(/^:description:[ \t]*(.+)$/m) || [])[1]
|
|
42
|
-
partials.push({
|
|
44
|
+
partials.push({
|
|
45
|
+
file,
|
|
46
|
+
title: match[1].trim(),
|
|
47
|
+
description: description && description.trim(),
|
|
48
|
+
hasMetaTag: content.includes('tag::meta['),
|
|
49
|
+
})
|
|
43
50
|
}
|
|
44
51
|
// Hierarchical order: sort by command words so parents precede children
|
|
45
52
|
partials.sort((a, b) => {
|
|
@@ -121,11 +128,23 @@ function inferIncludePrefix(stubDir, plugin) {
|
|
|
121
128
|
* @param {string} [params.description] - Meta description from the partial header
|
|
122
129
|
* @param {string} params.includePrefix - Antora resource prefix
|
|
123
130
|
* @param {Array<string>} params.attributes - Page attribute lines
|
|
131
|
+
* @param {boolean} [params.hasMetaTag] - Partial carries a tag::meta[] region (doc-tools >= 5.4)
|
|
124
132
|
* @returns {string}
|
|
125
133
|
*/
|
|
126
|
-
function renderStub({ title, file, description, includePrefix, attributes }) {
|
|
134
|
+
function renderStub({ title, file, description, includePrefix, attributes, hasMetaTag }) {
|
|
127
135
|
const lines = [`= ${title}`]
|
|
128
|
-
|
|
136
|
+
// The description mechanism is always the first header line after the
|
|
137
|
+
// title: a header include for partials with a meta tag region, or a
|
|
138
|
+
// literal copy as the fallback for partials generated before 5.4. Antora
|
|
139
|
+
// resolves page metadata with a header-only parse that stops at the first
|
|
140
|
+
// blank line, so either form must sit above it — an include below the
|
|
141
|
+
// blank line is never evaluated for attributes and the page ships the
|
|
142
|
+
// generic site description.
|
|
143
|
+
if (hasMetaTag) {
|
|
144
|
+
lines.push(`include::${includePrefix}${file}[tag=meta]`)
|
|
145
|
+
} else if (description) {
|
|
146
|
+
lines.push(`:description: ${description}`)
|
|
147
|
+
}
|
|
129
148
|
for (const attr of attributes) lines.push(attr)
|
|
130
149
|
lines.push('')
|
|
131
150
|
lines.push(`include::${includePrefix}${file}[tag=single-source]`)
|
|
@@ -162,6 +181,7 @@ function reconcileStubs({
|
|
|
162
181
|
const existingStubs = fs.readdirSync(stubDir).filter(f => f.endsWith('.adoc'))
|
|
163
182
|
|
|
164
183
|
const created = []
|
|
184
|
+
const upgraded_ = []
|
|
165
185
|
const deleted = []
|
|
166
186
|
const keptNonStub = []
|
|
167
187
|
const skippedAliasTargets = []
|
|
@@ -206,7 +226,34 @@ function reconcileStubs({
|
|
|
206
226
|
fs.existsSync(stubDir) ? fs.readdirSync(stubDir).filter(f => f.endsWith('.adoc')) : []
|
|
207
227
|
)
|
|
208
228
|
for (const partial of partials) {
|
|
209
|
-
if (remainingStubs.has(partial.file))
|
|
229
|
+
if (remainingStubs.has(partial.file)) {
|
|
230
|
+
// Existing stub: upgrade it in place when the partial has gained the
|
|
231
|
+
// meta tag region and the stub does not yet include it. The header
|
|
232
|
+
// include is what lets the stub inherit :description: as page
|
|
233
|
+
// metadata, so upgrading here makes the consumer repo self-heal on
|
|
234
|
+
// the next sync instead of needing a hand-written migration PR.
|
|
235
|
+
if (partial.hasMetaTag && !dryRun) {
|
|
236
|
+
const stubPath = path.join(stubDir, partial.file)
|
|
237
|
+
const stubContent = fs.readFileSync(stubPath, 'utf8')
|
|
238
|
+
if (!stubContent.includes('[tag=meta]')) {
|
|
239
|
+
const metaInclude = `include::${includePrefix}${partial.file}[tag=meta]`
|
|
240
|
+
// A later attribute entry overrides an earlier one (verified
|
|
241
|
+
// empirically against an Antora build), so a literal
|
|
242
|
+
// :description: below the include would win and leave the
|
|
243
|
+
// include dead weight. These are generated reference stubs, so
|
|
244
|
+
// any literal is a static backfill the include supersedes:
|
|
245
|
+
// strip it. Hand-maintained prose stubs never pass through this
|
|
246
|
+
// reconciler, so intentionally curated descriptions are safe.
|
|
247
|
+
let upgraded = stubContent.replace(/^:description: .*\n/m, '')
|
|
248
|
+
upgraded = upgraded.replace(/^(= .+)$/m, `$1\n${metaInclude}`)
|
|
249
|
+
if (upgraded !== stubContent) {
|
|
250
|
+
fs.writeFileSync(stubPath, upgraded, 'utf8')
|
|
251
|
+
upgraded_.push(partial.file)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
continue
|
|
256
|
+
}
|
|
210
257
|
if (aliasClaims.has(partial.file)) {
|
|
211
258
|
skippedAliasTargets.push({ file: partial.file, claimedBy: aliasClaims.get(partial.file) })
|
|
212
259
|
continue
|
|
@@ -285,7 +332,7 @@ function reconcileStubs({
|
|
|
285
332
|
}
|
|
286
333
|
}
|
|
287
334
|
|
|
288
|
-
return { created, deleted, keptNonStub, navUpdated, renameCandidates, skippedAliasTargets }
|
|
335
|
+
return { created, upgraded: upgraded_, deleted, keptNonStub, navUpdated, renameCandidates, skippedAliasTargets }
|
|
289
336
|
}
|
|
290
337
|
|
|
291
338
|
module.exports = {
|
|
@@ -6,7 +6,12 @@
|
|
|
6
6
|
page at worst. Availability pages are excluded from cloud by not
|
|
7
7
|
generating a stub for them in cloud-docs. --}}
|
|
8
8
|
= {{commandPath}}
|
|
9
|
+
{{!-- Comment lines are legal inside an AsciiDoc header, so the meta tag
|
|
10
|
+
region can wrap the page's single description. Stub pages inherit it
|
|
11
|
+
with a header include (see the note at the single-source tag). --}}
|
|
12
|
+
// tag::meta[]
|
|
9
13
|
:description: {{{wrapDescriptionPassthrough shortDesc}}}
|
|
14
|
+
// end::meta[]
|
|
10
15
|
{{#if pageAliases}}
|
|
11
16
|
:page-aliases: {{{pageAliases}}}
|
|
12
17
|
{{/if}}
|
|
@@ -23,11 +28,24 @@
|
|
|
23
28
|
{{/each}}
|
|
24
29
|
|
|
25
30
|
// tag::single-source[]
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
single-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
{{!-- Stub pages inherit this page's description by including the header's
|
|
32
|
+
meta tag region as a HEADER attribute. A stub that only includes
|
|
33
|
+
tag=single-source after a blank line never gets it: Antora resolves
|
|
34
|
+
page metadata with a header-only parse that stops at the stub's first
|
|
35
|
+
blank line, so the include is never evaluated for attributes and the
|
|
36
|
+
page ships the generic site description (verified empirically,
|
|
37
|
+
2026-08-05, including that a headerless include destroys the body).
|
|
38
|
+
The working stub shape is two includes:
|
|
39
|
+
|
|
40
|
+
= Page title
|
|
41
|
+
include::...page.adoc[tag=meta] <- header: no blank line above
|
|
42
|
+
<- blank line ends the header
|
|
43
|
+
include::...page.adoc[tag=single-source]
|
|
44
|
+
|
|
45
|
+
Earlier revisions repeated :description: inside this region on the
|
|
46
|
+
wrong belief that it reached stub metadata by itself; it never did.
|
|
47
|
+
page-aliases and page-platforms deliberately stay outside both tag
|
|
48
|
+
regions: consumers must not inherit alias registrations. --}}
|
|
31
49
|
{{!-- Deprecation notice --}}
|
|
32
50
|
{{#if deprecated}}
|
|
33
51
|
[CAUTION]
|