Monorepo - SonarCloud - Bitbucket - Quality Gate

Overview

Problem to configure SonarCloud with Monorepo (PNPM) on Bitbucket on Cloud

Status:

  • Our monorepo is setup at the Sonar cloud. Each sub-project can be seen on dashboard
  • We use PNPM with native orchestration with – filter
  • Then we have a single pipeline file to handle all projects
  • Javascript stack
  • Our company have a team plan / no enterprise plan Sonar Cloud

**Question: How to run the quality gate on monorepo ?
**
On non-monorepo, we used to use pipe to call the scanner and the quality gate and it works well.

Pipeline step - Sonar oldWay

    # need to provide an organisation and project keys
    - step: &sonarOldWay
        name: Analyze on SonarCloud
        size: x2
        caches:
          - pnpm
          - sonar
        script:
          - echo "Running SonarQube scan with SONAR_SCANNER_OPTS=${SONAR_SCANNER_OPTS}"
          - pipe: sonarsource/sonarcloud-scan:4.1.0
            variables:
              SONAR_SCANNER_OPTS: ${SONAR_SCANNER_OPTS}
              SONAR_TOKEN: ${SONAR_TOKEN}
              EXTRA_ARGS: -Dsonar.sources=\"src\" -Dsonar.tests=\"tests/unit\" -Dsonar.exclusions=\"**/tests/*.js,**/src/assets/images/*,**/src/assets/fonts/*\,**/src/main.ts" -Dsonar.language=\"js\" -Dsonar.javascript.lcov.reportPaths=\"./coverage/lcov.info\" -Dsonar.testExecutionReportPaths=\"./test-report.xml\"
          - pipe: sonarsource/sonarcloud-quality-gate:0.2.0
            variables:
              SONAR_TOKEN: ${SONAR_TOKEN}

This way above, with a monorepo, seems not working as it takes to provides an organization key and a project key and moreover, we do not want to run the scan and the quality gate on projects and libraries that are not touched/changed. Consequenlty SonarCloud pipe does not seen suitable for monorepo ? Moreover I did not find how to analyze in one shot all the monorepo as a workaround….

Consequenly and however, we got success in runner the scanner ‘manually’ based on the affected artefacts by using a javascript scripts and the npm artefact sonar/scan.

#!/usr/bin/env node

import fs from "fs";
import path from "path";
import { createRequire } from 'module';
import { getProjectInfo } from "./utils-app.js"

const require = createRequire(import.meta.url);

const { scan }  = require('@sonar/scan');

const projectName = process.argv[2];
if (!projectName) {
  console.error("Error - Please provide an application name.");
  process.exit(1);
}

const project = getProjectInfo(projectName);
if (!project) {
  console.error("Error - The project %s is not recognized.", projectName);
  process.exit(1);
}

if (!process.env.SONAR_TOKEN) {
  console.error("Error: SONAR_TOKEN must be set in the environment.");
  process.exit(1);
}

console.log("Analyzing project %s at path %s...", projectName , project.path);

const sonarConfig = {
  token: process.env.SONAR_TOKEN,
  options: {
    "sonar.projectKey": project.sonarKey,
    "sonar.organization": "ubeker",
    "sonar.sources": "src",
    "sonar.tests": "tests/unit",
    "sonar.exclusions": "**/tests/*.js,**/src/assets/images/*,**/src/assets/fonts/*,**/src/main.ts,./dist/**,**/node_modules/**,**/.pnpm/**",
    "sonar.language": "js",
    "sonar.typescript.lcov.reportPaths": "coverage/lcov.info",
    "sonar.testExecutionReportPaths":  "test-report.xml",
  },
};

scan(sonarConfig, (err) => {
  if (err) {
    console.error(`Sonar scan failed for project ${projectName}:\n${err.stack}`);
    process.exit(1);
  } else {
    console.log("Analyze successful for %s.", projectName);
  }
});

New pipeline step:

    - step: &sonarCloudAnalyze
        name: Sonar affectedd artefacts
        size: 2x
        artifacts:
          download: true
        caches:
          - pnpm
          - sonar
        script:
          - export PATH=./node_modules/.bin:$PATH
          - export PNPM_STORE_PATH="$HOME/.pnpm-store"
          - echo "Installing Pnpm at global scope..."
          - npm install -g pnpm@latest-10
          - pnpm config set store-dir "$PNPM_STORE_PATH"
          - pnpm install
          - if [ "$BITBUCKET_BRANCH" = "main" ]; then BASE="...[HEAD~1]"; else BASE="...[origin/main]"; fi
          - echo "Using base commit $BASE"
          - >-
            pnpm --filter "$BASE"
            --changed-files-ignore-pattern "**/*.stories.ts"
            --changed-files-ignore-pattern "**/*.md"
            --changed-files-ignore-pattern "./bitbucket-pipelines.yml"
            run sonar

and in each package.json of projects and libraries we have a sonar script:

 "scripts": {
    "sonar": "node ../../scripts/app/sonar-analyze-app.js ubeker-spa",
  },

This works wery well. But when it is time to run the gate, it seems we cannot use the pipe sonarsource/sonarcloud-quality-gate:0.2.0 as it expect some input file sonarcloud-scan.log which is not generated by the manual way obviously (but by the pipe sonarsource/sonarcloud-scan:4.1.0).

We did not find any npm artefact for the quality gate that allows us to use it and then we are stuck at this stage.

So is there a way, a config, to make the quality gate working on monorepo ? on affected artefact ?

Hi,

Welcome to the community!

You’re probably looking for the sonar.qualitygate.wait parameter.

 
HTH,
Ann

Thank you for your answer unless I missed something ? So, not sure as the first issue is to be able to run the scan on monorepo based on affected projects only. We are using the PNPM native ochestration We have 4 apps, 1 source library and 2 built libraries. Then we have a total of 7 projects to scan…. . Sonar Scan requests a project Id on monorepo. Does Sonar Scan supports the monorepo without hard coding all calls in a pipeline?

Then we use sonar/scan artefact to recreate a Sonar scan.log file to aggregate output needs by the gate pipe, like this:

+ cat "$LOG_PATH"

Project: packages-spa
INFO: ANALYSIS SUCCESSFUL, you can browse https://sonarcloud.io/dashboard?id=ubeker-packages&branch=feature%2FUBKR-19044-moonrepo-sonar-gate&resolved=false
INFO: More about the report processing at https://sonarcloud.io/api/ce/task?id=AZilqfWnX-prcD7e4uTeU

Project: search-spa
INFO: ANALYSIS SUCCESSFUL, you can browse https://sonarcloud.io/dashboard?id=ubeker-search&branch=feature%2FUBKR-19044-moonrepo-sonar-gate&resolved=false
INFO: More about the report processing at https://sonarcloud.io/api/ce/task?id=AZilqlw2DEti_YiLV94SZ


But the Quatilty gate seems to process only a single project, we did the test and only the first project is always performed. Then we studied the source code for Scan and Gate of the pipes and we simulate to Sonar Gate behavior in NodeJS (calling the URLs to get the quality) and we process all URLs from the sonar scan log. It seems working.

So our concern is: Does the Sonar Pipe can support a Monorepo based on affected projects notion? If yes, could you please provide a link on the doc? Hard-coding all SonarScan in pipeline will take too long to run and will consume a certain amoung of resources too. We have big projects and it takes 10mn to run a Sonar Scan for this project…so..7 projects….

Hi,

Okay, well, then why do you have everything running through the same file/pipeline? Why not have a pipeline file per project that’s executed based on changes in that project and that runs analysis on that project?

Uhm, no. You decide the scope of the analysis, and enforce it by setting a sonar.sources value appropriate to your project. And you provide the sonar.projectKey value for that project, not for the entire monorepo.

Now I’m confused. You started by saying you only wanted to process a single project.

Code is analyzed based on the parameters you feed to the scanner. The docs may help.

 
Ann

Bitbucket is very limited and we can only have one pipeline per project and the entire monorepo is considered as a single project from the point of view of Bitbucket even if we have 7 projects inside.

Moreover, the PNPM call us then it means we do not have any control about which projects needs to be scanned. It is based on native pnpm orchestration. It is totally dynamic. This is the same if we use Nx or Turborepo with the “affected” notion.

Recently we read that BitBucket has now child-pipeline but actually we do not explore that path yet and we do not know how it is flexible and how it will fit with a monorepo orchestration. Bitbucket does not have the flexibility of other CI/CD tools…

As shown, the step &sonarCloudAnalyze use the native PNPM ochestration and each projects inside the monorepo have a package.json with the necessary entry for PNPM call: "sonar": "node ../../scripts/app/sonar-analyze-app.js ubeker-spa" . So PNPM calls sonar entry in each involved project and it will execute any piece of code provided. Actually for sonar it is the script sonar-analyze-app.js and this script receives as parameter the name of the application. Then we map the name with the projectKey and we call the Sonar Scan. See the above "sonar.projectKey": project.sonarKey and the projectKey is calculated from const project = getProjectInfo(projectName);

const projects = {
  "hotels-spa": { path: "apps/hotels-spa", sonarKey: "ubeker_moonrepo-hotels" },
  "ubeker-spa": { path: "apps/ubeker-spa", sonarKey: "ubeker_moonrepo-ubeker" },
  "promotions-spa": { path: "apps/promotions-spa", sonarKey: "ubeker_moonrepo-promo" },
  "search-spa": { path: "apps/search-spa", sonarKey: "ubeker_moonrepo_search" },
  "vacv-core": { path: "libs/vacv-core", sonarKey: "ubeker_moonrepo-core" },
  "vacv-fetcher": { path: "packages/vacv-fetcher", sonarKey: "ubeker_moonrepo-fetcher" },
  "vacv-ui": { path: "packages/vacv-ui", sonarKey: "ubeker_moonrepo-ui" },
};

export function getProjectInfo(projectName) {
  if (!projectName) {
     return null;
  }
  return Object.hasOwn(projects, projectName) ? projects[projectName] : null;
}

We read the doc and the doc is based on Sonar Pipes but we do the tests and unless to hard coded in CI/CD all projects, it does not fit for the dynamic aspect of the ochestration, whatever it is.

And actually, the monorepo does not have a project key by itself (unless my mistake) under SonarCloud (probably with the Enterprise version with Portfolio concept ?) and then it also mean we cannot even use Sonar Pipes for the monorepo. If I search my monorepo on Sonar Cloud, I can only see the sub-projects and libraries from the monorepo not the monorepo itself. Even if we can, running the scan and the gate for untouched projects will be overkill…$..on Cloud.

To make a summary, PNPM ochestration determine which project is affected by the changes made by Devs, and it calls the entry point of the project itself. The project on the call can run any piece of code. So, it is totally dymanic. Then it is why using the sonar/scan package do the job for us, the given script shows how it is easy the call Sonar Cloud Scan. We recreated the Sonar Scan log but this log has more than one entry. The number of entries depend on the affected projects notion. Much less easy when it is time to call the gate as there is no sonar/gate package on NPM (unless my mistake).

It will be nice to provide a such packages on NPM repo, and this packages should be flexible enough to process any entry from the Sonar scan log or be able to retrieve the data from the projectKey. So, something like this….

Hope it helps to understand the case.

Hi,

I’m not entirely following everything, but it sounds like what’s affected by the changes will automatically be re-run. Thus which projects are fired on any given execution are out of your (and definitely out of my) control.

Thus the only thing I can advise is that you retrieve the Quality Gate status after each analysis. There is no “everything I just ran” Quality Gate status. And by running multiple analyses in succession, you’re losing (dropping) the ability to get the status for the earlier ones. Thus, you’ll need to slow it down, and

  • run analysis
  • get status
  • run analysis
  • get status
  • &etc.

 
HTH,
Ann

Ok. Thank you for your advice. In our design we can do that as we intercept the output from the scanner to cumulate the URLs needs for the gate. As it is the URL, we imagine that the information is always accurate. Even one day after the information given by the URL is the same. That should be ok but it case of problem, we will review the point.

{"task":{"id":"AZikIlIP5ttDkmoLs5","type":"REPORT","componentId":"AZif5AwPkZoEk7TQfA","componentKey":"ubeker_moonrepo-packages","componentName":"ubeker-spa","componentQualifier":"TRK","analysisId":"AZikI1jbE3fN8w40rn","status":"SUCCESS","submittedAt":"2025-08-13T15:52:49+0000","submitterLogin":"me@bitbucket","startedAt":"2025-08-13T15:52:49+0000","executedAt":"2025-08-13T15:52:51+0000","executionTimeMs":1930,"logs":false,"hasScannerContext":true,"com.ubeker":"ubeker","branch":"feature/UBKR-19044-moonrepo-sonar-gate","branchType":"SHORT","warningCount":0,"warnings":[]}}