From 76bccd55bd235ddf9b5d3fdf8af1a5826da1f72a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Tue, 29 Sep 2026 16:52:10 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E9=83=A8=E7=BD=B2=E8=AE=A1=E5=88=92?= =?UTF-8?q?=E5=90=B8=E6=94=B6=E5=AE=A1=E6=9F=A54=E9=A1=B9=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E2=80=94=E2=80=94tar=E6=8E=92=E9=99=A4.venv(97MB)/?= =?UTF-8?q?=E6=95=8F=E6=84=9F=E6=89=AB=E6=8F=8F=E5=89=8D=E7=BD=AE/mkdir?= =?UTF-8?q?=E5=85=88=E4=BA=8E=E5=8F=82=E6=95=B0=E8=A7=A3=E6=9E=90/?= =?UTF-8?q?=E8=84=8F=E6=B3=A8=E5=85=A5=E5=85=B7=E4=BD=93=E5=8C=96/?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E8=83=8C=E6=99=AF=E6=A8=A1=E6=9D=BF=E6=8F=90?= =?UTF-8?q?=E9=86=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Code --- .../2026-09-29-git-and-deploy-automation.md | 42 +++++++++++++------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/docs/superpowers/plans/2026-09-29-git-and-deploy-automation.md b/docs/superpowers/plans/2026-09-29-git-and-deploy-automation.md index 9571cd6..f8e7a0b 100644 --- a/docs/superpowers/plans/2026-09-29-git-and-deploy-automation.md +++ b/docs/superpowers/plans/2026-09-29-git-and-deploy-automation.md @@ -34,7 +34,14 @@ deploy/logs/ /模拟访客测试页.html ``` -- [ ] **Step 2: 核实 logo 改动组内聚后提交**(5 文件是一组:art-logo 改用 common/logo.png、LoginLeftView 引用调整、两个 logo 文件、logo-w 替换——`git diff` 快速确认无其他意外改动混入;**提交前跑 `cd admin && npx vue-tsc --noEmit` 确认引用可解析**): +- [ ] **Step 2: 敏感文件扫描**(spec Part 1 要求,推送前必做——m1+main 全历史将进新远程仓): + +```bash +git ls-files | grep -iE '\.env$|\.pem$|id_rsa|secret|password' ; echo "exit=$?" # 预期仅 .env.example 类 +git ls-files assets/ | head -20 # 目检设计稿清单(6.6MB/11 文件已核,确认无意外内容) +``` + +- [ ] **Step 3: 核实 logo 改动组内聚后提交**(5 文件是一组:art-logo 改用 common/logo.png、LoginLeftView 引用调整、两个 logo 文件、logo-w 替换——`git diff` 快速确认无其他意外改动混入;**提交前跑 `cd admin && npx vue-tsc --noEmit` 确认引用可解析**): ```bash git add .gitignore assets/ admin/src/assets/images/ admin/src/components/core/base/art-logo/index.vue admin/src/components/core/views/login/LoginLeftView.vue @@ -43,7 +50,7 @@ git commit -m "chore: 入库设计稿与品牌logo资产,gitignore部署日志 Co-Authored-By: Claude Code " ``` -- [ ] **Step 3: 确认 `git status --porcelain` 为空**(工作区干净——后续推送与部署门禁的前提) +- [ ] **Step 4: 确认 `git status --porcelain` 为空**(工作区干净——后续推送与部署门禁的前提) ### Task 2: 接入远程仓并推送 @@ -81,6 +88,7 @@ Expected: 推送成功。若报认证失败(`terminal prompts disabled` / 401 # 用法: bash deploy/deploy.sh [--skip-tests] [--allow-dirty] # 红线: 只操作 /opt/cms-cj 与 tracker-web 容器,绝不触碰服务器其他项目 set -euo pipefail +SECONDS=0 # ---- 配置(环境变量可覆盖)---- JUMP_HOST="${JUMP_HOST:-root@8.152.213.143}" @@ -102,6 +110,8 @@ log() { echo "[$(date +%H:%M:%S)] $*" | tee -a "$LOG_FILE"; } die() { log "✗ 失败: $*(部署中止,此前已完成的目标保持已部署状态)"; exit 1; } ok() { log "✓ $*"; } +mkdir -p "$LOG_DIR" # 必须先于参数解析——早期 die() 也要能写日志 + # ---- 参数解析 ---- TARGET_CURRENT="preflight"; STEP="-" [ $# -ge 1 ] || die "用法: bash deploy/deploy.sh [--skip-tests] [--allow-dirty]" @@ -118,8 +128,6 @@ case "$DEPLOY_TARGET" in server|admin|sdk|all) ;; *) die "目标必须是 server|admin|sdk|all,收到: $DEPLOY_TARGET" ;; esac - -mkdir -p "$LOG_DIR" GIT_HASH="$(git -C "$REPO_DIR" rev-parse --short HEAD)" log "===== 部署开始 target=$DEPLOY_TARGET git=$GIT_HASH =====" @@ -152,13 +160,13 @@ build_admin() { STEP="admin 构建" (cd "$REPO_DIR/admin" && NODE_OPTIONS=--max-old-space-size=6144 pnpm build >>"$LOG_FILE" 2>&1) \ || die "admin 构建失败(本机 OOM 则确认 NODE_OPTIONS 已带)" - ok "admin 构建完成: $(grep -oE 'index-[A-Za-z0-9_-]+\.js' admin/dist/index.html | head -1)" + ok "admin 构建完成: $(grep -oE 'index-[A-Za-z0-9_-]+\.js' "$REPO_DIR/admin/dist/index.html" | head -1)" } build_sdk() { STEP="sdk 构建" (cd "$REPO_DIR/sdk" && node build.mjs >>"$LOG_FILE" 2>&1) || die "sdk 构建失败" local gz; gz=$(gzip -c "$REPO_DIR/sdk/dist/v1.js" | wc -c) - ok "sdk 构建完成: build_id=$(grep -oE '[0-9a-f]{7,9}-2026[0-9]{8}' sdk/dist/v1.js | head -1) gzip=${gz}B" + ok "sdk 构建完成: build_id=$(grep -oE '[0-9a-f]{7,9}-2026[0-9]{8}' "$REPO_DIR/sdk/dist/v1.js" | head -1) gzip=${gz}B" } transfer() { # $1=包内路径列表(空格分隔,相对 REPO_DIR) $2=远端包名 STEP="传输与落位" @@ -176,7 +184,8 @@ rebuild_web() { "${SSH[@]}" "cd $REMOTE_DIR/deploy && docker compose up -d --build web" >>"$LOG_FILE" 2>&1 \ || die "docker compose 重建失败" sleep 5 - local status; status="$("${SSH[@]}" "docker ps --filter name=tracker-web --format '{{.Status}}'")" + local status; status="$("${SSH[@]}" "docker ps --filter name=tracker-web --format '{{.Status}}'")" \ + || die "容器状态查询失败" echo "$status" | grep -q "^Up" || die "tracker-web 容器状态异常: $status" ok "tracker-web: $status" } @@ -189,7 +198,8 @@ verify_admin_hash() { STEP="admin 哈希验证" local local_h online_h local_h=$(sha256sum "$REPO_DIR/admin/dist/index.html" | cut -d' ' -f1) - online_h=$(curl -sf --max-time 20 "$SITE_URL/" | sha256sum | cut -d' ' -f1) + online_h=$(curl -sf --max-time 20 "$SITE_URL/" | sha256sum | cut -d' ' -f1) \ + || die "线上 index.html 拉取失败" [ "$local_h" = "$online_h" ] || die "admin index.html 线上哈希与本机产物不一致(部署未生效或缓存)" ok "admin 哈希一致" } @@ -206,8 +216,10 @@ deploy_server() { TARGET_CURRENT=server server_tests STEP="server 打包" + # 排除清单镜像 server/.dockerignore 本意:Windows venv(97MB)/缓存/本地xdb 绝不上服务器 (cd "$REPO_DIR" && tar -czf /tmp/keji-server.tgz \ - --exclude='__pycache__' --exclude='.pytest_cache' --exclude='db.sqlite3' server/) \ + --exclude='__pycache__' --exclude='.pytest_cache' --exclude='db.sqlite3' \ + --exclude='.venv' --exclude='.cache' --exclude='tracker/data/*.xdb' server/) \ || die "server 打包失败" "${SCP[@]}" /tmp/keji-server.tgz "$TARGET_HOST:/tmp/" >/dev/null 2>&1 || die "scp 失败" rm -f /tmp/keji-server.tgz @@ -242,7 +254,7 @@ esac log "===== 部署完成 target=$DEPLOY_TARGET git=$GIT_HASH 用时 $SECONDS 秒 =====" ``` -**实现者注意**:`$SECONDS` 需在脚本开头(`set -euo pipefail` 之后)加 `SECONDS=0` 重置;落盘即上方最终形态,勿再引入管道判断测试退避的写法(pytest/vitest 退出码即真相,输出重定向进日志)。 +**实现者注意**:落盘即上方最终形态(含 SECONDS=0/mkdir 前置/tar 全排除清单/die 包装),勿再引入管道判断测试退避的写法(pytest/vitest 退出码即真相,输出重定向进日志)。 - [ ] **Step 2: 语法检查**:`bash -n deploy/deploy.sh`(无输出即通过) - [ ] **Step 3: 本地无副作用的参数面测试**:`bash deploy/deploy.sh` (无参 → 应打用法并退出 1);`bash deploy/deploy.sh foo` (非法目标 → 退出 1);`bash deploy/deploy.sh admin --bad-flag`(未知参数 → 退出 1)——三条都不应触碰网络 @@ -312,7 +324,13 @@ JUMP_HOST=nobody@127.0.0.1 bash deploy/deploy.sh admin; echo "exit=$?" Expected: 前置检查 ✗ SSH 不可达 → 退出码非 0,日志留档,**未发生任何传输**。 -再注入一次工作区脏检查(`touch /tmp/x && git add --intent-to-add 任意文件` 太绕——直接改一个已跟踪文件加空行)验证"工作区不干净 → 拒绝部署",验完还原。 +再注入工作区脏检查(具体方法): + +```bash +echo "" >> CLAUDE.md # 制造未提交改动 +bash deploy/deploy.sh admin; echo "exit=$?" # 预期: 工作区不干净 → 拒绝,退出 1,未触碰网络(脏检查先于 SSH 检查) +git checkout -- CLAUDE.md # 还原 +``` - [ ] **Step 3: all 完整验证**(含 server 容器秒级重启,用户已确认接受): @@ -329,7 +347,7 @@ Expected: pytest→打包→容器 Up→探活→admin 构建/哈希→sdk 测 ### Task 6: 收尾推送 - [ ] **Step 1: 推送全部新提交:`git push origin m1`**(deploy.sh/CLAUDE.md/gitignore 等) -- [ ] **Step 2: 汇报**:远程仓两分支状态、脚本用法示例、验证结果摘要(admin 首验/失败注入/all)、部署日志位置 +- [ ] **Step 2: 汇报**:远程仓两分支状态、脚本用法示例、验证结果摘要(admin 首验/失败注入/all)、部署日志位置;**提醒用户**:其会话粘贴的"项目背景"模板中"部署策略"段更新为指向 `bash deploy/deploy.sh`(spec Part 3 第 2 条,用户侧自行更新) ---