Loading...
Loading...
Playbook iterativo para llevar proyectos Node y TypeScript (NestJS + React en monorepo) a cumplir Quality Gates de SonarQube sin romper build ni pipelines. Usar cuando se necesite subir cobertura priorizando New Code, eliminar issues nuevos (Bugs, Vulnerabilities, Code Smells), revisar Security Hotspots y controlar duplicacion y deuda tecnica.
npx skill4agent add agustinalbonico/ai-customizations sonarqube-quality-gate-playbookSONAR_HOST_URLhttp://127.0.0.1:9000SONAR_TOKENSONAR_PROJECT_KEYSONAR_PROJECT_NAMESONAR_NEW_CODE_REFERENCE_BRANCHmainapps/backendapps/frontendpackages/*lcov.infosonar-project.propertiesTO_REVIEWpriorityScore = (isNewCode*100) + (isBugOrVuln*80) + (isHotspotToReview*70) + severityWeight + (criticalPath*20) + (coverageGap*10) - (estimatedEffort*5)severityWeight40302010SF:\\lcov.info\\packages/utilsSF:lcov.infosonar-scannerGet-Content "apps\\backend\\coverage\\lcov.info" | Select-String "^SF:" | Select-Object -First 10
Get-Content "apps\\frontend\\coverage\\lcov.info" | Select-String "^SF:" | Select-Object -First 10SF:src\\app.ts/lcov.infosonar-scannerscripts/fix-lcov-paths.jsconst fs = require('node:fs');
const reports = [
'apps/backend/coverage/lcov.info',
'apps/frontend/coverage/lcov.info',
'packages/utils/coverage/lcov.info'
];
for (const reportPath of reports) {
if (!fs.existsSync(reportPath)) continue;
const content = fs.readFileSync(reportPath, 'utf8');
const normalized = content.replace(/\\\\/g, '/');
fs.writeFileSync(reportPath, normalized);
}if grep -q 'SF:.*\\\\' apps/backend/coverage/lcov.info; then
echo "ERROR: Backslashes encontrados en lcov.info"
exit 1
fibun run --cwd apps/backend test -- --coverage
bun run --cwd apps/frontend test -- --coverage
sonar-scanner `
-Dsonar.host.url=$env:SONAR_HOST_URL `
-Dsonar.token=$env:SONAR_TOKEN `
-Dsonar.projectKey=$env:SONAR_PROJECT_KEY `
-Dsonar.qualitygate.wait=true
curl -4 -s "$env:SONAR_HOST_URL/api/measures/component?component=$env:SONAR_PROJECT_KEY&metricKeys=coverage,new_coverage,bugs,new_bugs,vulnerabilities,new_vulnerabilities,code_smells,new_code_smells,duplicated_lines_density,new_duplicated_lines_density" -u "$env:SONAR_TOKEN:"
curl -4 -s "$env:SONAR_HOST_URL/api/issues/search?componentKeys=$env:SONAR_PROJECT_KEY&resolved=false&inNewCodePeriod=true&types=BUG,VULNERABILITY,CODE_SMELL&ps=500" -u "$env:SONAR_TOKEN:"
curl -4 -s "$env:SONAR_HOST_URL/api/hotspots/search?projectKey=$env:SONAR_PROJECT_KEY&status=TO_REVIEW&ps=500" -u "$env:SONAR_TOKEN:"sonar-project.propertieslcov.infosonar.javascript.lcov.reportPathsSF:/\\lcov.infogit diff --name-only origin/main...HEAD | Where-Object { $_ -match '\.(ts|tsx)$' }*.spec.ts*.test.tsxshould <resultado> when <condicion>returns <error> when <input invalido>catch**/index.ts**/generated/**PASSEDnew_coverage >= 80new_bugs = 0new_vulnerabilities = 0new_code_smells = 0new_security_hotspots_reviewed = 100%sonar-scannersonar.qualitygate.wait=truesonar.projectKey=my-org_my-monorepo
sonar.projectName=my-monorepo
sonar.sourceEncoding=UTF-8
sonar.sources=apps/backend/src,apps/frontend/src,packages
sonar.tests=apps/backend,apps/frontend,packages
sonar.test.inclusions=**/*.spec.ts,**/*.test.ts,**/*.test.tsx,**/*.spec.tsx
sonar.javascript.lcov.reportPaths=apps/backend/coverage/lcov.info,apps/frontend/coverage/lcov.info
sonar.newCode.referenceBranch=main
sonar.exclusions=**/dist/**,**/build/**,**/node_modules/**,**/*.d.ts
sonar.coverage.exclusions=**/index.ts,**/*.dto.ts,**/generated/**,**/*.stories.tsximport type { Config } from 'jest';
const config: Config = {
preset: 'ts-jest',
testEnvironment: 'node',
collectCoverage: true,
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'html'],
collectCoverageFrom: [
'src/**/*.{ts,tsx}',
'!src/**/*.dto.ts',
'!src/**/index.ts',
'!src/**/generated/**'
]
};
export default config;import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'jsdom',
coverage: {
provider: 'v8',
reporter: ['text', 'lcov', 'html'],
reportsDirectory: './coverage',
include: ['src/**/*.{ts,tsx}'],
exclude: ['src/**/*.stories.tsx', 'src/**/index.ts']
}
}
});name: quality-gate
on:
pull_request:
push:
branches: [main]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- name: Backend tests with coverage
run: bun run --cwd apps/backend test -- --coverage
- name: Frontend tests with coverage
run: bun run --cwd apps/frontend test -- --coverage
- name: Normalize lcov paths (Windows/Linux safe)
run: node scripts/fix-lcov-paths.js
- name: Validate lcov path format
run: |
if grep -q 'SF:.*\\\\' apps/backend/coverage/lcov.info; then
echo "ERROR: Backslashes encontrados en backend lcov.info"
exit 1
fi
- name: Build
run: bun run build
- name: SonarQube Scan
env:
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: |
sonar-scanner \
-Dsonar.host.url=$SONAR_HOST_URL \
-Dsonar.token=$SONAR_TOKEN \
-Dsonar.qualitygate.wait=truestages:
- test
- quality
variables:
GIT_DEPTH: "0"
test_and_build:
stage: test
image: oven/bun:1
script:
- bun install --frozen-lockfile
- bun run --cwd apps/backend test -- --coverage
- bun run --cwd apps/frontend test -- --coverage
- node scripts/fix-lcov-paths.js
- bun run build
artifacts:
when: always
paths:
- apps/backend/coverage/
- apps/frontend/coverage/
sonarqube:
stage: quality
image: sonarsource/sonar-scanner-cli:latest
dependencies:
- test_and_build
script:
- sonar-scanner -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.token=$SONAR_TOKEN -Dsonar.qualitygate.wait=true
allow_failure: falsebun run --cwd apps/backend test -- --coveragebun run --cwd apps/frontend test -- --coveragesonar-scanner -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.token=$SONAR_TOKEN -Dsonar.qualitygate.wait=truelcov.infoSF:$env:SONAR_HOST_URL="http://127.0.0.1:9000"
$env:SONAR_TOKEN="<token>"
$env:SONAR_PROJECT_KEY="<project-key>"
bun run --cwd apps/backend test -- --coverage
bun run --cwd apps/frontend test -- --coverage
bun run build
sonar-scanner `
-Dsonar.host.url=$env:SONAR_HOST_URL `
-Dsonar.token=$env:SONAR_TOKEN `
-Dsonar.projectKey=$env:SONAR_PROJECT_KEY `
-Dsonar.qualitygate.wait=true
curl -4 -s "$env:SONAR_HOST_URL/api/measures/component?component=$env:SONAR_PROJECT_KEY&metricKeys=new_coverage,new_bugs,new_vulnerabilities,new_code_smells,new_duplicated_lines_density" -u "$env:SONAR_TOKEN:"main803%