I have an NX Monorepo with multiple projects and am encountering an issue where all the previous coverage % goes back to zero on running the github workflow below.
Am running tests using jest to generate lcov files, Sonar is able to find the same lcov files but the analysis somewhat does not happen or fails without an error (On adding debug to sonar properties for each project; analysis, upload and execution is successful).
Using the workflow below the matrix triggers multiple jobs, with each app having its own parallel isolated job all the way to analysis.
- ALM used (GitHub)
- CI system used (Github)
- Languages of the repository - Typescript
- Steps to reproduce - running this work in an nx monorepo with Angular apps and jest for testing
- Projects have unique projectKey and projectName but they share the SONAR_TOKEN
name: CI Pipeline
on:
push:
branches:
- main
pull_request:
branches: [main]
types: [opened, synchronize, reopened]
# To cancel a currently running workflow from the same PR, branch or tag when a new workflow is triggered
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
check-app-affected:
name: Run Lint, Tests, Sonar analysis
runs-on: ubuntu-latest
strategy:
matrix:
app: [app1, app2, app3]
node-version: [20]
steps:
- uses: actions/checkout@v4
name: Checkout Code
if: ${{ github.event_name == 'pull_request' }}
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- uses: actions/checkout@v4
name: Checkout Main Branch
if: ${{ github.event_name == 'push' }}
with:
ref: main
fetch-depth: 0
- name: Derive SHAs for `nx affected` base and head SHAs for PR
id: derive-shas
run: |
if [ "${{ github.event_name }}" == "pull_request" ]; then
BASE=${{ github.event.pull_request.base.sha }}
HEAD=${{ github.event.pull_request.head.sha }}
else
git fetch origin main --prune
BASE=$(git rev-parse HEAD~1)
HEAD=$(git rev-parse HEAD)
fi
echo "NX_BASE=$BASE" >> $GITHUB_ENV
echo "NX_HEAD=$HEAD" >> $GITHUB_ENV
################### Determine the affected apps ##########################
- name: Determine the Affected Apps/Projects by Opened or Closed PR
id: determine-affected-apps
run: |
# Fetch the full history
git fetch --prune --unshallow || true
# Get the list of changed files
affected_files=$(git diff --name-only "${{ env.NX_BASE }}" "${{ env.NX_HEAD }}" | tr -d '\r')
# Initialize affected_apps variable
affected_apps=""
if echo "$affected_files" | grep -qE "^apps/(app1)/"; then
affected_apps="${affected_apps:+$affected_apps,}app1"
fi
if echo "$affected_files" | grep -qE "^apps/(app2)/"; then
affected_apps="${affected_apps:+$affected_apps,}app2"
fi
if echo "$affected_files" | grep -qE "^apps/(app3)/"; then
affected_apps="${affected_apps:+$affected_apps,}app3"
fi
echo "Affected apps: $affected_apps"
# Remove any trailing commas
affected_apps=$(echo "$affected_apps" | sed 's/,$//')
# Export affected_apps to the environment
echo "affected_apps=$affected_apps" >> $GITHUB_ENV
################### Determine if the workflow should continue by checking if the an app has been affected ##########################
- name: Determine if the workflow should continue by checking if the an app has been affected
id: check
run: |
echo "Check if ${{ matrix.app }}" has been affected
if [[ "${{ env.affected_apps }}" =~ (^|,)"${{ matrix.app }}"($|,) ]]; then
echo "skip=false" >> $GITHUB_ENV
echo "App is affected. Continuing workflow."
else
echo "skip=true" >> $GITHUB_ENV
echo "App is not affected. Exiting..."
exit 0
fi
- uses: actions/checkout@v4
name: Checkout Code
if: ${{ github.event_name == 'pull_request' }}
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- uses: actions/checkout@v4
name: Checkout Main Branch
if: ${{ github.event_name == 'push' }}
with:
ref: main
fetch-depth: 0
################### NPM SETUP ##########################
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
################### Linting and Testing ################
- name: Linting Code
if: ${{ github.event_name == 'pull_request' }}
run: |
echo "Run Lint for ${{ matrix.app }}"
if [[ "${{ env.affected_apps }}" == *"${{ matrix.app }}"* ]]; then
pnpm nx lint ${{ matrix.app }} || exit 1
else
echo "No linting needed for ${{ matrix.app }}"
fi
- name: Run Tests for Affected Apps / Libs
if: ${{ github.event_name == 'pull_request' || github.event_name == 'push' }}
run: |
echo "Running tests for ${{ matrix.app }}"
if [[ "${{ env.affected_apps }}" == *"${{ matrix.app }}"* ]]; then
pnpm nx test ${{ matrix.app }} --ci --code-coverage --coverageReporters=lcov --parallel --maxParallel=4 --runInBand --maxWorkers=4 || exit 1
else
echo "No tests for ${{ matrix.app }}"
fi
pnpm run replace-lcov-paths "${{ matrix.app }}"
# Debug to see if the lcov file generated matches what is produced when running tests locally
- name: Check Coverage Files
run: |
ls -lh coverage/apps/${{ matrix.app }} || echo "Coverage file not found!"
cat coverage/apps/${{ matrix.app }}/lcov.info || echo "Coverage report is empty!"
################### SonarCloud Analysis ################
- name: Determine if affected for SonarCloud Scan
id: sonar_check
run: |
if [[ "${{ env.affected_apps }}" == *"${{ matrix.app }}"* ]]; then
echo "Running SonarCloud scan for ${{ matrix.app }}"
echo "RUN_SONAR=true" >> $GITHUB_ENV
else
echo "Skipping SonarCloud scan for ${{ matrix.app }}"
echo "RUN_SONAR=false" >> $GITHUB_ENV
fi
- name: Run SonarCloud Scan Analysis
if: ${{ env.RUN_SONAR == 'true' }}
uses: SonarSource/sonarqube-scan-action@v4
with:
projectBaseDir: apps/${{ matrix.app }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
Thank you.