@redpanda-data/docs-extensions-and-macros 4.15.5 → 4.15.7

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.
@@ -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 = [] // Array of cloud-only connectors from cloud binary inspection
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);
@@ -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,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 Common or Advanced YAML for one connector.
8
+ * Builds either "Common" or "Advanced" YAML for one connector.
6
9
  *
7
- * - type = input or output (or whatever type)
8
- * - connectorName = such as amqp_1
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 no default)
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
- // type:” top‐level
26
+ // "type:" top‐level
24
27
  lines.push(`${type}:`);
25
28
 
26
- // Two‐space indent for label
27
- lines.push(` label: ""`);
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,92 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ /**
7
+ * Create a master diff JSON that aggregates changes across multiple releases
8
+ *
9
+ * @param {Array} intermediateResults - Array of {fromVersion, toVersion, diffPath, success}
10
+ * @param {string} finalDiffPath - Path to the final diff JSON
11
+ * @param {string} outputPath - Where to write the master diff
12
+ * @returns {Object} Master diff object
13
+ */
14
+ function createMasterDiff(intermediateResults, finalDiffPath, outputPath) {
15
+ const releases = [];
16
+
17
+ // Add all intermediate releases
18
+ for (const result of intermediateResults) {
19
+ if (!result.success) continue;
20
+
21
+ try {
22
+ const diffData = JSON.parse(fs.readFileSync(result.diffPath, 'utf8'));
23
+ releases.push({
24
+ fromVersion: result.fromVersion,
25
+ toVersion: result.toVersion,
26
+ date: diffData.comparison?.timestamp || new Date().toISOString(),
27
+ summary: diffData.summary || {},
28
+ details: diffData.details || {},
29
+ binaryAnalysis: diffData.binaryAnalysis || null
30
+ });
31
+ } catch (err) {
32
+ console.warn(`Warning: Failed to load ${result.diffPath}: ${err.message}`);
33
+ }
34
+ }
35
+
36
+ // Add the final release
37
+ if (finalDiffPath && fs.existsSync(finalDiffPath)) {
38
+ try {
39
+ const diffData = JSON.parse(fs.readFileSync(finalDiffPath, 'utf8'));
40
+ const finalFromVersion = diffData.comparison?.oldVersion;
41
+ const finalToVersion = diffData.comparison?.newVersion;
42
+
43
+ releases.push({
44
+ fromVersion: finalFromVersion,
45
+ toVersion: finalToVersion,
46
+ date: diffData.comparison?.timestamp || new Date().toISOString(),
47
+ summary: diffData.summary || {},
48
+ details: diffData.details || {},
49
+ binaryAnalysis: diffData.binaryAnalysis || null
50
+ });
51
+ } catch (err) {
52
+ console.warn(`Warning: Failed to load ${finalDiffPath}: ${err.message}`);
53
+ }
54
+ }
55
+
56
+ // Calculate total summary across all releases
57
+ const totalSummary = {
58
+ versions: releases.map(r => r.toVersion),
59
+ releaseCount: releases.length,
60
+ newComponents: releases.reduce((sum, r) => sum + (r.summary.newComponents || 0), 0),
61
+ newFields: releases.reduce((sum, r) => sum + (r.summary.newFields || 0), 0),
62
+ removedComponents: releases.reduce((sum, r) => sum + (r.summary.removedComponents || 0), 0),
63
+ removedFields: releases.reduce((sum, r) => sum + (r.summary.removedFields || 0), 0),
64
+ deprecatedComponents: releases.reduce((sum, r) => sum + (r.summary.deprecatedComponents || 0), 0),
65
+ deprecatedFields: releases.reduce((sum, r) => sum + (r.summary.deprecatedFields || 0), 0),
66
+ changedDefaults: releases.reduce((sum, r) => sum + (r.summary.changedDefaults || 0), 0)
67
+ };
68
+
69
+ const masterDiff = {
70
+ metadata: {
71
+ generatedAt: new Date().toISOString(),
72
+ startVersion: releases[0]?.fromVersion,
73
+ endVersion: releases[releases.length - 1]?.toVersion,
74
+ processedReleases: releases.length
75
+ },
76
+ totalSummary,
77
+ releases
78
+ };
79
+
80
+ // Write to file
81
+ if (outputPath) {
82
+ fs.writeFileSync(outputPath, JSON.stringify(masterDiff, null, 2), 'utf8');
83
+ console.log(`✓ Created master diff: ${path.basename(outputPath)}`);
84
+ console.log(` Spans ${releases.length} release(s): ${releases[0]?.fromVersion} → ${releases[releases.length - 1]?.toVersion}`);
85
+ }
86
+
87
+ return masterDiff;
88
+ }
89
+
90
+ module.exports = {
91
+ createMasterDiff
92
+ };
@@ -48,6 +48,7 @@ async function parseCSVConnectors(localCsvPath, logger) {
48
48
  return {
49
49
  name: trimmed.name,
50
50
  type: trimmed.type,
51
+ support: trimmed.support || 'community', // certified, community, enterprise
51
52
  is_cloud_supported: (trimmed.cloud || '').toLowerCase() === 'y' ? 'y' : 'n'
52
53
  };
53
54
  })