#!/bin/sh # CI 公共步骤:前端依赖安装(Docker Volume 持久化缓存方案) # 通过 Docker named volume 缓存 node_modules,按 package-lock.json hash 命名 # 缓存命中时跳过 npm ci,直接复用已有 volume set -eu MODE="${1:-full}" echo "=== 前端依赖安装开始 (模式: $MODE) ===" # npm国内镜像源(加速下载,减少网络失败) NPM_REGISTRY="https://registry.npmmirror.com" # 缓存配置 — 与 step_frontend_run.sh 保持一致 LOCK_FILE="apps/web/package-lock.json" VOLUME_PREFIX="ci-web-nm-" KEEP_CACHE_COUNT=5 # 计算 package-lock.json 的 md5 hash 作为缓存 key VOLUME_NAME="" if [ -f "$LOCK_FILE" ]; then LOCK_HASH=$(md5sum "$LOCK_FILE" | cut -c1-12) VOLUME_NAME="${VOLUME_PREFIX}${LOCK_HASH}" echo "缓存 key: $LOCK_HASH (volume: $VOLUME_NAME)" else echo "警告: 未找到 $LOCK_FILE,将不使用持久化缓存" fi # 检查 volume 是否存在(缓存命中) CACHE_HIT=0 if [ -n "$VOLUME_NAME" ]; then if docker volume inspect "$VOLUME_NAME" >/dev/null 2>&1; then CACHE_HIT=1 echo "缓存命中!复用 volume: $VOLUME_NAME" else echo "缓存未命中,创建 volume 并安装依赖..." # 创建 volume(失败则降级为无缓存模式) if ! docker volume create "$VOLUME_NAME" >/dev/null 2>&1; then echo "警告: 创建 volume 失败,降级为无缓存模式" VOLUME_NAME="" fi fi fi # 构建 docker run 的 volume 挂载参数(空时不挂载) VOLUME_ARGS="" if [ -n "$VOLUME_NAME" ]; then VOLUME_ARGS="-v ${VOLUME_NAME}:/workspace/apps/web/node_modules" fi # 缓存未命中时执行 npm ci if [ "$CACHE_HIT" -eq 0 ]; then for i in 1 2 3; do echo "npm ci 尝试 $i/3 (镜像: $NPM_REGISTRY)" docker run --rm -v "$PWD:/workspace" $VOLUME_ARGS -w /workspace/apps/web docker.m.daocloud.io/library/node:20 sh -lc "npm config set registry $NPM_REGISTRY && npm ci --no-audit --no-fund" && break echo "npm ci 失败,重试 $i/3..." [ $i -eq 3 ] && exit 1 sleep 10 done else echo "跳过 npm ci,直接使用缓存的 node_modules" fi # 清理旧缓存 volume(保留最近 N 个,防止磁盘占用无限增长) if [ -n "$VOLUME_PREFIX" ]; then echo "清理旧缓存 volume(保留最近 ${KEEP_CACHE_COUNT} 个)..." ALL_VOLUMES=$(docker volume ls -q --filter "name=${VOLUME_PREFIX}" 2>/dev/null || true) if [ -n "$ALL_VOLUMES" ]; then TOTAL=$(echo "$ALL_VOLUMES" | wc -l) if [ "$TOTAL" -gt "$KEEP_CACHE_COUNT" ]; then # 按创建时间排序,保留最新的 N 个 SORTED_VOLUMES=$(for v in $ALL_VOLUMES; do CREATED=$(docker volume inspect --format '{{.CreatedAt}}' "$v" 2>/dev/null || echo "0") echo "$CREATED $v" done | sort | awk '{print $2}') # 删除超出保留数量的旧 volume REMOVE_COUNT=$((TOTAL - KEEP_CACHE_COUNT)) TO_DELETE=$(echo "$SORTED_VOLUMES" | head -n "$REMOVE_COUNT") REMOVED=0 for v in $TO_DELETE; do # 跳过当前正在使用的 volume if [ "$v" != "$VOLUME_NAME" ]; then if docker volume rm "$v" >/dev/null 2>&1; then REMOVED=$((REMOVED + 1)) fi fi done echo "已清理 $REMOVED 个旧缓存 volume,当前共 $((TOTAL - REMOVED)) 个" else echo "当前缓存 volume 数量: $TOTAL,无需清理" fi fi fi echo "=== 前端依赖安装完成 ==="