@redpanda-data/docs-extensions-and-macros 5.56.1 → 5.58.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 +95 -1
- package/extension-utils/property-values.js +255 -0
- package/macros/PROPERTY_AND_ENTERPRISE_REFERENCES.adoc +25 -1
- package/macros/README.adoc +3 -1
- package/macros/REFERENCE.adoc +158 -1
- package/macros/USER_GUIDE.adoc +28 -0
- package/macros/prop.js +135 -0
- package/package.json +1 -1
- package/tools/lint-strings/surfaces/connect.js +18 -1
- package/tools/migrate-property-refs.js +15 -12
- package/tools/migrate-property-values.js +1255 -0
- package/tools/property-extractor/README.adoc +4 -2
- package/tools/redpanda-connect/AUTOMATION.md +4 -2
- package/tools/redpanda-connect/multi-version-summary.js +2 -1
- package/tools/redpanda-connect/pr-summary-formatter.js +49 -8
- package/tools/redpanda-connect/report-delta.js +14 -5
- package/tools/redpanda-connect/rpcn-connector-docs-handler.js +17 -8
- package/tools/redpanda-release-notes/generate-release-notes.js +87 -48
- package/tools/redpanda-release-notes/parse-release-body.js +33 -4
- package/tools/redpanda-release-notes/property-names.js +65 -0
package/bin/doc-tools.js
CHANGED
|
@@ -1136,6 +1136,73 @@ automation
|
|
|
1136
1136
|
if (!options.write && result.conversions) console.log('Re-run with --write to apply.')
|
|
1137
1137
|
})
|
|
1138
1138
|
|
|
1139
|
+
/**
|
|
1140
|
+
* @description Report hand-written property values in prose ("defaults to
|
|
1141
|
+
* 128 MiB", "the minimum is 1 minute") next to a property mention, check each
|
|
1142
|
+
* against the property data for the repo's own version (selected exactly as
|
|
1143
|
+
* the prop: macro selects it), and propose a propval: replacement. Mismatches
|
|
1144
|
+
* are likely doc errors. Also reads example command output in listing
|
|
1145
|
+
* blocks (rpk topic describe, rpk cluster config get and export, Admin API
|
|
1146
|
+
* cluster_config, kafka-configs --describe, Key/Value/Source records) and
|
|
1147
|
+
* reports a row that asserts the default, such as SOURCE DEFAULT_CONFIG, but
|
|
1148
|
+
* shows another value as stale example output, listed apart from prose
|
|
1149
|
+
* findings. Dry run by default, writing a JSON and a Markdown report; --write
|
|
1150
|
+
* applies only exact matches stated in the same sentence as their property,
|
|
1151
|
+
* never a mismatch, a table cell or example output. Skips prose inside code
|
|
1152
|
+
* blocks and generated content.
|
|
1153
|
+
*
|
|
1154
|
+
* @example
|
|
1155
|
+
* # Report on a docs checkout
|
|
1156
|
+
* npx doc-tools generate migrate-property-values --docs-dir . --report-dir ./propval-report
|
|
1157
|
+
*
|
|
1158
|
+
* # Report on cloud-docs, which borrows the self-managed docs' property data
|
|
1159
|
+
* npx doc-tools generate migrate-property-values --docs-dir ../cloud-docs --lender ../docs
|
|
1160
|
+
*
|
|
1161
|
+
* # Apply the auto-safe exact matches
|
|
1162
|
+
* npx doc-tools generate migrate-property-values --docs-dir . --write
|
|
1163
|
+
*/
|
|
1164
|
+
automation
|
|
1165
|
+
.command('migrate-property-values')
|
|
1166
|
+
.description('Report hand-written property values in prose and in example command output, check them against the property data, and propose propval: macros. Dry run unless --write is given; --write applies exact same-sentence prose matches only, never example output.')
|
|
1167
|
+
.option('--docs-dir <path>', 'Docs repo root containing antora.yml and modules/', '.')
|
|
1168
|
+
.option('--lender <path...>', 'Other component checkouts whose property data the repo borrows (cloud-docs borrows the docs repo\'s)')
|
|
1169
|
+
.option('--report-dir <path>', 'Where to write property-values-report.json and property-values-report.md', '.')
|
|
1170
|
+
.option('--write', 'Replace exact, same-sentence matches with propval: macros (mismatches are never rewritten)')
|
|
1171
|
+
.action((options) => {
|
|
1172
|
+
const { scanRepo, renderMarkdown } = require('../tools/migrate-property-values.js')
|
|
1173
|
+
const result = scanRepo({
|
|
1174
|
+
docsDir: path.resolve(options.docsDir),
|
|
1175
|
+
lenderDirs: (options.lender || []).map((dir) => path.resolve(dir)),
|
|
1176
|
+
write: Boolean(options.write),
|
|
1177
|
+
})
|
|
1178
|
+
if (result.error) {
|
|
1179
|
+
console.error(`Error: ${result.error}`)
|
|
1180
|
+
process.exit(1)
|
|
1181
|
+
}
|
|
1182
|
+
result.label = `${result.component}@${result.version || 'unversioned'}`
|
|
1183
|
+
const reportDir = path.resolve(options.reportDir)
|
|
1184
|
+
fs.mkdirSync(reportDir, { recursive: true })
|
|
1185
|
+
fs.writeFileSync(path.join(reportDir, 'property-values-report.json'), JSON.stringify(result, null, 2) + '\n')
|
|
1186
|
+
fs.writeFileSync(path.join(reportDir, 'property-values-report.md'), renderMarkdown([result]))
|
|
1187
|
+
const { counts } = result
|
|
1188
|
+
console.log(`Checked against ${result.dataset} (${result.surface}).`)
|
|
1189
|
+
console.log(`${result.findings.length} value statement(s) in ${result.files} files: ${counts.mismatch} mismatch, ${counts.unset} unset, ${counts.exact} exact (${counts.autoSafe} auto-safe), ${counts.equivalent} equivalent, ${counts.unverifiable} unverifiable.`)
|
|
1190
|
+
for (const f of result.findings.filter((x) => x.status === 'mismatch')) {
|
|
1191
|
+
console.log(` MISMATCH ${f.file}:${f.line} ${f.property} ${f.field}: written ${f.written}, data ${f.dataValue}`)
|
|
1192
|
+
}
|
|
1193
|
+
for (const f of result.findings.filter((x) => x.status === 'unset')) {
|
|
1194
|
+
console.log(` UNSET ${f.file}:${f.line} ${f.property} ${f.field}: ${f.note}`)
|
|
1195
|
+
}
|
|
1196
|
+
const examples = result.findings.filter((x) => x.status === 'example-output')
|
|
1197
|
+
console.log(`${examples.length} value(s) in example output: ${counts.exampleStale} stale, ${examples.filter((x) => x.verdict === 'matches').length} matching, ${examples.filter((x) => x.verdict === 'unverifiable').length} unverifiable (never written).`)
|
|
1198
|
+
for (const f of examples.filter((x) => x.verdict === 'stale')) {
|
|
1199
|
+
console.log(` EXAMPLE ${f.file}:${f.line} ${f.property}${f.clusterProperty ? ` (${f.clusterProperty})` : ''}: shown ${f.written}${f.source ? ` ${f.source}` : ''}, default ${f.dataRaw || f.dataValue}`)
|
|
1200
|
+
}
|
|
1201
|
+
if (options.write) console.log(`Replaced auto-safe exact matches in ${result.changedFiles} file(s).`)
|
|
1202
|
+
else if (counts.autoSafe) console.log('Re-run with --write to replace the auto-safe exact matches.')
|
|
1203
|
+
console.log(`Report: ${path.join(reportDir, 'property-values-report.md')}`)
|
|
1204
|
+
})
|
|
1205
|
+
|
|
1139
1206
|
/**
|
|
1140
1207
|
* generate property-docs
|
|
1141
1208
|
*
|
|
@@ -2132,6 +2199,11 @@ automation
|
|
|
2132
2199
|
* bullets — it is a CANDIDATE for the curation step that rewrites voice, adds
|
|
2133
2200
|
* `Area::` labels, normalizes units, and phrases CVEs.
|
|
2134
2201
|
*
|
|
2202
|
+
* In phase 1, a backticked name that matches a property in the release
|
|
2203
|
+
* series' redpanda-properties-<tag>.json becomes a `prop:<name>[link=true]` link. The
|
|
2204
|
+
* file is found in modules/reference/attachments/ unless --properties-file
|
|
2205
|
+
* names one. With no file, names stay monospace and a warning is logged.
|
|
2206
|
+
*
|
|
2135
2207
|
* @why
|
|
2136
2208
|
* Release notes are the one reference surface whose source of truth is
|
|
2137
2209
|
* engineer-authored prose rather than code, so the deterministic part
|
|
@@ -2164,6 +2236,7 @@ automation
|
|
|
2164
2236
|
.option('--section-file <file>', 'Phase 2: a curated section (from the sm-release-notes skill) to insert (relative to repo root, must stay inside the repository)')
|
|
2165
2237
|
.option('--date <date>', 'Authored release date as YYYY-MM-DD (required with --body; confirmed on the PR)')
|
|
2166
2238
|
.option('--page <file>', 'Target release-notes page (relative to repo root, must stay inside the repository)', 'modules/reference/pages/releases/redpanda.adoc')
|
|
2239
|
+
.option('--properties-file <file>', 'Phase 1: property JSON whose names become prop: links (relative to repo root, must stay inside the repository). Default: the newest redpanda-properties-<tag>.json in modules/reference/attachments/ for the tag\'s release series')
|
|
2167
2240
|
.option('--section-only', 'Phase 1 only: print the candidate section and do not read or modify the page')
|
|
2168
2241
|
.option('--dry-run', 'Print output to stdout instead of writing the page')
|
|
2169
2242
|
.action(async (options) => {
|
|
@@ -2172,6 +2245,11 @@ automation
|
|
|
2172
2245
|
buildReleaseSection,
|
|
2173
2246
|
isGaTag,
|
|
2174
2247
|
} = require('../tools/redpanda-release-notes/generate-release-notes.js')
|
|
2248
|
+
const {
|
|
2249
|
+
ATTACHMENTS_DIR,
|
|
2250
|
+
findPropertiesFile,
|
|
2251
|
+
loadPropertyNames,
|
|
2252
|
+
} = require('../tools/redpanda-release-notes/property-names.js')
|
|
2175
2253
|
try {
|
|
2176
2254
|
const repoRoot = findRepoRoot()
|
|
2177
2255
|
|
|
@@ -2192,6 +2270,19 @@ automation
|
|
|
2192
2270
|
return abs
|
|
2193
2271
|
}
|
|
2194
2272
|
|
|
2273
|
+
// Phase 1 links known property names. An explicit --properties-file must
|
|
2274
|
+
// exist; a missing default only means names stay monospace.
|
|
2275
|
+
const readPropertyNames = () => {
|
|
2276
|
+
const file = options.propertiesFile
|
|
2277
|
+
? readInside(options.propertiesFile, '--properties-file')
|
|
2278
|
+
: findPropertiesFile(path.join(repoRoot, ATTACHMENTS_DIR), options.tag)
|
|
2279
|
+
if (!file) {
|
|
2280
|
+
console.warn(`[release-notes] WARN: no property file for ${options.tag} in ${ATTACHMENTS_DIR}; property names stay monospace.`)
|
|
2281
|
+
return undefined
|
|
2282
|
+
}
|
|
2283
|
+
return loadPropertyNames(file)
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2195
2286
|
// Phase-1 candidate preview: emit the candidate for the curation step. No
|
|
2196
2287
|
// page is read or written — this is the LLM step's input, nothing else.
|
|
2197
2288
|
if (options.sectionOnly) {
|
|
@@ -2203,7 +2294,9 @@ automation
|
|
|
2203
2294
|
return
|
|
2204
2295
|
}
|
|
2205
2296
|
const body = fs.readFileSync(readInside(options.body, '--body'), 'utf8')
|
|
2206
|
-
const section = buildReleaseSection({
|
|
2297
|
+
const section = buildReleaseSection({
|
|
2298
|
+
body, version: options.tag, date: options.date, propertyNames: readPropertyNames(),
|
|
2299
|
+
})
|
|
2207
2300
|
process.stdout.write(section)
|
|
2208
2301
|
return
|
|
2209
2302
|
}
|
|
@@ -2213,6 +2306,7 @@ automation
|
|
|
2213
2306
|
const genArgs = { tag: options.tag, date: options.date, pageContent }
|
|
2214
2307
|
if (hasBody) {
|
|
2215
2308
|
genArgs.body = fs.readFileSync(readInside(options.body, '--body'), 'utf8')
|
|
2309
|
+
genArgs.propertyNames = readPropertyNames()
|
|
2216
2310
|
} else {
|
|
2217
2311
|
genArgs.section = fs.readFileSync(readInside(options.sectionFile, '--section-file'), 'utf8')
|
|
2218
2312
|
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/* Field values for one entry of a published redpanda-properties-<tag>.json,
|
|
4
|
+
* formatted the way the generated property reference renders them.
|
|
5
|
+
*
|
|
6
|
+
* Shared by the propval: inline macro (macros/prop.js) and the
|
|
7
|
+
* migrate-property-values report, so prose, the migration report, and the
|
|
8
|
+
* reference tables agree. The value strings come from the reference
|
|
9
|
+
* generator's own helpers wherever the tables show the field:
|
|
10
|
+
*
|
|
11
|
+
* default formatPropertyValue (the table's code value), and the
|
|
12
|
+
* extractor's default_human_readable where the data carries it
|
|
13
|
+
* (the table's parenthetical, such as "2 weeks").
|
|
14
|
+
* unit the table's Unit row: the units field, else formatUnits(name).
|
|
15
|
+
*
|
|
16
|
+
* Two humanizations go beyond what the table prints, and both are derived
|
|
17
|
+
* only from data the table also shows, never guessed:
|
|
18
|
+
*
|
|
19
|
+
* Durations without default_human_readable use humanizeDuration, a port of
|
|
20
|
+
* the extractor's format_time_human_readable (the function that produced
|
|
21
|
+
* default_human_readable), so the wording is identical.
|
|
22
|
+
* Byte values use IEC units (128 MiB) only when the table's Unit row says
|
|
23
|
+
* bytes and the value is an exact multiple. Anything else renders exactly.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const formatPropertyValue = require('../tools/property-extractor/helpers/formatPropertyValue')
|
|
27
|
+
const formatUnits = require('../tools/property-extractor/helpers/formatUnits')
|
|
28
|
+
const { escapeHtml } = require('./html-utils')
|
|
29
|
+
|
|
30
|
+
const FORMAT_HUMAN = 'human'
|
|
31
|
+
const FORMAT_RAW = 'raw'
|
|
32
|
+
|
|
33
|
+
// Every field propval: supports. `cloudHidden` marks the fields the reference
|
|
34
|
+
// pages wrap in ifndef::env-cloud[], so a Cloud page never states them either.
|
|
35
|
+
const FIELDS = {
|
|
36
|
+
default: { cloudHidden: true },
|
|
37
|
+
type: {},
|
|
38
|
+
unit: {},
|
|
39
|
+
min: {},
|
|
40
|
+
max: {},
|
|
41
|
+
restart: {},
|
|
42
|
+
nullable: {},
|
|
43
|
+
values: {},
|
|
44
|
+
scope: {},
|
|
45
|
+
introduced: { cloudHidden: true },
|
|
46
|
+
visibility: { cloudHidden: true },
|
|
47
|
+
restored: { cloudHidden: true },
|
|
48
|
+
aliases: { cloudHidden: true },
|
|
49
|
+
'cloud-access': {},
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// The JSON key names and table labels are accepted as aliases, so a writer
|
|
53
|
+
// who types what they see in the data or on the page gets the same field.
|
|
54
|
+
const FIELD_ALIASES = {
|
|
55
|
+
minimum: 'min',
|
|
56
|
+
maximum: 'max',
|
|
57
|
+
needs_restart: 'restart',
|
|
58
|
+
'requires-restart': 'restart',
|
|
59
|
+
units: 'unit',
|
|
60
|
+
enum: 'values',
|
|
61
|
+
'accepted-values': 'values',
|
|
62
|
+
config_scope: 'scope',
|
|
63
|
+
version: 'introduced',
|
|
64
|
+
gets_restored: 'restored',
|
|
65
|
+
cloud: 'cloud-access',
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const DURATION_UNITS = { milliseconds: 1, seconds: 1000 }
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Canonical field name, or undefined for a field propval: does not support.
|
|
72
|
+
*/
|
|
73
|
+
function normalizeField (field) {
|
|
74
|
+
const key = String(field == null ? '' : field).trim().toLowerCase()
|
|
75
|
+
if (Object.prototype.hasOwnProperty.call(FIELDS, key)) return key
|
|
76
|
+
if (Object.prototype.hasOwnProperty.call(FIELD_ALIASES, key)) return FIELD_ALIASES[key]
|
|
77
|
+
return undefined
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The unit the reference table prints for a property, lowercased for prose:
|
|
82
|
+
* the units field, else the one formatUnits derives from the name suffix.
|
|
83
|
+
* Empty when the table prints no Unit row.
|
|
84
|
+
*/
|
|
85
|
+
function unitOf (entry) {
|
|
86
|
+
if (entry && typeof entry.units === 'string' && entry.units.trim()) return entry.units.trim()
|
|
87
|
+
return formatUnits(entry && entry.name) || ''
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Port of property_extractor.py's format_time_human_readable: the largest
|
|
92
|
+
* unit that divides the value evenly, else the value in its own unit.
|
|
93
|
+
*
|
|
94
|
+
* @param {number} value
|
|
95
|
+
* @param {'milliseconds'|'seconds'} unit
|
|
96
|
+
*/
|
|
97
|
+
function humanizeDuration (value, unit) {
|
|
98
|
+
const ms = unit === 'seconds' ? value * 1000 : value
|
|
99
|
+
const units = [
|
|
100
|
+
[365 * 24 * 60 * 60 * 1000, 'year', 'years'],
|
|
101
|
+
[7 * 24 * 60 * 60 * 1000, 'week', 'weeks'],
|
|
102
|
+
[24 * 60 * 60 * 1000, 'day', 'days'],
|
|
103
|
+
[60 * 60 * 1000, 'hour', 'hours'],
|
|
104
|
+
[60 * 1000, 'minute', 'minutes'],
|
|
105
|
+
[1000, 'second', 'seconds'],
|
|
106
|
+
[1, 'millisecond', 'milliseconds'],
|
|
107
|
+
]
|
|
108
|
+
for (const [threshold, singular, plural] of units) {
|
|
109
|
+
if (ms >= threshold && ms % threshold === 0) {
|
|
110
|
+
const count = ms / threshold
|
|
111
|
+
return `${count} ${count === 1 ? singular : plural}`
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return unit === 'seconds' ? `${value} seconds` : `${value} milliseconds`
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* IEC rendering of a byte count, only for exact multiples: 134217728 is
|
|
119
|
+
* "128 MiB", 1000 stays "1000 bytes".
|
|
120
|
+
*/
|
|
121
|
+
function humanizeBytes (value) {
|
|
122
|
+
if (value === 0) return '0 bytes'
|
|
123
|
+
const steps = [
|
|
124
|
+
[1024 ** 4, 'TiB'],
|
|
125
|
+
[1024 ** 3, 'GiB'],
|
|
126
|
+
[1024 ** 2, 'MiB'],
|
|
127
|
+
[1024, 'KiB'],
|
|
128
|
+
]
|
|
129
|
+
for (const [size, label] of steps) {
|
|
130
|
+
if (value >= size && value % size === 0) return `${value / size} ${label}`
|
|
131
|
+
}
|
|
132
|
+
return `${value} ${value === 1 ? 'byte' : 'bytes'}`
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* A number in the property's own unit, humanized when the unit is known and
|
|
137
|
+
* the value is a non-negative integer. Negative values are sentinels (-1 for
|
|
138
|
+
* "unlimited") and render as written.
|
|
139
|
+
*/
|
|
140
|
+
function humanizeNumber (value, unit) {
|
|
141
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || value < 0) return String(value)
|
|
142
|
+
if (DURATION_UNITS[unit]) return humanizeDuration(value, unit)
|
|
143
|
+
if (unit === 'bytes') return humanizeBytes(value)
|
|
144
|
+
return String(value)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// An integer outside JavaScript's exact range (2^64 - 1 sentinels such as
|
|
148
|
+
// virtual_cluster_min_producer_ids) has already lost digits by the time
|
|
149
|
+
// JSON.parse returns it, so there is no exact value left to render.
|
|
150
|
+
const isImprecise = (value) => typeof value === 'number' && Number.isInteger(value) && !Number.isSafeInteger(value)
|
|
151
|
+
const imprecise = () => ({ ok: false, reason: 'imprecise' })
|
|
152
|
+
const plain = (text) => ({ ok: true, text: String(text) })
|
|
153
|
+
const missing = () => ({ ok: false, reason: 'missing' })
|
|
154
|
+
const yesNo = (value, format) => plain(format === FORMAT_RAW ? String(value) : (value ? 'yes' : 'no'))
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The value of one field of one property entry.
|
|
158
|
+
*
|
|
159
|
+
* @param {object} entry - One value of the JSON's properties object.
|
|
160
|
+
* @param {string} field - A field name or alias.
|
|
161
|
+
* @param {object} [opts]
|
|
162
|
+
* @param {'human'|'raw'} [opts.format] - human (default) or the exact value.
|
|
163
|
+
* @returns {{ok: true, text: string, html?: string} |
|
|
164
|
+
* {ok: false, reason: 'unsupported'|'missing'|'imprecise'}}
|
|
165
|
+
* html, when present, is pre-escaped markup to use instead of text.
|
|
166
|
+
*/
|
|
167
|
+
function fieldValue (entry, field, { format = FORMAT_HUMAN } = {}) {
|
|
168
|
+
const name = normalizeField(field)
|
|
169
|
+
if (!name) return { ok: false, reason: 'unsupported' }
|
|
170
|
+
const has = (key) => Object.prototype.hasOwnProperty.call(entry, key) && entry[key] !== undefined
|
|
171
|
+
const raw = format === FORMAT_RAW
|
|
172
|
+
|
|
173
|
+
switch (name) {
|
|
174
|
+
case 'default': {
|
|
175
|
+
if (!has('default')) return missing()
|
|
176
|
+
const value = entry.default
|
|
177
|
+
if (isImprecise(value)) return imprecise()
|
|
178
|
+
const exact = String(formatPropertyValue(value, entry.type))
|
|
179
|
+
if (raw) return plain(exact)
|
|
180
|
+
if (typeof entry.default_human_readable === 'string' && entry.default_human_readable.trim()) {
|
|
181
|
+
return plain(entry.default_human_readable.trim())
|
|
182
|
+
}
|
|
183
|
+
if (typeof value === 'number') return plain(humanizeNumber(value, unitOf(entry)))
|
|
184
|
+
return plain(exact)
|
|
185
|
+
}
|
|
186
|
+
case 'type':
|
|
187
|
+
return has('type') && entry.type !== '' ? plain(entry.type) : missing()
|
|
188
|
+
case 'unit': {
|
|
189
|
+
const unit = unitOf(entry)
|
|
190
|
+
return unit ? plain(unit) : missing()
|
|
191
|
+
}
|
|
192
|
+
case 'min':
|
|
193
|
+
case 'max': {
|
|
194
|
+
const key = name === 'min' ? 'minimum' : 'maximum'
|
|
195
|
+
if (!has(key) || entry[key] === null) return missing()
|
|
196
|
+
if (isImprecise(entry[key])) return imprecise()
|
|
197
|
+
return plain(raw ? String(entry[key]) : humanizeNumber(entry[key], unitOf(entry)))
|
|
198
|
+
}
|
|
199
|
+
case 'restart':
|
|
200
|
+
return has('needs_restart') && typeof entry.needs_restart === 'boolean' ? yesNo(entry.needs_restart, format) : missing()
|
|
201
|
+
case 'nullable':
|
|
202
|
+
return has('nullable') && typeof entry.nullable === 'boolean' ? yesNo(entry.nullable, format) : missing()
|
|
203
|
+
case 'restored':
|
|
204
|
+
return has('gets_restored') && typeof entry.gets_restored === 'boolean' ? yesNo(entry.gets_restored, format) : missing()
|
|
205
|
+
case 'values': {
|
|
206
|
+
const list = Array.isArray(entry.enum) && entry.enum.length
|
|
207
|
+
? entry.enum
|
|
208
|
+
: entry.items && Array.isArray(entry.items.enum) && entry.items.enum.length ? entry.items.enum : undefined
|
|
209
|
+
if (!list) return missing()
|
|
210
|
+
if (raw) return plain(JSON.stringify(list))
|
|
211
|
+
// Each value in its own code element, as the table prints them: some
|
|
212
|
+
// values contain commas (compact,delete), so a plain comma-joined list
|
|
213
|
+
// would be ambiguous.
|
|
214
|
+
return {
|
|
215
|
+
ok: true,
|
|
216
|
+
text: list.map(String).join(', '),
|
|
217
|
+
html: list.map((v) => `<code>${escapeHtml(String(v))}</code>`).join(', '),
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
case 'scope':
|
|
221
|
+
return has('config_scope') && entry.config_scope ? plain(entry.config_scope) : missing()
|
|
222
|
+
case 'introduced':
|
|
223
|
+
return has('version') && entry.version ? plain(entry.version) : missing()
|
|
224
|
+
case 'visibility':
|
|
225
|
+
return has('visibility') && entry.visibility ? plain(entry.visibility) : missing()
|
|
226
|
+
case 'aliases':
|
|
227
|
+
return Array.isArray(entry.aliases) && entry.aliases.length
|
|
228
|
+
? plain(raw ? JSON.stringify(entry.aliases) : entry.aliases.join(', '))
|
|
229
|
+
: missing()
|
|
230
|
+
case 'cloud-access': {
|
|
231
|
+
// cloud_* fields describe cluster configuration only (see isAvailable
|
|
232
|
+
// in macros/prop.js), so other scopes have no Cloud access value.
|
|
233
|
+
if (entry.config_scope !== 'cluster') return missing()
|
|
234
|
+
if (entry.cloud_readonly === true) return plain(raw ? 'readonly' : 'read-only')
|
|
235
|
+
if (entry.cloud_editable === true) return plain('editable')
|
|
236
|
+
if (entry.cloud_supported === false) return plain(raw ? 'unsupported' : 'not available')
|
|
237
|
+
return missing()
|
|
238
|
+
}
|
|
239
|
+
default:
|
|
240
|
+
return { ok: false, reason: 'unsupported' }
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
module.exports = {
|
|
245
|
+
FIELDS,
|
|
246
|
+
FIELD_ALIASES,
|
|
247
|
+
FORMAT_HUMAN,
|
|
248
|
+
FORMAT_RAW,
|
|
249
|
+
normalizeField,
|
|
250
|
+
fieldValue,
|
|
251
|
+
unitOf,
|
|
252
|
+
humanizeDuration,
|
|
253
|
+
humanizeBytes,
|
|
254
|
+
humanizeNumber,
|
|
255
|
+
}
|
|
@@ -35,6 +35,29 @@ Every release series has its own copy of that data, and the build picks the copy
|
|
|
35
35
|
|
|
36
36
|
*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.
|
|
37
37
|
|
|
38
|
+
=== Property values: the propval macro
|
|
39
|
+
|
|
40
|
+
`prop:` marks a property's *name*. Prose often states its *value* too: "defaults to 128 MiB", "the minimum is one minute". Typed by hand, those drift when a release changes a default, and nothing notices. A real example: the disk space page said `log_segment_size` defaults to 1 GiB long after the default became 128 MiB.
|
|
41
|
+
|
|
42
|
+
`propval:` renders the value from the same property data instead:
|
|
43
|
+
|
|
44
|
+
[,asciidoc]
|
|
45
|
+
----
|
|
46
|
+
Set prop:log_segment_ms[], which defaults to propval:log_segment_ms[default].
|
|
47
|
+
----
|
|
48
|
+
|
|
49
|
+
That renders "defaults to 2 weeks". Other fields are `type`, `unit`, `min`, `max`, `restart`, `nullable`, `values`, `scope`, `introduced`, `visibility`, `restored`, `aliases`, and `cloud-access`, and `format=raw` gives the exact value (`1209600000`). The full list is in link:REFERENCE.adoc#propval[the macros reference].
|
|
50
|
+
|
|
51
|
+
*It reads the same data as prop:, chosen the same way.* A 25.3 page gets 25.3's value, a beta page gets the newest release candidate's, and a Cloud page gets the newest GA value. The code is shared, not copied, so the two macros can't drift apart.
|
|
52
|
+
|
|
53
|
+
*It agrees with the reference tables.* Values are formatted by the same helpers that render the property reference pages, so "2 weeks" in prose and "2 weeks" in the Default cell come from one place. Byte counts read as `128 MiB` only where the reference says the unit is bytes; where the data gives no unit, the exact number renders rather than a guessed unit.
|
|
54
|
+
|
|
55
|
+
*It never guesses.* When the build can't verify a value (no data for the page's version, an unknown property, a field the property doesn't have, a property Cloud readers can't set), the page shows a visible `<unknown default>` and the build log says why. A wrong number is worse than an obvious gap.
|
|
56
|
+
|
|
57
|
+
*Cloud pages state fewer values.* The Cloud property reference shows no defaults, because Cloud configures them, so `propval:x[default]` on a Cloud page renders the placeholder with a warning. Keep default values in `ifndef::env-cloud[]` content, or use `cloud-access` to say whether a Cloud user can change the property.
|
|
58
|
+
|
|
59
|
+
To find the hand-written values already in a repo, run `doc-tools generate migrate-property-values`. It reports every value stated next to a property mention, compares it with the data for that repo's version, and flags mismatches, which are usually real doc errors. It also reports stale example output, such as an `rpk topic describe` row with source `DEFAULT_CONFIG` that shows an old default. `--write` replaces only exact matches, never a mismatch or example output.
|
|
60
|
+
|
|
38
61
|
=== How "the build works out the page" (dynamic discovery)
|
|
39
62
|
|
|
40
63
|
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.
|
|
@@ -106,7 +129,8 @@ Cloud is different from Kubernetes here, not just another context to adapt to. R
|
|
|
106
129
|
|
|
107
130
|
* 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.
|
|
108
131
|
* 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).
|
|
132
|
+
* The extensions preview site's `property-values-test` page shows every `propval:` field, both formats, and each placeholder case.
|
|
109
133
|
|
|
110
134
|
== The one-sentence version
|
|
111
135
|
|
|
112
|
-
Nothing in the docs claims a name is a property or a feature
|
|
136
|
+
Nothing in the docs claims a name is a property, states a property's value, or marks a feature enterprise unless a writer said so explicitly *and* the build verified it against the source of truth.
|
package/macros/README.adoc
CHANGED
|
@@ -58,7 +58,9 @@ IMPORTANT: Macros must be registered under the `asciidoc.extensions` key in your
|
|
|
58
58
|
=== Inline macros
|
|
59
59
|
|
|
60
60
|
* **glossterm** - Reference glossary terms with tooltips
|
|
61
|
-
* **
|
|
61
|
+
* **prop** - Reference a configuration property, with a validated tooltip and optional link
|
|
62
|
+
* **propval** - Render a property's default, minimum, unit, or other value from the property data
|
|
63
|
+
* **config_ref** - Link to configuration properties (deprecated; use prop)
|
|
62
64
|
* **helm_ref** - Link to Helm chart values
|
|
63
65
|
* **badge** - Display status badges
|
|
64
66
|
|
package/macros/REFERENCE.adoc
CHANGED
|
@@ -223,7 +223,7 @@ Registered together with the `enterprise` inline macro.
|
|
|
223
223
|
|
|
224
224
|
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.
|
|
225
225
|
|
|
226
|
-
The macro checks that a property is actually available to the audience reading the page, and links it only where a page documents it. It validates against the published property data: a `redpanda-properties-<tag>.json` attachment in the `reference` module, the same data the tooltips fetch at runtime. See <<prop-availability>> for which properties each doc set may reference and <<prop-version-selection>> for which file a given page uses. To migrate existing plain-backtick property mentions, use `doc-tools generate migrate-property-refs`.
|
|
226
|
+
The macro checks that a property is actually available to the audience reading the page, and links it only where a page documents it. It validates against the published property data: a `redpanda-properties-<tag>.json` attachment in the `reference` module, the same data the tooltips fetch at runtime. See <<prop-availability>> for which properties each doc set may reference and <<prop-version-selection>> for which file a given page uses. To migrate existing plain-backtick property mentions, use `doc-tools generate migrate-property-refs`. To render a property's value (its default, minimum, unit, and so on) rather than its name, use <<propval>>.
|
|
227
227
|
|
|
228
228
|
=== Usage
|
|
229
229
|
|
|
@@ -331,6 +331,163 @@ asciidoc:
|
|
|
331
331
|
- '@redpanda-data/docs-extensions-and-macros/macros/prop'
|
|
332
332
|
----
|
|
333
333
|
|
|
334
|
+
== propval
|
|
335
|
+
|
|
336
|
+
This inline macro renders one value from the property data as plain text, so a sentence such as "defaults to 2 weeks" is generated from the same data as the property reference instead of typed by hand. Hand-typed values drift when a release changes a default, and nothing catches it.
|
|
337
|
+
|
|
338
|
+
[,asciidoc]
|
|
339
|
+
----
|
|
340
|
+
Set prop:log_segment_ms[], which defaults to propval:log_segment_ms[default].
|
|
341
|
+
The minimum is propval:log_segment_ms[min].
|
|
342
|
+
Changing prop:cloud_storage_enabled[] requires a restart: propval:cloud_storage_enabled[restart].
|
|
343
|
+
The exact default is `propval:log_segment_ms[default,format=raw]`.
|
|
344
|
+
----
|
|
345
|
+
|
|
346
|
+
Rendered against `redpanda-properties-v26.2.4.json`, those lines read: "defaults to 2 weeks", "The minimum is 1 minute", "requires a restart: yes", and "The exact default is `1209600000`".
|
|
347
|
+
|
|
348
|
+
`propval:` is registered by the `prop` macro's extension, so a playbook that registers `macros/prop` has both. It shares every data decision with `prop:` rather than copying them: the same dataset for the page's version (<<prop-version-selection>>), the same `property-validate` modes, the same Cloud availability gate (<<prop-availability>>), and the same refusal to use a neighbouring release's data when a version has none.
|
|
349
|
+
|
|
350
|
+
=== Usage
|
|
351
|
+
|
|
352
|
+
The target is the property name. The first positional attribute (or `field=`) names the field, and defaults to `default`.
|
|
353
|
+
|
|
354
|
+
[cols="1,2,2"]
|
|
355
|
+
|===
|
|
356
|
+
| Field | Source in the property JSON | Example (v26.2.4)
|
|
357
|
+
|
|
358
|
+
| `default`
|
|
359
|
+
| `default`, formatted as the reference table formats it. The human-readable form is the table's parenthetical (`default_human_readable`) when the data has one.
|
|
360
|
+
| `log_segment_ms`: `2 weeks`; `fetch_max_bytes`: `55 MiB`; `cloud_storage_enabled`: `false`; `node_id`: `null`
|
|
361
|
+
|
|
362
|
+
| `type`
|
|
363
|
+
| `type`
|
|
364
|
+
| `integer`
|
|
365
|
+
|
|
366
|
+
| `unit`
|
|
367
|
+
| `units`, else the unit the table derives from the name suffix (`_ms`, `_bytes`, and so on)
|
|
368
|
+
| `milliseconds`
|
|
369
|
+
|
|
370
|
+
| `min`, `max`
|
|
371
|
+
| `minimum`, `maximum`, humanized with the unit
|
|
372
|
+
| `log_segment_ms` min: `1 minute`
|
|
373
|
+
|
|
374
|
+
| `restart`
|
|
375
|
+
| `needs_restart`
|
|
376
|
+
| `yes` or `no`
|
|
377
|
+
|
|
378
|
+
| `nullable`
|
|
379
|
+
| `nullable`
|
|
380
|
+
| `yes` or `no`
|
|
381
|
+
|
|
382
|
+
| `values`
|
|
383
|
+
| `enum` (or `items.enum` for arrays), each value as inline code, because some values contain commas
|
|
384
|
+
| `none`, `delete`, `compact`, `compact,delete`
|
|
385
|
+
|
|
386
|
+
| `scope`
|
|
387
|
+
| `config_scope`
|
|
388
|
+
| `cluster`, `broker`, or `topic`
|
|
389
|
+
|
|
390
|
+
| `introduced`
|
|
391
|
+
| `version`
|
|
392
|
+
| `v26.1.1`
|
|
393
|
+
|
|
394
|
+
| `visibility`
|
|
395
|
+
| `visibility`
|
|
396
|
+
| `user`, `tunable`
|
|
397
|
+
|
|
398
|
+
| `restored`
|
|
399
|
+
| `gets_restored` (restored on Whole Cluster Restore)
|
|
400
|
+
| `yes` or `no`
|
|
401
|
+
|
|
402
|
+
| `aliases`
|
|
403
|
+
| `aliases`
|
|
404
|
+
| comma-separated names
|
|
405
|
+
|
|
406
|
+
| `cloud-access`
|
|
407
|
+
| `cloud_editable` and `cloud_readonly`, for cluster properties only
|
|
408
|
+
| `editable`, `read-only`, or `not available`
|
|
409
|
+
|===
|
|
410
|
+
|
|
411
|
+
The JSON key names and table labels also work as field names: `minimum`, `maximum`, `needs_restart`, `units`, `enum`, `config_scope`, `version`, `gets_restored`.
|
|
412
|
+
|
|
413
|
+
format::
|
|
414
|
+
`human` (default) or `raw`. `raw` gives the exact value: the code value in the reference table for `default`, the plain number for `min` and `max`, `true` or `false` for the yes-or-no fields, and a JSON array for `values` and `aliases`.
|
|
415
|
+
|
|
416
|
+
The output is a `<span>` with the stable class `property-value` and `data-property` and `data-field` attributes. It is plain text with no tooltip; mark the name with `prop:` for that. The macro works inside backticks, so `` `propval:x[default,format=raw]` `` renders as inline code. Don't put `values` inside backticks: it already renders each value as inline code, and backticks would nest one code element inside another.
|
|
417
|
+
|
|
418
|
+
=== Formatting
|
|
419
|
+
|
|
420
|
+
Values come from the reference generator's own helpers, so prose and the reference tables cannot disagree:
|
|
421
|
+
|
|
422
|
+
* `default` uses `formatPropertyValue`, the helper that renders the table's Default cell. In the human form, a duration uses the table's parenthetical when the data has one.
|
|
423
|
+
* A duration with no parenthetical in the data is humanized by a port of the extractor's `format_time_human_readable`, the function that produced the parenthetical: the largest unit that divides the value evenly, else the value in its own unit (`1500 milliseconds`).
|
|
424
|
+
* A byte count renders in IEC units (`128 MiB`) only when the table's Unit row says bytes and the value is an exact multiple. Otherwise the exact number renders. `log_segment_size` and `segment.bytes` have no unit in the data or the table, so they render `134217728`. Adding `units` for them belongs in the property extractor, not in prose.
|
|
425
|
+
* Negative numbers are sentinels (`-1` for unlimited) and render as written.
|
|
426
|
+
* `null`, an empty string, and booleans render as the table renders them: `null`, `null`, `true`, `false`.
|
|
427
|
+
* A topic property that the data leaves unset, such as `retention.bytes`, renders `null` for `default`, as the table does. The topic inherits its cluster property (`corresponding_cluster_property` in the data), so in prose, point to that property with `prop:`, such as "inherits prop:retention_bytes[]", rather than saying the default is `null`.
|
|
428
|
+
|
|
429
|
+
=== Failures
|
|
430
|
+
|
|
431
|
+
A value has no safe fallback, so whenever the build cannot verify one, `propval:` renders a visible placeholder such as `<unknown default>` (class `property-value property-value-unknown`) and logs why, naming the property, the field, and the dataset. It never renders a guessed value. The cases:
|
|
432
|
+
|
|
433
|
+
* The page's version has no property data, or the component has none (the same diagnostic `prop:` logs, plus one for the `propval:` call).
|
|
434
|
+
* The property is not in the page's dataset.
|
|
435
|
+
* The property is not available on a Cloud page (`cloud_supported: false` on a cluster property).
|
|
436
|
+
* The field is not one `propval:` supports.
|
|
437
|
+
* The property has no such field in the data, for example `max` on a property with no maximum, or `default` on a deprecated property the data gives no default.
|
|
438
|
+
* The value is an integer beyond JavaScript's exact range (the 2^64^-1 sentinel of `virtual_cluster_min_producer_ids`), which `JSON.parse` has already rounded.
|
|
439
|
+
* On a Cloud page, the field is one the Cloud property reference does not show: `default`, `introduced`, `visibility`, `restored`, and `aliases`. The reference renders those only outside `env-cloud` (Cloud shows "Available in the Redpanda Cloud Console" in the Default row), so a Cloud page cannot state them either. Put the sentence in an `ifndef::env-cloud[]` block, or use a field the Cloud reference shows, such as `cloud-access`.
|
|
440
|
+
|
|
441
|
+
With `property-validate` set to `error`, an unknown property, an unsupported field, a field the property does not have, a property not available on Cloud, or a field the Cloud reference does not show fails the build. The last two appear only in the Cloud build, so cloud-docs needs `property-validate: error` to catch single-sourced prose that would render a placeholder. With `off`, the placeholder is still rendered and still reported, because `off` means "do not check names", and a silent `<unknown default>` would reach readers unnoticed.
|
|
442
|
+
|
|
443
|
+
=== Migrating hand-written values
|
|
444
|
+
|
|
445
|
+
`doc-tools generate migrate-property-values` finds hand-written values next to a property mention ("defaults to X", "the default is X", "default: X", "minimum X", "is X by default") and checks each against the data for the repo's own version, selected exactly as `prop:` selects it. It writes a JSON and a Markdown report. Each finding is one of:
|
|
446
|
+
|
|
447
|
+
exact:: The written text is what `propval:` renders. Auto-safe when the statement is in the same sentence as its property, no other property is named in that paragraph line or its list entry, the data value is not null, and the file is not release or upgrade notes. On a self-managed page, a `default` (or another field the Cloud reference hides), or any field of a property Cloud does not support, is auto-safe only inside `ifndef::env-cloud[]` or in a page (not a partial) with no `tag::` regions. Otherwise the page may be single-sourced into cloud-docs, where `propval:` renders a placeholder, so the finding is proposed only: wrap the sentence in `ifndef::env-cloud[]` first.
|
|
448
|
+
equivalent:: Same value, different wording (`7 days` and `1 week`). Proposed, never applied.
|
|
449
|
+
mismatch:: The written value differs from the data. Likely a doc error. Never applied.
|
|
450
|
+
unset:: The data default is null (not set), but the prose states a value. Redpanda often computes a fallback at runtime for an unset property, such as 10% of free disk for `compaction_ctrl_backlog_size`, so confirm the value in the source. Never applied.
|
|
451
|
+
unverifiable:: Not comparable: unparseable, a field the property does not have, a sentence qualified to another audience or release ("In Redpanda Cloud", "in earlier versions", "If the default is"), content inside `ifdef::env-cloud[]` on a self-managed page, a Cloud default, or "no default" for a topic property, which inherits its cluster property.
|
|
452
|
+
example-output:: A value shown in command output inside a listing, literal, or fenced block. These findings are counted and listed apart from prose findings, and never applied. Each has a verdict: `stale`, `matches`, or `unverifiable` with a reason.
|
|
453
|
+
|
|
454
|
+
Tables are scanned too. A statement in a cell belongs to the property that opens the row or cell, and a `Default` column states the default of the row's property. Table findings are reported but never applied.
|
|
455
|
+
|
|
456
|
+
`--write` applies only auto-safe exact matches. The tool skips prose inside code blocks, headings, attribute entries, comment lines, `ifndef::env-cloud[]` content on a Cloud page, and generated content. For cloud-docs, pass the docs checkout with `--lender` so Cloud can borrow its GA data.
|
|
457
|
+
|
|
458
|
+
[,bash]
|
|
459
|
+
----
|
|
460
|
+
npx doc-tools generate migrate-property-values --docs-dir . --report-dir ./propval-report
|
|
461
|
+
npx doc-tools generate migrate-property-values --docs-dir ../cloud-docs --lender . --report-dir ./propval-report-cloud
|
|
462
|
+
----
|
|
463
|
+
|
|
464
|
+
==== Example output
|
|
465
|
+
|
|
466
|
+
The tool reads these output shapes: `rpk topic describe` config tables (`KEY VALUE SOURCE`), `name=value (SOURCE)` lines, `kafka-configs --describe` lines (the first synonym's source), Key/Value/Source records in JSON or YAML (debug bundles, Topic resource status), `rpk cluster config get` output in the same block or an Input/Output pair, `rpk cluster config export` YAML, and Admin API `/v1/cluster_config` JSON. `rpk cluster config status` output states no values. A topic property is compared with the cluster property it inherits, taken from the data's `corresponding_cluster_property`, or `alternate_cluster_property` when the same output shows `cleanup.policy` set to compact. A Kafka broker alias such as `log.segment.bytes` is not in the data, so the Redpanda-named synonym row is compared. Values are compared exactly as raw bytes, milliseconds, booleans, or enum text. `-1` matches a limit that the data leaves unset.
|
|
467
|
+
|
|
468
|
+
A row is compared only when it asserts the default. A row with source `DEFAULT_CONFIG` or `DEFAULT` asserts it. Output with no source column (`config get`, `export`, Admin API) asserts it only when the text just before the block mentions the default. The following are `unverifiable`, with the first matching reason:
|
|
469
|
+
|
|
470
|
+
* `not-default-source`: Another source, such as `DYNAMIC_TOPIC_CONFIG` or `STATIC_BROKER_CONFIG`.
|
|
471
|
+
* `ifdef::env-cloud` on a self-managed page, or `cloud-default` on a Cloud page.
|
|
472
|
+
* `set-earlier-on-page`: The page sets the cluster property before the block, as `name=value`, `name: value` (including Helm `config.cluster.name`), or `rpk cluster config set name value`. A topic-level override doesn't count, because it would change the row's source.
|
|
473
|
+
* `helm-operator-context`: A Kubernetes page (`kubernetes/` path or `k-` file name), or a command run through `kubectl` or `helm`. The chart or operator may set the value.
|
|
474
|
+
* `dev-or-bootstrap-config`: The page starts Redpanda in `dev-container` mode, with `rpk container start`, or from a bootstrap file.
|
|
475
|
+
* `current-value`: Output with no source column, and no mention of the default before it.
|
|
476
|
+
* `unset-in-data` or `not-comparable`.
|
|
477
|
+
|
|
478
|
+
The report lists stale examples in their own table. It lists unverifiable rows whose value differs from the default in a context that may set them (other than `not-default-source`) for a human to check.
|
|
479
|
+
|
|
480
|
+
=== Registration
|
|
481
|
+
|
|
482
|
+
Registered by the `prop` macro's extension:
|
|
483
|
+
|
|
484
|
+
[,yaml]
|
|
485
|
+
----
|
|
486
|
+
asciidoc:
|
|
487
|
+
extensions:
|
|
488
|
+
- '@redpanda-data/docs-extensions-and-macros/macros/prop'
|
|
489
|
+
----
|
|
490
|
+
|
|
334
491
|
== glossterm
|
|
335
492
|
|
|
336
493
|
The `glossterm` inline macro provides a way to define and reference glossary terms in your AsciiDoc documents.
|