@redpanda-data/docs-extensions-and-macros 5.11.0 → 5.12.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 +11 -42
- package/cli-utils/enterprise-sources.js +244 -0
- package/cli-utils/self-managed-docs-branch.js +32 -24
- package/cli-utils/setup-mcp.js +4 -3
- package/docs-data/rpk-overrides.schema.json +744 -0
- package/extensions/set-available-attachment-versions.js +9 -1
- package/macros/DOCUMENTING_UNRELEASED_FEATURES.adoc +188 -0
- package/macros/PROPERTY_AND_ENTERPRISE_REFERENCES.adoc +20 -2
- package/macros/REFERENCE.adoc +92 -3
- package/macros/badge.js +9 -1
- package/macros/enterprise.js +180 -19
- package/macros/prop.js +636 -183
- package/mcp/USER_GUIDE.adoc +1 -1
- package/package.json +6 -4
- package/tools/enterprise-features/parsers.js +0 -14
- package/tools/enterprise-features/verify.js +28 -14
- package/tools/property-extractor/cloud_config.py +16 -3
- package/tools/redpanda-connect/report-delta.js +49 -3
- package/tools/rpk-docs/extract-conditional-content.js +3 -1
package/macros/enterprise.js
CHANGED
|
@@ -58,13 +58,110 @@ const chalk = require('chalk')
|
|
|
58
58
|
const { buildBadgeHtml } = require('./badge')
|
|
59
59
|
|
|
60
60
|
const $enterpriseRegistry = Symbol('$enterpriseRegistry')
|
|
61
|
+
const $warned = Symbol('$warned')
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Report a message once per build. Keyed off the content catalog, which Antora
|
|
65
|
+
* rebuilds per build, so watch mode keeps reporting instead of going quiet.
|
|
66
|
+
*/
|
|
67
|
+
function warnOnce (contentCatalog, key, message) {
|
|
68
|
+
const seen = contentCatalog[$warned] || (contentCatalog[$warned] = new Set())
|
|
69
|
+
if (seen.has(key)) return false
|
|
70
|
+
seen.add(key)
|
|
71
|
+
console.warn(chalk.yellow(message))
|
|
72
|
+
return true
|
|
73
|
+
}
|
|
61
74
|
|
|
62
75
|
const DEFAULT_LICENSING_PAGE = 'get-started:licensing/overview.adoc'
|
|
63
76
|
const DEFAULT_ROLE = 'enterprise-feature'
|
|
64
77
|
const BETA_LABEL = 'beta'
|
|
78
|
+
const UNRELEASED_LABEL = 'unreleased'
|
|
79
|
+
const UNRELEASED_TOOLTIP =
|
|
80
|
+
'This feature is not in a released version of Redpanda yet. It is documented here for the upcoming release.'
|
|
81
|
+
|
|
82
|
+
// Release status of a feature, which decides where it may be referenced.
|
|
83
|
+
//
|
|
84
|
+
// ga Shipped. Referenced anywhere. The default.
|
|
85
|
+
// beta Publicly available as a beta. Referenced anywhere, badged, so
|
|
86
|
+
// readers know the feature is not yet stable.
|
|
87
|
+
// unreleased In a release candidate only, not yet public. Referenced ONLY
|
|
88
|
+
// from prerelease docs (a beta branch). A mention on a released
|
|
89
|
+
// page describes something readers cannot get, so it is reported
|
|
90
|
+
// and rendered as plain text.
|
|
91
|
+
const STATUS_GA = 'ga'
|
|
92
|
+
const STATUS_BETA = 'beta'
|
|
93
|
+
const STATUS_UNRELEASED = 'unreleased'
|
|
94
|
+
const VALID_STATUSES = [STATUS_GA, STATUS_BETA, STATUS_UNRELEASED]
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The release status of a registry entry.
|
|
98
|
+
*
|
|
99
|
+
* `beta: true` predates the status field and still means beta, so old entries
|
|
100
|
+
* keep working. An explicit status wins. An unrecognized status is reported
|
|
101
|
+
* rather than quietly treated as shipped, which is the failure mode that lets a
|
|
102
|
+
* typo publish an unreleased feature.
|
|
103
|
+
*
|
|
104
|
+
* @param {object} entry - Registry entry.
|
|
105
|
+
* @param {object} [report] - {mode, filePath} to report an invalid status.
|
|
106
|
+
* @returns {string} One of VALID_STATUSES.
|
|
107
|
+
*/
|
|
108
|
+
function entryStatus (entry, report) {
|
|
109
|
+
if (!entry) return STATUS_GA
|
|
110
|
+
if (entry.status !== undefined) {
|
|
111
|
+
const status = String(entry.status).trim().toLowerCase()
|
|
112
|
+
if (VALID_STATUSES.includes(status)) return status
|
|
113
|
+
if (report && report.mode !== 'off') {
|
|
114
|
+
const where = report.filePath ? ` in ${report.filePath}` : ''
|
|
115
|
+
const message =
|
|
116
|
+
`enterprise:${entry.name}[]${where}: registry status '${entry.status}' is not one of ${VALID_STATUSES.join(', ')}, so the feature is treated as released. ` +
|
|
117
|
+
'Fix the status in enterprise-features.yml.'
|
|
118
|
+
// Honour error mode like every other registry diagnostic. A typo here is
|
|
119
|
+
// exactly what publishes an unreleased feature, so the strictest setting
|
|
120
|
+
// has to stop it.
|
|
121
|
+
if (report.mode === 'error') throw new Error(message)
|
|
122
|
+
// One registry typo needs one fix, so report it once per build rather
|
|
123
|
+
// than once per mention of the feature.
|
|
124
|
+
if (report.contentCatalog) warnOnce(report.contentCatalog, `status:${entry.name}`, message)
|
|
125
|
+
else console.warn(chalk.yellow(message))
|
|
126
|
+
}
|
|
127
|
+
return STATUS_GA
|
|
128
|
+
}
|
|
129
|
+
return entry.beta === true ? STATUS_BETA : STATUS_GA
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Whether the page being converted belongs to a prerelease component version,
|
|
134
|
+
* which is where an unreleased feature may be referenced.
|
|
135
|
+
*
|
|
136
|
+
* Read from the component version's own prerelease flag (antora.yml) rather
|
|
137
|
+
* than inferred, with a document attribute as an escape hatch for playbooks
|
|
138
|
+
* that surface it themselves.
|
|
139
|
+
*/
|
|
140
|
+
function isPrereleasePage (config, document) {
|
|
141
|
+
const attribute = document && document.getAttribute('page-component-version-is-prerelease')
|
|
142
|
+
if (attribute !== undefined) {
|
|
143
|
+
const value = String(attribute).trim().toLowerCase()
|
|
144
|
+
if (value === 'true' || value === '') return true
|
|
145
|
+
if (value === 'false') return false
|
|
146
|
+
// Anything else is a typo. Falling through to the catalog is safer than
|
|
147
|
+
// guessing, and silently treating it as "released" produced a warning that
|
|
148
|
+
// told the writer their prerelease page belonged to a released version.
|
|
149
|
+
console.warn(chalk.yellow(
|
|
150
|
+
`enterprise macro: page-component-version-is-prerelease is '${attribute}', which is neither true nor false, so it is ignored and the component version's own prerelease flag is used instead.`
|
|
151
|
+
))
|
|
152
|
+
}
|
|
153
|
+
const contentCatalog = config && config.contentCatalog
|
|
154
|
+
const src = config && config.file && config.file.src
|
|
155
|
+
if (!contentCatalog || !src || typeof contentCatalog.getComponent !== 'function') return false
|
|
156
|
+
const component = contentCatalog.getComponent(src.component)
|
|
157
|
+
if (!component) return false
|
|
158
|
+
const versions = Array.isArray(component.versions) ? component.versions : []
|
|
159
|
+
const componentVersion = versions.find((entry) => entry.version === src.version)
|
|
160
|
+
return Boolean(componentVersion && componentVersion.prerelease)
|
|
161
|
+
}
|
|
65
162
|
|
|
66
163
|
/**
|
|
67
|
-
* Render the
|
|
164
|
+
* Render the release-status badge for a registry entry.
|
|
68
165
|
*
|
|
69
166
|
* The badge HTML is built directly rather than emitted as `badge:[...]`
|
|
70
167
|
* AsciiDoc, so it renders whether or not the consuming playbook registers the
|
|
@@ -73,14 +170,31 @@ const BETA_LABEL = 'beta'
|
|
|
73
170
|
* `badge::[label=beta]` in any build without that macro registered.
|
|
74
171
|
*
|
|
75
172
|
* @param {object} entry - Registry entry.
|
|
76
|
-
* @returns {string} Badge HTML, or an empty string
|
|
173
|
+
* @returns {string} Badge HTML, or an empty string for a shipped feature.
|
|
77
174
|
*/
|
|
78
175
|
function buildBetaBadge (entry) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
label: BETA_LABEL,
|
|
82
|
-
|
|
83
|
-
|
|
176
|
+
const status = entryStatus(entry)
|
|
177
|
+
if (status === STATUS_BETA) {
|
|
178
|
+
return buildBadgeHtml({ label: BETA_LABEL, tooltip: entry['beta-tooltip'] || undefined })
|
|
179
|
+
}
|
|
180
|
+
if (status === STATUS_UNRELEASED) {
|
|
181
|
+
return buildBadgeHtml({ label: UNRELEASED_LABEL, tooltip: entry['status-tooltip'] || UNRELEASED_TOOLTIP })
|
|
182
|
+
}
|
|
183
|
+
return ''
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Report an unreleased feature referenced from released documentation.
|
|
188
|
+
*/
|
|
189
|
+
function reportUnreleasedFeature ({ feature, mode, filePath }) {
|
|
190
|
+
if (mode === 'off') return
|
|
191
|
+
const where = filePath ? ` in ${filePath}` : ''
|
|
192
|
+
const message =
|
|
193
|
+
`enterprise:${feature}[]${where}: '${feature}' is marked status: unreleased in the enterprise features registry, so it is only documented for an upcoming release. ` +
|
|
194
|
+
'This page belongs to a released version, so the mention renders as plain text with no enterprise styling, tooltip, or link. ' +
|
|
195
|
+
'Move the mention to the prerelease (beta) branch, or change the status once the feature ships.'
|
|
196
|
+
if (mode === 'error') throw new Error(message)
|
|
197
|
+
console.warn(chalk.yellow(message))
|
|
84
198
|
}
|
|
85
199
|
|
|
86
200
|
/**
|
|
@@ -164,17 +278,33 @@ function loadRegistry (config) {
|
|
|
164
278
|
const partials = contentCatalog.findBy({ component: 'shared', module: 'ROOT', family: 'partial' }) || []
|
|
165
279
|
const registryFile = partials.find((file) => file.path && file.path.endsWith(REGISTRY_FILENAME))
|
|
166
280
|
if (registryFile) {
|
|
167
|
-
|
|
281
|
+
try {
|
|
282
|
+
registry = parseRegistry(registryFile.contents.toString(), registryFile.path)
|
|
283
|
+
} catch (error) {
|
|
284
|
+
// A malformed registry must not take the whole build down. Unvalidated
|
|
285
|
+
// enterprise mentions are the same graceful degradation as no registry at
|
|
286
|
+
// all, and this matches how prop.js treats a corrupt properties JSON.
|
|
287
|
+
// Caching the failure also stops the error being re-raised per macro call
|
|
288
|
+
// and attributed to whichever page happened to convert first.
|
|
289
|
+
registry = null
|
|
290
|
+
warnOnce(contentCatalog, 'badregistry',
|
|
291
|
+
`Enterprise features registry ${registryFile.path} could not be read (${error.message}); enterprise: targets are not validated.`)
|
|
292
|
+
}
|
|
168
293
|
}
|
|
169
294
|
// Cache null too, so a missing registry is only searched for once per build.
|
|
170
295
|
contentCatalog[$enterpriseRegistry] = registry
|
|
171
296
|
return registry || undefined
|
|
172
297
|
}
|
|
173
298
|
|
|
174
|
-
function warnNoRegistry () {
|
|
299
|
+
function warnNoRegistry (contentCatalog) {
|
|
300
|
+
const message = "Enterprise features registry (enterprise-features.yml in the 'shared' component) not found; enterprise: targets are not validated."
|
|
301
|
+
// Deduplicate per build, not per process: Antora's watch mode reuses one
|
|
302
|
+
// process across builds, and a module-level guard reported this only on a
|
|
303
|
+
// session's first build -- exactly when a writer is iterating.
|
|
304
|
+
if (contentCatalog) return warnOnce(contentCatalog, 'noregistry', message)
|
|
175
305
|
if (warnedNoRegistry) return
|
|
176
306
|
warnedNoRegistry = true
|
|
177
|
-
console.warn(chalk.yellow(
|
|
307
|
+
console.warn(chalk.yellow(message))
|
|
178
308
|
}
|
|
179
309
|
|
|
180
310
|
/**
|
|
@@ -268,8 +398,17 @@ function buildEnterpriseContent ({ feature, text, xref, url, tooltip, licensingP
|
|
|
268
398
|
* @returns {string}
|
|
269
399
|
*/
|
|
270
400
|
function buildFeatureTable (features, scope, opts = {}) {
|
|
401
|
+
// On released docs an unreleased feature is omitted entirely: the licensing
|
|
402
|
+
// table is a list of what a licence covers today, and listing something
|
|
403
|
+
// unavailable makes it wrong. Prerelease docs list it with its badge.
|
|
404
|
+
const includeUnreleased = opts.includeUnreleased === true
|
|
405
|
+
// Report a bad status from here too. Without a reporter a typo'd status was
|
|
406
|
+
// silently downgraded to released, so an unreleased feature that no page
|
|
407
|
+
// happens to reference inline was published in the table with no diagnostic.
|
|
408
|
+
const report = opts.report
|
|
271
409
|
const rows = features
|
|
272
410
|
.filter((feature) => feature.scope === scope)
|
|
411
|
+
.filter((feature) => includeUnreleased || entryStatus(feature, report) !== STATUS_UNRELEASED)
|
|
273
412
|
.sort((a, b) => a.name.localeCompare(b.name, 'en', { sensitivity: 'base' }))
|
|
274
413
|
const title = opts.title || TABLE_TITLES[scope]
|
|
275
414
|
const heading = opts.heading || THIRD_COLUMN_HEADINGS[scope] || THIRD_COLUMN_HEADINGS.default
|
|
@@ -302,7 +441,7 @@ function enterpriseInlineMacro (config) {
|
|
|
302
441
|
let registry
|
|
303
442
|
if (config && config.contentCatalog) {
|
|
304
443
|
registry = loadRegistry(config)
|
|
305
|
-
if (!registry) warnNoRegistry()
|
|
444
|
+
if (!registry) warnNoRegistry(config && config.contentCatalog)
|
|
306
445
|
}
|
|
307
446
|
let feature = target
|
|
308
447
|
let entry
|
|
@@ -319,6 +458,16 @@ function enterpriseInlineMacro (config) {
|
|
|
319
458
|
})
|
|
320
459
|
}
|
|
321
460
|
}
|
|
461
|
+
const mode = document.getAttribute('enterprise-validate', 'warn')
|
|
462
|
+
const filePath = config && config.file && config.file.src && config.file.src.path
|
|
463
|
+
const status = entryStatus(entry, { mode, filePath, contentCatalog: config && config.contentCatalog })
|
|
464
|
+
if (status === STATUS_UNRELEASED && !isPrereleasePage(config, document)) {
|
|
465
|
+
// Documented for an upcoming release, referenced from released docs.
|
|
466
|
+
// Styling it as an available enterprise feature would promise readers
|
|
467
|
+
// something they cannot get, so render the name and say so.
|
|
468
|
+
reportUnreleasedFeature({ feature, mode, filePath })
|
|
469
|
+
return self.createInline(parent, 'quoted', attributes.text || feature)
|
|
470
|
+
}
|
|
322
471
|
let content = buildEnterpriseContent({
|
|
323
472
|
feature,
|
|
324
473
|
text: attributes.text,
|
|
@@ -330,12 +479,13 @@ function enterpriseInlineMacro (config) {
|
|
|
330
479
|
tooltipAttr: resolveTooltipAttribute(document.getAttribute('enterprise-tooltip')),
|
|
331
480
|
links: document.getAttribute('enterprise-links', 'true') === 'true',
|
|
332
481
|
})
|
|
333
|
-
// A feature
|
|
334
|
-
//
|
|
335
|
-
//
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
482
|
+
// A feature's status holds wherever it is referenced, so prose gets the
|
|
483
|
+
// same badge as the generated tables. enterprise-beta-badge=false
|
|
484
|
+
// suppresses the beta badge in prose; the unreleased badge is not
|
|
485
|
+
// suppressible, because "not in a release yet" is not decoration.
|
|
486
|
+
if (status === STATUS_UNRELEASED || document.getAttribute('enterprise-beta-badge', 'true') === 'true') {
|
|
487
|
+
const badge = buildBetaBadge(entry)
|
|
488
|
+
if (badge) content += ` ${badge}`
|
|
339
489
|
}
|
|
340
490
|
// The xref inside the span is resolved by the 'macros' substitution,
|
|
341
491
|
// the same mechanism the config_ref macro relies on.
|
|
@@ -355,10 +505,21 @@ function enterpriseFeaturesBlockMacro (config) {
|
|
|
355
505
|
}
|
|
356
506
|
const registry = config && config.contentCatalog ? loadRegistry(config) : undefined
|
|
357
507
|
if (!registry) {
|
|
358
|
-
warnNoRegistry()
|
|
508
|
+
warnNoRegistry(config && config.contentCatalog)
|
|
359
509
|
return self.parseContent(parent, `WARNING: The enterprise features registry is unavailable, so the ${scope} feature table cannot be rendered.`)
|
|
360
510
|
}
|
|
361
|
-
const table = buildFeatureTable(registry.features, scope, {
|
|
511
|
+
const table = buildFeatureTable(registry.features, scope, {
|
|
512
|
+
title: attributes.title,
|
|
513
|
+
heading: attributes.heading,
|
|
514
|
+
report: {
|
|
515
|
+
mode: parent.getDocument().getAttribute('enterprise-validate', 'warn'),
|
|
516
|
+
filePath: config && config.file && config.file.src && config.file.src.path,
|
|
517
|
+
contentCatalog: config && config.contentCatalog,
|
|
518
|
+
},
|
|
519
|
+
// Prerelease docs describe the upcoming release, so they list features
|
|
520
|
+
// that are still only in a release candidate.
|
|
521
|
+
includeUnreleased: isPrereleasePage(config, parent.getDocument()),
|
|
522
|
+
})
|
|
362
523
|
// A block anchor before the macro call ([[my-id]]) is consumed into the
|
|
363
524
|
// macro's id attribute. Re-emit it so crossrefs to the table keep working.
|
|
364
525
|
const source = attributes.id ? `[[${attributes.id}]]\n${table}` : table
|