Skip to content

AI Prompt: Forma3D.Connect — SonarCloud Integration

Purpose: Integrate SonarCloud Team into the Azure DevOps CI/CD pipeline for continuous code quality analysis with PR decoration, quality gates, and coverage reporting
Estimated Effort: 1–2 hours
Prerequisites: Human has completed the manual SonarCloud setup steps listed in Section: Human Setup Required
Output: sonar-project.properties, pipeline changes, PR-level quality gates, coverage integration
Research: sonarqube-code-quality-research.md
Status:DONE


🎯 Mission

Integrate SonarCloud Team into the Forma3D.Connect CI/CD pipeline so that every push to main and every pull request is analyzed for code quality, bugs, vulnerabilities, code smells, duplications, and test coverage. Results appear as inline PR comments in Azure DevOps and quality gates block merges when thresholds are violated.

What this delivers:

  1. sonar-project.properties at the repository root — configures which code is analyzed and which is excluded
  2. CodeQuality job in the ValidateAndTest stage — runs SonarCloud analysis on both main and PR branches
  3. PR decoration — SonarCloud posts quality gate status and issue summaries directly on Azure DevOps pull requests
  4. Coverage integration — test coverage from Vitest/Jest is uploaded to SonarCloud for display alongside analysis results
  5. Quality gate — configurable thresholds that block merges when new code introduces bugs, vulnerabilities, or excessive duplication

Why this matters:

The codebase has ESLint for linting and Aikido for security scanning, but lacks cross-cutting code quality metrics: duplicated code detection, cognitive complexity scoring, technical debt quantification, and historical trend tracking. SonarCloud fills this gap with zero infrastructure overhead.

Critical constraints:

  • SonarCloud must analyze production code only — test files, mocks, fixtures, acceptance tests, generated code, and config files must be excluded
  • The libs/testing library must be fully excluded — it contains only test fixtures
  • The apps/acceptance-tests project must be fully excluded — it contains only Playwright/BDD tests
  • Coverage data flows from the existing UnitTests job — the CodeQuality job must wait for it
  • No changes to the existing Lint, TypeCheck, or UnitTests jobs
  • No changes to the Build, Deploy, or Acceptance Test stages
  • The pipeline must continue working on both main pushes and PR triggers
  • All SonarCloud tokens/secrets must be stored as Azure DevOps secret variables — never hardcoded

⚠️ Human Setup Required (Before You Start)

The following steps must be completed by a human before the AI begins implementation. These involve account creation, billing, and secret provisioning that cannot be automated.

Checklist

# Action Where Output Needed
1 Sign up for SonarCloud sonarcloud.io — sign in with the Azure DevOps account SonarCloud account linked to Azure DevOps org
2 Create SonarCloud organization SonarCloud > Import Azure DevOps organization Organization key (e.g., forma3d)
3 Create a project SonarCloud > Create project manually or import from Azure DevOps Project key (e.g., forma3d_forma3d-connect)
4 Select the Team plan SonarCloud > Organization > Billing > Upgrade to Team ($32/month) Active Team subscription
5 Generate an analysis token SonarCloud > My Account > Security > Generate token (type: Project Analysis) Token string (starts with sqp_)
6 Install the Azure DevOps extension Visual Studio Marketplace > Install into the Azure DevOps organization Extension installed
7 Create a SonarCloud service connection Azure DevOps > Project Settings > Service connections > New > SonarCloud — enter the token from step 5 Service connection named SonarCloud
8 Enable Azure DevOps integration in SonarCloud SonarCloud > Project > Administration > DevOps Platform Integration > Azure DevOps — link the repository PR decoration enabled

Values the AI Needs

Once the human has completed the checklist above, provide the following values to the AI:

Value Example Where It Goes
SONARCLOUD_ORGANIZATION forma3d sonar-project.properties
SONARCLOUD_PROJECT_KEY forma3d_forma3d-connect sonar-project.properties
SERVICE_CONNECTION_NAME SonarCloud azure-pipelines.yml (SonarCloudPrepare task)

The analysis token itself is stored in the service connection — the AI does not need it directly.


📌 Context (Current State)

Pipeline Structure (ValidateAndTest Stage)

The ValidateAndTest stage in azure-pipelines.yml currently has 3 parallel jobs:

ValidateAndTest
├── Lint              (MS-hosted: ubuntu-latest)
├── TypeCheck         (Self-hosted: DO-Build-Agents)
└── UnitTests         (Self-hosted: DO-Build-Agents)
    ├── Runs tests with --coverage
    ├── Merges coverage → coverage/merged-cobertura.xml
    └── Publishes via PublishCodeCoverageResults@2

Source Layout

Production source directories (to be analyzed):

Path Project
apps/web/src React 19 frontend
apps/gateway/src API gateway (NestJS)
apps/order-service/src Order service (NestJS)
apps/print-service/src Print service (NestJS)
apps/shipping-service/src Shipping service (NestJS)
apps/gridflock-service/src GridFlock service (NestJS)
libs/domain/src Shared domain logic
libs/domain-contracts/src Cross-service interfaces
libs/api-client/src Typed API client
libs/config/src Shared configuration
libs/utils/src Generic utilities
libs/observability/src Observability library
libs/service-common/src Service common library
libs/gridflock-core/src GridFlock core library

Directories to EXCLUDE from analysis:

Path Reason
apps/acceptance-tests Playwright/BDD tests only — no production code
libs/testing Test fixtures and helpers only — no production code
**/node_modules/** Dependencies
**/dist/** Build output
**/coverage/** Test coverage output
**/*.spec.ts / **/*.spec.tsx Unit test files
**/*.test.ts / **/*.test.tsx Unit test files
**/__mocks__/** Mock files
**/*.stories.tsx Storybook stories (if any)
**/*.d.ts TypeScript declaration files
**/migrations/** Prisma migrations (auto-generated SQL)
**/generated/** Generated code
prisma/** Prisma schema and seeds
deployment/** Deployment configuration
docs/** Documentation
agentic-team/** AI agent scripts
scripts/** Shell scripts

📋 Step-by-Step Implementation

Step 1: Create sonar-project.properties (Repository Root)

Create the file sonar-project.properties at the repository root with the following content. Replace SONARCLOUD_ORGANIZATION and SONARCLOUD_PROJECT_KEY with the values provided by the human.

sonar.organization=SONARCLOUD_ORGANIZATION
sonar.projectKey=SONARCLOUD_PROJECT_KEY
sonar.projectName=Forma 3D Connect

# ─────────────────────────────────────────────────────────────
# Source directories (production code only)
# ─────────────────────────────────────────────────────────────
sonar.sources=\
  apps/web/src,\
  apps/gateway/src,\
  apps/order-service/src,\
  apps/print-service/src,\
  apps/shipping-service/src,\
  apps/gridflock-service/src,\
  libs/domain/src,\
  libs/domain-contracts/src,\
  libs/api-client/src,\
  libs/config/src,\
  libs/utils/src,\
  libs/observability/src,\
  libs/service-common/src,\
  libs/gridflock-core/src

# ─────────────────────────────────────────────────────────────
# Test directories (for coverage mapping, not billed as LoC)
# ─────────────────────────────────────────────────────────────
sonar.tests=\
  apps/web/src,\
  apps/gateway/src,\
  apps/order-service/src,\
  apps/print-service/src,\
  apps/shipping-service/src,\
  apps/gridflock-service/src,\
  libs/domain/src,\
  libs/domain-contracts/src,\
  libs/api-client/src,\
  libs/config/src,\
  libs/utils/src,\
  libs/observability/src,\
  libs/service-common/src,\
  libs/gridflock-core/src

sonar.test.inclusions=\
  **/*.spec.ts,\
  **/*.spec.tsx,\
  **/*.test.ts,\
  **/*.test.tsx

# ─────────────────────────────────────────────────────────────
# Exclusions (keep analysis focused on production code)
# ─────────────────────────────────────────────────────────────
sonar.exclusions=\
  **/node_modules/**,\
  **/dist/**,\
  **/coverage/**,\
  **/*.spec.ts,\
  **/*.spec.tsx,\
  **/*.test.ts,\
  **/*.test.tsx,\
  **/__mocks__/**,\
  **/*.stories.tsx,\
  **/*.d.ts,\
  **/migrations/**,\
  **/generated/**,\
  apps/acceptance-tests/**,\
  libs/testing/**,\
  prisma/**,\
  deployment/**,\
  docs/**,\
  agentic-team/**,\
  scripts/**

# ─────────────────────────────────────────────────────────────
# Coverage reports (lcov format from Vitest and Jest)
# ─────────────────────────────────────────────────────────────
sonar.typescript.lcov.reportPaths=coverage/**/lcov.info
sonar.javascript.lcov.reportPaths=coverage/**/lcov.info

# ─────────────────────────────────────────────────────────────
# Encoding
# ─────────────────────────────────────────────────────────────
sonar.sourceEncoding=UTF-8

Important notes:

  • Do NOT include sonar.host.url — the SonarCloud extension handles this automatically
  • Do NOT include sonar.token — authentication is managed by the Azure DevOps service connection
  • The sonar.sources and sonar.tests lists point to the same directories — sonar.test.inclusions filters which files within those directories are treated as tests
  • apps/acceptance-tests and libs/testing are in sonar.exclusions because they contain zero production code

Step 2: Ensure Test Coverage Generates lcov.info Files

SonarCloud reads lcov.info files for coverage data. Verify that Vitest and Jest are configured to produce them.

Check Vitest config (apps/web/vite.config.ts or vitest.config.ts):

Ensure the coverage reporter includes lcov:

coverage: {
  reporter: ['text', 'lcov', 'cobertura'],
}

Check Jest config (each backend app's jest.config.ts):

Ensure coverage reporters include lcov:

coverageReporters: ['text', 'lcov', 'cobertura'],

If lcov is not currently in the reporters list, add it. The cobertura reporter is already used for Azure DevOps coverage publishing — lcov is needed additionally for SonarCloud. Both can coexist.

Step 3: Publish Coverage as a Pipeline Artifact

The UnitTests job currently generates coverage but does not publish it as a downloadable artifact. The CodeQuality job (running on a different agent) needs access to the coverage files.

Add a PublishPipelineArtifact step at the end of the UnitTests job in azure-pipelines.yml, after the existing PublishCodeCoverageResults@2 step and before the cleanup-database.yml template:

          - task: PublishPipelineArtifact@1
            displayName: 'Publish Coverage Artifact'
            condition: succeededOrFailed()
            inputs:
              targetPath: '$(System.DefaultWorkingDirectory)/coverage'
              artifact: 'coverage-reports'
              publishLocation: 'pipeline'

Step 4: Add the CodeQuality Job to the ValidateAndTest Stage

Add a new job to the ValidateAndTest stage in azure-pipelines.yml. This job:

  • Depends on UnitTests (to get coverage data)
  • Runs on the MS-hosted pool (ubuntu-latest) — no self-hosted infra needed
  • Uses the SonarCloud Azure DevOps extension tasks

Insert this job after the existing UnitTests job, inside the ValidateAndTest stage:

      - job: CodeQuality
        displayName: 'Code Quality (SonarCloud)'
        dependsOn: UnitTests
        condition: succeeded()
        cancelTimeoutInMinutes: 0
        steps:
          - template: .azuredevops/pipelines/templates/install-dependencies.yml

          - task: DownloadPipelineArtifact@2
            displayName: 'Download Coverage Reports'
            inputs:
              source: 'current'
              artifact: 'coverage-reports'
              path: '$(System.DefaultWorkingDirectory)/coverage'

          - task: SonarCloudPrepare@4
            displayName: 'Prepare SonarCloud Analysis'
            inputs:
              SonarCloud: 'SERVICE_CONNECTION_NAME'
              organization: 'SONARCLOUD_ORGANIZATION'
              scannerMode: 'cli'
              configMode: 'file'

          - task: SonarCloudAnalyze@4
            displayName: 'Run SonarCloud Analysis'

          - task: SonarCloudPublish@4
            displayName: 'Publish Quality Gate Result'
            inputs:
              pollingTimeoutSec: '300'

Replace SERVICE_CONNECTION_NAME and SONARCLOUD_ORGANIZATION with the values provided by the human.

Key decisions in this configuration:

  • configMode: 'file' — uses the sonar-project.properties file from the repo root (all source/exclusion config is centralized there, not scattered in YAML)
  • dependsOn: UnitTests — ensures coverage data is available before analysis starts
  • Runs on ubuntu-latest (MS-hosted) — no load on the self-hosted build agent
  • pollingTimeoutSec: 300 — waits up to 5 minutes for the SonarCloud quality gate result

Step 5: Verify the Pipeline Trigger Configuration

The existing pipeline already triggers on both main pushes and PRs:

trigger:
  branches:
    include:
      - main

pr:
  branches:
    include:
      - main

This is sufficient. SonarCloud will:

  • Analyze main branch on push → populates the dashboard with overall code health
  • Analyze PR branches on PR creation/update → shows new issues in the PR, decorates with quality gate status

No changes to the trigger configuration are needed.

Step 6: Verify .gitignore Does Not Block Required Files

Ensure sonar-project.properties is not in .gitignore. This file must be committed to the repository.

Check that .gitignore does not contain patterns that would match it (e.g., *.properties). If it does, add an exception:

!sonar-project.properties

✅ Verification

After First Pipeline Run

  1. Check Azure DevOps pipeline — the CodeQuality job should appear in the ValidateAndTest stage alongside Lint, TypeCheck, and UnitTests
  2. Check SonarCloud dashboard — navigate to the project at https://sonarcloud.io/project/overview?id=SONARCLOUD_PROJECT_KEY — verify analysis completed and metrics are populated
  3. Check billable LoC — go to SonarCloud > Organization > Billing — verify the LoC count is ~80k (production code only, not tests)
  4. Check coverage — on the SonarCloud dashboard, verify that coverage percentage is displayed (not "No coverage data")

After First PR

  1. Create a test PR — make a trivial change on a branch and open a PR targeting main
  2. Verify PR decoration — the PR should show a SonarCloud quality gate check and inline comments if issues are found
  3. Verify quality gate — the PR should show "Quality Gate passed" or "Quality Gate failed" as a status check

Troubleshooting

Symptom Likely Cause Fix
CodeQuality job fails with "service connection not found" Service connection name mismatch Verify the name in Azure DevOps > Project Settings > Service connections matches the YAML
SonarCloud shows 0% coverage lcov.info files not generated or not found Check that Vitest/Jest reporters include lcov; verify the coverage artifact was published
Billable LoC much higher than ~80k Exclusions not working Check sonar-project.properties exclusions; verify libs/testing and apps/acceptance-tests are excluded
PR decoration not appearing Azure DevOps integration not enabled in SonarCloud Go to SonarCloud > Project > Administration > DevOps Platform Integration
Analysis times out Large codebase on first scan Increase pollingTimeoutSec to 600

🚫 Out of Scope

  • SonarQube self-hosted deployment (decided against — see research document)
  • Custom quality profiles or rule customization (do this after initial baseline)
  • SonarLint Connected Mode setup for IDE (separate task, can be done anytime)
  • Aikido integration changes (Aikido continues independently for SCA, secrets, SBOM)
  • Per-project SonarCloud scanning via Nx plugin (start with monorepo-level scan; revisit later if per-service quality gates are needed)