@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/macros/USER_GUIDE.adoc
CHANGED
|
@@ -25,6 +25,7 @@ asciidoc:
|
|
|
25
25
|
extensions:
|
|
26
26
|
- '@redpanda-data/docs-extensions-and-macros/macros/glossary'
|
|
27
27
|
- '@redpanda-data/docs-extensions-and-macros/macros/config-ref'
|
|
28
|
+
- '@redpanda-data/docs-extensions-and-macros/macros/prop'
|
|
28
29
|
- '@redpanda-data/docs-extensions-and-macros/macros/helm-ref'
|
|
29
30
|
----
|
|
30
31
|
|
|
@@ -63,6 +64,33 @@ config_ref:cleanup.policy,false[]
|
|
|
63
64
|
* `isLink` - Whether to create a link (true/false, required)
|
|
64
65
|
* `path` - Document path for link (required if isLink is true)
|
|
65
66
|
|
|
67
|
+
=== prop and propval - Property names and values
|
|
68
|
+
|
|
69
|
+
Mark a configuration property's name, and render its values from the property data:
|
|
70
|
+
|
|
71
|
+
[,asciidoc]
|
|
72
|
+
----
|
|
73
|
+
Set prop:log_segment_ms[], which defaults to propval:log_segment_ms[default].
|
|
74
|
+
The minimum is propval:log_segment_ms[min].
|
|
75
|
+
----
|
|
76
|
+
|
|
77
|
+
With Redpanda 26.2 data, that renders "defaults to 2 weeks" and "The minimum is 1 minute".
|
|
78
|
+
|
|
79
|
+
*Syntax:* `prop:name[]` and `propval:name[field,format=raw]`
|
|
80
|
+
|
|
81
|
+
* `name` - Property name (required)
|
|
82
|
+
* `field` - `default` (when omitted), `type`, `unit`, `min`, `max`, `restart`, `nullable`, `values`, `scope`, `introduced`, `visibility`, `restored`, `aliases`, or `cloud-access`
|
|
83
|
+
* `format=raw` - The exact value (`1209600000`) instead of the human-readable one (`2 weeks`)
|
|
84
|
+
|
|
85
|
+
Register `@redpanda-data/docs-extensions-and-macros/macros/prop`, which provides both macros. If a value can't be verified for the page's version, the page shows `<unknown default>` and the build log says why. See link:REFERENCE.adoc#propval[the macros reference] for every field and failure case.
|
|
86
|
+
|
|
87
|
+
To find hand-written values in a repo and check them against the data:
|
|
88
|
+
|
|
89
|
+
[,bash]
|
|
90
|
+
----
|
|
91
|
+
npx doc-tools generate migrate-property-values --docs-dir . --report-dir ./propval-report
|
|
92
|
+
----
|
|
93
|
+
|
|
66
94
|
=== helm_ref - Helm value references
|
|
67
95
|
|
|
68
96
|
Link to Helm chart values:
|
package/macros/prop.js
CHANGED
|
@@ -83,6 +83,24 @@
|
|
|
83
83
|
* property-ref-role CSS class applied to the code element
|
|
84
84
|
* (default: property-ref).
|
|
85
85
|
*
|
|
86
|
+
* PROPERTY VALUES: propval:
|
|
87
|
+
*
|
|
88
|
+
* The same registration also provides propval:<name>[<field>], which renders
|
|
89
|
+
* one value from the property data as plain text, so prose such as "defaults
|
|
90
|
+
* to 128 MiB" comes from the data instead of being typed by hand:
|
|
91
|
+
*
|
|
92
|
+
* propval:log_segment_ms[default] -> 2 weeks
|
|
93
|
+
* propval:log_segment_ms[default,format=raw] -> 1209600000
|
|
94
|
+
* propval:log_segment_ms[min] -> 1 minute
|
|
95
|
+
* propval:cloud_storage_enabled[restart] -> yes
|
|
96
|
+
*
|
|
97
|
+
* It uses exactly prop:'s dataset selection, validation modes, Cloud gating
|
|
98
|
+
* and missing-data refusal (loadProperties, isAvailable, warnSeriesMissing),
|
|
99
|
+
* and formats values with the reference generator's own helpers (see
|
|
100
|
+
* extension-utils/property-values.js). Whenever no verified value exists, it
|
|
101
|
+
* renders a visible placeholder such as <unknown default> and reports why,
|
|
102
|
+
* because unlike a property name, a value has no safe fallback.
|
|
103
|
+
*
|
|
86
104
|
* Example use in a playbook:
|
|
87
105
|
*
|
|
88
106
|
* asciidoc:
|
|
@@ -92,6 +110,7 @@
|
|
|
92
110
|
|
|
93
111
|
const semver = require('semver')
|
|
94
112
|
const { escapeHtml } = require('../extension-utils/html-utils')
|
|
113
|
+
const propertyValues = require('../extension-utils/property-values')
|
|
95
114
|
const logger = require('@antora/logger')('prop-macro')
|
|
96
115
|
|
|
97
116
|
const $propertyRegistry = Symbol('$propertyRegistry')
|
|
@@ -968,6 +987,116 @@ function propInlineMacro (config) {
|
|
|
968
987
|
}
|
|
969
988
|
}
|
|
970
989
|
|
|
990
|
+
const VALUE_ROLE = 'property-value'
|
|
991
|
+
const UNKNOWN_VALUE_ROLE = 'property-value-unknown'
|
|
992
|
+
|
|
993
|
+
/**
|
|
994
|
+
* Build the HTML emitted for one propval: instance. Exported for unit testing.
|
|
995
|
+
*
|
|
996
|
+
* @param {object} opts
|
|
997
|
+
* @param {string} opts.name - Property name.
|
|
998
|
+
* @param {string} opts.field - Canonical (or as-written, for a placeholder) field.
|
|
999
|
+
* @param {string} [opts.text] - The value, as plain text.
|
|
1000
|
+
* @param {string} [opts.html] - Pre-escaped markup, used instead of text.
|
|
1001
|
+
* @param {boolean} [opts.unknown] - Render the placeholder instead.
|
|
1002
|
+
* @returns {string}
|
|
1003
|
+
*/
|
|
1004
|
+
function buildPropvalContent ({ name, field, text, html, unknown = false }) {
|
|
1005
|
+
const attrs = `data-property="${escapeHtml(name)}" data-field="${escapeHtml(field)}"`
|
|
1006
|
+
if (unknown) {
|
|
1007
|
+
return `<span class="${VALUE_ROLE} ${UNKNOWN_VALUE_ROLE}" ${attrs}>${escapeHtml(`<unknown ${field}>`)}</span>`
|
|
1008
|
+
}
|
|
1009
|
+
return `<span class="${VALUE_ROLE}" ${attrs}>${html !== undefined ? html : escapeHtml(text)}</span>`
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
/**
|
|
1013
|
+
* Resolve one propval: call to its value, or to the reason there is none.
|
|
1014
|
+
* Shares every decision with prop: -- the dataset (loadProperties), the
|
|
1015
|
+
* availability gate (isAvailable) -- so a value is shown exactly where prop:
|
|
1016
|
+
* would mark the name, and nowhere else. Exported for unit testing and for
|
|
1017
|
+
* the migrate-property-values report.
|
|
1018
|
+
*
|
|
1019
|
+
* @param {string} [opts.filePath] - Named in the message, when known.
|
|
1020
|
+
* @returns {{ok: true, text: string, html?: string, registry: object, field: string} |
|
|
1021
|
+
* {ok: false, kind: string, message: string, registry?: object, field: string}}
|
|
1022
|
+
*/
|
|
1023
|
+
function resolvePropertyValue ({ registry, name, field: requested, format, component, filePath }) {
|
|
1024
|
+
const field = propertyValues.normalizeField(requested)
|
|
1025
|
+
const call = `propval:${name}[${requested}]${filePath ? ` in ${filePath}` : ''}`
|
|
1026
|
+
if (!field) {
|
|
1027
|
+
const supported = Object.keys(propertyValues.FIELDS).join(', ')
|
|
1028
|
+
return { ok: false, kind: 'unsupported-field', field: requested, message: `${call}: '${requested}' is not a field propval: supports. Supported fields: ${supported}.` }
|
|
1029
|
+
}
|
|
1030
|
+
if (!registry) {
|
|
1031
|
+
return { ok: false, kind: 'no-data', field, message: `${call}: this page has no property data to read '${field}' from (see the prop macro warning for this component version), so it renders <unknown ${field}> rather than a value from another release.` }
|
|
1032
|
+
}
|
|
1033
|
+
const entry = Object.prototype.hasOwnProperty.call(registry.properties, name) ? registry.properties[name] : undefined
|
|
1034
|
+
if (!entry || typeof entry !== 'object') {
|
|
1035
|
+
return { ok: false, kind: 'unknown-property', field, registry, message: `${call}: '${name}' is not in the property data this page validates against (${describeDataset(registry)}). Either the name is misspelled or the property does not exist in that release.` }
|
|
1036
|
+
}
|
|
1037
|
+
if (!isAvailable(registry, entry)) {
|
|
1038
|
+
return { ok: false, kind: 'unavailable', field, registry, message: `${call}: '${name}' is not available in the ${component || 'this'} component: ${describeDataset(registry)} marks it cloud_supported: false, so no value is shown to readers who cannot set it.` }
|
|
1039
|
+
}
|
|
1040
|
+
if (registry.surface === SURFACE_CLOUD && propertyValues.FIELDS[field].cloudHidden) {
|
|
1041
|
+
return { ok: false, kind: 'cloud-hidden', field, registry, message: `${call}: the property reference does not state '${field}' for Cloud (Cloud configures it, and the reference renders it only outside env-cloud), so this Cloud page renders <unknown ${field}>. Put the sentence in an ifndef::env-cloud[] block, or use a field the Cloud reference shows, such as cloud-access.` }
|
|
1042
|
+
}
|
|
1043
|
+
const value = propertyValues.fieldValue(entry, field, { format })
|
|
1044
|
+
if (!value.ok && value.reason === 'imprecise') {
|
|
1045
|
+
return { ok: false, kind: 'imprecise', field, registry, message: `${call}: the ${field} of '${name}' in ${describeDataset(registry)} is an integer larger than JavaScript represents exactly, so the build cannot render it without changing digits and renders <unknown ${field}>. State the value in prose by hand.` }
|
|
1046
|
+
}
|
|
1047
|
+
if (!value.ok) {
|
|
1048
|
+
return { ok: false, kind: 'missing-field', field, registry, message: `${call}: '${name}' has no ${field} in ${describeDataset(registry)}, so it renders <unknown ${field}>.` }
|
|
1049
|
+
}
|
|
1050
|
+
return { ok: true, field, registry, text: value.text, html: value.html }
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
function propvalInlineMacro (config) {
|
|
1054
|
+
return function () {
|
|
1055
|
+
const self = this
|
|
1056
|
+
self.named('propval')
|
|
1057
|
+
self.positionalAttributes(['field'])
|
|
1058
|
+
self.process(function (parent, target, attributes) {
|
|
1059
|
+
const document = parent.getDocument()
|
|
1060
|
+
const name = target.trim()
|
|
1061
|
+
const mode = document.getAttribute('property-validate', 'warn')
|
|
1062
|
+
const filePath = config && config.file && config.file.src && config.file.src.path
|
|
1063
|
+
const component = (config && config.file && config.file.src && config.file.src.component) || ''
|
|
1064
|
+
const version = (config && config.file && config.file.src && config.file.src.version) || ''
|
|
1065
|
+
const requested = String(attributes.field || 'default').trim()
|
|
1066
|
+
let format = String(attributes.format || propertyValues.FORMAT_HUMAN).trim().toLowerCase()
|
|
1067
|
+
const where = filePath ? ` in ${filePath}` : ''
|
|
1068
|
+
if (format !== propertyValues.FORMAT_HUMAN && format !== propertyValues.FORMAT_RAW) {
|
|
1069
|
+
logger.warn(`propval:${name}[${requested}]${where}: format=${format} is not supported; use format=raw for the exact value. Rendering the human-readable value.`)
|
|
1070
|
+
format = propertyValues.FORMAT_HUMAN
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
const contentCatalog = config && config.contentCatalog
|
|
1074
|
+
const registry = contentCatalog ? loadProperties(config) : undefined
|
|
1075
|
+
if (!registry && contentCatalog && mode !== 'off') {
|
|
1076
|
+
// The same once-per-component-version diagnostic prop: emits.
|
|
1077
|
+
if (!warnSeriesMissing(contentCatalog, component, version)) warnNoPropertyData(contentCatalog, component, version)
|
|
1078
|
+
}
|
|
1079
|
+
const result = resolvePropertyValue({ registry, name, field: requested, format, component, filePath })
|
|
1080
|
+
if (result.ok) {
|
|
1081
|
+
return self.createInline(parent, 'quoted', buildPropvalContent({ name, field: result.field, text: result.text, html: result.html }))
|
|
1082
|
+
}
|
|
1083
|
+
// Authoring errors fail the build in error mode, like an unknown prop:
|
|
1084
|
+
// name. So do cloud-hidden and unavailable: they appear only in the
|
|
1085
|
+
// Cloud build of single-sourced prose, where a warning alone would let
|
|
1086
|
+
// the placeholder ship. Everything else is reported and rendered as the
|
|
1087
|
+
// placeholder. A placeholder is reported even in off mode: off means
|
|
1088
|
+
// "do not check names", but a value cannot fall back to anything, and a
|
|
1089
|
+
// silent <unknown default> would reach readers unnoticed.
|
|
1090
|
+
const { message } = result
|
|
1091
|
+
if (mode === 'error' && ['unknown-property', 'missing-field', 'unsupported-field', 'cloud-hidden', 'unavailable'].includes(result.kind)) {
|
|
1092
|
+
throw new Error(message)
|
|
1093
|
+
}
|
|
1094
|
+
logger.warn(message)
|
|
1095
|
+
return self.createInline(parent, 'quoted', buildPropvalContent({ name, field: result.field, unknown: true }))
|
|
1096
|
+
})
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
|
|
971
1100
|
/**
|
|
972
1101
|
* helm-path=auto displays the property as its Helm values path on pages
|
|
973
1102
|
* rendered with env-kubernetes, so single-sourced content reads correctly for
|
|
@@ -986,9 +1115,11 @@ function register (registry, config = {}) {
|
|
|
986
1115
|
if (typeof registry.register === 'function') {
|
|
987
1116
|
registry.register(function () {
|
|
988
1117
|
this.inlineMacro(propInlineMacro(config))
|
|
1118
|
+
this.inlineMacro(propvalInlineMacro(config))
|
|
989
1119
|
})
|
|
990
1120
|
} else if (typeof registry.inlineMacro === 'function') {
|
|
991
1121
|
registry.inlineMacro(propInlineMacro(config))
|
|
1122
|
+
registry.inlineMacro(propvalInlineMacro(config))
|
|
992
1123
|
} else {
|
|
993
1124
|
logger.warn("no 'inlineMacro' method on alleged registry")
|
|
994
1125
|
}
|
|
@@ -1008,3 +1139,7 @@ module.exports.releaseSeries = releaseSeries
|
|
|
1008
1139
|
module.exports.extractHeadingsWithTags = extractHeadingsWithTags
|
|
1009
1140
|
module.exports.evaluateTagExpression = evaluateTagExpression
|
|
1010
1141
|
module.exports.buildPageIndex = buildPageIndex
|
|
1142
|
+
module.exports.isAvailable = isAvailable
|
|
1143
|
+
module.exports.describeDataset = describeDataset
|
|
1144
|
+
module.exports.buildPropvalContent = buildPropvalContent
|
|
1145
|
+
module.exports.resolvePropertyValue = resolvePropertyValue
|
package/package.json
CHANGED
|
@@ -1531,6 +1531,23 @@ const MARKUP_PATTERNS = [
|
|
|
1531
1531
|
// link needs an explicit module to resolve in both places.
|
|
1532
1532
|
const XREF_MODULES = new Set(['components', 'configuration', 'cookbooks', 'get-started', 'guides', 'install', 'reference'])
|
|
1533
1533
|
|
|
1534
|
+
// Component reference pages live in the components module, one directory
|
|
1535
|
+
// per registration kind (inputs/, outputs/, ...). Derived from the kinds
|
|
1536
|
+
// above so a new component kind needs no second list.
|
|
1537
|
+
const COMPONENT_PAGE_DIRS = new Set(Object.values(REGISTRATION_TYPES)
|
|
1538
|
+
.filter((t) => !t.startsWith('bloblang-'))
|
|
1539
|
+
.map((t) => `${t}s`))
|
|
1540
|
+
|
|
1541
|
+
/** The fix a module-less xref needs. Component pages and Bloblang pages have
|
|
1542
|
+
* a module the lint can know (the review prompt says the same); any other
|
|
1543
|
+
* page's module is in its docs URL. */
|
|
1544
|
+
function xrefModuleHint (target) {
|
|
1545
|
+
const dir = target.split('/')[0]
|
|
1546
|
+
if (COMPONENT_PAGE_DIRS.has(dir)) return `Name the module: xref:components:${target}[...].`
|
|
1547
|
+
if (dir === 'bloblang') return `Name the module: xref:guides:${target}[...].`
|
|
1548
|
+
return 'Name the module the page lives in. It is the path segment after /connect/ in the page\'s docs URL, so https://docs.redpanda.com/connect/configuration/interpolation/ is xref:configuration:interpolation.adoc[...].'
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1534
1551
|
const AVAILABILITY_PROSE = /\b(?:not (?:available|supported) (?:in|on) (?:Redpanda )?Cloud|only available (?:in|on|for) (?:self-managed|Redpanda Cloud|Cloud)|self-managed only|requires? (?:a )?cgo|CGO_ENABLED|x_benthos_extra)\b/i
|
|
1535
1552
|
|
|
1536
1553
|
/** Surface-specific convention rules. */
|
|
@@ -1575,7 +1592,7 @@ const RULES = [
|
|
|
1575
1592
|
if (parts.length === 2 && XREF_MODULES.has(parts[0])) continue
|
|
1576
1593
|
issues.push({
|
|
1577
1594
|
message: parts.length === 1
|
|
1578
|
-
? `"xref:${m[1]}[" has no module. It resolves on the self-managed page, but Cloud includes the same partial into its own module, where the link breaks.
|
|
1595
|
+
? `"xref:${m[1]}[" has no module. It resolves on the self-managed page, but Cloud includes the same partial into its own module, where the link breaks. ${xrefModuleHint(m[1])}`
|
|
1579
1596
|
: `"xref:${m[1]}[" names the ${parts[0]} module, which the Redpanda Connect docs do not have, so the link breaks on the rendered page. Use one of ${[...XREF_MODULES].join(', ')}, a component:module:page xref, or a full https:// link.`
|
|
1580
1597
|
})
|
|
1581
1598
|
}
|
|
@@ -150,6 +150,19 @@ function convertDocument (content, convertible, options = {}) {
|
|
|
150
150
|
return { content: converted.join('\n'), count: total, skippedConfigRefs }
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
// Paths no migration touches: generated content, whose hand edits revert on
|
|
154
|
+
// regeneration, and non-page assets. Shared with migrate-property-values.
|
|
155
|
+
const DEFAULT_EXCLUDE = [
|
|
156
|
+
'modules/reference/partials/properties/',
|
|
157
|
+
'modules/reference/pages/rpk/',
|
|
158
|
+
'modules/reference/partials/rpk',
|
|
159
|
+
'modules/reference/pages/k-crd.adoc',
|
|
160
|
+
'-helm-spec.adoc',
|
|
161
|
+
'/attachments/',
|
|
162
|
+
'/examples/',
|
|
163
|
+
'/test-resources/',
|
|
164
|
+
]
|
|
165
|
+
|
|
153
166
|
/**
|
|
154
167
|
* Walk a modules tree and migrate every .adoc file.
|
|
155
168
|
*
|
|
@@ -160,17 +173,7 @@ function convertDocument (content, convertible, options = {}) {
|
|
|
160
173
|
* @param {string[]} [opts.exclude] - Path substrings to skip.
|
|
161
174
|
* @returns {{files: number, conversions: number, changed: {file: string, count: number}[], ambiguous: string[]}}
|
|
162
175
|
*/
|
|
163
|
-
function migrate ({ docsDir, propertiesJson, write, configRefs = false, exclude =
|
|
164
|
-
'modules/reference/partials/properties/',
|
|
165
|
-
// Autogenerated; hand edits revert on regeneration.
|
|
166
|
-
'modules/reference/pages/rpk/',
|
|
167
|
-
'modules/reference/partials/rpk',
|
|
168
|
-
'modules/reference/pages/k-crd.adoc',
|
|
169
|
-
'-helm-spec.adoc',
|
|
170
|
-
'/attachments/',
|
|
171
|
-
'/examples/',
|
|
172
|
-
'/test-resources/',
|
|
173
|
-
] }) {
|
|
176
|
+
function migrate ({ docsDir, propertiesJson, write, configRefs = false, exclude = DEFAULT_EXCLUDE }) {
|
|
174
177
|
const { convertible, ambiguous } = classifyNames(propertiesJson)
|
|
175
178
|
const knownNames = new Set(Object.keys(propertiesJson.properties || {}))
|
|
176
179
|
const changed = []
|
|
@@ -203,4 +206,4 @@ function migrate ({ docsDir, propertiesJson, write, configRefs = false, exclude
|
|
|
203
206
|
return { files, conversions, changed, ambiguous, skippedConfigRefs: [...skippedConfigRefs] }
|
|
204
207
|
}
|
|
205
208
|
|
|
206
|
-
module.exports = { classifyNames, convertLine, convertConfigRefLine, convertDocument, migrate }
|
|
209
|
+
module.exports = { classifyNames, convertLine, convertConfigRefLine, convertDocument, migrate, DEFAULT_EXCLUDE }
|