OpenAPI-First #3: Versioning Contract qua dev, SIT, production

Làm thế nào để quản lý version API contract qua các môi trường dev, SIT và production? Bài này hướng dẫn chiến lược versioning với npm tags và CI/CD automation.

Nghe bài viết này dưới dạng podcast

Khi API contract được đóng gói thành npm package, câu hỏi tiếp theo là: làm thế nào để dev environment luôn có version mới nhất, trong khi SIT và production lại cần version ổn định, được pin cứng?

Câu trả lời nằm ở npm tags — một tính năng ít được biết đến nhưng rất mạnh.

npm tags hoạt động như thế nào?

Mỗi npm package có thể có nhiều tags, mỗi tag trỏ đến một version:

@your-org/api-contracts-client
├── latest → 1.2.0        (production)
├── sit    → 1.2.0-sit.1  (SIT)
└── dev    → 1.2.0-dev.a3f9c1 (development)

Khác với Docker tag latest, npm tag không tự động cập nhật — bạn phải chỉ định khi publish:

pnpm publish --tag dev     # trỏ tag "dev" đến version vừa publish
pnpm publish --tag sit     # trỏ tag "sit"
pnpm publish               # mặc định trỏ tag "latest"

Chiến lược version theo môi trường

Môi trường Format Ví dụ Cách install
Dev x.y.z-dev.{sha} 1.2.0-dev.a3f9c1 pnpm add @org/pkg@dev
SIT x.y.z-sit.{n} 1.2.0-sit.1 pnpm add @org/pkg@1.2.0-sit.1
Production x.y.z 1.2.0 pnpm add @org/pkg@1.2.0

Dev dùng tag — luôn lấy version mới nhất khi pnpm install.

SIT và production pin version cứng — biết chính xác version nào đang chạy.

CI workflow cho dev branch

# .github/workflows/publish-dev.yml
name: Publish Dev
on:
  push:
    branches: [dev]
    paths: ['openapi.yaml']

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      packages: write
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          registry-url: https://npm.pkg.github.com

      - run: pnpm install

      - name: Bump dev version
        run: |
          SHA=$(git rev-parse --short HEAD)
          for pkg in packages/nestjs-contracts packages/client-types; do
            node -e "
              const fs = require('fs');
              const pkg = JSON.parse(fs.readFileSync('$pkg/package.json'));
              const base = pkg.version.split('-')[0];
              pkg.version = base + '-dev.' + '$SHA';
              fs.writeFileSync('$pkg/package.json', JSON.stringify(pkg, null, 2));
            "
          done

      - run: pnpm --filter './packages/*' publish --no-git-checks --tag dev
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Workflow cho SIT và production

SIT và production không auto publish — cần bump version tay để có ý thức về những gì đang release:

# .github/workflows/publish-release.yml
name: Publish Release
on:
  push:
    branches: [sit, main]
    paths:
      - 'packages/*/package.json'  # chỉ trigger khi version được bump tay

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - name: Determine tag
        id: tag
        run: |
          if [[ "${{ github.ref_name }}" == "sit" ]]; then
            echo "tag=sit" >> $GITHUB_OUTPUT
          else
            echo "tag=latest" >> $GITHUB_OUTPUT
          fi

      - run: pnpm --filter './packages/*' publish --no-git-checks --tag ${{ steps.tag.outputs.tag }}
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Quy trình bump version tay

# Chuẩn bị SIT release
cd packages/nestjs-contracts
pnpm version prerelease --preid=sit
# 1.1.0 → 1.1.1-sit.0

cd ../client-types
pnpm version prerelease --preid=sit

git commit -am "chore: bump to sit release"
git push origin sit
# → CI tự publish với tag "sit"
# Production release
cd packages/nestjs-contracts
pnpm version minor  # hoặc patch / major
# 1.1.1-sit.0 → 1.2.0

cd ../client-types
pnpm version minor

git commit -am "chore: release v1.2.0"
git push origin main
# → CI publish với tag "latest"

Consumer repo cập nhật version

# Dev — luôn dùng tag, không cần thay đổi package.json
pnpm install  # tự kéo dev version mới nhất

# SIT — pin cứng version
pnpm add @your-org/api-contracts-nestjs@1.2.0-sit.1

# Production — pin cứng version
pnpm add @your-org/api-contracts-nestjs@1.2.0

Tại sao SIT và production phải pin version?

Dùng tag @sit hay @latest ở môi trường quan trọng có rủi ro: nếu ai đó vô tình publish version mới lên tag đó, deploy tiếp theo sẽ kéo code khác mà không ai biết.

Pin version cứng đảm bảo:

  • Biết chính xác version nào đang chạy ở production
  • Rollback dễ dàng — chỉ cần revert package.json
  • Deploy reproducible — cùng package.json luôn ra cùng kết quả

Kết luận

  • npm tags cho phép cùng một package có nhiều "kênh" phân phối.
  • Dev dùng tag để luôn có version mới nhất tự động.
  • SIT và production pin version cứng để kiểm soát chính xác.
  • Chỉ bump version tay trước khi push lên sit/main — không auto bump.
  • CI chỉ publish khi package.json thay đổi — không publish thừa.

Chưa có bình luận

Để lại bình luận

Bình luận sẽ được phê duyệt trước khi hiển thị.

Nhận bài viết mới

Mình sẽ gửi email khi có bài mới. Không spam.