This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: PromptoLab-UI CI/CD Pipeline | |
| on: | |
| push: | |
| branches: [master, main, develop] | |
| paths: | |
| - 'prompto-lab-ui/**' | |
| - '.github/workflows/ui-release.yml' | |
| pull_request: | |
| branches: [master, main] | |
| paths: | |
| - 'prompto-lab-ui/**' | |
| - '.github/workflows/ui-release.yml' | |
| env: | |
| NODE_VERSION: '18' | |
| REGISTRY: ${{ secrets.DOCKER_REPO }} | |
| UI_DIR: 'prompto-lab-ui' | |
| jobs: | |
| # Job 1: 检测变更和代码质量检查 | |
| code-quality: | |
| name: 前端代码质量检查 | |
| runs-on: ubuntu-latest | |
| if: contains(github.event.head_commit.message, '<Auto>') || github.event_name == 'pull_request' | |
| outputs: | |
| should-deploy: ${{ steps.check.outputs.should-deploy }} | |
| version: ${{ steps.extract.outputs.version }} | |
| module: ${{ steps.extract.outputs.module }} | |
| has-ui-changes: ${{ steps.changes.outputs.ui }} | |
| steps: | |
| - name: Checkout代码 | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: 检测文件变更 | |
| id: changes | |
| run: | | |
| # 检查是否有前端文件变更 | |
| if git diff --name-only HEAD~1 HEAD | grep -q "^prompto-lab-ui/"; then | |
| echo "ui=true" >> $GITHUB_OUTPUT | |
| echo "检测到前端文件变更" | |
| else | |
| echo "ui=false" >> $GITHUB_OUTPUT | |
| echo "未检测到前端文件变更,跳过前端部署" | |
| fi | |
| - name: 提取提交信息 | |
| id: extract | |
| if: steps.changes.outputs.ui == 'true' | |
| run: | | |
| commit_message="${{ github.event.head_commit.message }}" | |
| echo "提交信息: $commit_message" | |
| # 默认值 | |
| version="$(date +%Y%m%d-%H%M%S)" | |
| module="promptolab-ui" | |
| skip_tests="false" | |
| skip_build="false" | |
| skip_deploy="false" | |
| # 解析参数 | |
| if [[ "$commit_message" =~ -v:([^\ ]*) ]]; then | |
| version="${BASH_REMATCH[1]}" | |
| fi | |
| if [[ "$commit_message" =~ -m:([^\ ]*) ]]; then | |
| module="${BASH_REMATCH[1]}" | |
| fi | |
| if [[ "$commit_message" =~ -skip:([^\ ]*) ]]; then | |
| skip_option="${BASH_REMATCH[1]}" | |
| case $skip_option in | |
| tests) skip_tests="true" ;; | |
| build) skip_build="true" ;; | |
| deploy) skip_deploy="true" ;; | |
| all) skip_tests="true"; skip_build="true"; skip_deploy="true" ;; | |
| esac | |
| fi | |
| echo "version=$version" >> $GITHUB_OUTPUT | |
| echo "module=$module" >> $GITHUB_OUTPUT | |
| echo "skip-tests=$skip_tests" >> $GITHUB_OUTPUT | |
| echo "skip-build=$skip_build" >> $GITHUB_OUTPUT | |
| echo "skip-deploy=$skip_deploy" >> $GITHUB_OUTPUT | |
| - name: 检查是否应该部署 | |
| id: check | |
| if: steps.changes.outputs.ui == 'true' | |
| run: | | |
| if [[ "${{ github.ref }}" == "refs/heads/master" ]] || [[ "${{ github.ref }}" == "refs/heads/main" ]]; then | |
| echo "should-deploy=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "should-deploy=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: 设置Node.js | |
| if: steps.changes.outputs.ui == 'true' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'npm' | |
| cache-dependency-path: '${{ env.UI_DIR }}/package-lock.json' | |
| - name: 安装依赖 | |
| if: steps.changes.outputs.ui == 'true' | |
| working-directory: ${{ env.UI_DIR }} | |
| run: npm ci | |
| - name: 代码格式检查 | |
| if: steps.changes.outputs.ui == 'true' && steps.extract.outputs.skip-tests != 'true' | |
| working-directory: ${{ env.UI_DIR }} | |
| run: | | |
| # 如果有eslint配置 | |
| if [ -f ".eslintrc.js" ] || [ -f ".eslintrc.json" ]; then | |
| npm run lint || echo "Lint检查完成" | |
| fi | |
| - name: 类型检查 | |
| if: steps.changes.outputs.ui == 'true' && steps.extract.outputs.skip-tests != 'true' | |
| working-directory: ${{ env.UI_DIR }} | |
| run: npm run type-check | |
| - name: 单元测试 | |
| if: steps.changes.outputs.ui == 'true' && steps.extract.outputs.skip-tests != 'true' | |
| working-directory: ${{ env.UI_DIR }} | |
| run: | | |
| # 如果有测试脚本 | |
| if npm run | grep -q "test"; then | |
| npm run test || echo "测试完成" | |
| else | |
| echo "未配置测试脚本,跳过测试" | |
| fi | |
| # Job 2: 构建应用 | |
| build: | |
| name: 构建前端应用 | |
| runs-on: ubuntu-latest | |
| needs: code-quality | |
| if: needs.code-quality.outputs.should-deploy == 'true' && needs.code-quality.outputs.has-ui-changes == 'true' && needs.code-quality.result == 'success' | |
| steps: | |
| - name: Checkout代码 | |
| uses: actions/checkout@v4 | |
| - name: 设置Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'npm' | |
| cache-dependency-path: '${{ env.UI_DIR }}/package-lock.json' | |
| - name: 安装依赖 | |
| working-directory: ${{ env.UI_DIR }} | |
| run: npm ci | |
| - name: 构建应用 | |
| working-directory: ${{ env.UI_DIR }} | |
| run: npm run build | |
| - name: 上传构建产物 | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ui-dist-files | |
| path: ${{ env.UI_DIR }}/dist/ | |
| retention-days: 1 | |
| # Job 3: 构建Docker镜像 | |
| docker-build: | |
| name: 构建前端Docker镜像 | |
| runs-on: ubuntu-latest | |
| needs: [code-quality, build] | |
| if: needs.code-quality.outputs.should-deploy == 'true' && needs.code-quality.outputs.has-ui-changes == 'true' && needs.build.result == 'success' | |
| steps: | |
| - name: Checkout代码 | |
| uses: actions/checkout@v4 | |
| - name: 下载构建产物 | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: ui-dist-files | |
| path: ${{ env.UI_DIR }}/dist/ | |
| - name: 设置Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: 登录Docker Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| username: ${{ secrets.DOCKER_REPO }} | |
| password: ${{ secrets.DOCKER_PWD }} | |
| - name: 构建并推送Docker镜像 | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: ${{ env.UI_DIR }} | |
| file: ${{ env.UI_DIR }}/Dockerfile | |
| push: true | |
| tags: | | |
| ${{ env.REGISTRY }}/${{ needs.code-quality.outputs.module }}:${{ needs.code-quality.outputs.version }} | |
| ${{ env.REGISTRY }}/${{ needs.code-quality.outputs.module }}:latest | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| # Job 4: 部署到服务器 | |
| deploy: | |
| name: 部署前端到服务器 | |
| runs-on: ubuntu-latest | |
| needs: [code-quality, docker-build] | |
| if: needs.code-quality.outputs.should-deploy == 'true' && needs.code-quality.outputs.has-ui-changes == 'true' && needs.docker-build.result == 'success' | |
| environment: production | |
| steps: | |
| - name: 部署到服务器 | |
| uses: appleboy/ssh-action@master | |
| with: | |
| host: ${{ secrets.SERVER_ADDRESS }} | |
| username: ${{ secrets.SERVER_USERNAME || 'root' }} | |
| password: ${{ secrets.SERVER_PWD }} | |
| port: ${{ secrets.SERVER_PORT || '22' }} | |
| script: | | |
| set -e | |
| MODULE="${{ needs.code-quality.outputs.module }}" | |
| VERSION="${{ needs.code-quality.outputs.version }}" | |
| REGISTRY="${{ env.REGISTRY }}" | |
| PORT="${{ secrets.UI_APP_PORT || '80' }}" | |
| echo "开始部署前端 $MODULE:$VERSION" | |
| # 拉取最新镜像 | |
| docker pull $REGISTRY/$MODULE:$VERSION | |
| # 停止并删除旧容器 | |
| if [ "$(docker ps -q -f name=$MODULE)" ]; then | |
| echo "停止旧容器..." | |
| docker stop $MODULE | |
| fi | |
| if [ "$(docker ps -aq -f name=$MODULE)" ]; then | |
| echo "删除旧容器..." | |
| docker rm $MODULE | |
| fi | |
| # 启动新容器 | |
| echo "启动新容器..." | |
| docker run -d \ | |
| --name $MODULE \ | |
| -p $PORT:80 \ | |
| --restart unless-stopped \ | |
| --label "version=$VERSION" \ | |
| --label "deployed-at=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ | |
| --label "type=frontend" \ | |
| $REGISTRY/$MODULE:$VERSION | |
| # 等待容器启动 | |
| sleep 10 | |
| # 健康检查 | |
| if curl -f http://localhost:$PORT > /dev/null 2>&1; then | |
| echo "✅ 前端部署成功!应用运行在端口 $PORT" | |
| else | |
| echo "❌ 前端健康检查失败" | |
| exit 1 | |
| fi | |
| # 清理未使用的镜像 | |
| docker image prune -f | |
| echo "🎉 前端部署完成!" | |
| # Job 5: 通知 | |
| notify: | |
| name: 前端部署通知 | |
| runs-on: ubuntu-latest | |
| needs: [code-quality, build, docker-build, deploy] | |
| if: always() && needs.code-quality.outputs.should-deploy == 'true' && needs.code-quality.outputs.has-ui-changes == 'true' | |
| steps: | |
| - name: 发送通知 | |
| run: | | |
| if [[ "${{ needs.deploy.result }}" == "success" ]]; then | |
| echo "✅ 前端部署成功通知" | |
| # 这里可以添加钉钉、企业微信等通知 | |
| else | |
| echo "❌ 前端部署失败通知" | |
| # 这里可以添加失败通知 | |
| fi |