@redpanda-data/docs-extensions-and-macros 4.11.0 β†’ 4.12.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 +4 -2
  2. package/extensions/convert-to-markdown.js +17 -1
  3. package/package.json +3 -1
  4. package/tools/property-extractor/COMPUTED_CONSTANTS.md +173 -0
  5. package/tools/property-extractor/Makefile +12 -1
  6. package/tools/property-extractor/README.adoc +828 -97
  7. package/tools/property-extractor/compare-properties.js +38 -13
  8. package/tools/property-extractor/constant_resolver.py +610 -0
  9. package/tools/property-extractor/file_pair.py +42 -0
  10. package/tools/property-extractor/generate-handlebars-docs.js +41 -8
  11. package/tools/property-extractor/helpers/gt.js +9 -0
  12. package/tools/property-extractor/helpers/includes.js +17 -0
  13. package/tools/property-extractor/helpers/index.js +3 -0
  14. package/tools/property-extractor/helpers/isEnterpriseEnum.js +24 -0
  15. package/tools/property-extractor/helpers/renderPropertyExample.js +6 -5
  16. package/tools/property-extractor/overrides.json +248 -0
  17. package/tools/property-extractor/parser.py +254 -32
  18. package/tools/property-extractor/property_bag.py +40 -0
  19. package/tools/property-extractor/property_extractor.py +1417 -430
  20. package/tools/property-extractor/requirements.txt +1 -0
  21. package/tools/property-extractor/templates/property-backup.hbs +161 -0
  22. package/tools/property-extractor/templates/property.hbs +104 -49
  23. package/tools/property-extractor/templates/topic-property-backup.hbs +148 -0
  24. package/tools/property-extractor/templates/topic-property.hbs +72 -34
  25. package/tools/property-extractor/tests/test_known_values.py +617 -0
  26. package/tools/property-extractor/tests/transformers_test.py +81 -6
  27. package/tools/property-extractor/topic_property_extractor.py +23 -10
  28. package/tools/property-extractor/transformers.py +2191 -369
  29. package/tools/property-extractor/type_definition_extractor.py +669 -0
  30. package/tools/property-extractor/definitions.json +0 -245
@@ -105,7 +105,17 @@ function registerPartials() {
105
105
  }
106
106
 
107
107
  /**
108
- * Generate consolidated AsciiDoc partials for properties grouped by type.
108
+ * Generate AsciiDoc partial files grouping input properties by scope (cluster, topic, broker, object-storage).
109
+ *
110
+ * Reads property and topic templates, groups provided properties by their config_scope (treating keys as authoritative property names),
111
+ * renders each property into the appropriate template, writes combined partial files to "<partialsDir>/properties/<type>-properties.adoc",
112
+ * and invokes the optional onRender callback for every rendered property name. Entries missing a name or config_scope are skipped;
113
+ * duplicate keys are detected, warned about, and skipped.
114
+ *
115
+ * @param {Object<string, Object>} properties - Map of property key β†’ property object; the map key is used as the property's name.
116
+ * @param {string} partialsDir - Destination directory under which a "properties" subdirectory will be created for output files.
117
+ * @param {(name: string) => void} [onRender] - Optional callback invoked with each rendered property's name.
118
+ * @returns {number} The total number of properties rendered and written to partial files.
109
119
  */
110
120
  function generatePropertyPartials(properties, partialsDir, onRender) {
111
121
  console.log(`πŸ“ Generating consolidated property partials in ${partialsDir}…`);
@@ -122,9 +132,23 @@ function generatePropertyPartials(properties, partialsDir, onRender) {
122
132
 
123
133
  const propertyGroups = { cluster: [], topic: [], broker: [], 'object-storage': [] };
124
134
 
125
- Object.values(properties).forEach(prop => {
135
+ // Track processed property keys to detect duplicates by unique key
136
+ const processedKeys = new Set();
137
+
138
+ Object.entries(properties).forEach(([key, prop]) => {
126
139
  if (!prop.name || !prop.config_scope) return;
127
140
 
141
+ // Skip if we've already processed this key
142
+ if (processedKeys.has(key)) {
143
+ console.warn(`⚠️ Duplicate key detected: ${key}`);
144
+ return;
145
+ }
146
+ processedKeys.add(key);
147
+
148
+ // Ensure the property uses the key as its name for consistency
149
+ // This fixes issues where key != name field due to bugs in the source code
150
+ prop.name = key;
151
+
128
152
  switch (prop.config_scope) {
129
153
  case 'topic':
130
154
  propertyGroups.topic.push(prop);
@@ -243,19 +267,28 @@ function generateErrorReports(properties, documentedProperties = []) {
243
267
  const documentedSet = new Set(documentedProperties);
244
268
  const undocumented = [];
245
269
 
246
- Object.entries(properties).forEach(([key, p]) => {
270
+ Object.entries(properties).forEach(([key, p]) => {
247
271
  const name = p.name || key;
248
- if (!p.description || !p.description.trim()) emptyDescriptions.push(name);
272
+ const desc = p.description;
273
+
249
274
  if (p.is_deprecated) deprecatedProperties.push(name);
275
+
276
+ // Ensure description is a non-empty string (exclude deprecated properties)
277
+ if (!p.is_deprecated && (typeof desc !== 'string' || desc.trim().length === 0)) {
278
+ emptyDescriptions.push(name);
279
+ }
280
+
250
281
  if (!documentedSet.has(name)) undocumented.push(name);
251
282
  });
252
283
 
284
+
253
285
  const total = allKeys.length;
254
- const pctEmpty = total ? ((emptyDescriptions.length / total) * 100).toFixed(2) : '0.00';
286
+ const nonDeprecatedTotal = total - deprecatedProperties.length;
287
+ const pctEmpty = nonDeprecatedTotal ? ((emptyDescriptions.length / nonDeprecatedTotal) * 100).toFixed(2) : '0.00';
255
288
  const pctDeprecated = total ? ((deprecatedProperties.length / total) * 100).toFixed(2) : '0.00';
256
289
  const pctUndocumented = total ? ((undocumented.length / total) * 100).toFixed(2) : '0.00';
257
290
 
258
- console.log(`πŸ“‰ Empty descriptions: ${emptyDescriptions.length} (${pctEmpty}%)`);
291
+ console.log(`πŸ“‰ Empty descriptions: ${emptyDescriptions.length} (${pctEmpty}%) (excludes deprecated)`);
259
292
  console.log(`πŸ•ΈοΈ Deprecated: ${deprecatedProperties.length} (${pctDeprecated}%)`);
260
293
  console.log(`🚫 Not documented: ${undocumented.length} (${pctUndocumented}%)`);
261
294
 
@@ -309,7 +342,7 @@ function generateAllDocs(inputFile, outputDir) {
309
342
  console.log(` Deprecated properties: ${deprecatedCount}`);
310
343
 
311
344
  if (notRendered > 0) {
312
- console.log('⚠️ Undocumented properties:\n ' + errors.undocumented_properties.join('\n '));
345
+ console.log('Ignored:\n ' + errors.undocumented_properties.join('\n '));
313
346
  }
314
347
 
315
348
  return {
@@ -349,4 +382,4 @@ if (require.main === module) {
349
382
  console.error(`❌ Error: ${err.message}`);
350
383
  process.exit(1);
351
384
  }
352
- }
385
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Handlebars helper for greater than comparison
3
+ * @param {*} a - First value
4
+ * @param {*} b - Second value
5
+ * @returns {boolean} True if a is greater than b
6
+ */
7
+ module.exports = function gt(a, b) {
8
+ return a > b;
9
+ };
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Handlebars helper to check if an array includes a value.
5
+ *
6
+ * Usage: {{#if (includes array value)}}...{{/if}}
7
+ *
8
+ * @param {Array} array - The array to search
9
+ * @param {*} value - The value to find
10
+ * @returns {boolean} True if array includes value
11
+ */
12
+ module.exports = function(array, value) {
13
+ if (!Array.isArray(array)) {
14
+ return false;
15
+ }
16
+ return array.includes(value);
17
+ };
@@ -4,9 +4,12 @@ module.exports = {
4
4
  join: require('./join.js'),
5
5
  eq: require('./eq.js'),
6
6
  ne: require('./ne.js'),
7
+ gt: require('./gt.js'),
7
8
  and: require('./and.js'),
8
9
  or: require('./or.js'),
9
10
  not: require('./not.js'),
11
+ includes: require('./includes.js'),
12
+ isEnterpriseEnum: require('./isEnterpriseEnum.js'),
10
13
  formatPropertyValue: require('./formatPropertyValue.js'),
11
14
  renderPropertyExample: require('./renderPropertyExample.js'),
12
15
  formatUnits: require('./formatUnits.js'),
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Handlebars helper to check if an enum value is marked as enterprise.
5
+ *
6
+ * Usage: {{#if (isEnterpriseEnum enumValue x-enum-metadata)}}(Enterprise){{/if}}
7
+ * Usage in template: {{#each enum}}`{{this}}`{{#if (isEnterpriseEnum this ../x-enum-metadata)}} (Enterprise){{/if}}{{/each}}
8
+ *
9
+ * @param {string} enumValue - The enum value to check
10
+ * @param {Object} metadata - The x-enum-metadata object mapping values to {is_enterprise: boolean}
11
+ * @returns {boolean} True if the enum value is enterprise-only
12
+ */
13
+ module.exports = function(enumValue, metadata) {
14
+ if (!metadata || typeof metadata !== 'object') {
15
+ return false;
16
+ }
17
+
18
+ const valueMetadata = metadata[enumValue];
19
+ if (!valueMetadata || typeof valueMetadata !== 'object') {
20
+ return false;
21
+ }
22
+
23
+ return valueMetadata.is_enterprise === true;
24
+ };
@@ -18,8 +18,8 @@ module.exports = function renderPropertyExample(property) {
18
18
  if (property.example.includes('.Example') || property.example.includes('[,yaml]')) {
19
19
  exampleContent = property.example;
20
20
  } else {
21
- // Simple string example - wrap it
22
- exampleContent = `.Example\n[,yaml]\n----\n${property.name}: ${property.example}\n----`;
21
+ // Wrap simple string examples in backticks for inline code formatting
22
+ exampleContent = `\`${property.example}\``;
23
23
  }
24
24
  } else if (Array.isArray(property.example)) {
25
25
  // Multiline array example
@@ -34,9 +34,10 @@ module.exports = function renderPropertyExample(property) {
34
34
  exampleContent += `[,yaml]\n----\n${JSON.stringify(property.example.config, null, 2)}\n----`;
35
35
  }
36
36
  } else {
37
- // Fallback: JSON stringify the example
38
- exampleContent = `.Example\n[,yaml]\n----\n${property.name}: ${JSON.stringify(property.example, null, 2)}\n----`;
37
+ // Fallback: JSON stringify and wrap in backticks for inline code
38
+ const jsonStr = JSON.stringify(property.example, null, 2);
39
+ exampleContent = `\`${jsonStr}\``;
39
40
  }
40
41
 
41
- return new handlebars.SafeString('\n' + exampleContent + '\n');
42
+ return new handlebars.SafeString(exampleContent);
42
43
  };
@@ -0,0 +1,248 @@
1
+ {
2
+ "$comment": "Overrides file for Redpanda property extraction. Supports two top-level keys: 'properties' for property overrides (descriptions, examples, etc.) and 'definitions' for type definition overrides (structs, enums with user-friendly names, etc.). Override definitions take precedence over dynamically extracted ones.",
3
+ "definitions": {
4
+ "client_group_quota": {
5
+ "defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/config/client_group_byte_rate_quota.h#L29",
6
+ "type": "object",
7
+ "properties": {
8
+ "group_name": {
9
+ "type": "string"
10
+ },
11
+ "clients_prefix": {
12
+ "type": "string"
13
+ },
14
+ "quota": {
15
+ "type": "integer",
16
+ "minimum": -9223372036854775808,
17
+ "maximum": 9223372036854775807
18
+ }
19
+ }
20
+ },
21
+ "config::broker_authn_endpoint": {
22
+ "defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/config/broker_authn_endpoint.h#L42",
23
+ "type": "object",
24
+ "properties": {
25
+ "name": {
26
+ "type": "string"
27
+ },
28
+ "address": {
29
+ "type": "string"
30
+ },
31
+ "port": {
32
+ "type": "integer",
33
+ "minimum": 0,
34
+ "maximum": 4294967295
35
+ }
36
+ }
37
+ },
38
+ "config::endpoint_tls_config": {
39
+ "defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/config/endpoint_tls_config.h#L21",
40
+ "type": "object",
41
+ "properties": {
42
+ "name": {
43
+ "type": "string"
44
+ },
45
+ "config": {
46
+ "$ref": "#/definitions/config::tls_config"
47
+ }
48
+ }
49
+ },
50
+ "config::tls_config": {
51
+ "defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/config/tls_config.h#L49",
52
+ "type": "object",
53
+ "properties": {
54
+ "enabled": {
55
+ "type": "boolean"
56
+ },
57
+ "require_client_auth": {
58
+ "type": "boolean"
59
+ },
60
+ "key_file": {
61
+ "type": "string"
62
+ },
63
+ "cert_file": {
64
+ "type": "string"
65
+ },
66
+ "truststore_file": {
67
+ "type": "string"
68
+ }
69
+ }
70
+ },
71
+ "tls_config": {
72
+ "$ref": "#/definitions/config::tls_config"
73
+ },
74
+ "config::rest_authn_endpoint": {
75
+ "defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/config/rest_authn_endpoint.h#L42",
76
+ "type": "object",
77
+ "properties": {
78
+ "name": {
79
+ "type": "string"
80
+ },
81
+ "address": {
82
+ "type": "string"
83
+ },
84
+ "port": {
85
+ "type": "integer",
86
+ "minimum": 0,
87
+ "maximum": 4294967295
88
+ },
89
+ "authentication_method": {
90
+ "$ref": "#/definitions/config::rest_authn_method"
91
+ }
92
+ }
93
+ },
94
+ "config::rest_authn_method": {
95
+ "defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/config/rest_authn_endpoint.h#L31",
96
+ "enum": [
97
+ "none",
98
+ "http_basic"
99
+ ]
100
+ },
101
+ "endpoint_tls_config": {
102
+ "$ref": "#/definitions/config::endpoint_tls_config"
103
+ },
104
+ "model::broker_endpoint": {
105
+ "defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/model/metadata.h#L88",
106
+ "type": "object",
107
+ "properties": {
108
+ "name": {
109
+ "type": "string"
110
+ },
111
+ "address": {
112
+ "type": "string"
113
+ },
114
+ "port": {
115
+ "type": "integer",
116
+ "minimum": 0,
117
+ "maximum": 4294967295
118
+ }
119
+ }
120
+ },
121
+ "model::cleanup_policy_bitflags": {
122
+ "defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/model/fundamental.h#L72",
123
+ "enum": [
124
+ "none",
125
+ "delete",
126
+ "compact"
127
+ ]
128
+ },
129
+ "model::cloud_credentials_source": {
130
+ "defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/model/metadata.h#L454",
131
+ "enum": [
132
+ "config_file",
133
+ "aws_instance_metadata",
134
+ "sts",
135
+ "gcp_instance_metadata"
136
+ ]
137
+ },
138
+ "model::cloud_storage_backend": {
139
+ "defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/model/metadata.h#L481",
140
+ "enum": [
141
+ "aws",
142
+ "google_s3_compat",
143
+ "azure",
144
+ "minio",
145
+ "unknown"
146
+ ]
147
+ },
148
+ "model::cloud_storage_chunk_eviction_strategy": {
149
+ "defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/model/metadata.h#L524",
150
+ "enum": [
151
+ "eager",
152
+ "capped",
153
+ "predictive"
154
+ ]
155
+ },
156
+ "model::compression": {
157
+ "defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/model/compression.h#L36",
158
+ "enum": [
159
+ "none",
160
+ "gzip",
161
+ "snappy",
162
+ "lz4",
163
+ "zstd",
164
+ "producer"
165
+ ]
166
+ },
167
+ "model::leader_balancer_mode": {
168
+ "defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/model/metadata.h#L504",
169
+ "enum": [
170
+ "greedy_balanced_shards",
171
+ "random_hill_climbing"
172
+ ]
173
+ },
174
+ "model::node_id": {
175
+ "defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/model/metadata.h#L36",
176
+ "type": "integer",
177
+ "minimum": -2147483648,
178
+ "maximum": 2147483647
179
+ },
180
+ "model::partition_autobalancing_mode": {
181
+ "defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/model/metadata.h#L463",
182
+ "enum": [
183
+ "off",
184
+ "node_add",
185
+ "continuous"
186
+ ]
187
+ },
188
+ "model::rack_id": {
189
+ "defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/model/metadata.h#L60",
190
+ "type": "string"
191
+ },
192
+ "model::timestamp_type": {
193
+ "defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/model/timestamp.h#L30",
194
+ "enum": [
195
+ "create_time",
196
+ "append_time"
197
+ ]
198
+ },
199
+ "net::unresolved_address": {
200
+ "defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/net/unresolved_address.h#L27",
201
+ "properties": {
202
+ "address": {
203
+ "type": "string"
204
+ },
205
+ "port": {
206
+ "type": "integer",
207
+ "minimum": 0,
208
+ "maximum": 4294967295
209
+ }
210
+ }
211
+ },
212
+ "pandaproxy::schema_registry::schema_id_validation_mode": {
213
+ "defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/pandaproxy/schema_registry/schema_id_validation.h#L22",
214
+ "enum": [
215
+ "none",
216
+ "redpanda",
217
+ "compat"
218
+ ]
219
+ },
220
+ "retention_duration_property": {
221
+ "defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/config/property.h#L878",
222
+ "type": "integer",
223
+ "minimum": -2147483648,
224
+ "maximum": 2147483647
225
+ },
226
+ "seed_server": {
227
+ "defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/config/seed_server.h#L24",
228
+ "type": "object",
229
+ "properties": {
230
+ "host": {
231
+ "$ref": "#/definitions/net::unresolved_address"
232
+ }
233
+ }
234
+ },
235
+ "throughput_control_group": {
236
+ "defined_in": "https://github.com/redpanda-data/redpanda/blob/dev/src/v/config/throughput_control_group.h#L36",
237
+ "type": "object",
238
+ "properties": {
239
+ "name": {
240
+ "type": "string"
241
+ },
242
+ "client_id": {
243
+ "type": "string"
244
+ }
245
+ }
246
+ }
247
+ }
248
+ }