@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
package/bin/doc-tools.js
CHANGED
|
@@ -827,6 +827,48 @@ automation
|
|
|
827
827
|
migrateMetadataToPartials({ write: Boolean(options.write) })
|
|
828
828
|
})
|
|
829
829
|
|
|
830
|
+
/**
|
|
831
|
+
* @description One-time migration of plain-backtick property mentions to the
|
|
832
|
+
* prop: inline macro, so property tooltips become opt-in. Only names that
|
|
833
|
+
* exist in the published property reference JSON AND contain a separator
|
|
834
|
+
* (_ or .) are converted. Separator-free names such as admin, brokers, rack,
|
|
835
|
+
* retries, and superusers are the ambiguous words that motivated opt-in
|
|
836
|
+
* marking, so they are always left for a human. Dry run unless --write is given.
|
|
837
|
+
*
|
|
838
|
+
* @example
|
|
839
|
+
* # Preview the conversion from a docs repo checkout
|
|
840
|
+
* npx doc-tools generate migrate-property-refs --properties modules/reference/attachments/redpanda-properties-v26.2.1.json
|
|
841
|
+
*
|
|
842
|
+
* # Apply it
|
|
843
|
+
* npx doc-tools generate migrate-property-refs --properties modules/reference/attachments/redpanda-properties-v26.2.1.json --write
|
|
844
|
+
*/
|
|
845
|
+
automation
|
|
846
|
+
.command('migrate-property-refs')
|
|
847
|
+
.description('One-time migration of `property_name` prose mentions to prop:property_name[] macros. Dry run unless --write is given.')
|
|
848
|
+
.requiredOption('--properties <path>', 'Path to the published redpanda-properties JSON to validate names against')
|
|
849
|
+
.option('--docs-dir <path>', 'Docs repo root containing modules/', '.')
|
|
850
|
+
.option('--config-refs', 'Also convert config_ref macro calls to prop macro calls')
|
|
851
|
+
.option('--write', 'Apply changes (default is a dry run that only reports)')
|
|
852
|
+
.action((options) => {
|
|
853
|
+
const { migrate } = require('../tools/migrate-property-refs.js')
|
|
854
|
+
const propertiesJson = JSON.parse(fs.readFileSync(path.resolve(options.properties), 'utf8'))
|
|
855
|
+
const result = migrate({
|
|
856
|
+
docsDir: path.resolve(options.docsDir),
|
|
857
|
+
propertiesJson,
|
|
858
|
+
write: Boolean(options.write),
|
|
859
|
+
configRefs: Boolean(options.configRefs),
|
|
860
|
+
})
|
|
861
|
+
result.changed
|
|
862
|
+
.sort((a, b) => b.count - a.count)
|
|
863
|
+
.forEach(({ file, count }) => console.log(`${String(count).padStart(4)} ${file}`))
|
|
864
|
+
console.log(`\n${options.write ? 'Converted' : 'Would convert'} ${result.conversions} mention(s) across ${result.changed.length} of ${result.files} files.`)
|
|
865
|
+
console.log(`Left alone (ambiguous, no separator): ${result.ambiguous.join(', ')}`)
|
|
866
|
+
if (result.skippedConfigRefs && result.skippedConfigRefs.length) {
|
|
867
|
+
console.log(`config_ref calls left alone (names not in the published JSON): ${result.skippedConfigRefs.join(', ')}`)
|
|
868
|
+
}
|
|
869
|
+
if (!options.write && result.conversions) console.log('Re-run with --write to apply.')
|
|
870
|
+
})
|
|
871
|
+
|
|
830
872
|
/**
|
|
831
873
|
* generate property-docs
|
|
832
874
|
*
|
|
@@ -1660,6 +1702,21 @@ automation
|
|
|
1660
1702
|
return match
|
|
1661
1703
|
}
|
|
1662
1704
|
})
|
|
1705
|
+
// Antora resolves page metadata with a header-only parse that stops at
|
|
1706
|
+
// the first blank line. The chart README templates emit :description:
|
|
1707
|
+
// below the title's blank line, so the page shipped the generic site
|
|
1708
|
+
// meta description despite carrying one (found on k-connect-helm-spec,
|
|
1709
|
+
// DOC-2414 investigation). Relocate the first :description: into the
|
|
1710
|
+
// header, directly under the title.
|
|
1711
|
+
const descMatch = doc.match(/^:description:[^\n]*$/m)
|
|
1712
|
+
if (descMatch) {
|
|
1713
|
+
const headerEnd = doc.indexOf('\n\n')
|
|
1714
|
+
if (headerEnd !== -1 && doc.indexOf(descMatch[0]) > headerEnd) {
|
|
1715
|
+
doc = doc.replace(descMatch[0] + '\n', '')
|
|
1716
|
+
doc = doc.replace(/^(= .+)$/m, `$1\n${descMatch[0]}`)
|
|
1717
|
+
doc = doc.replace(/\n{3,}/g, '\n\n')
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1663
1720
|
fs.writeFileSync(outFile, doc, 'utf8')
|
|
1664
1721
|
|
|
1665
1722
|
console.log(`Done: Wrote ${outFile}`)
|
|
@@ -2118,5 +2175,131 @@ automation
|
|
|
2118
2175
|
}
|
|
2119
2176
|
})
|
|
2120
2177
|
|
|
2178
|
+
const validation = new Command('validate').description('Validate docs data against internal sources of truth')
|
|
2179
|
+
|
|
2180
|
+
/**
|
|
2181
|
+
* @description Checks the enterprise features registry (the shared component's
|
|
2182
|
+
* enterprise-features.yml in the docs repo) against the internal sources of
|
|
2183
|
+
* truth: the license_required_feature enum and config::enterprise<> property
|
|
2184
|
+
* wrappers in redpanda core, the enterprise plugins in connect info.csv, and
|
|
2185
|
+
* the hand-maintained disable-enterprise-features.adoc table.
|
|
2186
|
+
* Exit codes: 0 clean, 1 drift found (error or needs-human findings), 2 execution error.
|
|
2187
|
+
*
|
|
2188
|
+
* @why A feature must only be documented as enterprise under its approved
|
|
2189
|
+
* external name. This check catches new license-gated features in core that
|
|
2190
|
+
* have no registry entry yet, registry pointers that no longer match core,
|
|
2191
|
+
* and stale connect enterprise lists.
|
|
2192
|
+
*
|
|
2193
|
+
* @example
|
|
2194
|
+
* # Check everything against the default remote sources
|
|
2195
|
+
* npx doc-tools validate enterprise-features
|
|
2196
|
+
*
|
|
2197
|
+
* # Check a local registry file (for example, in docs repo CI)
|
|
2198
|
+
* npx doc-tools validate enterprise-features --registry shared/modules/ROOT/partials/enterprise-features.yml
|
|
2199
|
+
*
|
|
2200
|
+
* # Regenerate the rpk name-mapping partial
|
|
2201
|
+
* npx doc-tools validate enterprise-features --write-mapping modules/get-started/partials/licensing/feature-name-mapping.adoc
|
|
2202
|
+
*
|
|
2203
|
+
* @requirements
|
|
2204
|
+
* Network access to raw.githubusercontent.com for any source not supplied
|
|
2205
|
+
* with a local path option.
|
|
2206
|
+
*/
|
|
2207
|
+
validation
|
|
2208
|
+
.command('enterprise-features')
|
|
2209
|
+
.description('Check the enterprise features registry against core, connect, and docs sources of truth')
|
|
2210
|
+
.option('--registry <path>', 'Local path to enterprise-features.yml (default: fetch from docs repo main)')
|
|
2211
|
+
.option('--tag <ref>', 'Redpanda git ref for the core headers', 'dev')
|
|
2212
|
+
.option('--connect-ref <ref>', 'Connect git ref for info.csv', 'main')
|
|
2213
|
+
.option('--docs-ref <ref>', 'Docs repo git ref for remote fetches', 'main')
|
|
2214
|
+
.option('--disable-page <path>', 'Local path to disable-enterprise-features.adoc (default: fetch from docs repo)')
|
|
2215
|
+
.option('--antora <path>', 'Local path to rp-connect-docs antora.yml (default: fetch from rp-connect-docs main)')
|
|
2216
|
+
.option('--properties <path>', 'Local path to a generated cluster-properties JSON; enables existence checks for gating properties')
|
|
2217
|
+
.option('--skip-connect', 'Skip the connect info.csv comparison')
|
|
2218
|
+
.option('--format <format>', 'Output format: text or json', 'text')
|
|
2219
|
+
.option('--write-mapping <path>', 'Write the internal-to-external name mapping partial to this path')
|
|
2220
|
+
.action(async (options) => {
|
|
2221
|
+
const { runChecks, buildMappingPartial } = require('../tools/enterprise-features/verify')
|
|
2222
|
+
const { extractAntoraEnterpriseComponents } = require('../tools/enterprise-features/parsers')
|
|
2223
|
+
const yamlLib = require('js-yaml')
|
|
2224
|
+
|
|
2225
|
+
const RAW = 'https://raw.githubusercontent.com'
|
|
2226
|
+
// Sources that fail to load are reported as error-level findings rather
|
|
2227
|
+
// than silently skipped, so CI cannot pass on a check that never ran.
|
|
2228
|
+
const failedSources = []
|
|
2229
|
+
async function fetchText (url, sourceName) {
|
|
2230
|
+
const resp = await fetch(url)
|
|
2231
|
+
if (!resp.ok) {
|
|
2232
|
+
if (sourceName) {
|
|
2233
|
+
failedSources.push({ level: 'error', check: 'fetch', message: `Could not load ${sourceName} (${url}): ${resp.status} ${resp.statusText}. The related checks did not run.` })
|
|
2234
|
+
return undefined
|
|
2235
|
+
}
|
|
2236
|
+
throw new Error(`Failed to fetch ${url}: ${resp.status} ${resp.statusText}`)
|
|
2237
|
+
}
|
|
2238
|
+
return resp.text()
|
|
2239
|
+
}
|
|
2240
|
+
const readLocal = (p) => fs.readFileSync(path.resolve(p), 'utf8')
|
|
2241
|
+
|
|
2242
|
+
try {
|
|
2243
|
+
const registryYaml = options.registry
|
|
2244
|
+
? readLocal(options.registry)
|
|
2245
|
+
: await fetchText(`${RAW}/redpanda-data/docs/${options.docsRef}/shared/modules/ROOT/partials/enterprise-features.yml`)
|
|
2246
|
+
const coreHeader = await fetchText(`${RAW}/redpanda-data/redpanda/${options.tag}/src/v/features/enterprise_features.h`, 'core enterprise_features.h')
|
|
2247
|
+
const configurationHeader = await fetchText(`${RAW}/redpanda-data/redpanda/${options.tag}/src/v/config/configuration.h`, 'core configuration.h')
|
|
2248
|
+
const infoCsv = options.skipConnect
|
|
2249
|
+
? undefined
|
|
2250
|
+
: await fetchText(`${RAW}/redpanda-data/connect/${options.connectRef}/internal/plugins/info.csv`, 'connect info.csv')
|
|
2251
|
+
const disablePage = options.disablePage
|
|
2252
|
+
? readLocal(options.disablePage)
|
|
2253
|
+
: await fetchText(`${RAW}/redpanda-data/docs/${options.docsRef}/modules/get-started/pages/licensing/disable-enterprise-features.adoc`, 'disable-enterprise-features.adoc')
|
|
2254
|
+
const antoraYaml = options.antora
|
|
2255
|
+
? readLocal(options.antora)
|
|
2256
|
+
: await fetchText(`${RAW}/redpanda-data/rp-connect-docs/main/antora.yml`, 'rp-connect-docs antora.yml')
|
|
2257
|
+
const antoraEnterpriseComponents = antoraYaml
|
|
2258
|
+
? extractAntoraEnterpriseComponents(yamlLib.load(antoraYaml))
|
|
2259
|
+
: undefined
|
|
2260
|
+
|
|
2261
|
+
let allPropertyNames
|
|
2262
|
+
if (options.properties) {
|
|
2263
|
+
const propertyData = JSON.parse(readLocal(options.properties))
|
|
2264
|
+
allPropertyNames = Object.keys(propertyData.properties || propertyData)
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
const { findings, features, enumValues } = runChecks({
|
|
2268
|
+
registryYaml,
|
|
2269
|
+
coreHeader,
|
|
2270
|
+
configurationHeader,
|
|
2271
|
+
infoCsv,
|
|
2272
|
+
antoraEnterpriseComponents,
|
|
2273
|
+
disablePage,
|
|
2274
|
+
allPropertyNames,
|
|
2275
|
+
})
|
|
2276
|
+
findings.push(...failedSources)
|
|
2277
|
+
|
|
2278
|
+
if (options.writeMapping) {
|
|
2279
|
+
const partial = buildMappingPartial(features, enumValues)
|
|
2280
|
+
fs.mkdirSync(path.dirname(path.resolve(options.writeMapping)), { recursive: true })
|
|
2281
|
+
fs.writeFileSync(path.resolve(options.writeMapping), `${partial}\n`)
|
|
2282
|
+
console.log(`Wrote name mapping partial to ${options.writeMapping}`)
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
const drift = findings.filter((f) => f.level === 'error' || f.level === 'needs-human')
|
|
2286
|
+
if (options.format === 'json') {
|
|
2287
|
+
console.log(JSON.stringify({ findings, drift: drift.length > 0 }, null, 2))
|
|
2288
|
+
} else {
|
|
2289
|
+
for (const level of ['error', 'needs-human', 'info']) {
|
|
2290
|
+
for (const f of findings.filter((entry) => entry.level === level)) {
|
|
2291
|
+
console.log(`${level.toUpperCase()} [${f.check}] ${f.message}`)
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
console.log(drift.length ? `\n${drift.length} finding(s) need attention.` : '\nRegistry is in sync with all checked sources.')
|
|
2295
|
+
}
|
|
2296
|
+
process.exit(drift.length ? 1 : 0)
|
|
2297
|
+
} catch (err) {
|
|
2298
|
+
console.error(`Error: ${err.message}`)
|
|
2299
|
+
process.exit(2)
|
|
2300
|
+
}
|
|
2301
|
+
})
|
|
2302
|
+
|
|
2121
2303
|
programCli.addCommand(automation)
|
|
2304
|
+
programCli.addCommand(validation)
|
|
2122
2305
|
programCli.parse(process.argv)
|
|
@@ -187,6 +187,116 @@ function generateFrontmatter(page) {
|
|
|
187
187
|
return `---\n${yamlContent}---\n\n`
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
// Default class the enterprise inline macro puts on its wrapping span. The
|
|
191
|
+
// macro lets a playbook override it via the enterprise-feature-role document
|
|
192
|
+
// attribute, so match on a substring rather than the exact value.
|
|
193
|
+
const ENTERPRISE_FEATURE_CLASS = 'enterprise-feature'
|
|
194
|
+
|
|
195
|
+
// Status words collected into the single trailing parenthetical.
|
|
196
|
+
const ENTERPRISE_STATUS = 'enterprise'
|
|
197
|
+
const BETA_STATUS = 'beta'
|
|
198
|
+
|
|
199
|
+
// Class the badge macro puts on a beta badge.
|
|
200
|
+
const BETA_BADGE_CLASS = 'badge--beta'
|
|
201
|
+
|
|
202
|
+
// Explicit textual marker for enterprise features in Markdown output.
|
|
203
|
+
const ENTERPRISE_MARKER = `(${ENTERPRISE_STATUS})`
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Format one parenthetical from the status words that apply to a feature.
|
|
207
|
+
*
|
|
208
|
+
* A beta enterprise feature carries two markers, and emitting them separately
|
|
209
|
+
* reads as "Stretch Clusters (enterprise) (beta)". One list avoids the stutter.
|
|
210
|
+
*
|
|
211
|
+
* @param {string[]} statuses - Status words, in display order.
|
|
212
|
+
* @returns {string} Parenthetical, or an empty string when there are none.
|
|
213
|
+
*/
|
|
214
|
+
function formatStatusMarker (statuses) {
|
|
215
|
+
const words = statuses.filter(Boolean)
|
|
216
|
+
return words.length ? `(${words.join(', ')})` : ''
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function hasClass (node, className) {
|
|
220
|
+
if (!node || node.nodeName !== 'SPAN') return false
|
|
221
|
+
const classAttr = node.getAttribute?.('class') || node.className || ''
|
|
222
|
+
return typeof classAttr === 'string' && classAttr.includes(className)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* The next sibling element, skipping the whitespace text nodes that sit between
|
|
227
|
+
* the enterprise span and a badge that follows it.
|
|
228
|
+
*/
|
|
229
|
+
function nextElementSibling (node) {
|
|
230
|
+
let sibling = node && node.nextSibling
|
|
231
|
+
while (sibling && sibling.nodeType === 3 && !(sibling.textContent || '').trim()) {
|
|
232
|
+
sibling = sibling.nextSibling
|
|
233
|
+
}
|
|
234
|
+
return sibling && sibling.nodeType === 1 ? sibling : null
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Turndown rules that keep the enterprise and beta signals in Markdown output.
|
|
239
|
+
*
|
|
240
|
+
* In HTML both signals are carried by things Turndown discards. The enterprise
|
|
241
|
+
* badge is drawn by CSS from the span's class and explained by a title tooltip,
|
|
242
|
+
* so `enterprise:Tiered Storage[]` would arrive in Markdown as the bare words
|
|
243
|
+
* "Tiered Storage", indistinguishable from any other feature name. The beta
|
|
244
|
+
* badge does survive, because its label keeps real bracketed text, but it lands
|
|
245
|
+
* as a second parenthetical right after the first.
|
|
246
|
+
*
|
|
247
|
+
* These rules emit one trailing parenthetical instead, listing every status
|
|
248
|
+
* that applies, and pass the span's already-converted inner Markdown through
|
|
249
|
+
* untouched so a linked feature keeps its link:
|
|
250
|
+
*
|
|
251
|
+
* [Stretch Clusters](../deploy/stretch-clusters.md) (enterprise, beta)
|
|
252
|
+
*
|
|
253
|
+
* The beta badge is a sibling of the enterprise span rather than a child. The
|
|
254
|
+
* enterprise rule looks ahead for it and, when it finds one, emits no marker of
|
|
255
|
+
* its own and hands the combined marker to the badge. Emitting it there rather
|
|
256
|
+
* than on the enterprise span reuses the whitespace already between the two
|
|
257
|
+
* elements, instead of suppressing the badge and leaving a doubled space.
|
|
258
|
+
*
|
|
259
|
+
* @returns {{enterpriseFeature: object, betaBadge: object}} Turndown rules,
|
|
260
|
+
* both of which must be registered.
|
|
261
|
+
*/
|
|
262
|
+
function createEnterpriseFeatureRules () {
|
|
263
|
+
// Scoped per call so each converter tracks its own deferred markers.
|
|
264
|
+
const deferredToBadge = new WeakSet()
|
|
265
|
+
|
|
266
|
+
const enterpriseFeature = {
|
|
267
|
+
filter: (node) => hasClass(node, ENTERPRISE_FEATURE_CLASS),
|
|
268
|
+
replacement: (content, node) => {
|
|
269
|
+
const inner = (content || '').trim()
|
|
270
|
+
// An empty span carries no feature to mark.
|
|
271
|
+
if (!inner) return ''
|
|
272
|
+
// Nested or already-marked content must not collect a second marker.
|
|
273
|
+
if (inner.toLowerCase().endsWith(ENTERPRISE_MARKER)) return inner
|
|
274
|
+
|
|
275
|
+
const sibling = nextElementSibling(node)
|
|
276
|
+
if (hasClass(sibling, BETA_BADGE_CLASS)) {
|
|
277
|
+
// The badge renders "(enterprise, beta)" for both of us.
|
|
278
|
+
deferredToBadge.add(sibling)
|
|
279
|
+
return inner
|
|
280
|
+
}
|
|
281
|
+
return `${inner} ${ENTERPRISE_MARKER}`
|
|
282
|
+
},
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const betaBadge = {
|
|
286
|
+
filter: (node) => hasClass(node, BETA_BADGE_CLASS),
|
|
287
|
+
replacement: (content, node) =>
|
|
288
|
+
deferredToBadge.has(node)
|
|
289
|
+
? formatStatusMarker([ENTERPRISE_STATUS, BETA_STATUS])
|
|
290
|
+
: formatStatusMarker([BETA_STATUS]),
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return { enterpriseFeature, betaBadge }
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
module.exports.createEnterpriseFeatureRules = createEnterpriseFeatureRules
|
|
297
|
+
module.exports.formatStatusMarker = formatStatusMarker
|
|
298
|
+
module.exports.ENTERPRISE_MARKER = ENTERPRISE_MARKER
|
|
299
|
+
|
|
190
300
|
module.exports.register = function () {
|
|
191
301
|
raiseListenerLimit(this)
|
|
192
302
|
const logger = this.getLogger('convert-to-markdown-extension')
|
|
@@ -406,6 +516,11 @@ module.exports.register = function () {
|
|
|
406
516
|
})
|
|
407
517
|
|
|
408
518
|
// Markdown table conversion
|
|
519
|
+
// Keep the enterprise-license signal, which is otherwise CSS-only.
|
|
520
|
+
const enterpriseRules = createEnterpriseFeatureRules()
|
|
521
|
+
turndownInstance.addRule('enterprise-feature', enterpriseRules.enterpriseFeature)
|
|
522
|
+
turndownInstance.addRule('beta-badge', enterpriseRules.betaBadge)
|
|
523
|
+
|
|
409
524
|
turndownInstance.addRule('tables', {
|
|
410
525
|
filter: (node) => {
|
|
411
526
|
if (node.nodeName !== 'TABLE') return false
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/* Antora extension that warms the prop macro's property-page index before
|
|
4
|
+
* document conversion starts.
|
|
5
|
+
*
|
|
6
|
+
* The prop macro discovers which reference page documents each property by
|
|
7
|
+
* reading page sources for include:: lines and partials for property
|
|
8
|
+
* headings. Antora replaces each page's contents with converted HTML as
|
|
9
|
+
* conversion proceeds, so an index built lazily by the first macro call can
|
|
10
|
+
* miss includes on pages that were converted earlier — the result silently
|
|
11
|
+
* depends on conversion order. Building the index at contentClassified,
|
|
12
|
+
* while every page still holds its AsciiDoc source, removes the ordering
|
|
13
|
+
* hazard entirely. The macro finds the warmed cache and never rebuilds it.
|
|
14
|
+
*
|
|
15
|
+
* Register under antora.extensions (NOT asciidoc.extensions):
|
|
16
|
+
*
|
|
17
|
+
* antora:
|
|
18
|
+
* extensions:
|
|
19
|
+
* - require: '@redpanda-data/docs-extensions-and-macros/extensions/property-page-index'
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const { buildPageIndex, loadPropertiesFor } = require('../macros/prop')
|
|
23
|
+
const { raiseListenerLimit } = require('./util/raise-listener-limit')
|
|
24
|
+
|
|
25
|
+
module.exports.register = function () {
|
|
26
|
+
raiseListenerLimit(this)
|
|
27
|
+
const logger = this.getLogger('property-page-index-extension')
|
|
28
|
+
|
|
29
|
+
this.once('contentClassified', ({ contentCatalog }) => {
|
|
30
|
+
let indexed = 0
|
|
31
|
+
for (const component of contentCatalog.getComponents()) {
|
|
32
|
+
for (const componentVersion of component.versions) {
|
|
33
|
+
const registry = loadPropertiesFor(contentCatalog, component.name, componentVersion.version)
|
|
34
|
+
if (!registry) continue
|
|
35
|
+
const index = buildPageIndex(contentCatalog, component.name, registry.properties, componentVersion.version)
|
|
36
|
+
if (index.size > 0) {
|
|
37
|
+
indexed++
|
|
38
|
+
logger.debug(`Indexed ${index.size} properties for ${component.name}@${componentVersion.version || 'default'}`)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (indexed > 0) logger.info(`Property page index warmed for ${indexed} component version(s)`)
|
|
43
|
+
})
|
|
44
|
+
}
|
|
@@ -68,7 +68,7 @@ function validateCategories(pageCategoryList, pageInfo, categoryMap, logger) {
|
|
|
68
68
|
});
|
|
69
69
|
|
|
70
70
|
if (!isValid) {
|
|
71
|
-
logger.warn(`Invalid categories detected. For a list of valid categories, see https://github.com/redpanda-data/docs/
|
|
71
|
+
logger.warn(`Invalid categories detected. For a list of valid categories, see https://github.com/redpanda-data/docs/blob/main/shared/modules/ROOT/partials/valid-categories.yml`);
|
|
72
72
|
}
|
|
73
73
|
return Array.from(adjustedCategories).join(', ');
|
|
74
74
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
= How property and enterprise references work
|
|
2
|
+
:toc:
|
|
3
|
+
:toclevels: 2
|
|
4
|
+
|
|
5
|
+
A plain-English explanation of how the docs mark configuration properties and enterprise features, why it works this way, and what the tooling does for you. For attribute-by-attribute details, see link:REFERENCE.adoc[the macros reference].
|
|
6
|
+
|
|
7
|
+
== The problem this solves
|
|
8
|
+
|
|
9
|
+
The docs used to *guess*. Property tooltips decorated every piece of inline code whose text happened to match a property name. That works fine for a name like `cloud_storage_enabled`, but five real property names are ordinary words: `admin`, `brokers`, `rack`, `retries`, and `superusers`. On a Helm chart page, `admin` usually means an audit-logging setting or a listener name -- and readers still got a tooltip describing the Admin API broker property. Wrong information, delivered confidently.
|
|
10
|
+
|
|
11
|
+
Enterprise feature mentions had the opposite problem: nothing checked them at all. Any writer could mark any feature as enterprise, under any name, and nothing would ever notice a typo, a made-up name, or a feature that isn't actually enterprise.
|
|
12
|
+
|
|
13
|
+
The fix for both is the same idea: *marking is explicit, and every mark is checked against a source of truth at build time.*
|
|
14
|
+
|
|
15
|
+
== Property references: the prop macro
|
|
16
|
+
|
|
17
|
+
To reference a configuration property in prose, write:
|
|
18
|
+
|
|
19
|
+
[,asciidoc]
|
|
20
|
+
----
|
|
21
|
+
prop:cloud_storage_enabled[]
|
|
22
|
+
----
|
|
23
|
+
|
|
24
|
+
That's it. Here's what happens for you:
|
|
25
|
+
|
|
26
|
+
*It's checked.* The name is looked up in the published property data -- the same JSON, generated from Redpanda's source code, that powers the reference pages and tooltips. A typo gets a build warning with the file name and a "did you mean" suggestion. Nothing unverified ships. (A site can turn this up to a hard build failure with the `property-validate` attribute.)
|
|
27
|
+
|
|
28
|
+
*It gets a tooltip.* The docs UI decorates only elements the macro marked. A plain backticked word never gets a tooltip anymore, so `admin` in a Helm example stays plain text unless a writer deliberately writes `prop:admin[]`.
|
|
29
|
+
|
|
30
|
+
*It can link itself.* Add `link=true` and the property links to its reference page. You don't say which page -- the build works that out by looking at which reference page actually documents that property (details below). If our property reference is ever reorganized into different pages, every link follows automatically.
|
|
31
|
+
|
|
32
|
+
*It adapts to Kubernetes pages.* Add `helm-path=auto` and, on pages rendered for Kubernetes, the property displays as the Helm values path readers actually type -- `config.cluster.log_segment_size`, `config.node.fips_mode`, or `storage.tiered.config.cloud_storage_enabled` for tiered storage settings. On Linux pages the same call shows the plain name. One sentence, correct for both audiences.
|
|
33
|
+
|
|
34
|
+
*Once per paragraph is enough.* Mark the first mention of a property in a paragraph and leave later mentions as plain backticks -- a paragraph doesn't need three identical tooltips. The migration tool follows this rule, and the UI enforces it as a backstop: even if every mention is marked, only the first one per paragraph gets the tooltip.
|
|
35
|
+
|
|
36
|
+
=== How "the build works out the page" (dynamic discovery)
|
|
37
|
+
|
|
38
|
+
Rather than hardcoding "cluster properties live on the cluster-properties page," the macro reads the actual docs during the build. Every documented property appears as a heading in the generated property files, and each reference page pulls in one or more of those files. The macro follows that chain -- which pages exist, what they include, which property headings end up where -- and builds a lookup table per doc set.
|
|
39
|
+
|
|
40
|
+
Because the lookup is done separately for each part of the site, a Cloud page links to Cloud's own property pages and a streaming page to streaming's. Cloud reuses streaming's property files with a filter ("only the cloud-relevant parts"), and the macro applies that same filter, so it never links to a property that a given page doesn't actually document. If a doc set doesn't cover a property at all -- Cloud publishes no topic-properties page, Connect publishes none -- the link borrows the streaming docs instead of pointing nowhere.
|
|
41
|
+
|
|
42
|
+
Think phone book, not memory: if a property moves, the next build's phone book is already right.
|
|
43
|
+
|
|
44
|
+
=== What happened to config_ref
|
|
45
|
+
|
|
46
|
+
The old `config_ref` macro did a smaller version of this job with three problems: you had to tell it which page to link to by hand, it never checked the property name, and on Kubernetes pages it prefixed *every* property with `storage.tiered.config.` -- which is only correct for tiered storage settings. The live docs really did render `storage.tiered.config.log_segment_size`, a path that mislabels where that setting belongs.
|
|
47
|
+
|
|
48
|
+
`config_ref` is deprecated. Every hand-written call site has been converted, the property extractor now emits `prop` in generated content, and the published JSON was cleaned up (it had been leaking raw macro text into tooltips). The macro stays registered only because older versioned docs, which are frozen, still contain it. Don't write it in new content.
|
|
49
|
+
|
|
50
|
+
=== Migrating existing content
|
|
51
|
+
|
|
52
|
+
One command converts a repo's prose:
|
|
53
|
+
|
|
54
|
+
[,bash]
|
|
55
|
+
----
|
|
56
|
+
npx doc-tools generate migrate-property-refs \
|
|
57
|
+
--properties modules/reference/attachments/redpanda-properties-<tag>.json \
|
|
58
|
+
--config-refs
|
|
59
|
+
----
|
|
60
|
+
|
|
61
|
+
It's a dry run by default; add `--write` to apply. It only converts names it can verify in the published JSON, never touches the five ambiguous word-like names, and skips code blocks, headings, generated content, and anything already inside another macro.
|
|
62
|
+
|
|
63
|
+
One thing no tool can check: *meaning*. Marking a name asserts "this mention refers to the Redpanda property" -- and even distinctive names collide in foreign-vocabulary contexts. A Terraform provider table's `replication.factor` argument, a SQL statement's `cluster_id` parameter, or an infrastructure checklist's `cloud_storage_bucket` are other systems' identifiers that happen to share a spelling. When reviewing a migration, unmark those. When writing, only reach for `prop:` when a tooltip describing the Redpanda property would help the reader in that sentence.
|
|
64
|
+
|
|
65
|
+
== Enterprise feature references: the enterprise macro
|
|
66
|
+
|
|
67
|
+
The same explicit-and-checked idea, for licensing:
|
|
68
|
+
|
|
69
|
+
[,asciidoc]
|
|
70
|
+
----
|
|
71
|
+
enterprise:Tiered Storage[]
|
|
72
|
+
----
|
|
73
|
+
|
|
74
|
+
The name is validated against the *enterprise features registry* -- a single YAML file in the shared component (`shared/modules/ROOT/partials/enterprise-features.yml` in the docs repo) that is the canonical list of what is enterprise and what each feature is called externally. Writers can't misname a feature (aliases and case differences resolve to the canonical name, and internal engineering names like `cloud_storage` render as their proper external name) and can't mark something enterprise that isn't in the registry. Adding a feature to the registry is a reviewed docs change, and each entry records *why* it's enterprise by pointing at the internal source of truth (a license-gate in Redpanda's source, the Connect plugin catalog, or a written justification).
|
|
75
|
+
|
|
76
|
+
Two more things come from the same registry:
|
|
77
|
+
|
|
78
|
+
* The licensing feature tables on the licensing pages are *generated* from it (`enterprise_features::redpanda[]` and friends), so the tables can never disagree with the registry.
|
|
79
|
+
* `doc-tools validate enterprise-features` compares the registry against the actual license gates in Redpanda's source code and Connect's plugin list. When engineering ships a new license-gated feature, CI fails until someone decides its documented name -- naming stays a human decision, but forgetting is no longer possible.
|
|
80
|
+
|
|
81
|
+
Like properties, feature links adapt to context: registry entries can carry Kubernetes- and Cloud-specific pages, chosen by the page's environment attributes.
|
|
82
|
+
|
|
83
|
+
== Where to see all of this working
|
|
84
|
+
|
|
85
|
+
* The extensions preview site's test page demonstrates every form of both macros, including intentional failure cases, and a Kubernetes-environment page shows the environment-specific behavior side by side.
|
|
86
|
+
* The docs-ui preview's `property-tooltips-test` and `enterprise-macro-test` pages show every output shape the UI styles, including the negative cases (unmarked names must get nothing).
|
|
87
|
+
|
|
88
|
+
== The one-sentence version
|
|
89
|
+
|
|
90
|
+
Nothing in the docs claims a name is a property or a feature is enterprise unless a writer said so explicitly *and* the build verified it against the source of truth.
|
package/macros/README.adoc
CHANGED
|
@@ -10,6 +10,8 @@ AsciiDoc macros are custom inline and block elements that extend AsciiDoc syntax
|
|
|
10
10
|
|
|
11
11
|
== Documentation
|
|
12
12
|
|
|
13
|
+
link:PROPERTY_AND_ENTERPRISE_REFERENCES.adoc[**How property and enterprise references work**]:: Plain-English explanation of the prop and enterprise macros: explicit marking, build-time validation against sources of truth, dynamic link discovery, and why config_ref was deprecated
|
|
14
|
+
|
|
13
15
|
link:USER_GUIDE.adoc[**User guide**]:: How to use AsciiDoc macros in your documentation
|
|
14
16
|
+
|
|
15
17
|
* Installation and setup
|