现代软件开发流程是确保高质量代码交付和团队协作的关键基础。随着软件开发复杂度的增加,自动化工具链和规范化流程变得尤为重要。本笔记将探讨CI/CD管道设计原理,分析OpenHands项目的开发流程,并通过实践搭建一个简化版的OpenHands开发环境。
代码分支策略:
代码审查机制:
测试策略:
发布管理:
从README_CN.md中,我们可以推断OpenHands项目采用了以下开发实践:
容器化开发环境:
开发模式选项:
Docker镜像构建:
docker.all-hands.dev/all-hands-ai/openhands:0.47
docker.all-hands.dev/all-hands-ai/runtime:0.47-nikolaik
部署选项:
开发环境架构
├── 开发工具
│ ├── VSCode/编辑器配置
│ ├── ESLint/代码质量工具
│ └── 调试工具配置
├── 本地构建系统
│ ├── Docker Compose开发环境
│ ├── 热重载配置
│ └── 调试端口映射
├── 测试框架
│ ├── 单元测试
│ ├── 集成测试
│ └── 端到端测试
└── CI/CD管道
├── GitHub Actions工作流
├── 自动化测试
└── Docker镜像发布
Docker Compose开发配置 (docker-compose.dev.yml):
version: '3.8'
services:
# 开发服务 - 前端
frontend:
build:
context: ./frontend
dockerfile: Dockerfile.dev
volumes:
- ./frontend:/app
- /app/node_modules
ports:
- "3000:3000"
environment:
- REACT_APP_API_URL=http://localhost:4000
- NODE_ENV=development
depends_on:
- backend
# 开发服务 - 后端
backend:
build:
context: ./backend
dockerfile: Dockerfile.dev
volumes:
- ./backend:/app
- /app/node_modules
ports:
- "4000:4000"
- "9229:9229" # Node.js 调试端口
environment:
- NODE_ENV=development
- DEBUG=openhands:*
- LOG_LEVEL=debug
command: npm run dev
# 运行时沙箱服务
runtime:
image: docker.all-hands.dev/all-hands-ai/runtime:0.47-nikolaik
volumes:
- runtime-workspace:/workspace
environment:
- OPENHANDS_DEV=true
cap_drop:
- ALL
cap_add:
- CHOWN
- SETGID
- SETUID
volumes:
runtime-workspace:
前端开发Dockerfile (frontend/Dockerfile.dev):
FROM node:18-alpine
WORKDIR /app
# 安装依赖
COPY package*.json ./
RUN npm install
# 为热重载准备
ENV CHOKIDAR_USEPOLLING=true
# 启动开发服务器
CMD ["npm", "start"]
后端开发Dockerfile (backend/Dockerfile.dev):
FROM node:18-alpine
WORKDIR /app
# 安装开发工具
RUN apk add --no-cache git python3 make g++ docker-cli
# 安装依赖
COPY package*.json ./
RUN npm install
# 为调试准备
ENV NODE_ENV=development
ENV DEBUG=openhands:*
# 启动开发服务器,支持调试
CMD ["npm", "run", "dev:debug"]
VSCode开发配置 (.vscode/launch.json):
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "Attach to Backend",
"port": 9229,
"restart": true,
"localRoot": "${workspaceFolder}/backend",
"remoteRoot": "/app",
"sourceMaps": true
},
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome against Frontend",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}/frontend/src"
}
],
"compounds": [
{
"name": "Full Stack: Backend + Frontend",
"configurations": ["Attach to Backend", "Launch Chrome against Frontend"]
}
]
}
GitHub Actions工作流 (.github/workflows/ci.yml):
name: OpenHands CI/CD
on:
push:
branches: [ main, dev ]
pull_request:
branches: [ main ]
jobs:
lint:
name: Code Quality Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: |
npm ci
- name: Run ESLint
run: npm run lint
- name: Run Type Check
run: npm run type-check
test:
name: Run Tests
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run unit tests
run: npm run test
- name: Run integration tests
run: npm run test:integration
- name: Upload test coverage
uses: actions/upload-artifact@v3
with:
name: coverage
path: coverage/
build:
name: Build and Push Images
needs: test
runs-on: ubuntu-latest
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev')
steps:
- uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Container Registry
uses: docker/login-action@v2
with:
registry: docker.all-hands.dev
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v4
with:
images: docker.all-hands.dev/all-hands-ai/openhands
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=sha,format=short
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=docker.all-hands.dev/all-hands-ai/openhands:buildcache
cache-to: type=registry,ref=docker.all-hands.dev/all-hands-ai/openhands:buildcache,mode=max
deploy:
name: Deploy to Development
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/dev'
steps:
- name: Deploy to development environment
run: |
echo "Deploying to development environment"
# 部署脚本放在这里
ESLint配置 (.eslintrc.js):
module.exports = {
root: true,
env: {
node: true,
browser: true,
es2021: true,
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'prettier',
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2021,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
plugins: ['@typescript-eslint', 'react', 'react-hooks', 'prettier'],
rules: {
'prettier/prettier': 'error',
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'react/prop-types': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'warn',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
},
settings: {
react: {
version: 'detect',
},
},
};
Jest配置 (jest.config.js):
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
collectCoverage: true,
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov'],
collectCoverageFrom: [
'src/**/*.{js,ts}',
'!src/**/*.d.ts',
'!src/**/*.test.{js,ts}',
],
testMatch: ['**/?(*.)+(spec|test).ts'],
moduleFileExtensions: ['ts', 'js', 'json'],
transform: {
'^.+\\.ts$': 'ts-jest',
},
globals: {
'ts-jest': {
tsconfig: 'tsconfig.json',
},
},
};
Prettier配置 (.prettierrc):
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"bracketSpacing": true,
"arrowParens": "avoid"
}
package.json 开发脚本:
{
"name": "openhands-dev",
"version": "0.1.0",
"scripts": {
"start": "docker-compose -f docker-compose.dev.yml up",
"build": "docker-compose -f docker-compose.dev.yml build",
"stop": "docker-compose -f docker-compose.dev.yml down",
"lint": "eslint . --ext .js,.ts,.jsx,.tsx",
"lint:fix": "eslint . --ext .js,.ts,.jsx,.tsx --fix",
"type-check": "tsc --noEmit",
"test": "jest",
"test:watch": "jest --watch",
"test:integration": "jest --config jest.integration.config.js",
"prepare": "husky install"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,ts,jsx,tsx}": [
"eslint --fix",
"prettier --write"
]
}
}
开发启动脚本 (dev-start.sh):
#!/bin/bash
# 确保本地开发环境干净启动
echo "正在清理之前的开发环境..."
docker-compose -f docker-compose.dev.yml down --remove-orphans
# 构建最新的开发容器
echo "正在构建开发容器..."
docker-compose -f docker-compose.dev.yml build
# 启动开发环境
echo "正在启动开发环境..."
docker-compose -f docker-compose.dev.yml up -d
# 显示容器状态
echo "开发环境启动状态:"
docker-compose -f docker-compose.dev.yml ps
# 显示日志
echo "正在关注日志输出..."
docker-compose -f docker-compose.dev.yml logs -f
自动化优先:
容器化与标准化:
质量内建:
DevOps文化: