From da6f51de9c7ccd2e75943c1243f355ca357ba32e Mon Sep 17 00:00:00 2001 From: Olivier Korach Date: Fri, 8 May 2026 11:00:55 +0200 Subject: [PATCH] fix: migrate source project version per branch instead of hardcoded 1.0.0 (Issue #137) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Source project version (e.g., "3.20") was systematically migrated to SonarCloud as "1.0.0" because the version was hardcoded in two places: 1. The `sonar.projectVersion` form parameter in the `api/ce/submit` form data. 2. The `projectVersion` field of the protobuf metadata embedded inside the scanner report zip uploaded to the CE. This second location is what SonarCloud's CE actually reads — fixing only the form parameter does nothing because the report metadata overrides it. This change fetches the version per branch from the source SonarQube via `GET /api/navigation/component?component=&branch=` (omitting `branch` for the main branch so the API's default applies, which is robust to SQ↔SC main branch name differences). The fetched version is passed through the transfer chain into both the form parameter and the protobuf metadata, falling back to "1.0.0" only when the API call fails or the setting is unset. Applied across all 4 pipelines (sq-9.9, sq-10.0, sq-10.4, sq-2025): - New shared helper `resolve-source-project-version.js` calls the navigation API. - `transferBranch` resolves the version once per branch and threads it down to `transferBranchBatched` / `uploadReport` and the `ProtobufBuilder` factory. - The `ProtobufBuilder` factory accepts `options.sourceProjectVersion` and `build-metadata.js` reads it with the "1.0.0" fallback. - All call sites (`transferMainBranch`, `transferOneBranch`/`transferSingleBranch`, `transferNonMainBranches`, top-level `runTransferPhases`/`executeTransfer`) forward `sonarQubeClient` so the helper can hit the source API. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../builder/helpers/build-metadata.js | 2 +- .../sq-10.0/protobuf/builder/index.js | 1 + .../helpers/finalize-transfer.js | 2 +- .../helpers/transfer-branch-batched.js | 6 ++-- .../helpers/transfer-branch.js | 11 ++++--- .../helpers/transfer-main-branch.js | 4 +-- .../helpers/transfer-non-main-branches.js | 4 +-- .../helpers/transfer-project.js | 2 +- .../transfer-single-non-main-branch.js | 4 +-- .../builder/helpers/build-metadata.js | 2 +- .../helpers/create-protobuf-builder.js | 1 + .../helpers/execute-transfer.js | 4 +-- .../helpers/transfer-branch-batched.js | 6 ++-- .../helpers/transfer-branch.js | 11 ++++--- .../helpers/transfer-main-branch.js | 4 +-- .../helpers/transfer-single-branch.js | 4 +-- .../builder/helpers/build-metadata.js | 2 +- .../helpers/create-protobuf-builder.js | 1 + .../helpers/run-transfer-phases.js | 4 +-- .../helpers/build-and-encode.js | 4 +-- .../helpers/transfer-batched.js | 6 ++-- .../transfer-branch/helpers/upload-report.js | 4 +-- .../helpers/transfer-branch/index.js | 11 ++++--- .../helpers/execute-main-branch-transfer.js | 4 +-- .../helpers/transfer-one-branch/index.js | 4 +-- .../builder/helpers/build-metadata.js | 2 +- .../sq-9.9/protobuf/builder/index.js | 1 + .../helpers/build-protobuf-messages.js | 4 +-- .../helpers/run-transfer-phases.js | 4 +-- .../helpers/transfer-branch-batched.js | 6 ++-- .../helpers/transfer-branch.js | 11 ++++--- .../helpers/transfer-main-branch.js | 4 +-- .../helpers/transfer-non-main-branches.js | 4 +-- .../helpers/transfer-one-branch.js | 4 +-- .../helpers/upload-report.js | 4 +-- .../resolve-source-project-version.js | 33 +++++++++++++++++++ 36 files changed, 117 insertions(+), 68 deletions(-) create mode 100644 src/shared/utils/source-version/resolve-source-project-version.js diff --git a/src/pipelines/sq-10.0/protobuf/builder/helpers/build-metadata.js b/src/pipelines/sq-10.0/protobuf/builder/helpers/build-metadata.js index a708232..4100eb1 100644 --- a/src/pipelines/sq-10.0/protobuf/builder/helpers/build-metadata.js +++ b/src/pipelines/sq-10.0/protobuf/builder/helpers/build-metadata.js @@ -18,7 +18,7 @@ export function buildMetadata(ctx) { branchName, branchType: 1, referenceBranchName: referenceBranch, scmRevisionId: ctx.data.metadata.scmRevisionId || generateFakeCommitHash(), - projectVersion: '1.0.0', + projectVersion: ctx.sourceProjectVersion || '1.0.0', analyzedIndexedFileCountPerType: ctx.buildFileCountsByType(), }; logger.debug(`Metadata built: projectKey=${metadata.projectKey}, branch=${metadata.branchName}, scmRevisionId=${metadata.scmRevisionId}`); diff --git a/src/pipelines/sq-10.0/protobuf/builder/index.js b/src/pipelines/sq-10.0/protobuf/builder/index.js index 5d2587f..f60027f 100644 --- a/src/pipelines/sq-10.0/protobuf/builder/index.js +++ b/src/pipelines/sq-10.0/protobuf/builder/index.js @@ -10,6 +10,7 @@ export function createProtobufBuilder(extractedData, scConfig = {}, scProfiles = referenceBranchName: options.referenceBranchName || null, sonarCloudRepos: options.sonarCloudRepos || new Set(), ruleEnrichmentMap: options.ruleEnrichmentMap || new Map(), + sourceProjectVersion: options.sourceProjectVersion || null, }; bindBuilderMethods(ctx); return ctx; diff --git a/src/pipelines/sq-10.0/transfer-pipeline/helpers/finalize-transfer.js b/src/pipelines/sq-10.0/transfer-pipeline/helpers/finalize-transfer.js index 17cb9e0..e2067a1 100644 --- a/src/pipelines/sq-10.0/transfer-pipeline/helpers/finalize-transfer.js +++ b/src/pipelines/sq-10.0/transfer-pipeline/helpers/finalize-transfer.js @@ -23,7 +23,7 @@ export async function finalizeTransfer({ mainResult, sonarCloudMainBranch, syncA if (nonMainBranches.length > 0) { await waitForMainAnalysis(sonarCloudClient, mainResult.ceTask?.id, wait); logger.info(`Syncing ${nonMainBranches.length} additional branch(es): ${nonMainBranches.map(b => b.name).join(', ')}`); - await transferNonMainBranches({ nonMainBranches, extractedData, extractor, sonarcloudConfig, sonarCloudProfiles, sonarCloudMainBranch, wait, sonarCloudClient, journal, cache, stateTracker, isIncremental, shutdownCheck, performanceConfig, sonarCloudRepos, ruleEnrichmentMap, aggregatedStats }); + await transferNonMainBranches({ nonMainBranches, extractedData, extractor, sonarcloudConfig, sonarCloudProfiles, sonarCloudMainBranch, wait, sonarCloudClient, sonarQubeClient, journal, cache, stateTracker, isIncremental, shutdownCheck, performanceConfig, sonarCloudRepos, ruleEnrichmentMap, aggregatedStats }); } else { logger.info('No additional branches to sync (only the main branch exists)'); } diff --git a/src/pipelines/sq-10.0/transfer-pipeline/helpers/transfer-branch-batched.js b/src/pipelines/sq-10.0/transfer-pipeline/helpers/transfer-branch-batched.js index 37c0097..415b66b 100644 --- a/src/pipelines/sq-10.0/transfer-pipeline/helpers/transfer-branch-batched.js +++ b/src/pipelines/sq-10.0/transfer-pipeline/helpers/transfer-branch-batched.js @@ -11,7 +11,7 @@ import logger from '../../../../shared/utils/logger.js'; export async function transferBranchBatched(opts) { const { extractedData, sonarcloudConfig, sonarCloudProfiles, branchName, referenceBranchName, sonarCloudClient, label, isMainBranch, - sonarCloudRepos, ruleEnrichmentMap } = opts; + sonarCloudRepos, ruleEnrichmentMap, sourceProjectVersion } = opts; extractedData.issues.sort((a, b) => (a.component || '').localeCompare(b.component || '')); @@ -29,7 +29,7 @@ export async function transferBranchBatched(opts) { logger.info(`[${batchLabel}] Issues ${batch.startIndex + 1}-${batch.endIndex} | date=${batchDate}`); const builder = new ProtobufBuilder(batchData, sonarcloudConfig, sonarCloudProfiles, { - sonarCloudBranchName: branchName, referenceBranchName, sonarCloudRepos, ruleEnrichmentMap, + sonarCloudBranchName: branchName, referenceBranchName, sonarCloudRepos, ruleEnrichmentMap, sourceProjectVersion, }); const messages = builder.buildAll(); const encoder = new ProtobufEncoder(); @@ -38,7 +38,7 @@ export async function transferBranchBatched(opts) { const uploader = new ReportUploader(sonarCloudClient); const metadata = { projectKey: sonarcloudConfig.projectKey, organization: sonarcloudConfig.organization, - version: '1.0.0', ...(!isMainBranch && branchName ? { branchName } : {}), + version: sourceProjectVersion || '1.0.0', ...(!isMainBranch && branchName ? { branchName } : {}), }; lastCeTask = await uploader.uploadAndWait(encodedReport, metadata); diff --git a/src/pipelines/sq-10.0/transfer-pipeline/helpers/transfer-branch.js b/src/pipelines/sq-10.0/transfer-pipeline/helpers/transfer-branch.js index 5adc9a7..06615f2 100644 --- a/src/pipelines/sq-10.0/transfer-pipeline/helpers/transfer-branch.js +++ b/src/pipelines/sq-10.0/transfer-pipeline/helpers/transfer-branch.js @@ -5,18 +5,21 @@ import { ReportUploader } from '../../sonarcloud/uploader.js'; import { computeBranchStats } from './compute-branch-stats.js'; import { transferBranchBatched } from './transfer-branch-batched.js'; import { shouldBatch, backdateChangesets } from '../../../../shared/utils/batch-distributor.js'; +import { resolveSourceProjectVersion } from '../../../../shared/utils/source-version/resolve-source-project-version.js'; // -------- Transfer Single Branch -------- /** * Build, encode, and upload a single branch report to SonarCloud. */ -export async function transferBranch({ extractedData, sonarcloudConfig, sonarCloudProfiles, branchName, referenceBranchName, wait, sonarCloudClient, label, isMainBranch = false, sonarCloudRepos = new Set(), ruleEnrichmentMap = new Map() }) { +export async function transferBranch({ extractedData, sonarcloudConfig, sonarCloudProfiles, branchName, referenceBranchName, wait, sonarCloudClient, sonarQubeClient, label, isMainBranch = false, sonarCloudRepos = new Set(), ruleEnrichmentMap = new Map() }) { + const sourceProjectVersion = await resolveSourceProjectVersion(sonarQubeClient, sonarQubeClient?.projectKey, isMainBranch ? null : branchName); + if (shouldBatch(extractedData)) { const ceTask = await transferBranchBatched({ extractedData, sonarcloudConfig, sonarCloudProfiles, branchName, referenceBranchName, sonarCloudClient, label, isMainBranch, - sonarCloudRepos, ruleEnrichmentMap, + sonarCloudRepos, ruleEnrichmentMap, sourceProjectVersion, }); return { stats: computeBranchStats(extractedData), ceTask }; } @@ -25,7 +28,7 @@ export async function transferBranch({ extractedData, sonarcloudConfig, sonarClo logger.info(`[${label}] Building protobuf messages...`); const builder = new ProtobufBuilder(extractedData, sonarcloudConfig, sonarCloudProfiles, { - sonarCloudBranchName: branchName, referenceBranchName, sonarCloudRepos, ruleEnrichmentMap, + sonarCloudBranchName: branchName, referenceBranchName, sonarCloudRepos, ruleEnrichmentMap, sourceProjectVersion, }); const messages = builder.buildAll(); @@ -39,7 +42,7 @@ export async function transferBranch({ extractedData, sonarcloudConfig, sonarClo const metadata = { projectKey: sonarcloudConfig.projectKey, organization: sonarcloudConfig.organization, - version: '1.0.0', + version: sourceProjectVersion || '1.0.0', ...(!isMainBranch && branchName ? { branchName } : {}), }; diff --git a/src/pipelines/sq-10.0/transfer-pipeline/helpers/transfer-main-branch.js b/src/pipelines/sq-10.0/transfer-pipeline/helpers/transfer-main-branch.js index 76198bd..76175d2 100644 --- a/src/pipelines/sq-10.0/transfer-pipeline/helpers/transfer-main-branch.js +++ b/src/pipelines/sq-10.0/transfer-pipeline/helpers/transfer-main-branch.js @@ -6,7 +6,7 @@ import { transferBranch } from './transfer-branch.js'; const ZERO_STATS = { issuesTransferred: 0, hotspotsTransferred: 0, componentsTransferred: 0, sourcesTransferred: 0, linesOfCode: 0 }; -export async function transferMainBranch({ extractedData, sonarcloudConfig, sonarCloudProfiles, sonarCloudMainBranch, wait, sonarCloudClient, journal, sonarCloudRepos, ruleEnrichmentMap }) { +export async function transferMainBranch({ extractedData, sonarcloudConfig, sonarCloudProfiles, sonarCloudMainBranch, wait, sonarCloudClient, sonarQubeClient, journal, sonarCloudRepos, ruleEnrichmentMap }) { const alreadyCompleted = journal?.getBranchStatus(sonarCloudMainBranch) === 'completed'; if (alreadyCompleted) { @@ -26,7 +26,7 @@ export async function transferMainBranch({ extractedData, sonarcloudConfig, sona const result = await transferBranch({ extractedData, sonarcloudConfig, sonarCloudProfiles, branchName: sonarCloudMainBranch, referenceBranchName: sonarCloudMainBranch, - wait, sonarCloudClient, label: 'main', isMainBranch: true, + wait, sonarCloudClient, sonarQubeClient, label: 'main', isMainBranch: true, sonarCloudRepos, ruleEnrichmentMap, }); diff --git a/src/pipelines/sq-10.0/transfer-pipeline/helpers/transfer-non-main-branches.js b/src/pipelines/sq-10.0/transfer-pipeline/helpers/transfer-non-main-branches.js index a20efac..d40c6c4 100644 --- a/src/pipelines/sq-10.0/transfer-pipeline/helpers/transfer-non-main-branches.js +++ b/src/pipelines/sq-10.0/transfer-pipeline/helpers/transfer-non-main-branches.js @@ -4,10 +4,10 @@ import { aggregateBranchStats } from './aggregate-branch-stats.js'; // -------- Transfer Non-Main Branches -------- -export async function transferNonMainBranches({ nonMainBranches, extractedData, extractor, sonarcloudConfig, sonarCloudProfiles, sonarCloudMainBranch, wait, sonarCloudClient, journal, cache, stateTracker, isIncremental, shutdownCheck, performanceConfig, sonarCloudRepos, ruleEnrichmentMap, aggregatedStats }) { +export async function transferNonMainBranches({ nonMainBranches, extractedData, extractor, sonarcloudConfig, sonarCloudProfiles, sonarCloudMainBranch, wait, sonarCloudClient, sonarQubeClient, journal, cache, stateTracker, isIncremental, shutdownCheck, performanceConfig, sonarCloudRepos, ruleEnrichmentMap, aggregatedStats }) { const branchResults = await mapConcurrent( nonMainBranches, - async (branch) => transferSingleNonMainBranch({ branch, extractedData, extractor, sonarcloudConfig, sonarCloudProfiles, sonarCloudMainBranch, wait, sonarCloudClient, journal, cache, stateTracker, isIncremental, shutdownCheck, sonarCloudRepos, ruleEnrichmentMap }), + async (branch) => transferSingleNonMainBranch({ branch, extractedData, extractor, sonarcloudConfig, sonarCloudProfiles, sonarCloudMainBranch, wait, sonarCloudClient, sonarQubeClient, journal, cache, stateTracker, isIncremental, shutdownCheck, sonarCloudRepos, ruleEnrichmentMap }), { concurrency: performanceConfig?.maxConcurrency || 4, settled: true } ); aggregateBranchStats(aggregatedStats, branchResults); diff --git a/src/pipelines/sq-10.0/transfer-pipeline/helpers/transfer-project.js b/src/pipelines/sq-10.0/transfer-pipeline/helpers/transfer-project.js index 37c3765..eeb5f34 100644 --- a/src/pipelines/sq-10.0/transfer-pipeline/helpers/transfer-project.js +++ b/src/pipelines/sq-10.0/transfer-pipeline/helpers/transfer-project.js @@ -38,7 +38,7 @@ export async function transferProject(opts) { const sonarCloudRepos = await sonarCloudClient.getRuleRepositories(); const ruleEnrichmentMap = p.prebuiltEnrichmentMap || new Map(); checkShutdown(p.shutdownCheck); - const mainResult = await transferMainBranch({ extractedData, sonarcloudConfig: p.sonarcloudConfig, sonarCloudProfiles, sonarCloudMainBranch, wait: p.wait, sonarCloudClient, journal, sonarCloudRepos, ruleEnrichmentMap }); + const mainResult = await transferMainBranch({ extractedData, sonarcloudConfig: p.sonarcloudConfig, sonarCloudProfiles, sonarCloudMainBranch, wait: p.wait, sonarCloudClient, sonarQubeClient, journal, sonarCloudRepos, ruleEnrichmentMap }); return await finalizeTransfer({ mainResult, sonarCloudMainBranch, ...p, extractedData, extractor, sonarCloudProfiles, sonarCloudClient, sonarQubeClient, journal, cache, stateTracker, sonarCloudRepos, ruleEnrichmentMap, lockFile }); } catch (error) { if (!(error instanceof GracefulShutdownError) && journal) { await journal.markInterrupted().catch(() => {}); } diff --git a/src/pipelines/sq-10.0/transfer-pipeline/helpers/transfer-single-non-main-branch.js b/src/pipelines/sq-10.0/transfer-pipeline/helpers/transfer-single-non-main-branch.js index ffa20d3..5be43b7 100644 --- a/src/pipelines/sq-10.0/transfer-pipeline/helpers/transfer-single-non-main-branch.js +++ b/src/pipelines/sq-10.0/transfer-pipeline/helpers/transfer-single-non-main-branch.js @@ -4,7 +4,7 @@ import { transferBranch } from './transfer-branch.js'; // -------- Transfer Single Non-Main Branch -------- -export async function transferSingleNonMainBranch({ branch, extractedData, extractor, sonarcloudConfig, sonarCloudProfiles, sonarCloudMainBranch, wait, sonarCloudClient, journal, cache, stateTracker, isIncremental, shutdownCheck, sonarCloudRepos, ruleEnrichmentMap }) { +export async function transferSingleNonMainBranch({ branch, extractedData, extractor, sonarcloudConfig, sonarCloudProfiles, sonarCloudMainBranch, wait, sonarCloudClient, sonarQubeClient, journal, cache, stateTracker, isIncremental, shutdownCheck, sonarCloudRepos, ruleEnrichmentMap }) { const branchName = branch.name; if (shutdownCheck()) return { skipped: true, branchName, reason: 'shutdown' }; if (isIncremental && stateTracker.isBranchCompleted(branchName)) { @@ -24,7 +24,7 @@ export async function transferSingleNonMainBranch({ branch, extractedData, extra const branchResult = await transferBranch({ extractedData: branchData, sonarcloudConfig, sonarCloudProfiles, branchName, referenceBranchName: sonarCloudMainBranch, wait, - sonarCloudClient, label: branchName, sonarCloudRepos, ruleEnrichmentMap, + sonarCloudClient, sonarQubeClient, label: branchName, sonarCloudRepos, ruleEnrichmentMap, }); if (journal) { await journal.recordUpload(branchName, branchResult.ceTask?.id); diff --git a/src/pipelines/sq-10.4/protobuf/builder/helpers/build-metadata.js b/src/pipelines/sq-10.4/protobuf/builder/helpers/build-metadata.js index 580e4e2..f8b0070 100644 --- a/src/pipelines/sq-10.4/protobuf/builder/helpers/build-metadata.js +++ b/src/pipelines/sq-10.4/protobuf/builder/helpers/build-metadata.js @@ -17,7 +17,7 @@ export function buildMetadata(instance) { qprofilesPerLanguage: instance.buildQProfiles(), branchName, branchType: 1, referenceBranchName: referenceBranch, scmRevisionId: instance.data.metadata.scmRevisionId || instance.generateFakeCommitHash(), - projectVersion: '1.0.0', + projectVersion: instance.sourceProjectVersion || '1.0.0', analyzedIndexedFileCountPerType: instance.buildFileCountsByType(), }; logger.debug(`Metadata built: projectKey=${metadata.projectKey}, branch=${metadata.branchName}, scmRevisionId=${metadata.scmRevisionId}`); diff --git a/src/pipelines/sq-10.4/protobuf/builder/helpers/create-protobuf-builder.js b/src/pipelines/sq-10.4/protobuf/builder/helpers/create-protobuf-builder.js index 449465d..c27ab0f 100644 --- a/src/pipelines/sq-10.4/protobuf/builder/helpers/create-protobuf-builder.js +++ b/src/pipelines/sq-10.4/protobuf/builder/helpers/create-protobuf-builder.js @@ -23,6 +23,7 @@ export function createProtobufBuilder(extractedData, sonarCloudConfig = {}, sona referenceBranchName: options.referenceBranchName || null, sonarCloudRepos: options.sonarCloudRepos || new Set(), ruleEnrichmentMap: options.ruleEnrichmentMap || new Map(), + sourceProjectVersion: options.sourceProjectVersion || null, }; attachUtilityMethods(instance); diff --git a/src/pipelines/sq-10.4/transfer-pipeline/helpers/execute-transfer.js b/src/pipelines/sq-10.4/transfer-pipeline/helpers/execute-transfer.js index 12eaf28..729e3ae 100644 --- a/src/pipelines/sq-10.4/transfer-pipeline/helpers/execute-transfer.js +++ b/src/pipelines/sq-10.4/transfer-pipeline/helpers/execute-transfer.js @@ -30,14 +30,14 @@ export async function executeTransfer(opts) { checkShutdown(shutdownCheck); - const mainBranchResult = await transferMainBranch({ extractedData, sonarcloudConfig, sonarCloudProfiles, branchName: sonarCloudMainBranch, wait, sonarCloudClient, journal, sonarCloudRepos, ruleEnrichmentMap }); + const mainBranchResult = await transferMainBranch({ extractedData, sonarcloudConfig, sonarCloudProfiles, branchName: sonarCloudMainBranch, wait, sonarCloudClient, sonarQubeClient, journal, sonarCloudRepos, ruleEnrichmentMap }); const aggregatedStats = { issuesTransferred: mainBranchResult.stats.issuesTransferred || 0, hotspotsTransferred: mainBranchResult.stats.hotspotsTransferred || 0, componentsTransferred: mainBranchResult.stats.componentsTransferred || 0, sourcesTransferred: mainBranchResult.stats.sourcesTransferred || 0, linesOfCode: mainBranchResult.stats.linesOfCode || 0, branchesTransferred: [sonarCloudMainBranch] }; if (isIncremental) { stateTracker.markBranchCompleted(sonarCloudMainBranch); await stateTracker.save(); } checkShutdown(shutdownCheck); if (syncAllBranches) { - await transferNonMainBranches({ extractedData, sonarcloudConfig, sonarCloudProfiles, mainBranchResult, sonarCloudMainBranch, wait, sonarCloudClient, extractor, journal, cache, stateTracker, isIncremental, shutdownCheck, excludeBranches, includeBranches, performanceConfig, aggregatedStats, sonarCloudRepos, ruleEnrichmentMap }); + await transferNonMainBranches({ extractedData, sonarcloudConfig, sonarCloudProfiles, mainBranchResult, sonarCloudMainBranch, wait, sonarCloudClient, sonarQubeClient, extractor, journal, cache, stateTracker, isIncremental, shutdownCheck, excludeBranches, includeBranches, performanceConfig, aggregatedStats, sonarCloudRepos, ruleEnrichmentMap }); } // -------- Phase 2: Metadata Sync -------- diff --git a/src/pipelines/sq-10.4/transfer-pipeline/helpers/transfer-branch-batched.js b/src/pipelines/sq-10.4/transfer-pipeline/helpers/transfer-branch-batched.js index 37c0097..415b66b 100644 --- a/src/pipelines/sq-10.4/transfer-pipeline/helpers/transfer-branch-batched.js +++ b/src/pipelines/sq-10.4/transfer-pipeline/helpers/transfer-branch-batched.js @@ -11,7 +11,7 @@ import logger from '../../../../shared/utils/logger.js'; export async function transferBranchBatched(opts) { const { extractedData, sonarcloudConfig, sonarCloudProfiles, branchName, referenceBranchName, sonarCloudClient, label, isMainBranch, - sonarCloudRepos, ruleEnrichmentMap } = opts; + sonarCloudRepos, ruleEnrichmentMap, sourceProjectVersion } = opts; extractedData.issues.sort((a, b) => (a.component || '').localeCompare(b.component || '')); @@ -29,7 +29,7 @@ export async function transferBranchBatched(opts) { logger.info(`[${batchLabel}] Issues ${batch.startIndex + 1}-${batch.endIndex} | date=${batchDate}`); const builder = new ProtobufBuilder(batchData, sonarcloudConfig, sonarCloudProfiles, { - sonarCloudBranchName: branchName, referenceBranchName, sonarCloudRepos, ruleEnrichmentMap, + sonarCloudBranchName: branchName, referenceBranchName, sonarCloudRepos, ruleEnrichmentMap, sourceProjectVersion, }); const messages = builder.buildAll(); const encoder = new ProtobufEncoder(); @@ -38,7 +38,7 @@ export async function transferBranchBatched(opts) { const uploader = new ReportUploader(sonarCloudClient); const metadata = { projectKey: sonarcloudConfig.projectKey, organization: sonarcloudConfig.organization, - version: '1.0.0', ...(!isMainBranch && branchName ? { branchName } : {}), + version: sourceProjectVersion || '1.0.0', ...(!isMainBranch && branchName ? { branchName } : {}), }; lastCeTask = await uploader.uploadAndWait(encodedReport, metadata); diff --git a/src/pipelines/sq-10.4/transfer-pipeline/helpers/transfer-branch.js b/src/pipelines/sq-10.4/transfer-pipeline/helpers/transfer-branch.js index 7211860..7672fe4 100644 --- a/src/pipelines/sq-10.4/transfer-pipeline/helpers/transfer-branch.js +++ b/src/pipelines/sq-10.4/transfer-pipeline/helpers/transfer-branch.js @@ -4,6 +4,7 @@ import { ReportUploader } from '../../sonarcloud/uploader.js'; import { computeBranchStats } from './compute-branch-stats.js'; import { transferBranchBatched } from './transfer-branch-batched.js'; import { shouldBatch, backdateChangesets } from '../../../../shared/utils/batch-distributor.js'; +import { resolveSourceProjectVersion } from '../../../../shared/utils/source-version/resolve-source-project-version.js'; import logger from '../../../../shared/utils/logger.js'; // -------- Main Logic -------- @@ -11,12 +12,14 @@ import logger from '../../../../shared/utils/logger.js'; /** * Build, encode, and upload a single branch report to SonarCloud. */ -export async function transferBranch({ extractedData, sonarcloudConfig, sonarCloudProfiles, branchName, referenceBranchName, wait, sonarCloudClient, label, isMainBranch = false, sonarCloudRepos = new Set(), ruleEnrichmentMap = new Map() }) { +export async function transferBranch({ extractedData, sonarcloudConfig, sonarCloudProfiles, branchName, referenceBranchName, wait, sonarCloudClient, sonarQubeClient, label, isMainBranch = false, sonarCloudRepos = new Set(), ruleEnrichmentMap = new Map() }) { + const sourceProjectVersion = await resolveSourceProjectVersion(sonarQubeClient, sonarQubeClient?.projectKey, isMainBranch ? null : branchName); + if (shouldBatch(extractedData)) { const ceTask = await transferBranchBatched({ extractedData, sonarcloudConfig, sonarCloudProfiles, branchName, referenceBranchName, sonarCloudClient, label, isMainBranch, - sonarCloudRepos, ruleEnrichmentMap, + sonarCloudRepos, ruleEnrichmentMap, sourceProjectVersion, }); return { stats: computeBranchStats(extractedData), ceTask }; } @@ -25,7 +28,7 @@ export async function transferBranch({ extractedData, sonarcloudConfig, sonarClo logger.info(`[${label}] Building protobuf messages...`); const builder = new ProtobufBuilder(extractedData, sonarcloudConfig, sonarCloudProfiles, { - sonarCloudBranchName: branchName, referenceBranchName, sonarCloudRepos, ruleEnrichmentMap, + sonarCloudBranchName: branchName, referenceBranchName, sonarCloudRepos, ruleEnrichmentMap, sourceProjectVersion, }); const messages = builder.buildAll(); @@ -38,7 +41,7 @@ export async function transferBranch({ extractedData, sonarcloudConfig, sonarClo const uploader = new ReportUploader(sonarCloudClient); const metadata = { projectKey: sonarcloudConfig.projectKey, organization: sonarcloudConfig.organization, - version: '1.0.0', ...(!isMainBranch && branchName ? { branchName } : {}), + version: sourceProjectVersion || '1.0.0', ...(!isMainBranch && branchName ? { branchName } : {}), }; let ceTask; diff --git a/src/pipelines/sq-10.4/transfer-pipeline/helpers/transfer-main-branch.js b/src/pipelines/sq-10.4/transfer-pipeline/helpers/transfer-main-branch.js index 5dffc85..b919eef 100644 --- a/src/pipelines/sq-10.4/transfer-pipeline/helpers/transfer-main-branch.js +++ b/src/pipelines/sq-10.4/transfer-pipeline/helpers/transfer-main-branch.js @@ -11,7 +11,7 @@ import logger from '../../../../shared/utils/logger.js'; * @param {object} opts - Transfer options * @returns {Promise} { stats, ceTask } */ -export async function transferMainBranch({ extractedData, sonarcloudConfig, sonarCloudProfiles, branchName, wait, sonarCloudClient, journal, sonarCloudRepos, ruleEnrichmentMap }) { +export async function transferMainBranch({ extractedData, sonarcloudConfig, sonarCloudProfiles, branchName, wait, sonarCloudClient, sonarQubeClient, journal, sonarCloudRepos, ruleEnrichmentMap }) { const mainBranchCompleted = journal?.getBranchStatus(branchName) === 'completed'; const emptyStats = { issuesTransferred: 0, hotspotsTransferred: 0, componentsTransferred: 0, sourcesTransferred: 0, linesOfCode: 0 }; @@ -33,7 +33,7 @@ export async function transferMainBranch({ extractedData, sonarcloudConfig, sona const result = await transferBranch({ extractedData, sonarcloudConfig, sonarCloudProfiles, branchName, - referenceBranchName: branchName, wait, sonarCloudClient, label: 'main', + referenceBranchName: branchName, wait, sonarCloudClient, sonarQubeClient, label: 'main', isMainBranch: true, sonarCloudRepos, ruleEnrichmentMap, }); diff --git a/src/pipelines/sq-10.4/transfer-pipeline/helpers/transfer-single-branch.js b/src/pipelines/sq-10.4/transfer-pipeline/helpers/transfer-single-branch.js index 9db8b5d..5afcf4b 100644 --- a/src/pipelines/sq-10.4/transfer-pipeline/helpers/transfer-single-branch.js +++ b/src/pipelines/sq-10.4/transfer-pipeline/helpers/transfer-single-branch.js @@ -12,7 +12,7 @@ import logger from '../../../../shared/utils/logger.js'; * @returns {Promise} { branchName, branchResult } or { skipped, branchName } */ export async function transferSingleBranch(opts) { - const { branch, extractedData, sonarcloudConfig, sonarCloudProfiles, sonarCloudMainBranch, wait, sonarCloudClient, extractor, journal, cache, stateTracker, isIncremental, shutdownCheck, sonarCloudRepos, ruleEnrichmentMap } = opts; + const { branch, extractedData, sonarcloudConfig, sonarCloudProfiles, sonarCloudMainBranch, wait, sonarCloudClient, sonarQubeClient, extractor, journal, cache, stateTracker, isIncremental, shutdownCheck, sonarCloudRepos, ruleEnrichmentMap } = opts; const branchName = branch.name; const skipResult = checkBranchSkip(branchName, { shutdownCheck, isIncremental, stateTracker, journal }); @@ -28,7 +28,7 @@ export async function transferSingleBranch(opts) { const branchResult = await transferBranch({ extractedData: branchData, sonarcloudConfig, sonarCloudProfiles, branchName, - referenceBranchName: sonarCloudMainBranch, wait, sonarCloudClient, label: branchName, + referenceBranchName: sonarCloudMainBranch, wait, sonarCloudClient, sonarQubeClient, label: branchName, sonarCloudRepos, ruleEnrichmentMap, }); diff --git a/src/pipelines/sq-2025/protobuf/builder/helpers/build-metadata.js b/src/pipelines/sq-2025/protobuf/builder/helpers/build-metadata.js index fc15f3e..c9fb1f1 100644 --- a/src/pipelines/sq-2025/protobuf/builder/helpers/build-metadata.js +++ b/src/pipelines/sq-2025/protobuf/builder/helpers/build-metadata.js @@ -19,7 +19,7 @@ export function buildMetadata(inst) { qprofilesPerLanguage: inst.buildQProfiles(), branchName, branchType: 1, referenceBranchName: referenceBranch, scmRevisionId: inst.data.metadata.scmRevisionId || randomBytes(20).toString('hex'), - projectVersion: '1.0.0', + projectVersion: inst.sourceProjectVersion || '1.0.0', analyzedIndexedFileCountPerType: inst.buildFileCountsByType(), }; diff --git a/src/pipelines/sq-2025/protobuf/builder/helpers/create-protobuf-builder.js b/src/pipelines/sq-2025/protobuf/builder/helpers/create-protobuf-builder.js index 8bd1f8d..38fe11a 100644 --- a/src/pipelines/sq-2025/protobuf/builder/helpers/create-protobuf-builder.js +++ b/src/pipelines/sq-2025/protobuf/builder/helpers/create-protobuf-builder.js @@ -14,6 +14,7 @@ export function createProtobufBuilder(extractedData, sonarCloudConfig = {}, sona referenceBranchName: options.referenceBranchName || null, sonarCloudRepos: options.sonarCloudRepos || new Set(), ruleEnrichmentMap: options.ruleEnrichmentMap || new Map(), + sourceProjectVersion: options.sourceProjectVersion || null, }; inst.getComponentRef = (key) => { diff --git a/src/pipelines/sq-2025/transfer-pipeline/helpers/execute-transfer/helpers/run-transfer-phases.js b/src/pipelines/sq-2025/transfer-pipeline/helpers/execute-transfer/helpers/run-transfer-phases.js index 065852e..ce756e8 100644 --- a/src/pipelines/sq-2025/transfer-pipeline/helpers/execute-transfer/helpers/run-transfer-phases.js +++ b/src/pipelines/sq-2025/transfer-pipeline/helpers/execute-transfer/helpers/run-transfer-phases.js @@ -15,13 +15,13 @@ export async function runTransferPhases(opts) { extractedData, extractor, projectKey, performanceConfig } = opts; const { mainBranchResult, aggregatedStats } = await transferMainBranch({ - journal, sonarCloudMainBranch, sonarCloudClient, extractedData, + journal, sonarCloudMainBranch, sonarCloudClient, sonarQubeClient, extractedData, sonarcloudConfig, sonarCloudProfiles, wait, sonarCloudRepos, ruleEnrichmentMap, stateTracker, isIncremental, }); checkShutdown(shutdownCheck); if (syncAllBranches) { - await transferNonMainBranches({ extractedData, excludeBranches, includeBranches, mainBranchResult, sonarCloudClient, sonarCloudMainBranch, wait, aggregatedStats, extractor, journal, cache, shutdownCheck, stateTracker, isIncremental, sonarcloudConfig, sonarCloudProfiles, sonarCloudRepos, ruleEnrichmentMap, performanceConfig }); + await transferNonMainBranches({ extractedData, excludeBranches, includeBranches, mainBranchResult, sonarCloudClient, sonarQubeClient, sonarCloudMainBranch, wait, aggregatedStats, extractor, journal, cache, shutdownCheck, stateTracker, isIncremental, sonarcloudConfig, sonarCloudProfiles, sonarCloudRepos, ruleEnrichmentMap, performanceConfig }); } // -------- Phase 2: Metadata Sync -------- diff --git a/src/pipelines/sq-2025/transfer-pipeline/helpers/transfer-branch/helpers/build-and-encode.js b/src/pipelines/sq-2025/transfer-pipeline/helpers/transfer-branch/helpers/build-and-encode.js index c7876f2..dbee837 100644 --- a/src/pipelines/sq-2025/transfer-pipeline/helpers/transfer-branch/helpers/build-and-encode.js +++ b/src/pipelines/sq-2025/transfer-pipeline/helpers/transfer-branch/helpers/build-and-encode.js @@ -5,11 +5,11 @@ import logger from '../../../../../../shared/utils/logger.js'; // -------- Build and Encode -------- /** Build protobuf messages and encode them. */ -export function buildProtobufMessages(data, scConfig, profiles, branch, refBranch, repos, enrichMap, label) { +export function buildProtobufMessages(data, scConfig, profiles, branch, refBranch, repos, enrichMap, label, sourceProjectVersion) { logger.info(`[${label}] Building protobuf messages...`); const builder = new ProtobufBuilder(data, scConfig, profiles, { sonarCloudBranchName: branch, referenceBranchName: refBranch, - sonarCloudRepos: repos, ruleEnrichmentMap: enrichMap, + sonarCloudRepos: repos, ruleEnrichmentMap: enrichMap, sourceProjectVersion, }); return builder.buildAll(); } diff --git a/src/pipelines/sq-2025/transfer-pipeline/helpers/transfer-branch/helpers/transfer-batched.js b/src/pipelines/sq-2025/transfer-pipeline/helpers/transfer-branch/helpers/transfer-batched.js index 8ec7dc9..07ad5d2 100644 --- a/src/pipelines/sq-2025/transfer-pipeline/helpers/transfer-branch/helpers/transfer-batched.js +++ b/src/pipelines/sq-2025/transfer-pipeline/helpers/transfer-branch/helpers/transfer-batched.js @@ -10,7 +10,7 @@ import logger from '../../../../../../shared/utils/logger.js'; export async function transferBatched(opts) { const { extractedData, sonarcloudConfig, sonarCloudProfiles, branchName, referenceBranchName, sonarCloudClient, label, isMainBranch, - sonarCloudRepos, ruleEnrichmentMap } = opts; + sonarCloudRepos, ruleEnrichmentMap, sourceProjectVersion } = opts; // Sort issues by component key so cumulative batches add issues from NEW // files each time. This prevents the CE's fuzzy issue tracker from matching @@ -30,9 +30,9 @@ export async function transferBatched(opts) { logger.info(`[${batchLabel}] Issues ${batch.startIndex + 1}-${batch.endIndex} | date=${batchDate}`); - const messages = buildProtobufMessages(batchData, sonarcloudConfig, sonarCloudProfiles, branchName, referenceBranchName, sonarCloudRepos, ruleEnrichmentMap, batchLabel); + const messages = buildProtobufMessages(batchData, sonarcloudConfig, sonarCloudProfiles, branchName, referenceBranchName, sonarCloudRepos, ruleEnrichmentMap, batchLabel, sourceProjectVersion); const encodedReport = await encodeMessages(messages, batchLabel); - lastCeTask = await uploadReport(encodedReport, sonarcloudConfig, sonarCloudClient, branchName, isMainBranch, true, batchLabel); + lastCeTask = await uploadReport(encodedReport, sonarcloudConfig, sonarCloudClient, branchName, isMainBranch, true, batchLabel, sourceProjectVersion); logger.info(`[${batchLabel}] CE task completed: ${lastCeTask.id}`); } diff --git a/src/pipelines/sq-2025/transfer-pipeline/helpers/transfer-branch/helpers/upload-report.js b/src/pipelines/sq-2025/transfer-pipeline/helpers/transfer-branch/helpers/upload-report.js index dbbd301..e432890 100644 --- a/src/pipelines/sq-2025/transfer-pipeline/helpers/transfer-branch/helpers/upload-report.js +++ b/src/pipelines/sq-2025/transfer-pipeline/helpers/transfer-branch/helpers/upload-report.js @@ -4,13 +4,13 @@ import logger from '../../../../../../shared/utils/logger.js'; // -------- Upload Report -------- /** Upload an encoded report to SonarCloud. */ -export async function uploadReport(encodedReport, scConfig, scClient, branchName, isMainBranch, wait, label) { +export async function uploadReport(encodedReport, scConfig, scClient, branchName, isMainBranch, wait, label, sourceProjectVersion) { logger.info(`[${label}] Uploading to SonarCloud...`); const uploader = new ReportUploader(scClient); const metadata = { projectKey: scConfig.projectKey, organization: scConfig.organization, - version: '1.0.0', + version: sourceProjectVersion || '1.0.0', ...(!isMainBranch && branchName ? { branchName } : {}), }; diff --git a/src/pipelines/sq-2025/transfer-pipeline/helpers/transfer-branch/index.js b/src/pipelines/sq-2025/transfer-pipeline/helpers/transfer-branch/index.js index 210f189..1854c6f 100644 --- a/src/pipelines/sq-2025/transfer-pipeline/helpers/transfer-branch/index.js +++ b/src/pipelines/sq-2025/transfer-pipeline/helpers/transfer-branch/index.js @@ -2,6 +2,7 @@ import { buildProtobufMessages, encodeMessages } from './helpers/build-and-encod import { uploadReport } from './helpers/upload-report.js'; import { transferBatched } from './helpers/transfer-batched.js'; import { shouldBatch, backdateChangesets } from '../../../../../shared/utils/batch-distributor.js'; +import { resolveSourceProjectVersion } from '../../../../../shared/utils/source-version/resolve-source-project-version.js'; // -------- Branch Transfer -------- @@ -9,25 +10,27 @@ import { shouldBatch, backdateChangesets } from '../../../../../shared/utils/bat export async function transferBranch(options) { const { extractedData, sonarcloudConfig, sonarCloudProfiles, branchName, - referenceBranchName, wait, sonarCloudClient, label, + referenceBranchName, wait, sonarCloudClient, sonarQubeClient, label, isMainBranch = false, sonarCloudRepos = new Set(), ruleEnrichmentMap = new Map(), } = options; + const sourceProjectVersion = await resolveSourceProjectVersion(sonarQubeClient, sonarQubeClient?.projectKey, isMainBranch ? null : branchName); + if (shouldBatch(extractedData)) { const ceTask = await transferBatched({ extractedData, sonarcloudConfig, sonarCloudProfiles, branchName, referenceBranchName, sonarCloudClient, label, isMainBranch, - sonarCloudRepos, ruleEnrichmentMap, + sonarCloudRepos, ruleEnrichmentMap, sourceProjectVersion, }); return { stats: computeBranchStats(extractedData), ceTask }; } backdateChangesets(extractedData); - const messages = buildProtobufMessages(extractedData, sonarcloudConfig, sonarCloudProfiles, branchName, referenceBranchName, sonarCloudRepos, ruleEnrichmentMap, label); + const messages = buildProtobufMessages(extractedData, sonarcloudConfig, sonarCloudProfiles, branchName, referenceBranchName, sonarCloudRepos, ruleEnrichmentMap, label, sourceProjectVersion); const encodedReport = await encodeMessages(messages, label); - const ceTask = await uploadReport(encodedReport, sonarcloudConfig, sonarCloudClient, branchName, isMainBranch, wait, label); + const ceTask = await uploadReport(encodedReport, sonarcloudConfig, sonarCloudClient, branchName, isMainBranch, wait, label, sourceProjectVersion); return { stats: computeBranchStats(extractedData), ceTask }; } diff --git a/src/pipelines/sq-2025/transfer-pipeline/helpers/transfer-main-branch/helpers/execute-main-branch-transfer.js b/src/pipelines/sq-2025/transfer-pipeline/helpers/transfer-main-branch/helpers/execute-main-branch-transfer.js index 70259b5..789bc50 100644 --- a/src/pipelines/sq-2025/transfer-pipeline/helpers/transfer-main-branch/helpers/execute-main-branch-transfer.js +++ b/src/pipelines/sq-2025/transfer-pipeline/helpers/transfer-main-branch/helpers/execute-main-branch-transfer.js @@ -6,7 +6,7 @@ import logger from '../../../../../../shared/utils/logger.js'; /** Execute the actual main branch transfer (build, encode, upload). */ export async function executeMainBranchTransfer(opts) { - const { journal, sonarCloudMainBranch, sonarCloudClient, extractedData, sonarcloudConfig, sonarCloudProfiles, wait, sonarCloudRepos, ruleEnrichmentMap } = opts; + const { journal, sonarCloudMainBranch, sonarCloudClient, sonarQubeClient, extractedData, sonarcloudConfig, sonarCloudProfiles, wait, sonarCloudRepos, ruleEnrichmentMap } = opts; if (journal) await journal.startBranch(sonarCloudMainBranch); @@ -23,7 +23,7 @@ export async function executeMainBranchTransfer(opts) { result = await transferBranch({ extractedData, sonarcloudConfig, sonarCloudProfiles, branchName: sonarCloudMainBranch, referenceBranchName: sonarCloudMainBranch, - wait, sonarCloudClient, label: 'main', isMainBranch: true, + wait, sonarCloudClient, sonarQubeClient, label: 'main', isMainBranch: true, sonarCloudRepos, ruleEnrichmentMap, }); } diff --git a/src/pipelines/sq-2025/transfer-pipeline/helpers/transfer-non-main-branches/helpers/transfer-one-branch/index.js b/src/pipelines/sq-2025/transfer-pipeline/helpers/transfer-non-main-branches/helpers/transfer-one-branch/index.js index eeb6135..faef4b9 100644 --- a/src/pipelines/sq-2025/transfer-pipeline/helpers/transfer-non-main-branches/helpers/transfer-one-branch/index.js +++ b/src/pipelines/sq-2025/transfer-pipeline/helpers/transfer-non-main-branches/helpers/transfer-one-branch/index.js @@ -9,7 +9,7 @@ import logger from '../../../../../../../shared/utils/logger.js'; export async function transferOneBranch(branch, opts) { const { shutdownCheck, isIncremental, stateTracker, journal, cache, extractor, extractedData, sonarcloudConfig, sonarCloudProfiles, - sonarCloudMainBranch, wait, sonarCloudClient, + sonarCloudMainBranch, wait, sonarCloudClient, sonarQubeClient, sonarCloudRepos, ruleEnrichmentMap } = opts; const branchName = branch.name; @@ -26,7 +26,7 @@ export async function transferOneBranch(branch, opts) { const branchResult = await transferBranch({ extractedData: branchData, sonarcloudConfig, sonarCloudProfiles, branchName, referenceBranchName: sonarCloudMainBranch, - wait, sonarCloudClient, label: branchName, + wait, sonarCloudClient, sonarQubeClient, label: branchName, sonarCloudRepos, ruleEnrichmentMap, }); diff --git a/src/pipelines/sq-9.9/protobuf/builder/helpers/build-metadata.js b/src/pipelines/sq-9.9/protobuf/builder/helpers/build-metadata.js index aad9792..f967b62 100644 --- a/src/pipelines/sq-9.9/protobuf/builder/helpers/build-metadata.js +++ b/src/pipelines/sq-9.9/protobuf/builder/helpers/build-metadata.js @@ -18,7 +18,7 @@ export function buildMetadata(ctx) { qprofilesPerLanguage: ctx.buildQProfiles(), branchName, branchType: 1, referenceBranchName: referenceBranch, scmRevisionId: ctx.data.metadata.scmRevisionId || randomBytes(20).toString('hex'), - projectVersion: '1.0.0', + projectVersion: ctx.sourceProjectVersion || '1.0.0', analyzedIndexedFileCountPerType: ctx.buildFileCountsByType(), }; diff --git a/src/pipelines/sq-9.9/protobuf/builder/index.js b/src/pipelines/sq-9.9/protobuf/builder/index.js index 0a2a952..5a1e80c 100644 --- a/src/pipelines/sq-9.9/protobuf/builder/index.js +++ b/src/pipelines/sq-9.9/protobuf/builder/index.js @@ -22,6 +22,7 @@ export function createProtobufBuilder(extractedData, scConfig = {}, scProfiles = referenceBranchName: options.referenceBranchName || null, sonarCloudRepos: options.sonarCloudRepos || new Set(), ruleEnrichmentMap: options.ruleEnrichmentMap || new Map(), + sourceProjectVersion: options.sourceProjectVersion || null, getComponentRef(key) { if (!this.componentRefMap.has(key)) this.componentRefMap.set(key, this.nextRef++); return this.componentRefMap.get(key); }, mapSeverity, buildQProfiles() { return buildQProfiles(this); }, buildFileCountsByType() { return buildFileCountsByType(this); }, buildMetadata() { return buildMetadata(this); }, buildComponents() { return buildComponents(this); }, diff --git a/src/pipelines/sq-9.9/transfer-pipeline/helpers/build-protobuf-messages.js b/src/pipelines/sq-9.9/transfer-pipeline/helpers/build-protobuf-messages.js index 403f114..7d0ff50 100644 --- a/src/pipelines/sq-9.9/transfer-pipeline/helpers/build-protobuf-messages.js +++ b/src/pipelines/sq-9.9/transfer-pipeline/helpers/build-protobuf-messages.js @@ -3,10 +3,10 @@ import logger from '../../../../shared/utils/logger.js'; // -------- Build Protobuf Messages -------- -export function buildProtobufMessages(extractedData, sonarcloudConfig, sonarCloudProfiles, branchName, referenceBranchName, sonarCloudRepos, ruleEnrichmentMap, label) { +export function buildProtobufMessages(extractedData, sonarcloudConfig, sonarCloudProfiles, branchName, referenceBranchName, sonarCloudRepos, ruleEnrichmentMap, label, sourceProjectVersion) { logger.info(`[${label}] Building protobuf messages...`); const builder = new ProtobufBuilder(extractedData, sonarcloudConfig, sonarCloudProfiles, { - sonarCloudBranchName: branchName, referenceBranchName, sonarCloudRepos, ruleEnrichmentMap, + sonarCloudBranchName: branchName, referenceBranchName, sonarCloudRepos, ruleEnrichmentMap, sourceProjectVersion, }); return builder.buildAll(); } diff --git a/src/pipelines/sq-9.9/transfer-pipeline/helpers/run-transfer-phases.js b/src/pipelines/sq-9.9/transfer-pipeline/helpers/run-transfer-phases.js index 8b5b1df..5492baf 100644 --- a/src/pipelines/sq-9.9/transfer-pipeline/helpers/run-transfer-phases.js +++ b/src/pipelines/sq-9.9/transfer-pipeline/helpers/run-transfer-phases.js @@ -22,7 +22,7 @@ export async function runTransferPhases({ sonarQubeClient, sonarCloudClient, son const { sonarCloudProfiles, sonarCloudMainBranch, sonarCloudRepos, ruleEnrichmentMap } = await fetchCloudContext(sonarCloudClient, prebuiltEnrichmentMap); checkShutdown(shutdownCheck); - const mainBranchResult = await transferMainBranch({ journal, sonarCloudMainBranch, sonarCloudClient, extractedData, sonarcloudConfig, sonarCloudProfiles, wait, sonarCloudRepos, ruleEnrichmentMap }); + const mainBranchResult = await transferMainBranch({ journal, sonarCloudMainBranch, sonarCloudClient, sonarQubeClient, extractedData, sonarcloudConfig, sonarCloudProfiles, wait, sonarCloudRepos, ruleEnrichmentMap }); const aggregatedStats = { issuesTransferred: mainBranchResult.stats.issuesTransferred || 0, @@ -37,7 +37,7 @@ export async function runTransferPhases({ sonarQubeClient, sonarCloudClient, son checkShutdown(shutdownCheck); if (syncAllBranches) { - const branchResults = await transferNonMainBranches({ extractedData, excludeBranches, includeBranches, sonarCloudMainBranch, mainBranchCeTaskId: mainBranchResult.ceTask?.id, wait, sonarCloudClient, extractor, journal, cache, shutdownCheck, sonarcloudConfig, sonarCloudProfiles, sonarCloudRepos, ruleEnrichmentMap, isIncremental, stateTracker, performanceConfig }); + const branchResults = await transferNonMainBranches({ extractedData, excludeBranches, includeBranches, sonarCloudMainBranch, mainBranchCeTaskId: mainBranchResult.ceTask?.id, wait, sonarCloudClient, sonarQubeClient, extractor, journal, cache, shutdownCheck, sonarcloudConfig, sonarCloudProfiles, sonarCloudRepos, ruleEnrichmentMap, isIncremental, stateTracker, performanceConfig }); aggregateBranchResults(branchResults, aggregatedStats); } diff --git a/src/pipelines/sq-9.9/transfer-pipeline/helpers/transfer-branch-batched.js b/src/pipelines/sq-9.9/transfer-pipeline/helpers/transfer-branch-batched.js index a90d9ea..63ae624 100644 --- a/src/pipelines/sq-9.9/transfer-pipeline/helpers/transfer-branch-batched.js +++ b/src/pipelines/sq-9.9/transfer-pipeline/helpers/transfer-branch-batched.js @@ -11,7 +11,7 @@ import logger from '../../../../shared/utils/logger.js'; export async function transferBranchBatched(opts) { const { extractedData, sonarcloudConfig, sonarCloudProfiles, branchName, referenceBranchName, sonarCloudClient, label, isMainBranch, - sonarCloudRepos, ruleEnrichmentMap } = opts; + sonarCloudRepos, ruleEnrichmentMap, sourceProjectVersion } = opts; extractedData.issues.sort((a, b) => (a.component || '').localeCompare(b.component || '')); @@ -28,9 +28,9 @@ export async function transferBranchBatched(opts) { logger.info(`[${batchLabel}] Issues ${batch.startIndex + 1}-${batch.endIndex} | date=${batchDate}`); - const messages = buildProtobufMessages(batchData, sonarcloudConfig, sonarCloudProfiles, branchName, referenceBranchName, sonarCloudRepos, ruleEnrichmentMap, batchLabel); + const messages = buildProtobufMessages(batchData, sonarcloudConfig, sonarCloudProfiles, branchName, referenceBranchName, sonarCloudRepos, ruleEnrichmentMap, batchLabel, sourceProjectVersion); const encodedReport = await encodeReport(messages, batchLabel); - lastCeTask = await uploadReport(encodedReport, sonarcloudConfig, sonarCloudClient, branchName, isMainBranch, true, batchLabel); + lastCeTask = await uploadReport(encodedReport, sonarcloudConfig, sonarCloudClient, branchName, isMainBranch, true, batchLabel, sourceProjectVersion); logger.info(`[${batchLabel}] CE task completed: ${lastCeTask.id}`); } diff --git a/src/pipelines/sq-9.9/transfer-pipeline/helpers/transfer-branch.js b/src/pipelines/sq-9.9/transfer-pipeline/helpers/transfer-branch.js index dce9aee..83d9903 100644 --- a/src/pipelines/sq-9.9/transfer-pipeline/helpers/transfer-branch.js +++ b/src/pipelines/sq-9.9/transfer-pipeline/helpers/transfer-branch.js @@ -4,23 +4,26 @@ import { uploadReport } from './upload-report.js'; import { buildBranchResult } from './build-branch-result.js'; import { transferBranchBatched } from './transfer-branch-batched.js'; import { shouldBatch, backdateChangesets } from '../../../../shared/utils/batch-distributor.js'; +import { resolveSourceProjectVersion } from '../../../../shared/utils/source-version/resolve-source-project-version.js'; // -------- Single Branch Transfer (build, encode, upload) -------- -export async function transferBranch({ extractedData, sonarcloudConfig, sonarCloudProfiles, branchName, referenceBranchName, wait, sonarCloudClient, label, isMainBranch = false, sonarCloudRepos = new Set(), ruleEnrichmentMap = new Map() }) { +export async function transferBranch({ extractedData, sonarcloudConfig, sonarCloudProfiles, branchName, referenceBranchName, wait, sonarCloudClient, sonarQubeClient, label, isMainBranch = false, sonarCloudRepos = new Set(), ruleEnrichmentMap = new Map() }) { + const sourceProjectVersion = await resolveSourceProjectVersion(sonarQubeClient, sonarQubeClient?.projectKey, isMainBranch ? null : branchName); + if (shouldBatch(extractedData)) { const ceTask = await transferBranchBatched({ extractedData, sonarcloudConfig, sonarCloudProfiles, branchName, referenceBranchName, sonarCloudClient, label, isMainBranch, - sonarCloudRepos, ruleEnrichmentMap, + sonarCloudRepos, ruleEnrichmentMap, sourceProjectVersion, }); return buildBranchResult(extractedData, ceTask); } backdateChangesets(extractedData); - const messages = buildProtobufMessages(extractedData, sonarcloudConfig, sonarCloudProfiles, branchName, referenceBranchName, sonarCloudRepos, ruleEnrichmentMap, label); + const messages = buildProtobufMessages(extractedData, sonarcloudConfig, sonarCloudProfiles, branchName, referenceBranchName, sonarCloudRepos, ruleEnrichmentMap, label, sourceProjectVersion); const encodedReport = await encodeReport(messages, label); - const ceTask = await uploadReport(encodedReport, sonarcloudConfig, sonarCloudClient, branchName, isMainBranch, wait, label); + const ceTask = await uploadReport(encodedReport, sonarcloudConfig, sonarCloudClient, branchName, isMainBranch, wait, label, sourceProjectVersion); return buildBranchResult(extractedData, ceTask); } diff --git a/src/pipelines/sq-9.9/transfer-pipeline/helpers/transfer-main-branch.js b/src/pipelines/sq-9.9/transfer-pipeline/helpers/transfer-main-branch.js index cdb32b0..eead7af 100644 --- a/src/pipelines/sq-9.9/transfer-pipeline/helpers/transfer-main-branch.js +++ b/src/pipelines/sq-9.9/transfer-pipeline/helpers/transfer-main-branch.js @@ -4,7 +4,7 @@ import logger from '../../../../shared/utils/logger.js'; // -------- Transfer Main Branch -------- -export async function transferMainBranch({ journal, sonarCloudMainBranch, sonarCloudClient, extractedData, sonarcloudConfig, sonarCloudProfiles, wait, sonarCloudRepos, ruleEnrichmentMap }) { +export async function transferMainBranch({ journal, sonarCloudMainBranch, sonarCloudClient, sonarQubeClient, extractedData, sonarcloudConfig, sonarCloudProfiles, wait, sonarCloudRepos, ruleEnrichmentMap }) { const mainBranchCompleted = journal?.getBranchStatus(sonarCloudMainBranch) === 'completed'; if (mainBranchCompleted) { @@ -36,7 +36,7 @@ export async function transferMainBranch({ journal, sonarCloudMainBranch, sonarC const result = await transferBranch({ extractedData, sonarcloudConfig, sonarCloudProfiles, branchName: sonarCloudMainBranch, referenceBranchName: sonarCloudMainBranch, - wait, sonarCloudClient, label: 'main', isMainBranch: true, sonarCloudRepos, ruleEnrichmentMap, + wait, sonarCloudClient, sonarQubeClient, label: 'main', isMainBranch: true, sonarCloudRepos, ruleEnrichmentMap, }); if (journal) { diff --git a/src/pipelines/sq-9.9/transfer-pipeline/helpers/transfer-non-main-branches.js b/src/pipelines/sq-9.9/transfer-pipeline/helpers/transfer-non-main-branches.js index bb23976..8c38705 100644 --- a/src/pipelines/sq-9.9/transfer-pipeline/helpers/transfer-non-main-branches.js +++ b/src/pipelines/sq-9.9/transfer-pipeline/helpers/transfer-non-main-branches.js @@ -4,7 +4,7 @@ import logger from '../../../../shared/utils/logger.js'; // -------- Transfer Non-Main Branches -------- -export async function transferNonMainBranches({ extractedData, excludeBranches, includeBranches, sonarCloudMainBranch, mainBranchCeTaskId, wait, sonarCloudClient, extractor, journal, cache, shutdownCheck, sonarcloudConfig, sonarCloudProfiles, sonarCloudRepos, ruleEnrichmentMap, isIncremental, stateTracker, performanceConfig }) { +export async function transferNonMainBranches({ extractedData, excludeBranches, includeBranches, sonarCloudMainBranch, mainBranchCeTaskId, wait, sonarCloudClient, sonarQubeClient, extractor, journal, cache, shutdownCheck, sonarcloudConfig, sonarCloudProfiles, sonarCloudRepos, ruleEnrichmentMap, isIncremental, stateTracker, performanceConfig }) { const allBranches = extractedData.project.branches || []; const nonMainBranches = allBranches.filter(b => { if (b.isMain) return false; @@ -23,7 +23,7 @@ export async function transferNonMainBranches({ extractedData, excludeBranches, return mapConcurrent(nonMainBranches, (branch) => transferOneBranch({ branch, extractedData, extractor, journal, cache, shutdownCheck, sonarcloudConfig, - sonarCloudProfiles, sonarCloudMainBranch, wait, sonarCloudClient, sonarCloudRepos, + sonarCloudProfiles, sonarCloudMainBranch, wait, sonarCloudClient, sonarQubeClient, sonarCloudRepos, ruleEnrichmentMap, isIncremental, stateTracker, }), { concurrency: performanceConfig?.maxConcurrency || 4, settled: true }); } diff --git a/src/pipelines/sq-9.9/transfer-pipeline/helpers/transfer-one-branch.js b/src/pipelines/sq-9.9/transfer-pipeline/helpers/transfer-one-branch.js index 1c8794b..95ff31b 100644 --- a/src/pipelines/sq-9.9/transfer-pipeline/helpers/transfer-one-branch.js +++ b/src/pipelines/sq-9.9/transfer-pipeline/helpers/transfer-one-branch.js @@ -4,7 +4,7 @@ import logger from '../../../../shared/utils/logger.js'; // -------- Transfer a Single Non-Main Branch -------- -export async function transferOneBranch({ branch, extractedData, extractor, journal, cache, shutdownCheck, sonarcloudConfig, sonarCloudProfiles, sonarCloudMainBranch, wait, sonarCloudClient, sonarCloudRepos, ruleEnrichmentMap, isIncremental, stateTracker }) { +export async function transferOneBranch({ branch, extractedData, extractor, journal, cache, shutdownCheck, sonarcloudConfig, sonarCloudProfiles, sonarCloudMainBranch, wait, sonarCloudClient, sonarQubeClient, sonarCloudRepos, ruleEnrichmentMap, isIncremental, stateTracker }) { const branchName = branch.name; if (shutdownCheck()) return { skipped: true, branchName, reason: 'shutdown' }; @@ -27,7 +27,7 @@ export async function transferOneBranch({ branch, extractedData, extractor, jour const branchResult = await transferBranch({ extractedData: branchData, sonarcloudConfig, sonarCloudProfiles, branchName, - referenceBranchName: sonarCloudMainBranch, wait, sonarCloudClient, label: branchName, + referenceBranchName: sonarCloudMainBranch, wait, sonarCloudClient, sonarQubeClient, label: branchName, sonarCloudRepos, ruleEnrichmentMap, }); diff --git a/src/pipelines/sq-9.9/transfer-pipeline/helpers/upload-report.js b/src/pipelines/sq-9.9/transfer-pipeline/helpers/upload-report.js index 8f59c1b..c9283b0 100644 --- a/src/pipelines/sq-9.9/transfer-pipeline/helpers/upload-report.js +++ b/src/pipelines/sq-9.9/transfer-pipeline/helpers/upload-report.js @@ -3,13 +3,13 @@ import logger from '../../../../shared/utils/logger.js'; // -------- Upload Report to SonarCloud -------- -export async function uploadReport(encodedReport, sonarcloudConfig, sonarCloudClient, branchName, isMainBranch, wait, label) { +export async function uploadReport(encodedReport, sonarcloudConfig, sonarCloudClient, branchName, isMainBranch, wait, label, sourceProjectVersion) { logger.info(`[${label}] Uploading to SonarCloud...`); const uploader = new ReportUploader(sonarCloudClient); const metadata = { projectKey: sonarcloudConfig.projectKey, organization: sonarcloudConfig.organization, - version: '1.0.0', + version: sourceProjectVersion || '1.0.0', ...(!isMainBranch && branchName ? { branchName } : {}), }; diff --git a/src/shared/utils/source-version/resolve-source-project-version.js b/src/shared/utils/source-version/resolve-source-project-version.js new file mode 100644 index 0000000..0ff3762 --- /dev/null +++ b/src/shared/utils/source-version/resolve-source-project-version.js @@ -0,0 +1,33 @@ +import logger from '../logger.js'; + +/** + * Resolve the source SonarQube project version for a given branch. + * + * Calls `GET /api/navigation/component` on the source SQS to read the + * `version` of the project (or a specific branch). This lets the migration + * preserve real source versions like "3.20" instead of using the hardcoded + * "1.0.0" fallback when uploading to SonarCloud. + * + * @param {object} sqClient - SonarQube client (must expose `client.get`). + * @param {string} projectKey - Source project key. + * @param {string|null} [branchName] - Branch to resolve. Pass null/undefined for the main branch. + * @returns {Promise} The version string, or null when the setting + * is missing/blank, the branch is unknown, or the API call fails. + */ +export async function resolveSourceProjectVersion(sqClient, projectKey, branchName) { + if (!sqClient || !sqClient.client || !projectKey) return null; + + const params = { component: projectKey }; + if (branchName) params.branch = branchName; + + try { + const response = await sqClient.client.get('/api/navigation/component', { params }); + const version = response?.data?.version; + if (typeof version === 'string' && version.trim()) return version.trim(); + return null; + } catch (error) { + const branchLabel = branchName ? ` branch '${branchName}'` : ''; + logger.debug(`Failed to resolve source project version for '${projectKey}'${branchLabel}: ${error.message}`); + return null; + } +}