pipeline { agent none options { timestamps() } environment { NODE_OPTIONS = '--max_old_space_size=2048' } stages { stage('Init') { agent any steps { script { env.COMMIT_AUTHOR = sh( script: "git show -s --format=%an HEAD", returnStdout: true ).trim() env.COMMIT_SHORT = sh( script: "git rev-parse --short HEAD", returnStdout: true ).trim() } echo "CI build for commit ${env.COMMIT_SHORT} by ${env.COMMIT_AUTHOR}" } } /* ========================= BACKEND CI ========================= */ stage('Backend: lint & test') { agent { docker { image 'python:3.11-slim' } } environment { HOME = "${WORKSPACE}" PIP_CACHE_DIR = "${WORKSPACE}/.cache/pip" PYTHONDONTWRITEBYTECODE = 1 } steps { dir('backend') { sh ''' set -e mkdir -p "$PIP_CACHE_DIR" "$WORKSPACE/.cache/pytest" python -m venv .venv . .venv/bin/activate pip install --upgrade pip pip install -r requirements-dev.txt ruff check app tests pytest -o cache_dir="$WORKSPACE/.cache/pytest" ''' } } } /* ========================= FRONTEND CI ========================= */ stage('Frontend: check & build') { agent { docker { image 'node:20-slim' } } environment { HOME = "${WORKSPACE}" NPM_CONFIG_CACHE = "${WORKSPACE}/.cache/npm" } steps { dir('frontend') { sh ''' set -e mkdir -p "$NPM_CONFIG_CACHE" npm install --no-progress --no-audit --prefer-offline npm run check npm test npm run build ''' } } } } post { always { script { node { junit testResults: 'backend/pytest.xml', allowEmptyResults: true try { if (env.CHANGE_ID) { def owner = null def repo = null def giteaBase = null if (env.CHANGE_URL) { def u = new URL(env.CHANGE_URL) giteaBase = "${u.protocol}://${u.host}" def path = u.path // /gitea/OpenBokeron/TallerCiCd/pulls/29 def m = (path =~ /\/gitea\/([^\/]+)\/([^\/]+)\/pulls\/\d+/) if (m) { owner = m[0][1] repo = m[0][2] } } if ((!owner || !repo) && env.GIT_URL) { def m = (env.GIT_URL =~ /[:\/]([^\/:]+)\/([^\/]+?)(?:\.git)?$/) if (m) { owner = m[0][1] repo = m[0][2] } if (!giteaBase) giteaBase = 'https://openbokeron.org' // AJUSTA si hace falta } if (owner && repo && giteaBase) { def pr = env.CHANGE_ID def result = currentBuild.currentResult def emoji = (result == 'SUCCESS') ? '✅' : (result == 'UNSTABLE') ? '⚠️' : '❌' def msg = "${emoji} Jenkins: **${result}**\\n\\n" + "- Job: `${env.JOB_NAME}`\\n" + "- Build: #${env.BUILD_NUMBER}\\n" + "- Commit: `${env.GIT_COMMIT?.take(7) ?: env.COMMIT_SHORT}`\\n" + "- URL: ${env.BUILD_URL}" def url = "${giteaBase}/gitea/api/v1/repos/${owner}/${repo}/issues/${pr}/comments" withCredentials([string(credentialsId: 'gitea-jenkins-bot-token', variable: 'GITEA_TOKEN')]) { sh """ set -e curl -sS -X POST \ -H "Authorization: token ${GITEA_TOKEN}" \ -H "Content-Type: application/json" \ --data @- \ '${url}' <<'JSON' {"body":"${msg.replace('\\', '\\\\').replace('"','\\"')}"} JSON """ } echo "Comentado en PR #${pr} (${owner}/${repo})." } else { echo "No pude deducir owner/repo/baseURL; no comento en la PR." } } else { echo "No es build de PR (CHANGE_ID vacío); no comento." } } catch (err) { echo "Fallo al comentar en PR: ${err}" } cleanWs() } } } } }