@redpanda-data/docs-extensions-and-macros 5.11.1 → 5.12.1
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/docs-data/rpk-overrides.schema.json +744 -0
- package/extensions/set-available-attachment-versions.js +32 -10
- package/macros/DOCUMENTING_UNRELEASED_FEATURES.adoc +188 -0
- package/macros/PROPERTY_AND_ENTERPRISE_REFERENCES.adoc +23 -3
- package/macros/REFERENCE.adoc +98 -9
- package/macros/badge.js +33 -3
- package/macros/enterprise.js +232 -22
- package/macros/prop.js +687 -187
- package/package.json +5 -4
- package/tools/property-extractor/cloud_config.py +16 -3
- package/tools/property-extractor/generate-handlebars-docs.js +100 -0
- package/tools/property-extractor/helpers/anchorName.js +14 -1
- package/tools/property-extractor/helpers/canonicalizePropertyXrefs.js +54 -0
- package/tools/property-extractor/helpers/index.js +2 -0
- package/tools/property-extractor/helpers/normalizePropertyAnchors.js +86 -0
- package/tools/property-extractor/templates/topic-property-mappings.hbs +13 -4
- package/tools/property-extractor/templates/topic-property.hbs +11 -1
- package/tools/redpanda-connect/report-delta.js +49 -3
- package/tools/rpk-docs/extract-conditional-content.js +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redpanda-data/docs-extensions-and-macros",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.12.1",
|
|
4
4
|
"description": "Antora extensions and macros developed for Redpanda documentation.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"antora",
|
|
@@ -102,7 +102,8 @@
|
|
|
102
102
|
"cli-utils",
|
|
103
103
|
"tools",
|
|
104
104
|
"docker-compose",
|
|
105
|
-
"mcp"
|
|
105
|
+
"mcp",
|
|
106
|
+
"docs-data/rpk-overrides.schema.json"
|
|
106
107
|
],
|
|
107
108
|
"license": "ISC",
|
|
108
109
|
"engines": {
|
|
@@ -149,8 +150,8 @@
|
|
|
149
150
|
"yargs": "^17.7.2"
|
|
150
151
|
},
|
|
151
152
|
"devDependencies": {
|
|
152
|
-
"@antora/cli": "3.1.
|
|
153
|
-
"@antora/site-generator": "3.1.
|
|
153
|
+
"@antora/cli": "^3.1.15",
|
|
154
|
+
"@antora/site-generator": "^3.1.15",
|
|
154
155
|
"@asciidoctor/core": "2.2.8",
|
|
155
156
|
"@web/dev-server": "^0.2.5",
|
|
156
157
|
"jest": "^29.7.0",
|
|
@@ -500,10 +500,23 @@ def add_cloud_support_metadata(properties: Dict, cloud_config: CloudConfig) -> D
|
|
|
500
500
|
skipped_count += 1
|
|
501
501
|
continue
|
|
502
502
|
|
|
503
|
-
# Only
|
|
503
|
+
# Only cluster properties get cloud metadata.
|
|
504
|
+
#
|
|
505
|
+
# The two sections this data comes from -- customer_managed_configs
|
|
506
|
+
# and readonly_cluster_config -- describe the Cloud control plane's
|
|
507
|
+
# view of CLUSTER configuration, and contain nothing else: every one
|
|
508
|
+
# of their entries resolves to a cluster property, and no topic
|
|
509
|
+
# property appears anywhere in the install pack.
|
|
510
|
+
#
|
|
511
|
+
# Emitting cloud_supported: false for a topic or broker property
|
|
512
|
+
# therefore asserted something this source cannot know. Downstream
|
|
513
|
+
# that read as "a Cloud reader cannot set this", which is wrong for
|
|
514
|
+
# topic properties -- Cloud users set them routinely through the
|
|
515
|
+
# Kafka API and Console, a surface the control plane does not gate.
|
|
516
|
+
# Leave the fields absent instead, so absent means "no opinion" and
|
|
517
|
+
# false keeps its meaning of "the control plane forbids it".
|
|
504
518
|
config_scope = prop_data.get('config_scope', '')
|
|
505
|
-
if config_scope
|
|
506
|
-
# Skip node config properties and others without cloud relevance
|
|
519
|
+
if config_scope != 'cluster':
|
|
507
520
|
skipped_count += 1
|
|
508
521
|
continue
|
|
509
522
|
|
|
@@ -104,6 +104,103 @@ function registerPartials() {
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Rewrite property anchors across every description and related-topics entry so
|
|
109
|
+
* they match the heading IDs Asciidoctor will actually emit.
|
|
110
|
+
*
|
|
111
|
+
* Runs once over the whole property map before rendering, because a property's
|
|
112
|
+
* description routinely links to a sibling property and the anchor shapes differ
|
|
113
|
+
* by where the text came from. Reports what it corrected: a steady stream of
|
|
114
|
+
* rewrites is a signal to fix the upstream doc string, not to lean on this pass.
|
|
115
|
+
*
|
|
116
|
+
* @param {Object<string, Object>} properties - Property map, mutated in place.
|
|
117
|
+
* @returns {number} Number of anchors rewritten.
|
|
118
|
+
*/
|
|
119
|
+
function normalizeAnchorsInProperties(properties) {
|
|
120
|
+
const seen = new Map();
|
|
121
|
+
let total = 0;
|
|
122
|
+
|
|
123
|
+
let xrefTotal = 0;
|
|
124
|
+
const fix = (value, scope) => {
|
|
125
|
+
const { text, rewrites } = helpers.normalizePropertyAnchors(value, properties, scope);
|
|
126
|
+
for (const r of rewrites) {
|
|
127
|
+
total += 1;
|
|
128
|
+
seen.set(r.from, r.to);
|
|
129
|
+
}
|
|
130
|
+
const canonical = helpers.canonicalizePropertyXrefs(text);
|
|
131
|
+
xrefTotal += canonical.rewrites;
|
|
132
|
+
return canonical.text;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
Object.values(properties).forEach((prop) => {
|
|
136
|
+
if (!prop || typeof prop !== 'object') return;
|
|
137
|
+
const scope = prop.config_scope;
|
|
138
|
+
if (typeof prop.description === 'string') prop.description = fix(prop.description, scope);
|
|
139
|
+
if (typeof prop.acceptable_values === 'string') prop.acceptable_values = fix(prop.acceptable_values, scope);
|
|
140
|
+
if (Array.isArray(prop.related_topics)) {
|
|
141
|
+
// Canonicalizing collapses forms that differed only by prefix, which turns
|
|
142
|
+
// some entries into exact duplicates of each other. Drop the repeats.
|
|
143
|
+
const seenTopics = new Set();
|
|
144
|
+
prop.related_topics = prop.related_topics
|
|
145
|
+
.map((t) => (typeof t === 'string' ? fix(t, scope) : t))
|
|
146
|
+
.filter((t) => {
|
|
147
|
+
if (typeof t !== 'string') return true;
|
|
148
|
+
const key = t.trim();
|
|
149
|
+
if (seenTopics.has(key)) return false;
|
|
150
|
+
seenTopics.add(key);
|
|
151
|
+
return true;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
if (total > 0) {
|
|
157
|
+
console.log(`Fixed ${total} property anchor(s) that would have rendered as dead links:`);
|
|
158
|
+
for (const [from, to] of seen) console.log(` <<${from}>> -> <<${to}>>`);
|
|
159
|
+
}
|
|
160
|
+
if (xrefTotal > 0) {
|
|
161
|
+
console.log(`Canonicalized ${xrefTotal} property-page xref(s) to reference:properties/…`);
|
|
162
|
+
}
|
|
163
|
+
return total;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Record whether a topic property's corresponding cluster property is one a Cloud
|
|
168
|
+
* customer can actually reach.
|
|
169
|
+
*
|
|
170
|
+
* Cloud publishes a subset of cluster properties, so linking a topic property to
|
|
171
|
+
* its cluster counterpart unconditionally sends Cloud readers to an anchor that
|
|
172
|
+
* does not exist on their reference page. `cloud_supported` on the cluster
|
|
173
|
+
* property is the authority here: it is derived from the install pack's
|
|
174
|
+
* customer_managed_configs and readonly_cluster_config, so true means the Cloud
|
|
175
|
+
* page publishes it.
|
|
176
|
+
*
|
|
177
|
+
* Note what this flag does NOT say. False means the control plane does not expose
|
|
178
|
+
* the property to customers; it does not distinguish "Redpanda manages the value"
|
|
179
|
+
* from "the property does not apply to Cloud at all". So the template drops the
|
|
180
|
+
* link and keeps the name, and asserts nothing about who sets it.
|
|
181
|
+
*
|
|
182
|
+
* Both the primary and the alternate mapping are resolved, because a row that
|
|
183
|
+
* links to two cluster properties needs both to be reachable before it is worth
|
|
184
|
+
* linking either.
|
|
185
|
+
*
|
|
186
|
+
* @param {Object} prop - Topic property to annotate in place.
|
|
187
|
+
* @param {Object<string, Object>} properties - Full property map, used to look up counterparts.
|
|
188
|
+
*/
|
|
189
|
+
function annotateCorrespondingClusterSupport(prop, properties) {
|
|
190
|
+
const isSupported = (name) => {
|
|
191
|
+
if (!name) return false;
|
|
192
|
+
const target = properties[name];
|
|
193
|
+
return Boolean(target && target.cloud_supported);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const primary = isSupported(prop.corresponding_cluster_property);
|
|
197
|
+
prop.corresponding_cluster_property_cloud_supported = primary;
|
|
198
|
+
prop.alternate_cluster_property_cloud_supported = isSupported(prop.alternate_cluster_property);
|
|
199
|
+
prop.cluster_property_mapping_cloud_supported = prop.alternate_cluster_property
|
|
200
|
+
? primary && prop.alternate_cluster_property_cloud_supported
|
|
201
|
+
: primary;
|
|
202
|
+
}
|
|
203
|
+
|
|
107
204
|
/**
|
|
108
205
|
* Generate AsciiDoc partial files grouping input properties by scope (cluster, topic, broker, object-storage).
|
|
109
206
|
*
|
|
@@ -120,6 +217,8 @@ function registerPartials() {
|
|
|
120
217
|
function generatePropertyPartials(properties, partialsDir, onRender) {
|
|
121
218
|
console.log(`📝 Generating consolidated property partials in ${partialsDir}…`);
|
|
122
219
|
|
|
220
|
+
normalizeAnchorsInProperties(properties);
|
|
221
|
+
|
|
123
222
|
const propertyTemplate = handlebars.compile(
|
|
124
223
|
fs.readFileSync(getTemplatePath(path.join(__dirname, 'templates', 'property.hbs'), 'TEMPLATE_PROPERTY'), 'utf8')
|
|
125
224
|
);
|
|
@@ -151,6 +250,7 @@ function generatePropertyPartials(properties, partialsDir, onRender) {
|
|
|
151
250
|
|
|
152
251
|
switch (prop.config_scope) {
|
|
153
252
|
case 'topic':
|
|
253
|
+
annotateCorrespondingClusterSupport(prop, properties);
|
|
154
254
|
propertyGroups.topic.push(prop);
|
|
155
255
|
break;
|
|
156
256
|
case 'broker':
|
|
@@ -1,5 +1,18 @@
|
|
|
1
|
+
// Builds the anchor for a property's generated section heading.
|
|
2
|
+
//
|
|
3
|
+
// The headings are plain AsciiDoc (`=== cleanup.policy`), so Asciidoctor derives
|
|
4
|
+
// their IDs itself: lowercase, every run of characters outside [a-z0-9_] replaced
|
|
5
|
+
// with the id separator, which Antora sets to `-`. Underscores are word
|
|
6
|
+
// characters and survive, so `redpanda.cloud_topic.enabled` becomes
|
|
7
|
+
// `redpanda-cloud_topic-enabled`.
|
|
8
|
+
//
|
|
9
|
+
// Reproduce that here rather than inventing a separate scheme: anything else
|
|
10
|
+
// renders as a link to an anchor that does not exist on the page.
|
|
1
11
|
module.exports = function anchorName(name) {
|
|
2
|
-
const anchor = String(name)
|
|
12
|
+
const anchor = String(name)
|
|
13
|
+
.toLowerCase()
|
|
14
|
+
.replace(/[^a-z0-9_]+/g, '-')
|
|
15
|
+
.replace(/^-+|-+$/g, '');
|
|
3
16
|
if (!anchor) {
|
|
4
17
|
throw new Error(`Invalid property name for anchor generation: "${name}"`);
|
|
5
18
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// The reference pages these xrefs target.
|
|
4
|
+
const PROPERTY_PAGES = [
|
|
5
|
+
'cluster-properties',
|
|
6
|
+
'object-storage-properties',
|
|
7
|
+
'topic-properties',
|
|
8
|
+
'broker-properties',
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
// Every prefix seen in the wild for the same target:
|
|
12
|
+
// xref:cluster-properties.adoc#x bare filename
|
|
13
|
+
// xref:./cluster-properties.adoc#x relative to the current directory
|
|
14
|
+
// xref:reference:cluster-properties.adoc#x legacy, when the pages sat at the module root
|
|
15
|
+
// xref:reference:properties/...#x canonical
|
|
16
|
+
const PREFIXES = '(?:\\./|reference:properties/|reference:)?';
|
|
17
|
+
const PATTERN = new RegExp(
|
|
18
|
+
`xref:${PREFIXES}(${PROPERTY_PAGES.join('|')})\\.adoc(?=[#\\[])`,
|
|
19
|
+
'g'
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Rewrite every reference to a property reference page to one canonical resource ID.
|
|
24
|
+
*
|
|
25
|
+
* The bare and `reference:`-prefixed forms both resolve to the module root, where
|
|
26
|
+
* these pages no longer live. Self-managed papers over that with a
|
|
27
|
+
* `reference:cluster-properties.adoc` page alias, so the broken form renders as a
|
|
28
|
+
* working link there and as an unresolved xref in cloud-docs, which has no such
|
|
29
|
+
* alias. The `./` form works but depends on the including page sitting in
|
|
30
|
+
* `reference/properties/`, which is a constraint on every page that includes a
|
|
31
|
+
* partial rather than a property of the partial.
|
|
32
|
+
*
|
|
33
|
+
* The fully qualified module-and-path form has neither problem: it resolves
|
|
34
|
+
* inside whichever component includes the partial, from any directory.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} text - AsciiDoc source that may contain property-page xrefs.
|
|
37
|
+
* @returns {{text: string, rewrites: number}}
|
|
38
|
+
*/
|
|
39
|
+
function canonicalizePropertyXrefs(text) {
|
|
40
|
+
if (typeof text !== 'string' || !text.includes('xref:')) {
|
|
41
|
+
return { text, rewrites: 0 };
|
|
42
|
+
}
|
|
43
|
+
let rewrites = 0;
|
|
44
|
+
const out = text.replace(PATTERN, (match, page) => {
|
|
45
|
+
const canonical = `xref:reference:properties/${page}.adoc`;
|
|
46
|
+
if (match === canonical) return match;
|
|
47
|
+
rewrites += 1;
|
|
48
|
+
return canonical;
|
|
49
|
+
});
|
|
50
|
+
return { text: out, rewrites };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = canonicalizePropertyXrefs;
|
|
54
|
+
module.exports.PROPERTY_PAGES = PROPERTY_PAGES;
|
|
@@ -14,6 +14,8 @@ module.exports = {
|
|
|
14
14
|
renderPropertyExample: require('./renderPropertyExample.js'),
|
|
15
15
|
formatUnits: require('./formatUnits.js'),
|
|
16
16
|
anchorName: require('./anchorName.js'),
|
|
17
|
+
normalizePropertyAnchors: require('./normalizePropertyAnchors.js'),
|
|
18
|
+
canonicalizePropertyXrefs: require('./canonicalizePropertyXrefs.js'),
|
|
17
19
|
parseRelatedTopic: require('./parseRelatedTopic.js'),
|
|
18
20
|
allTopicsConditional: require('./allTopicsConditional.js'),
|
|
19
21
|
capitalize: require('./capitalize.js'),
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const anchorName = require('./anchorName.js');
|
|
4
|
+
|
|
5
|
+
// Strip every separator so `redpanda.storage.mode.impl`, `redpanda-storage-mode-impl`
|
|
6
|
+
// and `redpandastoragemodeimpl` all collapse to one key.
|
|
7
|
+
function squash(value) {
|
|
8
|
+
return String(value).toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Repair intra-page anchors (`<<anchor,text>>`) that name a property but use the
|
|
13
|
+
* wrong ID shape.
|
|
14
|
+
*
|
|
15
|
+
* Descriptions come from three places -- Redpanda's own doc strings, the
|
|
16
|
+
* overrides file, and hand-edits -- and each has at some point written a
|
|
17
|
+
* property anchor by deleting the dots (`<<redpandastoragemodeimpl>>`) or by
|
|
18
|
+
* hyphenating only part of the name (`<<redpandastorage-mode>>`). Asciidoctor
|
|
19
|
+
* derives the real heading ID from the heading text, so those render as links to
|
|
20
|
+
* nothing. Rather than hand-correct each one and wait for the next author to
|
|
21
|
+
* reintroduce it, resolve any anchor that unambiguously names a known property
|
|
22
|
+
* back to that property's real anchor.
|
|
23
|
+
*
|
|
24
|
+
* Only rewrites when the anchor is not already correct and the match is
|
|
25
|
+
* unambiguous, so an anchor pointing at a genuine non-property section is left
|
|
26
|
+
* alone. Names from different scopes can collide once separators are gone --
|
|
27
|
+
* `retention.bytes` and `retention_bytes` both squash to `retentionbytes` -- so
|
|
28
|
+
* a collision is resolved in favour of the scope the referring property is in,
|
|
29
|
+
* which is where a cross-reference nearly always points, and refused outright if
|
|
30
|
+
* that still leaves two candidates.
|
|
31
|
+
*
|
|
32
|
+
* @param {string} text - AsciiDoc source that may contain `<<anchor,label>>` references.
|
|
33
|
+
* @param {Iterable<string>|Object<string, Object>} properties - Known property names, or the property map.
|
|
34
|
+
* @param {string} [scope] - config_scope of the property that owns this text.
|
|
35
|
+
* @returns {{text: string, rewrites: Array<{from: string, to: string}>}}
|
|
36
|
+
*/
|
|
37
|
+
function normalizePropertyAnchors(text, properties, scope) {
|
|
38
|
+
if (typeof text !== 'string' || !text.includes('<<')) {
|
|
39
|
+
return { text, rewrites: [] };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const isMap = properties && !Array.isArray(properties) && typeof properties === 'object'
|
|
43
|
+
&& !(typeof properties[Symbol.iterator] === 'function');
|
|
44
|
+
const names = isMap ? Object.keys(properties) : Array.from(properties || []);
|
|
45
|
+
const scopeOf = (name) => (isMap && properties[name] ? properties[name].config_scope : undefined);
|
|
46
|
+
|
|
47
|
+
const bySquashed = new Map();
|
|
48
|
+
const validAnchors = new Set();
|
|
49
|
+
for (const name of names) {
|
|
50
|
+
if (typeof name !== 'string' || !name) continue;
|
|
51
|
+
let anchor;
|
|
52
|
+
try {
|
|
53
|
+
anchor = anchorName(name);
|
|
54
|
+
} catch {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
validAnchors.add(anchor);
|
|
58
|
+
const key = squash(name);
|
|
59
|
+
const entry = bySquashed.get(key);
|
|
60
|
+
if (entry) entry.push({ anchor, scope: scopeOf(name) });
|
|
61
|
+
else bySquashed.set(key, [{ anchor, scope: scopeOf(name) }]);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const resolve = (key) => {
|
|
65
|
+
const candidates = bySquashed.get(key);
|
|
66
|
+
if (!candidates || candidates.length === 0) return null;
|
|
67
|
+
if (candidates.length === 1) return candidates[0].anchor;
|
|
68
|
+
const sameScope = scope ? candidates.filter((c) => c.scope === scope) : [];
|
|
69
|
+
return sameScope.length === 1 ? sameScope[0].anchor : null;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const rewrites = [];
|
|
73
|
+
const rewritten = text.replace(/<<([^<>,\]]+?)(,|>>)/g, (match, anchor, tail) => {
|
|
74
|
+
const trimmed = anchor.trim();
|
|
75
|
+
if (validAnchors.has(trimmed)) return match;
|
|
76
|
+
const target = resolve(squash(trimmed));
|
|
77
|
+
if (!target || target === trimmed) return match;
|
|
78
|
+
rewrites.push({ from: trimmed, to: target });
|
|
79
|
+
return `<<${target}${tail}`;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return { text: rewritten, rewrites };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = normalizePropertyAnchors;
|
|
86
|
+
module.exports.squash = squash;
|
|
@@ -4,12 +4,21 @@
|
|
|
4
4
|
|
|
5
5
|
{{#each topicProperties}}
|
|
6
6
|
| <<{{anchorName name}},`{{name}}`>>
|
|
7
|
-
|
|
|
7
|
+
|
|
|
8
|
+
ifdef::env-cloud[]
|
|
9
|
+
{{#if cluster_property_mapping_cloud_supported}}
|
|
10
|
+
{{#if alternate_cluster_property}}xref:reference:properties/{{cluster_property_doc_file}}#{{corresponding_cluster_property}}[`{{corresponding_cluster_property}}`] or xref:reference:properties/{{alternate_cluster_property_doc_file}}#{{alternate_cluster_property}}[`{{alternate_cluster_property}}`]{{else}}xref:reference:properties/{{cluster_property_doc_file}}#{{corresponding_cluster_property}}[`{{corresponding_cluster_property}}`]{{/if}}
|
|
11
|
+
{{else}}
|
|
12
|
+
{{#if alternate_cluster_property}}`{{corresponding_cluster_property}}` or `{{alternate_cluster_property}}`{{else}}`{{corresponding_cluster_property}}`{{/if}}
|
|
13
|
+
{{/if}}
|
|
14
|
+
endif::[]
|
|
15
|
+
ifndef::env-cloud[]
|
|
16
|
+
{{#if alternate_cluster_property}}xref:reference:properties/{{cluster_property_doc_file}}#{{corresponding_cluster_property}}[`{{corresponding_cluster_property}}`] or xref:reference:properties/{{alternate_cluster_property_doc_file}}#{{alternate_cluster_property}}[`{{alternate_cluster_property}}`]{{else}}xref:reference:properties/{{cluster_property_doc_file}}#{{corresponding_cluster_property}}[`{{corresponding_cluster_property}}`]{{/if}}
|
|
17
|
+
endif::[]
|
|
8
18
|
|
|
9
19
|
{{/each}}
|
|
10
20
|
|
|
11
21
|
|===
|
|
12
22
|
|
|
13
|
-
{{!--
|
|
14
|
-
{{!--
|
|
15
|
-
{{!-- Example: cleanup.policy -> cleanuppolicy --}}
|
|
23
|
+
{{!-- Anchors come from the anchorName helper, which reproduces Asciidoctor's --}}
|
|
24
|
+
{{!-- own auto-generated heading ID: cleanup.policy -> cleanup-policy. --}}
|
|
@@ -77,7 +77,17 @@ endif::[]
|
|
|
77
77
|
{{#if corresponding_cluster_property}}
|
|
78
78
|
|
|
79
79
|
| Corresponding cluster property
|
|
80
|
-
|
|
|
80
|
+
|
|
|
81
|
+
ifdef::env-cloud[]
|
|
82
|
+
{{#if corresponding_cluster_property_cloud_supported}}
|
|
83
|
+
xref:reference:properties/{{#if cluster_property_doc_file}}{{cluster_property_doc_file}}{{else}}cluster-properties.adoc{{/if}}#{{corresponding_cluster_property}}[`{{corresponding_cluster_property}}`]
|
|
84
|
+
{{else}}
|
|
85
|
+
`{{corresponding_cluster_property}}`
|
|
86
|
+
{{/if}}
|
|
87
|
+
endif::[]
|
|
88
|
+
ifndef::env-cloud[]
|
|
89
|
+
xref:reference:properties/{{#if cluster_property_doc_file}}{{cluster_property_doc_file}}{{else}}cluster-properties.adoc{{/if}}#{{corresponding_cluster_property}}[`{{corresponding_cluster_property}}`]
|
|
90
|
+
endif::[]
|
|
81
91
|
{{/if}}
|
|
82
92
|
{{#if (and minimum maximum)}}
|
|
83
93
|
|
|
@@ -422,10 +422,50 @@ function buildComponentMap(indexObj) {
|
|
|
422
422
|
return map;
|
|
423
423
|
}
|
|
424
424
|
|
|
425
|
+
/**
|
|
426
|
+
* Whether to refresh the Connect plugin before reading its version.
|
|
427
|
+
*
|
|
428
|
+
* Only ephemeral environments should: the refresh uses "install --force",
|
|
429
|
+
* which skips the safeguard that "upgrade" applies to a self-managed plugin
|
|
430
|
+
* (see the comment in getRpkConnectVersion), so on a real machine it can
|
|
431
|
+
* change which Connect binary the user runs afterwards.
|
|
432
|
+
*
|
|
433
|
+
* Defaults to on when CI is set. RPCN_FORCE_CONNECT_INSTALL overrides in
|
|
434
|
+
* either direction, for forcing a refresh locally or suppressing one in a
|
|
435
|
+
* CI-like environment that owns its own Connect installation.
|
|
436
|
+
*/
|
|
437
|
+
function shouldRefreshConnectPlugin() {
|
|
438
|
+
const override = process.env.RPCN_FORCE_CONNECT_INSTALL;
|
|
439
|
+
if (override !== undefined && override !== '') {
|
|
440
|
+
return !['0', 'false', 'no', 'off'].includes(override.toLowerCase());
|
|
441
|
+
}
|
|
442
|
+
return Boolean(process.env.CI);
|
|
443
|
+
}
|
|
444
|
+
|
|
425
445
|
function getRpkConnectVersion() {
|
|
426
446
|
try {
|
|
427
|
-
//
|
|
428
|
-
|
|
447
|
+
// Ensure the connect plugin is current (silent). Deliberately "install
|
|
448
|
+
// --force", not "upgrade": upgrade's version comparison parses the
|
|
449
|
+
// currently-installed version through a regex capped at two digits per
|
|
450
|
+
// segment (redpanda.VersionFromString in rpk/pkg/redpanda/version.go),
|
|
451
|
+
// which throws for any Connect version >= 4.100.0 — the same bug class
|
|
452
|
+
// as CON-529, in a different function that its fix didn't touch. Fixed
|
|
453
|
+
// upstream on dev (redpanda commit 8fc022e7) but present in all rpk
|
|
454
|
+
// releases up to at least v26.2.1; keep this workaround until a fixed
|
|
455
|
+
// rpk release ships. install's "latest" path (the default) skips that
|
|
456
|
+
// regex entirely, so it isn't exposed to the bug.
|
|
457
|
+
//
|
|
458
|
+
// The trade-off is that "install --force" drops a safeguard "upgrade"
|
|
459
|
+
// keeps: upgrade refuses to touch a plugin whose Managed flag is false
|
|
460
|
+
// (rpk/pkg/cli/connect/upgrade.go), which is how a Connect installed by
|
|
461
|
+
// Homebrew or by hand is protected. A forced install instead writes a
|
|
462
|
+
// managed binary into the plugin dir that rpk prioritises, shadowing
|
|
463
|
+
// that installation for every later rpk call. So refresh only where the
|
|
464
|
+
// Connect installation is disposable, and leave a developer's machine
|
|
465
|
+
// exactly as it is — reporting the version already installed there.
|
|
466
|
+
if (shouldRefreshConnectPlugin()) {
|
|
467
|
+
execSync('rpk connect install --force', { stdio: 'ignore' });
|
|
468
|
+
}
|
|
429
469
|
|
|
430
470
|
// Now capture the --version output
|
|
431
471
|
const raw = execSync('rpk connect --version', {
|
|
@@ -444,7 +484,13 @@ function getRpkConnectVersion() {
|
|
|
444
484
|
}
|
|
445
485
|
return match[1];
|
|
446
486
|
} catch (err) {
|
|
447
|
-
|
|
487
|
+
// Outside CI the plugin is never installed on the caller's behalf, so say
|
|
488
|
+
// how to get one rather than only reporting that the call failed.
|
|
489
|
+
const hint = shouldRefreshConnectPlugin()
|
|
490
|
+
? ''
|
|
491
|
+
: ' If Redpanda Connect is not installed, run "rpk connect install" first,' +
|
|
492
|
+
' or set RPCN_FORCE_CONNECT_INSTALL=1 to let this command install it.';
|
|
493
|
+
throw new Error(`Unable to run "rpk connect --version": ${err.message}${hint}`);
|
|
448
494
|
}
|
|
449
495
|
}
|
|
450
496
|
|
|
@@ -11,7 +11,9 @@ const { spawnSync } = require('child_process')
|
|
|
11
11
|
|
|
12
12
|
const docsDir = process.argv[2] || '../docs'
|
|
13
13
|
const rpkDir = path.join(docsDir, 'modules/reference/pages/rpk')
|
|
14
|
-
|
|
14
|
+
// The canonical overrides live in the docs repo (the docsDir this script
|
|
15
|
+
// already takes); this repo ships only the schema.
|
|
16
|
+
const overridesPath = process.argv[3] || path.join(docsDir, 'docs-data/rpk-overrides.json')
|
|
15
17
|
|
|
16
18
|
// Validate docsDir exists and is a directory
|
|
17
19
|
if (!fs.existsSync(docsDir) || !fs.statSync(docsDir).isDirectory()) {
|