@redpanda-data/docs-extensions-and-macros 5.3.0 → 5.3.2
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/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 +102 -7
- package/tools/rpk-docs/detect-platform-commands.js +566 -0
- package/tools/rpk-docs/generate-rpk-docs.js +33 -12
- package/tools/rpk-docs/rpk-docs-handler.js +105 -112
- package/tools/rpk-docs/templates/command.hbs +12 -0
- package/tools/rpk-docs/validate-overrides.js +16 -0
- 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,
|
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');
|
|
@@ -79,7 +79,7 @@ ENUM_PATTERN = re.compile(r'^[a-zA-Z0-9_:]+::([a-zA-Z0-9_]+)$') # Match full qu
|
|
|
79
79
|
CONSTRUCTOR_PATTERN = re.compile(r'([a-zA-Z0-9_:]+)\((.*)\)')
|
|
80
80
|
BRACED_CONSTRUCTOR_PATTERN = re.compile(r'([a-zA-Z0-9_:]+)\{(.*)\}')
|
|
81
81
|
DIGIT_SEPARATOR_PATTERN = re.compile(r"(?<=\d)'(?=\d)")
|
|
82
|
-
FUNCTION_CALL_PATTERN = re.compile(r'([a-zA-Z0-9_
|
|
82
|
+
FUNCTION_CALL_PATTERN = re.compile(r'([a-zA-Z0-9_:.]+)\(\)$')
|
|
83
83
|
CHRONO_PATTERN = re.compile(r'std::chrono::([a-zA-Z]+)\s*\{\s*(\d+)\s*\}')
|
|
84
84
|
CHRONO_PAREN_PATTERN = re.compile(r'(?:std::)?chrono::([a-zA-Z]+)\s*\(\s*([^)]+)\s*\)')
|
|
85
85
|
TIME_UNIT_PATTERN = re.compile(r'(\d+)\s*(min|s|ms|h)')
|
|
@@ -159,6 +159,15 @@ class ConstexprCache:
|
|
|
159
159
|
re.compile(r'const\s+model::ns\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(\s*"([^"]+)"\s*\)'),
|
|
160
160
|
]
|
|
161
161
|
|
|
162
|
+
# Member accessors on inline constants used in property defaults,
|
|
163
|
+
# for example model::schema_registry_internal_tp.topic() -> "_schemas".
|
|
164
|
+
# Each entry maps a variable declaration to the member name whose call
|
|
165
|
+
# returns the captured string, cached under "<variable>.<member>".
|
|
166
|
+
member_accessor_patterns = [
|
|
167
|
+
# Pattern: inline const model::topic_partition name{model::topic{"value"}, ...}
|
|
168
|
+
(re.compile(r'inline\s+const\s+(?:model::)?topic_partition\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\{\s*(?:model::)?topic\{\s*"([^"]+)"\s*\}'), 'topic'),
|
|
169
|
+
]
|
|
170
|
+
|
|
162
171
|
# Legacy specific patterns (kept for compatibility, but general patterns should cover these)
|
|
163
172
|
function_patterns = {}
|
|
164
173
|
|
|
@@ -206,6 +215,17 @@ class ConstexprCache:
|
|
|
206
215
|
qualified_name = f"{namespace}::{func_name}"
|
|
207
216
|
self.function_cache[qualified_name] = func_value
|
|
208
217
|
|
|
218
|
+
# Extract member accessors on inline constants
|
|
219
|
+
for pattern, member in member_accessor_patterns:
|
|
220
|
+
for match in pattern.finditer(content):
|
|
221
|
+
var_name = match.group(1)
|
|
222
|
+
value = match.group(2)
|
|
223
|
+
key = f"{var_name}.{member}"
|
|
224
|
+
namespace = self._extract_namespace_for_function(content, match.start())
|
|
225
|
+
self.function_cache[key] = value
|
|
226
|
+
if namespace:
|
|
227
|
+
self.function_cache[f"{namespace}::{key}"] = value
|
|
228
|
+
|
|
209
229
|
# Legacy: Extract function definitions from hardcoded patterns (if any)
|
|
210
230
|
for func_name, patterns in function_patterns.items():
|
|
211
231
|
for pattern in patterns:
|
|
@@ -301,16 +321,35 @@ _constexpr_cache = ConstexprCache()
|
|
|
301
321
|
_type_definitions_cache = {}
|
|
302
322
|
|
|
303
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
|
+
|
|
304
336
|
def find_redpanda_source():
|
|
305
337
|
"""
|
|
306
|
-
Locate the Redpanda source directory
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
+
|
|
311
347
|
Returns:
|
|
312
348
|
str or None: Path to the Redpanda source directory if found, None otherwise.
|
|
313
349
|
"""
|
|
350
|
+
if _redpanda_source_override and os.path.exists(_redpanda_source_override):
|
|
351
|
+
return _redpanda_source_override
|
|
352
|
+
|
|
314
353
|
redpanda_source_paths = [
|
|
315
354
|
'tmp/redpanda', # Current directory
|
|
316
355
|
'../tmp/redpanda', # Parent directory
|
|
@@ -705,6 +744,13 @@ def transform_files_with_properties(files_with_properties):
|
|
|
705
744
|
if src_v_path.exists():
|
|
706
745
|
constant_resolver = ConstantResolver(src_v_path)
|
|
707
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
|
+
)
|
|
708
754
|
|
|
709
755
|
transformers = [
|
|
710
756
|
EnterpriseTransformer(), ## this must be the first, as it modifies current data
|
|
@@ -922,6 +968,32 @@ def merge_properties_and_definitions(properties, definitions):
|
|
|
922
968
|
return dict(properties=properties, definitions=definitions)
|
|
923
969
|
|
|
924
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
|
+
|
|
925
997
|
def apply_property_overrides(properties, overrides, overrides_file_path=None):
|
|
926
998
|
"""
|
|
927
999
|
Apply overrides from an overrides mapping to the extracted properties, mutating and returning the properties dictionary.
|
|
@@ -941,6 +1013,7 @@ def apply_property_overrides(properties, overrides, overrides_file_path=None):
|
|
|
941
1013
|
Returns:
|
|
942
1014
|
dict: The same properties mapping with overrides applied and any new properties created.
|
|
943
1015
|
"""
|
|
1016
|
+
phantom_stub_entries.clear()
|
|
944
1017
|
if overrides and "properties" in overrides:
|
|
945
1018
|
for prop, override in overrides["properties"].items():
|
|
946
1019
|
# First check if property exists by key
|
|
@@ -962,7 +1035,13 @@ def apply_property_overrides(properties, overrides, overrides_file_path=None):
|
|
|
962
1035
|
else:
|
|
963
1036
|
# Create new property from override
|
|
964
1037
|
logger.info(f"Creating new property from override: {prop}")
|
|
965
|
-
|
|
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
|
+
})
|
|
966
1045
|
return properties
|
|
967
1046
|
|
|
968
1047
|
|
|
@@ -2856,6 +2935,11 @@ def main():
|
|
|
2856
2935
|
treesitter_dir, destination_path
|
|
2857
2936
|
)
|
|
2858
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
|
+
|
|
2859
2943
|
# Pre-build constexpr cache for performance
|
|
2860
2944
|
# This avoids repeated filesystem walks when resolving C++ identifiers and function calls
|
|
2861
2945
|
logger.info("🔧 Building constexpr identifier cache...")
|
|
@@ -3053,5 +3137,16 @@ def main():
|
|
|
3053
3137
|
logging.error(f"Failed to write enhanced output file: {e}")
|
|
3054
3138
|
sys.exit(1)
|
|
3055
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
|
+
|
|
3056
3151
|
if __name__ == "__main__":
|
|
3057
3152
|
main()
|