pipeline { agent any environment { MAVEN_CACHE = "${env.WORKSPACE}/.m2" NPM_CACHE = "${env.WORKSPACE}/.npm-cache" SONAR_TOKEN = credentials('sonar-token') } options { ansiColor('xterm') timestamps() skipStagesAfterUnstable() } stages { stage('Checkout') { steps { checkout scm } } stage('Backend: Build & Test') { agent { docker { image 'maven:3.9.9-eclipse-temurin-21' args "-v ${env.WORKSPACE}/.m2:/root/.m2" } } environment { MAVEN_OPTS = '-Dmaven.repo.local=/root/.m2/repository' } steps { dir('backend') { sh './mvnw --version' sh './mvnw clean test -Dmaven.repo.local=/root/.m2/repository' sh './mvnw package -DskipTests -Dmaven.repo.local=/root/.m2/repository' } } post { always { junit 'backend/target/surefire-reports/*.xml' } } } stage('Backend: SonarQube Analysis') { agent { docker { image 'maven:3.9.9-eclipse-temurin-21' args "-v ${env.WORKSPACE}/.m2:/root/.m2" } } steps { withSonarQubeEnv('SonarQube') { dir('backend') { sh './mvnw sonar:sonar -Dsonar.projectKey=code-journey-backend -Dsonar.host.url=${SONAR_HOST_URL} -Dsonar.login=${SONAR_TOKEN} -Dmaven.repo.local=/root/.m2/repository' } } } } stage('Frontend: Install & Build') { agent { docker { image 'node:20-alpine' args "-v ${env.WORKSPACE}/.npm-cache:/root/.npm -u root" } } steps { dir('frontend') { sh 'npm ci --cache /root/.npm --prefer-offline' sh 'npm run build' } } } stage('Frontend: SonarQube Analysis') { agent { docker { image 'node:20-alpine' args "-v ${env.WORKSPACE}/.npm-cache:/root/.npm -u root" } } steps { withSonarQubeEnv('SonarQube') { dir('frontend') { sh 'npm install -g sonar-scanner' sh 'sonar-scanner -Dsonar.projectKey=code-journey-frontend -Dsonar.sources=src -Dsonar.host.url=${SONAR_HOST_URL} -Dsonar.login=${SONAR_TOKEN}' } } } } stage('Docker: Build Images') { when { anyOf { branch 'main' branch 'develop' } } agent { docker { image 'docker:24.0.5-dind' args '--privileged -v /var/run/docker.sock:/var/run/docker.sock' } } steps { sh 'docker build -t code-journey-backend:latest backend' sh 'docker build -t code-journey-frontend:latest frontend' } } } post { always { archiveArtifacts artifacts: 'backend/target/*.jar,frontend/.next/**', allowEmptyArchive: true } } }