@redpanda-data/docs-extensions-and-macros 4.8.0 → 4.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/bin/doc-tools.js +236 -54
  2. package/package.json +1 -1
  3. package/tools/property-extractor/Makefile +68 -50
  4. package/tools/property-extractor/cloud_config.py +594 -0
  5. package/tools/property-extractor/compare-properties.js +378 -0
  6. package/tools/property-extractor/generate-handlebars-docs.js +444 -0
  7. package/tools/property-extractor/helpers/and.js +10 -0
  8. package/tools/property-extractor/helpers/eq.js +9 -0
  9. package/tools/property-extractor/helpers/formatPropertyValue.js +128 -0
  10. package/tools/property-extractor/helpers/formatUnits.js +26 -0
  11. package/tools/property-extractor/helpers/index.js +13 -0
  12. package/tools/property-extractor/helpers/join.js +18 -0
  13. package/tools/property-extractor/helpers/ne.js +9 -0
  14. package/tools/property-extractor/helpers/not.js +8 -0
  15. package/tools/property-extractor/helpers/or.js +10 -0
  16. package/tools/property-extractor/helpers/renderPropertyExample.js +42 -0
  17. package/tools/property-extractor/package-lock.json +77 -0
  18. package/tools/property-extractor/package.json +6 -0
  19. package/tools/property-extractor/parser.py +27 -1
  20. package/tools/property-extractor/property_extractor.py +1428 -49
  21. package/tools/property-extractor/requirements.txt +2 -0
  22. package/tools/property-extractor/templates/deprecated-properties.hbs +25 -0
  23. package/tools/property-extractor/templates/deprecated-property.hbs +7 -0
  24. package/tools/property-extractor/templates/property-cloud.hbs +105 -0
  25. package/tools/property-extractor/templates/property-page.hbs +22 -0
  26. package/tools/property-extractor/templates/property.hbs +85 -0
  27. package/tools/property-extractor/templates/topic-property-cloud.hbs +97 -0
  28. package/tools/property-extractor/templates/topic-property.hbs +73 -0
  29. package/tools/property-extractor/transformers.py +178 -6
  30. package/tools/property-extractor/json-to-asciidoc/generate_docs.py +0 -491
@@ -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 };