@redpanda-data/docs-extensions-and-macros 4.8.1 → 4.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/doc-tools.js +334 -35
- package/package.json +1 -1
- package/tools/property-extractor/Makefile +8 -18
- package/tools/property-extractor/cloud_config.py +594 -0
- package/tools/property-extractor/compare-properties.js +378 -0
- package/tools/property-extractor/generate-handlebars-docs.js +132 -32
- package/tools/property-extractor/parser.py +27 -1
- package/tools/property-extractor/property_extractor.py +335 -99
- package/tools/property-extractor/requirements.txt +1 -0
- package/tools/property-extractor/templates/property-cloud.hbs +105 -0
- package/tools/property-extractor/templates/property.hbs +16 -1
- package/tools/property-extractor/templates/topic-property-cloud.hbs +97 -0
- package/tools/property-extractor/templates/topic-property.hbs +26 -12
- package/tools/property-extractor/transformers.py +98 -2
- package/tools/redpanda-connect/generate-rpcn-connector-docs.js +96 -2
- package/tools/redpanda-connect/helpers/bloblangExample.js +42 -0
- package/tools/redpanda-connect/helpers/index.js +4 -3
- package/tools/redpanda-connect/helpers/renderConnectFields.js +32 -5
- package/tools/redpanda-connect/report-delta.js +101 -0
- package/tools/redpanda-connect/templates/bloblang-function.hbs +28 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Property Comparison Tool
|
|
5
|
+
*
|
|
6
|
+
* Compares two property JSON files and generates a detailed report of:
|
|
7
|
+
* - New properties added
|
|
8
|
+
* - Properties with changed defaults
|
|
9
|
+
* - Properties with changed descriptions
|
|
10
|
+
* - Properties with changed types
|
|
11
|
+
* - Deprecated properties
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Recursively compares two values for structural deep equality.
|
|
19
|
+
*
|
|
20
|
+
* - Returns true if values are strictly equal (`===`).
|
|
21
|
+
* - Returns false if types differ or either is `null`/`undefined` while the other is not.
|
|
22
|
+
* - Arrays: ensures same length and recursively compares each element.
|
|
23
|
+
* - Objects: compares own enumerable keys (order-insensitive) and recursively compares corresponding values.
|
|
24
|
+
*
|
|
25
|
+
* @param {*} a - First value to compare.
|
|
26
|
+
* @param {*} b - Second value to compare.
|
|
27
|
+
* @returns {boolean} True if the two values are deeply equal.
|
|
28
|
+
*/
|
|
29
|
+
function deepEqual(a, b) {
|
|
30
|
+
if (a === b) return true;
|
|
31
|
+
if (a == null || b == null) return false;
|
|
32
|
+
if (typeof a !== typeof b) return false;
|
|
33
|
+
|
|
34
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
35
|
+
if (a.length !== b.length) return false;
|
|
36
|
+
return a.every((val, i) => deepEqual(val, b[i]));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (typeof a === 'object' && typeof b === 'object') {
|
|
40
|
+
const keysA = Object.keys(a);
|
|
41
|
+
const keysB = Object.keys(b);
|
|
42
|
+
if (keysA.length !== keysB.length) return false;
|
|
43
|
+
return keysA.every(key => keysB.includes(key) && deepEqual(a[key], b[key]));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Format a value for concise human-readable display in comparison reports.
|
|
51
|
+
*
|
|
52
|
+
* Converts various JavaScript values into short string representations used
|
|
53
|
+
* in the report output:
|
|
54
|
+
* - null/undefined → `'null'`
|
|
55
|
+
* - Array:
|
|
56
|
+
* - empty → `'[]'`
|
|
57
|
+
* - single item → `[<formatted item>]` (recursively formatted)
|
|
58
|
+
* - multiple items → `'[<n> items]'`
|
|
59
|
+
* - Object → JSON string via `JSON.stringify`
|
|
60
|
+
* - String → quoted; truncated with `...` if longer than 50 characters
|
|
61
|
+
* - Other primitives → `String(value)`
|
|
62
|
+
*
|
|
63
|
+
* @param {*} value - The value to format for display.
|
|
64
|
+
* @return {string} A concise string suitable for report output.
|
|
65
|
+
*/
|
|
66
|
+
function formatValue(value) {
|
|
67
|
+
if (value === null || value === undefined) {
|
|
68
|
+
return 'null';
|
|
69
|
+
}
|
|
70
|
+
if (Array.isArray(value)) {
|
|
71
|
+
if (value.length === 0) return '[]';
|
|
72
|
+
if (value.length === 1) return `[${formatValue(value[0])}]`;
|
|
73
|
+
return `[${value.length} items]`;
|
|
74
|
+
}
|
|
75
|
+
if (typeof value === 'object') {
|
|
76
|
+
return JSON.stringify(value);
|
|
77
|
+
}
|
|
78
|
+
if (typeof value === 'string') {
|
|
79
|
+
return value.length > 50 ? `"${value.substring(0, 50)}..."` : `"${value}"`;
|
|
80
|
+
}
|
|
81
|
+
return String(value);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Extracts a flat map of property definitions from a parsed JSON schema or similar object.
|
|
86
|
+
*
|
|
87
|
+
* If the input contains a top-level `properties` object, that object is returned directly.
|
|
88
|
+
* Otherwise, the function scans the root keys (excluding `definitions`) and returns any
|
|
89
|
+
* entries that look like property definitions (an object with at least one of `type`,
|
|
90
|
+
* `description`, or `default`).
|
|
91
|
+
*
|
|
92
|
+
* @param {Object} data - Parsed JSON data to scan for property definitions.
|
|
93
|
+
* @returns {Object} A map of property definitions (key → property object). Returns an empty object if none found.
|
|
94
|
+
*/
|
|
95
|
+
function extractProperties(data) {
|
|
96
|
+
// Properties are nested under a 'properties' key in the JSON structure
|
|
97
|
+
if (data.properties && typeof data.properties === 'object') {
|
|
98
|
+
return data.properties;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Fallback: look for properties at root level
|
|
102
|
+
const properties = {};
|
|
103
|
+
for (const [key, value] of Object.entries(data)) {
|
|
104
|
+
if (key !== 'definitions' && typeof value === 'object' && value !== null) {
|
|
105
|
+
// Check if this looks like a property definition
|
|
106
|
+
if (value.hasOwnProperty('type') || value.hasOwnProperty('description') || value.hasOwnProperty('default')) {
|
|
107
|
+
properties[key] = value;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return properties;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Compare two property JSON structures and produce a detailed change report.
|
|
117
|
+
*
|
|
118
|
+
* Compares properties extracted from oldData and newData and classifies differences
|
|
119
|
+
* into newProperties, changedDefaults, changedDescriptions, changedTypes,
|
|
120
|
+
* deprecatedProperties (newly deprecated in newData), and removedProperties.
|
|
121
|
+
* Description fields in the report are truncated for brevity; default equality
|
|
122
|
+
* is determined by a deep structural comparison.
|
|
123
|
+
*
|
|
124
|
+
* @param {Object} oldData - Parsed JSON of the older property file.
|
|
125
|
+
* @param {Object} newData - Parsed JSON of the newer property file.
|
|
126
|
+
* @param {string} oldVersion - Version string corresponding to oldData.
|
|
127
|
+
* @param {string} newVersion - Version string corresponding to newData.
|
|
128
|
+
* @return {Object} Report object with arrays: newProperties, changedDefaults,
|
|
129
|
+
* changedDescriptions, changedTypes, deprecatedProperties, removedProperties.
|
|
130
|
+
*/
|
|
131
|
+
function compareProperties(oldData, newData, oldVersion, newVersion) {
|
|
132
|
+
const oldProps = extractProperties(oldData);
|
|
133
|
+
const newProps = extractProperties(newData);
|
|
134
|
+
|
|
135
|
+
const report = {
|
|
136
|
+
newProperties: [],
|
|
137
|
+
changedDefaults: [],
|
|
138
|
+
changedDescriptions: [],
|
|
139
|
+
changedTypes: [],
|
|
140
|
+
deprecatedProperties: [],
|
|
141
|
+
removedProperties: []
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// Find new properties
|
|
145
|
+
for (const [name, prop] of Object.entries(newProps)) {
|
|
146
|
+
if (!oldProps.hasOwnProperty(name)) {
|
|
147
|
+
report.newProperties.push({
|
|
148
|
+
name,
|
|
149
|
+
type: prop.type,
|
|
150
|
+
default: prop.default,
|
|
151
|
+
description: prop.description ? prop.description.substring(0, 100) + '...' : 'No description'
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Find changed properties
|
|
157
|
+
for (const [name, oldProp] of Object.entries(oldProps)) {
|
|
158
|
+
if (newProps.hasOwnProperty(name)) {
|
|
159
|
+
const newProp = newProps[name];
|
|
160
|
+
|
|
161
|
+
// Check for deprecation first (using is_deprecated field only)
|
|
162
|
+
const isNewlyDeprecated = newProp.is_deprecated === true &&
|
|
163
|
+
oldProp.is_deprecated !== true;
|
|
164
|
+
|
|
165
|
+
if (isNewlyDeprecated) {
|
|
166
|
+
report.deprecatedProperties.push({
|
|
167
|
+
name,
|
|
168
|
+
reason: newProp.deprecatedReason || 'Property marked as deprecated'
|
|
169
|
+
});
|
|
170
|
+
// Skip other change detection for deprecated properties
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Only check other changes if property is not newly deprecated
|
|
175
|
+
// Check for default value changes
|
|
176
|
+
if (!deepEqual(oldProp.default, newProp.default)) {
|
|
177
|
+
report.changedDefaults.push({
|
|
178
|
+
name,
|
|
179
|
+
oldDefault: oldProp.default,
|
|
180
|
+
newDefault: newProp.default
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Check for description changes
|
|
185
|
+
if (oldProp.description !== newProp.description) {
|
|
186
|
+
report.changedDescriptions.push({
|
|
187
|
+
name,
|
|
188
|
+
oldDescription: oldProp.description ? oldProp.description.substring(0, 50) + '...' : 'No description',
|
|
189
|
+
newDescription: newProp.description ? newProp.description.substring(0, 50) + '...' : 'No description'
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Check for type changes
|
|
194
|
+
if (oldProp.type !== newProp.type) {
|
|
195
|
+
report.changedTypes.push({
|
|
196
|
+
name,
|
|
197
|
+
oldType: oldProp.type,
|
|
198
|
+
newType: newProp.type
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
// Property was removed
|
|
203
|
+
report.removedProperties.push({
|
|
204
|
+
name,
|
|
205
|
+
type: oldProp.type,
|
|
206
|
+
description: oldProp.description ? oldProp.description.substring(0, 100) + '...' : 'No description'
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return report;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Print a human-readable console report summarizing property differences between two versions.
|
|
216
|
+
*
|
|
217
|
+
* The report includes sections for new properties, properties with changed defaults,
|
|
218
|
+
* changed types, updated descriptions, newly deprecated properties (with reason), and removed properties.
|
|
219
|
+
*
|
|
220
|
+
* @param {Object} report - Comparison report object returned by compareProperties().
|
|
221
|
+
* @param {string} oldVersion - Label for the old version (displayed as the "from" version).
|
|
222
|
+
* @param {string} newVersion - Label for the new version (displayed as the "to" version).
|
|
223
|
+
*/
|
|
224
|
+
function generateConsoleReport(report, oldVersion, newVersion) {
|
|
225
|
+
console.log('\n' + '='.repeat(60));
|
|
226
|
+
console.log(`📋 Property Changes Report (${oldVersion} → ${newVersion})`);
|
|
227
|
+
console.log('='.repeat(60));
|
|
228
|
+
|
|
229
|
+
if (report.newProperties.length > 0) {
|
|
230
|
+
console.log(`\n➤ New properties (${report.newProperties.length}):`);
|
|
231
|
+
report.newProperties.forEach(prop => {
|
|
232
|
+
console.log(` • ${prop.name} (${prop.type}) — default: ${formatValue(prop.default)}`);
|
|
233
|
+
});
|
|
234
|
+
} else {
|
|
235
|
+
console.log('\n➤ No new properties.');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (report.changedDefaults.length > 0) {
|
|
239
|
+
console.log(`\n➤ Properties with changed defaults (${report.changedDefaults.length}):`);
|
|
240
|
+
report.changedDefaults.forEach(prop => {
|
|
241
|
+
console.log(` • ${prop.name}:`);
|
|
242
|
+
console.log(` - Old: ${formatValue(prop.oldDefault)}`);
|
|
243
|
+
console.log(` - New: ${formatValue(prop.newDefault)}`);
|
|
244
|
+
});
|
|
245
|
+
} else {
|
|
246
|
+
console.log('\n➤ No default value changes.');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (report.changedTypes.length > 0) {
|
|
250
|
+
console.log(`\n➤ Properties with changed types (${report.changedTypes.length}):`);
|
|
251
|
+
report.changedTypes.forEach(prop => {
|
|
252
|
+
console.log(` • ${prop.name}: ${prop.oldType} → ${prop.newType}`);
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (report.changedDescriptions.length > 0) {
|
|
257
|
+
console.log(`\n➤ Properties with updated descriptions (${report.changedDescriptions.length}):`);
|
|
258
|
+
report.changedDescriptions.forEach(prop => {
|
|
259
|
+
console.log(` • ${prop.name} — description updated`);
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (report.deprecatedProperties.length > 0) {
|
|
264
|
+
console.log(`\n➤ Newly deprecated properties (${report.deprecatedProperties.length}):`);
|
|
265
|
+
report.deprecatedProperties.forEach(prop => {
|
|
266
|
+
console.log(` • ${prop.name} — ${prop.reason}`);
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (report.removedProperties.length > 0) {
|
|
271
|
+
console.log(`\n➤ Removed properties (${report.removedProperties.length}):`);
|
|
272
|
+
report.removedProperties.forEach(prop => {
|
|
273
|
+
console.log(` • ${prop.name} (${prop.type})`);
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
console.log('\n' + '='.repeat(60));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Write a structured JSON comparison report to disk.
|
|
282
|
+
*
|
|
283
|
+
* Produces a JSON file containing a comparison header (old/new versions and timestamp),
|
|
284
|
+
* a summary with counts for each change category, and the full details object passed as `report`.
|
|
285
|
+
*
|
|
286
|
+
* @param {Object} report - Comparison details object produced by compareProperties; expected to contain arrays: `newProperties`, `changedDefaults`, `changedDescriptions`, `changedTypes`, `deprecatedProperties`, and `removedProperties`.
|
|
287
|
+
* @param {string} oldVersion - The previous version identifier included in the comparison header.
|
|
288
|
+
* @param {string} newVersion - The new version identifier included in the comparison header.
|
|
289
|
+
* @param {string} outputPath - Filesystem path where the JSON report will be written.
|
|
290
|
+
*/
|
|
291
|
+
function generateJsonReport(report, oldVersion, newVersion, outputPath) {
|
|
292
|
+
const jsonReport = {
|
|
293
|
+
comparison: {
|
|
294
|
+
oldVersion,
|
|
295
|
+
newVersion,
|
|
296
|
+
timestamp: new Date().toISOString()
|
|
297
|
+
},
|
|
298
|
+
summary: {
|
|
299
|
+
newProperties: report.newProperties.length,
|
|
300
|
+
changedDefaults: report.changedDefaults.length,
|
|
301
|
+
changedDescriptions: report.changedDescriptions.length,
|
|
302
|
+
changedTypes: report.changedTypes.length,
|
|
303
|
+
deprecatedProperties: report.deprecatedProperties.length,
|
|
304
|
+
removedProperties: report.removedProperties.length
|
|
305
|
+
},
|
|
306
|
+
details: report
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
fs.writeFileSync(outputPath, JSON.stringify(jsonReport, null, 2));
|
|
310
|
+
console.log(`📄 Detailed JSON report saved to: ${outputPath}`);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Compare two property JSON files and produce a change report.
|
|
315
|
+
*
|
|
316
|
+
* Reads and parses the two JSON files at oldFilePath and newFilePath, compares their properties
|
|
317
|
+
* using compareProperties, prints a human-readable console report, and optionally writes a
|
|
318
|
+
* structured JSON report to outputDir/filename.
|
|
319
|
+
*
|
|
320
|
+
* Side effects:
|
|
321
|
+
* - Synchronously reads the two input files.
|
|
322
|
+
* - Writes a JSON report file when outputDir is provided (creates the directory if needed).
|
|
323
|
+
* - Logs progress and results to the console.
|
|
324
|
+
* - On error, logs the error and exits the process with code 1.
|
|
325
|
+
*
|
|
326
|
+
* @param {string} oldFilePath - Path to the old property JSON file.
|
|
327
|
+
* @param {string} newFilePath - Path to the new property JSON file.
|
|
328
|
+
* @param {string} oldVersion - Version label for the old file (used in reports).
|
|
329
|
+
* @param {string} newVersion - Version label for the new file (used in reports).
|
|
330
|
+
* @param {string|undefined} outputDir - Optional directory to write the JSON report; if falsy, no file is written.
|
|
331
|
+
* @param {string} [filename='property-changes.json'] - Name of the JSON report file to write inside outputDir.
|
|
332
|
+
* @returns {Object} The comparison report object produced by compareProperties.
|
|
333
|
+
*/
|
|
334
|
+
function comparePropertyFiles(oldFilePath, newFilePath, oldVersion, newVersion, outputDir, filename = 'property-changes.json') {
|
|
335
|
+
try {
|
|
336
|
+
console.log(`📊 Comparing property files:`);
|
|
337
|
+
console.log(` Old: ${oldFilePath}`);
|
|
338
|
+
console.log(` New: ${newFilePath}`);
|
|
339
|
+
|
|
340
|
+
const oldData = JSON.parse(fs.readFileSync(oldFilePath, 'utf8'));
|
|
341
|
+
const newData = JSON.parse(fs.readFileSync(newFilePath, 'utf8'));
|
|
342
|
+
|
|
343
|
+
const report = compareProperties(oldData, newData, oldVersion, newVersion);
|
|
344
|
+
|
|
345
|
+
// Generate console report
|
|
346
|
+
generateConsoleReport(report, oldVersion, newVersion);
|
|
347
|
+
|
|
348
|
+
// Generate JSON report if output directory provided
|
|
349
|
+
if (outputDir) {
|
|
350
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
351
|
+
const jsonReportPath = path.join(outputDir, filename);
|
|
352
|
+
generateJsonReport(report, oldVersion, newVersion, jsonReportPath);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return report;
|
|
356
|
+
} catch (error) {
|
|
357
|
+
console.error(`❌ Error comparing properties: ${error.message}`);
|
|
358
|
+
process.exit(1);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// CLI usage
|
|
363
|
+
if (require.main === module) {
|
|
364
|
+
const args = process.argv.slice(2);
|
|
365
|
+
|
|
366
|
+
if (args.length < 4) {
|
|
367
|
+
console.log('Usage: node compare-properties.js <old-file> <new-file> <old-version> <new-version> [output-dir] [filename]');
|
|
368
|
+
console.log('');
|
|
369
|
+
console.log('Example:');
|
|
370
|
+
console.log(' node compare-properties.js gen/v25.1.1-properties.json gen/v25.2.2-properties.json v25.1.1 v25.2.2 modules/reference property-changes-v25.1.1-to-v25.2.2.json');
|
|
371
|
+
process.exit(1);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const [oldFile, newFile, oldVersion, newVersion, outputDir, filename] = args;
|
|
375
|
+
comparePropertyFiles(oldFile, newFile, oldVersion, newVersion, outputDir, filename);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
module.exports = { comparePropertyFiles, compareProperties };
|
|
@@ -5,6 +5,19 @@ const path = require('path');
|
|
|
5
5
|
const handlebars = require('handlebars');
|
|
6
6
|
const helpers = require('./helpers');
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Handlebars documentation generator for Redpanda configuration properties.
|
|
10
|
+
*
|
|
11
|
+
* Supports custom template overrides using environment variables:
|
|
12
|
+
* - TEMPLATE_PROPERTY_PAGE: Main property page template
|
|
13
|
+
* - TEMPLATE_PROPERTY: Individual property section template
|
|
14
|
+
* - TEMPLATE_TOPIC_PROPERTY: Individual topic property section template
|
|
15
|
+
* - TEMPLATE_DEPRECATED_PROPERTY: Individual deprecated property section template
|
|
16
|
+
* - TEMPLATE_DEPRECATED: Deprecated properties page template
|
|
17
|
+
*
|
|
18
|
+
* CLI Usage: node generate-handlebars-docs.js <input-file> <output-dir>
|
|
19
|
+
*/
|
|
20
|
+
|
|
8
21
|
// Register all helpers
|
|
9
22
|
Object.entries(helpers).forEach(([name, fn]) => {
|
|
10
23
|
if (typeof fn !== 'function') {
|
|
@@ -110,34 +123,74 @@ function getTemplatePath(defaultPath, envVar) {
|
|
|
110
123
|
}
|
|
111
124
|
|
|
112
125
|
/**
|
|
113
|
-
*
|
|
126
|
+
* Register Handlebars partials used to render property documentation.
|
|
127
|
+
*
|
|
128
|
+
* Loads templates from the local templates directory (overridable via environment
|
|
129
|
+
* variables handled by getTemplatePath) and registers three partials:
|
|
130
|
+
* - "property" (uses cloud-aware `property-cloud.hbs` when enabled)
|
|
131
|
+
* - "topic-property" (uses cloud-aware `topic-property-cloud.hbs` when enabled)
|
|
132
|
+
* - "deprecated-property"
|
|
133
|
+
*
|
|
134
|
+
* @param {boolean} [hasCloudSupport=false] - If true, select cloud-aware templates for the `property` and `topic-property` partials.
|
|
135
|
+
* @throws {Error} If any required template file is missing or cannot be read; errors are rethrown after logging.
|
|
114
136
|
*/
|
|
115
|
-
function registerPartials() {
|
|
137
|
+
function registerPartials(hasCloudSupport = false) {
|
|
116
138
|
const templatesDir = path.join(__dirname, 'templates');
|
|
117
139
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
'
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
140
|
+
try {
|
|
141
|
+
console.log(`📝 Registering Handlebars templates (cloud support: ${hasCloudSupport ? 'enabled' : 'disabled'})`);
|
|
142
|
+
|
|
143
|
+
// Register property partial (choose cloud or regular version)
|
|
144
|
+
const propertyTemplateFile = hasCloudSupport ? 'property-cloud.hbs' : 'property.hbs';
|
|
145
|
+
const propertyTemplatePath = getTemplatePath(
|
|
146
|
+
path.join(templatesDir, propertyTemplateFile),
|
|
147
|
+
'TEMPLATE_PROPERTY'
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
if (!fs.existsSync(propertyTemplatePath)) {
|
|
151
|
+
throw new Error(`Property template not found: ${propertyTemplatePath}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const propertyTemplate = fs.readFileSync(propertyTemplatePath, 'utf8');
|
|
155
|
+
handlebars.registerPartial('property', propertyTemplate);
|
|
156
|
+
console.log(`✅ Registered property template: ${propertyTemplateFile}`);
|
|
157
|
+
|
|
158
|
+
// Register topic property partial (choose cloud or regular version)
|
|
159
|
+
const topicPropertyTemplateFile = hasCloudSupport ? 'topic-property-cloud.hbs' : 'topic-property.hbs';
|
|
160
|
+
const topicPropertyTemplatePath = getTemplatePath(
|
|
161
|
+
path.join(templatesDir, topicPropertyTemplateFile),
|
|
162
|
+
'TEMPLATE_TOPIC_PROPERTY'
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
if (!fs.existsSync(topicPropertyTemplatePath)) {
|
|
166
|
+
throw new Error(`Topic property template not found: ${topicPropertyTemplatePath}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const topicPropertyTemplate = fs.readFileSync(topicPropertyTemplatePath, 'utf8');
|
|
170
|
+
handlebars.registerPartial('topic-property', topicPropertyTemplate);
|
|
171
|
+
console.log(`✅ Registered topic property template: ${topicPropertyTemplateFile}`);
|
|
172
|
+
|
|
173
|
+
// Register deprecated property partial
|
|
174
|
+
const deprecatedPropertyTemplatePath = getTemplatePath(
|
|
175
|
+
path.join(templatesDir, 'deprecated-property.hbs'),
|
|
176
|
+
'TEMPLATE_DEPRECATED_PROPERTY'
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
if (!fs.existsSync(deprecatedPropertyTemplatePath)) {
|
|
180
|
+
throw new Error(`Deprecated property template not found: ${deprecatedPropertyTemplatePath}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const deprecatedPropertyTemplate = fs.readFileSync(deprecatedPropertyTemplatePath, 'utf8');
|
|
184
|
+
handlebars.registerPartial('deprecated-property', deprecatedPropertyTemplate);
|
|
185
|
+
console.log(`✅ Registered deprecated property template`);
|
|
186
|
+
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error('❌ Failed to register Handlebars templates:');
|
|
189
|
+
console.error(` Error: ${error.message}`);
|
|
190
|
+
console.error(' This indicates missing or corrupted template files.');
|
|
191
|
+
console.error(' Check that all .hbs files exist in tools/property-extractor/templates/');
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
141
194
|
}
|
|
142
195
|
|
|
143
196
|
/**
|
|
@@ -180,7 +233,17 @@ function generatePropertyDocs(properties, config, outputDir) {
|
|
|
180
233
|
}
|
|
181
234
|
|
|
182
235
|
/**
|
|
183
|
-
*
|
|
236
|
+
* Generate an AsciiDoc fragment listing deprecated properties and write it to disk.
|
|
237
|
+
*
|
|
238
|
+
* Scans the provided properties map for entries with `is_deprecated === true`, groups
|
|
239
|
+
* them by `config_scope` ("broker" and "cluster"), sorts each group by property name,
|
|
240
|
+
* renders the `deprecated-properties` Handlebars template, and writes the output to
|
|
241
|
+
* `<outputDir>/deprecated/partials/deprecated-properties.adoc`.
|
|
242
|
+
*
|
|
243
|
+
* @param {Object.<string, Object>} properties - Map of property objects keyed by property name.
|
|
244
|
+
* Each property object may contain `is_deprecated`, `config_scope`, and `name` fields.
|
|
245
|
+
* @param {string} outputDir - Destination directory where the deprecated fragment will be written.
|
|
246
|
+
* @returns {number} The total number of deprecated properties found and written.
|
|
184
247
|
*/
|
|
185
248
|
function generateDeprecatedDocs(properties, outputDir) {
|
|
186
249
|
const templatePath = getTemplatePath(
|
|
@@ -216,16 +279,51 @@ function generateDeprecatedDocs(properties, outputDir) {
|
|
|
216
279
|
}
|
|
217
280
|
|
|
218
281
|
/**
|
|
219
|
-
*
|
|
282
|
+
* Determine whether any property includes cloud support metadata.
|
|
283
|
+
*
|
|
284
|
+
* Checks the provided map of properties and returns true if at least one
|
|
285
|
+
* property object has a `cloud_supported` own property (regardless of its value).
|
|
286
|
+
*
|
|
287
|
+
* @param {Object<string, Object>} properties - Map from property name to its metadata object.
|
|
288
|
+
* @return {boolean} True if any property has a `cloud_supported` attribute; otherwise false.
|
|
220
289
|
*/
|
|
221
|
-
function
|
|
222
|
-
|
|
223
|
-
|
|
290
|
+
function hasCloudSupportMetadata(properties) {
|
|
291
|
+
return Object.values(properties).some(prop =>
|
|
292
|
+
Object.prototype.hasOwnProperty.call(prop, 'cloud_supported')
|
|
293
|
+
);
|
|
294
|
+
}
|
|
224
295
|
|
|
296
|
+
/**
|
|
297
|
+
* Generate all property documentation and write output files to disk.
|
|
298
|
+
*
|
|
299
|
+
* Reads properties from the provided JSON file, detects whether any property
|
|
300
|
+
* includes cloud support metadata to select cloud-aware templates, registers
|
|
301
|
+
* Handlebars partials accordingly, renders per-type property pages and a
|
|
302
|
+
* deprecated-properties partial, writes a flat list of all property names, and
|
|
303
|
+
* produces error reports.
|
|
304
|
+
*
|
|
305
|
+
* Generated artifacts are written under the given output directory (e.g.:
|
|
306
|
+
* pages/<type>/*.adoc, pages/deprecated/partials/deprecated-properties.adoc,
|
|
307
|
+
* all_properties.txt, and files under outputDir/error).
|
|
308
|
+
*
|
|
309
|
+
* @param {string} inputFile - Filesystem path to the input JSON containing a top-level `properties` object.
|
|
310
|
+
* @param {string} outputDir - Destination directory where generated pages and reports will be written.
|
|
311
|
+
* @returns {{totalProperties: number, brokerProperties: number, clusterProperties: number, objectStorageProperties: number, topicProperties: number, deprecatedProperties: number}} Summary counts for all properties and per-type totals.
|
|
312
|
+
*/
|
|
313
|
+
function generateAllDocs(inputFile, outputDir) {
|
|
225
314
|
// Read input JSON
|
|
226
315
|
const data = JSON.parse(fs.readFileSync(inputFile, 'utf8'));
|
|
227
316
|
const properties = data.properties || {};
|
|
228
317
|
|
|
318
|
+
// Check if cloud support is enabled
|
|
319
|
+
const hasCloudSupport = hasCloudSupportMetadata(properties);
|
|
320
|
+
if (hasCloudSupport) {
|
|
321
|
+
console.log('🌤️ Cloud support metadata detected, using cloud-aware templates');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Register partials with cloud support detection
|
|
325
|
+
registerPartials(hasCloudSupport);
|
|
326
|
+
|
|
229
327
|
let totalProperties = 0;
|
|
230
328
|
let totalBrokerProperties = 0;
|
|
231
329
|
let totalClusterProperties = 0;
|
|
@@ -291,13 +389,15 @@ function generateErrorReports(properties, outputDir) {
|
|
|
291
389
|
});
|
|
292
390
|
|
|
293
391
|
// Write error reports
|
|
392
|
+
const totalProperties = Object.keys(properties).length;
|
|
393
|
+
|
|
294
394
|
if (emptyDescriptions.length > 0) {
|
|
295
395
|
fs.writeFileSync(
|
|
296
396
|
path.join(errorDir, 'empty_description.txt'),
|
|
297
397
|
emptyDescriptions.join('\n'),
|
|
298
398
|
'utf8'
|
|
299
399
|
);
|
|
300
|
-
const percentage = ((emptyDescriptions.length /
|
|
400
|
+
const percentage = totalProperties > 0 ? ((emptyDescriptions.length / totalProperties) * 100).toFixed(2) : '0.00';
|
|
301
401
|
console.log(`You have ${emptyDescriptions.length} properties with empty description. Percentage of errors: ${percentage}%. Data written in 'empty_description.txt'.`);
|
|
302
402
|
}
|
|
303
403
|
|
|
@@ -307,7 +407,7 @@ function generateErrorReports(properties, outputDir) {
|
|
|
307
407
|
deprecatedProperties.join('\n'),
|
|
308
408
|
'utf8'
|
|
309
409
|
);
|
|
310
|
-
const percentage = ((deprecatedProperties.length /
|
|
410
|
+
const percentage = totalProperties > 0 ? ((deprecatedProperties.length / totalProperties) * 100).toFixed(2) : '0.00';
|
|
311
411
|
console.log(`You have ${deprecatedProperties.length} deprecated properties. Percentage of errors: ${percentage}%. Data written in 'deprecated_properties.txt'.`);
|
|
312
412
|
}
|
|
313
413
|
}
|
|
@@ -14,11 +14,37 @@ HEADER_QUERY = """
|
|
|
14
14
|
) @declaration
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
|
+
# Tree-sitter query for extracting C++ property constructor arguments and enterprise values
|
|
18
|
+
#
|
|
19
|
+
# - Enhanced to capture all expression types including:
|
|
20
|
+
# * call_expression: Handles function calls like model::kafka_audit_logging_topic()
|
|
21
|
+
# * template_instantiation: Handles template syntax like std::vector<ss::sstring>{...}
|
|
22
|
+
# * concatenated_string: Handles C++ string concatenation with +
|
|
23
|
+
# * qualified_identifier: Handles namespaced identifiers like model::partition_autobalancing_mode::continuous
|
|
24
|
+
# * (_) @argument: Fallback to capture any other expression types
|
|
25
|
+
#
|
|
26
|
+
# This ensures enterprise values are captured in their complete form for proper
|
|
27
|
+
# processing by the process_enterprise_value function.
|
|
17
28
|
SOURCE_QUERY = """
|
|
18
29
|
(field_initializer_list
|
|
19
30
|
(field_initializer
|
|
20
31
|
(field_identifier) @field
|
|
21
|
-
(argument_list
|
|
32
|
+
(argument_list
|
|
33
|
+
[
|
|
34
|
+
(call_expression) @argument
|
|
35
|
+
(initializer_list) @argument
|
|
36
|
+
(template_instantiation) @argument
|
|
37
|
+
(concatenated_string) @argument
|
|
38
|
+
(string_literal) @argument
|
|
39
|
+
(raw_string_literal) @argument
|
|
40
|
+
(identifier) @argument
|
|
41
|
+
(qualified_identifier) @argument
|
|
42
|
+
(number_literal) @argument
|
|
43
|
+
(true) @argument
|
|
44
|
+
(false) @argument
|
|
45
|
+
(_) @argument
|
|
46
|
+
]
|
|
47
|
+
)? @arguments
|
|
22
48
|
) @field
|
|
23
49
|
)
|
|
24
50
|
"""
|