@redpanda-data/docs-extensions-and-macros 4.5.0 → 4.6.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/README.adoc +0 -163
- package/bin/doc-tools.js +492 -283
- package/cli-utils/antora-utils.js +127 -0
- package/cli-utils/generate-cluster-docs.sh +41 -29
- package/cli-utils/self-managed-docs-branch.js +2 -1
- package/cli-utils/start-cluster.sh +70 -30
- package/extensions/generate-rp-connect-info.js +14 -9
- package/package.json +6 -5
- package/tools/redpanda-connect/generate-rpcn-connector-docs.js +233 -0
- package/tools/redpanda-connect/helpers/advancedConfig.js +17 -0
- package/tools/redpanda-connect/helpers/buildConfigYaml.js +53 -0
- package/tools/redpanda-connect/helpers/commonConfig.js +31 -0
- package/tools/redpanda-connect/helpers/eq.js +10 -0
- package/tools/redpanda-connect/helpers/index.js +19 -0
- package/tools/redpanda-connect/helpers/isObject.js +1 -0
- package/tools/redpanda-connect/helpers/join.js +6 -0
- package/tools/redpanda-connect/helpers/ne.js +10 -0
- package/tools/redpanda-connect/helpers/or.js +4 -0
- package/tools/redpanda-connect/helpers/renderConnectExamples.js +37 -0
- package/tools/redpanda-connect/helpers/renderConnectFields.js +148 -0
- package/tools/redpanda-connect/helpers/renderLeafField.js +64 -0
- package/tools/redpanda-connect/helpers/renderObjectField.js +41 -0
- package/tools/redpanda-connect/helpers/renderYamlList.js +24 -0
- package/tools/redpanda-connect/helpers/toYaml.js +11 -0
- package/tools/redpanda-connect/helpers/uppercase.js +9 -0
- package/tools/redpanda-connect/parse-csv-connectors.js +63 -0
- package/tools/redpanda-connect/report-delta.js +152 -0
- package/tools/redpanda-connect/templates/connector.hbs +20 -0
- package/tools/redpanda-connect/templates/examples-partials.hbs +7 -0
- package/tools/redpanda-connect/templates/fields-partials.hbs +13 -0
- package/tools/redpanda-connect/templates/intro.hbs +33 -0
- package/macros/data-template.js +0 -591
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const handlebars = require('handlebars');
|
|
6
|
+
const yaml = require('yaml');
|
|
7
|
+
const helpers = require('./helpers');
|
|
8
|
+
|
|
9
|
+
// Register each helper under handlebars, verifying that it’s a function
|
|
10
|
+
Object.entries(helpers).forEach(([name, fn]) => {
|
|
11
|
+
if (typeof fn !== 'function') {
|
|
12
|
+
console.error(`❌ Helper "${name}" is not a function`);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
handlebars.registerHelper(name, fn);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Default “main” template (connector.hbs) which invokes partials {{> intro}}, {{> fields}}, {{> examples}}
|
|
19
|
+
const DEFAULT_TEMPLATE = path.resolve(__dirname, './templates/connector.hbs');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Reads a file at `filePath` and registers it as a Handlebars partial called `name`.
|
|
23
|
+
* Throws if the file cannot be read.
|
|
24
|
+
*/
|
|
25
|
+
function registerPartial(name, filePath) {
|
|
26
|
+
const resolved = path.resolve(filePath);
|
|
27
|
+
let source;
|
|
28
|
+
try {
|
|
29
|
+
source = fs.readFileSync(resolved, 'utf8');
|
|
30
|
+
} catch (err) {
|
|
31
|
+
throw new Error(`Unable to read "${name}" template at ${resolved}: ${err.message}`);
|
|
32
|
+
}
|
|
33
|
+
handlebars.registerPartial(name, source);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Deep-merge `overrides` into `target`. Only 'description', 'type',
|
|
38
|
+
* plus nested array/object entries get overridden; other keys remain intact.
|
|
39
|
+
*/
|
|
40
|
+
function mergeOverrides(target, overrides) {
|
|
41
|
+
if (!overrides || typeof overrides !== 'object') return target;
|
|
42
|
+
if (!target || typeof target !== 'object') {
|
|
43
|
+
throw new Error('Target must be a valid object');
|
|
44
|
+
}
|
|
45
|
+
for (const key in overrides) {
|
|
46
|
+
if (Array.isArray(target[key]) && Array.isArray(overrides[key])) {
|
|
47
|
+
// Merge two parallel arrays by matching items on `.name`
|
|
48
|
+
target[key] = target[key].map(item => {
|
|
49
|
+
const overrideItem = overrides[key].find(o => o.name === item.name);
|
|
50
|
+
if (overrideItem) {
|
|
51
|
+
// Overwrite description/type if present
|
|
52
|
+
['description', 'type'].forEach(field => {
|
|
53
|
+
if (Object.hasOwn(overrideItem, field)) {
|
|
54
|
+
item[field] = overrideItem[field];
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
// Copy through selfManagedOnly flag
|
|
58
|
+
if (Object.hasOwn(overrideItem, 'selfManagedOnly')) {
|
|
59
|
+
item.selfManagedOnly = overrideItem.selfManagedOnly;
|
|
60
|
+
}
|
|
61
|
+
// Recurse for nested children
|
|
62
|
+
item = mergeOverrides(item, overrideItem);
|
|
63
|
+
}
|
|
64
|
+
return item;
|
|
65
|
+
});
|
|
66
|
+
} else if (
|
|
67
|
+
typeof target[key] === 'object' &&
|
|
68
|
+
typeof overrides[key] === 'object' &&
|
|
69
|
+
!Array.isArray(target[key]) &&
|
|
70
|
+
!Array.isArray(overrides[key])
|
|
71
|
+
) {
|
|
72
|
+
// Deep-merge plain objects
|
|
73
|
+
target[key] = mergeOverrides(target[key], overrides[key]);
|
|
74
|
+
} else if (['description', 'type'].includes(key) && Object.hasOwn(overrides, key)) {
|
|
75
|
+
// Overwrite the primitive
|
|
76
|
+
target[key] = overrides[key];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return target;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Generates documentation files for RPCN connectors using Handlebars templates.
|
|
84
|
+
*
|
|
85
|
+
* Depending on the {@link writeFullDrafts} flag, generates either partial documentation files for connector fields and examples, or full draft documentation for each connector component. Supports merging override data and skips draft generation for components marked as deprecated.
|
|
86
|
+
*
|
|
87
|
+
* @param {Object} options - Configuration options for documentation generation.
|
|
88
|
+
* @param {string} options.data - Path to the connector data file (JSON or YAML).
|
|
89
|
+
* @param {string} [options.overrides] - Optional path to a JSON file with override data.
|
|
90
|
+
* @param {string} options.template - Path to the main Handlebars template.
|
|
91
|
+
* @param {string} [options.templateIntro] - Path to the intro partial template (used in full draft mode).
|
|
92
|
+
* @param {string} [options.templateFields] - Path to the fields partial template.
|
|
93
|
+
* @param {string} [options.templateExamples] - Path to the examples partial template.
|
|
94
|
+
* @param {boolean} options.writeFullDrafts - If true, generates full draft documentation; otherwise, generates partials.
|
|
95
|
+
* @returns {Promise<Object>} An object summarizing the number and paths of generated partials and drafts.
|
|
96
|
+
*
|
|
97
|
+
* @throws {Error} If reading or parsing input files fails, or if template rendering fails for a component.
|
|
98
|
+
*
|
|
99
|
+
* @remark
|
|
100
|
+
* When generating full drafts, components with a `status` of `'deprecated'` are skipped.
|
|
101
|
+
*/
|
|
102
|
+
async function generateRpcnConnectorDocs(options) {
|
|
103
|
+
const {
|
|
104
|
+
data,
|
|
105
|
+
overrides,
|
|
106
|
+
template, // main Handlebars template (for full-draft mode)
|
|
107
|
+
templateIntro,
|
|
108
|
+
templateFields,
|
|
109
|
+
templateExamples,
|
|
110
|
+
writeFullDrafts
|
|
111
|
+
} = options;
|
|
112
|
+
|
|
113
|
+
// Read connector index (JSON or YAML)
|
|
114
|
+
const raw = fs.readFileSync(data, 'utf8');
|
|
115
|
+
const ext = path.extname(data).toLowerCase();
|
|
116
|
+
const dataObj = ext === '.json' ? JSON.parse(raw) : yaml.parse(raw);
|
|
117
|
+
|
|
118
|
+
// Apply overrides if provided
|
|
119
|
+
if (overrides) {
|
|
120
|
+
const ovRaw = fs.readFileSync(overrides, 'utf8');
|
|
121
|
+
const ovObj = JSON.parse(ovRaw);
|
|
122
|
+
mergeOverrides(dataObj, ovObj);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Compile the “main” template (used when writeFullDrafts = true)
|
|
126
|
+
const compiledTemplate = handlebars.compile(fs.readFileSync(template, 'utf8'));
|
|
127
|
+
|
|
128
|
+
// Determine which templates to use for “fields” and “examples”
|
|
129
|
+
// If templateFields is not provided, fall back to the single `template`.
|
|
130
|
+
// If templateExamples is not provided, skip examples entirely.
|
|
131
|
+
const fieldsTemplatePath = templateFields || template;
|
|
132
|
+
const examplesTemplatePath = templateExamples || null;
|
|
133
|
+
|
|
134
|
+
// Register partials
|
|
135
|
+
if (!writeFullDrafts) {
|
|
136
|
+
if (fieldsTemplatePath) {
|
|
137
|
+
registerPartial('fields', fieldsTemplatePath);
|
|
138
|
+
}
|
|
139
|
+
if (examplesTemplatePath) {
|
|
140
|
+
registerPartial('examples', examplesTemplatePath);
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
registerPartial('intro', templateIntro);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const outputRoot = path.resolve(process.cwd(), 'modules/components/partials');
|
|
147
|
+
const fieldsOutRoot = path.join(outputRoot, 'fields');
|
|
148
|
+
const examplesOutRoot = path.join(outputRoot, 'examples');
|
|
149
|
+
const draftsRoot = path.join(outputRoot, 'drafts');
|
|
150
|
+
|
|
151
|
+
if (!writeFullDrafts) {
|
|
152
|
+
fs.mkdirSync(fieldsOutRoot, { recursive: true });
|
|
153
|
+
fs.mkdirSync(examplesOutRoot, { recursive: true });
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
let partialsWritten = 0;
|
|
157
|
+
let draftsWritten = 0;
|
|
158
|
+
const partialFiles = [];
|
|
159
|
+
const draftFiles = [];
|
|
160
|
+
|
|
161
|
+
for (const [type, items] of Object.entries(dataObj)) {
|
|
162
|
+
if (!Array.isArray(items)) continue;
|
|
163
|
+
|
|
164
|
+
for (const item of items) {
|
|
165
|
+
if (!item.name) continue;
|
|
166
|
+
const name = item.name;
|
|
167
|
+
|
|
168
|
+
if (!writeFullDrafts) {
|
|
169
|
+
// Render fields using the registered “fields” partial
|
|
170
|
+
const fieldsOut = handlebars
|
|
171
|
+
.compile('{{> fields children=config.children}}')(item);
|
|
172
|
+
|
|
173
|
+
// Render examples only if an examples template was provided
|
|
174
|
+
let examplesOut = '';
|
|
175
|
+
if (examplesTemplatePath) {
|
|
176
|
+
examplesOut = handlebars
|
|
177
|
+
.compile('{{> examples examples=examples}}')(item);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (fieldsOut.trim()) {
|
|
181
|
+
const fPath = path.join(fieldsOutRoot, type, `${name}.adoc`);
|
|
182
|
+
fs.mkdirSync(path.dirname(fPath), { recursive: true });
|
|
183
|
+
fs.writeFileSync(fPath, fieldsOut);
|
|
184
|
+
partialsWritten++;
|
|
185
|
+
partialFiles.push(path.relative(process.cwd(), fPath));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (examplesOut.trim()) {
|
|
189
|
+
const ePath = path.join(examplesOutRoot, type, `${name}.adoc`);
|
|
190
|
+
fs.mkdirSync(path.dirname(ePath), { recursive: true });
|
|
191
|
+
fs.writeFileSync(ePath, examplesOut);
|
|
192
|
+
partialsWritten++;
|
|
193
|
+
partialFiles.push(path.relative(process.cwd(), ePath));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (writeFullDrafts) {
|
|
198
|
+
if (String(item.status || '').toLowerCase() === 'deprecated') {
|
|
199
|
+
console.log(`Skipping draft for deprecated component: ${type}/${name}`);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
let content;
|
|
203
|
+
try {
|
|
204
|
+
content = compiledTemplate(item);
|
|
205
|
+
} catch (err) {
|
|
206
|
+
throw new Error(`Template render failed for component "${name}": ${err.message}`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const draftSubdir = name === 'gateway'
|
|
210
|
+
? path.join(draftsRoot, 'cloud-only')
|
|
211
|
+
: draftsRoot;
|
|
212
|
+
|
|
213
|
+
const destFile = path.join(draftSubdir, `${name}.adoc`);
|
|
214
|
+
fs.mkdirSync(path.dirname(destFile), { recursive: true });
|
|
215
|
+
fs.writeFileSync(destFile, content, 'utf8');
|
|
216
|
+
draftsWritten++;
|
|
217
|
+
draftFiles.push(path.relative(process.cwd(), destFile));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
partialsWritten,
|
|
224
|
+
draftsWritten,
|
|
225
|
+
partialFiles,
|
|
226
|
+
draftFiles
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
module.exports = {
|
|
231
|
+
generateRpcnConnectorDocs,
|
|
232
|
+
mergeOverrides
|
|
233
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const buildConfigYaml = require('./buildConfigYaml.js');
|
|
2
|
+
const handlebars = require('handlebars');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Handlebars helper “advancedConfig”. Omits only deprecated.
|
|
6
|
+
*
|
|
7
|
+
* Usage in template:
|
|
8
|
+
* {{advancedConfig this.type this.name this.config.children}}
|
|
9
|
+
*/
|
|
10
|
+
module.exports = function advancedConfig(type, connectorName, children) {
|
|
11
|
+
if (typeof type !== 'string' || typeof connectorName !== 'string' || !Array.isArray(children)) {
|
|
12
|
+
return '';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const yamlText = buildConfigYaml(type, connectorName, children, /*includeAdvanced=*/ true);
|
|
16
|
+
return new handlebars.SafeString(yamlText);
|
|
17
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const renderLeafField = require('./renderLeafField');
|
|
2
|
+
const renderObjectField = require('./renderObjectField');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Builds either “Common” or “Advanced” YAML for one connector.
|
|
6
|
+
*
|
|
7
|
+
* - type = “input” or “output” (or whatever type)
|
|
8
|
+
* - connectorName = such as “amqp_1”
|
|
9
|
+
* - children = the array of field‐definitions (entry.config.children)
|
|
10
|
+
* - includeAdvanced = if false → only fields where is_advanced !== true
|
|
11
|
+
* if true → all fields (except deprecated)
|
|
12
|
+
*
|
|
13
|
+
* Structure produced:
|
|
14
|
+
*
|
|
15
|
+
* type:
|
|
16
|
+
* label: ""
|
|
17
|
+
* connectorName:
|
|
18
|
+
* ...child fields (with comments for “no default”)
|
|
19
|
+
*/
|
|
20
|
+
module.exports = function buildConfigYaml(type, connectorName, children, includeAdvanced) {
|
|
21
|
+
const lines = [];
|
|
22
|
+
|
|
23
|
+
// “type:” top‐level
|
|
24
|
+
lines.push(`${type}:`);
|
|
25
|
+
|
|
26
|
+
// Two‐space indent for “label”
|
|
27
|
+
lines.push(` label: ""`);
|
|
28
|
+
|
|
29
|
+
// Two‐space indent for connectorName heading
|
|
30
|
+
lines.push(` ${connectorName}:`);
|
|
31
|
+
|
|
32
|
+
// Four‐space indent for children
|
|
33
|
+
const baseIndent = 4;
|
|
34
|
+
children.forEach(field => {
|
|
35
|
+
if (field.is_deprecated) {
|
|
36
|
+
return; // skip deprecated fields
|
|
37
|
+
}
|
|
38
|
+
if (!includeAdvanced && field.is_advanced) {
|
|
39
|
+
return; // skip advanced fields in “common” mode
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (field.type === 'object' && Array.isArray(field.children)) {
|
|
43
|
+
// Render nested object
|
|
44
|
+
const nestedLines = renderObjectField(field, baseIndent);
|
|
45
|
+
lines.push(...nestedLines);
|
|
46
|
+
} else {
|
|
47
|
+
// Render a scalar or array leaf
|
|
48
|
+
lines.push(renderLeafField(field, baseIndent));
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return lines.join('\n');
|
|
53
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const buildConfigYaml = require('./buildConfigYaml.js');
|
|
2
|
+
const handlebars = require('handlebars');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Handlebars helper “commonConfig”. Omits deprecated + advanced.
|
|
6
|
+
*
|
|
7
|
+
* Usage in template:
|
|
8
|
+
* {{commonConfig this.type this.name this.config.children}}
|
|
9
|
+
*/
|
|
10
|
+
module.exports = function commonConfig(type, connectorName, children) {
|
|
11
|
+
if (typeof type !== 'string' || !type.trim()) {
|
|
12
|
+
console.warn('commonConfig: type must be a non-empty string');
|
|
13
|
+
return '';
|
|
14
|
+
}
|
|
15
|
+
if (typeof connectorName !== 'string' || !connectorName.trim()) {
|
|
16
|
+
console.warn('commonConfig: connectorName must be a non-empty string');
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
if (!Array.isArray(children)) {
|
|
20
|
+
console.warn('commonConfig: children must be an array');
|
|
21
|
+
return '';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const yamlText = buildConfigYaml(type, connectorName, children, /*includeAdvanced=*/ false);
|
|
26
|
+
return new handlebars.SafeString(yamlText);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error('Error in commonConfig helper:', error);
|
|
29
|
+
return new handlebars.SafeString('<!-- Error generating configuration -->');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
uppercase: require('./uppercase.js'),
|
|
5
|
+
eq: require('./eq.js'),
|
|
6
|
+
ne: require('./ne.js'),
|
|
7
|
+
join: require('./join.js'),
|
|
8
|
+
or: require('./or.js'),
|
|
9
|
+
toYaml: require('./toYaml.js'),
|
|
10
|
+
isObject: require('./isObject.js'),
|
|
11
|
+
renderYamlList: require('./renderYamlList.js'),
|
|
12
|
+
renderConnectFields: require('./renderConnectFields.js'),
|
|
13
|
+
renderConnectExamples: require('./renderConnectExamples.js'),
|
|
14
|
+
renderLeafField: require('./renderLeafField.js'),
|
|
15
|
+
renderObjectField: require('./renderObjectField.js'),
|
|
16
|
+
buildConfigYaml: require('./buildConfigYaml.js'),
|
|
17
|
+
commonConfig: require('./commonConfig.js'),
|
|
18
|
+
advancedConfig: require('./advancedConfig.js'),
|
|
19
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = v => v !== null && typeof v === 'object' && !Array.isArray(v)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const handlebars = require('handlebars');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Renders a list of examples.
|
|
5
|
+
*
|
|
6
|
+
* @param {Array<Object>} examples - An array of example objects.
|
|
7
|
+
* @returns {handlebars.SafeString} The rendered SafeString containing the examples.
|
|
8
|
+
*/
|
|
9
|
+
module.exports = function renderConnectExamples(examples) {
|
|
10
|
+
if (!examples || !Array.isArray(examples) || examples.length === 0) {
|
|
11
|
+
return '';
|
|
12
|
+
}
|
|
13
|
+
let output = '';
|
|
14
|
+
examples.forEach(example => {
|
|
15
|
+
if (example.title) {
|
|
16
|
+
const sanitizedTitle = example.title.replace(/[=]/g, '\\=');
|
|
17
|
+
output += `=== ${sanitizedTitle}\n\n`;
|
|
18
|
+
}
|
|
19
|
+
if (example.summary) {
|
|
20
|
+
output += `${example.summary}\n\n`;
|
|
21
|
+
}
|
|
22
|
+
if (example.config) {
|
|
23
|
+
if (typeof example.config !== 'string') {
|
|
24
|
+
console.warn('Example config must be a string, skipping');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const configContent = example.config.trim();
|
|
28
|
+
if (configContent.includes('----')) {
|
|
29
|
+
console.warn('Example config contains AsciiDoc delimiters, this may break rendering');
|
|
30
|
+
}
|
|
31
|
+
output += '[source,yaml]\n----\n';
|
|
32
|
+
output += configContent + '\n';
|
|
33
|
+
output += '----\n\n';
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
return new handlebars.SafeString(output);
|
|
37
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
const yaml = require('yaml');
|
|
2
|
+
const renderYamlList = require('./renderYamlList');
|
|
3
|
+
const handlebars = require('handlebars');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Renders the children of a configuration object into AsciiDoc.
|
|
7
|
+
*
|
|
8
|
+
* @param {Array<Object>} children - An array of child objects.
|
|
9
|
+
* @param {string} [prefix=''] - The prefix path for nested fields.
|
|
10
|
+
* @returns {handlebars.SafeString} The rendered SafeString containing the configuration details.
|
|
11
|
+
*/
|
|
12
|
+
module.exports = function renderConnectFields(children, prefix = '') {
|
|
13
|
+
if (!children || !Array.isArray(children) || children.length === 0) {
|
|
14
|
+
return '';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const sorted = [...children].sort((a, b) => {
|
|
18
|
+
const an = a.name || '';
|
|
19
|
+
const bn = b.name || '';
|
|
20
|
+
return an.localeCompare(bn, undefined, { sensitivity: 'base' });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
let output = '';
|
|
24
|
+
prefix = typeof prefix === 'string' ? prefix : '';
|
|
25
|
+
|
|
26
|
+
sorted.forEach(child => {
|
|
27
|
+
if (child.is_deprecated || !child.name) return;
|
|
28
|
+
|
|
29
|
+
// Normalize type
|
|
30
|
+
let displayType;
|
|
31
|
+
if (child.type === 'string' && child.kind === 'array') {
|
|
32
|
+
displayType = 'array';
|
|
33
|
+
} else if (child.type === 'unknown' && child.kind === 'map') {
|
|
34
|
+
displayType = 'object';
|
|
35
|
+
} else {
|
|
36
|
+
displayType = child.type;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let block = '';
|
|
40
|
+
const isArray = child.kind === 'array';
|
|
41
|
+
const currentPath = prefix
|
|
42
|
+
? `${prefix}.${child.name}${isArray ? '[]' : ''}`
|
|
43
|
+
: `${child.name}${isArray ? '[]' : ''}`;
|
|
44
|
+
|
|
45
|
+
block += `=== \`${currentPath}\`\n\n`;
|
|
46
|
+
|
|
47
|
+
if (child.description) {
|
|
48
|
+
block += `${child.description}\n\n`;
|
|
49
|
+
}
|
|
50
|
+
if (child.is_secret) {
|
|
51
|
+
block += `include::redpanda-connect:components:partial$secret_warning.adoc[]\n\n`;
|
|
52
|
+
}
|
|
53
|
+
if (child.version) {
|
|
54
|
+
block += `ifndef::env-cloud[]\nRequires version ${child.version} or later.\nendif::[]\n\n`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
block += `*Type*: \`${displayType}\`\n\n`;
|
|
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 {
|
|
65
|
+
const defYaml = yaml.stringify(child.default).trim();
|
|
66
|
+
block += `*Default*:\n[source,yaml]\n----\n${defYaml}\n----\n\n`;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Annotated options
|
|
71
|
+
if (child.annotated_options && child.annotated_options.length) {
|
|
72
|
+
block += `[cols=\"1m,2a\"]\n|===\n|Option |Summary\n\n`;
|
|
73
|
+
child.annotated_options.forEach(([opt, summary]) => {
|
|
74
|
+
block += `|${opt}\n|${summary}\n\n`;
|
|
75
|
+
});
|
|
76
|
+
block += `|===\n\n`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Options list
|
|
80
|
+
if (child.options && child.options.length) {
|
|
81
|
+
block += `*Options*: ${child.options.map(opt => `\`${opt}\``).join(', ')}\n\n`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Examples
|
|
85
|
+
if (child.examples && child.examples.length) {
|
|
86
|
+
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
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
child.examples.forEach(example => {
|
|
127
|
+
block += `${child.name}: \`${String(example)}\`\n`;
|
|
128
|
+
});
|
|
129
|
+
block += '\n';
|
|
130
|
+
}
|
|
131
|
+
block += `----\n\n`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Nested
|
|
135
|
+
if (child.children && child.children.length) {
|
|
136
|
+
block += renderConnectFields(child.children, currentPath);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Cloud guard
|
|
140
|
+
if (child.selfManagedOnly) {
|
|
141
|
+
output += `ifndef::env-cloud[]\n${block}endif::[]\n\n`;
|
|
142
|
+
} else {
|
|
143
|
+
output += block;
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
return new handlebars.SafeString(output);
|
|
148
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const yaml = require('yaml');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Renders a single “leaf” field (scalar or array) at the given indentation.
|
|
5
|
+
* If `field.default` is present, prints that. Otherwise prints an empty-string
|
|
6
|
+
* or empty-array plus an inline comment (# No default (optional/required)).
|
|
7
|
+
*
|
|
8
|
+
* @param {Object} field – one field object from “children”
|
|
9
|
+
* @param {number} indentLevel – number of spaces to indent
|
|
10
|
+
* @returns {string} – one line, including comment if needed
|
|
11
|
+
*/
|
|
12
|
+
module.exports = function renderLeafField(field, indentLevel) {
|
|
13
|
+
if (!field || typeof field !== 'object') {
|
|
14
|
+
throw new Error('renderLeafField: field must be an object');
|
|
15
|
+
}
|
|
16
|
+
if (typeof indentLevel !== 'number' || indentLevel < 0) {
|
|
17
|
+
throw new Error('renderLeafField: indentLevel must be a non-negative number');
|
|
18
|
+
}
|
|
19
|
+
if (!field.name || typeof field.name !== 'string') {
|
|
20
|
+
throw new Error('renderLeafField: field.name must be a non-empty string');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const indent = ' '.repeat(indentLevel);
|
|
24
|
+
const name = field.name;
|
|
25
|
+
|
|
26
|
+
// Decide whether optional or required
|
|
27
|
+
const optional = Boolean(field.is_optional);
|
|
28
|
+
const comment = optional
|
|
29
|
+
? '# No default (optional)'
|
|
30
|
+
: '# No default (required)';
|
|
31
|
+
|
|
32
|
+
// If a default is provided, use it:
|
|
33
|
+
if (field.default !== undefined) {
|
|
34
|
+
// If default is itself an object or array → dump as YAML block
|
|
35
|
+
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
|
|
41
|
+
.split('\n')
|
|
42
|
+
.map(line => ' '.repeat(indentLevel + 2) + line)
|
|
43
|
+
.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
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Otherwise, default is a primitive (string/number/bool)
|
|
52
|
+
if (field.type === 'string') {
|
|
53
|
+
return `${indent}${name}: ${yaml.stringify(field.default)}`;
|
|
54
|
+
}
|
|
55
|
+
return `${indent}${name}: ${field.default}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// No default → choose representation based on kind
|
|
59
|
+
if (field.kind === 'array') {
|
|
60
|
+
return `${indent}${name}: [] ${comment}`;
|
|
61
|
+
} else {
|
|
62
|
+
return `${indent}${name}: "" ${comment}`;
|
|
63
|
+
}
|
|
64
|
+
}
|