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

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.
@@ -362,7 +362,7 @@ const tools = [
362
362
  },
363
363
  surface: {
364
364
  type: 'string',
365
- description: 'Which API surface(s) to bundle (optional, defaults to "both")',
365
+ description: 'Which API surfaces to bundle (optional, defaults to "both")',
366
366
  enum: ['admin', 'connect', 'both']
367
367
  },
368
368
  out_admin: {
package/bin/doc-tools.js CHANGED
@@ -1630,7 +1630,7 @@ automation
1630
1630
  .option('-t, --tag <tag>', 'Git tag for released content')
1631
1631
  .option('-b, --branch <branch>', 'Branch name for in-progress content')
1632
1632
  .option('--repo <url>', 'Repository URL', 'https://github.com/redpanda-data/redpanda.git')
1633
- .addOption(new Option('-s, --surface <surface>', 'Which API surface(s) to bundle').choices(['admin', 'connect', 'both']).makeOptionMandatory())
1633
+ .addOption(new Option('-s, --surface <surface>', 'Which API surfaces to bundle').choices(['admin', 'connect', 'both']).makeOptionMandatory())
1634
1634
  .option('--out-admin <path>', 'Output path for admin API', 'admin/redpanda-admin-api.yaml')
1635
1635
  .option('--out-connect <path>', 'Output path for connect API', 'connect/redpanda-connect-api.yaml')
1636
1636
  .option('--admin-major <string>', 'Admin API major version', 'v2.0.0')
@@ -306,10 +306,10 @@ function generateReviewReport(results, outputPath) {
306
306
  // Next steps
307
307
  report += `== Next Steps\n\n`;
308
308
  if (errorIssues.length > 0) {
309
- report += `. *Fix errors first* - Address the ${errorIssues.length} error(s) above\n`;
309
+ report += `. *Fix errors first* - Address the ${errorIssues.length} errors above\n`;
310
310
  }
311
311
  if (warningIssues.length > 0) {
312
- report += `${errorIssues.length > 0 ? '. ' : '. '}*Review warnings* - Prioritize the ${warningIssues.length} warning(s)\n`;
312
+ report += `${errorIssues.length > 0 ? '. ' : '. '}*Review warnings* - Prioritize the ${warningIssues.length} warnings\n`;
313
313
  }
314
314
  const step = errorIssues.length > 0 && warningIssues.length > 0 ? 3 : errorIssues.length > 0 || warningIssues.length > 0 ? 2 : 1;
315
315
  report += `${step > 1 ? '. ' : '. '}*Regenerate documentation* - After making changes, regenerate the docs\n`;
@@ -250,7 +250,7 @@ function formatValidationResults(results, config) {
250
250
  lines.push('✓ Validation passed');
251
251
  } else {
252
252
  lines.push('✗ Validation failed');
253
- lines.push(` ${results.errors.length} error(s) must be fixed`);
253
+ lines.push(` ${results.errors.length} errors must be fixed`);
254
254
  }
255
255
 
256
256
  return lines.join('\n');
@@ -16,7 +16,7 @@ const { getAntoraStructure } = require('./antora');
16
16
  * @param {string} [args.tag] - Git tag for released content (for example, "v24.3.2" or "24.3.2")
17
17
  * @param {string} [args.branch] - Branch name for in-progress content (for example, "dev", "main")
18
18
  * @param {string} [args.repo] - Repository URL
19
- * @param {string} [args.surface] - Which API surface(s) to bundle: 'admin', 'connect', or 'both'
19
+ * @param {string} [args.surface] - Which API surfaces to bundle: 'admin', 'connect', or 'both'
20
20
  * @param {string} [args.out_admin] - Output path for admin API
21
21
  * @param {string} [args.out_connect] - Output path for connect API
22
22
  * @param {string} [args.admin_major] - Admin API major version
@@ -155,7 +155,7 @@ function generateBundleOpenApi(args) {
155
155
  surface,
156
156
  files_generated: filesGenerated,
157
157
  output: output.trim(),
158
- summary: `Bundled OpenAPI documentation for ${surface} API(s) at ${refType} ${version}`
158
+ summary: `Bundled OpenAPI documentation for ${surface} APIs at ${refType} ${version}`
159
159
  };
160
160
  } catch (err) {
161
161
  return {
@@ -73,7 +73,6 @@ module.exports.register = function ({ config }) {
73
73
  const status = attrs.status || extractAttribute(file, 'status')
74
74
  const driverSupport = attrs['driver-support'] || extractAttribute(file, 'driver-support')
75
75
  const cacheSupport = attrs['cache-support'] || extractAttribute(file, 'cache-support')
76
- // FIXED: Use correct attribute name (page-commercial-names, not commercial-names)
77
76
  const commercialNames = attrs['page-commercial-names'] || extractAttribute(file, 'page-commercial-names')
78
77
 
79
78
  const pubUrl = file.pub.url
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redpanda-data/docs-extensions-and-macros",
3
- "version": "4.13.2",
3
+ "version": "4.13.4",
4
4
  "description": "Antora extensions and macros developed for Redpanda documentation.",
5
5
  "keywords": [
6
6
  "antora",
@@ -551,7 +551,7 @@ function postProcessBundle(filePath, options, quiet = false) {
551
551
  }
552
552
 
553
553
  /**
554
- * Bundle OpenAPI fragments for the specified API surface(s) from a repository tag and write the resulting bundled YAML files to disk.
554
+ * Bundle OpenAPI fragments for the specified API surfaces from a repository tag and write the resulting bundled YAML files to disk.
555
555
  *
556
556
  * @param {Object} options - Configuration options.
557
557
  * @param {string} options.tag - Git tag to checkout (for example, 'v25.1.1').
@@ -665,13 +665,13 @@ Connectors can have human-friendly commercial names that appear in catalogs and
665
665
  ----
666
666
  = kafka_franz
667
667
  :type: input
668
- :commercial-names: Franz-go, Apache Kafka
668
+ :page-commercial-names: Franz-go, Apache Kafka
669
669
  :categories: ["Services"]
670
670
  ----
671
671
 
672
672
  The `generate-rp-connect-categories` extension reads these commercial names and uses the first one as the primary display name. If multiple names are provided (comma-separated), only the first is used for display.
673
673
 
674
- *Default behavior:* If no `:commercial-names:` attribute is found, the connector key name is used (e.g., `kafka_franz`).
674
+ *Default behavior:* If no `:page-commercial-names:` attribute is found, the connector key name is used (such as `kafka_franz`).
675
675
 
676
676
  *Adding commercial names to new connectors:*
677
677
 
@@ -681,12 +681,12 @@ When generating documentation for new connectors with `--draft-missing`, writers
681
681
  ----
682
682
  ### ✍️ Writer Action Required
683
683
 
684
- For each new connector, please add the `:commercial-names:` attribute to the frontmatter:
684
+ For each new connector, please add the `:page-commercial-names:` attribute to the frontmatter:
685
685
 
686
686
  ```asciidoc
687
687
  = Connector Name
688
688
  :type: input
689
- :commercial-names: Commercial Name, Alternative Name
689
+ :page-commercial-names: Commercial Name, Alternative Name
690
690
  ```
691
691
 
692
692
  _This helps improve discoverability and ensures proper categorization._
@@ -503,7 +503,7 @@ async function analyzeAllBinaries(ossVersion, cloudVersion = null, dataDir = nul
503
503
  // These are connectors that require cgo-enabled builds
504
504
  cgoOnly = findCgoOnlyConnectors(ossIndex, cgoIndex);
505
505
 
506
- console.log(`Done: Cgo analysis complete: ${cgoOnly.length} cgo-only connector(s) found`);
506
+ console.log(`Done: Cgo analysis complete: ${cgoOnly.length} cgo-only connectors found`);
507
507
  if (cgoOnly.length > 0) {
508
508
  console.log(' cgo-only connectors:');
509
509
  cgoOnly.forEach(c => console.log(` • ${c.type}/${c.name} (${c.status})`));
@@ -30,7 +30,7 @@ function generatePRSummary(diffData, binaryAnalysis = null, draftedConnectors =
30
30
 
31
31
  // High-level stats
32
32
  const stats = diffData.summary;
33
- const hasChanges = Object.values(stats).some(v => v > 0);
33
+ const hasChanges = Object.values(stats).some(v => v > 0) || (draftedConnectors && draftedConnectors.length > 0);
34
34
 
35
35
  if (!hasChanges) {
36
36
  lines.push('✅ **No changes detected** - Documentation is up to date');
@@ -91,16 +91,78 @@ function generatePRSummary(diffData, binaryAnalysis = null, draftedConnectors =
91
91
  if (stats.newComponents > 0) {
92
92
  lines.push('### ✍️ Writer Action Required');
93
93
  lines.push('');
94
- lines.push('For each new connector, please add the `:commercial-names:` attribute to the frontmatter:');
94
+ lines.push('For each new connector, add the `:page-commercial-names:` attribute to the frontmatter:');
95
95
  lines.push('');
96
96
  lines.push('```asciidoc');
97
97
  lines.push('= Connector Name');
98
98
  lines.push(':type: input');
99
- lines.push(':commercial-names: Commercial Name, Alternative Name');
99
+ lines.push(':page-commercial-names: Commercial Name, Alternative Name');
100
100
  lines.push('```');
101
101
  lines.push('');
102
102
  lines.push('_This helps improve discoverability and ensures proper categorization._');
103
103
  lines.push('');
104
+
105
+ // Check if any new connectors are cloud-supported
106
+ if (binaryAnalysis && binaryAnalysis.comparison) {
107
+ const newConnectorKeys = diffData.details.newComponents.map(c => ({
108
+ key: `${c.type}:${c.name}`,
109
+ type: c.type,
110
+ name: c.name
111
+ }));
112
+
113
+ const cloudSupported = newConnectorKeys.filter(item => {
114
+ // Check both inCloud (OSS+Cloud) and cloudOnly (Cloud-only)
115
+ const inCloud = binaryAnalysis.comparison.inCloud.some(c => `${c.type}:${c.name}` === item.key);
116
+ const cloudOnly = binaryAnalysis.comparison.cloudOnly &&
117
+ binaryAnalysis.comparison.cloudOnly.some(c => `${c.type}:${c.name}` === item.key);
118
+ return inCloud || cloudOnly;
119
+ });
120
+
121
+ if (cloudSupported.length > 0) {
122
+ lines.push('### ☁️ Cloud Docs Update Required');
123
+ lines.push('');
124
+ lines.push(`**${cloudSupported.length}** new connector${cloudSupported.length !== 1 ? 's are' : ' is'} available in Redpanda Cloud.`);
125
+ lines.push('');
126
+ lines.push('**Action:** Submit a separate PR to https://github.com/redpanda-data/cloud-docs to add the connector pages using include syntax:');
127
+ lines.push('');
128
+
129
+ // Check if any are cloud-only (need partial syntax)
130
+ const cloudOnly = cloudSupported.filter(item =>
131
+ binaryAnalysis.comparison.cloudOnly &&
132
+ binaryAnalysis.comparison.cloudOnly.some(c => `${c.type}:${c.name}` === item.key)
133
+ );
134
+ const regularCloud = cloudSupported.filter(item =>
135
+ !binaryAnalysis.comparison.cloudOnly ||
136
+ !binaryAnalysis.comparison.cloudOnly.some(c => `${c.type}:${c.name}` === item.key)
137
+ );
138
+
139
+ if (regularCloud.length > 0) {
140
+ lines.push('**For connectors in pages:**');
141
+ lines.push('```asciidoc');
142
+ lines.push('= Connector Name');
143
+ lines.push('');
144
+ lines.push('include::redpanda-connect:components:page$type/connector-name.adoc[tag=single-source]');
145
+ lines.push('```');
146
+ lines.push('');
147
+ }
148
+
149
+ if (cloudOnly.length > 0) {
150
+ lines.push('**For cloud-only connectors (in partials):**');
151
+ lines.push('```asciidoc');
152
+ lines.push('= Connector Name');
153
+ lines.push('');
154
+ lines.push('include::redpanda-connect:components:partial$components/cloud-only/type/connector-name.adoc[tag=single-source]');
155
+ lines.push('```');
156
+ lines.push('');
157
+ }
158
+
159
+ // Add instruction to update cloud whats-new
160
+ lines.push('**Also update the cloud whats-new file:**');
161
+ lines.push('');
162
+ lines.push('Add entries for the new cloud-supported connectors to the Redpanda Cloud whats-new file in the cloud-docs repository.');
163
+ lines.push('');
164
+ }
165
+ }
104
166
  }
105
167
 
106
168
  // Breaking Changes Section
@@ -335,8 +397,9 @@ function generatePRSummary(diffData, binaryAnalysis = null, draftedConnectors =
335
397
  lines.push('');
336
398
  cloudSupportedNew.forEach(c => {
337
399
  lines.push(`- **${c.name}** (${c.type}, ${c.status})`);
338
- if (c.description) {
339
- const shortDesc = truncateToSentence(c.description, 2);
400
+ const desc = c.summary || c.description;
401
+ if (desc) {
402
+ const shortDesc = truncateToSentence(desc, 2);
340
403
  lines.push(` - ${shortDesc}`);
341
404
  }
342
405
  });
@@ -348,8 +411,9 @@ function generatePRSummary(diffData, binaryAnalysis = null, draftedConnectors =
348
411
  lines.push('');
349
412
  selfHostedOnlyNew.forEach(c => {
350
413
  lines.push(`- **${c.name}** (${c.type}, ${c.status})`);
351
- if (c.description) {
352
- const shortDesc = truncateToSentence(c.description, 2);
414
+ const desc = c.summary || c.description;
415
+ if (desc) {
416
+ const shortDesc = truncateToSentence(desc, 2);
353
417
  lines.push(` - ${shortDesc}`);
354
418
  }
355
419
  });
@@ -359,8 +423,9 @@ function generatePRSummary(diffData, binaryAnalysis = null, draftedConnectors =
359
423
  // No cloud support info, just list all
360
424
  diffData.details.newComponents.forEach(c => {
361
425
  lines.push(`- **${c.name}** (${c.type}, ${c.status})`);
362
- if (c.description) {
363
- const shortDesc = truncateToSentence(c.description, 2);
426
+ const desc = c.summary || c.description;
427
+ if (desc) {
428
+ const shortDesc = truncateToSentence(desc, 2);
364
429
  lines.push(` - ${shortDesc}`);
365
430
  }
366
431
  });
@@ -250,6 +250,7 @@ function generateConnectorDiffJson(oldIndex, newIndex, opts = {}) {
250
250
  result.binaryAnalysis.details = {
251
251
  cloudSupported: ba.comparison?.inCloud?.map(c => ({ type: c.type, name: c.name, status: c.status })) || [],
252
252
  selfHostedOnly: ba.comparison?.notInCloud?.map(c => ({ type: c.type, name: c.name, status: c.status })) || [],
253
+ cloudOnly: ba.comparison?.cloudOnly?.map(c => ({ type: c.type, name: c.name, status: c.status })) || [],
253
254
  cgoOnly: ba.cgoOnly?.map(c => ({ type: c.type, name: c.name, status: c.status })) || []
254
255
  };
255
256
  }
@@ -148,13 +148,20 @@ function updateWhatsNew ({ dataDir, oldVersion, newVersion, binaryAnalysis }) {
148
148
  const regularComponents = []
149
149
 
150
150
  if (diff.details.newComponents && diff.details.newComponents.length) {
151
- for (const comp of diff.details.newComponents) {
151
+ // Filter out cloud-only connectors - they don't go in whats-new.adoc
152
+ const nonCloudOnlyComponents = diff.details.newComponents.filter(comp => {
153
+ const isCloudOnly = diff.binaryAnalysis?.details?.cloudOnly?.some(cloudComp => {
154
+ return cloudComp.name === comp.name && cloudComp.type === comp.type
155
+ })
156
+ return !isCloudOnly
157
+ })
158
+
159
+ for (const comp of nonCloudOnlyComponents) {
152
160
  if (comp.type === 'bloblang-functions' || comp.type === 'bloblang-methods') {
153
161
  bloblangComponents.push(comp)
154
162
  } else {
155
- const isCgoOnly = binaryAnalysis?.cgoOnly?.some(cgo => {
156
- const typeSingular = cgo.type.replace(/s$/, '')
157
- return cgo.name === comp.name && typeSingular === comp.type
163
+ const isCgoOnly = diff.binaryAnalysis?.details?.cgoOnly?.some(cgo => {
164
+ return cgo.name === comp.name && cgo.type === comp.type
158
165
  })
159
166
 
160
167
  regularComponents.push({
@@ -182,8 +189,9 @@ function updateWhatsNew ({ dataDir, oldVersion, newVersion, binaryAnalysis }) {
182
189
  for (const comp of comps) {
183
190
  section += `** xref:guides:bloblang/functions.adoc#${comp.name}[\`${comp.name}\`]`
184
191
  if (comp.status && comp.status !== 'stable') section += ` (${comp.status})`
185
- if (comp.description) {
186
- section += `: ${capToTwoSentences(comp.description)}`
192
+ const desc = comp.summary || comp.description
193
+ if (desc) {
194
+ section += `: ${capToTwoSentences(desc)}`
187
195
  } else {
188
196
  section += `\n+\n// TODO: Add description for ${comp.name} function`
189
197
  }
@@ -194,8 +202,9 @@ function updateWhatsNew ({ dataDir, oldVersion, newVersion, binaryAnalysis }) {
194
202
  for (const comp of comps) {
195
203
  section += `** xref:guides:bloblang/methods.adoc#${comp.name}[\`${comp.name}\`]`
196
204
  if (comp.status && comp.status !== 'stable') section += ` (${comp.status})`
197
- if (comp.description) {
198
- section += `: ${capToTwoSentences(comp.description)}`
205
+ const desc = comp.summary || comp.description
206
+ if (desc) {
207
+ section += `: ${capToTwoSentences(desc)}`
199
208
  } else {
200
209
  section += `\n+\n// TODO: Add description for ${comp.name} method`
201
210
  }
@@ -260,7 +269,8 @@ function updateWhatsNew ({ dataDir, oldVersion, newVersion, binaryAnalysis }) {
260
269
 
261
270
  for (const comp of diff.details.deprecatedComponents) {
262
271
  const typeLabel = comp.type.charAt(0).toUpperCase() + comp.type.slice(1)
263
- const desc = comp.description ? capToTwoSentences(comp.description) : '-'
272
+ const descText = comp.summary || comp.description
273
+ const desc = descText ? capToTwoSentences(descText) : '-'
264
274
 
265
275
  if (comp.type === 'bloblang-functions') {
266
276
  section += `|xref:guides:bloblang/functions.adoc#${comp.name}[${comp.name}]\n`
@@ -654,6 +664,27 @@ async function handleRpcnConnectorDocs (options) {
654
664
 
655
665
  let newIndex = JSON.parse(fs.readFileSync(dataFile, 'utf8'))
656
666
 
667
+ // Save a clean copy of OSS data for binary analysis (before augmentation)
668
+ // This ensures the binary analyzer compares actual binaries, not augmented data
669
+ const cleanOssDataPath = path.join(dataDir, `._connect-${newVersion}-clean.json`)
670
+
671
+ // Strip augmentation fields to create clean data for comparison
672
+ const cleanData = JSON.parse(JSON.stringify(newIndex))
673
+ const connectorTypes = ['inputs', 'outputs', 'processors', 'caches', 'rate_limits', 'buffers', 'metrics', 'scanners', 'tracers']
674
+
675
+ for (const type of connectorTypes) {
676
+ if (Array.isArray(cleanData[type])) {
677
+ cleanData[type] = cleanData[type].filter(c => !c.cloudOnly) // Remove cloud-only connectors added by augmentation
678
+ cleanData[type].forEach(c => {
679
+ delete c.cloudSupported
680
+ delete c.requiresCgo
681
+ delete c.cloudOnly
682
+ })
683
+ }
684
+ }
685
+
686
+ fs.writeFileSync(cleanOssDataPath, JSON.stringify(cleanData, null, 2), 'utf8')
687
+
657
688
  const versionsMatch = oldVersion && newVersion && oldVersion === newVersion
658
689
  if (versionsMatch) {
659
690
  console.log(`\n✓ Already at version ${newVersion}`)
@@ -740,6 +771,19 @@ async function handleRpcnConnectorDocs (options) {
740
771
  console.log('\nAnalyzing connector binaries...')
741
772
  const { analyzeAllBinaries } = require('./connector-binary-analyzer.js')
742
773
 
774
+ // Always use clean OSS data for comparison
775
+ // Temporarily rename the file so the analyzer finds it
776
+ const expectedPath = path.join(dataDir, `connect-${newVersion}.json`)
777
+ let tempRenamed = false
778
+
779
+ if (fs.existsSync(cleanOssDataPath)) {
780
+ if (fs.existsSync(expectedPath)) {
781
+ fs.renameSync(expectedPath, path.join(dataDir, `._connect-${newVersion}-augmented.json.tmp`))
782
+ tempRenamed = true
783
+ }
784
+ fs.copyFileSync(cleanOssDataPath, expectedPath)
785
+ }
786
+
743
787
  const analysisOptions = {
744
788
  skipCloud: false,
745
789
  skipCgo: false,
@@ -753,6 +797,13 @@ async function handleRpcnConnectorDocs (options) {
753
797
  analysisOptions
754
798
  )
755
799
 
800
+ // Restore the augmented file
801
+ if (tempRenamed) {
802
+ const expectedPath = path.join(dataDir, `connect-${newVersion}.json`)
803
+ fs.unlinkSync(expectedPath)
804
+ fs.renameSync(path.join(dataDir, `._connect-${newVersion}-augmented.json.tmp`), expectedPath)
805
+ }
806
+
756
807
  console.log('Done: Binary analysis complete:')
757
808
  console.log(` • OSS version: ${binaryAnalysis.ossVersion}`)
758
809
 
@@ -848,10 +899,10 @@ async function handleRpcnConnectorDocs (options) {
848
899
  fs.writeFileSync(dataFile, JSON.stringify(connectorData, null, 2), 'utf8')
849
900
  console.log(`Done: Augmented ${augmentedCount} connectors with cloud/cgo fields`)
850
901
  if (addedCgoCount > 0) {
851
- console.log(` • Added ${addedCgoCount} cgo-only connector(s) to data file`)
902
+ console.log(` • Added ${addedCgoCount} cgo-only connectors to data file`)
852
903
  }
853
904
  if (addedCloudOnlyCount > 0) {
854
- console.log(` • Added ${addedCloudOnlyCount} cloud-only connector(s) to data file`)
905
+ console.log(` • Added ${addedCloudOnlyCount} cloud-only connectors to data file`)
855
906
  }
856
907
 
857
908
  // Keep only 2 most recent versions
@@ -865,6 +916,9 @@ async function handleRpcnConnectorDocs (options) {
865
916
  fs.unlinkSync(oldestPath)
866
917
  console.log(`🧹 Deleted old version from docs-data: ${oldestFile}`)
867
918
  }
919
+
920
+ // Reload newIndex after augmentation so diff generation uses augmented data
921
+ newIndex = JSON.parse(fs.readFileSync(dataFile, 'utf8'))
868
922
  } catch (err) {
869
923
  console.error(`Warning: Failed to augment data file: ${err.message}`)
870
924
  }
@@ -890,8 +944,42 @@ async function handleRpcnConnectorDocs (options) {
890
944
  }
891
945
  )
892
946
 
947
+ // Filter out components that already have documentation
948
+ const docRoots = {
949
+ pages: path.resolve(process.cwd(), 'modules/components/pages'),
950
+ partials: path.resolve(process.cwd(), 'modules/components/partials/components'),
951
+ cloudOnly: path.resolve(process.cwd(), 'modules/components/partials/components/cloud-only')
952
+ }
953
+
954
+ if (diffJson.details && diffJson.details.newComponents) {
955
+ const originalCount = diffJson.details.newComponents.length
956
+ diffJson.details.newComponents = diffJson.details.newComponents.filter(comp => {
957
+ const typePlural = comp.type.endsWith('s') ? comp.type : `${comp.type}s`
958
+ const relPath = path.join(typePlural, `${comp.name}.adoc`)
959
+ const docsExist = Object.values(docRoots).some(root =>
960
+ fs.existsSync(path.join(root, relPath))
961
+ )
962
+ return !docsExist
963
+ })
964
+ const filteredCount = originalCount - diffJson.details.newComponents.length
965
+ if (filteredCount > 0) {
966
+ console.log(` ℹ️ Filtered out ${filteredCount} components that already have documentation`)
967
+ }
968
+ // Update summary count
969
+ if (diffJson.summary) {
970
+ diffJson.summary.newComponents = diffJson.details.newComponents.length
971
+ }
972
+ }
973
+
893
974
  // Add new cgo-only components
894
975
  if (binaryAnalysis && binaryAnalysis.cgoOnly && binaryAnalysis.cgoOnly.length > 0) {
976
+ // Define roots for checking if docs already exist
977
+ const docRoots = {
978
+ pages: path.resolve(process.cwd(), 'modules/components/pages'),
979
+ partials: path.resolve(process.cwd(), 'modules/components/partials/components'),
980
+ cloudOnly: path.resolve(process.cwd(), 'modules/components/partials/components/cloud-only')
981
+ }
982
+
895
983
  let newCgoComponents
896
984
 
897
985
  if (oldBinaryAnalysis) {
@@ -899,20 +987,36 @@ async function handleRpcnConnectorDocs (options) {
899
987
  newCgoComponents = binaryAnalysis.cgoOnly.filter(cgoComp => {
900
988
  const wasInOldOss = oldIndex[cgoComp.type]?.some(c => c.name === cgoComp.name)
901
989
  const wasInOldCgo = oldCgoSet.has(`${cgoComp.type}:${cgoComp.name}`)
902
- return !wasInOldOss && !wasInOldCgo
990
+
991
+ // Check if docs already exist
992
+ const typePlural = cgoComp.type.endsWith('s') ? cgoComp.type : `${cgoComp.type}s`
993
+ const relPath = path.join(typePlural, `${cgoComp.name}.adoc`)
994
+ const docsExist = Object.values(docRoots).some(root =>
995
+ fs.existsSync(path.join(root, relPath))
996
+ )
997
+
998
+ return !wasInOldOss && !wasInOldCgo && !docsExist
903
999
  })
904
1000
  } else {
905
1001
  newCgoComponents = binaryAnalysis.cgoOnly.filter(cgoComp => {
906
1002
  const wasInOldOss = oldIndex[cgoComp.type]?.some(c => c.name === cgoComp.name)
907
- return !wasInOldOss
1003
+
1004
+ // Check if docs already exist
1005
+ const typePlural = cgoComp.type.endsWith('s') ? cgoComp.type : `${cgoComp.type}s`
1006
+ const relPath = path.join(typePlural, `${cgoComp.name}.adoc`)
1007
+ const docsExist = Object.values(docRoots).some(root =>
1008
+ fs.existsSync(path.join(root, relPath))
1009
+ )
1010
+
1011
+ return !wasInOldOss && !docsExist
908
1012
  })
909
1013
  if (newCgoComponents.length > 0) {
910
- console.log(` ℹ️ No old binary analysis found - treating ${newCgoComponents.length} cgo component(s) not in old OSS data as new`)
1014
+ console.log(` ℹ️ No old binary analysis found - treating ${newCgoComponents.length} cgo components not in old OSS data as new`)
911
1015
  }
912
1016
  }
913
1017
 
914
1018
  if (newCgoComponents && newCgoComponents.length > 0) {
915
- console.log(` • Found ${newCgoComponents.length} new cgo-only component(s)`)
1019
+ console.log(` • Found ${newCgoComponents.length} new cgo-only components`)
916
1020
  newCgoComponents.forEach(cgoComp => {
917
1021
  const typeSingular = cgoComp.type.replace(/s$/, '')
918
1022
  diffJson.details.newComponents.push({
@@ -940,7 +1044,7 @@ async function handleRpcnConnectorDocs (options) {
940
1044
  .filter(f => f.startsWith('connect-diff-') && f.endsWith('.json') && f !== path.basename(diffPath))
941
1045
 
942
1046
  if (oldDiffFiles.length > 0) {
943
- console.log(`🧹 Cleaning up ${oldDiffFiles.length} old diff file(s)...`)
1047
+ console.log(`🧹 Cleaning up ${oldDiffFiles.length} old diff files...`)
944
1048
  oldDiffFiles.forEach(f => {
945
1049
  const oldDiffPath = path.join(dataDir, f)
946
1050
  fs.unlinkSync(oldDiffPath)
@@ -1011,7 +1115,8 @@ async function handleRpcnConnectorDocs (options) {
1011
1115
 
1012
1116
  const roots = {
1013
1117
  pages: path.resolve(process.cwd(), 'modules/components/pages'),
1014
- partials: path.resolve(process.cwd(), 'modules/components/partials/components')
1118
+ partials: path.resolve(process.cwd(), 'modules/components/partials/components'),
1119
+ cloudOnly: path.resolve(process.cwd(), 'modules/components/partials/components/cloud-only')
1015
1120
  }
1016
1121
 
1017
1122
  const allMissing = validConnectors.filter(({ name, type }) => {
@@ -190,7 +190,18 @@ function updateNavFromDrafts(draftFiles, navPath = null) {
190
190
 
191
191
  console.log(`📝 Updating navigation: ${navPath}`);
192
192
 
193
- const connectors = draftFiles.map(draft => ({
193
+ // Filter out cloud-only connectors (they go in partials, not nav)
194
+ const nonCloudOnlyFiles = draftFiles.filter(draft => !draft.cloudOnly);
195
+ const cloudOnlyFiles = draftFiles.filter(draft => draft.cloudOnly);
196
+
197
+ if (cloudOnlyFiles.length > 0) {
198
+ console.log(` ℹ️ Skipping ${cloudOnlyFiles.length} cloud-only connectors (partials don't need nav entries):`);
199
+ cloudOnlyFiles.forEach(draft => {
200
+ console.log(` • ${draft.type}/${draft.name}`);
201
+ });
202
+ }
203
+
204
+ const connectors = nonCloudOnlyFiles.map(draft => ({
194
205
  type: draft.type,
195
206
  name: draft.name
196
207
  }));