diff --git a/npm-install/README.md b/npm-install/README.md new file mode 100644 index 0000000..3e79d10 --- /dev/null +++ b/npm-install/README.md @@ -0,0 +1,211 @@ +# npm 依赖安装与缓存 Action + +这个 GitHub Action 自动处理 npm 依赖的缓存和安装,支持多种包管理器(npm、pnpm、yarn),可以大幅提升 CI/CD 流水线的执行速度。 + +## ✨ 特性 + +- 🚀 **智能缓存**: 自动缓存 node_modules,避免重复安装 +- 📦 **多包管理器支持**: 支持 npm、pnpm、yarn +- 🎯 **灵活配置**: 可自定义缓存前缀、安装命令等 +- 🔄 **Git 集成**: 可选的 git stash 功能 +- 📊 **详细输出**: 提供缓存命中状态和使用的缓存 key + +## 📋 输入参数 + +| 参数名 | 描述 | 是否必需 | 默认值 | +| ------------------- | ---------------------------- | -------- | -------------- | +| `package-manager` | 包管理器类型 (npm/pnpm/yarn) | 否 | `npm` | +| `lockfile-name` | lock 文件名称 | 否 | 自动检测 | +| `cache-prefix` | 缓存前缀名称 | 否 | `modules` | +| `node-modules-path` | node_modules 目录路径 | 否 | `node_modules` | +| `force-install` | 是否强制安装 | 否 | `false` | +| `enable-git-stash` | 安装后是否执行 git stash | 否 | `false` | +| `install-command` | 自定义安装命令(覆盖默认) | 否 | `''` | + +## 📤 输出参数 + +| 参数名 | 描述 | +| ----------- | ------------------------- | +| `cache-hit` | 是否命中缓存 (true/false) | +| `cache-key` | 使用的缓存 key | + +## 🚀 使用方法 + +### 基础用法 (npm) + +```yaml +- name: 安装npm依赖 + uses: actions/xgj/npm-install@v1 +``` + +### 使用 pnpm + +```yaml +- name: 安装pnpm依赖 + uses: actions/xgj/npm-install@v1 + with: + package-manager: "pnpm" +``` + +### 强制安装 + Git Stash + +```yaml +- name: 强制安装依赖并stash + uses: actions/xgj/npm-install@v1 + with: + package-manager: "npm" + force-install: "true" + enable-git-stash: "true" +``` + +### 自定义缓存配置 + +```yaml +- name: 自定义缓存安装 + uses: actions/xgj/npm-install@v1 + with: + package-manager: "yarn" + cache-prefix: "my-project" + node-modules-path: "./frontend/node_modules" +``` + +### 自定义安装命令 + +```yaml +- name: 使用自定义命令安装 + uses: actions/xgj/npm-install@v1 + with: + install-command: "npm ci --only=production" +``` + +### 检查缓存状态 + +```yaml +- name: 安装依赖 + id: install-deps + uses: actions/xgj/npm-install@v1 + with: + package-manager: "pnpm" + +- name: 根据缓存状态执行操作 + if: steps.install-deps.outputs.cache-hit != 'true' + run: | + echo "依赖已重新安装,执行额外步骤" + npm run build:fresh +``` + +## 📝 完整工作流示例 + +```yaml +name: CI Pipeline + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: 检出代码 + uses: actions/checkout@v4 + + - name: 设置Node.js + uses: actions/setup-node@v4 + with: + node-version: "18" + + - name: 安装依赖 + id: deps + uses: actions/xgj/npm-install@v1 + with: + package-manager: "pnpm" + force-install: "false" + + - name: 运行测试 + run: pnpm test + + - name: 构建项目 + if: steps.deps.outputs.cache-hit != 'true' + run: pnpm build +``` + +## 🔧 工作原理 + +1. **缓存 Key 生成**: 根据包管理器类型和 lock 文件生成唯一的缓存 key +2. **缓存检查**: 使用`actions/cache@v4`检查是否存在匹配的缓存 +3. **条件安装**: 仅在缓存未命中时执行依赖安装 +4. **可选操作**: 根据配置执行 git stash 等额外操作 + +## 🎯 最佳实践 + +### 1. 选择合适的包管理器 + +```yaml +# 推荐:根据项目实际使用的包管理器 +- uses: actions/xgj/npm-install@v1 + with: + package-manager: "pnpm" # 如果项目使用pnpm +``` + +### 2. 合理使用强制安装 + +```yaml +# 仅在必要时使用强制安装 +- uses: actions/xgj/npm-install@v1 + with: + force-install: "true" # 仅用于解决依赖冲突 +``` + +### 3. 自定义缓存前缀避免冲突 + +```yaml +# 为不同项目使用不同的缓存前缀 +- uses: actions/xgj/npm-install@v1 + with: + cache-prefix: "frontend-app" +``` + +### 4. 利用输出参数优化流程 + +```yaml +- name: 安装依赖 + id: install + uses: actions/xgj/npm-install@v1 + +# 仅在重新安装依赖时执行某些步骤 +- name: 重建缓存 + if: steps.install.outputs.cache-hit != 'true' + run: npm run build:cache +``` + +## 🚨 注意事项 + +1. **Git Stash**: 启用`enable-git-stash`会在安装后执行`git stash`,请确保这符合你的工作流需求 +2. **缓存大小**: node_modules 可能很大,请关注 GitHub Actions 的缓存限制 +3. **Lock 文件**: 确保 lock 文件已提交到仓库,这是缓存 key 生成的基础 +4. **权限**: 某些自定义安装命令可能需要额外的权限 + +## 🔍 故障排除 + +### 缓存未命中 + +如果缓存总是未命中,检查: + +- lock 文件是否存在且已提交 +- package.json 或 lock 文件是否有变更 +- 缓存前缀是否与之前一致 + +### 安装失败 + +如果安装失败,尝试: + +- 启用`force-install` +- 使用自定义安装命令 +- 检查 Node.js 版本兼容性 + +### Git Stash 问题 + +如果 git stash 出现问题: + +- 确保工作目录有可 stash 的变更 +- 考虑禁用`enable-git-stash` +- 在 action 之前确保 git 配置正确 diff --git a/npm-install/action.yml b/npm-install/action.yml new file mode 100644 index 0000000..ab416b7 --- /dev/null +++ b/npm-install/action.yml @@ -0,0 +1,165 @@ +name: 'npm依赖安装与缓存' +description: '自动缓存和安装npm依赖,支持多种包管理器' +branding: + icon: 'package' + color: 'blue' + +inputs: + package-manager: + description: '包管理器类型 (npm, pnpm, yarn)' + required: false + default: 'npm' + + lockfile-name: + description: 'lock文件名称' + required: false + default: 'package-lock.json' + + cache-prefix: + description: '缓存前缀名称' + required: false + default: 'modules' + + node-modules-path: + description: 'node_modules目录路径' + required: false + default: 'node_modules' + + force-install: + description: '是否强制安装 (true/false)' + required: false + default: 'false' + + enable-git-stash: + description: '安装后是否执行git stash (true/false)' + required: false + default: 'false' + + install-command: + description: '自定义安装命令(可选,会覆盖默认命令)' + required: false + default: '' + +outputs: + cache-hit: + description: '是否命中缓存 (true/false)' + value: ${{ steps.cache.outputs.cache-hit }} + + cache-key: + description: '使用的缓存key' + value: ${{ steps.cache-key.outputs.key }} + +runs: + using: 'composite' + steps: + - name: 生成缓存key + id: cache-key + shell: bash + run: | + LOCKFILE="${{ inputs.lockfile-name }}" + # 根据包管理器自动检测lock文件 + if [[ "${{ inputs.package-manager }}" == "pnpm" ]]; then + LOCKFILE="pnpm-lock.yaml" + elif [[ "${{ inputs.package-manager }}" == "yarn" ]]; then + LOCKFILE="yarn.lock" + fi + + # 如果用户没有指定lockfile-name且是npm,则使用package-lock.json + if [[ "${{ inputs.lockfile-name }}" == "package-lock.json" && "${{ inputs.package-manager }}" != "npm" ]]; then + if [[ "${{ inputs.package-manager }}" == "pnpm" ]]; then + LOCKFILE="pnpm-lock.yaml" + elif [[ "${{ inputs.package-manager }}" == "yarn" ]]; then + LOCKFILE="yarn.lock" + fi + fi + + CACHE_KEY="${{ runner.os }}-${{ inputs.cache-prefix }}-$(echo '${{ hashFiles('${LOCKFILE}') }}' | head -c 12)" + echo "key=${CACHE_KEY}" >> $GITHUB_OUTPUT + echo "lockfile=${LOCKFILE}" >> $GITHUB_OUTPUT + echo "使用锁文件: ${LOCKFILE}" + echo "缓存key: ${CACHE_KEY}" + + - name: 拉取缓存依赖 + id: cache + uses: actions/cache@v4 + with: + path: ${{ inputs.node-modules-path }} + key: ${{ steps.cache-key.outputs.key }} + restore-keys: | + ${{ runner.os }}-${{ inputs.cache-prefix }}- + + - name: 显示缓存状态 + shell: bash + run: | + if [[ "${{ steps.cache.outputs.cache-hit }}" == "true" ]]; then + echo "✅ 缓存命中,跳过依赖安装" + else + echo "⚠️ 缓存未命中,开始安装依赖" + fi + + - name: 安装依赖 + if: steps.cache.outputs.cache-hit != 'true' + shell: bash + run: | + # 如果提供了自定义安装命令,使用自定义命令 + if [[ -n "${{ inputs.install-command }}" ]]; then + echo "🔧 使用自定义安装命令: ${{ inputs.install-command }}" + ${{ inputs.install-command }} + else + # 根据包管理器选择安装命令 + case "${{ inputs.package-manager }}" in + "npm") + if [[ "${{ inputs.force-install }}" == "true" ]]; then + echo "🔧 使用npm强制安装" + npm install --force + else + echo "🔧 使用npm安装" + npm install + fi + ;; + "pnpm") + if [[ "${{ inputs.force-install }}" == "true" ]]; then + echo "🔧 使用pnpm强制安装" + pnpm install --force + else + echo "🔧 使用pnpm安装" + pnpm install + fi + ;; + "yarn") + if [[ "${{ inputs.force-install }}" == "true" ]]; then + echo "🔧 使用yarn强制安装" + yarn install --force + else + echo "🔧 使用yarn安装" + yarn install + fi + ;; + *) + echo "❌ 不支持的包管理器: ${{ inputs.package-manager }}" + exit 1 + ;; + esac + fi + + echo "✅ 依赖安装完成" + + - name: 执行Git Stash + if: steps.cache.outputs.cache-hit != 'true' && inputs.enable-git-stash == 'true' + shell: bash + run: | + echo "🔄 执行git stash..." + git stash + echo "✅ git stash完成" + + - name: 安装总结 + shell: bash + run: | + echo "📦 依赖安装总结:" + echo " - 包管理器: ${{ inputs.package-manager }}" + echo " - 缓存命中: ${{ steps.cache.outputs.cache-hit }}" + echo " - 缓存key: ${{ steps.cache-key.outputs.key }}" + if [[ "${{ steps.cache.outputs.cache-hit }}" != "true" ]]; then + echo " - 强制安装: ${{ inputs.force-install }}" + echo " - Git Stash: ${{ inputs.enable-git-stash }}" + fi diff --git a/npm-install/examples/basic-npm.yml b/npm-install/examples/basic-npm.yml new file mode 100644 index 0000000..adc4020 --- /dev/null +++ b/npm-install/examples/basic-npm.yml @@ -0,0 +1,35 @@ +# 基础npm项目示例 +name: 基础npm项目CI + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: 检出代码 + uses: actions/checkout@v4 + + - name: 设置Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + cache: 'npm' + + - name: 安装依赖 + uses: actions/xgj/npm-install@v1 + # 使用默认配置:npm包管理器,不强制安装,不启用git stash + + - name: 运行linting + run: npm run lint + + - name: 运行测试 + run: npm test + + - name: 构建项目 + run: npm run build diff --git a/npm-install/examples/custom-command.yml b/npm-install/examples/custom-command.yml new file mode 100644 index 0000000..70b3a6b --- /dev/null +++ b/npm-install/examples/custom-command.yml @@ -0,0 +1,61 @@ +# 自定义安装命令示例 +name: 自定义安装命令 + +on: + push: + branches: [ production ] + +jobs: + production-build: + runs-on: ubuntu-latest + + steps: + - name: 检出代码 + uses: actions/checkout@v4 + + - name: 设置Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: 生产环境依赖安装 + uses: actions/xgj/npm-install@v1 + with: + package-manager: 'npm' + install-command: 'npm ci --only=production --ignore-scripts' + cache-prefix: 'production' + + - name: 验证依赖 + run: | + echo "检查生产依赖..." + npm ls --depth=0 --only=production + + - name: 构建生产版本 + run: npm run build:production + + - name: 运行生产测试 + run: npm run test:production + + development-build: + runs-on: ubuntu-latest + + steps: + - name: 检出代码 + uses: actions/checkout@v4 + + - name: 设置Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: 开发环境依赖安装 + uses: actions/xgj/npm-install@v1 + with: + package-manager: 'npm' + install-command: 'npm install --include=dev' + cache-prefix: 'development' + + - name: 运行开发工具 + run: | + npm run lint + npm run test:coverage diff --git a/npm-install/examples/force-install-with-stash.yml b/npm-install/examples/force-install-with-stash.yml new file mode 100644 index 0000000..2b072b3 --- /dev/null +++ b/npm-install/examples/force-install-with-stash.yml @@ -0,0 +1,50 @@ +# 强制安装 + Git Stash 示例 +name: 强制安装示例 + +on: + push: + branches: [ hotfix/* ] + workflow_dispatch: + +jobs: + force-install: + runs-on: ubuntu-latest + + steps: + - name: 检出代码 + uses: actions/checkout@v4 + + - name: 设置Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: 强制安装依赖 + id: force-install + uses: actions/xgj/npm-install@v1 + with: + package-manager: 'npm' + force-install: 'true' + enable-git-stash: 'true' + cache-prefix: 'hotfix' + + - name: 检查是否重新安装 + run: | + if [[ "${{ steps.force-install.outputs.cache-hit }}" != "true" ]]; then + echo "✅ 依赖已重新安装" + echo "🔄 已执行git stash" + else + echo "ℹ️ 使用了缓存" + fi + + - name: 清理构建缓存 + if: steps.force-install.outputs.cache-hit != 'true' + run: | + npm run clean + echo "🧹 清理完成" + + - name: 重新构建 + run: npm run build + + - name: 运行完整测试套件 + run: npm run test:full diff --git a/npm-install/examples/multi-project.yml b/npm-install/examples/multi-project.yml new file mode 100644 index 0000000..29638d1 --- /dev/null +++ b/npm-install/examples/multi-project.yml @@ -0,0 +1,89 @@ +# 多项目/多目录示例 +name: 多项目构建 + +on: + push: + branches: [ main ] + +jobs: + frontend: + runs-on: ubuntu-latest + + steps: + - name: 检出代码 + uses: actions/checkout@v4 + + - name: 设置Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: 安装前端依赖 + uses: actions/xgj/npm-install@v1 + with: + package-manager: 'yarn' + node-modules-path: './frontend/node_modules' + cache-prefix: 'frontend' + + - name: 构建前端 + working-directory: ./frontend + run: yarn build + + - name: 前端测试 + working-directory: ./frontend + run: yarn test + + backend: + runs-on: ubuntu-latest + + steps: + - name: 检出代码 + uses: actions/checkout@v4 + + - name: 设置Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: 安装后端依赖 + uses: actions/xgj/npm-install@v1 + with: + package-manager: 'npm' + node-modules-path: './backend/node_modules' + cache-prefix: 'backend' + + - name: 后端测试 + working-directory: ./backend + run: npm test + + - name: 构建后端 + working-directory: ./backend + run: npm run build + + docs: + runs-on: ubuntu-latest + + steps: + - name: 检出代码 + uses: actions/checkout@v4 + + - name: 设置Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: 安装文档依赖 + uses: actions/xgj/npm-install@v1 + with: + package-manager: 'pnpm' + node-modules-path: './docs/node_modules' + cache-prefix: 'docs' + + - name: 构建文档 + working-directory: ./docs + run: pnpm build + + - name: 部署文档 + if: github.ref == 'refs/heads/main' + working-directory: ./docs + run: pnpm deploy diff --git a/npm-install/examples/pnpm-monorepo.yml b/npm-install/examples/pnpm-monorepo.yml new file mode 100644 index 0000000..a6ddda9 --- /dev/null +++ b/npm-install/examples/pnpm-monorepo.yml @@ -0,0 +1,52 @@ +# pnpm monorepo项目示例 +name: pnpm Monorepo CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + install-and-test: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [16, 18, 20] + + steps: + - name: 检出代码 + uses: actions/checkout@v4 + + - name: 设置pnpm + uses: pnpm/action-setup@v2 + with: + version: 8 + + - name: 设置Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'pnpm' + + - name: 安装依赖 + id: install-deps + uses: actions/xgj/npm-install@v1 + with: + package-manager: 'pnpm' + cache-prefix: 'monorepo' + + - name: 显示缓存状态 + run: | + echo "缓存命中: ${{ steps.install-deps.outputs.cache-hit }}" + echo "缓存Key: ${{ steps.install-deps.outputs.cache-key }}" + + - name: 运行所有包的测试 + run: pnpm run test --recursive + + - name: 构建所有包 + run: pnpm run build --recursive + + - name: 类型检查 + run: pnpm run type-check --recursive