@redpanda-data/docs-extensions-and-macros 4.12.0 → 4.12.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 CHANGED
@@ -719,6 +719,18 @@ automation
719
719
  fs.unlinkSync(tmpFile);
720
720
  dataFile = finalFile;
721
721
  console.log(`āœ… Fetched and saved: ${finalFile}`);
722
+
723
+ // Keep only 2 most recent versions in docs-data
724
+ const dataFiles = fs.readdirSync(dataDir)
725
+ .filter(f => /^connect-\d+\.\d+\.\d+\.json$/.test(f))
726
+ .sort();
727
+
728
+ while (dataFiles.length > 2) {
729
+ const oldestFile = dataFiles.shift();
730
+ const oldestPath = path.join(dataDir, oldestFile);
731
+ fs.unlinkSync(oldestPath);
732
+ console.log(`🧹 Deleted old version from docs-data: ${oldestFile}`);
733
+ }
722
734
  } catch (err) {
723
735
  console.error(`āŒ Failed to fetch connectors: ${err.message}`);
724
736
  process.exit(1);
@@ -845,6 +857,45 @@ automation
845
857
  }
846
858
 
847
859
  const newIndex = JSON.parse(fs.readFileSync(dataFile, 'utf8'));
860
+
861
+ // Publish merged version with overrides to modules/components/attachments
862
+ if (options.overrides && fs.existsSync(options.overrides)) {
863
+ try {
864
+ const { mergeOverrides, resolveReferences } = require('../tools/redpanda-connect/generate-rpcn-connector-docs.js');
865
+
866
+ // Create a copy of newIndex to merge overrides into
867
+ const mergedData = JSON.parse(JSON.stringify(newIndex));
868
+
869
+ // Read and apply overrides
870
+ const ovRaw = fs.readFileSync(options.overrides, 'utf8');
871
+ const ovObj = JSON.parse(ovRaw);
872
+ const resolvedOverrides = resolveReferences(ovObj, ovObj);
873
+ mergeOverrides(mergedData, resolvedOverrides);
874
+
875
+ // Publish to modules/components/attachments
876
+ const attachmentsRoot = path.resolve(process.cwd(), 'modules/components/attachments');
877
+ fs.mkdirSync(attachmentsRoot, { recursive: true });
878
+
879
+ // Delete older versions from modules/components/attachments
880
+ const existingFiles = fs.readdirSync(attachmentsRoot)
881
+ .filter(f => /^connect-\d+\.\d+\.\d+\.json$/.test(f))
882
+ .sort();
883
+
884
+ for (const oldFile of existingFiles) {
885
+ const oldFilePath = path.join(attachmentsRoot, oldFile);
886
+ fs.unlinkSync(oldFilePath);
887
+ console.log(`🧹 Deleted old version: ${oldFile}`);
888
+ }
889
+
890
+ // Save merged version to modules/components/attachments
891
+ const destFile = path.join(attachmentsRoot, `connect-${newVersion}.json`);
892
+ fs.writeFileSync(destFile, JSON.stringify(mergedData, null, 2), 'utf8');
893
+ console.log(`āœ… Published merged version to: ${path.relative(process.cwd(), destFile)}`);
894
+ } catch (err) {
895
+ console.error(`āŒ Failed to publish merged version: ${err.message}`);
896
+ }
897
+ }
898
+
848
899
  printDeltaReport(oldIndex, newIndex);
849
900
 
850
901
  // Generate JSON diff file for whats-new.adoc
@@ -895,6 +946,93 @@ automation
895
946
 
896
947
  // Optionally update whats-new.adoc
897
948
  if (options.updateWhatsNew) {
949
+ // Helper function to cap description to two sentences
950
+ const capToTwoSentences = (description) => {
951
+ if (!description) return '';
952
+
953
+ // Helper to check if text contains problematic content
954
+ const hasProblematicContent = (text) => {
955
+ return /```[\s\S]*?```/.test(text) || // code blocks
956
+ /`[^`]+`/.test(text) || // inline code
957
+ /^[=#]+\s+.+$/m.test(text) || // headings
958
+ /\n/.test(text); // newlines
959
+ };
960
+
961
+ // Step 1: Replace common abbreviations and ellipses with placeholders
962
+ const abbreviations = [
963
+ /\bv\d+\.\d+(?:\.\d+)?/gi, // version numbers like v4.12 or v4.12.0 (must come before decimal)
964
+ /\d+\.\d+/g, // decimal numbers
965
+ /\be\.g\./gi, // e.g.
966
+ /\bi\.e\./gi, // i.e.
967
+ /\betc\./gi, // etc.
968
+ /\bvs\./gi, // vs.
969
+ /\bDr\./gi, // Dr.
970
+ /\bMr\./gi, // Mr.
971
+ /\bMs\./gi, // Ms.
972
+ /\bMrs\./gi, // Mrs.
973
+ /\bSt\./gi, // St.
974
+ /\bNo\./gi // No.
975
+ ];
976
+
977
+ let normalized = description;
978
+ const placeholders = [];
979
+
980
+ // Replace abbreviations with placeholders
981
+ abbreviations.forEach((abbrevRegex, idx) => {
982
+ normalized = normalized.replace(abbrevRegex, (match) => {
983
+ const placeholder = `__ABBREV${idx}_${placeholders.length}__`;
984
+ placeholders.push({ placeholder, original: match });
985
+ return placeholder;
986
+ });
987
+ });
988
+
989
+ // Replace ellipses (three or more dots) with placeholder
990
+ normalized = normalized.replace(/\.{3,}/g, (match) => {
991
+ const placeholder = `__ELLIPSIS_${placeholders.length}__`;
992
+ placeholders.push({ placeholder, original: match });
993
+ return placeholder;
994
+ });
995
+
996
+ // Step 2: Split sentences using the regex
997
+ const sentenceRegex = /[^.!?]+[.!?]+(?:\s|$)/g;
998
+ const sentences = normalized.match(sentenceRegex);
999
+
1000
+ if (!sentences || sentences.length === 0) {
1001
+ // Restore placeholders and return original
1002
+ let result = normalized;
1003
+ placeholders.forEach(({ placeholder, original }) => {
1004
+ result = result.replace(placeholder, original);
1005
+ });
1006
+ return result;
1007
+ }
1008
+
1009
+ // Step 3: Determine how many sentences to include
1010
+ let maxSentences = 2;
1011
+
1012
+ // If we have at least 2 sentences, check if the second one has problematic content
1013
+ if (sentences.length >= 2) {
1014
+ // Restore placeholders in second sentence to check original content
1015
+ let secondSentence = sentences[1];
1016
+ placeholders.forEach(({ placeholder, original }) => {
1017
+ secondSentence = secondSentence.replace(new RegExp(placeholder, 'g'), original);
1018
+ });
1019
+
1020
+ // If second sentence has problematic content, only take first sentence
1021
+ if (hasProblematicContent(secondSentence)) {
1022
+ maxSentences = 1;
1023
+ }
1024
+ }
1025
+
1026
+ let result = sentences.slice(0, maxSentences).join('');
1027
+
1028
+ // Step 4: Restore placeholders back to original text
1029
+ placeholders.forEach(({ placeholder, original }) => {
1030
+ result = result.replace(new RegExp(placeholder, 'g'), original);
1031
+ });
1032
+
1033
+ return result.trim();
1034
+ };
1035
+
898
1036
  try {
899
1037
  const whatsNewPath = path.join(findRepoRoot(), 'modules/get-started/pages/whats-new.adoc');
900
1038
  if (!fs.existsSync(whatsNewPath)) {
@@ -956,7 +1094,7 @@ automation
956
1094
  for (const comp of comps) {
957
1095
  section += `** xref:components:${type}/${comp.name}.adoc[\`${comp.name}\`]`;
958
1096
  if (comp.status) section += ` (${comp.status})`;
959
- if (comp.description) section += `: ${comp.description}`;
1097
+ if (comp.description) section += `: ${capToTwoSentences(comp.description)}`;
960
1098
  section += '\n';
961
1099
  }
962
1100
  }
@@ -977,6 +1115,60 @@ automation
977
1115
  description: field.description || '',
978
1116
  });
979
1117
  }
1118
+ for (const [type, fields] of Object.entries(fieldsByType)) {
1119
+ section += `* ${type.charAt(0).toUpperCase() + type.slice(1)}:\n`;
1120
+ // Group by component name
1121
+ const byComp = {};
1122
+ for (const f of fields) {
1123
+ if (!byComp[f.compName]) byComp[f.compName] = [];
1124
+ byComp[f.compName].push(f);
1125
+ }
1126
+ for (const [comp, compFields] of Object.entries(byComp)) {
1127
+ section += `** xref:components:${type}/${comp}.adoc[\`${comp}\`]:`;
1128
+ section += '\n';
1129
+ for (const f of compFields) {
1130
+ section += `*** xref:components:${type}/${comp}.adoc#${f.field}[\`${f.field}\`]`;
1131
+ if (f.description) section += `: ${capToTwoSentences(f.description)}`;
1132
+ section += '\n';
1133
+ }
1134
+ }
1135
+ }
1136
+ }
1137
+
1138
+ // Deprecated components
1139
+ if (diff.details.deprecatedComponents && diff.details.deprecatedComponents.length) {
1140
+ section += '\n=== Deprecations\n\n';
1141
+ section += 'The following components are now deprecated:\n\n';
1142
+ // Group by type
1143
+ const byType = {};
1144
+ for (const comp of diff.details.deprecatedComponents) {
1145
+ if (!byType[comp.type]) byType[comp.type] = [];
1146
+ byType[comp.type].push(comp);
1147
+ }
1148
+ for (const [type, comps] of Object.entries(byType)) {
1149
+ section += `* ${type.charAt(0).toUpperCase() + type.slice(1)}:\n`;
1150
+ for (const comp of comps) {
1151
+ section += `** xref:components:${type}/${comp.name}.adoc[\`${comp.name}\`]\n`;
1152
+ }
1153
+ }
1154
+ }
1155
+
1156
+ // Deprecated fields
1157
+ if (diff.details.deprecatedFields && diff.details.deprecatedFields.length) {
1158
+ if (!diff.details.deprecatedComponents || diff.details.deprecatedComponents.length === 0) {
1159
+ section += '\n=== Deprecations\n\n';
1160
+ }
1161
+ section += '\nThe following fields are now deprecated:\n\n';
1162
+ // Group deprecated fields by component type
1163
+ const fieldsByType = {};
1164
+ for (const field of diff.details.deprecatedFields) {
1165
+ const [type, compName] = field.component.split(':');
1166
+ if (!fieldsByType[type]) fieldsByType[type] = [];
1167
+ fieldsByType[type].push({
1168
+ compName,
1169
+ field: field.field
1170
+ });
1171
+ }
980
1172
  for (const [type, fields] of Object.entries(fieldsByType)) {
981
1173
  section += `* ${type.charAt(0).toUpperCase() + type.slice(1)} components\n`;
982
1174
  // Group by component name
@@ -986,23 +1178,20 @@ automation
986
1178
  byComp[f.compName].push(f);
987
1179
  }
988
1180
  for (const [comp, compFields] of Object.entries(byComp)) {
989
- section += `** xref:components:${type}/${comp}.adoc['${comp}']`;
1181
+ section += `** xref:components:${type}/${comp}.adoc[\`${comp}\`]`;
990
1182
  if (compFields.length === 1) {
991
1183
  const f = compFields[0];
992
- section += `: xref:components:${type}/${comp}.adoc#${f.field}['${f.field}']`;
993
- if (f.description) section += ` - ${f.description}`;
994
- section += '\n';
1184
+ section += `: xref:components:${type}/${comp}.adoc#${f.field}[\`${f.field}\`]\n`;
995
1185
  } else {
996
1186
  section += '\n';
997
1187
  for (const f of compFields) {
998
- section += `*** xref:components:${type}/${comp}.adoc#${f.field}['${f.field}']`;
999
- if (f.description) section += ` - ${f.description}`;
1000
- section += '\n';
1188
+ section += `*** xref:components:${type}/${comp}.adoc#${f.field}[\`${f.field}\`]\n`;
1001
1189
  }
1002
1190
  }
1003
1191
  }
1004
1192
  }
1005
1193
  }
1194
+
1006
1195
  let updated;
1007
1196
  if (startIdx !== -1) {
1008
1197
  // Replace the existing section
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redpanda-data/docs-extensions-and-macros",
3
- "version": "4.12.0",
3
+ "version": "4.12.1",
4
4
  "description": "Antora extensions and macros developed for Redpanda documentation.",
5
5
  "keywords": [
6
6
  "antora",
@@ -138,7 +138,39 @@ module.exports = function renderConnectFields(children, prefix = '') {
138
138
  if (child.examples && child.examples.length) {
139
139
  block += `[source,yaml]\n----\n# Examples:\n`;
140
140
  if (child.kind === 'array') {
141
- block += renderYamlList(child.name, child.examples);
141
+ // Render arrays in flow style (with brackets) instead of block style
142
+ child.examples.forEach(example => {
143
+ if (Array.isArray(example)) {
144
+ // Format as flow style: fieldName: [item1, item2, ...]
145
+ const items = example.map(item => {
146
+ if (typeof item === 'string') {
147
+ // Check if quoting is needed
148
+ const needsQuoting = item === '*' ||
149
+ /[:\[\]\{\},&>|%@`"]/.test(item) ||
150
+ /^[\s]|[\s]$/.test(item); // leading/trailing whitespace
151
+
152
+ if (needsQuoting) {
153
+ // Escape backslashes first, then double quotes
154
+ const escaped = item.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
155
+ return `"${escaped}"`;
156
+ }
157
+ return item;
158
+ }
159
+ // For non-strings, convert to string
160
+ const strValue = String(item);
161
+ // Check if the stringified value needs quoting
162
+ if (/[:\[\]\{\},&>|%@`"]/.test(strValue) || /^[\s]|[\s]$/.test(strValue)) {
163
+ const escaped = strValue.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
164
+ return `"${escaped}"`;
165
+ }
166
+ return strValue;
167
+ });
168
+ block += `${child.name}: [${items.join(', ')}]\n`;
169
+ } else {
170
+ // Fallback for non-array examples (shouldn't happen for array fields)
171
+ block += `${child.name}: ${example}\n`;
172
+ }
173
+ });
142
174
  } else {
143
175
  child.examples.forEach(example => {
144
176
  if (typeof example === 'object') {
@@ -1,3 +1,5 @@
1
+ const { execSync } = require('child_process');
2
+
1
3
  /**
2
4
  * Generate a JSON diff report between two connector index objects.
3
5
  * @param {object} oldIndex - Previous version connector index
@@ -78,6 +80,66 @@ function generateConnectorDiffJson(oldIndex, newIndex, opts = {}) {
78
80
  });
79
81
  });
80
82
 
83
+ // Newly deprecated components (exist in both versions but became deprecated)
84
+ const deprecatedComponents = [];
85
+ Object.keys(newMap).forEach(cKey => {
86
+ if (!(cKey in oldMap)) return;
87
+ const oldStatus = (oldMap[cKey].raw.status || '').toLowerCase();
88
+ const newStatus = (newMap[cKey].raw.status || '').toLowerCase();
89
+ if (oldStatus !== 'deprecated' && newStatus === 'deprecated') {
90
+ const [type, name] = cKey.split(':');
91
+ const raw = newMap[cKey].raw;
92
+ deprecatedComponents.push({
93
+ name,
94
+ type,
95
+ status: raw.status || raw.type || '',
96
+ version: raw.version || raw.introducedInVersion || '',
97
+ description: raw.description || ''
98
+ });
99
+ }
100
+ });
101
+
102
+ // Newly deprecated fields (exist in both versions but became deprecated)
103
+ const deprecatedFields = [];
104
+ Object.keys(newMap).forEach(cKey => {
105
+ if (!(cKey in oldMap)) return;
106
+ const oldFieldsArr = oldMap[cKey].fields || [];
107
+ const newFieldsArr = newMap[cKey].fields || [];
108
+
109
+ // Check fields that exist in both versions
110
+ const commonFields = oldFieldsArr.filter(f => newFieldsArr.includes(f));
111
+ commonFields.forEach(fName => {
112
+ const [type, compName] = cKey.split(':');
113
+
114
+ // Get old field object
115
+ let oldFieldObj = null;
116
+ if (type === 'config') {
117
+ oldFieldObj = (oldMap[cKey].raw.children || []).find(f => f.name === fName);
118
+ } else {
119
+ oldFieldObj = (oldMap[cKey].raw.config?.children || []).find(f => f.name === fName);
120
+ }
121
+
122
+ // Get new field object
123
+ let newFieldObj = null;
124
+ if (type === 'config') {
125
+ newFieldObj = (newMap[cKey].raw.children || []).find(f => f.name === fName);
126
+ } else {
127
+ newFieldObj = (newMap[cKey].raw.config?.children || []).find(f => f.name === fName);
128
+ }
129
+
130
+ const oldDeprecated = oldFieldObj && (oldFieldObj.is_deprecated === true || oldFieldObj.deprecated === true || (oldFieldObj.status || '').toLowerCase() === 'deprecated');
131
+ const newDeprecated = newFieldObj && (newFieldObj.is_deprecated === true || newFieldObj.deprecated === true || (newFieldObj.status || '').toLowerCase() === 'deprecated');
132
+
133
+ if (!oldDeprecated && newDeprecated) {
134
+ deprecatedFields.push({
135
+ component: cKey,
136
+ field: fName,
137
+ description: newFieldObj && newFieldObj.description
138
+ });
139
+ }
140
+ });
141
+ });
142
+
81
143
  return {
82
144
  comparison: {
83
145
  oldVersion: opts.oldVersion || '',
@@ -88,23 +150,20 @@ function generateConnectorDiffJson(oldIndex, newIndex, opts = {}) {
88
150
  newComponents: newComponents.length,
89
151
  removedComponents: removedComponents.length,
90
152
  newFields: newFields.length,
91
- removedFields: removedFields.length
153
+ removedFields: removedFields.length,
154
+ deprecatedComponents: deprecatedComponents.length,
155
+ deprecatedFields: deprecatedFields.length
92
156
  },
93
157
  details: {
94
158
  newComponents,
95
159
  removedComponents,
96
160
  newFields,
97
- removedFields
161
+ removedFields,
162
+ deprecatedComponents,
163
+ deprecatedFields
98
164
  }
99
165
  };
100
166
  }
101
- // tools/redpanda-connect/report-delta.js
102
- 'use strict';
103
-
104
- const fs = require('fs');
105
- const path = require('path');
106
- const yaml = require('yaml');
107
- const { execSync } = require('child_process');
108
167
 
109
168
  function discoverComponentKeys(obj) {
110
169
  return Object.keys(obj).filter(key => Array.isArray(obj[key]));
@@ -209,6 +268,47 @@ function printDeltaReport(oldIndex, newIndex) {
209
268
  });
210
269
  });
211
270
 
271
+ // Newly deprecated components
272
+ const deprecatedComponentKeys = [];
273
+ Object.keys(newMap).forEach(cKey => {
274
+ if (!(cKey in oldMap)) return;
275
+ const oldStatus = (oldMap[cKey].raw.status || '').toLowerCase();
276
+ const newStatus = (newMap[cKey].raw.status || '').toLowerCase();
277
+ if (oldStatus !== 'deprecated' && newStatus === 'deprecated') {
278
+ deprecatedComponentKeys.push(cKey);
279
+ }
280
+ });
281
+
282
+ // Newly deprecated fields
283
+ const deprecatedFieldsList = [];
284
+ Object.keys(newMap).forEach(cKey => {
285
+ if (!(cKey in oldMap)) return;
286
+ const oldFieldsArr = oldMap[cKey].fields || [];
287
+ const newFieldsArr = newMap[cKey].fields || [];
288
+ const commonFields = oldFieldsArr.filter(f => newFieldsArr.includes(f));
289
+
290
+ commonFields.forEach(fName => {
291
+ const [type, compName] = cKey.split(':');
292
+ let oldFieldObj = null;
293
+ if (type === 'config') {
294
+ oldFieldObj = (oldMap[cKey].raw.children || []).find(f => f.name === fName);
295
+ } else {
296
+ oldFieldObj = (oldMap[cKey].raw.config?.children || []).find(f => f.name === fName);
297
+ }
298
+ let newFieldObj = null;
299
+ if (type === 'config') {
300
+ newFieldObj = (newMap[cKey].raw.children || []).find(f => f.name === fName);
301
+ } else {
302
+ newFieldObj = (newMap[cKey].raw.config?.children || []).find(f => f.name === fName);
303
+ }
304
+ const oldDeprecated = oldFieldObj && (oldFieldObj.is_deprecated === true || oldFieldObj.deprecated === true || (oldFieldObj.status || '').toLowerCase() === 'deprecated');
305
+ const newDeprecated = newFieldObj && (newFieldObj.is_deprecated === true || newFieldObj.deprecated === true || (newFieldObj.status || '').toLowerCase() === 'deprecated');
306
+ if (!oldDeprecated && newDeprecated) {
307
+ deprecatedFieldsList.push({ component: cKey, field: fName });
308
+ }
309
+ });
310
+ });
311
+
212
312
  console.log('\nšŸ“‹ RPCN Connector Delta Report\n');
213
313
 
214
314
  if (newComponentKeys.length) {
@@ -242,6 +342,29 @@ function printDeltaReport(oldIndex, newIndex) {
242
342
  } else {
243
343
  console.log('āž¤ No newly added fields.\n');
244
344
  }
345
+
346
+ if (deprecatedComponentKeys.length) {
347
+ console.log('āž¤ Newly deprecated components:');
348
+ deprecatedComponentKeys.forEach(key => {
349
+ const [type, name] = key.split(':');
350
+ const raw = newMap[key].raw;
351
+ console.log(` • ${type}/${name}`);
352
+ });
353
+ console.log('');
354
+ } else {
355
+ console.log('āž¤ No newly deprecated components.\n');
356
+ }
357
+
358
+ if (deprecatedFieldsList.length) {
359
+ console.log('āž¤ Newly deprecated fields:');
360
+ deprecatedFieldsList.forEach(entry => {
361
+ const { component, field } = entry;
362
+ console.log(` • ${component} → ${field}`);
363
+ });
364
+ console.log('');
365
+ } else {
366
+ console.log('āž¤ No newly deprecated fields.\n');
367
+ }
245
368
  }
246
369
 
247
370
  module.exports = {