@redpanda-data/docs-extensions-and-macros 4.15.6 → 4.15.8
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 +2 -0
- package/package.json +1 -1
- package/tools/redpanda-connect/AUTOMATION.md +834 -0
- package/tools/redpanda-connect/generate-rpcn-connector-docs.js +107 -1
- package/tools/redpanda-connect/github-release-utils.js +275 -0
- package/tools/redpanda-connect/helpers/bloblangExample.js +23 -3
- package/tools/redpanda-connect/helpers/buildConfigYaml.js +13 -8
- package/tools/redpanda-connect/helpers/ensurePeriod.js +27 -0
- package/tools/redpanda-connect/helpers/hasOptionalParams.js +7 -0
- package/tools/redpanda-connect/helpers/index.js +3 -0
- package/tools/redpanda-connect/helpers/toSentenceCase.js +69 -0
- package/tools/redpanda-connect/multi-version-summary.js +92 -0
- package/tools/redpanda-connect/parse-csv-connectors.js +1 -0
- package/tools/redpanda-connect/pr-summary-formatter.js +672 -25
- package/tools/redpanda-connect/report-delta.js +58 -2
- package/tools/redpanda-connect/rpcn-connector-docs-handler.js +462 -66
- package/tools/redpanda-connect/templates/bloblang-function.hbs +28 -2
- package/tools/redpanda-connect/templates/bloblang-functions-overview.hbs +29 -0
- package/tools/redpanda-connect/templates/bloblang-methods-overview.hbs +39 -0
- package/tools/redpanda-connect/templates/connector.hbs +2 -2
- package/tools/redpanda-connect/templates/intro.hbs +1 -1
|
@@ -244,7 +244,8 @@ async function generateRpcnConnectorDocs(options) {
|
|
|
244
244
|
templateBloblang,
|
|
245
245
|
writeFullDrafts,
|
|
246
246
|
cgoOnly = [], // Array of cgo-only connectors from cgo binary inspection
|
|
247
|
-
cloudOnly = []
|
|
247
|
+
cloudOnly = [], // Array of cloud-only connectors from cloud binary inspection
|
|
248
|
+
csvMetadata = [] // Array of CSV metadata with support levels
|
|
248
249
|
} = options;
|
|
249
250
|
|
|
250
251
|
// Read connector index (JSON or YAML)
|
|
@@ -274,6 +275,16 @@ async function generateRpcnConnectorDocs(options) {
|
|
|
274
275
|
});
|
|
275
276
|
}
|
|
276
277
|
|
|
278
|
+
// Build a Map of CSV metadata for fast support level lookup
|
|
279
|
+
const csvMetadataMap = new Map();
|
|
280
|
+
if (Array.isArray(csvMetadata)) {
|
|
281
|
+
csvMetadata.forEach(item => {
|
|
282
|
+
if (item.type && item.name) {
|
|
283
|
+
csvMetadataMap.set(`${item.type}:${item.name}`, item);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
277
288
|
// Apply overrides if provided
|
|
278
289
|
if (overrides && fs.existsSync(overrides)) {
|
|
279
290
|
const ovRaw = fs.readFileSync(overrides, 'utf8');
|
|
@@ -395,6 +406,7 @@ async function generateRpcnConnectorDocs(options) {
|
|
|
395
406
|
}
|
|
396
407
|
|
|
397
408
|
// Check if this connector is cgo-only or cloud-only and mark it
|
|
409
|
+
// Use plural type for CGO/cloud detection (matches test expectations)
|
|
398
410
|
const connectorKey = `${type}:${name}`;
|
|
399
411
|
const isCloudOnly = cloudOnlySet.has(connectorKey);
|
|
400
412
|
const isCgoOnly = cgoOnlySet.has(connectorKey);
|
|
@@ -411,6 +423,13 @@ async function generateRpcnConnectorDocs(options) {
|
|
|
411
423
|
item.cloudOnly = true;
|
|
412
424
|
}
|
|
413
425
|
|
|
426
|
+
// Lookup support level from CSV metadata using singular type
|
|
427
|
+
const csvKey = `${item.type}:${name}`;
|
|
428
|
+
const csvData = csvMetadataMap.get(csvKey);
|
|
429
|
+
if (csvData && csvData.support) {
|
|
430
|
+
item.support = csvData.support;
|
|
431
|
+
}
|
|
432
|
+
|
|
414
433
|
let content;
|
|
415
434
|
try {
|
|
416
435
|
content = compiledTemplate(item);
|
|
@@ -453,6 +472,28 @@ async function generateRpcnConnectorDocs(options) {
|
|
|
453
472
|
if (!Array.isArray(items)) continue;
|
|
454
473
|
const outRoot = path.join(outputRoot, folder);
|
|
455
474
|
fs.mkdirSync(outRoot, { recursive: true });
|
|
475
|
+
|
|
476
|
+
// Get current item names
|
|
477
|
+
const currentNames = new Set(items.filter(fn => fn.name).map(fn => fn.name));
|
|
478
|
+
|
|
479
|
+
// Delete partials that no longer exist in the data
|
|
480
|
+
if (fs.existsSync(outRoot)) {
|
|
481
|
+
const existingFiles = fs.readdirSync(outRoot).filter(f => f.endsWith('.adoc'));
|
|
482
|
+
for (const file of existingFiles) {
|
|
483
|
+
const name = file.replace('.adoc', '');
|
|
484
|
+
// Skip hand-authored files (by convention, start with underscore)
|
|
485
|
+
if (name.startsWith('_')) {
|
|
486
|
+
console.log(`Skipping hand-authored file: ${file}`);
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
if (!currentNames.has(name)) {
|
|
490
|
+
const filePath = path.join(outRoot, file);
|
|
491
|
+
fs.unlinkSync(filePath);
|
|
492
|
+
console.log(`Deleted removed ${folder} partial: ${file}`);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
456
497
|
// Use custom or default template
|
|
457
498
|
const bloblangTemplatePath = templateBloblang || path.resolve(__dirname, './templates/bloblang-function.hbs');
|
|
458
499
|
const bloblangTemplate = handlebars.compile(fs.readFileSync(bloblangTemplatePath, 'utf8'));
|
|
@@ -465,6 +506,71 @@ async function generateRpcnConnectorDocs(options) {
|
|
|
465
506
|
partialFiles.push(path.relative(process.cwd(), outPath));
|
|
466
507
|
}
|
|
467
508
|
}
|
|
509
|
+
|
|
510
|
+
// Generate overview pages for Bloblang methods and functions
|
|
511
|
+
const guidesRoot = path.join(process.cwd(), 'modules/guides/pages/bloblang');
|
|
512
|
+
fs.mkdirSync(guidesRoot, { recursive: true });
|
|
513
|
+
|
|
514
|
+
// Generate methods.adoc with categorized methods
|
|
515
|
+
if (dataObj['bloblang-methods']) {
|
|
516
|
+
const methodsData = dataObj['bloblang-methods'];
|
|
517
|
+
const categoriesMap = new Map();
|
|
518
|
+
|
|
519
|
+
// Extract categories and their methods
|
|
520
|
+
for (const method of methodsData) {
|
|
521
|
+
if (!method.name || !method.categories || !Array.isArray(method.categories)) continue;
|
|
522
|
+
|
|
523
|
+
for (const cat of method.categories) {
|
|
524
|
+
const categoryName = cat.Category;
|
|
525
|
+
if (!categoryName) continue;
|
|
526
|
+
|
|
527
|
+
if (!categoriesMap.has(categoryName)) {
|
|
528
|
+
categoriesMap.set(categoryName, []);
|
|
529
|
+
}
|
|
530
|
+
categoriesMap.get(categoryName).push(method.name);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Sort categories and prepare data for template
|
|
535
|
+
const categories = Array.from(categoriesMap.entries())
|
|
536
|
+
.map(([name, methods]) => ({
|
|
537
|
+
name,
|
|
538
|
+
methods: methods.sort()
|
|
539
|
+
}))
|
|
540
|
+
.sort((a, b) => {
|
|
541
|
+
// Special ordering: General first, Deprecated last
|
|
542
|
+
if (a.name === 'General') return -1;
|
|
543
|
+
if (b.name === 'General') return 1;
|
|
544
|
+
if (a.name === 'Deprecated') return 1;
|
|
545
|
+
if (b.name === 'Deprecated') return -1;
|
|
546
|
+
return a.name.localeCompare(b.name);
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
const methodsTemplatePath = path.resolve(__dirname, './templates/bloblang-methods-overview.hbs');
|
|
550
|
+
const methodsTemplate = handlebars.compile(fs.readFileSync(methodsTemplatePath, 'utf8'));
|
|
551
|
+
const methodsAdoc = methodsTemplate({ categories });
|
|
552
|
+
const methodsOutPath = path.join(guidesRoot, 'methods.adoc');
|
|
553
|
+
fs.writeFileSync(methodsOutPath, methodsAdoc, 'utf8');
|
|
554
|
+
console.log('Generated methods.adoc overview page');
|
|
555
|
+
partialFiles.push(path.relative(process.cwd(), methodsOutPath));
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Generate functions.adoc with all functions
|
|
559
|
+
if (dataObj['bloblang-functions']) {
|
|
560
|
+
const functionsData = dataObj['bloblang-functions'];
|
|
561
|
+
const functionNames = functionsData
|
|
562
|
+
.filter(fn => fn.name)
|
|
563
|
+
.map(fn => fn.name)
|
|
564
|
+
.sort();
|
|
565
|
+
|
|
566
|
+
const functionsTemplatePath = path.resolve(__dirname, './templates/bloblang-functions-overview.hbs');
|
|
567
|
+
const functionsTemplate = handlebars.compile(fs.readFileSync(functionsTemplatePath, 'utf8'));
|
|
568
|
+
const functionsAdoc = functionsTemplate({ functions: functionNames });
|
|
569
|
+
const functionsOutPath = path.join(guidesRoot, 'functions.adoc');
|
|
570
|
+
fs.writeFileSync(functionsOutPath, functionsAdoc, 'utf8');
|
|
571
|
+
console.log('Generated functions.adoc overview page');
|
|
572
|
+
partialFiles.push(path.relative(process.cwd(), functionsOutPath));
|
|
573
|
+
}
|
|
468
574
|
}
|
|
469
575
|
|
|
470
576
|
// Common/Advanced config snippet YAMLs in modules/components/examples
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const octokit = require('../../cli-utils/octokit-client');
|
|
4
|
+
const { hasGitHubToken } = require('../../cli-utils/github-token');
|
|
5
|
+
const semver = require('semver');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* GitHub release discovery utilities for Redpanda Connect
|
|
9
|
+
*
|
|
10
|
+
* Provides functions to discover and filter GitHub releases between versions.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// Cache for GitHub releases to avoid repeated API calls
|
|
14
|
+
let releaseCache = null;
|
|
15
|
+
let cacheTimestamp = null;
|
|
16
|
+
const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Fetch all releases from redpanda-data/connect repository
|
|
20
|
+
* @param {boolean} useCache - Whether to use cached results
|
|
21
|
+
* @returns {Promise<Array>} Array of release objects
|
|
22
|
+
*/
|
|
23
|
+
async function fetchAllReleases(useCache = true) {
|
|
24
|
+
// Check cache
|
|
25
|
+
if (useCache && releaseCache && cacheTimestamp) {
|
|
26
|
+
const age = Date.now() - cacheTimestamp;
|
|
27
|
+
if (age < CACHE_TTL_MS) {
|
|
28
|
+
console.log(`✓ Using cached releases (${Math.round(age / 1000)}s old)`);
|
|
29
|
+
return releaseCache;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log('Fetching releases from GitHub...');
|
|
34
|
+
|
|
35
|
+
// Warn if no token available (only once per execution)
|
|
36
|
+
if (!hasGitHubToken() && !fetchAllReleases._warnedAboutToken) {
|
|
37
|
+
console.warn('⚠️ No GitHub token found. API rate limits will be more restrictive.');
|
|
38
|
+
console.warn(' Set GITHUB_TOKEN or GH_TOKEN environment variable for higher limits.');
|
|
39
|
+
fetchAllReleases._warnedAboutToken = true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
// Fetch all releases (paginated)
|
|
44
|
+
const releases = await octokit.paginate(
|
|
45
|
+
octokit.rest.repos.listReleases,
|
|
46
|
+
{
|
|
47
|
+
owner: 'redpanda-data',
|
|
48
|
+
repo: 'connect',
|
|
49
|
+
per_page: 100
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
console.log(`✓ Fetched ${releases.length} releases from GitHub`);
|
|
54
|
+
|
|
55
|
+
// Update cache
|
|
56
|
+
releaseCache = releases;
|
|
57
|
+
cacheTimestamp = Date.now();
|
|
58
|
+
|
|
59
|
+
return releases;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (error.status === 403 && error.response?.headers?.['x-ratelimit-remaining'] === '0') {
|
|
62
|
+
const resetTime = new Date(parseInt(error.response.headers['x-ratelimit-reset']) * 1000);
|
|
63
|
+
throw new Error(
|
|
64
|
+
`GitHub API rate limit exceeded. Resets at ${resetTime.toLocaleTimeString()}. ` +
|
|
65
|
+
`Consider setting GITHUB_TOKEN environment variable for higher limits.`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
throw new Error(`Failed to fetch GitHub releases: ${error.message}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Parse version from GitHub release tag
|
|
75
|
+
* Handles formats: "v4.50.0", "4.50.0", "v4.50.0-beta.1"
|
|
76
|
+
* @param {string} tag - Release tag name
|
|
77
|
+
* @returns {string|null} Normalized version string or null if invalid
|
|
78
|
+
*/
|
|
79
|
+
function parseVersionFromTag(tag) {
|
|
80
|
+
if (!tag) return null;
|
|
81
|
+
|
|
82
|
+
// Remove 'v' prefix if present
|
|
83
|
+
const normalized = tag.startsWith('v') ? tag.slice(1) : tag;
|
|
84
|
+
|
|
85
|
+
// Validate semver format
|
|
86
|
+
const version = semver.valid(normalized);
|
|
87
|
+
return version;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Check if a version is a pre-release (beta, RC, alpha, etc.)
|
|
92
|
+
* @param {string} version - Semver version string
|
|
93
|
+
* @returns {boolean} True if pre-release
|
|
94
|
+
*/
|
|
95
|
+
function isPrerelease(version) {
|
|
96
|
+
const parsed = semver.parse(version);
|
|
97
|
+
if (!parsed) return false;
|
|
98
|
+
|
|
99
|
+
return parsed.prerelease.length > 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Filter releases to stable GA releases only
|
|
104
|
+
* @param {Array} releases - Array of GitHub release objects
|
|
105
|
+
* @returns {Array} Filtered releases with only stable versions
|
|
106
|
+
*/
|
|
107
|
+
function filterToStableReleases(releases) {
|
|
108
|
+
return releases.filter(release => {
|
|
109
|
+
// Skip drafts
|
|
110
|
+
if (release.draft) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Parse version
|
|
115
|
+
const version = parseVersionFromTag(release.tag_name);
|
|
116
|
+
if (!version) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Skip pre-releases (beta, RC, etc.)
|
|
121
|
+
if (isPrerelease(version)) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return true;
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Discover all intermediate releases between two versions
|
|
131
|
+
* @param {string} fromVersion - Starting version (e.g., "4.50.0")
|
|
132
|
+
* @param {string} toVersion - Ending version (e.g., "4.54.0")
|
|
133
|
+
* @param {Object} options - Optional configuration
|
|
134
|
+
* @param {boolean} options.includePrerelease - Include beta/RC versions (default: false)
|
|
135
|
+
* @param {boolean} options.useCache - Use cached GitHub data (default: true)
|
|
136
|
+
* @returns {Promise<Array>} Array of version objects: [{version, tag, date, url}]
|
|
137
|
+
*/
|
|
138
|
+
async function discoverIntermediateReleases(fromVersion, toVersion, options = {}) {
|
|
139
|
+
const {
|
|
140
|
+
includePrerelease = false,
|
|
141
|
+
useCache = true
|
|
142
|
+
} = options;
|
|
143
|
+
|
|
144
|
+
// Validate versions are strings
|
|
145
|
+
if (typeof fromVersion !== 'string') {
|
|
146
|
+
throw new Error(`Invalid starting version: ${fromVersion}`);
|
|
147
|
+
}
|
|
148
|
+
if (typeof toVersion !== 'string') {
|
|
149
|
+
throw new Error(`Invalid ending version: ${toVersion}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Normalize versions (remove 'v' prefix if present)
|
|
153
|
+
const normalizedFrom = fromVersion.startsWith('v') ? fromVersion.slice(1) : fromVersion;
|
|
154
|
+
const normalizedTo = toVersion.startsWith('v') ? toVersion.slice(1) : toVersion;
|
|
155
|
+
|
|
156
|
+
// Validate versions
|
|
157
|
+
if (!semver.valid(normalizedFrom)) {
|
|
158
|
+
throw new Error(`Invalid starting version: ${fromVersion}`);
|
|
159
|
+
}
|
|
160
|
+
if (!semver.valid(normalizedTo)) {
|
|
161
|
+
throw new Error(`Invalid ending version: ${toVersion}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.log('');
|
|
165
|
+
console.log(`Discovering releases between ${normalizedFrom} and ${normalizedTo}...`);
|
|
166
|
+
|
|
167
|
+
// Fetch all releases
|
|
168
|
+
const allReleases = await fetchAllReleases(useCache);
|
|
169
|
+
|
|
170
|
+
// Filter to stable releases unless includePrerelease is true
|
|
171
|
+
const filteredReleases = includePrerelease
|
|
172
|
+
? allReleases.filter(r => !r.draft && parseVersionFromTag(r.tag_name))
|
|
173
|
+
: filterToStableReleases(allReleases);
|
|
174
|
+
|
|
175
|
+
// Parse and filter versions in range
|
|
176
|
+
const versionsInRange = [];
|
|
177
|
+
|
|
178
|
+
for (const release of filteredReleases) {
|
|
179
|
+
const version = parseVersionFromTag(release.tag_name);
|
|
180
|
+
if (!version) continue;
|
|
181
|
+
|
|
182
|
+
// Check if version is in range (inclusive)
|
|
183
|
+
if (
|
|
184
|
+
semver.gte(version, normalizedFrom) &&
|
|
185
|
+
semver.lte(version, normalizedTo)
|
|
186
|
+
) {
|
|
187
|
+
versionsInRange.push({
|
|
188
|
+
version,
|
|
189
|
+
tag: release.tag_name,
|
|
190
|
+
date: release.published_at,
|
|
191
|
+
url: release.html_url,
|
|
192
|
+
isPrerelease: isPrerelease(version)
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Sort by semver (oldest to newest)
|
|
198
|
+
versionsInRange.sort((a, b) => semver.compare(a.version, b.version));
|
|
199
|
+
|
|
200
|
+
console.log(`✓ Found ${versionsInRange.length} release(s) in range`);
|
|
201
|
+
|
|
202
|
+
if (versionsInRange.length === 0) {
|
|
203
|
+
console.warn('⚠️ No releases found in the specified range');
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Log the discovered versions
|
|
208
|
+
console.log('');
|
|
209
|
+
console.log('Releases to process:');
|
|
210
|
+
versionsInRange.forEach((v, i) => {
|
|
211
|
+
const prereleaseTag = v.isPrerelease ? ' (pre-release)' : '';
|
|
212
|
+
const date = new Date(v.date).toLocaleDateString();
|
|
213
|
+
console.log(` ${i + 1}. ${v.version}${prereleaseTag} - ${date}`);
|
|
214
|
+
});
|
|
215
|
+
console.log('');
|
|
216
|
+
|
|
217
|
+
return versionsInRange;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Find the appropriate cloud version for a given OSS release date
|
|
222
|
+
* Returns the latest stable cloud version published on or before the OSS release date
|
|
223
|
+
* @param {string} ossReleaseDate - ISO date string of the OSS release
|
|
224
|
+
* @param {Object} options - Optional configuration
|
|
225
|
+
* @param {boolean} options.useCache - Use cached GitHub data (default: true)
|
|
226
|
+
* @returns {Promise<string|null>} Cloud version string or null if not found
|
|
227
|
+
*/
|
|
228
|
+
async function findCloudVersionForDate(ossReleaseDate, options = {}) {
|
|
229
|
+
const { useCache = true } = options;
|
|
230
|
+
|
|
231
|
+
// Fetch all releases from the main connect repo (includes cloud builds)
|
|
232
|
+
const allReleases = await fetchAllReleases(useCache);
|
|
233
|
+
|
|
234
|
+
// Filter to stable releases
|
|
235
|
+
const stableReleases = filterToStableReleases(allReleases);
|
|
236
|
+
|
|
237
|
+
// Filter to releases on or before the OSS release date
|
|
238
|
+
const ossDate = new Date(ossReleaseDate);
|
|
239
|
+
const eligibleReleases = stableReleases.filter(release => {
|
|
240
|
+
const releaseDate = new Date(release.published_at);
|
|
241
|
+
return releaseDate <= ossDate;
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
if (eligibleReleases.length === 0) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Sort by date (most recent first)
|
|
249
|
+
eligibleReleases.sort((a, b) => {
|
|
250
|
+
return new Date(b.published_at) - new Date(a.published_at);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// Return the most recent version
|
|
254
|
+
const cloudVersion = parseVersionFromTag(eligibleReleases[0].tag_name);
|
|
255
|
+
return cloudVersion;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Clear the release cache
|
|
260
|
+
* Useful for testing or when you need fresh data
|
|
261
|
+
*/
|
|
262
|
+
function clearCache() {
|
|
263
|
+
releaseCache = null;
|
|
264
|
+
cacheTimestamp = null;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
module.exports = {
|
|
268
|
+
discoverIntermediateReleases,
|
|
269
|
+
fetchAllReleases,
|
|
270
|
+
parseVersionFromTag,
|
|
271
|
+
isPrerelease,
|
|
272
|
+
filterToStableReleases,
|
|
273
|
+
findCloudVersionForDate,
|
|
274
|
+
clearCache
|
|
275
|
+
};
|
|
@@ -1,10 +1,30 @@
|
|
|
1
1
|
// Bloblang example formatting helper for Handlebars
|
|
2
2
|
function bloblangExample(example) {
|
|
3
3
|
if (typeof example === 'object' && example !== null && example.mapping) {
|
|
4
|
+
let leadIn = '';
|
|
4
5
|
let codeBlock = '';
|
|
6
|
+
|
|
7
|
+
// Extract summary as lead-in prose (not a comment in code)
|
|
5
8
|
if (example.summary && example.summary.trim()) {
|
|
6
|
-
|
|
9
|
+
let summary = example.summary.trim();
|
|
10
|
+
|
|
11
|
+
// Convert Markdown headings to AsciiDoc
|
|
12
|
+
// ##### Heading -> ==== Heading (H5 -> H4 in AsciiDoc)
|
|
13
|
+
summary = summary.replace(/^#####\s+(.+)$/gm, '==== $1');
|
|
14
|
+
// #### Heading -> === Heading (H4 -> H3 in AsciiDoc)
|
|
15
|
+
summary = summary.replace(/^####\s+(.+)$/gm, '=== $1');
|
|
16
|
+
// ### Heading -> == Heading (H3 -> H2 in AsciiDoc)
|
|
17
|
+
summary = summary.replace(/^###\s+(.+)$/gm, '== $1');
|
|
18
|
+
|
|
19
|
+
// Ensure lead-in ends with a colon (replace period/exclamation/question mark if present)
|
|
20
|
+
if (summary.endsWith('.') || summary.endsWith('!') || summary.endsWith('?')) {
|
|
21
|
+
summary = summary.slice(0, -1) + ':';
|
|
22
|
+
} else if (!summary.endsWith(':')) {
|
|
23
|
+
summary += ':';
|
|
24
|
+
}
|
|
25
|
+
leadIn = summary + '\n\n';
|
|
7
26
|
}
|
|
27
|
+
|
|
8
28
|
if (typeof example.mapping === 'string') {
|
|
9
29
|
codeBlock += example.mapping.trim() + '\n';
|
|
10
30
|
}
|
|
@@ -15,7 +35,7 @@ function bloblangExample(example) {
|
|
|
15
35
|
}
|
|
16
36
|
}
|
|
17
37
|
}
|
|
18
|
-
return
|
|
38
|
+
return `${leadIn}[,bloblang]\n----\n${codeBlock.trim()}\n----\n`;
|
|
19
39
|
} else {
|
|
20
40
|
let exStr = '';
|
|
21
41
|
if (typeof example === 'string') {
|
|
@@ -35,7 +55,7 @@ function bloblangExample(example) {
|
|
|
35
55
|
} else {
|
|
36
56
|
exStr = String(example);
|
|
37
57
|
}
|
|
38
|
-
return `[source,
|
|
58
|
+
return `[source,bloblang]\n----\n${exStr}\n----\n`;
|
|
39
59
|
}
|
|
40
60
|
}
|
|
41
61
|
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
const renderLeafField = require('./renderLeafField');
|
|
2
2
|
const renderObjectField = require('./renderObjectField');
|
|
3
3
|
|
|
4
|
+
// Component types that support the 'label' field
|
|
5
|
+
const TYPES_WITH_LABEL = new Set(['inputs', 'outputs', 'processors']);
|
|
6
|
+
|
|
4
7
|
/**
|
|
5
|
-
* Builds either
|
|
8
|
+
* Builds either "Common" or "Advanced" YAML for one connector.
|
|
6
9
|
*
|
|
7
|
-
* - type =
|
|
8
|
-
* - connectorName = such as
|
|
10
|
+
* - type = "input" or "output" (or whatever type)
|
|
11
|
+
* - connectorName = such as "amqp_1"
|
|
9
12
|
* - children = the array of field‐definitions (entry.config.children)
|
|
10
13
|
* - includeAdvanced = if false → only fields where is_advanced !== true
|
|
11
14
|
* if true → all fields (except deprecated)
|
|
@@ -13,18 +16,20 @@ const renderObjectField = require('./renderObjectField');
|
|
|
13
16
|
* Structure produced:
|
|
14
17
|
*
|
|
15
18
|
* type:
|
|
16
|
-
* label: ""
|
|
19
|
+
* label: "" (only for inputs, outputs, processors)
|
|
17
20
|
* connectorName:
|
|
18
|
-
* ...child fields (with comments for
|
|
21
|
+
* ...child fields (with comments for "no default")
|
|
19
22
|
*/
|
|
20
23
|
module.exports = function buildConfigYaml(type, connectorName, children, includeAdvanced) {
|
|
21
24
|
const lines = [];
|
|
22
25
|
|
|
23
|
-
//
|
|
26
|
+
// "type:" top‐level
|
|
24
27
|
lines.push(`${type}:`);
|
|
25
28
|
|
|
26
|
-
// Two‐space indent for
|
|
27
|
-
|
|
29
|
+
// Two‐space indent for "label" (only for types that support it)
|
|
30
|
+
if (TYPES_WITH_LABEL.has(type)) {
|
|
31
|
+
lines.push(` label: ""`);
|
|
32
|
+
}
|
|
28
33
|
|
|
29
34
|
// Two‐space indent for connectorName heading
|
|
30
35
|
lines.push(` ${connectorName}:`);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensures a text string ends with a period (or other terminal punctuation).
|
|
3
|
+
* Used for normalizing Bloblang descriptions.
|
|
4
|
+
*
|
|
5
|
+
* @param {string} text - The text to process
|
|
6
|
+
* @returns {string} Text ending with appropriate punctuation
|
|
7
|
+
*/
|
|
8
|
+
function ensurePeriod(text) {
|
|
9
|
+
if (!text || typeof text !== 'string') {
|
|
10
|
+
return text;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const trimmed = text.trim();
|
|
14
|
+
if (!trimmed) {
|
|
15
|
+
return text;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Check if already ends with terminal punctuation
|
|
19
|
+
if (trimmed.endsWith('.') || trimmed.endsWith('!') || trimmed.endsWith('?')) {
|
|
20
|
+
return text;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Add period
|
|
24
|
+
return text.trim() + '.';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = ensurePeriod;
|
|
@@ -17,4 +17,7 @@ module.exports = {
|
|
|
17
17
|
commonConfig: require('./commonConfig.js'),
|
|
18
18
|
advancedConfig: require('./advancedConfig.js'),
|
|
19
19
|
bloblangExample: require('./bloblangExample.js'),
|
|
20
|
+
hasOptionalParams: require('./hasOptionalParams.js'),
|
|
21
|
+
ensurePeriod: require('./ensurePeriod.js'),
|
|
22
|
+
toSentenceCase: require('./toSentenceCase.js'),
|
|
20
23
|
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Convert a string to sentence case, preserving acronyms and proper nouns
|
|
5
|
+
* @param {string} text - The text to convert
|
|
6
|
+
* @returns {string} The text in sentence case
|
|
7
|
+
*/
|
|
8
|
+
function toSentenceCase(text) {
|
|
9
|
+
if (!text) return ''
|
|
10
|
+
|
|
11
|
+
// Map of exact word matches to preserve (case-sensitive)
|
|
12
|
+
const exactPreserve = new Map([
|
|
13
|
+
['geoip', 'GeoIP'],
|
|
14
|
+
['GEOIP', 'GeoIP'],
|
|
15
|
+
['GeoIP', 'GeoIP']
|
|
16
|
+
])
|
|
17
|
+
|
|
18
|
+
// List of acronyms to preserve (will be uppercased)
|
|
19
|
+
const preserveWords = new Set([
|
|
20
|
+
'SQL',
|
|
21
|
+
'JSON',
|
|
22
|
+
'JWT',
|
|
23
|
+
'XML',
|
|
24
|
+
'HTML',
|
|
25
|
+
'URL',
|
|
26
|
+
'URI',
|
|
27
|
+
'HTTP',
|
|
28
|
+
'HTTPS',
|
|
29
|
+
'TLS',
|
|
30
|
+
'SSL',
|
|
31
|
+
'AWS',
|
|
32
|
+
'GCP',
|
|
33
|
+
'API',
|
|
34
|
+
'ID',
|
|
35
|
+
'UUID',
|
|
36
|
+
'CSV'
|
|
37
|
+
])
|
|
38
|
+
|
|
39
|
+
// Split into words
|
|
40
|
+
const words = text.split(/\s+/)
|
|
41
|
+
|
|
42
|
+
return words.map((word, index) => {
|
|
43
|
+
// Check if word is in exact preserve map first
|
|
44
|
+
if (exactPreserve.has(word) || exactPreserve.has(word.toLowerCase()) || exactPreserve.has(word.toUpperCase())) {
|
|
45
|
+
const key = exactPreserve.has(word) ? word : (exactPreserve.has(word.toLowerCase()) ? word.toLowerCase() : word.toUpperCase())
|
|
46
|
+
return exactPreserve.get(key)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Check if word is in preserve list (case-insensitive check)
|
|
50
|
+
if (preserveWords.has(word.toUpperCase())) {
|
|
51
|
+
return word.toUpperCase()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Check if word contains special characters like &
|
|
55
|
+
if (word === '&') {
|
|
56
|
+
return word
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// First word: capitalize first letter, lowercase rest
|
|
60
|
+
if (index === 0) {
|
|
61
|
+
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Subsequent words: lowercase
|
|
65
|
+
return word.toLowerCase()
|
|
66
|
+
}).join(' ')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = toSentenceCase
|