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:
sonar-project.propertiesat the repository root — configures which code is analyzed and which is excludedCodeQualityjob in theValidateAndTeststage — runs SonarCloud analysis on bothmainand PR branches- PR decoration — SonarCloud posts quality gate status and issue summaries directly on Azure DevOps pull requests
- Coverage integration — test coverage from Vitest/Jest is uploaded to SonarCloud for display alongside analysis results
- 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/testinglibrary must be fully excluded — it contains only test fixtures - The
apps/acceptance-testsproject must be fully excluded — it contains only Playwright/BDD tests - Coverage data flows from the existing
UnitTestsjob — theCodeQualityjob 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
mainpushes 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.sourcesandsonar.testslists point to the same directories —sonar.test.inclusionsfilters which files within those directories are treated as tests apps/acceptance-testsandlibs/testingare insonar.exclusionsbecause 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 thesonar-project.propertiesfile 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
mainbranch 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¶
- Check Azure DevOps pipeline — the
CodeQualityjob should appear in theValidateAndTeststage alongside Lint, TypeCheck, and UnitTests - Check SonarCloud dashboard — navigate to the project at
https://sonarcloud.io/project/overview?id=SONARCLOUD_PROJECT_KEY— verify analysis completed and metrics are populated - Check billable LoC — go to SonarCloud > Organization > Billing — verify the LoC count is ~80k (production code only, not tests)
- Check coverage — on the SonarCloud dashboard, verify that coverage percentage is displayed (not "No coverage data")
After First PR¶
- Create a test PR — make a trivial change on a branch and open a PR targeting
main - Verify PR decoration — the PR should show a SonarCloud quality gate check and inline comments if issues are found
- 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)