@redpanda-data/docs-extensions-and-macros 5.3.1 → 5.3.3
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 +46 -8
- package/cli-utils/diff-utils.js +42 -7
- package/extensions/generate-rp-connect-info.js +9 -3
- package/extensions/process-context-switcher.js +5 -0
- package/package.json +1 -1
- package/tools/property-extractor/Makefile +46 -12
- package/tools/property-extractor/cloud_config.py +43 -14
- package/tools/property-extractor/compare-properties.js +4 -3
- package/tools/property-extractor/pr-summary-formatter.js +7 -4
- package/tools/property-extractor/property_extractor.py +81 -6
- package/tools/rpk-docs/generate-rpk-docs.js +79 -3
- package/tools/rpk-docs/rpk-docs-handler.js +115 -4
- package/tools/rpk-docs/templates/command.hbs +7 -16
- package/tools/property-extractor/templates/property-backup.hbs +0 -161
- package/tools/property-extractor/templates/topic-property-backup.hbs +0 -148
package/bin/doc-tools.js
CHANGED
|
@@ -27,7 +27,8 @@ const {
|
|
|
27
27
|
generatePropertyComparisonReport,
|
|
28
28
|
updatePropertyOverridesWithVersion,
|
|
29
29
|
updatePropertiesJsonWithVersion,
|
|
30
|
-
cleanupOldDiffs
|
|
30
|
+
cleanupOldDiffs,
|
|
31
|
+
resolveDiffBaseline
|
|
31
32
|
} = require('../cli-utils/diff-utils')
|
|
32
33
|
|
|
33
34
|
// Import other utilities
|
|
@@ -890,6 +891,7 @@ automation
|
|
|
890
891
|
.option('-t, --tag <tag>', 'Git tag for released content (GA/beta)')
|
|
891
892
|
.option('-b, --branch <branch>', 'Branch name for in-progress content')
|
|
892
893
|
.option('--diff <oldTag>', 'Diff properties against <oldTag> and restore removed deprecated properties. Recommended for accurate output; falls back to latest-redpanda-tag from antora.yml if not specified')
|
|
894
|
+
.option('--regenerate-old-baseline', 'Re-extract the --diff tag from source instead of using the committed attachments/redpanda-properties-<oldTag>.json baseline')
|
|
893
895
|
.option('--overrides <path>', 'Optional JSON file with property description overrides', 'docs-data/property-overrides.json')
|
|
894
896
|
.option('--output-dir <dir>', 'Where to write all generated files', 'modules/reference')
|
|
895
897
|
.option('--cloud-support', 'Add AsciiDoc tags to generated property docs to indicate which ones are supported in Redpanda Cloud. This data is fetched from the cloudv2 repository so requires a GitHub token with repo permissions. Set the token as an environment variable using GITHUB_TOKEN, GH_TOKEN, or REDPANDA_GITHUB_TOKEN', true)
|
|
@@ -982,7 +984,13 @@ automation
|
|
|
982
984
|
// When a diff is needed, skip AsciiDoc generation during extraction so we
|
|
983
985
|
// can merge removed deprecated properties first and generate only once.
|
|
984
986
|
if (needsDiff) {
|
|
985
|
-
|
|
987
|
+
const { useCommitted, baselinePath } = resolveDiffBaseline(outputDir, oldTag, options.regenerateOldBaseline)
|
|
988
|
+
if (useCommitted) {
|
|
989
|
+
console.log(`Using committed baseline for ${oldTag}: ${baselinePath}`)
|
|
990
|
+
console.log(' Pass --regenerate-old-baseline to rebuild it from source instead.')
|
|
991
|
+
} else {
|
|
992
|
+
make(oldTag, overridesPath, templates, outputDir, { skipPartials: true })
|
|
993
|
+
}
|
|
986
994
|
make(newTag, overridesPath, templates, outputDir, { skipPartials: true })
|
|
987
995
|
} else {
|
|
988
996
|
make(newTag, overridesPath, templates, outputDir)
|
|
@@ -992,7 +1000,16 @@ automation
|
|
|
992
1000
|
// the new JSON so they appear in the generated documentation.
|
|
993
1001
|
if (needsDiff) {
|
|
994
1002
|
const diffOutputDir = overridesPath ? path.dirname(path.resolve(overridesPath)) : outputDir
|
|
995
|
-
|
|
1003
|
+
try {
|
|
1004
|
+
generatePropertyComparisonReport(oldTag, newTag, diffOutputDir)
|
|
1005
|
+
} catch (err) {
|
|
1006
|
+
// A missing baseline only warns inside generatePropertyComparisonReport.
|
|
1007
|
+
// Reaching this catch means both inputs existed and the comparison
|
|
1008
|
+
// itself failed, so fail the run: continuing would silently drop
|
|
1009
|
+
// removed-deprecated restoration and "Introduced in" version stamping.
|
|
1010
|
+
console.error(`Error: Property comparison failed: ${err.message}`)
|
|
1011
|
+
process.exit(1)
|
|
1012
|
+
}
|
|
996
1013
|
|
|
997
1014
|
try {
|
|
998
1015
|
const diffReportPath = path.join(diffOutputDir, `redpanda-property-changes-${oldTag}-to-${newTag}.json`)
|
|
@@ -1012,7 +1029,11 @@ automation
|
|
|
1012
1029
|
}
|
|
1013
1030
|
}
|
|
1014
1031
|
} catch (err) {
|
|
1015
|
-
|
|
1032
|
+
// The diff report exists but could not be read or applied. Fail the
|
|
1033
|
+
// run rather than shipping docs that are missing removed-deprecated
|
|
1034
|
+
// restoration or version stamps.
|
|
1035
|
+
console.error(`Error: Failed to process the property diff report: ${err.message}`)
|
|
1036
|
+
process.exit(1)
|
|
1016
1037
|
}
|
|
1017
1038
|
|
|
1018
1039
|
cleanupOldDiffs(diffOutputDir)
|
|
@@ -1040,15 +1061,32 @@ automation
|
|
|
1040
1061
|
|
|
1041
1062
|
try {
|
|
1042
1063
|
const jsonDir = path.resolve(outputDir, 'attachments')
|
|
1064
|
+
// Invariant: always retain the comparison pair this run actually used
|
|
1065
|
+
// (the current tag's JSON and the diff baseline's JSON) in addition to
|
|
1066
|
+
// the 2 newest versioned JSONs. A backport run can compare tags that
|
|
1067
|
+
// are not the 2 newest, and the next run's comparison needs its
|
|
1068
|
+
// baseline JSON to survive. Mirrors the retention in
|
|
1069
|
+
// tools/property-extractor/Makefile (generate-docs cleanup).
|
|
1070
|
+
const parseVersion = f => f.match(/^redpanda-properties-v([\d.]+)\.json$/)[1].split('.').map(Number)
|
|
1071
|
+
const byVersionDesc = (a, b) => {
|
|
1072
|
+
const [va, vb] = [parseVersion(a), parseVersion(b)]
|
|
1073
|
+
for (let i = 0; i < Math.max(va.length, vb.length); i++) {
|
|
1074
|
+
if ((vb[i] || 0) !== (va[i] || 0)) return (vb[i] || 0) - (va[i] || 0)
|
|
1075
|
+
}
|
|
1076
|
+
return 0
|
|
1077
|
+
}
|
|
1043
1078
|
const propertyFiles = fs.readdirSync(jsonDir)
|
|
1044
1079
|
.filter(f => /^redpanda-properties-v[\d.]+\.json$/.test(f))
|
|
1045
|
-
.sort()
|
|
1080
|
+
.sort(byVersionDesc)
|
|
1046
1081
|
|
|
1047
|
-
const
|
|
1048
|
-
|
|
1082
|
+
const filesToKeep = new Set(propertyFiles.slice(0, 2))
|
|
1083
|
+
for (const tag of [newTag, oldTag]) {
|
|
1084
|
+
if (tag) filesToKeep.add(`redpanda-properties-${tag}.json`)
|
|
1085
|
+
}
|
|
1086
|
+
const filesToDelete = propertyFiles.filter(f => !filesToKeep.has(f))
|
|
1049
1087
|
|
|
1050
1088
|
if (filesToDelete.length > 0) {
|
|
1051
|
-
console.log('🧹 Cleaning up old property JSON files...')
|
|
1089
|
+
console.log('🧹 Cleaning up old property JSON files (keeping the 2 newest plus the comparison pair)...')
|
|
1052
1090
|
filesToDelete.forEach(file => {
|
|
1053
1091
|
fs.unlinkSync(path.join(jsonDir, file))
|
|
1054
1092
|
console.log(` Deleted: ${file}`)
|
package/cli-utils/diff-utils.js
CHANGED
|
@@ -65,9 +65,16 @@ function cleanupOldDiffs (diffDir) {
|
|
|
65
65
|
/**
|
|
66
66
|
* Generate a detailed JSON report describing property changes between two releases.
|
|
67
67
|
*
|
|
68
|
+
* When either input JSON is missing the comparison is skipped with a warning
|
|
69
|
+
* (a genuinely new oldest tag legitimately has no baseline). When both inputs
|
|
70
|
+
* exist but the comparison itself fails, this throws so callers fail loudly
|
|
71
|
+
* instead of silently dropping removed-deprecated restoration and version
|
|
72
|
+
* stamping.
|
|
73
|
+
*
|
|
68
74
|
* @param {string} oldTag - Release tag or identifier for the "old" properties set.
|
|
69
75
|
* @param {string} newTag - Release tag or identifier for the "new" properties set.
|
|
70
76
|
* @param {string} outputDir - Directory where the comparison report will be written.
|
|
77
|
+
* @throws {Error} When both input JSONs exist but the comparison fails to run or exits non-zero.
|
|
71
78
|
*/
|
|
72
79
|
function generatePropertyComparisonReport (oldTag, newTag, outputDir) {
|
|
73
80
|
try {
|
|
@@ -81,13 +88,14 @@ function generatePropertyComparisonReport (oldTag, newTag, outputDir) {
|
|
|
81
88
|
|
|
82
89
|
if (!fs.existsSync(oldJsonPath)) {
|
|
83
90
|
console.log(`Warning: Old properties JSON not found at: ${oldJsonPath}`)
|
|
84
|
-
console.log(' Skipping detailed property comparison.')
|
|
91
|
+
console.log(' Skipping detailed property comparison: no diff report will be generated, so removed deprecated properties are not restored and new properties are not stamped with "Introduced in" versions.')
|
|
92
|
+
console.log(` This is expected only when ${oldTag} has never been extracted (for example, the oldest supported release).`)
|
|
85
93
|
return
|
|
86
94
|
}
|
|
87
95
|
|
|
88
96
|
if (!fs.existsSync(newJsonPath)) {
|
|
89
97
|
console.log(`Warning: New properties JSON not found at: ${newJsonPath}`)
|
|
90
|
-
console.log(' Skipping detailed property comparison.')
|
|
98
|
+
console.log(' Skipping detailed property comparison: no diff report will be generated, so removed deprecated properties are not restored and new properties are not stamped with "Introduced in" versions.')
|
|
91
99
|
return
|
|
92
100
|
}
|
|
93
101
|
|
|
@@ -107,15 +115,19 @@ function generatePropertyComparisonReport (oldTag, newTag, outputDir) {
|
|
|
107
115
|
cwd: propertyExtractorDir
|
|
108
116
|
})
|
|
109
117
|
|
|
118
|
+
// Both inputs exist, so a failed comparison is a hard error: continuing
|
|
119
|
+
// would silently skip removed-deprecated restoration and version stamping
|
|
110
120
|
if (result.error) {
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
console.log(`Done: Property comparison report saved to: ${reportPath}`)
|
|
121
|
+
throw new Error(`Property comparison failed to run: ${result.error.message}`)
|
|
122
|
+
}
|
|
123
|
+
if (result.status !== 0) {
|
|
124
|
+
throw new Error(`Property comparison exited with code ${result.status}`)
|
|
116
125
|
}
|
|
126
|
+
console.log(`Done: Property comparison report saved to: ${reportPath}`)
|
|
117
127
|
} catch (error) {
|
|
118
128
|
console.error(`Error: Error generating property comparison: ${error.message}`)
|
|
129
|
+
// Propagate so callers fail loudly instead of shipping incomplete docs
|
|
130
|
+
throw error
|
|
119
131
|
}
|
|
120
132
|
}
|
|
121
133
|
|
|
@@ -346,7 +358,30 @@ function diffDirs (kind, oldTag, newTag, oldTempDir, newTempDir) {
|
|
|
346
358
|
}
|
|
347
359
|
}
|
|
348
360
|
|
|
361
|
+
/**
|
|
362
|
+
* Decide whether a committed baseline attachment can serve as the old side of
|
|
363
|
+
* a property diff. Rebuilding the old tag in place overwrites the committed
|
|
364
|
+
* attachment with a fresh extraction, so any contamination in that extraction
|
|
365
|
+
* silently becomes both the stored baseline and the diff input (the diff then
|
|
366
|
+
* compares the run against its own output and reports no new properties).
|
|
367
|
+
* @param {string} outputDir - Docs output dir that contains attachments/
|
|
368
|
+
* @param {string} oldTag - Old version tag (for example, v26.1.14)
|
|
369
|
+
* @param {boolean} [regenerate=false] - Force re-extraction even if a baseline exists
|
|
370
|
+
* @returns {{useCommitted: boolean, baselinePath: string}}
|
|
371
|
+
*/
|
|
372
|
+
function resolveDiffBaseline (outputDir, oldTag, regenerate = false) {
|
|
373
|
+
const attachmentsDir = path.resolve(outputDir, 'attachments')
|
|
374
|
+
const baselinePath = path.resolve(attachmentsDir, `redpanda-properties-${oldTag}.json`)
|
|
375
|
+
const relativePath = path.relative(attachmentsDir, baselinePath)
|
|
376
|
+
if (relativePath.startsWith('..') || path.isAbsolute(relativePath) || relativePath.includes(path.sep)) {
|
|
377
|
+
throw new Error(`Invalid old tag "${oldTag}": baseline must resolve within the attachments directory`)
|
|
378
|
+
}
|
|
379
|
+
return { useCommitted: !regenerate && fs.existsSync(baselinePath), baselinePath }
|
|
380
|
+
}
|
|
381
|
+
|
|
349
382
|
module.exports = {
|
|
383
|
+
resolveDiffBaseline,
|
|
384
|
+
|
|
350
385
|
runClusterDocs,
|
|
351
386
|
cleanupOldDiffs,
|
|
352
387
|
generatePropertyComparisonReport,
|
|
@@ -428,13 +428,19 @@ module.exports.register = function ({ config }) {
|
|
|
428
428
|
function buildContextSwitcher (connectorRows, currentRow, variant) {
|
|
429
429
|
const capitalize = (value) => value.charAt(0).toUpperCase() + value.slice(1)
|
|
430
430
|
const orderedRows = [currentRow, ...connectorRows.filter((row) => row !== currentRow)]
|
|
431
|
-
|
|
431
|
+
// The process-context-switcher extension and the UI's context-switcher partial
|
|
432
|
+
// both expect an array of { name, to } items; `to` is a root-relative pub URL.
|
|
433
|
+
const items = []
|
|
432
434
|
for (const row of orderedRows) {
|
|
433
435
|
const link = (variant === 'cloud' && row.redpandaCloudUrl) || row.redpandaConnectUrl
|
|
434
436
|
if (!link) continue
|
|
435
437
|
const type = row.type.trim()
|
|
436
|
-
|
|
438
|
+
// First occurrence wins: orderedRows puts the current page's row first,
|
|
439
|
+
// so a connector with duplicate types keeps its own entry. (The old
|
|
440
|
+
// object-keyed build let later rows overwrite earlier ones.)
|
|
441
|
+
if (items.some((item) => item.name === capitalize(type))) continue
|
|
442
|
+
items.push({ name: capitalize(type), to: link })
|
|
437
443
|
}
|
|
438
|
-
return JSON.stringify(
|
|
444
|
+
return JSON.stringify(items)
|
|
439
445
|
}
|
|
440
446
|
}
|
|
@@ -74,6 +74,11 @@ module.exports.register = function ({ config }) {
|
|
|
74
74
|
item.to = currentResourceId;
|
|
75
75
|
hasChanges = true;
|
|
76
76
|
logger.debug(`Replaced 'current' with '${currentResourceId}' in context-switcher`);
|
|
77
|
+
} else if (typeof item.to === 'string' && item.to.startsWith('/')) {
|
|
78
|
+
// Root-relative URLs (e.g. pub.url values set by generate-rp-connect-info)
|
|
79
|
+
// need no resolution or target injection: the UI's resolve-resource helper
|
|
80
|
+
// passes them through, and the producer decorates every variant directly.
|
|
81
|
+
continue;
|
|
77
82
|
} else if (item.to !== currentResourceId) {
|
|
78
83
|
// For non-current items, find and update the target page
|
|
79
84
|
const targetPage = findPageByResourceId(item.to, contentCatalog, page);
|
package/package.json
CHANGED
|
@@ -72,19 +72,30 @@ clean:
|
|
|
72
72
|
@rm -rf $(VENV) $(TMP_ROOT) $(REPO_ROOT)/autogenerated
|
|
73
73
|
|
|
74
74
|
# --- Clone or update Redpanda, checkout TAG or default branch ---
|
|
75
|
+
# Clones are shallow: the extractor only reads source files at a single tag,
|
|
76
|
+
# so full history is wasted time and disk.
|
|
77
|
+
# Auth is passed as a per-invocation HTTP header (git -c before the clone
|
|
78
|
+
# subcommand), never embedded in the remote URL, so no token is persisted in
|
|
79
|
+
# the clone's .git/config. The clone lives inside the installed package dir,
|
|
80
|
+
# so a persisted token would be readable by anything that can list it.
|
|
75
81
|
redpanda-git:
|
|
76
82
|
@mkdir -p "$(TMP_ROOT)"
|
|
77
83
|
@echo "🔄 Cloning/updating Redpanda into $(REDPANDA_SRC)…"
|
|
78
84
|
@if [ -d "$(REDPANDA_SRC)" ]; then \
|
|
79
|
-
git -C "$(REDPANDA_SRC)" fetch --
|
|
85
|
+
git -C "$(REDPANDA_SRC)" fetch --depth 1 -q origin "+refs/tags/$(TAG):refs/tags/$(TAG)" 2>/dev/null || \
|
|
86
|
+
git -C "$(REDPANDA_SRC)" fetch --depth 1 -q origin "+refs/heads/$(TAG):refs/remotes/origin/$(TAG)" 2>/dev/null || \
|
|
87
|
+
git -C "$(REDPANDA_SRC)" fetch --all --tags -q; \
|
|
80
88
|
else \
|
|
81
89
|
GH_TOKEN=$${REDPANDA_GITHUB_TOKEN:-$${GITHUB_TOKEN:-$${GH_TOKEN}}}; \
|
|
90
|
+
REPO_URL="https://github.com/redpanda-data/redpanda.git"; \
|
|
82
91
|
if [ -n "$$GH_TOKEN" ]; then \
|
|
83
92
|
echo "🔑 Using authenticated clone (token provided)"; \
|
|
84
|
-
|
|
93
|
+
AUTH_HEADER="AUTHORIZATION: basic $$(printf '%s' "x-access-token:$$GH_TOKEN" | base64 | tr -d '\n')"; \
|
|
85
94
|
else \
|
|
86
|
-
|
|
95
|
+
AUTH_HEADER=""; \
|
|
87
96
|
fi; \
|
|
97
|
+
git -c http.https://github.com/.extraheader="$$AUTH_HEADER" clone -q --depth 1 --branch "$(TAG)" "$$REPO_URL" "$(REDPANDA_SRC)" 2>/dev/null || \
|
|
98
|
+
git -c http.https://github.com/.extraheader="$$AUTH_HEADER" clone -q --depth 1 "$$REPO_URL" "$(REDPANDA_SRC)"; \
|
|
88
99
|
fi; \
|
|
89
100
|
if git -C "$(REDPANDA_SRC)" rev-parse --verify -q "origin/$(TAG)" >/dev/null 2>&1; then \
|
|
90
101
|
echo "🔖 Checking out remote branch 'origin/$(TAG)'"; \
|
|
@@ -94,24 +105,44 @@ redpanda-git:
|
|
|
94
105
|
echo "🔖 Checking out '$(TAG)'"; \
|
|
95
106
|
git -C "$(REDPANDA_SRC)" checkout -q "$(TAG)"; \
|
|
96
107
|
else \
|
|
97
|
-
DEFAULT_BRANCH=$$(git -C "$(REDPANDA_SRC)" symbolic-ref --short refs/remotes/origin/HEAD); \
|
|
98
|
-
|
|
99
|
-
|
|
108
|
+
DEFAULT_BRANCH=$$(git -C "$(REDPANDA_SRC)" symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null || true); \
|
|
109
|
+
if [ -n "$$DEFAULT_BRANCH" ]; then \
|
|
110
|
+
echo "⚠️ Tag '$(TAG)' not found; falling back to '$${DEFAULT_BRANCH}'"; \
|
|
111
|
+
git -C "$(REDPANDA_SRC)" checkout -q "$${DEFAULT_BRANCH}"; \
|
|
112
|
+
else \
|
|
113
|
+
DEFAULT_BRANCH=$$(git -C "$(REDPANDA_SRC)" ls-remote --symref origin HEAD | awk '/^ref:/ {sub(/^refs\/heads\//, "", $$2); print $$2; exit}'); \
|
|
114
|
+
if [ -z "$$DEFAULT_BRANCH" ]; then \
|
|
115
|
+
echo "❌ Tag '$(TAG)' not found and the default branch could not be determined"; \
|
|
116
|
+
exit 1; \
|
|
117
|
+
fi; \
|
|
118
|
+
echo "⚠️ Tag '$(TAG)' not found; falling back to '$${DEFAULT_BRANCH}'"; \
|
|
119
|
+
git -C "$(REDPANDA_SRC)" fetch --depth 1 -q origin "$$DEFAULT_BRANCH"; \
|
|
120
|
+
git -C "$(REDPANDA_SRC)" checkout -q FETCH_HEAD; \
|
|
121
|
+
fi; \
|
|
100
122
|
fi
|
|
101
123
|
|
|
102
124
|
# --- Clone Tree-sitter grammar & generate parser ---
|
|
125
|
+
# Skips npm install and parser generation when the generated output already
|
|
126
|
+
# exists for the pinned grammar version, so repeat builds stay fast
|
|
103
127
|
treesitter:
|
|
104
128
|
@echo "🌲 Ensuring tree-sitter-cpp grammar…"
|
|
105
129
|
@if [ ! -d "$(TREESITTER_DIR)/.git" ]; then \
|
|
106
130
|
rm -rf "$(TREESITTER_DIR)"; \
|
|
107
131
|
git clone https://github.com/tree-sitter/tree-sitter-cpp.git "$(TREESITTER_DIR)"; \
|
|
108
132
|
fi
|
|
109
|
-
@
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
133
|
+
@CURRENT_TAG=$$(git -C "$(TREESITTER_DIR)" describe --tags --exact-match 2>/dev/null || true); \
|
|
134
|
+
if [ "$$CURRENT_TAG" = "v0.20.5" ] && [ -f "$(TREESITTER_DIR)/src/parser.c" ]; then \
|
|
135
|
+
echo "✅ tree-sitter-cpp parser already generated at v0.20.5; skipping regeneration"; \
|
|
136
|
+
else \
|
|
137
|
+
echo "🔄 Ensuring tree-sitter-cpp is at compatible version v0.20.5…"; \
|
|
138
|
+
cd "$(TREESITTER_DIR)" && \
|
|
139
|
+
git fetch --tags -q && \
|
|
140
|
+
git checkout -q v0.20.5 && \
|
|
141
|
+
echo "🔧 Generating parser in $(TREESITTER_DIR)…" && \
|
|
142
|
+
export CFLAGS="-Wno-unused-but-set-variable" && \
|
|
143
|
+
npm install --silent && \
|
|
144
|
+
$(TREE_SITTER) generate; \
|
|
145
|
+
fi
|
|
115
146
|
|
|
116
147
|
# --- Install Node.js dependencies for Handlebars ---
|
|
117
148
|
node-deps:
|
|
@@ -146,6 +177,9 @@ generate-docs: node-deps
|
|
|
146
177
|
else \
|
|
147
178
|
echo " ⚠️ Source file $(TOOL_ROOT)/gen/$(TAG)-properties.json not found, skipping copy"; \
|
|
148
179
|
fi
|
|
180
|
+
@# Invariant: keep at least 2 versioned JSONs, the current release plus the
|
|
181
|
+
@# diff baseline that the next run's comparison depends on. Mirrors the
|
|
182
|
+
@# retention in bin/doc-tools.js (post-run property JSON cleanup).
|
|
149
183
|
@echo "🧹 Cleaning up old versioned JSON files (keeping only 2 most recent)…"
|
|
150
184
|
@cd "$(OUTPUT_JSON_DIR)" && \
|
|
151
185
|
ls -t redpanda-properties-*.json 2>/dev/null | tail -n +3 | while read file; do \
|
|
@@ -434,6 +434,22 @@ def fetch_cloud_config(github_token: Optional[str] = None) -> CloudConfig:
|
|
|
434
434
|
raise CloudConfigError(error_msg) from e
|
|
435
435
|
|
|
436
436
|
|
|
437
|
+
def _match_name_or_alias(prop_name: str, prop_data: Dict, cloud_set: Set[str]) -> Optional[str]:
|
|
438
|
+
"""
|
|
439
|
+
Return the name under which a property appears in a cloud property set.
|
|
440
|
+
|
|
441
|
+
Checks the property's own name first, then each entry in its `aliases`
|
|
442
|
+
array. Returns the matching name, or None when neither the name nor any
|
|
443
|
+
alias is in the set.
|
|
444
|
+
"""
|
|
445
|
+
if prop_name in cloud_set:
|
|
446
|
+
return prop_name
|
|
447
|
+
for alias in prop_data.get('aliases') or []:
|
|
448
|
+
if alias in cloud_set:
|
|
449
|
+
return alias
|
|
450
|
+
return None
|
|
451
|
+
|
|
452
|
+
|
|
437
453
|
def add_cloud_support_metadata(properties: Dict, cloud_config: CloudConfig) -> Dict:
|
|
438
454
|
"""
|
|
439
455
|
Annotate property definitions with cloud-support metadata derived from a CloudConfig.
|
|
@@ -492,29 +508,39 @@ def add_cloud_support_metadata(properties: Dict, cloud_config: CloudConfig) -> D
|
|
|
492
508
|
continue
|
|
493
509
|
|
|
494
510
|
processed_count += 1
|
|
495
|
-
|
|
511
|
+
|
|
496
512
|
# Initialize cloud metadata with defaults
|
|
497
513
|
prop_data['cloud_editable'] = False
|
|
498
514
|
prop_data['cloud_readonly'] = False
|
|
499
515
|
prop_data['cloud_supported'] = False
|
|
500
516
|
prop_data['cloud_byoc_only'] = False
|
|
501
|
-
|
|
502
|
-
# Determine cloud support status
|
|
503
|
-
|
|
517
|
+
|
|
518
|
+
# Determine cloud support status. Match on the property's current
|
|
519
|
+
# name or any of its aliases, so a property renamed in Redpanda
|
|
520
|
+
# still picks up cloud metadata when the cloud configuration
|
|
521
|
+
# references the old name.
|
|
522
|
+
editable_match = _match_name_or_alias(prop_name, prop_data, editable_props)
|
|
523
|
+
readonly_match = None if editable_match else _match_name_or_alias(prop_name, prop_data, readonly_props)
|
|
524
|
+
|
|
525
|
+
if editable_match:
|
|
526
|
+
if editable_match != prop_name:
|
|
527
|
+
logger.info(f"Property '{prop_name}' matched cloud editable configuration via alias '{editable_match}'")
|
|
504
528
|
prop_data['cloud_editable'] = True
|
|
505
529
|
prop_data['cloud_readonly'] = False
|
|
506
530
|
prop_data['cloud_supported'] = True
|
|
507
531
|
cloud_supported_count += 1
|
|
508
532
|
editable_count += 1
|
|
509
|
-
|
|
510
|
-
# Check if BYOC only
|
|
511
|
-
if cloud_config.is_byoc_only(
|
|
533
|
+
|
|
534
|
+
# Check if BYOC only, using the name the cloud configuration knows
|
|
535
|
+
if cloud_config.is_byoc_only(editable_match):
|
|
512
536
|
prop_data['cloud_byoc_only'] = True
|
|
513
537
|
byoc_only_count += 1
|
|
514
538
|
else:
|
|
515
539
|
prop_data['cloud_byoc_only'] = False
|
|
516
|
-
|
|
517
|
-
elif
|
|
540
|
+
|
|
541
|
+
elif readonly_match:
|
|
542
|
+
if readonly_match != prop_name:
|
|
543
|
+
logger.info(f"Property '{prop_name}' matched cloud readonly configuration via alias '{readonly_match}'")
|
|
518
544
|
prop_data['cloud_editable'] = False
|
|
519
545
|
prop_data['cloud_readonly'] = True
|
|
520
546
|
prop_data['cloud_supported'] = True
|
|
@@ -565,11 +591,14 @@ def add_cloud_support_metadata(properties: Dict, cloud_config: CloudConfig) -> D
|
|
|
565
591
|
logger.warning(" 2. Property names don't match between sources")
|
|
566
592
|
logger.warning(" 3. All properties are self-managed only")
|
|
567
593
|
|
|
568
|
-
# Check for potential mismatches
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
594
|
+
# Check for potential mismatches, counting aliases as known names so a
|
|
595
|
+
# renamed property does not show up as missing
|
|
596
|
+
extracted_cloud_names = set()
|
|
597
|
+
for name, data in properties.items():
|
|
598
|
+
if isinstance(data, dict) and data.get('config_scope') in ['cluster', 'broker', 'topic']:
|
|
599
|
+
extracted_cloud_names.add(name)
|
|
600
|
+
extracted_cloud_names.update(data.get('aliases') or [])
|
|
601
|
+
unmatched_cloud_props = (editable_props | readonly_props) - extracted_cloud_names
|
|
573
602
|
|
|
574
603
|
if unmatched_cloud_props:
|
|
575
604
|
logger.info(f"Cloud configuration contains {len(unmatched_cloud_props)} properties not found in extracted properties:")
|
|
@@ -364,8 +364,10 @@ function generateConsoleReport(report, oldVersion, newVersion) {
|
|
|
364
364
|
/**
|
|
365
365
|
* Write a structured JSON comparison report to disk.
|
|
366
366
|
*
|
|
367
|
-
* Produces a JSON file containing a comparison header (old/new versions
|
|
367
|
+
* Produces a JSON file containing a comparison header (old/new versions),
|
|
368
368
|
* a summary with counts for each change category, and the full details object passed as `report`.
|
|
369
|
+
* The report is deterministic: rerunning the comparison on the same inputs
|
|
370
|
+
* produces byte-identical output, so committed reports do not churn.
|
|
369
371
|
*
|
|
370
372
|
* @param {Object} report - Comparison details object produced by compareProperties; expected to contain arrays: `newProperties`, `changedDefaults`, `changedDescriptions`, `changedTypes`, `deprecatedProperties`, `removedProperties`, and `emptyDescriptions`.
|
|
371
373
|
* @param {string} oldVersion - The previous version identifier included in the comparison header.
|
|
@@ -376,8 +378,7 @@ function generateJsonReport(report, oldVersion, newVersion, outputPath) {
|
|
|
376
378
|
const jsonReport = {
|
|
377
379
|
comparison: {
|
|
378
380
|
oldVersion,
|
|
379
|
-
newVersion
|
|
380
|
-
timestamp: new Date().toISOString()
|
|
381
|
+
newVersion
|
|
381
382
|
},
|
|
382
383
|
summary: {
|
|
383
384
|
newProperties: report.newProperties.length,
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*
|
|
21
21
|
* Input format (diff data from compare-properties.js):
|
|
22
22
|
* {
|
|
23
|
-
* comparison: { oldVersion, newVersion
|
|
23
|
+
* comparison: { oldVersion, newVersion },
|
|
24
24
|
* summary: { newProperties, changedDefaults, ... },
|
|
25
25
|
* details: { newProperties: [...], changedDefaults: [...], ... }
|
|
26
26
|
* }
|
|
@@ -278,11 +278,14 @@ function generatePRSummary(diffData) {
|
|
|
278
278
|
lines.push('</details>');
|
|
279
279
|
lines.push('');
|
|
280
280
|
|
|
281
|
-
// Footer
|
|
281
|
+
// Footer. The timestamp only appears in reports generated before the
|
|
282
|
+
// field was removed to keep committed diff reports deterministic.
|
|
282
283
|
lines.push('---');
|
|
283
284
|
lines.push('');
|
|
284
|
-
|
|
285
|
-
|
|
285
|
+
if (diffData.comparison.timestamp) {
|
|
286
|
+
lines.push(`*Generated: ${diffData.comparison.timestamp}*`);
|
|
287
|
+
lines.push('');
|
|
288
|
+
}
|
|
286
289
|
lines.push('<!-- PR_SUMMARY_END -->');
|
|
287
290
|
|
|
288
291
|
return lines.join('\n');
|
|
@@ -321,16 +321,35 @@ _constexpr_cache = ConstexprCache()
|
|
|
321
321
|
_type_definitions_cache = {}
|
|
322
322
|
|
|
323
323
|
# Import topic property extractor
|
|
324
|
+
# Set from --path in main() so every consumer of the Redpanda source uses the
|
|
325
|
+
# tree the caller pointed at, regardless of the current working directory. The
|
|
326
|
+
# hardcoded search below is only a fallback for direct module use.
|
|
327
|
+
_redpanda_source_override = None
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def set_redpanda_source(path):
|
|
331
|
+
"""Record the Redpanda source directory passed via --path."""
|
|
332
|
+
global _redpanda_source_override
|
|
333
|
+
_redpanda_source_override = path
|
|
334
|
+
|
|
335
|
+
|
|
324
336
|
def find_redpanda_source():
|
|
325
337
|
"""
|
|
326
|
-
Locate the Redpanda source directory
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
338
|
+
Locate the Redpanda source directory.
|
|
339
|
+
|
|
340
|
+
Prefers the path passed via --path (recorded by set_redpanda_source);
|
|
341
|
+
falls back to searching standard locations relative to the current
|
|
342
|
+
working directory. Before this preference existed, running the extractor
|
|
343
|
+
from any directory that did not match a hardcoded guess silently
|
|
344
|
+
disabled the ConstantResolver: no validator enums, no enterprise enum
|
|
345
|
+
metadata, no constexpr resolution, with only a debug-level log line.
|
|
346
|
+
|
|
331
347
|
Returns:
|
|
332
348
|
str or None: Path to the Redpanda source directory if found, None otherwise.
|
|
333
349
|
"""
|
|
350
|
+
if _redpanda_source_override and os.path.exists(_redpanda_source_override):
|
|
351
|
+
return _redpanda_source_override
|
|
352
|
+
|
|
334
353
|
redpanda_source_paths = [
|
|
335
354
|
'tmp/redpanda', # Current directory
|
|
336
355
|
'../tmp/redpanda', # Parent directory
|
|
@@ -725,6 +744,13 @@ def transform_files_with_properties(files_with_properties):
|
|
|
725
744
|
if src_v_path.exists():
|
|
726
745
|
constant_resolver = ConstantResolver(src_v_path)
|
|
727
746
|
logger.debug(f"Initialized ConstantResolver with path: {src_v_path}")
|
|
747
|
+
if constant_resolver is None:
|
|
748
|
+
logger.warning(
|
|
749
|
+
"Redpanda source directory not found: validator enum extraction "
|
|
750
|
+
"and enterprise enum metadata are DISABLED for this run. "
|
|
751
|
+
"Properties like sasl_mechanisms will be missing items.enum and "
|
|
752
|
+
"x-enum-metadata. Pass --path to the Redpanda checkout to fix this."
|
|
753
|
+
)
|
|
728
754
|
|
|
729
755
|
transformers = [
|
|
730
756
|
EnterpriseTransformer(), ## this must be the first, as it modifies current data
|
|
@@ -942,6 +968,32 @@ def merge_properties_and_definitions(properties, definitions):
|
|
|
942
968
|
return dict(properties=properties, definitions=definitions)
|
|
943
969
|
|
|
944
970
|
|
|
971
|
+
# Phantom stub entries created by apply_property_overrides during the current
|
|
972
|
+
# run. An entry is recorded whenever an override key matches no extracted
|
|
973
|
+
# property and a placeholder doc entry is fabricated from the override alone.
|
|
974
|
+
# Tracked at module level so the run summary in main() can report them all in
|
|
975
|
+
# one place at the end of the run.
|
|
976
|
+
phantom_stub_entries = []
|
|
977
|
+
|
|
978
|
+
|
|
979
|
+
def report_phantom_stubs():
|
|
980
|
+
"""Log one prominent warning block listing every phantom stub created from overrides."""
|
|
981
|
+
if not phantom_stub_entries:
|
|
982
|
+
return
|
|
983
|
+
logger.warning("=" * 70)
|
|
984
|
+
logger.warning(
|
|
985
|
+
f"⚠️ {len(phantom_stub_entries)} override "
|
|
986
|
+
f"{'entry' if len(phantom_stub_entries) == 1 else 'entries'} matched no extracted property"
|
|
987
|
+
)
|
|
988
|
+
for entry in phantom_stub_entries:
|
|
989
|
+
logger.warning(
|
|
990
|
+
f"Override key '{entry['name']}' matched no extracted property — created a stub entry "
|
|
991
|
+
f"with config_scope '{entry['config_scope']}'. If the property was renamed or removed, "
|
|
992
|
+
f"update docs-data/property-overrides.json."
|
|
993
|
+
)
|
|
994
|
+
logger.warning("=" * 70)
|
|
995
|
+
|
|
996
|
+
|
|
945
997
|
def apply_property_overrides(properties, overrides, overrides_file_path=None):
|
|
946
998
|
"""
|
|
947
999
|
Apply overrides from an overrides mapping to the extracted properties, mutating and returning the properties dictionary.
|
|
@@ -961,6 +1013,7 @@ def apply_property_overrides(properties, overrides, overrides_file_path=None):
|
|
|
961
1013
|
Returns:
|
|
962
1014
|
dict: The same properties mapping with overrides applied and any new properties created.
|
|
963
1015
|
"""
|
|
1016
|
+
phantom_stub_entries.clear()
|
|
964
1017
|
if overrides and "properties" in overrides:
|
|
965
1018
|
for prop, override in overrides["properties"].items():
|
|
966
1019
|
# First check if property exists by key
|
|
@@ -982,7 +1035,13 @@ def apply_property_overrides(properties, overrides, overrides_file_path=None):
|
|
|
982
1035
|
else:
|
|
983
1036
|
# Create new property from override
|
|
984
1037
|
logger.info(f"Creating new property from override: {prop}")
|
|
985
|
-
|
|
1038
|
+
new_property = _create_property_from_override(prop, override, overrides_file_path)
|
|
1039
|
+
properties[prop] = new_property
|
|
1040
|
+
# Record the phantom stub so the run summary can flag it loudly
|
|
1041
|
+
phantom_stub_entries.append({
|
|
1042
|
+
"name": prop,
|
|
1043
|
+
"config_scope": new_property.get("config_scope"),
|
|
1044
|
+
})
|
|
986
1045
|
return properties
|
|
987
1046
|
|
|
988
1047
|
|
|
@@ -2876,6 +2935,11 @@ def main():
|
|
|
2876
2935
|
treesitter_dir, destination_path
|
|
2877
2936
|
)
|
|
2878
2937
|
|
|
2938
|
+
# Every consumer of the Redpanda source (ConstantResolver, constexpr
|
|
2939
|
+
# fallback search) must use the tree the caller pointed at, not a
|
|
2940
|
+
# cwd-relative guess.
|
|
2941
|
+
set_redpanda_source(options.path)
|
|
2942
|
+
|
|
2879
2943
|
# Pre-build constexpr cache for performance
|
|
2880
2944
|
# This avoids repeated filesystem walks when resolving C++ identifiers and function calls
|
|
2881
2945
|
logger.info("🔧 Building constexpr identifier cache...")
|
|
@@ -3073,5 +3137,16 @@ def main():
|
|
|
3073
3137
|
logging.error(f"Failed to write enhanced output file: {e}")
|
|
3074
3138
|
sys.exit(1)
|
|
3075
3139
|
|
|
3140
|
+
# Surface phantom stubs prominently at the end of the run. These are
|
|
3141
|
+
# override keys that matched no extracted property, so a placeholder doc
|
|
3142
|
+
# entry was fabricated from the override alone.
|
|
3143
|
+
if phantom_stub_entries:
|
|
3144
|
+
report_phantom_stubs()
|
|
3145
|
+
phantom_names = ", ".join(entry["name"] for entry in phantom_stub_entries)
|
|
3146
|
+
print(
|
|
3147
|
+
f"⚠️ Phantom stub entries created from overrides: "
|
|
3148
|
+
f"{len(phantom_stub_entries)} ({phantom_names})"
|
|
3149
|
+
)
|
|
3150
|
+
|
|
3076
3151
|
if __name__ == "__main__":
|
|
3077
3152
|
main()
|
|
@@ -15,6 +15,19 @@ Object.entries(helpers).forEach(([name, fn]) => {
|
|
|
15
15
|
// Template paths
|
|
16
16
|
const TEMPLATES_DIR = path.resolve(__dirname, './templates')
|
|
17
17
|
|
|
18
|
+
// Full command paths from the tree being generated (e.g. "rpk ai run codex").
|
|
19
|
+
// Registered by generateRpkDocs so formatDescription can wrap real multi-word
|
|
20
|
+
// command paths as a unit instead of heuristically wrapping `rpk` alone.
|
|
21
|
+
let knownCommandPaths = new Set()
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Register the set of real command paths for the current generation run.
|
|
25
|
+
* @param {string[]} paths - Full command paths (e.g. "rpk ai run codex")
|
|
26
|
+
*/
|
|
27
|
+
function registerKnownCommandPaths(paths) {
|
|
28
|
+
knownCommandPaths = new Set(paths)
|
|
29
|
+
}
|
|
30
|
+
|
|
18
31
|
/**
|
|
19
32
|
* Register a Handlebars partial from file
|
|
20
33
|
* @param {string} name - Partial name
|
|
@@ -1607,6 +1620,25 @@ function formatDescription(desc, customTransformations = null, options = {}) {
|
|
|
1607
1620
|
// Fix product name: "Redpanda cloud" → "Redpanda Cloud" (product name)
|
|
1608
1621
|
.replace(/Redpanda\s+cloud\b/g, 'Redpanda Cloud')
|
|
1609
1622
|
|
|
1623
|
+
// === RPK COMMAND FORMATTING (known paths first, ground truth) ===
|
|
1624
|
+
// Wrap full multi-word command paths that exist in the generated tree
|
|
1625
|
+
// (e.g. "rpk ai run codex" -> `rpk ai run codex`). The heuristic formatter
|
|
1626
|
+
// below only ever matches "rpk <word>", so without this pass it wraps
|
|
1627
|
+
// `rpk` alone and splits the command in half. Matching against the real
|
|
1628
|
+
// tree means prose that merely resembles a command is never wrapped.
|
|
1629
|
+
if (knownCommandPaths.size > 0) {
|
|
1630
|
+
result = result.replace(/(?<![`\w])rpk((?: [a-z][-a-z0-9]*)+)/g, (match, rest) => {
|
|
1631
|
+
const tokens = rest.trim().split(' ')
|
|
1632
|
+
for (let n = tokens.length; n >= 2; n--) {
|
|
1633
|
+
const path = `rpk ${tokens.slice(0, n).join(' ')}`
|
|
1634
|
+
if (knownCommandPaths.has(path)) {
|
|
1635
|
+
return `\`${path}\`` + match.slice(path.length)
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
return match
|
|
1639
|
+
})
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1610
1642
|
// === RPK COMMAND FORMATTING (context-aware) ===
|
|
1611
1643
|
// Process "rpk X" patterns based on what follows
|
|
1612
1644
|
result = result.replace(/(?<![`\w])rpk(\s+)([a-z][-a-z0-9]*)(?:\s+|(?=[.,;:!?)'"}\]]|$))/gi, (match, space, word, offset, str) => {
|
|
@@ -1702,6 +1734,11 @@ function formatDescription(desc, customTransformations = null, options = {}) {
|
|
|
1702
1734
|
// Add backticks around standalone file names (not inside paths)
|
|
1703
1735
|
result = result.replace(/(?<![`/])(redpanda\.yaml|rpk\.yaml)(?!`)/gi, '`$1`')
|
|
1704
1736
|
|
|
1737
|
+
// Add backticks around internal topic names (_redpanda.transform_logs,
|
|
1738
|
+
// _redpanda.audit_log, ...). The leading underscore is also an AsciiDoc
|
|
1739
|
+
// italics delimiter, so these must never render as bare prose.
|
|
1740
|
+
result = result.replace(/(?<![`\w/])(_redpanda(?:\.[a-z_]+)+)(?!`)/g, '`$1`')
|
|
1741
|
+
|
|
1705
1742
|
// Add backticks around standalone "rpk" (at end of phrase, before punctuation)
|
|
1706
1743
|
// Also exclude rpk inside paths (preceded by /)
|
|
1707
1744
|
result = result.replace(/(?<![`\w/])rpk(?=[\s]*[.,;:!?)'"}\]]|[\s]*$)/g, '`rpk`')
|
|
@@ -1730,9 +1767,13 @@ function formatDescription(desc, customTransformations = null, options = {}) {
|
|
|
1730
1767
|
result = result.replace(`__EARLY_CODE_BLOCK_${i}__`, () => transformed)
|
|
1731
1768
|
})
|
|
1732
1769
|
|
|
1733
|
-
// Restore inline code
|
|
1770
|
+
// Restore inline code. Spans are verbatim, but rules flagged applyToCode
|
|
1771
|
+
// (like the rpai -> rpk ai binary-name rewrite) must reach them the same
|
|
1772
|
+
// way they reach code blocks, or the internal binary name survives in
|
|
1773
|
+
// published spans like `rpai auth token`.
|
|
1734
1774
|
inlineCode.forEach((code, i) => {
|
|
1735
|
-
|
|
1775
|
+
const transformed = applyTextTransformations(code, customTransformations, { code: true })
|
|
1776
|
+
result = result.replace(`__INLINE_CODE_${i}__`, () => transformed)
|
|
1736
1777
|
})
|
|
1737
1778
|
|
|
1738
1779
|
// Restore xrefs
|
|
@@ -2173,6 +2214,12 @@ function capToTwoSentences(desc) {
|
|
|
2173
2214
|
// The pattern matches: colon, optional whitespace/newlines, then indented content
|
|
2174
2215
|
cleaned = cleaned.replace(/:\s*\n+[ \t]+.+$/s, ':')
|
|
2175
2216
|
|
|
2217
|
+
// A paragraph break is a sentence boundary even when the paragraph has no
|
|
2218
|
+
// terminal punctuation (cobra short descriptions often lack one: "Generate
|
|
2219
|
+
// a trial license\n\nThis command..."). Without this, flattening newlines
|
|
2220
|
+
// glues the paragraphs into one run-on "sentence".
|
|
2221
|
+
cleaned = cleaned.replace(/([^.!?:\s])[ \t]*\n[ \t]*\n/g, '$1.\n\n')
|
|
2222
|
+
|
|
2176
2223
|
// Normalize newlines to spaces for inline use (like :description: attribute)
|
|
2177
2224
|
const singleLine = cleaned.replace(/\s*\n+\s*/g, ' ').trim()
|
|
2178
2225
|
|
|
@@ -2202,6 +2249,14 @@ function capToTwoSentences(desc) {
|
|
|
2202
2249
|
// dropped by the sentence matcher
|
|
2203
2250
|
normalized = normalized.replace(/(\d)\.(\d)/g, '$1__DECIMAL__$2')
|
|
2204
2251
|
|
|
2252
|
+
// Protect ALL mid-token periods (no whitespace after): dotted names like
|
|
2253
|
+
// _redpanda.transform_logs or URLs like redpanda.com/contact are not
|
|
2254
|
+
// sentence boundaries. Without this the sentence matcher below cannot
|
|
2255
|
+
// match the sentence containing the token, silently drops everything up
|
|
2256
|
+
// to the mid-token period, and emits the tail fragment as a "sentence"
|
|
2257
|
+
// ("View logs for a transform. transform_logs.").
|
|
2258
|
+
normalized = normalized.replace(/\.(?=\S)/g, '__MIDDOT__')
|
|
2259
|
+
|
|
2205
2260
|
// Match sentences
|
|
2206
2261
|
let sentences = normalized.match(/[^.!?]+[.!?]+(?:\s|$)/g)
|
|
2207
2262
|
|
|
@@ -2217,6 +2272,7 @@ function capToTwoSentences(desc) {
|
|
|
2217
2272
|
if (!sentences || sentences.length === 0) {
|
|
2218
2273
|
// Restore and return
|
|
2219
2274
|
let result = normalized.replace(/__DECIMAL__/g, '.')
|
|
2275
|
+
result = result.replace(/__MIDDOT__/g, '.')
|
|
2220
2276
|
placeholders.forEach(({ ph, original }) => {
|
|
2221
2277
|
result = result.replace(ph, original)
|
|
2222
2278
|
})
|
|
@@ -2230,6 +2286,7 @@ function capToTwoSentences(desc) {
|
|
|
2230
2286
|
|
|
2231
2287
|
// Restore decimal points
|
|
2232
2288
|
result = result.replace(/__DECIMAL__/g, '.')
|
|
2289
|
+
result = result.replace(/__MIDDOT__/g, '.')
|
|
2233
2290
|
|
|
2234
2291
|
// Restore abbreviations
|
|
2235
2292
|
placeholders.forEach(({ ph, original }) => {
|
|
@@ -2578,6 +2635,9 @@ async function generateRpkDocs(options = {}) {
|
|
|
2578
2635
|
// Flatten command tree
|
|
2579
2636
|
const commands = flattenCommands(tree)
|
|
2580
2637
|
|
|
2638
|
+
// Let formatDescription wrap real multi-word command paths as a unit
|
|
2639
|
+
registerKnownCommandPaths(commands.map(c => c.path))
|
|
2640
|
+
|
|
2581
2641
|
// Determine which managed plugins are protected this run: explicitly
|
|
2582
2642
|
// passed by the caller (failed installs) merged with auto-detection of
|
|
2583
2643
|
// absent or shim-only plugin subtrees. Protection covers the whole
|
|
@@ -3000,6 +3060,21 @@ async function generateRpkDocs(options = {}) {
|
|
|
3000
3060
|
}
|
|
3001
3061
|
|
|
3002
3062
|
try {
|
|
3063
|
+
// A page that HAD a Flags section and loses it in this render is the
|
|
3064
|
+
// signature of missing flag data (a snapshot without extraction, or a
|
|
3065
|
+
// curated table removed before the extracted one exists). Nothing else
|
|
3066
|
+
// catches this class: the run exits 0 with a clean log while the
|
|
3067
|
+
// published page silently drops its flag documentation (seen on
|
|
3068
|
+
// rpk connect run).
|
|
3069
|
+
if (fs.existsSync(filePath)) {
|
|
3070
|
+
const previous = fs.readFileSync(filePath, 'utf8')
|
|
3071
|
+
if (/^== Flags$/m.test(previous) && !/^== Flags$/m.test(content)) {
|
|
3072
|
+
console.warn(
|
|
3073
|
+
`⚠️ ${commandPath}: the previously rendered page had a Flags section and this render has none. ` +
|
|
3074
|
+
'The command likely lost its flag data (snapshot without extraction, or a curated Flags override removed too early).'
|
|
3075
|
+
)
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3003
3078
|
fs.writeFileSync(filePath, content, 'utf8')
|
|
3004
3079
|
writtenFiles.add(filePath)
|
|
3005
3080
|
filesGenerated++
|
|
@@ -3121,5 +3196,6 @@ module.exports = {
|
|
|
3121
3196
|
// Exported for testing
|
|
3122
3197
|
filterExamples,
|
|
3123
3198
|
formatExamples,
|
|
3124
|
-
applyTextTransformationsToExamples
|
|
3199
|
+
applyTextTransformationsToExamples,
|
|
3200
|
+
registerKnownCommandPaths
|
|
3125
3201
|
}
|
|
@@ -894,7 +894,7 @@ function loadVersionedJson(version, dataDir) {
|
|
|
894
894
|
* version (the page note renders "introduced in <plugin> version X"), not
|
|
895
895
|
* the rpk version, because plugins release on their own cadence.
|
|
896
896
|
*/
|
|
897
|
-
function updateOverridesWithIntroducedVersions(diffData, overridesPath, version, pluginVersions = {}) {
|
|
897
|
+
function updateOverridesWithIntroducedVersions(diffData, overridesPath, version, pluginVersions = {}, options = {}) {
|
|
898
898
|
const hasNewCommands = diffData.details.newCommands && diffData.details.newCommands.length > 0
|
|
899
899
|
const hasNewFlags = diffData.details.newFlags && diffData.details.newFlags.length > 0
|
|
900
900
|
|
|
@@ -908,6 +908,23 @@ function updateOverridesWithIntroducedVersions(diffData, overridesPath, version,
|
|
|
908
908
|
return pluginVersions[topLevel] || version
|
|
909
909
|
}
|
|
910
910
|
|
|
911
|
+
// Plugin-owned entries are only stamped when the caller vouches that the
|
|
912
|
+
// baseline is manifest-adjacent to this run's plugin version (see
|
|
913
|
+
// isPluginStampAttributable). "New relative to the snapshot" is not "new
|
|
914
|
+
// in this release" when the snapshot skipped releases. Callers that omit
|
|
915
|
+
// attributablePlugins keep legacy stamp-everything behavior.
|
|
916
|
+
const attributable = options.attributablePlugins
|
|
917
|
+
? new Set(options.attributablePlugins)
|
|
918
|
+
: null
|
|
919
|
+
const skippedByPlugin = new Map()
|
|
920
|
+
const stampable = (cmdPath) => {
|
|
921
|
+
const topLevel = cmdPath.split(' ')[1]
|
|
922
|
+
if (!KNOWN_PLUGINS.includes(topLevel) || !attributable) return true
|
|
923
|
+
if (attributable.has(topLevel)) return true
|
|
924
|
+
skippedByPlugin.set(topLevel, (skippedByPlugin.get(topLevel) || 0) + 1)
|
|
925
|
+
return false
|
|
926
|
+
}
|
|
927
|
+
|
|
911
928
|
let overrides = {}
|
|
912
929
|
if (fs.existsSync(overridesPath)) {
|
|
913
930
|
try {
|
|
@@ -929,6 +946,7 @@ function updateOverridesWithIntroducedVersions(diffData, overridesPath, version,
|
|
|
929
946
|
if (hasNewCommands) {
|
|
930
947
|
for (const newCmd of diffData.details.newCommands) {
|
|
931
948
|
const cmdPath = newCmd.path
|
|
949
|
+
if (!stampable(cmdPath)) continue
|
|
932
950
|
if (!overrides.commands[cmdPath]) {
|
|
933
951
|
overrides.commands[cmdPath] = {}
|
|
934
952
|
}
|
|
@@ -945,6 +963,7 @@ function updateOverridesWithIntroducedVersions(diffData, overridesPath, version,
|
|
|
945
963
|
for (const newFlag of diffData.details.newFlags) {
|
|
946
964
|
const cmdPath = newFlag.commandPath
|
|
947
965
|
const flagName = newFlag.flagName
|
|
966
|
+
if (!stampable(cmdPath)) continue
|
|
948
967
|
|
|
949
968
|
if (!overrides.commands[cmdPath]) {
|
|
950
969
|
overrides.commands[cmdPath] = {}
|
|
@@ -964,6 +983,15 @@ function updateOverridesWithIntroducedVersions(diffData, overridesPath, version,
|
|
|
964
983
|
}
|
|
965
984
|
}
|
|
966
985
|
|
|
986
|
+
for (const [plugin, count] of skippedByPlugin) {
|
|
987
|
+
console.warn(
|
|
988
|
+
`\u26a0 Skipped stamping introducedInVersion for ${count} new '${plugin}' entr${count === 1 ? 'y' : 'ies'}: ` +
|
|
989
|
+
`the baseline snapshot's ${plugin} version is not the release immediately before ` +
|
|
990
|
+
`${pluginVersions[plugin] || version} in the plugin manifest, so the introduction ` +
|
|
991
|
+
'version cannot be attributed automatically. Attribute manually against released binaries.'
|
|
992
|
+
)
|
|
993
|
+
}
|
|
994
|
+
|
|
967
995
|
if (commandsUpdated > 0 || flagsUpdated > 0) {
|
|
968
996
|
fs.writeFileSync(overridesPath, JSON.stringify(overrides, null, 2), 'utf8')
|
|
969
997
|
const updates = []
|
|
@@ -1444,6 +1472,75 @@ function fetchLatestPluginVersion(plugin) {
|
|
|
1444
1472
|
}
|
|
1445
1473
|
}
|
|
1446
1474
|
|
|
1475
|
+
/**
|
|
1476
|
+
* Fetch the ordered list of published versions for a plugin from its
|
|
1477
|
+
* manifest. Cached per plugin for the process lifetime.
|
|
1478
|
+
* @param {string} plugin - rpk command name (ai, connect, k8s, check)
|
|
1479
|
+
* @returns {string[]|null} Versions in manifest (release) order, or null
|
|
1480
|
+
*/
|
|
1481
|
+
const pluginManifestVersionsCache = new Map()
|
|
1482
|
+
function fetchPluginManifestVersions(plugin) {
|
|
1483
|
+
if (pluginManifestVersionsCache.has(plugin)) {
|
|
1484
|
+
return pluginManifestVersionsCache.get(plugin)
|
|
1485
|
+
}
|
|
1486
|
+
const slug = PLUGIN_MANIFEST_SLUGS[plugin] || plugin
|
|
1487
|
+
const result = spawnSync('curl', [
|
|
1488
|
+
'-fsSL', '--retry', '3', '--connect-timeout', '15', '--max-time', '30',
|
|
1489
|
+
`${PLUGIN_MANIFEST_HOST}/${slug}/manifest.json`
|
|
1490
|
+
], { encoding: 'utf8', timeout: 60000 })
|
|
1491
|
+
let versions = null
|
|
1492
|
+
if (result.status === 0) {
|
|
1493
|
+
try {
|
|
1494
|
+
versions = (JSON.parse(result.stdout).archives || []).map(a => a.version)
|
|
1495
|
+
} catch (err) {
|
|
1496
|
+
console.warn(`Could not parse plugin manifest for ${plugin}: ${err.message}`)
|
|
1497
|
+
}
|
|
1498
|
+
} else {
|
|
1499
|
+
console.warn(`Could not fetch plugin manifest for ${plugin} (slug: ${slug})`)
|
|
1500
|
+
}
|
|
1501
|
+
pluginManifestVersionsCache.set(plugin, versions)
|
|
1502
|
+
return versions
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
/**
|
|
1506
|
+
* Decide whether new commands under a plugin can truthfully be attributed
|
|
1507
|
+
* to newVersion: only when the baseline snapshot recorded the plugin
|
|
1508
|
+
* version AND that version is the release immediately before newVersion in
|
|
1509
|
+
* the plugin's manifest. Any gap means a "new" command may have shipped in
|
|
1510
|
+
* an intermediate release, so a stamp would fabricate history (30 rpk ai
|
|
1511
|
+
* commands were once labeled 0.2.32 when they shipped in 0.2.26/0.2.28).
|
|
1512
|
+
* @param {string} plugin - rpk command name
|
|
1513
|
+
* @param {string|undefined} oldVersion - Plugin version recorded in the
|
|
1514
|
+
* baseline snapshot's plugin_versions, if any
|
|
1515
|
+
* @param {string} newVersion - Plugin version in this run
|
|
1516
|
+
* @returns {boolean}
|
|
1517
|
+
*/
|
|
1518
|
+
function isPluginStampAttributable(plugin, oldVersion, newVersion) {
|
|
1519
|
+
if (!oldVersion || !newVersion) return false
|
|
1520
|
+
if (oldVersion === newVersion) return true
|
|
1521
|
+
const versions = fetchPluginManifestVersions(plugin)
|
|
1522
|
+
if (!versions) return false
|
|
1523
|
+
const oldIdx = versions.indexOf(oldVersion)
|
|
1524
|
+
const newIdx = versions.indexOf(newVersion)
|
|
1525
|
+
return oldIdx !== -1 && newIdx === oldIdx + 1
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
/**
|
|
1529
|
+
* Compute the set of plugins whose new commands may be stamped this run.
|
|
1530
|
+
* @param {Object} oldPluginVersions - plugin_versions from the baseline snapshot
|
|
1531
|
+
* @param {Object} newPluginVersions - plugin_versions for this run
|
|
1532
|
+
* @returns {Set<string>}
|
|
1533
|
+
*/
|
|
1534
|
+
function attributablePluginSet(oldPluginVersions = {}, newPluginVersions = {}) {
|
|
1535
|
+
const attributable = new Set()
|
|
1536
|
+
for (const plugin of Object.keys(newPluginVersions)) {
|
|
1537
|
+
if (isPluginStampAttributable(plugin, oldPluginVersions[plugin], newPluginVersions[plugin])) {
|
|
1538
|
+
attributable.add(plugin)
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
return attributable
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1447
1544
|
/**
|
|
1448
1545
|
* Check whether a plugin node contains real plugin commands, as opposed to
|
|
1449
1546
|
* only the install/uninstall/upgrade shim compiled into rpk itself.
|
|
@@ -1952,7 +2049,12 @@ async function handleRpkDocsGeneration(options = {}) {
|
|
|
1952
2049
|
pluginDiffData,
|
|
1953
2050
|
overridesPath || path.join(dataDir, 'rpk-overrides.json'),
|
|
1954
2051
|
resolvedVersion,
|
|
1955
|
-
{ [plugin]: resolvedVersion }
|
|
2052
|
+
{ [plugin]: resolvedVersion },
|
|
2053
|
+
{
|
|
2054
|
+
attributablePlugins: isPluginStampAttributable(
|
|
2055
|
+
plugin, (jsonData.plugin_versions || {})[plugin], resolvedVersion
|
|
2056
|
+
) ? [plugin] : []
|
|
2057
|
+
}
|
|
1956
2058
|
)
|
|
1957
2059
|
}
|
|
1958
2060
|
|
|
@@ -1996,7 +2098,9 @@ async function handleRpkDocsGeneration(options = {}) {
|
|
|
1996
2098
|
newDeprecatedCommands: jsonData.deprecated_commands || {}
|
|
1997
2099
|
})
|
|
1998
2100
|
if (stampDiff.details.newCommands.length > 0 || stampDiff.details.newFlags.length > 0) {
|
|
1999
|
-
updateOverridesWithIntroducedVersions(stampDiff, effectiveOverridesPath, rpkVersion, pluginVersions
|
|
2101
|
+
updateOverridesWithIntroducedVersions(stampDiff, effectiveOverridesPath, rpkVersion, pluginVersions, {
|
|
2102
|
+
attributablePlugins: attributablePluginSet(oldDataForStamp.plugin_versions, pluginVersions)
|
|
2103
|
+
})
|
|
2000
2104
|
}
|
|
2001
2105
|
}
|
|
2002
2106
|
}
|
|
@@ -2400,7 +2504,9 @@ async function handleRpkDocsGeneration(options = {}) {
|
|
|
2400
2504
|
// cadence, so the rpk version would be wrong and the page note would
|
|
2401
2505
|
// render "introduced in <plugin> version <rpk version>").
|
|
2402
2506
|
if ((diffData.details.newCommands.length > 0 || diffData.details.newFlags.length > 0) && effectiveOverridesPath) {
|
|
2403
|
-
updateOverridesWithIntroducedVersions(diffData, effectiveOverridesPath, rpkVersion, pluginVersions
|
|
2507
|
+
updateOverridesWithIntroducedVersions(diffData, effectiveOverridesPath, rpkVersion, pluginVersions, {
|
|
2508
|
+
attributablePlugins: attributablePluginSet(oldData.plugin_versions, pluginVersions)
|
|
2509
|
+
})
|
|
2404
2510
|
}
|
|
2405
2511
|
|
|
2406
2512
|
// Update what's-new file if requested
|
|
@@ -2819,6 +2925,11 @@ module.exports = {
|
|
|
2819
2925
|
getPlatformDescription,
|
|
2820
2926
|
getCurrentPlatform,
|
|
2821
2927
|
updateOverridesWithIntroducedVersions,
|
|
2928
|
+
isPluginStampAttributable,
|
|
2929
|
+
attributablePluginSet,
|
|
2930
|
+
fetchPluginManifestVersions,
|
|
2931
|
+
// Exported for tests to seed manifest data without network access
|
|
2932
|
+
pluginManifestVersionsCache,
|
|
2822
2933
|
filterDiffForWhatsNew,
|
|
2823
2934
|
computeDescriptionCoverage,
|
|
2824
2935
|
updateWhatsNewFile,
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
{{!--
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
{{/if}}
|
|
1
|
+
{{!-- cloudOnly/selfHostedOnly deliberately do NOT wrap the page. The
|
|
2
|
+
gating that matters happens inside the single-source tag region
|
|
3
|
+
(subcommand rows, flags, content items): includes only extract that
|
|
4
|
+
region, and the docs component never builds with env-cloud set, so a
|
|
5
|
+
page-level conditional is dead code at best and an empty, untitled
|
|
6
|
+
page at worst. Availability pages are excluded from cloud by not
|
|
7
|
+
generating a stub for them in cloud-docs. --}}
|
|
9
8
|
= {{commandPath}}
|
|
10
9
|
:description: {{{wrapDescriptionPassthrough shortDesc}}}
|
|
11
10
|
{{#if pageAliases}}
|
|
@@ -583,11 +582,3 @@ include::{{{this}}}[]
|
|
|
583
582
|
{{> content-item this commandPath=../commandPath}}
|
|
584
583
|
{{/each}}
|
|
585
584
|
// end::single-source[]
|
|
586
|
-
{{!-- Self-hosted-only wrapper end --}}
|
|
587
|
-
{{#if selfHostedOnly}}
|
|
588
|
-
endif::[]
|
|
589
|
-
{{/if}}
|
|
590
|
-
{{!-- Cloud-only wrapper end --}}
|
|
591
|
-
{{#if cloudOnly}}
|
|
592
|
-
endif::[]
|
|
593
|
-
{{/if}}
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
{{#if category}}
|
|
2
|
-
// tag::category-{{category}}[]
|
|
3
|
-
{{/if}}
|
|
4
|
-
{{#if exclude_from_docs}}
|
|
5
|
-
// tag::exclude-from-docs[]
|
|
6
|
-
{{/if}}
|
|
7
|
-
{{#if is_deprecated}}
|
|
8
|
-
// tag::deprecated[]
|
|
9
|
-
{{/if}}
|
|
10
|
-
{{#if cloud_supported}}
|
|
11
|
-
// tag::redpanda-cloud[]
|
|
12
|
-
{{/if}}
|
|
13
|
-
=== {{name}}
|
|
14
|
-
{{#if version}}
|
|
15
|
-
|
|
16
|
-
ifndef::env-cloud[]
|
|
17
|
-
*Introduced in {{version}}*
|
|
18
|
-
endif::[]
|
|
19
|
-
{{/if}}
|
|
20
|
-
{{#if description}}
|
|
21
|
-
|
|
22
|
-
{{{description}}}
|
|
23
|
-
{{else}}
|
|
24
|
-
|
|
25
|
-
No description available.
|
|
26
|
-
|
|
27
|
-
{{/if}}
|
|
28
|
-
{{#if is_enterprise}}
|
|
29
|
-
|
|
30
|
-
ifndef::env-cloud[]
|
|
31
|
-
*Enterprise license required*: `{{enterprise_value}}` (for license details, see xref:get-started:licensing/index.adoc[Redpanda Licensing])
|
|
32
|
-
endif::[]
|
|
33
|
-
{{/if}}
|
|
34
|
-
{{#if cloud_byoc_only}}
|
|
35
|
-
|
|
36
|
-
ifdef::env-cloud[]
|
|
37
|
-
NOTE: This property is available only in Redpanda Cloud BYOC deployments.
|
|
38
|
-
endif::[]
|
|
39
|
-
|
|
40
|
-
{{/if}}
|
|
41
|
-
{{#if units}}
|
|
42
|
-
|
|
43
|
-
*Unit:* {{units}}
|
|
44
|
-
{{else}}
|
|
45
|
-
{{#if (formatUnits name)}}
|
|
46
|
-
|
|
47
|
-
*Unit:* {{formatUnits name}}
|
|
48
|
-
{{/if}}
|
|
49
|
-
{{/if}}
|
|
50
|
-
{{#if (ne defined_in "src/v/config/node_config.cc")}}
|
|
51
|
-
{{#if (ne needs_restart undefined)}}
|
|
52
|
-
|
|
53
|
-
*Requires restart:* {{#if needs_restart}}Yes{{else}}No{{/if}}
|
|
54
|
-
{{/if}}
|
|
55
|
-
{{/if}}
|
|
56
|
-
|
|
57
|
-
ifndef::env-cloud[]
|
|
58
|
-
*Restored during xref:manage:whole-cluster-restore.adoc[Whole Cluster Restore]:* {{#if (ne gets_restored false)}}Yes{{else}}No{{/if}}
|
|
59
|
-
endif::[]
|
|
60
|
-
{{#if visibility}}
|
|
61
|
-
|
|
62
|
-
// tag::self-managed-only[]
|
|
63
|
-
*Visibility:* `{{visibility}}`
|
|
64
|
-
// end::self-managed-only[]
|
|
65
|
-
{{/if}}
|
|
66
|
-
{{#if type}}
|
|
67
|
-
|
|
68
|
-
*Type:* {{type}}
|
|
69
|
-
{{/if}}
|
|
70
|
-
{{#if (and minimum maximum)}}
|
|
71
|
-
|
|
72
|
-
*Accepted values:* [`{{minimum}}`, `{{maximum}}`]
|
|
73
|
-
{{else}}
|
|
74
|
-
{{#if minimum}}
|
|
75
|
-
|
|
76
|
-
*Minimum value:* `{{minimum}}`
|
|
77
|
-
{{/if}}
|
|
78
|
-
{{#if maximum}}
|
|
79
|
-
|
|
80
|
-
*Maximum value:* `{{maximum}}`
|
|
81
|
-
{{/if}}
|
|
82
|
-
{{/if}}
|
|
83
|
-
{{#if (ne default undefined)}}
|
|
84
|
-
|
|
85
|
-
ifdef::env-cloud[]
|
|
86
|
-
*Default:* Available in the Redpanda Cloud Console
|
|
87
|
-
endif::[]
|
|
88
|
-
ifndef::env-cloud[]
|
|
89
|
-
*Default:* `{{formatPropertyValue default type}}`
|
|
90
|
-
endif::[]
|
|
91
|
-
{{/if}}
|
|
92
|
-
|
|
93
|
-
*Nullable:* {{#if nullable}}Yes{{else}}No{{/if}}
|
|
94
|
-
{{#if example}}
|
|
95
|
-
|
|
96
|
-
{{{renderPropertyExample this}}}
|
|
97
|
-
{{/if}}
|
|
98
|
-
{{#if related_topics}}
|
|
99
|
-
{{#with (allTopicsConditional related_topics) as |sectionType|}}
|
|
100
|
-
|
|
101
|
-
{{#if (eq sectionType "cloud")}}
|
|
102
|
-
ifdef::env-cloud[]
|
|
103
|
-
*Related topics:*
|
|
104
|
-
|
|
105
|
-
{{#each ../related_topics}}
|
|
106
|
-
{{#with (parseRelatedTopic this)}}
|
|
107
|
-
* {{{value}}}
|
|
108
|
-
{{/with}}
|
|
109
|
-
{{/each}}
|
|
110
|
-
endif::[]
|
|
111
|
-
{{else if (eq sectionType "self-managed")}}
|
|
112
|
-
ifndef::env-cloud[]
|
|
113
|
-
*Related topics:*
|
|
114
|
-
|
|
115
|
-
{{#each ../related_topics}}
|
|
116
|
-
{{#with (parseRelatedTopic this)}}
|
|
117
|
-
* {{{value}}}
|
|
118
|
-
{{/with}}
|
|
119
|
-
{{/each}}
|
|
120
|
-
endif::[]
|
|
121
|
-
{{else}}
|
|
122
|
-
*Related topics:*
|
|
123
|
-
|
|
124
|
-
{{#each ../related_topics}}
|
|
125
|
-
{{#with (parseRelatedTopic this)}}
|
|
126
|
-
{{#if (eq type "cloud")}}
|
|
127
|
-
ifdef::env-cloud[]
|
|
128
|
-
* {{{value}}}
|
|
129
|
-
endif::[]
|
|
130
|
-
{{else if (eq type "self-managed")}}
|
|
131
|
-
ifndef::env-cloud[]
|
|
132
|
-
* {{{value}}}
|
|
133
|
-
endif::[]
|
|
134
|
-
{{else}}
|
|
135
|
-
* {{{value}}}
|
|
136
|
-
{{/if}}
|
|
137
|
-
{{/with}}
|
|
138
|
-
{{/each}}
|
|
139
|
-
{{/if}}
|
|
140
|
-
{{/with}}
|
|
141
|
-
{{/if}}
|
|
142
|
-
{{#if aliases}}
|
|
143
|
-
|
|
144
|
-
// tag::self-managed-only[]
|
|
145
|
-
*Aliases:* {{join aliases ", "}}
|
|
146
|
-
// end::self-managed-only[]
|
|
147
|
-
{{/if}}
|
|
148
|
-
|
|
149
|
-
---
|
|
150
|
-
{{#if cloud_supported}}
|
|
151
|
-
// end::redpanda-cloud[]
|
|
152
|
-
{{/if}}
|
|
153
|
-
{{#if is_deprecated}}
|
|
154
|
-
// end::deprecated[]
|
|
155
|
-
{{/if}}
|
|
156
|
-
{{#if exclude_from_docs}}
|
|
157
|
-
// end::exclude-from-docs[]
|
|
158
|
-
{{/if}}
|
|
159
|
-
{{#if category}}
|
|
160
|
-
// end::category-{{category}}[]
|
|
161
|
-
{{/if}}
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
{{#if category}}
|
|
2
|
-
// tag::category-{{category}}[]
|
|
3
|
-
{{/if}}
|
|
4
|
-
{{#if exclude_from_docs}}
|
|
5
|
-
// tag::exclude-from-docs[]
|
|
6
|
-
{{/if}}
|
|
7
|
-
{{#if is_deprecated}}
|
|
8
|
-
// tag::deprecated[]
|
|
9
|
-
{{/if}}
|
|
10
|
-
{{#if cloud_supported}}
|
|
11
|
-
// tag::redpanda-cloud[]
|
|
12
|
-
{{/if}}
|
|
13
|
-
=== {{name}}
|
|
14
|
-
{{#if version}}
|
|
15
|
-
|
|
16
|
-
*Introduced in {{version}}*
|
|
17
|
-
{{/if}}
|
|
18
|
-
{{#if description}}
|
|
19
|
-
|
|
20
|
-
{{{description}}}
|
|
21
|
-
{{else}}
|
|
22
|
-
|
|
23
|
-
No description available.
|
|
24
|
-
{{/if}}
|
|
25
|
-
{{#if is_enterprise}}
|
|
26
|
-
|
|
27
|
-
ifndef::env-cloud[]
|
|
28
|
-
*Enterprise license required*: `{{enterprise_value}}` (for license details, see xref:get-started:licensing/index.adoc[Redpanda Licensing])
|
|
29
|
-
endif::[]
|
|
30
|
-
{{/if}}
|
|
31
|
-
{{#if cloud_byoc_only}}
|
|
32
|
-
ifdef::env-cloud[]
|
|
33
|
-
NOTE: This property is only available in Redpanda Cloud BYOC deployments.
|
|
34
|
-
endif::[]
|
|
35
|
-
{{/if}}
|
|
36
|
-
{{#if type}}
|
|
37
|
-
|
|
38
|
-
*Type:* {{type}}
|
|
39
|
-
{{/if}}
|
|
40
|
-
{{#if acceptable_values}}
|
|
41
|
-
|
|
42
|
-
*Accepted values:* {{{acceptable_values}}}
|
|
43
|
-
{{/if}}
|
|
44
|
-
{{#if corresponding_cluster_property}}
|
|
45
|
-
|
|
46
|
-
*Related cluster property:* xref:reference:cluster-properties.adoc#{{corresponding_cluster_property}}[{{corresponding_cluster_property}}]
|
|
47
|
-
{{/if}}
|
|
48
|
-
{{#if (and minimum maximum)}}
|
|
49
|
-
|
|
50
|
-
*Accepted values:* [`{{minimum}}`, `{{maximum}}`]
|
|
51
|
-
{{else}}
|
|
52
|
-
{{#if minimum}}
|
|
53
|
-
|
|
54
|
-
*Minimum value:* `{{minimum}}`
|
|
55
|
-
{{/if}}
|
|
56
|
-
{{#if maximum}}
|
|
57
|
-
|
|
58
|
-
*Maximum value:* `{{maximum}}`
|
|
59
|
-
{{/if}}
|
|
60
|
-
{{/if}}
|
|
61
|
-
{{#if (ne default undefined)}}
|
|
62
|
-
{{#if cloud_supported}}
|
|
63
|
-
|
|
64
|
-
ifdef::env-cloud[]
|
|
65
|
-
*Default:* Available in the Redpanda Cloud Console
|
|
66
|
-
endif::[]
|
|
67
|
-
ifndef::env-cloud[]
|
|
68
|
-
*Default:* `{{formatPropertyValue default type}}`
|
|
69
|
-
endif::[]
|
|
70
|
-
{{else}}
|
|
71
|
-
|
|
72
|
-
*Default:* `{{formatPropertyValue default type}}`
|
|
73
|
-
{{/if}}
|
|
74
|
-
{{/if}}
|
|
75
|
-
|
|
76
|
-
*Nullable:* {{#if nullable}}Yes{{else}}No{{/if}}
|
|
77
|
-
|
|
78
|
-
ifndef::env-cloud[]
|
|
79
|
-
*Restored during xref:manage:whole-cluster-restore.adoc[Whole Cluster Restore]:* {{#if (ne gets_restored false)}}Yes{{else}}No{{/if}}
|
|
80
|
-
endif::[]
|
|
81
|
-
{{#if example}}
|
|
82
|
-
|
|
83
|
-
{{{renderPropertyExample this}}}
|
|
84
|
-
{{/if}}
|
|
85
|
-
{{#if related_topics}}
|
|
86
|
-
{{#with (allTopicsConditional related_topics) as |sectionType|}}
|
|
87
|
-
|
|
88
|
-
{{#if (eq sectionType "cloud")}}
|
|
89
|
-
ifdef::env-cloud[]
|
|
90
|
-
*Related topics:*
|
|
91
|
-
|
|
92
|
-
{{#each ../related_topics}}
|
|
93
|
-
{{#with (parseRelatedTopic this)}}
|
|
94
|
-
* {{{value}}}
|
|
95
|
-
{{/with}}
|
|
96
|
-
{{/each}}
|
|
97
|
-
endif::[]
|
|
98
|
-
{{else if (eq sectionType "self-managed")}}
|
|
99
|
-
ifndef::env-cloud[]
|
|
100
|
-
*Related topics:*
|
|
101
|
-
|
|
102
|
-
{{#each ../related_topics}}
|
|
103
|
-
{{#with (parseRelatedTopic this)}}
|
|
104
|
-
* {{{value}}}
|
|
105
|
-
{{/with}}
|
|
106
|
-
{{/each}}
|
|
107
|
-
endif::[]
|
|
108
|
-
{{else}}
|
|
109
|
-
*Related topics:*
|
|
110
|
-
|
|
111
|
-
{{#each ../related_topics}}
|
|
112
|
-
{{#with (parseRelatedTopic this)}}
|
|
113
|
-
{{#if (eq type "cloud")}}
|
|
114
|
-
ifdef::env-cloud[]
|
|
115
|
-
* {{{value}}}
|
|
116
|
-
endif::[]
|
|
117
|
-
{{else if (eq type "self-managed")}}
|
|
118
|
-
ifndef::env-cloud[]
|
|
119
|
-
* {{{value}}}
|
|
120
|
-
endif::[]
|
|
121
|
-
{{else}}
|
|
122
|
-
* {{{value}}}
|
|
123
|
-
{{/if}}
|
|
124
|
-
{{/with}}
|
|
125
|
-
{{/each}}
|
|
126
|
-
{{/if}}
|
|
127
|
-
{{/with}}
|
|
128
|
-
{{/if}}
|
|
129
|
-
{{#if aliases}}
|
|
130
|
-
|
|
131
|
-
// tag::self-managed-only[]
|
|
132
|
-
*Aliases:* {{join aliases ", "}}
|
|
133
|
-
// end::self-managed-only[]
|
|
134
|
-
{{/if}}
|
|
135
|
-
|
|
136
|
-
---
|
|
137
|
-
{{#if cloud_supported}}
|
|
138
|
-
// end::redpanda-cloud[]
|
|
139
|
-
{{/if}}
|
|
140
|
-
{{#if is_deprecated}}
|
|
141
|
-
// end::deprecated[]
|
|
142
|
-
{{/if}}
|
|
143
|
-
{{#if exclude_from_docs}}
|
|
144
|
-
// end::exclude-from-docs[]
|
|
145
|
-
{{/if}}
|
|
146
|
-
{{#if category}}
|
|
147
|
-
// end::category-{{category}}[]
|
|
148
|
-
{{/if}}
|