@redpanda-data/docs-extensions-and-macros 4.13.0 → 4.13.2

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.
Files changed (49) hide show
  1. package/bin/doc-tools-mcp.js +15 -3
  2. package/bin/doc-tools.js +767 -2088
  3. package/bin/mcp-tools/property-docs.js +18 -0
  4. package/bin/mcp-tools/rpcn-docs.js +28 -3
  5. package/cli-utils/antora-utils.js +53 -2
  6. package/cli-utils/dependencies.js +313 -0
  7. package/cli-utils/diff-utils.js +273 -0
  8. package/cli-utils/doc-tools-utils.js +54 -0
  9. package/extensions/algolia-indexer/generate-index.js +134 -102
  10. package/extensions/algolia-indexer/index.js +70 -38
  11. package/extensions/collect-bloblang-samples.js +2 -1
  12. package/extensions/generate-rp-connect-categories.js +126 -67
  13. package/extensions/generate-rp-connect-info.js +291 -137
  14. package/macros/rp-connect-components.js +34 -5
  15. package/mcp/CLI_INTERFACE.adoc +384 -0
  16. package/mcp/COSTS.adoc +167 -0
  17. package/mcp/DEVELOPMENT.adoc +726 -0
  18. package/mcp/README.adoc +172 -0
  19. package/mcp/USER_GUIDE.adoc +1392 -0
  20. package/mcp/WRITER_EXTENSION_GUIDE.adoc +814 -0
  21. package/mcp/prompts/README.adoc +183 -0
  22. package/mcp/prompts/property-docs-guide.md +283 -0
  23. package/mcp/prompts/review-for-style.md +128 -0
  24. package/mcp/prompts/rpcn-connector-docs-guide.md +126 -0
  25. package/mcp/prompts/write-new-guide.md +222 -0
  26. package/mcp/team-standards/style-guide.md +321 -0
  27. package/mcp/templates/README.adoc +212 -0
  28. package/mcp/templates/prompt-review-template.md +80 -0
  29. package/mcp/templates/prompt-write-template.md +110 -0
  30. package/mcp/templates/resource-template.md +76 -0
  31. package/package.json +8 -5
  32. package/tools/add-commercial-names.js +207 -0
  33. package/tools/generate-cli-docs.js +6 -2
  34. package/tools/get-console-version.js +5 -0
  35. package/tools/get-redpanda-version.js +5 -0
  36. package/tools/property-extractor/compare-properties.js +3 -3
  37. package/tools/property-extractor/generate-handlebars-docs.js +14 -14
  38. package/tools/property-extractor/generate-pr-summary.js +46 -0
  39. package/tools/property-extractor/pr-summary-formatter.js +375 -0
  40. package/tools/redpanda-connect/README.adoc +403 -38
  41. package/tools/redpanda-connect/connector-binary-analyzer.js +588 -0
  42. package/tools/redpanda-connect/generate-rpcn-connector-docs.js +97 -34
  43. package/tools/redpanda-connect/parse-csv-connectors.js +1 -1
  44. package/tools/redpanda-connect/pr-summary-formatter.js +601 -0
  45. package/tools/redpanda-connect/report-delta.js +69 -2
  46. package/tools/redpanda-connect/rpcn-connector-docs-handler.js +1180 -0
  47. package/tools/redpanda-connect/templates/connector.hbs +38 -0
  48. package/tools/redpanda-connect/templates/intro.hbs +0 -20
  49. package/tools/redpanda-connect/update-nav.js +205 -0
@@ -1,186 +1,340 @@
1
1
  'use strict'
2
- const fs = require('fs');
3
- const path = require('path');
4
- const Papa = require('papaparse');
2
+ const fs = require('fs')
3
+ const path = require('path')
4
+ const Papa = require('papaparse')
5
5
 
6
- const CSV_PATH = 'internal/plugins/info.csv'
7
- const GITHUB_OWNER = 'redpanda-data'
8
- const GITHUB_REPO = 'connect'
9
- const GITHUB_REF = 'main'
6
+ // Default configuration - can be overridden via playbook config
7
+ const DEFAULTS = {
8
+ csvPath: 'internal/plugins/info.csv',
9
+ githubOwner: 'redpanda-data',
10
+ githubRepo: 'connect'
11
+ }
10
12
 
11
13
  module.exports.register = function ({ config }) {
12
- const logger = this.getLogger('redpanda-connect-info-extension');
14
+ const logger = this.getLogger('redpanda-connect-info-extension')
15
+ const { getAntoraValue } = require('../cli-utils/antora-utils')
13
16
 
14
- async function loadOctokit() {
15
- const { Octokit } = await import('@octokit/rest');
17
+ // Merge config with defaults
18
+ const {
19
+ csvpath,
20
+ csvPath = DEFAULTS.csvPath,
21
+ githubOwner = DEFAULTS.githubOwner,
22
+ githubRepo = DEFAULTS.githubRepo
23
+ } = config || {}
24
+
25
+ // Use csvpath (legacy) or csvPath
26
+ const localCsvPath = csvpath || null
27
+
28
+ async function loadOctokit () {
29
+ const { Octokit } = await import('@octokit/rest')
16
30
  const { getGitHubToken } = require('../cli-utils/github-token')
17
31
  const token = getGitHubToken()
18
- return token ? new Octokit({ auth: token }) : new Octokit();
32
+ return token ? new Octokit({ auth: token }) : new Octokit()
19
33
  }
20
34
 
21
- this.once('contentClassified', async ({ contentCatalog }) => {
22
- const redpandaConnect = contentCatalog.getComponents().find(component => component.name === 'redpanda-connect');
23
- const redpandaCloud = contentCatalog.getComponents().find(component => component.name === 'redpanda-cloud');
24
- const preview = contentCatalog.getComponents().find(component => component.name === 'preview');
25
- if (!redpandaConnect) return;
26
- const pages = contentCatalog.getPages();
35
+ // Use 'on' and return the promise so Antora waits for async completion
36
+ this.on('contentClassified', ({ contentCatalog }) => {
37
+ return processContent(contentCatalog)
38
+ })
39
+
40
+ async function processContent (contentCatalog) {
41
+ const redpandaConnect = contentCatalog.getComponents().find(component => component.name === 'redpanda-connect')
42
+ const redpandaCloud = contentCatalog.getComponents().find(component => component.name === 'redpanda-cloud')
43
+ const preview = contentCatalog.getComponents().find(component => component.name === 'preview')
44
+
45
+ if (!redpandaConnect) {
46
+ logger.warn('redpanda-connect component not found, skipping CSV enrichment')
47
+ return
48
+ }
49
+
50
+ const pages = contentCatalog.getPages()
27
51
 
28
52
  try {
29
- // Fetch CSV data (either from local file or GitHub)
30
- const csvData = await fetchCSV(config.csvpath);
31
- const parsedData = Papa.parse(csvData, { header: true, skipEmptyLines: true });
32
- const enrichedData = translateCsvData(parsedData, pages, logger);
33
- parsedData.data = enrichedData;
34
-
35
- if (redpandaConnect) {
36
- redpandaConnect.latest.asciidoc.attributes.csvData = parsedData;
37
- }
38
- if (redpandaCloud) {
39
- redpandaCloud.latest.asciidoc.attributes.csvData = parsedData;
53
+ // Get the Connect version from antora.yml
54
+ const connectVersion = getAntoraValue('asciidoc.attributes.latest-connect-version')
55
+
56
+ // Fetch CSV data (from local file first, then GitHub as fallback)
57
+ const csvData = await fetchCSV(localCsvPath, connectVersion, logger)
58
+ const parsedData = Papa.parse(csvData, { header: true, skipEmptyLines: true })
59
+ const enrichedData = translateCsvData(parsedData, pages, logger)
60
+ parsedData.data = enrichedData
61
+
62
+ // Set csvData on all relevant components
63
+ const componentsToEnrich = [redpandaConnect, redpandaCloud, preview].filter(Boolean)
64
+ for (const component of componentsToEnrich) {
65
+ if (component.latest?.asciidoc?.attributes) {
66
+ component.latest.asciidoc.attributes.csvData = parsedData
67
+ }
40
68
  }
41
- // For previewing the data on our extensions site
42
- if (preview) {
43
- preview.latest.asciidoc.attributes.csvData = parsedData;
69
+
70
+ // Enrich component pages with commercial names from CSV + AsciiDoc
71
+ const commercialNamesMap = enrichPagesWithCommercialNames(pages, parsedData, logger)
72
+
73
+ // Convert Map to plain object for serialization and macro access
74
+ const commercialNamesObj = {}
75
+ commercialNamesMap.forEach((names, connector) => {
76
+ commercialNamesObj[connector] = Array.from(names)
77
+ })
78
+
79
+ // Make commercial names available to macros
80
+ for (const component of componentsToEnrich) {
81
+ if (component.latest?.asciidoc?.attributes) {
82
+ component.latest.asciidoc.attributes.commercialNamesMap = commercialNamesObj
83
+ }
44
84
  }
45
85
 
86
+ logger.info(`Successfully processed ${parsedData.data.length} connectors from CSV`)
46
87
  } catch (error) {
47
- logger.error('Error fetching or parsing CSV data:', error.message);
48
- logger.error(error.stack);
88
+ logger.error(`Error fetching or parsing CSV data: ${error.message}`)
89
+ logger.error(error.stack)
90
+ // Don't throw - allow build to continue with degraded functionality
49
91
  }
50
- });
92
+ }
51
93
 
52
- // Check for local CSV file first. If not found, fetch from GitHub
53
- async function fetchCSV(localCsvPath) {
54
- if (localCsvPath && fs.existsSync(localCsvPath)) {
55
- if (path.extname(localCsvPath).toLowerCase() !== '.csv') {
56
- throw new Error(`Invalid file type: ${localCsvPath}. Expected a CSV file.`);
94
+ // Fetch CSV from GitHub or local file (local file for testing/override only)
95
+ async function fetchCSV (localPath, connectVersion, logger) {
96
+ // Priority 1: Use explicitly provided CSV path (for testing/override)
97
+ if (localPath && fs.existsSync(localPath)) {
98
+ if (path.extname(localPath).toLowerCase() !== '.csv') {
99
+ throw new Error(`Invalid file type: ${localPath}. Expected a CSV file.`)
57
100
  }
58
- logger.info(`Loading CSV data from local file: ${localCsvPath}`);
59
- return fs.readFileSync(localCsvPath, 'utf8');
60
- } else {
61
- logger.info('Local CSV file not found. Fetching from GitHub...');
62
- return await fetchCsvFromGitHub();
101
+ logger.info(`Loading CSV data from local file: ${localPath}`)
102
+ return fs.readFileSync(localPath, 'utf8')
63
103
  }
104
+
105
+ // Priority 2: Fetch from GitHub using the version tag
106
+ logger.info(`Fetching CSV from GitHub (version: ${connectVersion || 'main'})...`)
107
+ return fetchCsvFromGitHub(connectVersion)
64
108
  }
65
109
 
66
110
  // Fetch CSV data from GitHub
67
- async function fetchCsvFromGitHub() {
68
- const octokit = await loadOctokit();
111
+ async function fetchCsvFromGitHub (connectVersion) {
112
+ const octokit = await loadOctokit()
113
+ // Normalize version: trim whitespace and remove leading 'v' if present
114
+ const normalizedVersion = connectVersion ? connectVersion.trim().replace(/^v/, '') : ''
115
+ // Use version tag if valid, otherwise fallback to main branch
116
+ const ref = normalizedVersion ? `v${normalizedVersion}` : 'main'
117
+
69
118
  try {
70
119
  const { data: fileContent } = await octokit.rest.repos.getContent({
71
- owner: GITHUB_OWNER,
72
- repo: GITHUB_REPO,
73
- path: CSV_PATH,
74
- ref: GITHUB_REF,
75
- });
76
- return Buffer.from(fileContent.content, 'base64').toString('utf8');
120
+ owner: githubOwner,
121
+ repo: githubRepo,
122
+ path: csvPath,
123
+ ref: ref
124
+ })
125
+ return Buffer.from(fileContent.content, 'base64').toString('utf8')
77
126
  } catch (error) {
78
- console.error('Error fetching Redpanda Connect catalog from GitHub:', error);
79
- return '';
127
+ logger.error(`Error fetching Redpanda Connect catalog from GitHub (ref: ${ref}): ${error.message}`)
128
+ throw error
80
129
  }
81
130
  }
82
131
 
83
132
  /**
84
133
  * Transforms and enriches parsed CSV connector data with normalized fields and documentation URLs.
85
- *
86
- * Each row is trimmed, mapped to expected output fields, and enriched with documentation URLs for Redpanda Connect and Cloud components if available. The `support` field is normalized, and licensing information is derived. Logs a warning if documentation URLs are missing for non-deprecated, non-SQL driver connectors that indicate cloud support.
87
- *
88
- * @param {object} parsedData - Parsed CSV data containing connector rows.
89
- * @param {array} pages - Array of page objects used to resolve documentation URLs.
90
- * @param {object} logger - Logger instance for warning about missing documentation.
91
- * @returns {array} Array of enriched connector objects with normalized fields and URLs.
134
+ * Uses O(n) lookup maps for efficient page matching.
92
135
  */
93
- function translateCsvData(parsedData, pages, logger) {
136
+ function translateCsvData (parsedData, pages, logger) {
137
+ // Build lookup maps once for O(1) access - much faster than O(n) iteration per row
138
+ const connectPages = new Map()
139
+ const cloudPages = new Map()
140
+
141
+ for (const file of pages) {
142
+ const { component } = file.src
143
+ const stem = file.src.stem
144
+ const filePath = file.path
145
+
146
+ if (component === 'redpanda-connect') {
147
+ // Store by stem, but only for connector doc paths
148
+ if (isConnectorDocPath(filePath, file)) {
149
+ const type = extractTypeFromPath(filePath)
150
+ if (type) {
151
+ const key = `${stem}:${type}`
152
+ connectPages.set(key, file)
153
+ }
154
+ }
155
+ } else if (component === 'redpanda-cloud') {
156
+ // Cloud docs have a specific path pattern
157
+ const cloudMatch = filePath.match(/connect\/components\/([^/]+)s\/([^/]+)\.adoc$/)
158
+ if (cloudMatch) {
159
+ const [, type, name] = cloudMatch
160
+ const key = `${name}:${type}`
161
+ cloudPages.set(key, file)
162
+ }
163
+ }
164
+ }
165
+
166
+ function isConnectorDocPath (filePath) {
167
+ const dirsToCheck = [
168
+ '/pages/inputs/',
169
+ '/pages/outputs/',
170
+ '/pages/processors/',
171
+ '/pages/caches/',
172
+ '/pages/rate_limits/',
173
+ '/pages/buffers/',
174
+ '/pages/metrics/',
175
+ '/pages/tracers/',
176
+ '/pages/scanners/',
177
+ '/partials/components/'
178
+ ]
179
+ return dirsToCheck.some(dir => filePath.includes(dir))
180
+ }
181
+
182
+ function extractTypeFromPath (filePath) {
183
+ const typeMatch = filePath.match(/\/(inputs|outputs|processors|caches|rate_limits|buffers|metrics|tracers|scanners)\//)
184
+ if (typeMatch) {
185
+ // Convert plural to singular
186
+ return typeMatch[1].replace(/s$/, '').replace('rate_limit', 'rate_limit')
187
+ }
188
+ return null
189
+ }
190
+
94
191
  return parsedData.data.map(row => {
95
192
  // Create a new object with trimmed keys and values
96
193
  const trimmedRow = Object.fromEntries(
97
- Object.entries(row).map(([key, value]) => [key.trim(), value.trim()])
98
- );
194
+ Object.entries(row).map(([key, value]) => [key.trim(), (value || '').trim()])
195
+ )
99
196
 
100
197
  // Map fields from the trimmed row to the desired output
101
- const connector = trimmedRow.name;
102
- const type = trimmedRow.type;
103
- const commercial_name = trimmedRow.commercial_name;
104
- const available_connect_version = trimmedRow.version;
105
- const deprecated = trimmedRow.deprecated.toLowerCase() === 'y' ? 'y' : 'n';
106
- const is_cloud_supported = trimmedRow.cloud.toLowerCase() === 'y' ? 'y' : 'n';
107
- const cloud_ai = trimmedRow.cloud_with_gpu.toLowerCase() === 'y' ? 'y' : 'n';
198
+ const connector = trimmedRow.name
199
+ const type = trimmedRow.type
200
+ const commercialName = trimmedRow.commercial_name
201
+ const availableConnectVersion = trimmedRow.version
202
+ const deprecated = (trimmedRow.deprecated || '').toLowerCase() === 'y' ? 'y' : 'n'
203
+ const isCloudSupported = (trimmedRow.cloud || '').toLowerCase() === 'y' ? 'y' : 'n'
204
+ const cloudAi = (trimmedRow.cloud_with_gpu || '').toLowerCase() === 'y' ? 'y' : 'n'
205
+
108
206
  // Handle enterprise to certified conversion and set enterprise license flag
109
- const originalSupport = trimmedRow.support.toLowerCase();
110
- const support_level = originalSupport === 'enterprise' ? 'certified' : originalSupport;
111
- const is_licensed = originalSupport === 'enterprise' ? 'Yes' : 'No';
112
-
113
- // Redpanda Connect and Cloud enrichment URLs
114
- let redpandaConnectUrl = '';
115
- let redpandaCloudUrl = '';
116
-
117
- function isConnectorDocPath (filePath, type) {
118
- const dirsToCheck = [
119
- `pages/${type}s/`,
120
- `partials/components/${type}s/`
121
- ];
122
- return dirsToCheck.some(dir => filePath.includes(dir));
123
- }
207
+ const originalSupport = (trimmedRow.support || '').toLowerCase()
208
+ const supportLevel = originalSupport === 'enterprise' ? 'certified' : originalSupport
209
+ const isLicensed = originalSupport === 'enterprise' ? 'Yes' : 'No'
124
210
 
125
- function isCloudDoc(file, connector, type) {
126
- return (
127
- file.src.component === 'redpanda-cloud' &&
128
- file.path.includes(`connect/components/${type}s/${connector}.adoc`)
129
- );
130
- }
211
+ // O(1) lookup for URLs
212
+ const lookupKey = `${connector}:${type}`
213
+ const connectPage = connectPages.get(lookupKey)
214
+ const cloudPage = cloudPages.get(lookupKey)
131
215
 
132
- // Look for both Redpanda Connect and Cloud URLs
133
- for (const file of pages) {
134
- const { component } = file.src; // such as 'redpanda-connect'
135
- const { path: filePath } = file; // such as modules/.../pages/sinks/foo.adoc
136
-
137
- if (
138
- component === 'redpanda-connect' &&
139
- filePath.endsWith(`/${connector}.adoc`) &&
140
- isConnectorDocPath(filePath, type)
141
- ) {
142
- redpandaConnectUrl = file.pub.url;
143
- }
216
+ const redpandaConnectUrl = connectPage?.pub?.url || ''
217
+ const redpandaCloudUrl = cloudPage?.pub?.url || ''
144
218
 
145
- // -------------------------------------------------
146
- // Redpanda Cloud (only if cloud supported)
147
- // -------------------------------------------------
148
- if (is_cloud_supported === 'y' && isCloudDoc(file, connector, type)) {
149
- redpandaCloudUrl = file.pub.url;
219
+ // Warn about missing docs (but not for deprecated or SQL drivers)
220
+ if (deprecated !== 'y' && !connector.includes('sql_driver')) {
221
+ if (!redpandaConnectUrl) {
222
+ logger.warn(`Self-Managed docs missing for: ${connector} of type: ${type}`)
223
+ }
224
+ if (isCloudSupported === 'y' && !redpandaCloudUrl && redpandaConnectUrl) {
225
+ logger.warn(`Cloud docs missing for: ${connector} of type: ${type}`)
150
226
  }
151
227
  }
152
228
 
153
- if (
154
- deprecated !== 'y' &&
155
- !connector.includes('sql_driver') &&
156
- !redpandaConnectUrl &&
157
- !(is_cloud_supported === 'y' && redpandaCloudUrl)
158
- ) {
159
- logger.warn(`Self-Managed docs missing for: ${connector} of type: ${type}`);
160
- }
161
-
162
- if (
163
- is_cloud_supported === 'y' &&
164
- !redpandaCloudUrl &&
165
- redpandaConnectUrl
166
- ) {
167
- logger.warn(`Cloud docs missing for: ${connector} of type: ${type}`);
168
- }
169
-
170
- // Return the translated and enriched row
171
229
  return {
172
230
  connector,
173
231
  type,
174
- commercial_name,
175
- available_connect_version,
176
- support_level, // "enterprise" is replaced with "certified"
232
+ commercial_name: commercialName,
233
+ available_connect_version: availableConnectVersion,
234
+ support_level: supportLevel,
177
235
  deprecated,
178
- is_cloud_supported,
179
- cloud_ai,
180
- is_licensed, // "Yes" if the original support level was "enterprise"
236
+ is_cloud_supported: isCloudSupported,
237
+ cloud_ai: cloudAi,
238
+ is_licensed: isLicensed,
181
239
  redpandaConnectUrl,
182
- redpandaCloudUrl,
183
- };
184
- });
240
+ redpandaCloudUrl
241
+ }
242
+ })
243
+ }
244
+
245
+ /**
246
+ * Enriches component pages with commercial names from CSV data and existing AsciiDoc attributes.
247
+ */
248
+ function enrichPagesWithCommercialNames (pages, parsedData, logger) {
249
+ // Build a lookup map: connector name -> Set of commercial names from CSV
250
+ const csvCommercialNames = new Map()
251
+
252
+ for (const row of parsedData.data) {
253
+ const { connector, commercial_name: commercialName } = row
254
+ if (!connector || !commercialName) continue
255
+
256
+ // Skip N/A and empty values
257
+ const trimmedName = commercialName.trim()
258
+ if (trimmedName.toLowerCase() === 'n/a' || trimmedName === '') continue
259
+
260
+ if (!csvCommercialNames.has(connector)) {
261
+ csvCommercialNames.set(connector, new Set())
262
+ }
263
+
264
+ // Add the commercial name if it's different from the connector name
265
+ if (trimmedName.toLowerCase() !== connector.toLowerCase()) {
266
+ csvCommercialNames.get(connector).add(trimmedName)
267
+ }
268
+ }
269
+
270
+ // Enrich each component page with combined commercial names
271
+ let enrichedCount = 0
272
+
273
+ for (const page of pages) {
274
+ const { component, relative, module: moduleName } = page.src
275
+
276
+ // Only process Redpanda Connect and Cloud component pages
277
+ if (component !== 'redpanda-connect' && component !== 'redpanda-cloud') continue
278
+
279
+ // Match component documentation pages:
280
+ // 1. Cloud-style paths: connect/components/processors/archive.adoc
281
+ // 2. Connect module-based paths: module=components, relative=processors/archive.adoc
282
+ const isComponentsModule = moduleName === 'components'
283
+ const hasComponentsInPath = relative.includes('/components/')
284
+
285
+ if (!isComponentsModule && !hasComponentsInPath) continue
286
+
287
+ // Extract connector name from path
288
+ let connectorMatch
289
+ if (hasComponentsInPath) {
290
+ connectorMatch = relative.match(/\/components\/[^/]+\/([^/]+)\.adoc$/)
291
+ } else if (isComponentsModule) {
292
+ connectorMatch = relative.match(/^[^/]+\/([^/]+)\.adoc$/)
293
+ }
294
+
295
+ if (!connectorMatch) continue
296
+
297
+ const connectorName = connectorMatch[1]
298
+ const csvNames = csvCommercialNames.get(connectorName) || new Set()
299
+
300
+ // Get existing commercial names from AsciiDoc page attribute
301
+ let existingNames = []
302
+ const existingAttr = page.asciidoc?.attributes?.['page-commercial-names']
303
+
304
+ if (existingAttr) {
305
+ existingNames = existingAttr.split(',').map(n => n.trim()).filter(n => n)
306
+ } else if (page.contents) {
307
+ // Fallback: parse from file contents if attribute not yet available
308
+ // Note: This regex handles single-line attributes only
309
+ const fileContents = page.contents.toString('utf8')
310
+ const attrMatch = fileContents.match(/:page-commercial-names:\s*(.+)/)
311
+ if (attrMatch) {
312
+ existingNames = attrMatch[1].split(',').map(n => n.trim()).filter(n => n)
313
+ }
314
+ }
315
+
316
+ // Combine CSV names and existing names, deduplicate
317
+ const allNames = new Set([...csvNames, ...existingNames])
318
+
319
+ if (allNames.size > 0) {
320
+ // Ensure attributes object exists
321
+ if (!page.asciidoc) page.asciidoc = {}
322
+ if (!page.asciidoc.attributes) page.asciidoc.attributes = {}
323
+
324
+ // Set the combined commercial names as a comma-separated list
325
+ const commercialNamesList = Array.from(allNames).join(', ')
326
+ page.asciidoc.attributes['page-commercial-names'] = commercialNamesList
327
+ enrichedCount++
328
+
329
+ // Update the mapping with the enriched names
330
+ csvCommercialNames.set(connectorName, allNames)
331
+
332
+ logger.debug(`Added commercial names to ${connectorName}: ${commercialNamesList}`)
333
+ }
334
+ }
335
+
336
+ logger.info(`Enriched ${enrichedCount} component pages with commercial names`)
337
+
338
+ return csvCommercialNames
185
339
  }
186
- }
340
+ }
@@ -350,7 +350,7 @@ module.exports.register = function (registry, context) {
350
350
  * - Enterprise licensing information
351
351
  * - Cloud support status (Yes/No with a link if applicable)
352
352
  */
353
- function generateConnectorsHTMLTable(connectors, sqlDrivers, isCloud, showAllInfo) {
353
+ function generateConnectorsHTMLTable(connectors, sqlDrivers, isCloud, showAllInfo, commercialNamesMap = {}) {
354
354
  return Object.entries(connectors)
355
355
  .filter(([_, details]) => {
356
356
  // If isCloud is true, filter out rows that do not support cloud
@@ -468,12 +468,38 @@ module.exports.register = function (registry, context) {
468
468
 
469
469
  const firstUrl = getFirstUrlFromTypesArray(Array.from(types.entries()), isCloud);
470
470
 
471
+ // Collect all unique commercial names for search
472
+ // First try to get enriched names from the map, then fall back to CSV
473
+ const allCommercialNames = new Set();
474
+ if (commercialNamesMap[connector]) {
475
+ // Use enriched names from the map (includes CSV + AsciiDoc)
476
+ commercialNamesMap[connector].forEach(name => {
477
+ if (name.toLowerCase() !== connector.toLowerCase() &&
478
+ name.toLowerCase() !== 'n/a') {
479
+ allCommercialNames.add(name);
480
+ }
481
+ });
482
+ } else {
483
+ // Fallback to CSV-only names
484
+ Array.from(types.entries()).forEach(([type, commercialNames]) => {
485
+ Object.keys(commercialNames).forEach(commercialName => {
486
+ if (commercialName.toLowerCase() !== connector.toLowerCase() &&
487
+ commercialName.toLowerCase() !== 'n/a') {
488
+ allCommercialNames.add(commercialName);
489
+ }
490
+ });
491
+ });
492
+ }
493
+ const commercialNamesText = allCommercialNames.size > 0
494
+ ? `<span class="search-terms" style="position: absolute; left: -9999px;">${Array.from(allCommercialNames).join(' ')}</span>`
495
+ : '';
496
+
471
497
  // Logic for showAllInfo = true and isCloud = false
472
498
  if (showAllInfo && !isCloud) {
473
499
  return `
474
500
  <tr id="row-${id}">
475
501
  <td class="tableblock halign-left valign-top" id="componentName-${id}">
476
- <p class="tableblock"><a href="${firstUrl}"><code>${connector}</code></a></p>
502
+ <p class="tableblock"><a href="${firstUrl}"><code>${connector}</code></a>${commercialNamesText}</p>
477
503
  </td>
478
504
  <td class="tableblock halign-left valign-top" id="componentType-${id}">
479
505
  <p class="tableblock">${typesArray}</p> <!-- Display types linked to Connect URL only -->
@@ -494,7 +520,7 @@ module.exports.register = function (registry, context) {
494
520
  return `
495
521
  <tr id="row-${id}">
496
522
  <td class="tableblock halign-left valign-top" id="componentName-${id}">
497
- <p class="tableblock"><a href="${firstUrl}"><code>${connector}</code></a></p>
523
+ <p class="tableblock"><a href="${firstUrl}"><code>${connector}</code></a>${commercialNamesText}</p>
498
524
  </td>
499
525
  <td class="tableblock halign-left valign-top" id="componentType-${id}">
500
526
  ${typesArray} <!-- Display bulleted list for cloud types if commercial name differs -->
@@ -505,7 +531,7 @@ module.exports.register = function (registry, context) {
505
531
  return `
506
532
  <tr id="row-${id}">
507
533
  <td class="tableblock halign-left valign-top" id="componentName-${id}">
508
- <p class="tableblock"><a href="${firstUrl}"><code>${connector}</code></a></p>
534
+ <p class="tableblock"><a href="${firstUrl}"><code>${connector}</code></a>${commercialNamesText}</p>
509
535
  </td>
510
536
  <td class="tableblock halign-left valign-top" id="componentType-${id}">
511
537
  <p class="tableblock">${typesArray}</p> <!-- Display types without commercial names -->
@@ -576,6 +602,9 @@ module.exports.register = function (registry, context) {
576
602
  const csvData = context.config?.attributes?.csvData || null;
577
603
  if (!csvData) return console.error(`CSV data is not available for ${parent.getDocument().getAttributes()['page-relative-src-path']}. Make sure your playbook includes the generate-rp-connect-info extension.`)
578
604
 
605
+ // Get the enriched commercial names map (includes CSV + AsciiDoc names)
606
+ const commercialNamesMap = context.config?.attributes?.commercialNamesMap || {};
607
+
579
608
  const sqlDriversData = processSqlDrivers(csvData);
580
609
 
581
610
  const types = new Set();
@@ -695,7 +724,7 @@ module.exports.register = function (registry, context) {
695
724
  </tr>
696
725
  </thead>
697
726
  <tbody>
698
- ${generateConnectorsHTMLTable(processConnectors(csvData), sqlDriversData, isCloud, showAllInfo)}
727
+ ${generateConnectorsHTMLTable(processConnectors(csvData), sqlDriversData, isCloud, showAllInfo, commercialNamesMap)}
699
728
  </tbody>
700
729
  </table>
701
730
  <script>