# ============================================ # Novalon Website - 全自动CI/CD工作流 # ============================================ # 发布策略:feature -> dev -> release -> main # # 分支角色与流程: # 1. feature/** 分支:开发新功能 # - 触发:PR和push # - 执行:Lint + TypeCheck + Smoke Test # - 合并到:dev分支 # # 2. dev 分支:开发集成 # - 触发:push # - 执行:完整测试套件(不部署) # - 创建/合并到:release/**分支 # # 3. release/** 分支:生产环境代码 # - 触发:push # - 执行:代码检查 + 构建产物 + 同步部署 # - 部署到:生产环境(139.155.109.62) # - 归档到:main分支 # # 4. main 分支:稳定代码归档 # - 只读分支 # - 仅接收来自release的自动归档 # # 流水线阶段: # 阶段0: 依赖安装(统一缓存) # 阶段1: 并行代码质量检查 (lint, type-check, security-scan) # 阶段2: 单元测试 -> E2E测试 (允许失败) # 阶段3: 构建生产产物 (仅release分支) # 阶段4: 同步产物到生产服务器并部署 (仅release分支) # 阶段5: 归档到main分支 (仅release分支) # 阶段6: 企业微信通知 # ============================================ variables: - &node_image node:20-alpine steps: # ============================================ # 阶段0: 依赖安装(统一缓存) # ============================================ install-deps: image: *node_image environment: NODE_ENV: development commands: - npm ci --cache /tmp/npm-cache --prefer-offline volumes: - /tmp/npm-cache:/root/.npm - /tmp/node-modules-cache:/woodpecker/src/node_modules when: event: - push - pull_request branch: - feature/** - dev - release - release/** # ============================================ # 阶段1: 并行代码质量检查 # ============================================ lint: image: *node_image environment: NODE_ENV: development depends_on: - install-deps commands: - npm run lint volumes: - /tmp/npm-cache:/root/.npm - /tmp/node-modules-cache:/woodpecker/src/node_modules when: event: - push - pull_request branch: - feature/** - dev - release - release/** type-check: image: *node_image environment: NODE_ENV: development depends_on: - install-deps commands: - npm run type-check volumes: - /tmp/npm-cache:/root/.npm - /tmp/node-modules-cache:/woodpecker/src/node_modules when: event: - push - pull_request branch: - feature/** - dev - release - release/** security-scan: image: *node_image environment: NODE_ENV: production HUSKY: 0 depends_on: - install-deps commands: - npm audit --audit-level=high --omit=dev volumes: - /tmp/npm-cache:/root/.npm - /tmp/node-modules-cache:/woodpecker/src/node_modules when: event: - push - pull_request branch: - feature/** - dev - release - release/** # ============================================ # 阶段2: 测试 (允许失败) # ============================================ unit-tests: image: *node_image environment: NODE_ENV: test CI: true depends_on: - lint - type-check commands: - npm run test:unit -- --coverage --coverageReporters=text-summary --forceExit 2>&1 | tee test-results.txt || true - echo "Unit tests completed." failure: ignore volumes: - /tmp/npm-cache:/root/.npm - /tmp/node-modules-cache:/woodpecker/src/node_modules when: event: - push - pull_request branch: - dev - release - release/** e2e-tests: image: mcr.microsoft.com/playwright:v1.48.0-jammy environment: NODE_ENV: test CI: true BASE_URL: http://localhost:3000 TEST_TIER: standard depends_on: - unit-tests commands: - npm run build - npx playwright install chromium --with-deps - npm run test:e2e failure: ignore volumes: - /tmp/npm-cache:/root/.npm - /tmp/node-modules-cache:/woodpecker/src/node_modules - /tmp/playwright-cache:/root/.cache/ms-playwright when: event: - push branch: - dev # ============================================ # 阶段3: 构建生产产物 (release分支) # ============================================ build-artifacts: image: *node_image environment: NODE_ENV: production NEXT_TELEMETRY_DISABLED: 1 depends_on: - lint - type-check commands: - echo "Building production artifacts..." - npm run build - echo "✅ Build completed" - ls -la dist/ volumes: - /tmp/npm-cache:/root/.npm - /tmp/node-modules-cache:/woodpecker/src/node_modules when: - event: push branch: - release - release/** # ============================================ # 阶段4: 部署到生产环境 (release分支) # ============================================ deploy-production: image: alpine/git:latest environment: DEPLOY_ENV: production SSH_PRIVATE_KEY: from_secret: ssh_private_key depends_on: - build-artifacts commands: - echo "Deploying to production environment..." - mkdir -p ~/.ssh - echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - ssh-keyscan -H 139.155.109.62 >> ~/.ssh/known_hosts - echo "Pre-deployment checks..." - ssh root@139.155.109.62 "echo 'Server connection OK'" - ssh root@139.155.109.62 "df -h | grep -E '/$|/home'" - echo "Syncing build artifacts to production server..." - rsync -avz --delete dist/ root@139.155.109.62:/home/novalon/docker-app/novalon-website/dist/ - rsync -avz public/ root@139.155.109.62:/home/novalon/docker-app/novalon-website/public/ - rsync -avz package.json package-lock.json root@139.155.109.62:/home/novalon/docker-app/novalon-website/ - rsync -avz Dockerfile.prod docker-compose.server.yml root@139.155.109.62:/home/novalon/docker-app/novalon-website/ - rsync -avz scripts/deploy-production.sh root@139.155.109.62:/home/novalon/docker-app/novalon-website/scripts/ - rsync -avz .env.production root@139.155.109.62:/home/novalon/docker-app/novalon-website/ 2>/dev/null || echo "No .env.production file" - | ssh root@139.155.109.62 << 'EOF' set -e cd /home/novalon/docker-app/novalon-website if [ -f docker-compose.server.yml ]; then mv docker-compose.server.yml docker-compose.yml fi chmod +x scripts/deploy-production.sh ./scripts/deploy-production.sh EOF - echo "✅ Production deployment completed!" when: event: - push branch: - release - release/** # ============================================ # 阶段5: 归档到main分支 (release分支) # ============================================ archive-to-main: image: alpine/git:latest environment: SSH_PRIVATE_KEY: from_secret: ssh_private_key depends_on: - deploy-production commands: - echo "Archiving to main branch..." - mkdir -p ~/.ssh - echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - ssh-keyscan -H git.f.novalon.cn >> ~/.ssh/known_hosts - | set -e git config --global user.email "ci@novalon.cn" git config --global user.name "Woodpecker CI" git remote set-url origin git@git.f.novalon.cn:novalon/novalon-website.git git fetch origin CURRENT_BRANCH="${CI_COMMIT_BRANCH}" echo "Current branch: $CURRENT_BRANCH" git checkout main git pull origin main git merge "$CURRENT_BRANCH" --no-ff -m "chore: 归档${CURRENT_BRANCH} ${CI_COMMIT_SHA:0:7}" VERSION_TAG="v$(date +%Y.%m.%d)-${CI_COMMIT_SHA:0:7}" git tag -a "$VERSION_TAG" -m "Release $(date +%Y-%m-%d) from ${CURRENT_BRANCH}" for i in {1..3}; do if git push origin main && git push origin --tags; then echo "✅ Archive succeeded! Version: $VERSION_TAG" exit 0 fi echo "Retry $i/3..." sleep 5 done echo "⚠️ Archive failed, but deployment succeeded" exit 0 when: event: - push branch: - release - release/** status: - success # ============================================ # 阶段6: 企业微信通知 # ============================================ notify-wechat-success: image: curlimages/curl:latest environment: WECHAT_WEBHOOK: from_secret: wechat_webhook depends_on: - archive-to-main commands: - sh scripts/notify-wechat.sh success when: event: - push branch: - release - release/** status: - success notify-wechat-failure: image: curlimages/curl:latest environment: WECHAT_WEBHOOK: from_secret: wechat_webhook depends_on: - deploy-production commands: - sh scripts/notify-wechat.sh failure when: event: - push branch: - release - release/** status: - failure workspace: base: /woodpecker path: src clone: git: image: woodpeckerci/plugin-git settings: depth: 1 partial: false lfs: false