@redpanda-data/docs-extensions-and-macros 5.8.0 → 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 +168 -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/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
|
*
|
|
@@ -2133,5 +2175,131 @@ automation
|
|
|
2133
2175
|
}
|
|
2134
2176
|
})
|
|
2135
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
|
+
|
|
2136
2303
|
programCli.addCommand(automation)
|
|
2304
|
+
programCli.addCommand(validation)
|
|
2137
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
|
package/macros/REFERENCE.adoc
CHANGED
|
@@ -20,6 +20,8 @@ antora:
|
|
|
20
20
|
|
|
21
21
|
== config_ref
|
|
22
22
|
|
|
23
|
+
IMPORTANT: `config_ref` is deprecated. The <<prop>> macro supersedes all of its functionality: it validates names against the published property reference, discovers the target page dynamically instead of taking a manual path, powers hover tooltips, and its `helm-path=auto` attribute replaces `config_ref`'s Kubernetes prefixing with a per-property correct Helm values path (`config_ref` prefixed every property with `storage.tiered.config.`, mislabeling non-tiered properties). Migrate call sites with `doc-tools generate migrate-property-refs --config-refs`. The macro stays registered only because the generated property partials still emit it.
|
|
24
|
+
|
|
23
25
|
This inline macro is used to generate a reference to a configuration value in the Redpanda documentation. The macro's parameters allow for control over the generated reference's format and the type of output produced.
|
|
24
26
|
|
|
25
27
|
=== Usage
|
|
@@ -62,6 +64,171 @@ asciidoc:
|
|
|
62
64
|
- '@redpanda-data/docs-extensions-and-macros/macros/config-ref'
|
|
63
65
|
----
|
|
64
66
|
|
|
67
|
+
== enterprise
|
|
68
|
+
|
|
69
|
+
This inline macro marks a feature as an enterprise feature in prose. The feature name renders as a uniquely styled term with a tooltip explaining that the feature requires an Enterprise Edition license, and links to the feature's documentation or to the licensing page.
|
|
70
|
+
|
|
71
|
+
The macro validates its target against the enterprise features registry: `modules/ROOT/partials/enterprise-features.yml` in the `shared` component (the `shared/` folder of the docs repository). The registry is the canonical list of enterprise features and their approved external names. Targets are matched case-insensitively, including aliases, and the canonical name, feature page link, and tooltip come from the matching entry. A target that matches no entry is reported according to `enterprise-validate`. To mark a new feature as enterprise, add it to the registry first.
|
|
72
|
+
|
|
73
|
+
=== Usage
|
|
74
|
+
|
|
75
|
+
[,asciidoc]
|
|
76
|
+
----
|
|
77
|
+
enterprise:Continuous Data Balancing[]
|
|
78
|
+
enterprise:Tiered Storage[xref=manage:tiered-storage.adoc]
|
|
79
|
+
enterprise:Audit Logging[text=audit logging]
|
|
80
|
+
enterprise:Iceberg Topics[tooltip=Iceberg Topics requires an Enterprise Edition license and object storage.]
|
|
81
|
+
----
|
|
82
|
+
|
|
83
|
+
The macro target is the feature name, which can contain spaces. Named attributes:
|
|
84
|
+
|
|
85
|
+
xref::
|
|
86
|
+
Optional Antora resource ID of the feature's documentation page. Overrides the registry entry's link. When neither is set, the term links to the licensing page.
|
|
87
|
+
+
|
|
88
|
+
Registry entries can carry environment-specific pages: on pages with the `env-cloud` attribute the entry's `xref-cloud` is used, on pages with `env-kubernetes` the entry's `xref-kubernetes` is used, and otherwise the entry's default `xref` (the Linux or generic self-managed page). This mirrors the `config_ref` macro's environment awareness.
|
|
89
|
+
|
|
90
|
+
text::
|
|
91
|
+
Optional display text override. Use it when the sentence needs different casing or phrasing than the feature name.
|
|
92
|
+
|
|
93
|
+
tooltip::
|
|
94
|
+
Optional tooltip text override. Overrides the registry entry's `tooltip`. The default is `<feature> requires an Enterprise Edition license.`
|
|
95
|
+
|
|
96
|
+
=== Configuration options
|
|
97
|
+
|
|
98
|
+
enterprise-validate::
|
|
99
|
+
How to report a target that does not match any registry entry: `warn` (default) logs a build warning with the file path and close-match suggestions, `error` fails the build, and `off` disables validation. When no registry is available in the build, the macro renders without validation and logs a single warning.
|
|
100
|
+
|
|
101
|
+
enterprise-licensing-page::
|
|
102
|
+
Resource ID of the licensing page used when no `xref` attribute is given. The default is `get-started:licensing/overview.adoc`.
|
|
103
|
+
|
|
104
|
+
enterprise-feature-role::
|
|
105
|
+
CSS class applied to the wrapping span. The default is `enterprise-feature`. Style this class in the UI bundle to give enterprise terms a consistent visual treatment.
|
|
106
|
+
|
|
107
|
+
enterprise-tooltip::
|
|
108
|
+
Controls the tooltip attribute: `title` (default), `true` (renders `data-enterprise-tooltip` for UI-driven tooltips), any attribute name starting with `data-`, or `false` to disable tooltips.
|
|
109
|
+
|
|
110
|
+
enterprise-links::
|
|
111
|
+
Set to `false` to render the term without a link.
|
|
112
|
+
|
|
113
|
+
enterprise-beta-badge::
|
|
114
|
+
Set to `false` to suppress the beta badge in prose. Generated tables always show it. See <<beta-features>>.
|
|
115
|
+
|
|
116
|
+
[[beta-features]]
|
|
117
|
+
=== Beta features
|
|
118
|
+
|
|
119
|
+
Add `beta: true` to a registry entry to mark the feature as beta. The macro then renders a beta badge after the feature name, both in prose and in the generated `enterprise_features` tables, so one registry field keeps every reference consistent:
|
|
120
|
+
|
|
121
|
+
[,yaml]
|
|
122
|
+
----
|
|
123
|
+
- name: Stretch Clusters
|
|
124
|
+
scope: operator
|
|
125
|
+
beta: true
|
|
126
|
+
feature-suffix: (StretchCluster resource)
|
|
127
|
+
----
|
|
128
|
+
|
|
129
|
+
Add `beta-tooltip` to give the badge hover text.
|
|
130
|
+
|
|
131
|
+
Do not write a `badge:` macro call into `feature-suffix` to achieve this. The badge is emitted as an inline passthrough, so it renders whether or not the consuming playbook registers the `badge` macro, and it degrades to the text `(beta)` in Markdown. A hand-written macro call renders as literal source text in any build that does not register that macro.
|
|
132
|
+
|
|
133
|
+
=== Markdown output
|
|
134
|
+
|
|
135
|
+
In HTML, the enterprise signal is carried by the span's CSS class and its tooltip attribute. Both are attributes, so an HTML-to-Markdown conversion drops them and the term arrives as an ordinary feature name. To keep the signal for readers of the Markdown pages, including AI agents, the `convert-to-markdown` extension appends an explicit `(enterprise)` marker and preserves any link:
|
|
136
|
+
|
|
137
|
+
[,markdown]
|
|
138
|
+
----
|
|
139
|
+
Enable [Tiered Storage](../manage/tiered-storage.md) (enterprise) today.
|
|
140
|
+
----
|
|
141
|
+
|
|
142
|
+
A feature that is also marked `beta: true` gets one combined marker rather than two adjacent parentheticals:
|
|
143
|
+
|
|
144
|
+
[,markdown]
|
|
145
|
+
----
|
|
146
|
+
Enable [Stretch Clusters](../deploy/stretch-clusters.md) (enterprise, beta) today.
|
|
147
|
+
----
|
|
148
|
+
|
|
149
|
+
This applies to the inline macro. The tables from `enterprise_features` need no enterprise marker, because their expiration column already states the licensing behavior as text. A beta badge in a table converts to `(beta)` on its own.
|
|
150
|
+
|
|
151
|
+
=== Registration
|
|
152
|
+
|
|
153
|
+
[,yaml]
|
|
154
|
+
----
|
|
155
|
+
asciidoc:
|
|
156
|
+
extensions:
|
|
157
|
+
- '@redpanda-data/docs-extensions-and-macros/macros/enterprise'
|
|
158
|
+
----
|
|
159
|
+
|
|
160
|
+
== enterprise_features
|
|
161
|
+
|
|
162
|
+
This block macro renders the licensing feature table for one scope from the enterprise features registry (see <<enterprise>>). Because the table is generated, it can never drift from the registry.
|
|
163
|
+
|
|
164
|
+
=== Usage
|
|
165
|
+
|
|
166
|
+
[,asciidoc]
|
|
167
|
+
----
|
|
168
|
+
enterprise_features::redpanda[]
|
|
169
|
+
enterprise_features::connect[]
|
|
170
|
+
enterprise_features::operator[title=Operator features,heading=What stops working]
|
|
171
|
+
----
|
|
172
|
+
|
|
173
|
+
The target is the scope: `redpanda`, `console`, `connect`, `operator`, or `cloud`. Rows render alphabetically by feature name with three columns: the feature name (linked to its documentation page when the entry has an `xref` or `url`), the description, and the expiration or restriction behavior. Named attributes:
|
|
174
|
+
|
|
175
|
+
title::
|
|
176
|
+
Optional table title override. The default is scope-specific, for example `Enterprise features in Redpanda`.
|
|
177
|
+
|
|
178
|
+
heading::
|
|
179
|
+
Optional third-column heading override. The default is `Behavior Upon Expiration` for the `redpanda` scope and `Restrictions Without Valid License` for the other scopes.
|
|
180
|
+
|
|
181
|
+
=== Registration
|
|
182
|
+
|
|
183
|
+
Registered together with the `enterprise` inline macro (see <<enterprise>>).
|
|
184
|
+
|
|
185
|
+
== prop
|
|
186
|
+
|
|
187
|
+
This inline macro references a Redpanda configuration property in prose. The property renders as a marked `<code>` element (`property-ref` class plus a `data-property-name` attribute) that the docs UI decorates with a hover-documentation tooltip. Marking is opt-in: only properties referenced through this macro get tooltips, so ambiguous words such as `admin` or `rack` in unrelated contexts (Helm values, feature settings) are never decorated by mistake.
|
|
188
|
+
|
|
189
|
+
The macro validates its target against the published property reference: the newest `redpanda-properties-<tag>.json` attachment in the `reference` module of the build, the same data the tooltips fetch at runtime. To migrate existing plain-backtick property mentions, use `doc-tools generate migrate-property-refs`.
|
|
190
|
+
|
|
191
|
+
=== Usage
|
|
192
|
+
|
|
193
|
+
[,asciidoc]
|
|
194
|
+
----
|
|
195
|
+
prop:cloud_storage_enabled[]
|
|
196
|
+
prop:iceberg_enabled[link=true]
|
|
197
|
+
prop:retention_ms[link=true,page=properties/topic-properties]
|
|
198
|
+
prop:write_caching_default[text=write caching]
|
|
199
|
+
----
|
|
200
|
+
|
|
201
|
+
The macro target is the property name. Named attributes:
|
|
202
|
+
|
|
203
|
+
link::
|
|
204
|
+
Set to `true` to also link the property to its reference page. The page is discovered dynamically: the macro indexes which reference-module page in the current component documents each property, by scanning property headings in the partials each page includes (respecting include tag filters such as `tags=redpanda-cloud`). Links are component-relative, so a Cloud page links to the Cloud component's own property pages, and they keep working if properties are split across different pages in the future. When discovery finds nothing, the page falls back deterministically to the property's `config_scope` (`cluster`, `broker`, or `topic`). This supersedes the `config_ref` macro's linking for most uses.
|
|
205
|
+
|
|
206
|
+
page::
|
|
207
|
+
Optional reference-page override (module-relative, without `.adoc`), for example `properties/object-storage-properties`. Overrides discovery entirely.
|
|
208
|
+
|
|
209
|
+
helm-path::
|
|
210
|
+
Set to `auto` to display the property as its Helm values path on pages rendered with the `env-kubernetes` attribute, so single-sourced content reads correctly for both Linux and Kubernetes audiences. The path is derived per property: `storage.tiered.config.<name>` for tiered storage properties, `config.node.<name>` for broker properties, and `config.cluster.<name>` for other cluster properties. On pages without `env-kubernetes`, the plain property name renders. This supersedes the deprecated `config_ref` macro's prefixing.
|
|
211
|
+
|
|
212
|
+
text::
|
|
213
|
+
Optional display text override. Wins over the Helm path display.
|
|
214
|
+
|
|
215
|
+
=== Configuration options
|
|
216
|
+
|
|
217
|
+
property-validate::
|
|
218
|
+
How to report a target that does not match any published property: `warn` (default) logs a build warning with the file path and close-match suggestions, `error` fails the build, and `off` disables validation. When no property JSON is available in the build, the macro renders without validation and logs a single warning.
|
|
219
|
+
|
|
220
|
+
property-ref-role::
|
|
221
|
+
CSS class applied to the code element. The default is `property-ref`.
|
|
222
|
+
|
|
223
|
+
=== Registration
|
|
224
|
+
|
|
225
|
+
[,yaml]
|
|
226
|
+
----
|
|
227
|
+
asciidoc:
|
|
228
|
+
extensions:
|
|
229
|
+
- '@redpanda-data/docs-extensions-and-macros/macros/prop'
|
|
230
|
+
----
|
|
231
|
+
|
|
65
232
|
== glossterm
|
|
66
233
|
|
|
67
234
|
The `glossterm` inline macro provides a way to define and reference glossary terms in your AsciiDoc documents.
|