@redpanda-data/docs-extensions-and-macros 4.17.2 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  module.exports.register = function () {
4
+ const logger = this.getLogger('unpublish-pages-extension');
5
+
4
6
  this.on('documentsConverted', ({ siteCatalog, contentCatalog }) => {
5
7
  // Get all pages that have an `out` property
6
8
  const pages = contentCatalog.getPages((page) => page.out);
@@ -14,6 +16,15 @@ module.exports.register = function () {
14
16
  pageVersion = page.asciidoc?.attributes['page-component-version'];
15
17
  const component = contentCatalog.getComponent(componentName);
16
18
 
19
+ // Debug logging for ANY page with publish-only-during-beta attribute
20
+ if (page.asciidoc?.attributes['publish-only-during-beta']) {
21
+ logger.debug(`Found page with publish-only-during-beta: ${page.src?.relative || page.pub?.url}`);
22
+ logger.debug(` - publish-only-during-beta: ${page.asciidoc?.attributes['publish-only-during-beta']}`);
23
+ logger.debug(` - page-component-version-is-prerelease: ${page.asciidoc?.attributes['page-component-version-is-prerelease']}`);
24
+ logger.debug(` - page-unpublish: ${page.asciidoc?.attributes['page-unpublish']}`);
25
+ logger.debug(` - component: ${componentName}, version: ${pageVersion}`);
26
+ }
27
+
17
28
  // Check the conditions for unpublishing the page
18
29
  const shouldUnpublish = (
19
30
  page.asciidoc?.attributes['page-unpublish'] ||
@@ -24,6 +35,7 @@ module.exports.register = function () {
24
35
 
25
36
  // Unpublish the shouldUnpublish pages
26
37
  if (shouldUnpublish) {
38
+ logger.info(`Unpublishing page: ${page.src?.relative || page.pub?.url}`);
27
39
  siteCatalog.unpublishedPages.push(page.pub.url)
28
40
  delete page.out;
29
41
  }
@@ -6,9 +6,10 @@
6
6
  *
7
7
  * @param {string} dockerNamespace - The Docker Hub namespace (organization or username)
8
8
  * @param {string} dockerRepo - The repository name on Docker Hub
9
+ * @param {Object} logger - Optional Antora logger instance for logging
9
10
  * @returns {Promise<{ latestStableRelease: string|null, latestBetaRelease: string|null }>}
10
11
  */
11
- module.exports = async (dockerNamespace, dockerRepo) => {
12
+ module.exports = async (dockerNamespace, dockerRepo, logger = null) => {
12
13
  const { default: fetch } = await import('node-fetch');
13
14
 
14
15
  try {
@@ -64,7 +65,11 @@ module.exports = async (dockerNamespace, dockerRepo) => {
64
65
  };
65
66
 
66
67
  } catch (error) {
67
- console.error('Error fetching Docker tags:', error);
68
+ if (logger) {
69
+ logger.error('Error fetching Docker tags:', error);
70
+ } else {
71
+ console.error('Error fetching Docker tags:', error);
72
+ }
68
73
  return {
69
74
  latestStableRelease: null,
70
75
  latestBetaRelease: null
@@ -1,10 +1,14 @@
1
- module.exports = async (github, owner, repo) => {
1
+ module.exports = async (github, owner, repo, logger = null) => {
2
2
 
3
3
  try {
4
4
  const release = await github.rest.repos.getLatestRelease({ owner, repo });
5
5
  return release.data.tag_name
6
6
  } catch (error) {
7
- console.error(error);
7
+ if (logger) {
8
+ logger.error(error);
9
+ } else {
10
+ console.error(error);
11
+ }
8
12
  return null;
9
13
  }
10
14
  };
@@ -1,6 +1,6 @@
1
1
  const { retryWithBackoff, isRetryableGitHubError } = require('./retry-util');
2
2
 
3
- module.exports = async (github, owner, repo) => {
3
+ module.exports = async (github, owner, repo, logger = null) => {
4
4
  const semver = require('semver');
5
5
 
6
6
  return retryWithBackoff(
@@ -29,7 +29,11 @@ module.exports = async (github, owner, repo) => {
29
29
  latestBetaRelease: latestBetaReleaseVersion || null
30
30
  };
31
31
  } else {
32
- console.log("No valid semver releases found.");
32
+ if (logger) {
33
+ logger.warn("No valid semver releases found.");
34
+ } else {
35
+ console.log("No valid semver releases found.");
36
+ }
33
37
  return { latestStableRelease: null, latestBetaRelease: null };
34
38
  }
35
39
  },
@@ -38,9 +42,14 @@ module.exports = async (github, owner, repo) => {
38
42
  initialDelay: 1000,
39
43
  shouldRetry: isRetryableGitHubError,
40
44
  operationName: `Fetch Console version from ${owner}/${repo}`
41
- }
45
+ },
46
+ logger
42
47
  ).catch(error => {
43
- console.error('Failed to fetch release information after retries:', error);
48
+ if (logger) {
49
+ logger.error('Failed to fetch release information after retries:', error);
50
+ } else {
51
+ console.error('Failed to fetch release information after retries:', error);
52
+ }
44
53
  return { latestStableRelease: null, latestBetaRelease: null };
45
54
  });
46
55
  };
@@ -8,9 +8,10 @@
8
8
  * @param {string} repo - The repository name (redpanda-operator).
9
9
  * @param {string} stableDockerTag - The stable docker tag in format v<major>.<minor>.<patch>.
10
10
  * @param {string} betaDockerTag - Optional beta docker tag in format v<major>.<minor>.<patch>-beta.
11
+ * @param {Object} logger - Optional Antora logger instance for logging
11
12
  * @returns {Promise<{ latestStableRelease: string|null, latestBetaRelease: string|null }>}
12
13
  */
13
- module.exports = async (github, owner, repo, stableDockerTag, betaDockerTag) => {
14
+ module.exports = async (github, owner, repo, stableDockerTag, betaDockerTag, logger = null) => {
14
15
  const yaml = require('js-yaml');
15
16
  const path = 'charts/redpanda/chart/Chart.yaml';
16
17
 
@@ -29,7 +30,11 @@ module.exports = async (github, owner, repo, stableDockerTag, betaDockerTag) =>
29
30
  const versionMatch = dockerTag.match(/^v(\d+)\.(\d+)(?:\.(\d+))?(?:-beta.*)?$/);
30
31
 
31
32
  if (!versionMatch) {
32
- console.error(`Invalid docker tag format: ${dockerTag}`);
33
+ if (logger) {
34
+ logger.error(`Invalid docker tag format: ${dockerTag}`);
35
+ } else {
36
+ console.error(`Invalid docker tag format: ${dockerTag}`);
37
+ }
33
38
  return null;
34
39
  }
35
40
 
@@ -55,11 +60,28 @@ module.exports = async (github, owner, repo, stableDockerTag, betaDockerTag) =>
55
60
  const version = chartYaml.version || null;
56
61
  return version;
57
62
  } catch (error) {
58
- console.error(`Failed to fetch Chart.yaml for branch ${branchName}:`, error.message);
63
+ if (error.status === 404) {
64
+ // Branch doesn't exist yet - this is expected for unreleased versions
65
+ if (logger) {
66
+ logger.warn(`Fetched operator ${dockerTag} from Docker Hub but branch ${branchName} not found in GitHub (source may not be released yet)`);
67
+ } else {
68
+ console.warn(`Fetched operator ${dockerTag} from Docker Hub but branch ${branchName} not found in GitHub (source may not be released yet)`);
69
+ }
70
+ } else {
71
+ if (logger) {
72
+ logger.error(`Failed to fetch Chart.yaml for branch ${branchName}:`, error.message);
73
+ } else {
74
+ console.error(`Failed to fetch Chart.yaml for branch ${branchName}:`, error.message);
75
+ }
76
+ }
59
77
  return null;
60
78
  }
61
79
  } catch (error) {
62
- console.error(`Error processing docker tag ${dockerTag}:`, error.message);
80
+ if (logger) {
81
+ logger.error(`Error processing docker tag ${dockerTag}:`, error.message);
82
+ } else {
83
+ console.error(`Error processing docker tag ${dockerTag}:`, error.message);
84
+ }
63
85
  return null;
64
86
  }
65
87
  };
@@ -75,7 +97,11 @@ module.exports = async (github, owner, repo, stableDockerTag, betaDockerTag) =>
75
97
  latestBetaRelease: betaChartVersion
76
98
  };
77
99
  } catch (error) {
78
- console.error('Failed to fetch chart versions:', error.message);
100
+ if (logger) {
101
+ logger.error('Failed to fetch chart versions:', error.message);
102
+ } else {
103
+ console.error('Failed to fetch chart versions:', error.message);
104
+ }
79
105
  return {
80
106
  latestStableRelease: null,
81
107
  latestBetaRelease: null
@@ -1,6 +1,6 @@
1
1
  const { retryWithBackoff, isRetryableGitHubError } = require('./retry-util');
2
2
 
3
- module.exports = async (github, owner, repo) => {
3
+ module.exports = async (github, owner, repo, logger = null) => {
4
4
  const semver = require('semver');
5
5
 
6
6
  return retryWithBackoff(
@@ -67,9 +67,14 @@ module.exports = async (github, owner, repo) => {
67
67
  initialDelay: 1000,
68
68
  shouldRetry: isRetryableGitHubError,
69
69
  operationName: `Fetch Redpanda version from ${owner}/${repo}`
70
- }
70
+ },
71
+ logger
71
72
  ).catch(error => {
72
- console.error('Failed to fetch Redpanda release information after retries:', error);
73
+ if (logger) {
74
+ logger.error('Failed to fetch Redpanda release information after retries:', error);
75
+ } else {
76
+ console.error('Failed to fetch Redpanda release information after retries:', error);
77
+ }
73
78
  return { latestRedpandaRelease: null, latestRcRelease: null };
74
79
  });
75
80
  };
@@ -8,9 +8,10 @@
8
8
  * @param {number} options.maxDelay - Maximum delay in ms (default: 10000)
9
9
  * @param {Function} options.shouldRetry - Function to determine if error is retryable (default: all errors)
10
10
  * @param {string} options.operationName - Name for logging
11
+ * @param {Object} logger - Optional Antora logger instance for logging
11
12
  * @returns {Promise<*>} Result of the function
12
13
  */
13
- async function retryWithBackoff(fn, options = {}) {
14
+ async function retryWithBackoff(fn, options = {}, logger = null) {
14
15
  const {
15
16
  maxRetries = 3,
16
17
  initialDelay = 1000,
@@ -37,8 +38,14 @@ async function retryWithBackoff(fn, options = {}) {
37
38
  const jitter = Math.random() * 0.3 * baseDelay; // Add up to 30% jitter
38
39
  const delay = Math.floor(baseDelay + jitter);
39
40
 
40
- console.error(`⚠️ ${operationName} failed (attempt ${attempt + 1}/${maxRetries + 1}): ${error.message}`);
41
- console.error(` Retrying in ${delay}ms...`);
41
+ // Log retry attempt using logger if available, otherwise fall back to console
42
+ if (logger) {
43
+ logger.warn(`${operationName} failed (attempt ${attempt + 1}/${maxRetries + 1}): ${error.message}`);
44
+ logger.warn(`Retrying in ${delay}ms...`);
45
+ } else {
46
+ console.error(`⚠️ ${operationName} failed (attempt ${attempt + 1}/${maxRetries + 1}): ${error.message}`);
47
+ console.error(` Retrying in ${delay}ms...`);
48
+ }
42
49
 
43
50
  // Wait before retrying
44
51
  await new Promise(resolve => setTimeout(resolve, delay));
@@ -37,10 +37,10 @@ module.exports.register = function ({ config }) {
37
37
  latestOperatorResult,
38
38
  latestConnectResult,
39
39
  ] = await Promise.allSettled([
40
- GetLatestRedpandaVersion(github, owner, 'redpanda'),
41
- GetLatestDockerTag(dockerNamespace, 'console'),
42
- GetLatestDockerTag(dockerNamespace, 'redpanda-operator'),
43
- GetLatestConnectVersion(github, owner, 'connect'),
40
+ GetLatestRedpandaVersion(github, owner, 'redpanda', logger),
41
+ GetLatestDockerTag(dockerNamespace, 'console', logger),
42
+ GetLatestDockerTag(dockerNamespace, 'redpanda-operator', logger),
43
+ GetLatestConnectVersion(github, owner, 'connect', logger),
44
44
  ]);
45
45
 
46
46
  // Get the Helm chart version after we have the operator version (for both stable and beta)
@@ -49,11 +49,12 @@ module.exports.register = function ({ config }) {
49
49
  if (latestOperatorResult.status === 'fulfilled') {
50
50
  try {
51
51
  const helmChartVersions = await GetLatestHelmChartVersionFromOperator(
52
- github,
53
- owner,
54
- 'redpanda-operator',
52
+ github,
53
+ owner,
54
+ 'redpanda-operator',
55
55
  latestOperatorResult.value?.latestStableRelease,
56
- latestOperatorResult.value?.latestBetaRelease
56
+ latestOperatorResult.value?.latestBetaRelease,
57
+ logger
57
58
  );
58
59
 
59
60
  latestHelmChartResult = {
@@ -1,4 +1,6 @@
1
1
  'use strict';
2
+ const { posix: path } = require('path')
3
+
2
4
  /**
3
5
  * Registers macros for use in Redpanda Connect contexts in the Redpanda documentation.
4
6
  * @param {Registry} registry - The Antora registry where this block macro is registered.
@@ -1024,7 +1026,31 @@ module.exports.register = function (registry, context) {
1024
1026
  // Filter for the specific connector by name
1025
1027
  const componentRows = csvData.data.filter(row => row.connector.trim().toLowerCase() === name.trim().toLowerCase());
1026
1028
  if (componentRows.length === 0) {
1027
- console.error(`No data found for connector: ${name}`);
1029
+ console.warn(`No CSV data found for connector: ${name}. The connector may have been removed or renamed.`);
1030
+ // Build link to release notes using content catalog for proper URL resolution
1031
+ const isCloud = attributes['env-cloud'] !== undefined;
1032
+ let whatsNewUrl = isCloud
1033
+ ? '/redpanda-cloud/develop/connect/get-started/whats-new/'
1034
+ : '/redpanda-connect/get-started/whats-new/';
1035
+
1036
+ // Try to resolve the page from the content catalog for accurate URLs
1037
+ if (context.contentCatalog && context.file) {
1038
+ const pageSpec = isCloud
1039
+ ? 'redpanda-cloud:develop/connect/get-started:whats-new.adoc'
1040
+ : 'redpanda-connect:get-started:whats-new.adoc';
1041
+ const page = context.contentCatalog.resolvePage(pageSpec, context.file.src);
1042
+ if (page) {
1043
+ whatsNewUrl = path.relative(path.dirname(context.file.pub.url), page.pub.url);
1044
+ }
1045
+ }
1046
+
1047
+ // Return a notice for removed/unknown connectors
1048
+ return self.createBlock(parent, 'pass', `
1049
+ <div class="metadata-block">
1050
+ <div class="metadata-content">
1051
+ <p><strong>Available in:</strong> <em>This connector is no longer available. Check the <a href="${whatsNewUrl}">release notes</a> for migration guidance.</em></p>
1052
+ </div>
1053
+ </div>`);
1028
1054
  }
1029
1055
  // Process types and metadata from CSV
1030
1056
  const types = componentRows.map(row => ({
@@ -1041,6 +1067,21 @@ module.exports.register = function (registry, context) {
1041
1067
  const [currentType] = sortedTypes.splice(currentTypeIndex, 1);
1042
1068
  sortedTypes.unshift(currentType);
1043
1069
  }
1070
+
1071
+ // Set context-switcher attribute for UI template to render compact dropdown in sticky bar
1072
+ if (sortedTypes.length > 1) {
1073
+ const contextSwitcherData = {};
1074
+ sortedTypes.forEach(typeObj => {
1075
+ const link = (component === 'Cloud' && typeObj.redpandaCloudUrl) || typeObj.redpandaConnectUrl;
1076
+ contextSwitcherData[typeObj.type.toLowerCase()] = {
1077
+ name: capitalize(typeObj.type),
1078
+ to: link
1079
+ };
1080
+ });
1081
+ // Set as page attribute (not document attribute) so it's accessible in UI template
1082
+ attributes['page-context-switcher'] = JSON.stringify(contextSwitcherData);
1083
+ parent.getDocument().setAttribute('page-context-switcher', JSON.stringify(contextSwitcherData));
1084
+ }
1044
1085
  // Check if the component requires an Enterprise license (based on support level)
1045
1086
  let enterpriseLicenseInfo = '';
1046
1087
  if (component !== 'Cloud') {
@@ -1051,6 +1092,26 @@ module.exports.register = function (registry, context) {
1051
1092
  }
1052
1093
  }
1053
1094
  const isCloudSupported = componentRows.some(row => row.is_cloud_supported === 'y');
1095
+
1096
+ // Set availability attributes for UI template badges with URLs
1097
+ if (isCloudSupported) {
1098
+ const cloudUrl = sortedTypes[0].redpandaCloudUrl;
1099
+ const connectUrl = sortedTypes[0].redpandaConnectUrl;
1100
+
1101
+ if (component === 'Connect' && cloudUrl) {
1102
+ // Viewing Self-Managed, but also available in Cloud - show Cloud badge with link
1103
+ parent.getDocument().setAttribute('cloud-available', 'true');
1104
+ parent.getDocument().setAttribute('cloud-available-url', cloudUrl);
1105
+ } else if (component === 'Cloud' && connectUrl) {
1106
+ // Viewing Cloud, but also available in Self-Managed - show Self-Managed badge with link
1107
+ parent.getDocument().setAttribute('self-managed-available', 'true');
1108
+ parent.getDocument().setAttribute('self-managed-available-url', connectUrl);
1109
+ }
1110
+ } else if (!isCloudSupported && component === 'Connect') {
1111
+ // Only available in Self-Managed - show "Self-Managed Only" badge (no link)
1112
+ parent.getDocument().setAttribute('self-managed-only', 'true');
1113
+ }
1114
+
1054
1115
  let availableInInfo = '';
1055
1116
 
1056
1117
  if (isCloudSupported) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redpanda-data/docs-extensions-and-macros",
3
- "version": "4.17.2",
3
+ "version": "5.0.0",
4
4
  "description": "Antora extensions and macros developed for Redpanda documentation.",
5
5
  "keywords": [
6
6
  "antora",
@@ -77,6 +77,8 @@
77
77
  "./extensions/algolia-indexer/index": "./extensions/algolia-indexer/index.js",
78
78
  "./extensions/aggregate-terms": "./extensions/aggregate-terms.js",
79
79
  "./extensions/resolve-xrefs-in-attachments": "./extensions/resolve-xrefs-in-attachments.js",
80
+ "./extensions/unified-navigation": "./extensions/unified-navigation.js",
81
+ "./extensions/conditional-home-redirect": "./extensions/conditional-home-redirect.js",
80
82
  "./macros/glossary": "./macros/glossary.js",
81
83
  "./macros/rp-connect-components": "./macros/rp-connect-components.js",
82
84
  "./macros/config-ref": "./macros/config-ref.js",
@@ -79,7 +79,7 @@ module.exports = function renderConnectFields(children, prefix = '') {
79
79
  block += `${desc}\n\n`;
80
80
  }
81
81
  if (child.is_secret) {
82
- block += `include::redpanda-connect:components:partial$secret_warning.adoc[]\n\n`;
82
+ block += `include::connect:components:partial$secret_warning.adoc[]\n\n`;
83
83
  }
84
84
  if (child.version) {
85
85
  block += `ifndef::env-cloud[]\nRequires version ${child.version} or later.\nendif::[]\n\n`;
@@ -106,6 +106,94 @@ function stripPlatformMetadata (connectors) {
106
106
  })
107
107
  }
108
108
 
109
+ /**
110
+ * Augment connector data with platform metadata from binary analysis.
111
+ * This adds cloudSupported/requiresCgo flags and includes CGO-only and cloud-only connectors.
112
+ *
113
+ * @param {Object} connectorData - Connector index object with arrays for each component type
114
+ * @param {Object} binaryAnalysis - Binary analysis results from analyzeAllBinaries()
115
+ * @returns {Object} Object with augmentedData, augmentedCount, addedCgoCount, addedCloudOnlyCount
116
+ */
117
+ function augmentConnectorData (connectorData, binaryAnalysis) {
118
+ if (!binaryAnalysis) {
119
+ return { augmentedData: connectorData, augmentedCount: 0, addedCgoCount: 0, addedCloudOnlyCount: 0 }
120
+ }
121
+
122
+ // Deep clone to avoid mutating original
123
+ const augmentedData = JSON.parse(JSON.stringify(connectorData))
124
+
125
+ const cloudSet = new Set(
126
+ (binaryAnalysis.comparison?.inCloud || []).map(c => `${c.type}:${c.name}`)
127
+ )
128
+ const cgoOnlySet = new Set(
129
+ (binaryAnalysis.cgoOnly || []).map(c => `${c.type}:${c.name}`)
130
+ )
131
+
132
+ let augmentedCount = 0
133
+ let addedCgoCount = 0
134
+ let addedCloudOnlyCount = 0
135
+
136
+ const connectorTypes = ['inputs', 'outputs', 'processors', 'caches', 'rate_limits',
137
+ 'buffers', 'metrics', 'scanners', 'tracers']
138
+
139
+ for (const type of connectorTypes) {
140
+ if (!Array.isArray(augmentedData[type])) {
141
+ augmentedData[type] = []
142
+ }
143
+
144
+ // Add cloudSupported and requiresCgo flags to existing connectors
145
+ for (const connector of augmentedData[type]) {
146
+ const key = `${type}:${connector.name}`
147
+ connector.cloudSupported = cloudSet.has(key)
148
+ connector.requiresCgo = cgoOnlySet.has(key)
149
+ augmentedCount++
150
+ }
151
+
152
+ // Add CGO-only connectors that aren't in the OSS list
153
+ if (binaryAnalysis.cgoOnly) {
154
+ for (const cgoConn of binaryAnalysis.cgoOnly) {
155
+ if (cgoConn.type === type) {
156
+ const exists = augmentedData[type].some(c => c.name === cgoConn.name)
157
+ if (!exists) {
158
+ // Singularize type for consistency with stored data (except "metrics" which stays plural)
159
+ const componentType = cgoConn.type === 'metrics' ? 'metrics' : cgoConn.type.replace(/s$/, '')
160
+ augmentedData[type].push({
161
+ ...cgoConn,
162
+ type: componentType,
163
+ cloudSupported: false,
164
+ requiresCgo: true
165
+ })
166
+ addedCgoCount++
167
+ }
168
+ }
169
+ }
170
+ }
171
+
172
+ // Add cloud-only connectors that aren't in the OSS list
173
+ if (binaryAnalysis.comparison?.cloudOnly) {
174
+ for (const cloudConn of binaryAnalysis.comparison.cloudOnly) {
175
+ if (cloudConn.type === type) {
176
+ const exists = augmentedData[type].some(c => c.name === cloudConn.name)
177
+ if (!exists) {
178
+ // Singularize type for consistency with stored data (except "metrics" which stays plural)
179
+ const componentType = cloudConn.type === 'metrics' ? 'metrics' : cloudConn.type.replace(/s$/, '')
180
+ augmentedData[type].push({
181
+ ...cloudConn,
182
+ type: componentType,
183
+ cloudSupported: true,
184
+ requiresCgo: false,
185
+ cloudOnly: true
186
+ })
187
+ addedCloudOnlyCount++
188
+ }
189
+ }
190
+ }
191
+ }
192
+ }
193
+
194
+ return { augmentedData, augmentedCount, addedCgoCount, addedCloudOnlyCount }
195
+ }
196
+
109
197
  /**
110
198
  * Update whats-new.adoc with new release information
111
199
  * @param {Object} params - Parameters
@@ -830,13 +918,22 @@ async function handleRpcnConnectorDocs (options) {
830
918
  }
831
919
  }
832
920
 
921
+ // Augment newData with binary analysis results before generating diff
922
+ // This ensures CGO-only and cloud-only connectors are included and metadata is accurate
923
+ const { augmentedData: augmentedNewData, addedCgoCount, addedCloudOnlyCount } =
924
+ augmentConnectorData(newData, intermediateAnalysis)
925
+
926
+ if (addedCgoCount > 0 || addedCloudOnlyCount > 0) {
927
+ console.log(` • Augmented newData: +${addedCgoCount} CGO-only, +${addedCloudOnlyCount} cloud-only`)
928
+ }
929
+
833
930
  // Generate diff
834
931
  console.log(`\nGenerating diff: ${fromVer} → ${toVer}...`)
835
932
  const { generateConnectorDiffJson } = require('./report-delta.js')
836
933
 
837
934
  const diffData = generateConnectorDiffJson(
838
935
  oldData,
839
- newData,
936
+ augmentedNewData,
840
937
  {
841
938
  oldVersion: fromVer,
842
939
  newVersion: toVer,
@@ -850,6 +947,12 @@ async function handleRpcnConnectorDocs (options) {
850
947
  fs.writeFileSync(diffPath, JSON.stringify(diffData, null, 2), 'utf8')
851
948
  console.log(`✓ Diff saved: ${path.basename(diffPath)}`)
852
949
 
950
+ // Save augmented data back to file for subsequent iterations
951
+ // This ensures the next version pair has consistent metadata when loading this version as oldData
952
+ const augmentedDataPath = path.join(dataDir, `connect-${toVer}.json`)
953
+ fs.writeFileSync(augmentedDataPath, JSON.stringify(augmentedNewData, null, 2), 'utf8')
954
+ console.log(`✓ Augmented data saved: connect-${toVer}.json`)
955
+
853
956
  // Update what's-new if requested
854
957
  if (options.updateWhatsNew) {
855
958
  console.log(`Updating what's-new.adoc for ${toVer}...`)
@@ -1097,73 +1200,10 @@ async function handleRpcnConnectorDocs (options) {
1097
1200
 
1098
1201
  const connectorData = JSON.parse(fs.readFileSync(dataFile, 'utf8'))
1099
1202
 
1100
- const cloudSet = new Set(
1101
- (binaryAnalysis.comparison?.inCloud || []).map(c => `${c.type}:${c.name}`)
1102
- )
1103
- const cgoOnlySet = new Set(
1104
- (binaryAnalysis.cgoOnly || []).map(c => `${c.type}:${c.name}`)
1105
- )
1106
-
1107
- let augmentedCount = 0
1108
- let addedCgoCount = 0
1109
- let addedCloudOnlyCount = 0
1110
-
1111
- const connectorTypes = ['inputs', 'outputs', 'processors', 'caches', 'rate_limits',
1112
- 'buffers', 'metrics', 'scanners', 'tracers']
1203
+ const { augmentedData, augmentedCount, addedCgoCount, addedCloudOnlyCount } =
1204
+ augmentConnectorData(connectorData, binaryAnalysis)
1113
1205
 
1114
- for (const type of connectorTypes) {
1115
- if (!Array.isArray(connectorData[type])) {
1116
- connectorData[type] = []
1117
- }
1118
-
1119
- for (const connector of connectorData[type]) {
1120
- const key = `${type}:${connector.name}`
1121
- connector.cloudSupported = cloudSet.has(key)
1122
- connector.requiresCgo = cgoOnlySet.has(key)
1123
- augmentedCount++
1124
- }
1125
-
1126
- if (binaryAnalysis.cgoOnly) {
1127
- for (const cgoConn of binaryAnalysis.cgoOnly) {
1128
- if (cgoConn.type === type) {
1129
- const exists = connectorData[type].some(c => c.name === cgoConn.name)
1130
- if (!exists) {
1131
- // Singularize type for consistency with stored data (except "metrics" which stays plural)
1132
- const componentType = cgoConn.type === 'metrics' ? 'metrics' : cgoConn.type.replace(/s$/, '')
1133
- connectorData[type].push({
1134
- ...cgoConn,
1135
- type: componentType,
1136
- cloudSupported: false,
1137
- requiresCgo: true
1138
- })
1139
- addedCgoCount++
1140
- }
1141
- }
1142
- }
1143
- }
1144
-
1145
- if (binaryAnalysis.comparison?.cloudOnly) {
1146
- for (const cloudConn of binaryAnalysis.comparison.cloudOnly) {
1147
- if (cloudConn.type === type) {
1148
- const exists = connectorData[type].some(c => c.name === cloudConn.name)
1149
- if (!exists) {
1150
- // Singularize type for consistency with stored data (except "metrics" which stays plural)
1151
- const componentType = cloudConn.type === 'metrics' ? 'metrics' : cloudConn.type.replace(/s$/, '')
1152
- connectorData[type].push({
1153
- ...cloudConn,
1154
- type: componentType,
1155
- cloudSupported: true,
1156
- requiresCgo: false,
1157
- cloudOnly: true
1158
- })
1159
- addedCloudOnlyCount++
1160
- }
1161
- }
1162
- }
1163
- }
1164
- }
1165
-
1166
- fs.writeFileSync(dataFile, JSON.stringify(connectorData, null, 2), 'utf8')
1206
+ fs.writeFileSync(dataFile, JSON.stringify(augmentedData, null, 2), 'utf8')
1167
1207
  console.log(`Done: Augmented ${augmentedCount} connectors with cloud/cgo fields`)
1168
1208
  if (addedCgoCount > 0) {
1169
1209
  console.log(` • Added ${addedCgoCount} cgo-only connectors to data file`)
@@ -1172,8 +1212,7 @@ async function handleRpcnConnectorDocs (options) {
1172
1212
  console.log(` • Added ${addedCloudOnlyCount} cloud-only connectors to data file`)
1173
1213
  }
1174
1214
 
1175
- // Keep only the latest version (delete all older versions)
1176
- // BUT preserve any files from intermediate processing during this run
1215
+ // Keep only the latest 2 versions (by semver), delete all others
1177
1216
  const dataVersions = fs.readdirSync(dataDir)
1178
1217
  .filter(f => /^connect-(\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?)\.json$/.test(f))
1179
1218
  .map(f => {
@@ -1181,21 +1220,12 @@ async function handleRpcnConnectorDocs (options) {
1181
1220
  return match ? match[1] : null
1182
1221
  })
1183
1222
  .filter(v => v && semver.valid(v))
1223
+ .sort((a, b) => semver.rcompare(a, b)) // Sort descending (newest first)
1184
1224
 
1185
- // Build list of versions we need to keep for this run
1186
- const versionsToKeep = new Set([newVersion]); // Always keep the latest
1187
- if (intermediateProcessingResults.length > 0) {
1188
- // Keep intermediate versions from this run
1189
- intermediateProcessingResults.forEach(r => {
1190
- versionsToKeep.add(r.fromVersion);
1191
- versionsToKeep.add(r.toVersion);
1192
- });
1193
- }
1194
- if (oldVersion) {
1195
- versionsToKeep.add(oldVersion); // Keep old version for diff
1196
- }
1225
+ // Keep only the latest 2 versions
1226
+ const versionsToKeep = new Set(dataVersions.slice(0, 2))
1197
1227
 
1198
- // Delete only files that are NOT needed for this run
1228
+ // Delete all older versions
1199
1229
  for (const version of dataVersions) {
1200
1230
  if (!versionsToKeep.has(version)) {
1201
1231
  const dataFile = `connect-${version}.json`
@@ -1205,9 +1235,29 @@ async function handleRpcnConnectorDocs (options) {
1205
1235
  }
1206
1236
  }
1207
1237
 
1238
+ // Also clean up old CSV files (keep latest 2)
1239
+ const csvVersions = fs.readdirSync(dataDir)
1240
+ .filter(f => /^connect-info-(\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?)\.csv$/.test(f))
1241
+ .map(f => {
1242
+ const match = f.match(/^connect-info-(\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?)\.csv$/)
1243
+ return match ? match[1] : null
1244
+ })
1245
+ .filter(v => v && semver.valid(v))
1246
+ .sort((a, b) => semver.rcompare(a, b))
1247
+
1248
+ const csvToKeep = new Set(csvVersions.slice(0, 2))
1249
+ for (const version of csvVersions) {
1250
+ if (!csvToKeep.has(version)) {
1251
+ const csvFile = `connect-info-${version}.csv`
1252
+ const csvPath = path.join(dataDir, csvFile);
1253
+ fs.unlinkSync(csvPath);
1254
+ console.log(`🧹 Deleted old CSV from docs-data: ${csvFile}`);
1255
+ }
1256
+ }
1257
+
1208
1258
  // IMPORTANT: Reload newIndex with augmented data for unified diff
1209
1259
  // The unified diff approach compares platform metadata to detect transitions
1210
- newIndex = connectorData
1260
+ newIndex = augmentedData
1211
1261
  console.log(`✓ Reloaded newIndex with augmented data for diff comparison`)
1212
1262
  } catch (err) {
1213
1263
  console.error(`Warning: Failed to augment data file: ${err.message}`)