18.3 CI/CD 파이프라인 — GitHub Actions와 타입 체크 자동화
TypeScript CI/CD 핵심 체크리스트
CI 단계:
1. 타입 체크 (tsc --noEmit)
2. 린트 (ESLint)
3. 단위 테스트 (Vitest/Jest)
4. 빌드 검증
5. E2E 테스트 (선택적)
CD 단계:
1. Docker 이미지 빌드
2. 컨테이너 레지스트리 푸시
3. DB 마이그레이션
4. 무중단 배포
완전한 GitHub Actions 워크플로우
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
NODE_VERSION: '20'
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# 1. 코드 품질 검사
quality:
name: Type Check & Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: TypeScript type check
run: npx tsc --noEmit
- name: ESLint
run: npm run lint
- name: Prettier check
run: npm run format:check
# 2. 테스트
test:
name: Unit Tests
runs-on: ubuntu-latest
needs: quality
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- run: npm ci
- name: Run tests with coverage
run: npm run test:coverage
- name: Upload coverage report
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
# 3. 빌드 검증
build:
name: Build Validation
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- run: npm ci
- run: npm run build
- name: Verify build output
run: |
test -d dist || exit 1
test -f dist/main.js || exit 1
# 4. E2E 테스트 (main 브랜치만)
e2e:
name: E2E Tests
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main'
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_DB: testdb
POSTGRES_USER: test
POSTGRES_PASSWORD: test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- run: npm ci
- run: npx playwright install --with-deps chromium
- name: Run migrations
env:
DATABASE_URL: postgresql://test:test@localhost:5432/testdb
run: npx prisma migrate deploy
- name: Run E2E tests
env:
DATABASE_URL: postgresql://test:test@localhost:5432/testdb
JWT_SECRET: test-secret-that-is-at-least-32-characters
run: npx playwright test
- uses: actions/upload-artifact@v3
if: failure()
with:
name: playwright-report
path: playwright-report/
package.json 스크립트
{
"scripts": {
"build": "tsc -p tsconfig.build.json",
"typecheck": "tsc --noEmit",
"lint": "eslint 'src/**/*.ts' --ext .ts",
"lint:fix": "eslint 'src/**/*.ts' --ext .ts --fix",
"format": "prettier --write 'src/**/*.ts'",
"format:check": "prettier --check 'src/**/*.ts'",
"test": "vitest",
"test:run": "vitest run",
"test:coverage": "vitest run --coverage",
"test:ci": "vitest run --reporter=junit --outputFile=test-results.xml",
"test:e2e": "playwright test",
"migrate": "prisma migrate deploy",
"migrate:dev": "prisma migrate dev",
"db:seed": "prisma db seed"
}
}
ESLint + TypeScript 설정
// .eslintrc.json
{
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json",
"tsconfigRootDir": "."
},
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking"
],
"rules": {
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/await-thenable": "error",
"@typescript-eslint/no-misused-promises": "error"
}
}
캐시 최적화
# npm 캐시 + 빌드 아티팩트 캐시
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
# TypeScript 증분 빌드 캐시
- name: Cache TypeScript build
uses: actions/cache@v3
with:
path: |
.tsbuildinfo
dist
key: ts-build-${{ runner.os }}-${{ hashFiles('src/**/*.ts', 'tsconfig*.json') }}
restore-keys: |
ts-build-${{ runner.os }}-
고수 팁
PR 자동 타입 체크 코멘트
# .github/workflows/typecheck-comment.yml
- name: TypeScript check with annotations
run: npx tsc --noEmit 2>&1 | npx ts-to-markdown > ts-errors.md || true
- name: Comment on PR
if: failure()
uses: actions/github-script@v7
with:
script: |
const fs = require('fs')
const errors = fs.readFileSync('ts-errors.md', 'utf8')
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## TypeScript Errors\n${errors}`,
})