@redpanda-data/docs-extensions-and-macros 4.15.6 → 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.
@@ -8,6 +8,9 @@ const { getAntoraValue, setAntoraValue } = require('../../cli-utils/antora-utils
8
8
  const fetchFromGithub = require('../fetch-from-github.js')
9
9
  const { generateRpcnConnectorDocs } = require('./generate-rpcn-connector-docs.js')
10
10
  const { getRpkConnectVersion, printDeltaReport } = require('./report-delta')
11
+ const { discoverIntermediateReleases } = require('./github-release-utils')
12
+ const parseCSVConnectors = require('./parse-csv-connectors.js')
13
+ const semver = require('semver')
11
14
 
12
15
  /**
13
16
  * Cap description to two sentences
@@ -484,6 +487,100 @@ function logCollapsed (label, filesArray, maxToShow = 10) {
484
487
  console.log('')
485
488
  }
486
489
 
490
+ /**
491
+ * Strip augmentation fields from connector data to ensure clean comparisons
492
+ * Removes cloudSupported, requiresCgo, cloudOnly fields and filters out cloud-only connectors
493
+ * @param {object} data - Connector index data
494
+ * @returns {object} Clean connector data without augmentation
495
+ */
496
+ function stripAugmentationFields(data) {
497
+ const cleanData = JSON.parse(JSON.stringify(data));
498
+ const connectorTypes = ['inputs', 'outputs', 'processors', 'caches', 'rate_limits',
499
+ 'buffers', 'metrics', 'scanners', 'tracers', 'config', 'bloblang-methods'];
500
+
501
+ for (const type of connectorTypes) {
502
+ if (Array.isArray(cleanData[type])) {
503
+ // Remove connectors that were added by augmentation (cloudOnly or requiresCgo without OSS data)
504
+ cleanData[type] = cleanData[type].filter(c => {
505
+ // Keep if it's not marked as cloudOnly or requiresCgo
506
+ // OR if it has a config/fields (meaning it came from OSS, not just binary analysis)
507
+ return (!(c.cloudOnly || c.requiresCgo) || c.config || c.fields);
508
+ });
509
+
510
+ // Remove augmentation fields
511
+ cleanData[type].forEach(c => {
512
+ delete c.cloudSupported;
513
+ delete c.requiresCgo;
514
+ delete c.cloudOnly;
515
+ });
516
+ }
517
+ }
518
+
519
+ return cleanData;
520
+ }
521
+
522
+ /**
523
+ * Load or fetch connector data for a specific version
524
+ * @param {string} version - Version to load (e.g., "4.50.0")
525
+ * @param {string} dataDir - Directory where JSON files are stored
526
+ * @param {Object} options - Options for fetching if needed
527
+ * @returns {Promise<Object>} Parsed connector data
528
+ */
529
+ async function loadConnectorDataForVersion(version, dataDir, options = {}) {
530
+ const dataFile = path.join(dataDir, `connect-${version}.json`);
531
+
532
+ // If file exists, load it
533
+ if (fs.existsSync(dataFile)) {
534
+ console.log(`āœ“ Using existing data file: connect-${version}.json`);
535
+ const data = JSON.parse(fs.readFileSync(dataFile, 'utf8'));
536
+
537
+ // Strip augmentation fields to ensure clean comparisons
538
+ // Augmentation adds CGO/cloud-only components that shouldn't affect version diffs
539
+ const cleanData = stripAugmentationFields(data);
540
+ return cleanData;
541
+ }
542
+
543
+ // If not, fetch it
544
+ console.log(`šŸ“„ Data file not found for ${version}, attempting to fetch...`);
545
+
546
+ try {
547
+ // Try installing that specific version and fetching data
548
+ console.log(` Installing Redpanda Connect version ${version}...`);
549
+ const installResult = spawnSync('rpk', ['connect', 'install', '--connect-version', version, '--force'], {
550
+ stdio: 'pipe'
551
+ });
552
+
553
+ if (installResult.status !== 0) {
554
+ throw new Error(`Failed to install Connect version ${version}`);
555
+ }
556
+
557
+ // Fetch connector list
558
+ const tmpFile = path.join(dataDir, `connect-${version}.tmp.json`);
559
+ const fd = fs.openSync(tmpFile, 'w');
560
+ const listResult = spawnSync('rpk', ['connect', 'list', '--format', 'json-full'], {
561
+ stdio: ['ignore', fd, 'pipe']
562
+ });
563
+ fs.closeSync(fd);
564
+
565
+ if (listResult.status !== 0) {
566
+ throw new Error(`Failed to fetch connector list for version ${version}`);
567
+ }
568
+
569
+ // Parse and validate
570
+ const rawJson = fs.readFileSync(tmpFile, 'utf8');
571
+ const parsed = JSON.parse(rawJson);
572
+
573
+ // Move to final location
574
+ fs.renameSync(tmpFile, dataFile);
575
+
576
+ console.log(`āœ“ Successfully fetched data for version ${version}`);
577
+ return parsed;
578
+ } catch (error) {
579
+ console.error(`āŒ Failed to fetch data for version ${version}: ${error.message}`);
580
+ throw new Error(`Cannot process version ${version} - data unavailable`);
581
+ }
582
+ }
583
+
487
584
  /**
488
585
  * Main handler for rpcn-connector-docs command
489
586
  * @param {Object} options - Command options
@@ -500,10 +597,16 @@ async function handleRpcnConnectorDocs (options) {
500
597
  let draftsWritten = 0
501
598
  let draftFiles = []
502
599
  let needsAugmentation = false
600
+ let csvMetadata = []
503
601
 
504
602
  if (options.fetchConnectors) {
505
603
  try {
506
604
  if (options.connectVersion) {
605
+ if (!semver.valid(options.connectVersion)) {
606
+ console.error(`Error: Invalid --connect-version format: ${options.connectVersion}`)
607
+ console.error('Expected format: X.Y.Z (e.g., 4.50.0)')
608
+ process.exit(1)
609
+ }
507
610
  console.log(`Installing Redpanda Connect version ${options.connectVersion}...`)
508
611
  const installResult = spawnSync('rpk', ['connect', 'install', '--connect-version', options.connectVersion, '--force'], {
509
612
  stdio: 'inherit'
@@ -555,6 +658,16 @@ async function handleRpcnConnectorDocs (options) {
555
658
  console.warn(`Warning: Failed to fetch info.csv: ${csvErr.message}`)
556
659
  }
557
660
 
661
+ // Parse CSV metadata
662
+ try {
663
+ const csvFile = path.join(dataDir, `connect-info-${newVersion}.csv`)
664
+ if (fs.existsSync(csvFile)) {
665
+ csvMetadata = await parseCSVConnectors(csvFile, console)
666
+ }
667
+ } catch (csvParseErr) {
668
+ console.warn(`Warning: Failed to parse info.csv: ${csvParseErr.message}`)
669
+ }
670
+
558
671
  // Fetch Bloblang examples
559
672
  try {
560
673
  console.log(`Fetching Bloblang playground examples for Connect v${newVersion}...`)
@@ -601,16 +714,206 @@ async function handleRpcnConnectorDocs (options) {
601
714
  process.exit(1)
602
715
  }
603
716
  } else {
604
- const candidates = fs.readdirSync(dataDir).filter(f => /^connect-\d+\.\d+\.\d+\.json$/.test(f))
717
+ const candidates = fs.readdirSync(dataDir)
718
+ .filter(f => /^connect-(\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?)\.json$/.test(f))
719
+ .map(f => {
720
+ const match = f.match(/^connect-(\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?)\.json$/)
721
+ return match ? match[1] : null
722
+ })
723
+ .filter(v => v && semver.valid(v))
724
+
605
725
  if (candidates.length === 0) {
606
- console.error('Error: No connect-<version>.json found. Use --fetch-connectors.')
726
+ console.error('Error: No valid connect-<version>.json found. Use --fetch-connectors.')
727
+ process.exit(1)
728
+ }
729
+
730
+ const sortedVersions = semver.rsort(candidates)
731
+ newVersion = sortedVersions[0]
732
+ dataFile = path.join(dataDir, `connect-${newVersion}.json`)
733
+ }
734
+
735
+ // ========================================================================
736
+ // Multi-Release Processing: Discover and process intermediate releases
737
+ // ========================================================================
738
+
739
+ const processIntermediate = !options.skipIntermediate && !options.oldData
740
+ let versionsToProcess = []
741
+ let intermediateProcessingResults = []
742
+
743
+ if (processIntermediate) {
744
+ // Determine starting version
745
+ let startVersion = options.fromVersion
746
+
747
+ if (startVersion && !semver.valid(startVersion)) {
748
+ console.error(`Error: Invalid --from-version format: ${startVersion}`)
749
+ console.error('Expected format: X.Y.Z (e.g., 4.50.0)')
607
750
  process.exit(1)
608
751
  }
609
- candidates.sort()
610
- dataFile = path.join(dataDir, candidates[candidates.length - 1])
611
- newVersion = candidates[candidates.length - 1].match(/connect-(\d+\.\d+\.\d+)\.json/)[1]
752
+
753
+ if (!startVersion) {
754
+ // Try antora.yml first
755
+ startVersion = getAntoraValue('asciidoc.attributes.latest-connect-version')
756
+
757
+ // Fallback: check existing data files
758
+ if (!startVersion) {
759
+ const existingVersions = fs.readdirSync(dataDir)
760
+ .filter(f => /^connect-(\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?)\.json$/.test(f))
761
+ .filter(f => f !== path.basename(dataFile))
762
+ .map(f => {
763
+ const match = f.match(/^connect-(\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?)\.json$/)
764
+ return match ? match[1] : null
765
+ })
766
+ .filter(v => v && semver.valid(v))
767
+
768
+ if (existingVersions.length > 0) {
769
+ const sortedVersions = semver.rsort(existingVersions)
770
+ startVersion = sortedVersions[0]
771
+ }
772
+ }
773
+ }
774
+
775
+ if (startVersion && startVersion !== newVersion) {
776
+ console.log(`\n${'='.repeat(80)}`)
777
+ console.log(`šŸ” Checking for intermediate releases between ${startVersion} and ${newVersion}...`)
778
+ console.log('='.repeat(80))
779
+
780
+ try {
781
+ const intermediateReleases = await discoverIntermediateReleases(
782
+ startVersion,
783
+ newVersion,
784
+ { includePrerelease: false, useCache: true }
785
+ )
786
+
787
+ versionsToProcess = intermediateReleases.map(r => r.version)
788
+
789
+ // Process all version pairs EXCEPT the last one (which will be handled by the main flow)
790
+ if (versionsToProcess.length > 2) {
791
+ console.log(`\nšŸ“¦ Processing ${versionsToProcess.length - 2} intermediate release(s)...\n`)
792
+
793
+ for (let i = 0; i < versionsToProcess.length - 2; i++) {
794
+ const fromVer = versionsToProcess[i]
795
+ const toVer = versionsToProcess[i + 1]
796
+
797
+ console.log(`\n${'─'.repeat(80)}`)
798
+ console.log(`šŸ“‹ Processing intermediate release: ${fromVer} → ${toVer}`)
799
+ console.log('─'.repeat(80) + '\n')
800
+
801
+ try {
802
+ // Load data for both versions
803
+ console.log(`Loading connector data for ${fromVer}...`)
804
+ const oldData = await loadConnectorDataForVersion(fromVer, dataDir)
805
+
806
+ console.log(`Loading connector data for ${toVer}...`)
807
+ const newData = await loadConnectorDataForVersion(toVer, dataDir)
808
+
809
+ // Determine the appropriate cloud version for this release date
810
+ const releaseInfo = intermediateReleases.find(r => r.version === toVer)
811
+ let cloudVersionForRelease = options.cloudVersion || null
812
+
813
+ if (!options.cloudVersion && releaseInfo && releaseInfo.date) {
814
+ const { findCloudVersionForDate } = require('./github-release-utils')
815
+ cloudVersionForRelease = await findCloudVersionForDate(releaseInfo.date, { useCache: true })
816
+ if (cloudVersionForRelease) {
817
+ console.log(` Using cloud version ${cloudVersionForRelease} (current at ${new Date(releaseInfo.date).toLocaleDateString()})`)
818
+ } else {
819
+ console.log(` No cloud version found for release date, using OSS version ${toVer}`)
820
+ cloudVersionForRelease = toVer
821
+ }
822
+ }
823
+
824
+ // Run binary analysis for the new version
825
+ console.log(`\nAnalyzing binaries for version ${toVer}...`)
826
+ const { analyzeAllBinaries } = require('./connector-binary-analyzer.js')
827
+
828
+ const analysisOptions = {
829
+ skipCloud: false,
830
+ skipCgo: false,
831
+ cgoVersion: options.cgoVersion || null
832
+ }
833
+
834
+ const intermediateAnalysis = await analyzeAllBinaries(
835
+ toVer,
836
+ cloudVersionForRelease,
837
+ dataDir,
838
+ analysisOptions
839
+ )
840
+
841
+ console.log('āœ“ Binary analysis complete:')
842
+ console.log(` • OSS version: ${intermediateAnalysis.ossVersion}`)
843
+ if (intermediateAnalysis.cloudVersion) {
844
+ console.log(` • Cloud version: ${intermediateAnalysis.cloudVersion}`)
845
+ }
846
+ if (intermediateAnalysis.comparison) {
847
+ console.log(` • Connectors in cloud: ${intermediateAnalysis.comparison.inCloud.length}`)
848
+ console.log(` • Self-hosted only: ${intermediateAnalysis.comparison.notInCloud.length}`)
849
+ if (intermediateAnalysis.comparison.cloudOnly) {
850
+ console.log(` • Cloud-only: ${intermediateAnalysis.comparison.cloudOnly.length}`)
851
+ }
852
+ }
853
+
854
+ // Generate diff
855
+ console.log(`\nGenerating diff: ${fromVer} → ${toVer}...`)
856
+ const { generateConnectorDiffJson } = require('./report-delta.js')
857
+
858
+ const diffData = generateConnectorDiffJson(
859
+ oldData,
860
+ newData,
861
+ {
862
+ oldVersion: fromVer,
863
+ newVersion: toVer,
864
+ timestamp,
865
+ binaryAnalysis: intermediateAnalysis
866
+ }
867
+ )
868
+
869
+ // Save diff
870
+ const diffPath = path.join(dataDir, `connect-diff-${fromVer}_to_${toVer}.json`)
871
+ fs.writeFileSync(diffPath, JSON.stringify(diffData, null, 2), 'utf8')
872
+ console.log(`āœ“ Diff saved: ${path.basename(diffPath)}`)
873
+
874
+ // Update what's-new if requested
875
+ if (options.updateWhatsNew) {
876
+ console.log(`Updating what's-new.adoc for ${toVer}...`)
877
+ updateWhatsNew({ dataDir, oldVersion: fromVer, newVersion: toVer, binaryAnalysis: intermediateAnalysis })
878
+ }
879
+
880
+ intermediateProcessingResults.push({
881
+ fromVersion: fromVer,
882
+ toVersion: toVer,
883
+ diffPath,
884
+ success: true
885
+ })
886
+
887
+ console.log(`āœ… Completed processing: ${fromVer} → ${toVer}\n`)
888
+ } catch (err) {
889
+ console.error(`āŒ Error processing ${fromVer} → ${toVer}: ${err.message}`)
890
+ console.error(' Continuing with next version...\n')
891
+
892
+ intermediateProcessingResults.push({
893
+ fromVersion: fromVer,
894
+ toVersion: toVer,
895
+ error: err.message,
896
+ success: false
897
+ })
898
+ }
899
+ }
900
+
901
+ console.log(`\n${'='.repeat(80)}`)
902
+ console.log(`āœ“ Intermediate release processing complete`)
903
+ console.log(` Processed: ${intermediateProcessingResults.filter(r => r.success).length}/${intermediateProcessingResults.length} version pairs`)
904
+ console.log('='.repeat(80) + '\n')
905
+ }
906
+ } catch (err) {
907
+ console.warn(`\nāš ļø Warning: Failed to discover intermediate releases: ${err.message}`)
908
+ console.warn(' Falling back to single version comparison...\n')
909
+ }
910
+ }
612
911
  }
613
912
 
913
+ // ========================================================================
914
+ // Main Processing: Handle the latest version (final iteration)
915
+ // ========================================================================
916
+
614
917
  console.log('Generating connector partials...')
615
918
  let partialsWritten, partialFiles
616
919
 
@@ -624,7 +927,8 @@ async function handleRpcnConnectorDocs (options) {
624
927
  templateExamples: options.templateExamples,
625
928
  templateBloblang: options.templateBloblang,
626
929
  writeFullDrafts: false,
627
- includeBloblang: !!options.includeBloblang
930
+ includeBloblang: !!options.includeBloblang,
931
+ csvMetadata
628
932
  })
629
933
  partialsWritten = result.partialsWritten
630
934
  partialFiles = result.partialFiles
@@ -636,52 +940,49 @@ async function handleRpcnConnectorDocs (options) {
636
940
  let oldIndex = {}
637
941
  let oldVersion = null
638
942
  if (options.oldData && fs.existsSync(options.oldData)) {
639
- oldIndex = JSON.parse(fs.readFileSync(options.oldData, 'utf8'))
943
+ // Strip augmentation fields to ensure clean comparisons
944
+ oldIndex = stripAugmentationFields(JSON.parse(fs.readFileSync(options.oldData, 'utf8')))
640
945
  const m = options.oldData.match(/connect-([\d.]+)\.json$/)
641
946
  if (m) oldVersion = m[1]
642
947
  } else {
643
- const existingDataFiles = fs.readdirSync(dataDir)
644
- .filter(f => /^connect-\d+\.\d+\.\d+\.json$/.test(f))
948
+ const existingVersions = fs.readdirSync(dataDir)
949
+ .filter(f => /^connect-(\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?)\.json$/.test(f))
645
950
  .filter(f => f !== path.basename(dataFile))
646
- .sort()
951
+ .map(f => {
952
+ const match = f.match(/^connect-(\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?)\.json$/)
953
+ return match ? match[1] : null
954
+ })
955
+ .filter(v => v && semver.valid(v))
647
956
 
648
- if (existingDataFiles.length > 0) {
649
- const oldFile = existingDataFiles[existingDataFiles.length - 1]
650
- oldVersion = oldFile.match(/connect-(\d+\.\d+\.\d+)\.json/)[1]
957
+ if (existingVersions.length > 0) {
958
+ const sortedVersions = semver.rsort(existingVersions)
959
+ oldVersion = sortedVersions[0]
960
+ const oldFile = `connect-${oldVersion}.json`
651
961
  const oldPath = path.join(dataDir, oldFile)
652
- oldIndex = JSON.parse(fs.readFileSync(oldPath, 'utf8'))
962
+ // Strip augmentation fields to ensure clean comparisons
963
+ oldIndex = stripAugmentationFields(JSON.parse(fs.readFileSync(oldPath, 'utf8')))
653
964
  console.log(`šŸ“‹ Using old version data: ${oldFile}`)
654
965
  } else {
655
966
  oldVersion = getAntoraValue('asciidoc.attributes.latest-connect-version')
656
967
  if (oldVersion) {
657
968
  const oldPath = path.join(dataDir, `connect-${oldVersion}.json`)
658
969
  if (fs.existsSync(oldPath)) {
659
- oldIndex = JSON.parse(fs.readFileSync(oldPath, 'utf8'))
970
+ // Strip augmentation fields to ensure clean comparisons
971
+ oldIndex = stripAugmentationFields(JSON.parse(fs.readFileSync(oldPath, 'utf8')))
660
972
  }
661
973
  }
662
974
  }
663
975
  }
664
976
 
665
- let newIndex = JSON.parse(fs.readFileSync(dataFile, 'utf8'))
977
+ // Load and strip augmentation fields for clean comparisons
978
+ let newIndex = stripAugmentationFields(JSON.parse(fs.readFileSync(dataFile, 'utf8')))
666
979
 
667
980
  // Save a clean copy of OSS data for binary analysis (before augmentation)
668
981
  // This ensures the binary analyzer compares actual binaries, not augmented data
669
982
  const cleanOssDataPath = path.join(dataDir, `._connect-${newVersion}-clean.json`)
670
983
 
671
- // Strip augmentation fields to create clean data for comparison
984
+ // Use the already-stripped newIndex for clean data
672
985
  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
986
 
686
987
  fs.writeFileSync(cleanOssDataPath, JSON.stringify(cleanData, null, 2), 'utf8')
687
988
 
@@ -705,11 +1006,18 @@ async function handleRpcnConnectorDocs (options) {
705
1006
  const attachmentsRoot = path.resolve(process.cwd(), 'modules/components/attachments')
706
1007
  fs.mkdirSync(attachmentsRoot, { recursive: true })
707
1008
 
708
- const existingFiles = fs.readdirSync(attachmentsRoot)
709
- .filter(f => /^connect-\d+\.\d+\.\d+\.json$/.test(f))
710
- .sort()
1009
+ const existingVersions = fs.readdirSync(attachmentsRoot)
1010
+ .filter(f => /^connect-(\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?)\.json$/.test(f))
1011
+ .map(f => {
1012
+ const match = f.match(/^connect-(\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?)\.json$/)
1013
+ return match ? match[1] : null
1014
+ })
1015
+ .filter(v => v && semver.valid(v))
1016
+
1017
+ const sortedVersions = semver.sort(existingVersions) // ascending order
711
1018
 
712
- for (const oldFile of existingFiles) {
1019
+ for (const version of sortedVersions) {
1020
+ const oldFile = `connect-${version}.json`
713
1021
  const oldFilePath = path.join(attachmentsRoot, oldFile)
714
1022
  fs.unlinkSync(oldFilePath)
715
1023
  console.log(`🧹 Deleted old version: ${oldFile}`)
@@ -767,15 +1075,15 @@ async function handleRpcnConnectorDocs (options) {
767
1075
  }
768
1076
  }
769
1077
 
1078
+ // Always use clean OSS data for comparison
1079
+ // Temporarily rename the file so the analyzer finds it
1080
+ const expectedPath = path.join(dataDir, `connect-${newVersion}.json`)
1081
+ let tempRenamed = false
1082
+
770
1083
  try {
771
1084
  console.log('\nAnalyzing connector binaries...')
772
1085
  const { analyzeAllBinaries } = require('./connector-binary-analyzer.js')
773
1086
 
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
1087
  if (fs.existsSync(cleanOssDataPath)) {
780
1088
  if (fs.existsSync(expectedPath)) {
781
1089
  fs.renameSync(expectedPath, path.join(dataDir, `._connect-${newVersion}-augmented.json.tmp`))
@@ -797,13 +1105,6 @@ async function handleRpcnConnectorDocs (options) {
797
1105
  analysisOptions
798
1106
  )
799
1107
 
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
-
807
1108
  console.log('Done: Binary analysis complete:')
808
1109
  console.log(` • OSS version: ${binaryAnalysis.ossVersion}`)
809
1110
 
@@ -825,6 +1126,17 @@ async function handleRpcnConnectorDocs (options) {
825
1126
  } catch (err) {
826
1127
  console.error(`Warning: Binary analysis failed: ${err.message}`)
827
1128
  console.error(' Continuing without binary analysis data...')
1129
+ } finally {
1130
+ // Restore the augmented file regardless of success or failure
1131
+ if (tempRenamed) {
1132
+ if (fs.existsSync(expectedPath)) {
1133
+ fs.unlinkSync(expectedPath)
1134
+ }
1135
+ const tmpPath = path.join(dataDir, `._connect-${newVersion}-augmented.json.tmp`)
1136
+ if (fs.existsSync(tmpPath)) {
1137
+ fs.renameSync(tmpPath, expectedPath)
1138
+ }
1139
+ }
828
1140
  }
829
1141
 
830
1142
  // Augment data file
@@ -905,20 +1217,42 @@ async function handleRpcnConnectorDocs (options) {
905
1217
  console.log(` • Added ${addedCloudOnlyCount} cloud-only connectors to data file`)
906
1218
  }
907
1219
 
908
- // Keep only 2 most recent versions
909
- const dataFiles = fs.readdirSync(dataDir)
910
- .filter(f => /^connect-\d+\.\d+\.\d+\.json$/.test(f))
911
- .sort()
1220
+ // Keep only the latest version (delete all older versions)
1221
+ // BUT preserve any files from intermediate processing during this run
1222
+ const dataVersions = fs.readdirSync(dataDir)
1223
+ .filter(f => /^connect-(\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?)\.json$/.test(f))
1224
+ .map(f => {
1225
+ const match = f.match(/^connect-(\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?)\.json$/)
1226
+ return match ? match[1] : null
1227
+ })
1228
+ .filter(v => v && semver.valid(v))
1229
+
1230
+ // Build list of versions we need to keep for this run
1231
+ const versionsToKeep = new Set([newVersion]); // Always keep the latest
1232
+ if (intermediateProcessingResults.length > 0) {
1233
+ // Keep intermediate versions from this run
1234
+ intermediateProcessingResults.forEach(r => {
1235
+ versionsToKeep.add(r.fromVersion);
1236
+ versionsToKeep.add(r.toVersion);
1237
+ });
1238
+ }
1239
+ if (oldVersion) {
1240
+ versionsToKeep.add(oldVersion); // Keep old version for diff
1241
+ }
912
1242
 
913
- while (dataFiles.length > 2) {
914
- const oldestFile = dataFiles.shift()
915
- const oldestPath = path.join(dataDir, oldestFile)
916
- fs.unlinkSync(oldestPath)
917
- console.log(`🧹 Deleted old version from docs-data: ${oldestFile}`)
1243
+ // Delete only files that are NOT needed for this run
1244
+ for (const version of dataVersions) {
1245
+ if (!versionsToKeep.has(version)) {
1246
+ const dataFile = `connect-${version}.json`
1247
+ const dataPath = path.join(dataDir, dataFile);
1248
+ fs.unlinkSync(dataPath);
1249
+ console.log(`🧹 Deleted old version from docs-data: ${dataFile}`);
1250
+ }
918
1251
  }
919
1252
 
920
- // Reload newIndex after augmentation so diff generation uses augmented data
921
- newIndex = JSON.parse(fs.readFileSync(dataFile, 'utf8'))
1253
+ // NOTE: We do NOT reload newIndex after augmentation
1254
+ // Diff generation should use clean OSS data to avoid false positives from CGO/cloud-only components
1255
+ // The augmented data is saved to disk but not used for version comparisons
922
1256
  } catch (err) {
923
1257
  console.error(`Warning: Failed to augment data file: ${err.message}`)
924
1258
  }
@@ -1038,21 +1372,40 @@ async function handleRpcnConnectorDocs (options) {
1038
1372
  console.log(` • Includes binary analysis: OSS ${diffJson.binaryAnalysis.versions.oss}, Cloud ${diffJson.binaryAnalysis.versions.cloud || 'N/A'}, cgo ${diffJson.binaryAnalysis.versions.cgo || 'N/A'}`)
1039
1373
  }
1040
1374
 
1041
- // Cleanup old diff files
1375
+ // Cleanup only individual diff files from THIS run (not master diff or diffs from intermediate processing)
1376
+ // We keep intermediate diffs to build the master diff at the end
1042
1377
  try {
1378
+ const currentRunDiffs = new Set();
1379
+
1380
+ // Collect diffs from this run
1381
+ if (intermediateProcessingResults.length > 0) {
1382
+ intermediateProcessingResults.forEach(r => {
1383
+ if (r.diffPath) {
1384
+ currentRunDiffs.add(path.basename(r.diffPath));
1385
+ }
1386
+ });
1387
+ }
1388
+ currentRunDiffs.add(path.basename(diffPath)); // Current final diff
1389
+
1390
+ // Find old diff files (not from this run, not master-diff)
1043
1391
  const oldDiffFiles = fs.readdirSync(dataDir)
1044
- .filter(f => f.startsWith('connect-diff-') && f.endsWith('.json') && f !== path.basename(diffPath))
1392
+ .filter(f =>
1393
+ f.startsWith('connect-diff-') &&
1394
+ f.endsWith('.json') &&
1395
+ !f.startsWith('connect-diff-master-') &&
1396
+ !currentRunDiffs.has(f)
1397
+ );
1045
1398
 
1046
1399
  if (oldDiffFiles.length > 0) {
1047
- console.log(`🧹 Cleaning up ${oldDiffFiles.length} old diff files...`)
1400
+ console.log(`🧹 Cleaning up ${oldDiffFiles.length} old diff file(s) from previous runs...`);
1048
1401
  oldDiffFiles.forEach(f => {
1049
- const oldDiffPath = path.join(dataDir, f)
1050
- fs.unlinkSync(oldDiffPath)
1051
- console.log(` • Deleted: ${f}`)
1052
- })
1402
+ const oldDiffPath = path.join(dataDir, f);
1403
+ fs.unlinkSync(oldDiffPath);
1404
+ console.log(` • Deleted: ${f}`);
1405
+ });
1053
1406
  }
1054
1407
  } catch (err) {
1055
- console.warn(`Warning: Failed to clean up old diff files: ${err.message}`)
1408
+ console.warn(`Warning: Failed to clean up old diff files: ${err.message}`);
1056
1409
  }
1057
1410
  }
1058
1411
 
@@ -1362,7 +1715,8 @@ async function handleRpcnConnectorDocs (options) {
1362
1715
  templateIntro: options.templateIntro,
1363
1716
  writeFullDrafts: true,
1364
1717
  cgoOnly: binaryAnalysis?.cgoOnly || [],
1365
- cloudOnly: binaryAnalysis?.comparison?.cloudOnly || []
1718
+ cloudOnly: binaryAnalysis?.comparison?.cloudOnly || [],
1719
+ csvMetadata
1366
1720
  })
1367
1721
 
1368
1722
  fs.unlinkSync(tempDataPath)
@@ -1399,14 +1753,40 @@ async function handleRpcnConnectorDocs (options) {
1399
1753
  }
1400
1754
  }
1401
1755
 
1756
+ // Create master diff if we processed intermediate releases
1757
+ let masterDiff = null
1758
+ if (intermediateProcessingResults.length > 0) {
1759
+ try {
1760
+ const { createMasterDiff } = require('./multi-version-summary.js')
1761
+ const masterDiffPath = path.join(dataDir, `connect-diff-master-${intermediateProcessingResults[0].fromVersion}_to_${newVersion}.json`)
1762
+ const finalDiffPath = path.join(dataDir, `connect-diff-${oldVersion}_to_${newVersion}.json`)
1763
+ masterDiff = createMasterDiff(intermediateProcessingResults, finalDiffPath, masterDiffPath)
1764
+ } catch (err) {
1765
+ console.error(`Warning: Failed to create master diff: ${err.message}`)
1766
+ }
1767
+ }
1768
+
1402
1769
  // Generate PR summary
1403
1770
  try {
1404
1771
  const { printPRSummary } = require('./pr-summary-formatter.js')
1405
- printPRSummary(diffJson, binaryAnalysis, draftFiles)
1772
+ // Use master diff if available, otherwise use single diff
1773
+ printPRSummary(masterDiff || diffJson, binaryAnalysis, draftFiles, masterDiff ? true : false)
1406
1774
  } catch (err) {
1407
1775
  console.error(`Warning: Failed to generate PR summary: ${err.message}`)
1408
1776
  }
1409
1777
 
1778
+ // Check for failures in intermediate processing before updating Antora version
1779
+ if (intermediateProcessingResults.length > 0) {
1780
+ const failures = intermediateProcessingResults.filter(r => !r.success)
1781
+ if (failures.length > 0) {
1782
+ console.error(`\nāŒ Cannot update Antora version: ${failures.length} intermediate release(s) failed to process`)
1783
+ failures.forEach(f => {
1784
+ console.error(` • ${f.fromVersion} → ${f.toVersion}: ${f.error}`)
1785
+ })
1786
+ process.exit(1)
1787
+ }
1788
+ }
1789
+
1410
1790
  const wrote = setAntoraValue('asciidoc.attributes.latest-connect-version', newVersion)
1411
1791
  if (wrote) {
1412
1792
  console.log(`Done: Updated Antora version: ${newVersion}`)
@@ -1438,6 +1818,22 @@ async function handleRpcnConnectorDocs (options) {
1438
1818
  console.log('\nšŸ“„ Summary:')
1439
1819
  console.log(` • Run time: ${timestamp}`)
1440
1820
  console.log(` • Version used: ${newVersion}`)
1821
+
1822
+ if (intermediateProcessingResults.length > 0) {
1823
+ const successCount = intermediateProcessingResults.filter(r => r.success).length
1824
+ console.log(` • Intermediate releases processed: ${successCount}/${intermediateProcessingResults.length}`)
1825
+
1826
+ if (successCount < intermediateProcessingResults.length) {
1827
+ console.log(' āš ļø Some intermediate releases failed:')
1828
+ intermediateProcessingResults.filter(r => !r.success).forEach(r => {
1829
+ console.log(` - ${r.fromVersion} → ${r.toVersion}: ${r.error}`)
1830
+ })
1831
+ }
1832
+ }
1833
+
1834
+ // Note: Version cleanup is handled earlier in the augmentation phase (versionsToKeep logic)
1835
+ // This preserves intermediate versions needed for diff generation while removing unneeded files
1836
+
1441
1837
  process.exit(0)
1442
1838
  }
1443
1839