@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.
@@ -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
- * Registers Handlebars partials from template files
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
- // Register property partial
119
- const propertyTemplatePath = getTemplatePath(
120
- path.join(templatesDir, 'property.hbs'),
121
- 'TEMPLATE_PROPERTY'
122
- );
123
- const propertyTemplate = fs.readFileSync(propertyTemplatePath, 'utf8');
124
- handlebars.registerPartial('property', propertyTemplate);
125
-
126
- // Register topic property partial
127
- const topicPropertyTemplatePath = getTemplatePath(
128
- path.join(templatesDir, 'topic-property.hbs'),
129
- 'TEMPLATE_TOPIC_PROPERTY'
130
- );
131
- const topicPropertyTemplate = fs.readFileSync(topicPropertyTemplatePath, 'utf8');
132
- handlebars.registerPartial('topic-property', topicPropertyTemplate);
133
-
134
- // Register deprecated property partial
135
- const deprecatedPropertyTemplatePath = getTemplatePath(
136
- path.join(templatesDir, 'deprecated-property.hbs'),
137
- 'TEMPLATE_DEPRECATED_PROPERTY'
138
- );
139
- const deprecatedPropertyTemplate = fs.readFileSync(deprecatedPropertyTemplatePath, 'utf8');
140
- handlebars.registerPartial('deprecated-property', deprecatedPropertyTemplate);
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
- * Generates deprecated properties documentation
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
- * Main function to generate all property documentation
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 generateAllDocs(inputFile, outputDir) {
222
- // Register partials
223
- registerPartials();
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 / Object.keys(properties).length) * 100).toFixed(2);
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 / Object.keys(properties).length) * 100).toFixed(2);
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 (_) @argument)? @arguments
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
  """