单体仓库工作流程Skill monorepo-workflows

这个技能专注于单体仓库(monorepo)环境下的开发工作流程、CI/CD模式、版本管理、发布策略和团队协作实践,提供全面指导和自动化工具配置。关键词:单体仓库、CI/CD、版本控制、工作流程、自动化、DevOps、软件开发。

DevOps 0 次安装 0 次浏览 更新于 3/25/2026

名称: 单体仓库工作流程 用户可调用: false 描述: 用于设置CI/CD、实现版本控制、优化工作流程或管理单体仓库开发工作流中的发布,包括版本管理、发布和团队协作实践。 允许工具:

  • 读取
  • 写入
  • 编辑
  • Bash
  • Glob
  • Grep

单体仓库工作流程技能

概述

这个技能提供全面的指导,用于单体仓库环境下的开发工作流程、CI/CD模式、版本管理、发布策略和团队协作实践。

开发工作流程

本地开发设置

配置高效的本地开发环境。

Package.json 脚本:

{
  "scripts": {
    "dev": "turbo run dev --parallel",
    "dev:web": "turbo run dev --filter=@myorg/web...",
    "build": "turbo run build",
    "test": "turbo run test",
    "lint": "turbo run lint",
    "clean": "turbo run clean && rm -rf node_modules",
    "reset": "pnpm clean && pnpm install"
  }
}

环境设置脚本:

#!/bin/bash
# scripts/setup-dev.sh

echo "设置开发环境..."

# 检查 Node 版本
required_node_version="18.0.0"
current_node_version=$(node -v | cut -d'v' -f2)

if [ "$(printf '%s
' "$required_node_version" \
  "$current_node_version" | sort -V | head -n1)" != \
  "$required_node_version" ]; then
  echo "错误: 需要 Node.js $required_node_version 或更高版本"
  exit 1
fi

# 启用 pnpm
corepack enable pnpm

# 安装依赖
pnpm install

# 构建所有包
pnpm run build

# 设置 git hooks
pnpm husky install

echo "开发环境就绪!"

跨包开发

同时处理多个包。

使用工作区链接:

# 所有工作区包自动链接
pnpm install

# 验证链接
pnpm list --depth 1

开发使用监视模式:

{
  "scripts": {
    "dev:packages": "turbo run dev --filter='./packages/*'",
    "dev:apps": "turbo run dev --filter='./apps/*'"
  }
}

并发开发:

{
  "scripts": {
    "dev:all": "concurrently \"pnpm:dev:*\"",
    "dev:ui": "pnpm --filter @myorg/ui run dev",
    "dev:web": "pnpm --filter @myorg/web run dev",
    "dev:api": "pnpm --filter @myorg/api run dev"
  },
  "devDependencies": {
    "concurrently": "^8.2.2"
  }
}

热模块重载

启用跨包边界的快速刷新。

Vite 配置:

// apps/web/vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  server: {
    watch: {
      // 监视工作区包
      ignored: ['!**/node_modules/@myorg/**']
    }
  },
  optimizeDeps: {
    // 强制优化工作区包
    include: ['@myorg/ui', '@myorg/utils']
  }
});

Next.js 配置:

// apps/web/next.config.js
const withTM = require('next-transpile-modules')([
  '@myorg/ui',
  '@myorg/utils'
]);

module.exports = withTM({
  reactStrictMode: true,
  experimental: {
    esmExternals: 'loose'
  }
});

跨包调试

设置单体仓库项目的调试。

VS Code 启动配置:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "调试 Web 应用",
      "type": "node",
      "request": "launch",
      "runtimeExecutable": "pnpm",
      "runtimeArgs": ["--filter", "@myorg/web", "run", "dev"],
      "skipFiles": ["<node_internals>/**"],
      "console": "integratedTerminal"
    },
    {
      "name": "调试 API",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/apps/api/src/index.ts",
      "preLaunchTask": "build-dependencies",
      "outFiles": ["${workspaceFolder}/apps/api/dist/**/*.js"],
      "sourceMaps": true
    },
    {
      "name": "调试测试",
      "type": "node",
      "request": "launch",
      "runtimeExecutable": "pnpm",
      "runtimeArgs": ["test", "--", "--inspect-brk"],
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen"
    }
  ]
}

Chrome DevTools 调试:

{
  "scripts": {
    "debug:web": "NODE_OPTIONS='--inspect' pnpm --filter @myorg/web run dev",
    "debug:api": "NODE_OPTIONS='--inspect-brk' pnpm --filter @myorg/api run dev"
  }
}

测试策略

全面的单体仓库包测试。

测试组织:

packages/ui/
├── src/
│   ├── components/
│   │   ├── Button/
│   │   │   ├── Button.tsx
│   │   │   └── Button.test.tsx
│   │   └── Input/
│   │       ├── Input.tsx
│   │       └── Input.test.tsx
└── __tests__/
    └── integration/
        └── form.test.tsx

共享测试配置:

// packages/test-config/jest.config.js
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
  moduleNameMapper: {
    '^@myorg/(.*)$': '<rootDir>/../../packages/$1/src'
  },
  collectCoverageFrom: [
    'src/**/*.{ts,tsx}',
    '!src/**/*.d.ts',
    '!src/**/*.stories.tsx'
  ]
};

包测试脚本:

{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage",
    "test:ci": "jest --ci --coverage --maxWorkers=2"
  }
}

集成测试:

// __tests__/integration/package-interaction.test.ts
import { Button } from '@myorg/ui';
import { formatDate } from '@myorg/utils';

describe('包集成', () => {
  it('在组件中使用工具', () => {
    const date = new Date('2024-01-01');
    const formatted = formatDate(date);
    expect(formatted).toBe('2024-01-01');
  });
});

CI/CD 模式

按包矩阵构建

并行跨包运行构建。

# .github/workflows/ci.yml
名称: CI
用户可调用: false

在:
  拉取请求:
  推送:
    分支: [main]

工作:
  设置:
    运行于: ubuntu-latest
    输出:
      包: ${{ steps.packages.outputs.packages }}
    步骤:
      - 使用: actions/checkout@v4
      - 名称: 获取更改的包
        id: packages
        运行: |
          packages=$(pnpm -r list --json | jq -r '.[].name' | jq -R -s -c 'split("
")[:-1]')
          echo "packages=$packages" >> $GITHUB_OUTPUT

  构建:
    需要: 设置
    运行于: ubuntu-latest
    策略:
      矩阵:
        包: ${{ fromJson(needs.setup.outputs.packages) }}
    步骤:
      - 使用: actions/checkout@v4
      - 使用: pnpm/action-setup@v2
      - 使用: actions/setup-node@v4
        带:
          node-版本: 18
          缓存: 'pnpm'
      - 运行: pnpm install --frozen-lockfile
      - 运行: pnpm --filter ${{ matrix.package }} run build
      - 运行: pnpm --filter ${{ matrix.package }} run test

仅影响CI

仅构建和测试更改的包。

# .github/workflows/ci.yml
名称: CI
用户可调用: false

在:
  拉取请求:
  推送:
    分支: [main]

工作:
  受影响:
    运行于: ubuntu-latest
    步骤:
      - 使用: actions/checkout@v4
        带:
          获取深度: 0

      - 使用: pnpm/action-setup@v2
      - 使用: actions/setup-node@v4
        带:
          node-版本: 18
          缓存: 'pnpm'

      - 名称: 安装依赖
        运行: pnpm install --frozen-lockfile

      - 名称: 构建受影响
        运行: pnpm turbo run build --filter=[origin/main...HEAD]

      - 名称: 测试受影响
        运行: pnpm turbo run test --filter=[origin/main...HEAD]

      - 名称: 代码检查受影响
        运行: pnpm turbo run lint --filter=[origin/main...HEAD]

使用 Nx 受影响:

- 名称: 构建受影响
  运行: npx nx affected --target=build --base=origin/main --head=HEAD

- 名称: 测试受影响
  运行: npx nx affected --target=test --base=origin/main --head=HEAD --parallel=3

分布式任务执行

跨多个CI代理分配任务。

# .github/workflows/ci.yml
名称: 分布式CI
用户可调用: false

在: [拉取请求, 推送]

工作:
  设置:
    运行于: ubuntu-latest
    输出:
      任务: ${{ steps.tasks.outputs.tasks }}
    步骤:
      - 使用: actions/checkout@v4
      - 名称: 生成任务列表
        id: tasks
        运行: |
          tasks=$(pnpm turbo run build test --dry-run=json | jq -c '.tasks')
          echo "tasks=$tasks" >> $GITHUB_OUTPUT

  执行:
    需要: 设置
    运行于: ubuntu-latest
    策略:
      矩阵:
        任务: ${{ fromJson(needs.setup.outputs.tasks) }}
      最大并行: 10
    步骤:
      - 使用: actions/checkout@v4
      - 使用: pnpm/action-setup@v2
      - 运行: pnpm install --frozen-lockfile
      - 运行: ${{ matrix.task.command }}

并行管道作业

并发执行独立作业。

# .github/workflows/ci.yml
名称: 并行CI
用户可调用: false

在: [拉取请求, 推送]

工作:
  安装:
    运行于: ubuntu-latest
    步骤:
      - 使用: actions/checkout@v4
      - 使用: pnpm/action-setup@v2
      - 使用: actions/setup-node@v4
        带:
          node-版本: 18
          缓存: 'pnpm'
      - 运行: pnpm install --frozen-lockfile
      - 使用: actions/cache@v3
        带:
          路径: node_modules
          键: ${{ runner.os }}-node-modules-${{ hashFiles('pnpm-lock.yaml') }}

  代码检查:
    需要: 安装
    运行于: ubuntu-latest
    步骤:
      - 使用: actions/checkout@v4
      - 使用: actions/cache@v3
        带:
          路径: node_modules
          键: ${{ runner.os }}-node-modules-${{ hashFiles('pnpm-lock.yaml') }}
      - 运行: pnpm turbo run lint

  测试:
    需要: 安装
    运行于: ubuntu-latest
    步骤:
      - 使用: actions/checkout@v4
      - 使用: actions/cache@v3
        带:
          路径: node_modules
          键: ${{ runner.os }}-node-modules-${{ hashFiles('pnpm-lock.yaml') }}
      - 运行: pnpm turbo run test --coverage

  构建:
    需要: 安装
    运行于: ubuntu-latest
    步骤:
      - 使用: actions/checkout@v4
      - 使用: actions/cache@v3
        带:
          路径: node_modules
          键: ${{ runner.os }}-node-modules-${{ hashFiles('pnpm-lock.yaml') }}
      - 运行: pnpm turbo run build

选择性部署

仅部署更改的应用程序。

# .github/workflows/deploy.yml
名称: 部署
用户可调用: false

在:
  推送:
    分支: [main]

工作:
  部署-web:
    运行于: ubuntu-latest
    步骤:
      - 使用: actions/checkout@v4
        带:
          获取深度: 0

      - 名称: 检查 web 是否更改
        id: changed
        运行: |
          if git diff --name-only HEAD^ HEAD | grep -q "^apps/web/"; then
            echo "changed=true" >> $GITHUB_OUTPUT
          fi

      - 名称: 部署 web
        如果: steps.changed.outputs.changed == 'true'
        运行: |
          pnpm turbo run build --filter=@myorg/web
          # 部署命令在这里

  部署-api:
    运行于: ubuntu-latest
    步骤:
      - 使用: actions/checkout@v4
        带:
          获取深度: 0

      - 名称: 检查 API 是否更改
        id: changed
        运行: |
          if git diff --name-only HEAD^ HEAD | grep -q "^apps/api/"; then
            echo "changed=true" >> $GITHUB_OUTPUT
          fi

      - 名称: 部署 API
        如果: steps.changed.outputs.changed == 'true'
        运行: |
          pnpm turbo run build --filter=@myorg/api
          # 部署命令在这里

版本管理

Changesets 工作流程

自动化版本控制和变更日志生成。

安装和设置:

pnpm add -DW @changesets/cli
pnpm changeset init

配置:

{
  "changelog": "@changesets/cli/changelog",
  "commit": false,
  "fixed": [],
  "linked": [],
  "access": "restricted",
  "baseBranch": "main",
  "updateInternalDependencies": "patch",
  "ignore": [
    "@myorg/private-package"
  ]
}

创建 changesets:

# 交互式 changeset 创建
pnpm changeset

# 示例生成的 changeset 文件:
# .changeset/cool-feature.md
---
"@myorg/ui": minor
"@myorg/web": patch
---

添加新 Button 变体并更新文档

版本提升:

# 消费 changesets 并更新版本
pnpm changeset version

# 更新 package.json 版本
# 更新 CHANGELOG.md 文件
# 删除已消费的 changeset 文件

发布:

# 构建并发布更改的包
pnpm changeset publish

# 推送标签
git push --follow-tags

GitHub Action 集成:

# .github/workflows/release.yml
名称: 发布
用户可调用: false

在:
  推送:
    分支: [main]

工作:
  发布:
    运行于: ubuntu-latest
    步骤:
      - 使用: actions/checkout@v4
      - 使用: pnpm/action-setup@v2
      - 使用: actions/setup-node@v4
        带:
          node-版本: 18
          缓存: 'pnpm'

      - 运行: pnpm install --frozen-lockfile
      - 运行: pnpm turbo run build

      - 名称: 创建发布拉取请求或发布
        使用: changesets/action@v1
        带:
          publish: pnpm changeset publish
        环境:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

规范提交

标准化提交消息格式用于自动化版本控制。

提交消息格式:

<类型>(<范围>): <主题>

<正文>

<页脚>

示例:

git commit -m "feat(ui): 添加 Button 组件变体"
git commit -m "fix(api): 解决身份验证错误"
git commit -m "docs(readme): 更新安装说明"
git commit -m "chore(deps): 更新依赖"

Commitlint 配置:

// commitlint.config.js
module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'scope-enum': [
      2,
      'always',
      ['ui', 'api', 'web', 'mobile', 'utils', 'config', 'ci']
    ],
    'type-enum': [
      2,
      'always',
      [
        'feat',
        'fix',
        'docs',
        'style',
        'refactor',
        'perf',
        'test',
        'chore',
        'revert'
      ]
    ]
  }
};

Husky 预提交钩子:

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

pnpm commitlint --edit $1

自动化变更日志

从提交历史生成变更日志。

使用 conventional-changelog:

pnpm add -DW conventional-changelog-cli
{
  "scripts": {
    "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
    "version": "pnpm run changelog && git add CHANGELOG.md"
  }
}

使用 semantic-release:

{
  "branches": ["main"],
  "plugins": [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    "@semantic-release/changelog",
    "@semantic-release/npm",
    "@semantic-release/github",
    "@semantic-release/git"
  ]
}

版本提升策略

不同的版本管理方法。

独立版本控制:

{
  "version": "independent",
  "packages": [
    "packages/*"
  ]
}

每个包有自己的版本:

  • @myorg/ui@2.1.0
  • @myorg/utils@1.5.3
  • @myorg/api@3.0.1

固定版本控制:

{
  "version": "1.2.3",
  "packages": [
    "packages/*"
  ]
}

所有包共享相同版本:

  • @myorg/ui@1.2.3
  • @myorg/utils@1.2.3
  • @myorg/api@1.2.3

预发布版本

管理 alpha、beta 和发布候选版本。

使用 changesets:

# 进入预发布模式
pnpm changeset pre enter next

# 创建 changeset
pnpm changeset

# 版本包(创建 -next.0 版本)
pnpm changeset version

# 发布预发布
pnpm changeset publish --tag next

# 退出预发布模式
pnpm changeset pre exit

结果:

@myorg/ui@2.1.0-next.0
@myorg/ui@2.1.0-next.1
@myorg/ui@2.1.0

使用 NPM 分发标签:

# 发布到特定标签
pnpm publish --tag beta

# 从标签安装
pnpm add @myorg/ui@beta

# 列出标签
pnpm view @myorg/ui dist-tags

发布工作流程

NPM 发布

将包发布到 NPM 注册表。

配置:

{
  "name": "@myorg/ui",
  "version": "1.0.0",
  "publishConfig": {
    "access": "public",
    "registry": "https://registry.npmjs.org/"
  },
  "files": [
    "dist",
    "README.md",
    "LICENSE"
  ]
}

发布脚本:

#!/bin/bash
# scripts/publish.sh

# 构建所有包
pnpm turbo run build

# 运行测试
pnpm turbo run test

# 版本包
pnpm changeset version

# 发布
pnpm changeset publish

# 推送标签
git push --follow-tags

NPM 自动化令牌:

# 在 npmjs.com 生成自动化令牌
# 添加到 GitHub 机密作为 NPM_TOKEN

# 在 CI 中使用
echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc
pnpm publish --no-git-checks

GitHub 包

发布到 GitHub 包注册表。

配置:

{
  "name": "@myorg/ui",
  "publishConfig": {
    "registry": "https://npm.pkg.github.com/"
  }
}

.npmrc 用于发布:

@myorg:registry=https://npm.pkg.github.com/
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}

GitHub Action:

- 名称: 发布到 GitHub 包
  运行: pnpm changeset publish
  环境:
    NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

金丝雀发布

从 PR 或分支发布测试版本。

金丝雀脚本:

#!/bin/bash
# scripts/canary-release.sh

# 获取短提交哈希
COMMIT_HASH=$(git rev-parse --short HEAD)

# 使用金丝雀标识符更新版本
pnpm changeset version --snapshot canary-$COMMIT_HASH

# 使用金丝雀标签发布
pnpm changeset publish --tag canary

echo "已发布金丝雀版本: canary-$COMMIT_HASH"

GitHub Action 用于金丝雀:

名称: 金丝雀发布
用户可调用: false

在:
  拉取请求:
    类型: [labeled]

工作:
  金丝雀:
    如果: contains(github.event.pull_request.labels.*.name, 'canary')
    运行于: ubuntu-latest
    步骤:
      - 使用: actions/checkout@v4
      - 使用: pnpm/action-setup@v2
      - 运行: pnpm install --frozen-lockfile
      - 运行: pnpm turbo run build
      - 运行: bash scripts/canary-release.sh
        环境:
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

包来源

启用包来源以增强供应链安全。

NPM 来源:

- 名称: 发布并带来源
  运行: pnpm publish --provenance --access public
  环境:
    NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

Package.json 元数据:

{
  "repository": {
    "type": "git",
    "url": "https://github.com/myorg/monorepo.git",
    "directory": "packages/ui"
  }
}

注册表认证

管理多个注册表的认证。

.npmrc 配置:

# 公共包
@myorg:registry=https://registry.npmjs.org/
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

# 私有包
@private:registry=https://npm.pkg.github.com/
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}

# 企业注册表
@corp:registry=https://npm.corp.example.com/
//npm.corp.example.com/:_authToken=${CORP_TOKEN}

环境特定认证:

# 开发
cp .npmrc.dev .npmrc

# CI
cp .npmrc.ci .npmrc

代码共享

共享 ESLint 配置

跨包维护一致的代码检查。

创建配置包:

// packages/eslint-config/index.js
module.exports = {
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:react/recommended',
    'plugin:react-hooks/recommended'
  ],
  rules: {
    'no-console': 'warn',
    '@typescript-eslint/no-unused-vars': 'error',
    'react/react-in-jsx-scope': 'off'
  },
  settings: {
    react: {
      version: 'detect'
    }
  }
};

Package.json:

{
  "name": "@myorg/eslint-config",
  "version": "1.0.0",
  "main": "index.js",
  "peerDependencies": {
    "eslint": "^8.0.0",
    "typescript": "^5.0.0"
  }
}

在包中使用:

{
  "extends": "@myorg/eslint-config"
}

共享 TypeScript 配置

跨包共享 TypeScript 配置。

基础配置:

// packages/tsconfig/base.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "jsx": "react-jsx",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  }
}

React 配置:

// packages/tsconfig/react.json
{
  "extends": "./base.json",
  "compilerOptions": {
    "jsx": "react-jsx",
    "lib": ["ES2022", "DOM", "DOM.Iterable"]
  }
}

Node 配置:

// packages/tsconfig/node.json
{
  "extends": "./base.json",
  "compilerOptions": {
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "lib": ["ES2022"]
  }
}

包使用:

// apps/web/tsconfig.json
{
  "extends": "@myorg/tsconfig/react.json",
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src"],
  "exclude": ["node_modules"]
}

共享测试设置

通用测试配置和工具。

Jest 预设:

// packages/test-config/jest-preset.js
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
  moduleNameMapper: {
    '^@myorg/(.*)$': '<rootDir>/../../packages/$1/src',
    '\\.(css|less|scss|sass)$': 'identity-obj-proxy'
  },
  collectCoverageFrom: [
    'src/**/*.{ts,tsx}',
    '!src/**/*.d.ts',
    '!src/**/*.stories.tsx',
    '!src/**/index.ts'
  ],
  coverageThresholds: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    }
  }
};

测试工具:

// packages/test-utils/src/index.ts
import { render, RenderOptions } from '@testing-library/react';
import { ReactElement } from 'react';

export function customRender(
  ui: ReactElement,
  options?: RenderOptions
) {
  return render(ui, {
    wrapper: ({ children }) => children,
    ...options
  });
}

export * from '@testing-library/react';
export { customRender as render };

包使用:

// packages/ui/jest.config.js
{
  "preset": "@myorg/test-config"
}
// packages/ui/src/Button.test.tsx
import { render, screen } from '@myorg/test-utils';
import { Button } from './Button';

describe('Button', () => {
  it('正确渲染', () => {
    render(<Button>点击我</Button>);
    expect(screen.getByText('点击我')).toBeInTheDocument();
  });
});

共享构建配置

通用构建工具配置。

Vite 配置:

// packages/build-config/vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import dts from 'vite-plugin-dts';

export function createConfig() {
  return defineConfig({
    plugins: [
      react(),
      dts({
        insertTypesEntry: true
      })
    ],
    build: {
      lib: {
        entry: 'src/index.ts',
        formats: ['es', 'cjs'],
        fileName: (format) => `index.${format}.js`
      },
      rollupOptions: {
        external: ['react', 'react-dom'],
        output: {
          globals: {
            react: 'React',
            'react-dom': 'ReactDOM'
          }
        }
      }
    }
  });
}

包使用:

// packages/ui/vite.config.ts
import { createConfig } from '@myorg/build-config';

export default createConfig();

包模板

新包的标准模板。

模板结构:

templates/package/
├── package.json.template
├── tsconfig.json
├── README.md.template
├── src/
│   └── index.ts
└── __tests__/
    └── index.test.ts

生成器脚本:

// scripts/create-package.ts
import fs from 'fs';
import path from 'path';

interface PackageOptions {
  name: string;
  type: 'library' | 'app';
  description: string;
}

function createPackage({ name, type, description }: PackageOptions) {
  const dir = path.join('packages', name);
  const template = path.join('templates', type);

  // 复制模板
  fs.cpSync(template, dir, { recursive: true });

  // 更新 package.json
  const pkgPath = path.join(dir, 'package.json');
  const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
  pkg.name = `@myorg/${name}`;
  pkg.description = description;
  fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));

  console.log(`已创建包: @myorg/${name}`);
}

迁移策略

从多仓库到单体仓库

将多个仓库迁移到单体仓库。

迁移步骤:

#!/bin/bash
# scripts/migrate-to-monorepo.sh

# 1. 创建单体仓库结构
mkdir my-monorepo
cd my-monorepo

# 2. 初始化 git 并保持干净历史
git init
git commit --allow-empty -m "初始提交"

# 3. 添加第一个仓库并保留历史
git remote add -f repo1 ../old-repo1
git merge repo1/main --allow-unrelated-histories
mkdir -p packages/package1
git mv * packages/package1/
git commit -m "将 repo1 移动到 packages/package1"

# 4. 添加第二个仓库并保留历史
git remote add -f repo2 ../old-repo2
git merge repo2/main --allow-unrelated-histories
mkdir -p packages/package2
git mv * packages/package2/
git commit -m "将 repo2 移动到 packages/package2"

# 5. 设置工作区
cat > package.json << EOF
{
  "name": "my-monorepo",
  "private": true,
  "workspaces": ["packages/*"]
}
EOF

git add package.json
git commit -m "添加工作区配置"

单体仓库拆分

从单体仓库中提取包到单独仓库。

拆分脚本:

#!/bin/bash
# scripts/split-package.sh

PACKAGE=$1
TARGET_REPO=$2

# 为包过滤 git 历史
git filter-branch --prune-empty --subdirectory-filter packages/$PACKAGE -- --all

# 推送到新仓库
git remote add origin $TARGET_REPO
git push -u origin main

# 清理原始单体仓库
cd ../monorepo
rm -rf packages/$PACKAGE
git add .
git commit -m "移除 $PACKAGE (已移动到单独仓库)"

增量采用

逐步采用单体仓库实践。

阶段方法:

  1. 移动包: 一次迁移一个仓库
  2. 添加工具: 逐步实现 Turborepo/Nx
  3. 优化构建: 启用缓存和受影响分析
  4. 自动化工作流程: 设置 CI/CD 和发布

渐进式迁移:

{
  "workspaces": [
    "packages/migrated/*",
    "legacy/*"
  ]
}

工具迁移

在单体仓库工具之间切换。

Lerna 到 Turborepo:

# 1. 安装 Turborepo
pnpm add -DW turbo

# 2. 创建 turbo.json
cat > turbo.json << EOF
{
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"]
    }
  }
}
EOF

# 3. 移除 Lerna
pnpm remove -DW lerna
rm lerna.json

# 4. 更新脚本
# 将 "lerna run build" 替换为 "turbo run build"

NPM 到 PNPM:

# 1. 安装 PNPM
corepack enable pnpm

# 2. 创建工作区文件
cat > pnpm-workspace.yaml << EOF
packages:
  - 'packages/*'
  - 'apps/*'
EOF

# 3. 移除旧文件
rm -rf node_modules package-lock.json

# 4. 使用 PNPM 安装
pnpm install

Git 工作流程

分支策略

有效的单体仓库开发分支策略。

功能分支:

# 创建功能分支
git checkout -b feature/add-button-component

# 在特定包上工作
cd packages/ui
# 进行更改

# 使用规范格式提交
git commit -m "feat(ui): 添加 Button 组件"

# 推送并创建 PR
git push -u origin feature/add-button-component

发布分支:

# 创建发布分支
git checkout -b release/v2.0.0

# 版本包
pnpm changeset version

# 提交并标记
git commit -m "chore: 为 v2.0.0 版本包"
git tag v2.0.0

# 合并到 main
git checkout main
git merge release/v2.0.0

拉取请求结构

组织 PR 以高效审查。

PR 模板:

## 描述
更改的简要描述

## 更改的包
- @myorg/ui
- @myorg/web

## 更改类型
- [ ] 错误修复
- [ ] 新功能
- [ ] 破坏性更改
- [ ] 文档

## 检查清单
- [ ] 测试已添加/更新
- [ ] 文档已更新
- [ ] 已添加 changeset
- [ ] 无破坏性更改(或已记录)
- [ ] 所有 CI 检查通过

CODEOWNERS:

# 全局所有者
* @myorg/core-team

# 包特定所有者
/packages/ui/ @myorg/frontend-team
/packages/api/ @myorg/backend-team
/apps/web/ @myorg/web-team
/apps/mobile/ @myorg/mobile-team

代码审查实践

在单体仓库上下文中的有效代码审查。

审查检查清单:

  • [ ] 更改限于必要包
  • [ ] 无不必要的依赖添加
  • [ ] 测试覆盖新/更改代码
  • [ ] 文档已更新
  • [ ] 破坏性更改已记录
  • [ ] 已添加 changesets
  • [ ] 未引入循环依赖

自动化检查:

# .github/workflows/pr-checks.yml
- 名称: 检查循环依赖
  运行: pnpm check:circular

- 名称: 检查缺失的 changesets
  运行: pnpm changeset status

- 名称: 验证包边界
  运行: pnpm lint:boundaries

合并策略

有效的单体仓库合并策略。

压缩合并(推荐):

# PR 作为单个提交合并
git merge --squash feature/add-button

# 使用规范格式提交
git commit -m "feat(ui): 添加 Button 组件 (#123)"

变基合并:

# 变基功能分支
git rebase main

# 快速前向合并
git merge --ff-only feature/add-button

受保护包

限制对关键包的更改。

GitHub 分支保护:

# .github/settings.yml
branches:
  - name: main
    protection:
      required_pull_request_reviews:
        required_approving_review_count: 2
        dismiss_stale_reviews: true
      required_status_checks:
        strict: true
        contexts:
          - build
          - test
          - lint

包特定保护:

# CODEOWNERS
/packages/core/ @myorg/core-maintainers
/packages/security/ @myorg/security-team

文档实践

包 README

每个包的全面文档。

README 模板:

# @myorg/ui

用于 MyOrg 应用的 React 组件库。

## 安装

pnpm add @myorg/ui

## 使用

import { Button } from '@myorg/ui';

function App() {
  return <Button onClick={() => alert('已点击!')}>点击我</Button>;
}

## 组件

- Button
- Input
- Modal

## API 参考

查看 [API.md](./API.md) 获取详细文档。

## 开发

pnpm run dev
pnpm run build
pnpm run test

## 贡献

查看 [CONTRIBUTING.md](../../CONTRIBUTING.md)。

## 许可证

MIT

架构决策记录

记录重要的架构决策。

ADR 模板:

# ADR-001: 使用 Turborepo 进行构建编排

## 状态
已接受

## 上下文
需要高效的构建系统用于具有 20+ 包的单体仓库。

## 决策
使用 Turborepo 进行任务编排和缓存。

## 后果
积极:
- 具有智能缓存的快速构建
- 简单配置
- 支持远程缓存

消极:
- 额外依赖
- 团队学习曲线

## 考虑的替代方案
- Nx: 更多功能但学习曲线更陡
- Lerna: 更简单但缺乏缓存

## 日期
2024-01-15

API 文档

生成和维护 API 文档。

TypeDoc 配置:

{
  "entryPoints": ["packages/*/src/index.ts"],
  "out": "docs/api",
  "excludePrivate": true,
  "excludeProtected": true,
  "categorizeByGroup": true,
  "categoryOrder": ["Components", "Hooks", "Utilities", "*"]
}

生成文档:

pnpm typedoc --options typedoc.json

入职指南

帮助新开发人员快速上手。

ONBOARDING.md:

# 开发人员入职

## 先决条件
- Node.js 18+
- PNPM 8+

## 设置

# 克隆仓库
git clone https://github.com/myorg/monorepo.git

# 安装依赖
pnpm install

# 构建所有包
pnpm run build

## 开发

# 启动开发服务器
pnpm run dev

# 运行测试
pnpm run test

## 项目结构

/apps          - 应用
/packages      - 共享包
/docs          - 文档

## 常见任务

查看 [COMMON_TASKS.md](./COMMON_TASKS.md)。

运行手册

操作程序和故障排除。

RUNBOOK.md:

# 操作运行手册

## 构建失败

### 症状
构建失败并显示 "找不到模块 '@myorg/ui'"

### 解决方案
pnpm install
pnpm run build --filter=@myorg/ui...

## 缓存问题

### 症状
尽管有更改,但构建过时

### 解决方案
turbo run build --force

## 版本冲突

### 症状
同行依赖警告

### 解决方案
pnpm syncpack fix-mismatches
pnpm install

最佳实践

1. 使用 Changesets 进行版本控制

使用 changesets 自动化版本管理。

2. 实现基于影响的 CI

在 CI 中仅构建和测试更改的包。

3. 自动化包发布

使用 CI/CD 进行一致可靠的发布。

4. 清晰记录工作流程

维护全面的工作流程文档。

5. 使用一致的 Git 实践

强制执行规范提交和分支命名。

6. 实现代码所有者

分配包所有权以进行更好的审查。

7. 设置预提交钩子

在问题到达 CI 之前捕获它们。

8. 使用语义版本控制

所有包版本遵循 semver。

9. 在发布前在 CI 中测试

切勿发布未经测试的包。

10. 监控部署健康度

跟踪部署成功和回滚能力。

常见陷阱

1. 手动版本管理

导致错误和不一致。

2. 对所有更改运行完整 CI

浪费时间和资源。

3. 不一致的发布流程

导致混淆和潜在错误。

4. 文档不足

使入职和协作困难。

5. 复杂的 Git 工作流程

减慢开发速度并引起混淆。

6. 无部署自动化

手动部署易出错。

7. 未警告的破坏性更改

破坏依赖包和应用。

8. 缺失变更日志

用户不知道更改了什么。

9. 发布未经测试的包

破坏消费者的生产环境。

10. 无回滚策略

无法从错误部署中恢复。

何时使用此技能

在以下情况下应用单体仓库工作流程实践:

  • 设置 CI/CD - 配置自动化构建和部署
  • 实现版本控制 - 建立版本管理
  • 优化工作流程 - 提高开发效率
  • 管理发布 - 发布和部署包
  • 团队协作 - 建立团队实践
  • 自动化流程 - 创建自动化工作流程
  • 故障排除问题 - 解决工作流程问题
  • 开发人员入职 - 教授单体仓库工作流程
  • 迁移工作流程 - 更新或更改流程
  • 扩展操作 - 增长团队和流程

资源