@redpanda-data/docs-extensions-and-macros 4.10.0 → 4.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/doc-tools.js CHANGED
@@ -1032,17 +1032,20 @@ automation
1032
1032
 
1033
1033
  automation
1034
1034
  .command('property-docs')
1035
- .description('Generate JSON and AsciiDoc documentation for Redpanda configuration properties')
1035
+ .description('Generate JSON and AsciiDoc documentation for Redpanda configuration properties. By default, only extracts properties to JSON. Use --generate-partials to create consolidated AsciiDoc partials (including deprecated properties). Use --generate-pages to create complete property pages that include the partials using AsciiDoc includes.')
1036
1036
  .option('--tag <tag>', 'Git tag or branch to extract from', 'dev')
1037
- .option('--diff <oldTag>', 'Also diff autogenerated properties from <oldTag> <tag>')
1037
+ .option('--diff <oldTag>', 'Also diff autogenerated properties from <oldTag> to <tag>')
1038
1038
  .option('--overrides <path>', 'Optional JSON file with property description overrides')
1039
1039
  .option('--output-dir <dir>', 'Where to write all generated files', 'modules/reference')
1040
- .option('--cloud-support', 'Enable cloud support metadata by fetching configuration from the cloudv2 repository (requires GITHUB_TOKEN, GH_TOKEN, or REDPANDA_GITHUB_TOKEN)')
1040
+ .option('--cloud-support', 'Add AsciiDoc tags to generated property docs to indicate which ones are supported in Redpanda Cloud. This data is fetched from the cloudv2 repository so requires a GitHub token with repo permissions. Set the token as an environment variable using GITHUB_TOKEN, GH_TOKEN, or REDPANDA_GITHUB_TOKEN')
1041
1041
  .option('--template-property-page <path>', 'Custom Handlebars template for property page layout')
1042
1042
  .option('--template-property <path>', 'Custom Handlebars template for individual property sections')
1043
1043
  .option('--template-topic-property <path>', 'Custom Handlebars template for individual topic property sections')
1044
1044
  .option('--template-deprecated <path>', 'Custom Handlebars template for deprecated properties page')
1045
1045
  .option('--template-deprecated-property <path>', 'Custom Handlebars template for individual deprecated property sections')
1046
+ .option('--generate-partials', 'Generate consolidated property partials (cluster-properties.adoc, topic-properties.adoc, etc.) in the partials directory')
1047
+ .option('--partials-dir <path>', 'Directory for property partials (relative to output-dir)', 'partials')
1048
+ .option('--generate-pages', 'Generate complete property pages that include the partials using AsciiDoc includes')
1046
1049
  .action((options) => {
1047
1050
  verifyPropertyDependencies();
1048
1051
 
@@ -1091,6 +1094,7 @@ automation
1091
1094
  }
1092
1095
  if (templates.propertyPage) {
1093
1096
  env.TEMPLATE_PROPERTY_PAGE = path.resolve(templates.propertyPage);
1097
+ env.TEMPLATE_PROPERTY_PAGE_WITH_INCLUDES = env.TEMPLATE_PROPERTY_PAGE;
1094
1098
  }
1095
1099
  if (templates.property) {
1096
1100
  env.TEMPLATE_PROPERTY = path.resolve(templates.property);
@@ -1111,6 +1115,22 @@ automation
1111
1115
  } else {
1112
1116
  env.OUTPUT_JSON_DIR = path.resolve(outputDir, 'examples');
1113
1117
  env.OUTPUT_AUTOGENERATED_DIR = path.resolve(outputDir);
1118
+ // Set property files to go to properties subdirectory
1119
+ env.OUTPUT_ASCIIDOC_DIR = path.resolve(outputDir, 'pages', 'properties');
1120
+ }
1121
+
1122
+ // Set partials generation options
1123
+ if (options.generatePartials) {
1124
+ env.GENERATE_PARTIALS = '1';
1125
+ env.OUTPUT_PARTIALS_DIR = path.resolve(outputDir, options.partialsDir || 'partials');
1126
+ }
1127
+
1128
+ // Set page generation options
1129
+ if (options.generatePages) {
1130
+ env.GENERATE_PAGES = '1';
1131
+ if (templates.propertyPage) {
1132
+ env.TEMPLATE_PROPERTY_PAGE_WITH_INCLUDES = env.TEMPLATE_PROPERTY_PAGE;
1133
+ }
1114
1134
  }
1115
1135
 
1116
1136
  const r = spawnSync('make', args, { cwd, stdio: 'inherit', env });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redpanda-data/docs-extensions-and-macros",
3
- "version": "4.10.0",
3
+ "version": "4.10.2",
4
4
  "description": "Antora extensions and macros developed for Redpanda documentation.",
5
5
  "keywords": [
6
6
  "antora",
@@ -28,6 +28,7 @@ TREE_SITTER := npx tree-sitter
28
28
  OUTPUT_AUTOGENERATED_DIR ?= $(REPO_ROOT)/modules/reference
29
29
  OUTPUT_ASCIIDOC_DIR ?= $(OUTPUT_AUTOGENERATED_DIR)/pages
30
30
  OUTPUT_JSON_DIR ?= $(OUTPUT_AUTOGENERATED_DIR)/examples
31
+ OUTPUT_PARTIALS_DIR ?= $(OUTPUT_AUTOGENERATED_DIR)/partials
31
32
 
32
33
  # --- Main build: venv, fetch code, build parser, extract & docgen ---
33
34
  build: venv redpanda-git treesitter
@@ -111,10 +112,10 @@ generate-docs: node-deps
111
112
  @# Use the enhanced properties file (with overrides) for documentation generation if it exists
112
113
  @if [ -f "$(TOOL_ROOT)/gen/$(TAG)-properties.json" ]; then \
113
114
  cd $(TOOL_ROOT) && \
114
- node generate-handlebars-docs.js "gen/$(TAG)-properties.json" "$(OUTPUT_AUTOGENERATED_DIR)"; \
115
+ node generate-handlebars-docs.js "gen/$(TAG)-properties.json" "$(OUTPUT_ASCIIDOC_DIR)"; \
115
116
  else \
116
117
  cd $(TOOL_ROOT) && \
117
- node generate-handlebars-docs.js "gen/properties-output.json" "$(OUTPUT_AUTOGENERATED_DIR)"; \
118
+ node generate-handlebars-docs.js "gen/properties-output.json" "$(OUTPUT_ASCIIDOC_DIR)"; \
118
119
  fi
119
120
  @echo "📄 Copying properties JSON files to $(OUTPUT_JSON_DIR)…"
120
121
  @if [ -f "$(TOOL_ROOT)/gen/$(TAG)-properties.json" ]; then \
@@ -27,6 +27,23 @@ Object.entries(helpers).forEach(([name, fn]) => {
27
27
  handlebars.registerHelper(name, fn);
28
28
  });
29
29
 
30
+ /**
31
+ * Determines if a property is related to object storage.
32
+ * @param {Object} prop - The property object
33
+ * @returns {boolean} True if the property is object storage related
34
+ */
35
+ function isObjectStorageProperty(prop) {
36
+ return prop.name && (
37
+ prop.name.includes('cloud_storage') ||
38
+ prop.name.includes('s3_') ||
39
+ prop.name.includes('azure_') ||
40
+ prop.name.includes('gcs_') ||
41
+ prop.name.includes('archival_') ||
42
+ prop.name.includes('remote_') ||
43
+ prop.name.includes('tiered_')
44
+ );
45
+ }
46
+
30
47
  /**
31
48
  * Configuration mapping for different property types
32
49
  */
@@ -60,7 +77,7 @@ NOTE: Some cluster properties require that you restart the cluster for any updat
60
77
  sectionTitle: 'Cluster configuration',
61
78
  groups: [
62
79
  {
63
- filter: (prop) => prop.config_scope === 'cluster' && !prop.is_deprecated
80
+ filter: (prop) => prop.config_scope === 'cluster' && !prop.is_deprecated && !isObjectStorageProperty(prop)
64
81
  }
65
82
  ],
66
83
  filename: 'cluster-properties.adoc'
@@ -75,15 +92,7 @@ NOTE: Some object storage properties require that you restart the cluster for an
75
92
  sectionIntro: 'Object storage properties should only be set if you enable xref:manage:tiered-storage.adoc[Tiered Storage].',
76
93
  groups: [
77
94
  {
78
- filter: (prop) => prop.name && (
79
- prop.name.includes('cloud_storage') ||
80
- prop.name.includes('s3_') ||
81
- prop.name.includes('azure_') ||
82
- prop.name.includes('gcs_') ||
83
- prop.name.includes('archival_') ||
84
- prop.name.includes('remote_') ||
85
- prop.name.includes('tiered_')
86
- ) && !prop.is_deprecated
95
+ filter: (prop) => isObjectStorageProperty(prop) && !prop.is_deprecated
87
96
  }
88
97
  ],
89
98
  filename: 'object-storage-properties.adoc'
@@ -197,39 +206,165 @@ function registerPartials(hasCloudSupport = false) {
197
206
  * Generates documentation for a specific property type
198
207
  */
199
208
  function generatePropertyDocs(properties, config, outputDir) {
209
+ // Check if partials are being generated to determine which template to use
210
+ const useIncludes = process.env.GENERATE_PARTIALS === '1';
211
+
212
+ let templatePath;
213
+ if (useIncludes) {
214
+ // Use the include-based template when partials are also being generated
215
+ templatePath = getTemplatePath(
216
+ path.join(__dirname, 'templates', 'property-page-with-includes.hbs'),
217
+ 'TEMPLATE_PROPERTY_PAGE_WITH_INCLUDES'
218
+ );
219
+ } else {
220
+ // Use the standard template for full content
221
+ templatePath = getTemplatePath(
222
+ path.join(__dirname, 'templates', 'property-page.hbs'),
223
+ 'TEMPLATE_PROPERTY_PAGE'
224
+ );
225
+ }
226
+
227
+ const template = handlebars.compile(fs.readFileSync(templatePath, 'utf8'));
228
+
229
+ if (useIncludes) {
230
+ // For include-based pages, we need minimal data - just page metadata and filename for include
231
+ const data = {
232
+ ...config,
233
+ filename: config.filename.replace('.adoc', '') // Remove .adoc extension for include
234
+ };
235
+
236
+ const output = template(data);
237
+ const outputPath = path.join(outputDir, config.filename);
238
+
239
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
240
+ fs.writeFileSync(outputPath, output, 'utf8');
241
+
242
+ console.log(`✅ Generated include-based page ${outputPath}`);
243
+
244
+ // Count properties for this type
245
+ const typeCount = Object.values(properties).filter(prop => {
246
+ return config.groups.some(group => group.filter(prop));
247
+ }).length;
248
+
249
+ return typeCount;
250
+ } else {
251
+ // Filter and group properties according to configuration
252
+ const groups = config.groups.map(group => {
253
+ const filteredProperties = Object.values(properties)
254
+ .filter(prop => group.filter(prop))
255
+ .sort((a, b) => String(a.name || '').localeCompare(String(b.name || '')));
256
+
257
+ return {
258
+ title: group.title,
259
+ intro: group.intro,
260
+ properties: filteredProperties,
261
+ template: group.template || 'property' // Default to 'property' template
262
+ };
263
+ }).filter(group => group.properties.length > 0);
264
+
265
+ const data = {
266
+ ...config,
267
+ groups
268
+ };
269
+
270
+ const output = template(data);
271
+ const outputPath = path.join(outputDir, config.filename);
272
+
273
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
274
+ fs.writeFileSync(outputPath, output, 'utf8');
275
+
276
+ console.log(`✅ Generated full content page ${outputPath}`);
277
+ return groups.reduce((total, group) => total + group.properties.length, 0);
278
+ }
279
+ }
280
+
281
+ /**
282
+ * Generate consolidated AsciiDoc partials for properties grouped by type.
283
+ *
284
+ * Creates separate .adoc files for each property type (cluster-properties.adoc,
285
+ * topic-properties.adoc, object-storage-properties.adoc, broker-properties.adoc)
286
+ * containing all properties of that type using the appropriate templates.
287
+ *
288
+ * @param {Object} properties - Map of properties (property name → property object).
289
+ * @param {string} partialsDir - Directory where consolidated property files will be written.
290
+ * @param {boolean} [hasCloudSupport=false] - If true, use cloud-aware templates.
291
+ * @returns {number} The total number of properties included in the consolidated partials.
292
+ */
293
+ function generatePropertyPartials(properties, partialsDir, hasCloudSupport = false) {
294
+ console.log(`📝 Generating consolidated property partials in ${partialsDir}…`);
295
+
296
+ // Use the appropriate template based on cloud support
297
+ const templateName = hasCloudSupport ? 'property-cloud' : 'property';
200
298
  const templatePath = getTemplatePath(
201
- path.join(__dirname, 'templates', 'property-page.hbs'),
202
- 'TEMPLATE_PROPERTY_PAGE'
299
+ path.join(__dirname, 'templates', `${templateName}.hbs`),
300
+ 'TEMPLATE_PROPERTY'
203
301
  );
204
302
  const template = handlebars.compile(fs.readFileSync(templatePath, 'utf8'));
205
303
 
206
- // Filter and group properties according to configuration
207
- const groups = config.groups.map(group => {
208
- const filteredProperties = Object.values(properties)
209
- .filter(prop => group.filter(prop))
210
- .sort((a, b) => String(a.name || '').localeCompare(String(b.name || '')));
211
-
212
- return {
213
- title: group.title,
214
- intro: group.intro,
215
- properties: filteredProperties,
216
- template: group.template || 'property' // Default to 'property' template
217
- };
218
- }).filter(group => group.properties.length > 0);
304
+ // Use the topic property template for topic properties
305
+ const topicTemplateName = hasCloudSupport ? 'topic-property-cloud' : 'topic-property';
306
+ const topicTemplatePath = getTemplatePath(
307
+ path.join(__dirname, 'templates', `${topicTemplateName}.hbs`),
308
+ 'TEMPLATE_TOPIC_PROPERTY'
309
+ );
310
+ const topicTemplate = handlebars.compile(fs.readFileSync(topicTemplatePath, 'utf8'));
219
311
 
220
- const data = {
221
- ...config,
222
- groups
312
+ // Create the main partials directory
313
+ const propertiesPartialsDir = path.join(partialsDir, 'properties');
314
+ fs.mkdirSync(propertiesPartialsDir, { recursive: true });
315
+
316
+ // Group properties by type
317
+ const propertyGroups = {
318
+ cluster: [],
319
+ topic: [],
320
+ broker: [],
321
+ 'object-storage': []
223
322
  };
224
323
 
225
- const output = template(data);
226
- const outputPath = path.join(outputDir, config.filename);
324
+ // Categorize properties
325
+ Object.values(properties).forEach(prop => {
326
+ if (!prop.name || !prop.config_scope) return; // Skip properties without names or scope
327
+
328
+ if (prop.config_scope === 'topic') {
329
+ propertyGroups.topic.push(prop);
330
+ } else if (prop.config_scope === 'broker') {
331
+ propertyGroups.broker.push(prop);
332
+ } else if (prop.config_scope === 'cluster') {
333
+ // Check if it's an object storage property
334
+ if (isObjectStorageProperty(prop)) {
335
+ propertyGroups['object-storage'].push(prop);
336
+ } else {
337
+ propertyGroups.cluster.push(prop);
338
+ }
339
+ }
340
+ });
341
+
342
+ let totalCount = 0;
227
343
 
228
- fs.mkdirSync(path.dirname(outputPath), { recursive: true });
229
- fs.writeFileSync(outputPath, output, 'utf8');
344
+ // Generate consolidated partials for each property type
345
+ Object.entries(propertyGroups).forEach(([type, props]) => {
346
+ if (props.length === 0) return;
347
+
348
+ // Sort properties by name
349
+ props.sort((a, b) => String(a.name || '').localeCompare(String(b.name || '')));
350
+
351
+ // Choose the appropriate template based on property type
352
+ const selectedTemplate = type === 'topic' ? topicTemplate : template;
353
+
354
+ // Generate content for all properties of this type
355
+ const content = props.map(prop => selectedTemplate(prop)).join('\n');
356
+
357
+ // Write the consolidated file
358
+ const filename = `${type}-properties.adoc`;
359
+ const outputPath = path.join(propertiesPartialsDir, filename);
360
+ fs.writeFileSync(outputPath, content, 'utf8');
361
+
362
+ console.log(`✅ Generated ${outputPath} with ${props.length} properties`);
363
+ totalCount += props.length;
364
+ });
230
365
 
231
- console.log(`✅ Generated ${outputPath}`);
232
- return groups.reduce((total, group) => total + group.properties.length, 0);
366
+ console.log(`✅ Generated consolidated property partials in ${partialsDir} (${totalCount} total properties)`);
367
+ return totalCount;
233
368
  }
234
369
 
235
370
  /**
@@ -269,7 +404,19 @@ function generateDeprecatedDocs(properties, outputDir) {
269
404
  };
270
405
 
271
406
  const output = template(data);
272
- const outputPath = path.join(outputDir, 'deprecated', 'partials', 'deprecated-properties.adoc');
407
+
408
+ // Determine the correct path for deprecated properties
409
+ let outputPath;
410
+ if (process.env.OUTPUT_PARTIALS_DIR) {
411
+ // Use the explicitly set partials directory
412
+ outputPath = path.join(process.env.OUTPUT_PARTIALS_DIR, 'deprecated', 'deprecated-properties.adoc');
413
+ } else if (outputDir.includes('pages/properties')) {
414
+ // Fallback: Navigate back from pages/properties to reference, then into partials/deprecated
415
+ outputPath = path.join(path.dirname(path.dirname(outputDir)), 'partials', 'deprecated', 'deprecated-properties.adoc');
416
+ } else {
417
+ // Fallback: Direct path when outputDir is the base directory
418
+ outputPath = path.join(outputDir, 'partials', 'deprecated', 'deprecated-properties.adoc');
419
+ }
273
420
 
274
421
  fs.mkdirSync(path.dirname(outputPath), { recursive: true });
275
422
  fs.writeFileSync(outputPath, output, 'utf8');
@@ -330,26 +477,68 @@ function generateAllDocs(inputFile, outputDir) {
330
477
  let totalObjectStorageProperties = 0;
331
478
  let totalTopicProperties = 0;
332
479
 
333
- // Generate each type of documentation
334
- for (const [type, config] of Object.entries(PROPERTY_CONFIG)) {
335
- const count = generatePropertyDocs(properties, config, path.join(outputDir, 'pages'));
336
- totalProperties += count;
480
+ // Generate complete property pages only if requested
481
+ if (process.env.GENERATE_PAGES === '1') {
482
+ console.log(`📄 Generating complete property pages...`);
483
+
484
+ // Generate each type of documentation
485
+ for (const [type, config] of Object.entries(PROPERTY_CONFIG)) {
486
+ const count = generatePropertyDocs(properties, config, outputDir);
487
+ totalProperties += count;
488
+
489
+ if (type === 'broker') totalBrokerProperties = count;
490
+ else if (type === 'cluster') totalClusterProperties = count;
491
+ else if (type === 'object-storage') totalObjectStorageProperties = count;
492
+ else if (type === 'topic') totalTopicProperties = count;
493
+ }
494
+ } else {
495
+ console.log(`📄 Skipping complete property pages (use --generate-pages to enable)`);
337
496
 
338
- if (type === 'broker') totalBrokerProperties = count;
339
- else if (type === 'cluster') totalClusterProperties = count;
340
- else if (type === 'object-storage') totalObjectStorageProperties = count;
341
- else if (type === 'topic') totalTopicProperties = count;
497
+ // Still count properties for summary
498
+ Object.values(properties).forEach(prop => {
499
+ if (prop.config_scope === 'broker' && !prop.is_deprecated) totalBrokerProperties++;
500
+ else if (prop.config_scope === 'cluster' && !prop.is_deprecated) {
501
+ if (prop.name && (
502
+ prop.name.includes('cloud_storage') ||
503
+ prop.name.includes('s3_') ||
504
+ prop.name.includes('azure_') ||
505
+ prop.name.includes('gcs_') ||
506
+ prop.name.includes('archival_') ||
507
+ prop.name.includes('remote_') ||
508
+ prop.name.includes('tiered_')
509
+ )) {
510
+ totalObjectStorageProperties++;
511
+ } else {
512
+ totalClusterProperties++;
513
+ }
514
+ }
515
+ else if (prop.config_scope === 'topic' && !prop.is_deprecated) totalTopicProperties++;
516
+ });
517
+ totalProperties = totalBrokerProperties + totalClusterProperties + totalObjectStorageProperties + totalTopicProperties;
342
518
  }
343
519
 
344
- // Generate deprecated properties documentation
345
- const deprecatedCount = generateDeprecatedDocs(properties, path.join(outputDir, 'pages'));
520
+ // Generate individual property partials if requested
521
+ let partialsCount = 0;
522
+ let deprecatedCount = 0;
523
+ if (process.env.GENERATE_PARTIALS === '1' && process.env.OUTPUT_PARTIALS_DIR) {
524
+ // Generate deprecated properties documentation
525
+ deprecatedCount = generateDeprecatedDocs(properties, outputDir);
526
+
527
+ partialsCount = generatePropertyPartials(properties, process.env.OUTPUT_PARTIALS_DIR, hasCloudSupport);
528
+ } else {
529
+ console.log(`📄 Skipping property partials (use --generate-partials to enable)`);
530
+ console.log(`📄 Skipping deprecated properties documentation (use --generate-partials to enable)`);
531
+ }
346
532
 
347
- // Generate summary file
348
- const allPropertiesContent = Object.keys(properties).sort().join('\n');
349
- fs.writeFileSync(path.join(outputDir, 'all_properties.txt'), allPropertiesContent, 'utf8');
533
+ // Generate error reports and add to input JSON output
534
+ const errorReport = generateErrorReports(properties, outputDir);
350
535
 
351
- // Generate error reports
352
- generateErrorReports(properties, outputDir);
536
+ // Add error arrays directly to the input file so they're included when copied
537
+ const inputData = JSON.parse(fs.readFileSync(inputFile, 'utf8'));
538
+ inputData.empty_descriptions = errorReport.empty_descriptions;
539
+ inputData.deprecated_properties = errorReport.deprecated_properties;
540
+ fs.writeFileSync(inputFile, JSON.stringify(inputData, null, 2), 'utf8');
541
+ console.log(`📝 Added error arrays to ${inputFile}`);
353
542
 
354
543
  console.log(`📊 Generation Summary:`);
355
544
  console.log(` Total properties read: ${Object.keys(properties).length}`);
@@ -358,6 +547,9 @@ function generateAllDocs(inputFile, outputDir) {
358
547
  console.log(` Total Object Storage properties: ${totalObjectStorageProperties}`);
359
548
  console.log(` Total Topic properties: ${totalTopicProperties}`);
360
549
  console.log(` Total Deprecated properties: ${deprecatedCount}`);
550
+ if (partialsCount > 0) {
551
+ console.log(` Total Property partials: ${partialsCount}`);
552
+ }
361
553
 
362
554
  return {
363
555
  totalProperties: Object.keys(properties).length,
@@ -365,7 +557,8 @@ function generateAllDocs(inputFile, outputDir) {
365
557
  clusterProperties: totalClusterProperties,
366
558
  objectStorageProperties: totalObjectStorageProperties,
367
559
  topicProperties: totalTopicProperties,
368
- deprecatedProperties: deprecatedCount
560
+ deprecatedProperties: deprecatedCount,
561
+ propertyPartials: partialsCount
369
562
  };
370
563
  }
371
564
 
@@ -373,9 +566,6 @@ function generateAllDocs(inputFile, outputDir) {
373
566
  * Generate error reports for properties with missing or invalid data
374
567
  */
375
568
  function generateErrorReports(properties, outputDir) {
376
- const errorDir = path.join(outputDir, 'error');
377
- fs.mkdirSync(errorDir, { recursive: true });
378
-
379
569
  const emptyDescriptions = [];
380
570
  const deprecatedProperties = [];
381
571
 
@@ -388,34 +578,25 @@ function generateErrorReports(properties, outputDir) {
388
578
  }
389
579
  });
390
580
 
391
- // Write error reports
581
+ // Add these arrays to the properties JSON file
392
582
  const totalProperties = Object.keys(properties).length;
393
-
394
- if (emptyDescriptions.length > 0) {
395
- fs.writeFileSync(
396
- path.join(errorDir, 'empty_description.txt'),
397
- emptyDescriptions.join('\n'),
398
- 'utf8'
399
- );
400
- const percentage = totalProperties > 0 ? ((emptyDescriptions.length / totalProperties) * 100).toFixed(2) : '0.00';
401
- console.log(`You have ${emptyDescriptions.length} properties with empty description. Percentage of errors: ${percentage}%. Data written in 'empty_description.txt'.`);
402
- }
583
+ const percentageEmpty = totalProperties > 0 ? ((emptyDescriptions.length / totalProperties) * 100).toFixed(2) : '0.00';
584
+ const percentageDeprecated = totalProperties > 0 ? ((deprecatedProperties.length / totalProperties) * 100).toFixed(2) : '0.00';
585
+ console.log(`You have ${emptyDescriptions.length} properties with empty description. Percentage of errors: ${percentageEmpty}%.`);
586
+ console.log(`You have ${deprecatedProperties.length} deprecated properties. Percentage of errors: ${percentageDeprecated}%.`);
403
587
 
404
- if (deprecatedProperties.length > 0) {
405
- fs.writeFileSync(
406
- path.join(errorDir, 'deprecated_properties.txt'),
407
- deprecatedProperties.join('\n'),
408
- 'utf8'
409
- );
410
- const percentage = totalProperties > 0 ? ((deprecatedProperties.length / totalProperties) * 100).toFixed(2) : '0.00';
411
- console.log(`You have ${deprecatedProperties.length} deprecated properties. Percentage of errors: ${percentage}%. Data written in 'deprecated_properties.txt'.`);
412
- }
588
+ // Return the arrays sorted for deterministic output
589
+ return {
590
+ empty_descriptions: emptyDescriptions.sort(),
591
+ deprecated_properties: deprecatedProperties.sort()
592
+ };
413
593
  }
414
594
 
415
595
  module.exports = {
416
596
  generateAllDocs,
417
597
  generatePropertyDocs,
418
598
  generateDeprecatedDocs,
599
+ generatePropertyPartials,
419
600
  PROPERTY_CONFIG
420
601
  };
421
602