@redpanda-data/docs-extensions-and-macros 4.9.0 → 4.10.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.
@@ -209,6 +209,31 @@ function resolveReferences(obj, root) {
209
209
  * When generating full drafts, components with a `status` of `'deprecated'` are skipped.
210
210
  */
211
211
  async function generateRpcnConnectorDocs(options) {
212
+ // Types and output folders for bloblang function/method partials
213
+ const bloblangTypes = [
214
+ { key: 'bloblang-functions', folder: 'bloblang-functions' },
215
+ { key: 'bloblang-methods', folder: 'bloblang-methods' }
216
+ ];
217
+ // Recursively mark is_beta on any field/component with description starting with BETA:
218
+ function markBeta(obj) {
219
+ if (!obj || typeof obj !== 'object') return;
220
+ if (Array.isArray(obj)) {
221
+ obj.forEach(markBeta);
222
+ return;
223
+ }
224
+ // Mark as beta if description starts with 'BETA:' (case-insensitive, trims leading whitespace)
225
+ if (typeof obj.description === 'string' && /^\s*BETA:\s*/i.test(obj.description)) {
226
+ obj.is_beta = true;
227
+ }
228
+ // Recurse into children/config/fields
229
+ if (Array.isArray(obj.children)) obj.children.forEach(markBeta);
230
+ if (obj.config && Array.isArray(obj.config.children)) obj.config.children.forEach(markBeta);
231
+ // For connector/component arrays
232
+ for (const key of Object.keys(obj)) {
233
+ if (Array.isArray(obj[key])) obj[key].forEach(markBeta);
234
+ }
235
+ }
236
+
212
237
  const {
213
238
  data,
214
239
  overrides,
@@ -216,6 +241,7 @@ async function generateRpcnConnectorDocs(options) {
216
241
  templateIntro,
217
242
  templateFields,
218
243
  templateExamples,
244
+ templateBloblang,
219
245
  writeFullDrafts
220
246
  } = options;
221
247
 
@@ -223,16 +249,38 @@ async function generateRpcnConnectorDocs(options) {
223
249
  const raw = fs.readFileSync(data, 'utf8');
224
250
  const ext = path.extname(data).toLowerCase();
225
251
  const dataObj = ext === '.json' ? JSON.parse(raw) : yaml.parse(raw);
252
+ // Mark beta fields/components before overrides
253
+ markBeta(dataObj);
226
254
 
227
255
  // Apply overrides if provided
228
256
  if (overrides) {
229
257
  const ovRaw = fs.readFileSync(overrides, 'utf8');
230
258
  const ovObj = JSON.parse(ovRaw);
231
-
232
259
  // Resolve any $ref references in the overrides
233
260
  const resolvedOverrides = resolveReferences(ovObj, ovObj);
234
-
235
261
  mergeOverrides(dataObj, resolvedOverrides);
262
+
263
+ // Special: merge bloblang_methods and bloblang_functions from overrides into main data
264
+ for (const [overrideKey, mainKey] of [
265
+ ['bloblang_methods', 'bloblang-methods'],
266
+ ['bloblang_functions', 'bloblang-functions']
267
+ ]) {
268
+ if (Array.isArray(resolvedOverrides[overrideKey])) {
269
+ if (!Array.isArray(dataObj[mainKey])) dataObj[mainKey] = [];
270
+ // Merge by name
271
+ const mainArr = dataObj[mainKey];
272
+ const overrideArr = resolvedOverrides[overrideKey];
273
+ for (const overrideItem of overrideArr) {
274
+ if (!overrideItem.name) continue;
275
+ const idx = mainArr.findIndex(i => i.name === overrideItem.name);
276
+ if (idx !== -1) {
277
+ mainArr[idx] = { ...mainArr[idx], ...overrideItem };
278
+ } else {
279
+ mainArr.push(overrideItem);
280
+ }
281
+ }
282
+ }
283
+ }
236
284
  }
237
285
 
238
286
  // Compile the “main” template (used when writeFullDrafts = true)
@@ -260,6 +308,7 @@ async function generateRpcnConnectorDocs(options) {
260
308
  const fieldsOutRoot = path.join(outputRoot, 'fields');
261
309
  const examplesOutRoot = path.join(outputRoot, 'examples');
262
310
  const draftsRoot = path.join(outputRoot, 'drafts');
311
+ const configExamplesRoot = path.resolve(process.cwd(), 'modules/components/examples');
263
312
 
264
313
  if (!writeFullDrafts) {
265
314
  fs.mkdirSync(fieldsOutRoot, { recursive: true });
@@ -332,6 +381,51 @@ async function generateRpcnConnectorDocs(options) {
332
381
  }
333
382
  }
334
383
 
384
+ // Bloblang function/method partials (only if includeBloblang is true)
385
+ if (options.includeBloblang) {
386
+ for (const { key, folder } of bloblangTypes) {
387
+ const items = dataObj[key];
388
+ if (!Array.isArray(items)) continue;
389
+ const outRoot = path.join(outputRoot, folder);
390
+ fs.mkdirSync(outRoot, { recursive: true });
391
+ // Use custom or default template
392
+ const bloblangTemplatePath = templateBloblang || path.resolve(__dirname, './templates/bloblang-function.hbs');
393
+ const bloblangTemplate = handlebars.compile(fs.readFileSync(bloblangTemplatePath, 'utf8'));
394
+ for (const fn of items) {
395
+ if (!fn.name) continue;
396
+ const adoc = bloblangTemplate(fn);
397
+ const outPath = path.join(outRoot, `${fn.name}.adoc`);
398
+ fs.writeFileSync(outPath, adoc, 'utf8');
399
+ partialsWritten++;
400
+ partialFiles.push(path.relative(process.cwd(), outPath));
401
+ }
402
+ }
403
+ }
404
+
405
+ // Common/Advanced config snippet YAMLs in modules/components/examples
406
+ const commonConfig = helpers.commonConfig;
407
+ const advancedConfig = helpers.advancedConfig;
408
+ for (const [type, items] of Object.entries(dataObj)) {
409
+ if (!Array.isArray(items)) continue;
410
+ for (const item of items) {
411
+ if (!item.name || !item.config || !Array.isArray(item.config.children)) continue;
412
+ // Common config
413
+ const commonYaml = commonConfig(type, item.name, item.config.children);
414
+ const commonPath = path.join(configExamplesRoot, 'common', type, `${item.name}.yaml`);
415
+ fs.mkdirSync(path.dirname(commonPath), { recursive: true });
416
+ fs.writeFileSync(commonPath, commonYaml.toString(), 'utf8');
417
+ partialsWritten++;
418
+ partialFiles.push(path.relative(process.cwd(), commonPath));
419
+ // Advanced config
420
+ const advYaml = advancedConfig(type, item.name, item.config.children);
421
+ const advPath = path.join(configExamplesRoot, 'advanced', type, `${item.name}.yaml`);
422
+ fs.mkdirSync(path.dirname(advPath), { recursive: true });
423
+ fs.writeFileSync(advPath, advYaml.toString(), 'utf8');
424
+ partialsWritten++;
425
+ partialFiles.push(path.relative(process.cwd(), advPath));
426
+ }
427
+ }
428
+
335
429
  return {
336
430
  partialsWritten,
337
431
  draftsWritten,
@@ -0,0 +1,42 @@
1
+ // Bloblang example formatting helper for Handlebars
2
+ function bloblangExample(example) {
3
+ if (typeof example === 'object' && example !== null && example.mapping) {
4
+ let codeBlock = '';
5
+ if (example.summary && example.summary.trim()) {
6
+ codeBlock += `# ${example.summary.trim().replace(/\n/g, '\n# ')}\n\n`;
7
+ }
8
+ if (typeof example.mapping === 'string') {
9
+ codeBlock += example.mapping.trim() + '\n';
10
+ }
11
+ if (Array.isArray(example.results)) {
12
+ for (const pair of example.results) {
13
+ if (Array.isArray(pair) && pair.length === 2) {
14
+ codeBlock += `\n# In: ${pair[0]}\n# Out: ${pair[1]}\n`;
15
+ }
16
+ }
17
+ }
18
+ return `[,coffeescript]\n----\n${codeBlock.trim()}\n----\n`;
19
+ } else {
20
+ let exStr = '';
21
+ if (typeof example === 'string') {
22
+ exStr = example;
23
+ } else if (typeof example === 'object' && example !== null) {
24
+ if (example.code) {
25
+ exStr = example.code;
26
+ } else if (example.example) {
27
+ exStr = example.example;
28
+ } else {
29
+ try {
30
+ exStr = require('yaml').stringify(example).trim();
31
+ } catch {
32
+ exStr = JSON.stringify(example, null, 2);
33
+ }
34
+ }
35
+ } else {
36
+ exStr = String(example);
37
+ }
38
+ return `[source,coffeescript]\n----\n${exStr}\n----\n`;
39
+ }
40
+ }
41
+
42
+ module.exports = bloblangExample;
@@ -4,10 +4,10 @@ module.exports = {
4
4
  uppercase: require('./uppercase.js'),
5
5
  eq: require('./eq.js'),
6
6
  ne: require('./ne.js'),
7
- join: require('./join.js'),
7
+ join: require('./join.js'),
8
8
  or: require('./or.js'),
9
- toYaml: require('./toYaml.js'),
10
- isObject: require('./isObject.js'),
9
+ toYaml: require('./toYaml.js'),
10
+ isObject: require('./isObject.js'),
11
11
  renderYamlList: require('./renderYamlList.js'),
12
12
  renderConnectFields: require('./renderConnectFields.js'),
13
13
  renderConnectExamples: require('./renderConnectExamples.js'),
@@ -16,4 +16,5 @@ module.exports = {
16
16
  buildConfigYaml: require('./buildConfigYaml.js'),
17
17
  commonConfig: require('./commonConfig.js'),
18
18
  advancedConfig: require('./advancedConfig.js'),
19
+ bloblangExample: require('./bloblangExample.js'),
19
20
  };
@@ -28,7 +28,10 @@ module.exports = function renderConnectFields(children, prefix = '') {
28
28
 
29
29
  // Normalize types
30
30
  let displayType;
31
- if (child.type === 'string' && child.kind === 'array') {
31
+ const isArrayTitle = typeof child.name === 'string' && child.name.endsWith('[]');
32
+ if (isArrayTitle) {
33
+ displayType = 'array<object>';
34
+ } else if (child.type === 'string' && child.kind === 'array') {
32
35
  displayType = 'array';
33
36
  } else if (child.type === 'unknown' && child.kind === 'map') {
34
37
  displayType = 'object';
@@ -42,14 +45,38 @@ module.exports = function renderConnectFields(children, prefix = '') {
42
45
 
43
46
  let block = '';
44
47
  const isArray = child.kind === 'array';
48
+ // Only append [] if kind is array and name does not already end with []
49
+ const nameWithArray = (typeof child.name === 'string' && isArray && !child.name.endsWith('[]'))
50
+ ? `${child.name}[]`
51
+ : child.name;
45
52
  const currentPath = prefix
46
- ? `${prefix}.${child.name}${isArray ? '[]' : ''}`
47
- : `${child.name}${isArray ? '[]' : ''}`;
53
+ ? `${prefix}.${nameWithArray}`
54
+ : `${nameWithArray}`;
48
55
 
49
56
  block += `=== \`${currentPath}\`\n\n`;
50
57
 
51
- if (child.description) {
52
- block += `${child.description}\n\n`;
58
+ // --- Beta badge logic (now uses is_beta) ---
59
+ let desc = child.description || '';
60
+ if (child.is_beta) {
61
+ // Remove any leading "BETA:" label (case-insensitive, trims leading whitespace)
62
+ desc = 'badge::[label=Beta, size=large, tooltip={page-beta-text}]\n\n' + desc.replace(/^\s*BETA:\s*/i, '');
63
+ }
64
+
65
+ // --- Interpolation support notice ---
66
+ const interpolationNotice = 'This field supports xref:configuration:interpolation.adoc#bloblang-queries[interpolation functions].';
67
+ if (child.interpolated === true) {
68
+ // Only add if not already present (case-insensitive)
69
+ const descLower = desc.toLowerCase();
70
+ if (!descLower.includes('interpolation functions')) {
71
+ desc = desc.trim() + (desc.trim() ? '\n\n' : '') + interpolationNotice;
72
+ }
73
+ } else {
74
+ // If interpolation is not true, remove the notice if present
75
+ desc = desc.replace(new RegExp(interpolationNotice.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), '').replace(/\n{2,}/g, '\n\n');
76
+ }
77
+
78
+ if (desc) {
79
+ block += `${desc}\n\n`;
53
80
  }
54
81
  if (child.is_secret) {
55
82
  block += `include::redpanda-connect:components:partial$secret_warning.adoc[]\n\n`;
@@ -1,3 +1,103 @@
1
+ /**
2
+ * Generate a JSON diff report between two connector index objects.
3
+ * @param {object} oldIndex - Previous version connector index
4
+ * @param {object} newIndex - Current version connector index
5
+ * @param {object} opts - { oldVersion, newVersion, timestamp }
6
+ * @returns {object} JSON diff report
7
+ */
8
+ function generateConnectorDiffJson(oldIndex, newIndex, opts = {}) {
9
+ const oldMap = buildComponentMap(oldIndex);
10
+ const newMap = buildComponentMap(newIndex);
11
+
12
+ // New components
13
+ const newComponentKeys = Object.keys(newMap).filter(k => !(k in oldMap));
14
+ const newComponents = newComponentKeys.map(key => {
15
+ const [type, name] = key.split(':');
16
+ const raw = newMap[key].raw;
17
+ return {
18
+ name,
19
+ type,
20
+ status: raw.status || raw.type || '',
21
+ version: raw.version || raw.introducedInVersion || '',
22
+ description: raw.description || ''
23
+ };
24
+ });
25
+
26
+ // Removed components
27
+ const removedComponentKeys = Object.keys(oldMap).filter(k => !(k in newMap));
28
+ const removedComponents = removedComponentKeys.map(key => {
29
+ const [type, name] = key.split(':');
30
+ const raw = oldMap[key].raw;
31
+ return {
32
+ name,
33
+ type,
34
+ status: raw.status || raw.type || '',
35
+ version: raw.version || raw.introducedInVersion || '',
36
+ description: raw.description || ''
37
+ };
38
+ });
39
+
40
+ // New fields under existing components
41
+ const newFields = [];
42
+ Object.keys(newMap).forEach(cKey => {
43
+ if (!(cKey in oldMap)) return;
44
+ const oldFields = new Set(oldMap[cKey].fields || []);
45
+ const newFieldsArr = newMap[cKey].fields || [];
46
+ newFieldsArr.forEach(fName => {
47
+ if (!oldFields.has(fName)) {
48
+ const [type, compName] = cKey.split(':');
49
+ let rawFieldObj = null;
50
+ if (type === 'config') {
51
+ rawFieldObj = (newMap[cKey].raw.children || []).find(f => f.name === fName);
52
+ } else {
53
+ rawFieldObj = (newMap[cKey].raw.config?.children || []).find(f => f.name === fName);
54
+ }
55
+ newFields.push({
56
+ component: cKey,
57
+ field: fName,
58
+ introducedIn: rawFieldObj && (rawFieldObj.introducedInVersion || rawFieldObj.version),
59
+ description: rawFieldObj && rawFieldObj.description
60
+ });
61
+ }
62
+ });
63
+ });
64
+
65
+ // Removed fields under existing components
66
+ const removedFields = [];
67
+ Object.keys(oldMap).forEach(cKey => {
68
+ if (!(cKey in newMap)) return;
69
+ const newFieldsSet = new Set(newMap[cKey].fields || []);
70
+ const oldFieldsArr = oldMap[cKey].fields || [];
71
+ oldFieldsArr.forEach(fName => {
72
+ if (!newFieldsSet.has(fName)) {
73
+ removedFields.push({
74
+ component: cKey,
75
+ field: fName
76
+ });
77
+ }
78
+ });
79
+ });
80
+
81
+ return {
82
+ comparison: {
83
+ oldVersion: opts.oldVersion || '',
84
+ newVersion: opts.newVersion || '',
85
+ timestamp: opts.timestamp || new Date().toISOString()
86
+ },
87
+ summary: {
88
+ newComponents: newComponents.length,
89
+ removedComponents: removedComponents.length,
90
+ newFields: newFields.length,
91
+ removedFields: removedFields.length
92
+ },
93
+ details: {
94
+ newComponents,
95
+ removedComponents,
96
+ newFields,
97
+ removedFields
98
+ }
99
+ };
100
+ }
1
101
  // tools/redpanda-connect/report-delta.js
2
102
  'use strict';
3
103
 
@@ -149,4 +249,5 @@ module.exports = {
149
249
  buildComponentMap,
150
250
  getRpkConnectVersion,
151
251
  printDeltaReport,
252
+ generateConnectorDiffJson,
152
253
  };
@@ -0,0 +1,28 @@
1
+ // This content is autogenerated. Do not edit manually. To override descriptions, use the doc-tools CLI with the --overrides option: https://redpandadata.atlassian.net/wiki/spaces/DOC/pages/1247543314/Generate+reference+docs+for+Redpanda+Connect
2
+
3
+ = {{name}}
4
+
5
+ {{#if signature}}
6
+ *Signature*: `{{signature}}`
7
+ {{/if}}
8
+
9
+ {{#if description}}
10
+ {{description}}
11
+ {{/if}}
12
+
13
+
14
+ {{#if examples}}
15
+ == Examples
16
+
17
+ {{#each examples}}
18
+ {{{bloblangExample this}}}
19
+ {{/each}}
20
+ {{/if}}
21
+
22
+ {{#if related}}
23
+ == Related
24
+
25
+ {{#each related}}
26
+ * {{this}}
27
+ {{/each}}
28
+ {{/if}}