242 lines
9.2 KiB
Groovy
242 lines
9.2 KiB
Groovy
pipeline {
|
||
agent any
|
||
|
||
options {
|
||
skipDefaultCheckout(true)
|
||
}
|
||
|
||
parameters {
|
||
booleanParam(name: 'RUN_FULL_E2E', defaultValue: false, description: '是否在部署后额外执行全量 E2E(失败仅标记 UNSTABLE)')
|
||
booleanParam(name: 'RUN_COVERAGE', defaultValue: false, description: '是否在主链路执行覆盖率测试(关闭可提速)')
|
||
}
|
||
|
||
environment {
|
||
APP_NAME = 'no-whatever'
|
||
CI_IMAGE = 'mcr.microsoft.com/playwright:v1.51.1-jammy'
|
||
NPM_REGISTRY = 'https://registry.npmmirror.com'
|
||
BUILDX_CACHE_DIR = '.buildx-cache'
|
||
}
|
||
|
||
triggers {
|
||
GenericTrigger(tokenCredentialId: 'no-whatever-deploy-token')
|
||
}
|
||
|
||
stages {
|
||
stage('Checkout') {
|
||
steps {
|
||
checkout scm
|
||
}
|
||
}
|
||
|
||
stage('Runtime Check') {
|
||
steps {
|
||
sh 'docker --version'
|
||
}
|
||
}
|
||
|
||
stage('Prepare Test Images') {
|
||
options {
|
||
timeout(time: 15, unit: 'MINUTES')
|
||
}
|
||
steps {
|
||
retry(2) {
|
||
sh '''
|
||
set -e
|
||
|
||
if docker image inspect ${CI_IMAGE} >/dev/null 2>&1; then
|
||
echo "${CI_IMAGE} already exists, skip pull"
|
||
else
|
||
docker pull ${CI_IMAGE}
|
||
fi
|
||
'''
|
||
}
|
||
}
|
||
}
|
||
|
||
stage('Prepare CI Deps Image') {
|
||
options {
|
||
timeout(time: 20, unit: 'MINUTES')
|
||
}
|
||
steps {
|
||
script {
|
||
env.LOCK_HASH = sh(script: "sha256sum package-lock.json | cut -d ' ' -f1", returnStdout: true).trim()
|
||
env.CI_DEPS_IMAGE = "${APP_NAME}-ci-deps:${env.LOCK_HASH}"
|
||
}
|
||
sh '''
|
||
set -e
|
||
if docker image inspect "${CI_DEPS_IMAGE}" >/dev/null 2>&1; then
|
||
echo "deps image cache hit: ${CI_DEPS_IMAGE}"
|
||
else
|
||
echo "deps image cache miss, building: ${CI_DEPS_IMAGE}"
|
||
docker build \
|
||
--build-arg CI_IMAGE=${CI_IMAGE} \
|
||
--build-arg NPM_REGISTRY=${NPM_REGISTRY} \
|
||
-f Dockerfile.ci-deps \
|
||
-t "${CI_DEPS_IMAGE}" .
|
||
fi
|
||
'''
|
||
}
|
||
}
|
||
|
||
stage('CI Gate (Lint + Unit + Smoke E2E)') {
|
||
options {
|
||
timeout(time: 30, unit: 'MINUTES')
|
||
}
|
||
steps {
|
||
sh '''
|
||
set -e
|
||
rm -rf playwright-report test-results
|
||
cid=$(docker create --ipc=host \
|
||
-e HOME=/tmp \
|
||
-e RUN_COVERAGE=${RUN_COVERAGE} \
|
||
${CI_DEPS_IMAGE} \
|
||
sh -lc 'set -e; cd /workspace; npx prisma generate; npm run lint; if [ "$RUN_COVERAGE" = "true" ]; then npm run test:coverage; else npm run test; fi; npm run test:e2e:smoke')
|
||
cleanup() {
|
||
docker rm -f "$cid" >/dev/null 2>&1 || true
|
||
}
|
||
trap cleanup EXIT
|
||
git archive --format=tar HEAD | docker cp - "$cid":/workspace
|
||
set +e
|
||
docker start -a "$cid"
|
||
status=$?
|
||
set -e
|
||
docker cp "$cid":/workspace/playwright-report ./playwright-report 2>/dev/null || true
|
||
docker cp "$cid":/workspace/test-results ./test-results 2>/dev/null || true
|
||
exit $status
|
||
'''
|
||
}
|
||
post {
|
||
always {
|
||
archiveArtifacts artifacts: 'playwright-report/**,test-results/**', allowEmptyArchive: true
|
||
}
|
||
}
|
||
}
|
||
|
||
stage('Build Docker Image') {
|
||
steps {
|
||
withCredentials([
|
||
string(credentialsId: 'amap-api-key', variable: 'AMAP_KEY')
|
||
]) {
|
||
sh '''
|
||
set -e
|
||
CACHE_DIR="${BUILDX_CACHE_DIR}"
|
||
CACHE_NEW_DIR="${BUILDX_CACHE_DIR}-new"
|
||
rm -rf "$CACHE_NEW_DIR"
|
||
|
||
if docker buildx version >/dev/null 2>&1; then
|
||
echo "using docker buildx with local cache"
|
||
docker buildx create --name "${APP_NAME}-builder" --use >/dev/null 2>&1 || true
|
||
docker buildx use "${APP_NAME}-builder" >/dev/null 2>&1 || true
|
||
mkdir -p "$CACHE_DIR"
|
||
|
||
set +e
|
||
docker buildx build \
|
||
--load \
|
||
--build-arg NEXT_PUBLIC_AMAP_API_KEY="$AMAP_KEY" \
|
||
--cache-from type=local,src="$CACHE_DIR" \
|
||
--cache-to type=local,dest="$CACHE_NEW_DIR",mode=max \
|
||
-t "${APP_NAME}:${BUILD_NUMBER}" \
|
||
-t "${APP_NAME}:latest" \
|
||
.
|
||
bx_status=$?
|
||
set -e
|
||
|
||
if [ $bx_status -eq 0 ]; then
|
||
rm -rf "$CACHE_DIR"
|
||
mv "$CACHE_NEW_DIR" "$CACHE_DIR"
|
||
else
|
||
echo "buildx build failed, fallback to classic docker build"
|
||
docker build \
|
||
--build-arg NEXT_PUBLIC_AMAP_API_KEY="$AMAP_KEY" \
|
||
-t "${APP_NAME}:${BUILD_NUMBER}" \
|
||
-t "${APP_NAME}:latest" \
|
||
.
|
||
fi
|
||
else
|
||
echo "docker buildx unavailable, fallback to classic docker build"
|
||
docker build \
|
||
--build-arg NEXT_PUBLIC_AMAP_API_KEY="$AMAP_KEY" \
|
||
-t "${APP_NAME}:${BUILD_NUMBER}" \
|
||
-t "${APP_NAME}:latest" \
|
||
.
|
||
fi
|
||
'''
|
||
}
|
||
}
|
||
}
|
||
|
||
stage('Deploy') {
|
||
steps {
|
||
withCredentials([
|
||
string(credentialsId: 'amap-api-key', variable: 'AMAP_KEY'),
|
||
string(credentialsId: 'deepseek-api-key', variable: 'DEEPSEEK_KEY')
|
||
]) {
|
||
sh """
|
||
docker stop ${APP_NAME} || true
|
||
docker rm ${APP_NAME} || true
|
||
mkdir -p /data/${APP_NAME}
|
||
chown 1001:1001 /data/${APP_NAME}
|
||
docker run -d \
|
||
--name ${APP_NAME} \
|
||
--network nginx \
|
||
-p 3721:3721 \
|
||
-v /data/${APP_NAME}:/app/data \
|
||
-e DATABASE_URL=file:/app/data/prod.db \
|
||
-e AMAP_API_KEY=${AMAP_KEY} \
|
||
-e DEEPSEEK_API_KEY=${DEEPSEEK_KEY} \
|
||
--restart unless-stopped \
|
||
${APP_NAME}:latest
|
||
"""
|
||
}
|
||
}
|
||
}
|
||
|
||
stage('Full E2E (Optional, Non-Blocking)') {
|
||
when {
|
||
expression { return params.RUN_FULL_E2E }
|
||
}
|
||
options {
|
||
timeout(time: 30, unit: 'MINUTES')
|
||
}
|
||
steps {
|
||
catchError(buildResult: 'UNSTABLE', stageResult: 'UNSTABLE') {
|
||
sh '''
|
||
set -e
|
||
rm -rf playwright-report-full test-results-full
|
||
cid=$(docker create --ipc=host \
|
||
-e HOME=/tmp \
|
||
${CI_DEPS_IMAGE} \
|
||
sh -lc 'set -e; cd /workspace; npx prisma generate; npm run test:e2e')
|
||
cleanup() {
|
||
docker rm -f "$cid" >/dev/null 2>&1 || true
|
||
}
|
||
trap cleanup EXIT
|
||
git archive --format=tar HEAD | docker cp - "$cid":/workspace
|
||
set +e
|
||
docker start -a "$cid"
|
||
status=$?
|
||
set -e
|
||
docker cp "$cid":/workspace/playwright-report ./playwright-report-full 2>/dev/null || true
|
||
docker cp "$cid":/workspace/test-results ./test-results-full 2>/dev/null || true
|
||
exit $status
|
||
'''
|
||
}
|
||
}
|
||
post {
|
||
always {
|
||
archiveArtifacts artifacts: 'playwright-report-full/**,test-results-full/**', allowEmptyArchive: true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
post {
|
||
success {
|
||
echo "Deployed ${APP_NAME} build #${BUILD_NUMBER} successfully"
|
||
}
|
||
failure {
|
||
echo "Build #${BUILD_NUMBER} failed"
|
||
}
|
||
}
|
||
}
|