@redpanda-data/docs-extensions-and-macros 4.6.1 → 4.6.3

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
@@ -546,22 +546,27 @@ automation
546
546
  }
547
547
 
548
548
  if (options.draftMissing) {
549
- console.log('⏳ Drafting missing connectors...');
549
+ console.log('⏳ Drafting missing connectors');
550
550
  try {
551
551
  const connectorList = await parseCSVConnectors(options.csv, console);
552
- const validConnectors = connectorList.filter(row => row.name && row.type);
552
+ const validConnectors = connectorList.filter(r => r.name && r.type);
553
553
 
554
- const pagesRoot = path.resolve(process.cwd(), 'modules/components/pages');
554
+ const roots = {
555
+ pages: path.resolve(process.cwd(), 'modules/components/pages'),
556
+ partials:path.resolve(process.cwd(), 'modules/components/partials/components'),
557
+ };
558
+
559
+ // find any connector that has NO .adoc under pages/TYPEs or partials/TYPEs
555
560
  const allMissing = validConnectors.filter(({ name, type }) => {
556
- if (!name || !type) {
557
- console.warn(`⚠️ Skipping invalid connector entry:`, { name, type });
558
- return false;
559
- }
560
- const expected = path.join(pagesRoot, `${type}s`, `${name}.adoc`);
561
- return !fs.existsSync(expected);
561
+ const relPath = path.join(`${type}s`, `${name}.adoc`);
562
+ const existsInAny = Object.values(roots).some(root =>
563
+ fs.existsSync(path.join(root, relPath))
564
+ );
565
+ return !existsInAny;
562
566
  });
563
567
 
564
- const missingConnectors = allMissing.filter(({ name }) => !name.includes('sql_driver'));
568
+ // still skip sql_driver
569
+ const missingConnectors = allMissing.filter(c => !c.name.includes('sql_driver'));
565
570
 
566
571
  if (missingConnectors.length === 0) {
567
572
  console.log('✅ All connectors (excluding sql_drivers) already have docs—nothing to draft.');
@@ -572,17 +577,20 @@ automation
572
577
  });
573
578
  console.log('');
574
579
 
580
+ // build your filtered JSON as before…
575
581
  const rawData = fs.readFileSync(dataFile, 'utf8');
576
582
  const dataObj = JSON.parse(rawData);
577
-
578
583
  const filteredDataObj = {};
584
+
579
585
  for (const [key, arr] of Object.entries(dataObj)) {
580
586
  if (!Array.isArray(arr)) {
581
587
  filteredDataObj[key] = arr;
582
588
  continue;
583
589
  }
584
590
  filteredDataObj[key] = arr.filter(component =>
585
- missingConnectors.some(m => m.name === component.name && `${m.type}s` === key)
591
+ missingConnectors.some(
592
+ m => m.name === component.name && `${m.type}s` === key
593
+ )
586
594
  );
587
595
  }
588
596
 
@@ -590,12 +598,12 @@ automation
590
598
  fs.writeFileSync(tempDataPath, JSON.stringify(filteredDataObj, null, 2), 'utf8');
591
599
 
592
600
  const draftResult = await generateRpcnConnectorDocs({
593
- data: tempDataPath,
594
- overrides: options.overrides,
595
- template: options.templateMain,
596
- templateFields: options.templateFields,
597
- templateExamples: options.templateExamples,
598
- templateIntro: options.templateIntro,
601
+ data: tempDataPath,
602
+ overrides: options.overrides,
603
+ template: options.templateMain,
604
+ templateFields: options.templateFields,
605
+ templateExamples:options.templateExamples,
606
+ templateIntro: options.templateIntro,
599
607
  writeFullDrafts: true
600
608
  });
601
609
 
@@ -115,40 +115,58 @@ module.exports.register = function ({ config }) {
115
115
  let redpandaConnectUrl = '';
116
116
  let redpandaCloudUrl = '';
117
117
 
118
+ function isConnectorDocPath (filePath, type) {
119
+ const dirsToCheck = [
120
+ `pages/${type}s/`,
121
+ `partials/components/${type}s/`
122
+ ];
123
+ return dirsToCheck.some(dir => filePath.includes(dir));
124
+ }
125
+
126
+ function isCloudDoc(file, connector, type) {
127
+ return (
128
+ file.src.component === 'redpanda-cloud' &&
129
+ file.path.includes(`connect/components/${type}s/${connector}.adoc`)
130
+ );
131
+ }
132
+
118
133
  // Look for both Redpanda Connect and Cloud URLs
119
134
  for (const file of pages) {
120
- const component = file.src.component;
121
- const filePath = file.path;
135
+ const { component } = file.src; // such as 'redpanda-connect'
136
+ const { path: filePath } = file; // such as modules/.../pages/sinks/foo.adoc
122
137
 
123
138
  if (
124
139
  component === 'redpanda-connect' &&
125
140
  filePath.endsWith(`/${connector}.adoc`) &&
126
- filePath.includes(`pages/${type}s/`)
141
+ isConnectorDocPath(filePath, type)
127
142
  ) {
128
143
  redpandaConnectUrl = file.pub.url;
129
144
  }
130
145
 
131
- // Only check for Redpanda Cloud URLs if cloud is supported
132
- if (
133
- is_cloud_supported === 'y' &&
134
- component === 'redpanda-cloud' &&
135
- filePath.endsWith(`/${connector}.adoc`) &&
136
- filePath.includes(`${type}s/`)
137
- ) {
146
+ // -------------------------------------------------
147
+ // Redpanda Cloud (only if cloud supported)
148
+ // -------------------------------------------------
149
+ if (is_cloud_supported === 'y' && isCloudDoc(file, connector, type)) {
138
150
  redpandaCloudUrl = file.pub.url;
139
151
  }
140
152
  }
141
153
 
142
- // Log a warning if neither URL was found and the component is not deprecated
143
154
  if (
144
155
  deprecated !== 'y' &&
145
156
  !connector.includes('sql_driver') &&
146
157
  !redpandaConnectUrl &&
147
- (!redpandaCloudUrl && is_cloud_supported === 'y')
158
+ !(is_cloud_supported === 'y' && redpandaCloudUrl)
148
159
  ) {
149
- logger.warn(`Docs missing for: ${connector} of type: ${type}`);
160
+ logger.warn(`Self-Managed docs missing for: ${connector} of type: ${type}`);
150
161
  }
151
162
 
163
+ if (
164
+ is_cloud_supported === 'y' &&
165
+ !redpandaCloudUrl &&
166
+ redpandaConnectUrl
167
+ ) {
168
+ logger.warn(`Cloud docs missing for: ${connector} of type: ${type}`);
169
+ }
152
170
 
153
171
  // Return the translated and enriched row
154
172
  return {
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ module.exports.register = function (registry) {
4
+ registry.inlineMacro(function () {
5
+ const self = this;
6
+ self.named('badge');
7
+ self.process((parent, target, attrs) => {
8
+ const label = attrs.label || 'label';
9
+ const className = `badge--${label.toLowerCase().replace(/\s+/g, '-')}`;
10
+ const isLarge = attrs.size === 'large';
11
+ const sizeClass = isLarge ? 'badge--large' : '';
12
+ const tooltip = attrs.tooltip;
13
+ const tooltipAttr = tooltip ? ` data-tooltip="${tooltip}"` : '';
14
+
15
+ // Add brackets if not large
16
+ const renderedLabel = isLarge ? label : `(${label})`;
17
+
18
+ return `<span class="badge ${className} ${sizeClass}"${tooltipAttr}>${renderedLabel}</span>`;
19
+ });
20
+ });
21
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redpanda-data/docs-extensions-and-macros",
3
- "version": "4.6.1",
3
+ "version": "4.6.3",
4
4
  "description": "Antora extensions and macros developed for Redpanda documentation.",
5
5
  "keywords": [
6
6
  "antora",
@@ -57,7 +57,8 @@
57
57
  "./macros/glossary": "./macros/glossary.js",
58
58
  "./macros/rp-connect-components": "./macros/rp-connect-components.js",
59
59
  "./macros/config-ref": "./macros/config-ref.js",
60
- "./macros/helm-ref": "./macros/helm-ref.js"
60
+ "./macros/helm-ref": "./macros/helm-ref.js",
61
+ "./macros/badge": "./macros/badge.js"
61
62
  },
62
63
  "files": [
63
64
  "extensions",
@@ -35,47 +35,99 @@ function registerPartial(name, filePath) {
35
35
 
36
36
  /**
37
37
  * Deep-merge `overrides` into `target`. Only 'description', 'type',
38
- * plus nested array/object entries get overridden; other keys remain intact.
38
+ * 'annotated_field', 'examples', and known nested fields are overridden.
39
39
  */
40
40
  function mergeOverrides(target, overrides) {
41
41
  if (!overrides || typeof overrides !== 'object') return target;
42
42
  if (!target || typeof target !== 'object') {
43
43
  throw new Error('Target must be a valid object');
44
44
  }
45
+
46
+ const scalarKeys = ['description', 'type', 'annotated_field', 'version'];
47
+
45
48
  for (const key in overrides) {
49
+ // === Handle annotated_options ===
50
+ if (key === 'annotated_options' && Array.isArray(overrides[key]) && Array.isArray(target[key])) {
51
+ const overrideMap = new Map(overrides[key].map(([name, desc]) => [name, desc]));
52
+
53
+ target[key] = target[key].map(([name, desc]) => {
54
+ if (overrideMap.has(name)) {
55
+ return [name, overrideMap.get(name)];
56
+ }
57
+ return [name, desc];
58
+ });
59
+
60
+ const existingNames = new Set(target[key].map(([name]) => name));
61
+ for (const [name, desc] of overrides[key]) {
62
+ if (!existingNames.has(name)) {
63
+ target[key].push([name, desc]);
64
+ }
65
+ }
66
+ continue;
67
+ }
68
+
69
+ // === Handle examples ===
70
+ if (key === 'examples' && Array.isArray(overrides[key]) && Array.isArray(target[key])) {
71
+ const overrideMap = new Map(overrides[key].map(o => [o.title, o]));
72
+
73
+ target[key] = target[key].map(example => {
74
+ const override = overrideMap.get(example.title);
75
+ if (override) {
76
+ return {
77
+ ...example,
78
+ ...(override.summary && { summary: override.summary }),
79
+ ...(override.config && { config: override.config }),
80
+ };
81
+ }
82
+ return example;
83
+ });
84
+
85
+ const existingTitles = new Set(target[key].map(e => e.title));
86
+ for (const example of overrides[key]) {
87
+ if (!existingTitles.has(example.title)) {
88
+ target[key].push(example);
89
+ }
90
+ }
91
+ continue;
92
+ }
93
+
94
+ // === Merge arrays of objects with .name ===
46
95
  if (Array.isArray(target[key]) && Array.isArray(overrides[key])) {
47
- // Merge two parallel arrays by matching items on `.name`
48
96
  target[key] = target[key].map(item => {
49
97
  const overrideItem = overrides[key].find(o => o.name === item.name);
50
98
  if (overrideItem) {
51
- // Overwrite description/type if present
52
- ['description', 'type'].forEach(field => {
99
+ scalarKeys.forEach(field => {
53
100
  if (Object.hasOwn(overrideItem, field)) {
54
101
  item[field] = overrideItem[field];
55
102
  }
56
103
  });
57
- // Copy through selfManagedOnly flag
58
104
  if (Object.hasOwn(overrideItem, 'selfManagedOnly')) {
59
105
  item.selfManagedOnly = overrideItem.selfManagedOnly;
60
106
  }
61
- // Recurse for nested children
62
- item = mergeOverrides(item, overrideItem);
107
+ return mergeOverrides(item, overrideItem);
63
108
  }
64
109
  return item;
65
110
  });
66
- } else if (
111
+ continue;
112
+ }
113
+
114
+ // === Merge nested objects ===
115
+ if (
67
116
  typeof target[key] === 'object' &&
68
117
  typeof overrides[key] === 'object' &&
69
118
  !Array.isArray(target[key]) &&
70
119
  !Array.isArray(overrides[key])
71
120
  ) {
72
- // Deep-merge plain objects
73
121
  target[key] = mergeOverrides(target[key], overrides[key]);
74
- } else if (['description', 'type'].includes(key) && Object.hasOwn(overrides, key)) {
75
- // Overwrite the primitive
122
+ continue;
123
+ }
124
+
125
+ // === Overwrite scalar keys ===
126
+ if (scalarKeys.includes(key) && Object.hasOwn(overrides, key)) {
76
127
  target[key] = overrides[key];
77
128
  }
78
129
  }
130
+
79
131
  return target;
80
132
  }
81
133
 
@@ -185,7 +237,7 @@ async function generateRpcnConnectorDocs(options) {
185
237
  partialFiles.push(path.relative(process.cwd(), fPath));
186
238
  }
187
239
 
188
- if (examplesOut.trim()) {
240
+ if (examplesOut.trim() && type !== 'bloblang-functions' && type !== 'bloblang-methods') {
189
241
  const ePath = path.join(examplesOutRoot, type, `${name}.adoc`);
190
242
  fs.mkdirSync(path.dirname(ePath), { recursive: true });
191
243
  fs.writeFileSync(ePath, examplesOut);
@@ -26,7 +26,7 @@ module.exports = function renderConnectFields(children, prefix = '') {
26
26
  sorted.forEach(child => {
27
27
  if (child.is_deprecated || !child.name) return;
28
28
 
29
- // Normalize type
29
+ // Normalize type: arrays and unknown-map as object
30
30
  let displayType;
31
31
  if (child.type === 'string' && child.kind === 'array') {
32
32
  displayType = 'array';
@@ -56,27 +56,49 @@ module.exports = function renderConnectFields(children, prefix = '') {
56
56
 
57
57
  block += `*Type*: \`${displayType}\`\n\n`;
58
58
 
59
- // Default
60
- if (child.type !== 'object' && child.default !== undefined) {
61
- if (typeof child.default !== 'object') {
62
- const display = child.default === '' ? '""' : String(child.default);
63
- block += `*Default*: \`${display}\`\n\n`;
64
- } else {
59
+ // Default value
60
+ if (child.default !== undefined) {
61
+ // Empty array
62
+ if (Array.isArray(child.default) && child.default.length === 0) {
63
+ block += `*Default*: \`[]\`\n\n`;
64
+ }
65
+ // Empty object
66
+ else if (
67
+ child.default !== null &&
68
+ typeof child.default === 'object' &&
69
+ !Array.isArray(child.default) &&
70
+ Object.keys(child.default).length === 0
71
+ ) {
72
+ block += `*Default*: \`{}\`\n\n`;
73
+ }
74
+ // Complex object/array
75
+ else if (typeof child.default === 'object') {
65
76
  const defYaml = yaml.stringify(child.default).trim();
66
77
  block += `*Default*:\n[source,yaml]\n----\n${defYaml}\n----\n\n`;
67
78
  }
79
+ // Primitive
80
+ else {
81
+ const display = typeof child.default === 'string'
82
+ ? (child.default.startsWith('"') && child.default.endsWith('"')
83
+ ? child.default
84
+ : child.default === ''
85
+ ? '""'
86
+ : child.default)
87
+ : String(child.default);
88
+ block += `*Default*: \`${display}\`\n\n`;
89
+ }
68
90
  }
69
91
 
70
- // Annotated options
92
+ // Annotated options table
71
93
  if (child.annotated_options && child.annotated_options.length) {
72
- block += `[cols=\"1m,2a\"]\n|===\n|Option |Summary\n\n`;
94
+ block += `[cols="1m,2a"]\n|===\n|Option |Summary\n\n`;
73
95
  child.annotated_options.forEach(([opt, summary]) => {
74
96
  block += `|${opt}\n|${summary}\n\n`;
75
97
  });
76
98
  block += `|===\n\n`;
77
99
  }
78
100
 
79
- // Options list
101
+ // Simple options list
80
102
  if (child.options && child.options.length) {
81
103
  block += `*Options*: ${child.options.map(opt => `\`${opt}\``).join(', ')}\n\n`;
82
104
  }
@@ -84,54 +106,27 @@ module.exports = function renderConnectFields(children, prefix = '') {
84
106
  // Examples
85
107
  if (child.examples && child.examples.length) {
86
108
  block += `[source,yaml]\n----\n# Examples:\n`;
87
- if (child.type === 'string') {
88
- if (child.kind === 'array') {
89
- block += renderYamlList(child.name, child.examples);
90
- } else {
91
- child.examples.forEach(example => {
92
- if (typeof example === 'string' && example.includes('\n')) {
93
- block += `${child.name}: |-\n`;
94
- block += example.split('\n').map(line => ' ' + line).join('\n') + '\n';
95
- } else {
96
- block += `${child.name}: \`${example}\`\n`;
97
- }
98
- });
99
- block += '\n';
100
- }
101
- } else if (child.type === 'processor') {
102
- if (child.kind === 'array') {
103
- block += renderYamlList(child.name, child.examples);
104
- } else {
105
- child.examples.forEach(example => {
106
- block += `${child.name}: \`${String(example)}\`\n`;
107
- });
108
- block += '\n';
109
- }
110
- } else if (child.type === 'object') {
111
- if (child.kind === 'array') {
112
- block += renderYamlList(child.name, child.examples);
113
- } else {
114
- child.examples.forEach(example => {
115
- if (typeof example === 'object') {
116
- const snippet = yaml.stringify(example).trim();
117
- block += `${child.name}:\n`;
118
- block += snippet.split('\n').map(line => ' ' + line).join('\n') + '\n';
119
- } else {
120
- block += `${child.name}: \`${String(example)}\`\n`;
121
- }
122
- });
123
- block += '\n';
124
- }
109
+ if (child.kind === 'array') {
110
+ block += renderYamlList(child.name, child.examples);
125
111
  } else {
126
112
  child.examples.forEach(example => {
127
- block += `${child.name}: \`${String(example)}\`\n`;
113
+ if (typeof example === 'object') {
114
+ const snippet = yaml.stringify(example).trim();
115
+ block += `${child.name}:\n`;
116
+ block += snippet.split('\n').map(line => ' ' + line).join('\n') + '\n';
117
+ } else if (typeof example === 'string' && example.includes('\n')) {
118
+ block += `${child.name}: |-\n`;
119
+ block += example.split('\n').map(line => ' ' + line).join('\n') + '\n';
120
+ } else {
121
+ // Primitive values
122
+ block += `${child.name}: ${example}\n`;
123
+ }
128
124
  });
129
- block += '\n';
130
125
  }
131
126
  block += `----\n\n`;
132
127
  }
133
128
 
134
- // Nested
129
+ // Nested children
135
130
  if (child.children && child.children.length) {
136
131
  block += renderConnectFields(child.children, currentPath);
137
132
  }
@@ -31,34 +31,57 @@ module.exports = function renderLeafField(field, indentLevel) {
31
31
 
32
32
  // If a default is provided, use it:
33
33
  if (field.default !== undefined) {
34
- // If default is itself an object or array → dump as YAML block
34
+ // Empty array inline
35
+ if (Array.isArray(field.default) && field.default.length === 0) {
36
+ return `${indent}${name}: []`;
37
+ }
38
+ // Empty object inline
39
+ if (
40
+ field.default !== null &&
41
+ typeof field.default === 'object' &&
42
+ !Array.isArray(field.default) &&
43
+ Object.keys(field.default).length === 0
44
+ ) {
45
+ return `${indent}${name}: {}`;
46
+ }
47
+
48
+ // Complex object/array: dump as YAML block
35
49
  if (typeof field.default === 'object') {
36
- try {
37
- // Turn the object/array into a YAML string. We also need to indent that block
38
- const rawYaml = yaml.stringify(field.default).trim();
39
- // Indent each line of rawYaml by (indentLevel + 2) spaces:
40
- const indentedYaml = rawYaml
50
+ try {
51
+ const rawYaml = yaml.stringify(field.default).trim();
52
+ const indentedYaml = rawYaml
41
53
  .split('\n')
42
54
  .map(line => ' '.repeat(indentLevel + 2) + line)
43
55
  .join('\n');
44
- return `${indent}${name}:\n${indentedYaml}`;
45
- } catch (error) {
46
- console.warn(`Failed to serialize default value for field ${field.name}:`, error);
47
- return `${indent}${name}: {} # Error serializing default value`;
48
- }
56
+ return `${indent}${name}:\n${indentedYaml}`;
57
+ } catch (error) {
58
+ console.warn(`Failed to serialize default for ${field.name}:`, error);
59
+ return `${indent}${name}: {} # Error serializing default`;
60
+ }
49
61
  }
50
62
 
51
- // Otherwise, default is a primitive (string/number/bool)
52
- if (field.type === 'string') {
53
- return `${indent}${name}: ${yaml.stringify(field.default)}`;
63
+ // Primitive default: string, number, boolean
64
+ let value;
65
+ if (typeof field.default === 'string') {
66
+ // Preserve existing quotes
67
+ if (field.default.startsWith('"') && field.default.endsWith('"')) {
68
+ value = field.default;
69
+ } else if (field.default === '') {
70
+ value = '""';
71
+ } else {
72
+ value = field.default;
73
+ }
74
+ } else {
75
+ value = String(field.default);
54
76
  }
55
- return `${indent}${name}: ${field.default}`;
77
+
78
+ return `${indent}${name}: ${value}`;
56
79
  }
57
80
 
58
- // No default → choose representation based on kind
81
+ // No default → choose representation
59
82
  if (field.kind === 'array') {
60
83
  return `${indent}${name}: [] ${comment}`;
61
84
  } else {
62
85
  return `${indent}${name}: "" ${comment}`;
63
86
  }
64
- }
87
+ };
@@ -12,13 +12,27 @@ module.exports = function renderYamlList(name, exampleGroups) {
12
12
  exampleGroups.forEach(group => {
13
13
  const items = Array.isArray(group) ? group : [group];
14
14
  items.forEach(item => {
15
- const snippet = yaml.stringify(item).trim();
16
- const lines = snippet.split('\n');
17
- out += lines
18
- .map((line, idx) => (idx === 0 ? ` - ${line}` : ` ${line}`))
19
- .join('\n') + '\n';
15
+ // Scalars
16
+ if (typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean') {
17
+ let value = String(item);
18
+ // Quote when needed: already-quoted scalars stay as-is; otherwise quote `*`
19
+ // and any value containing YAML-special characters.
20
+ if (!(value.startsWith('"') && value.endsWith('"'))) {
21
+ if (value === '*' || /[:\[\]\{\},&>|%@`]/.test(value)) {
22
+ value = `"${value}"`;
23
+ }
24
+ }
25
+ out += ` - ${value}\n`;
26
+ } else {
27
+ // Objects/arrays: stringify with indentation
28
+ const snippet = yaml.stringify(item).trim();
29
+ const lines = snippet.split('\n');
30
+ out += lines
31
+ .map((line, idx) => (idx === 0 ? ` - ${line}` : ` ${line}`))
32
+ .join('\n') + '\n';
33
+ }
20
34
  });
21
35
  out += '\n';
22
36
  });
23
37
  return out;
24
- }
38
+ };
@@ -1,11 +0,0 @@
1
- 'use strict';
2
-
3
- module.exports.register = function (registry) {
4
- registry.inlineMacro(function () {
5
- const self = this;
6
- self.named('enterprise-label');
7
- self.process((parent, target, attrs) => {
8
- return `<span class="inline-enterprise-label">Enterprise</span>`;
9
- });
10
- });
11
- };