Quản lý phiên bản cho Polyglot Monorepo hiệu quả với Changesets
Bài viết khám phá việc sử dụng Changesets—một công cụ vốn dành cho hệ sinh thái JavaScript—để quản lý phiên bản cho các monorepo đa ngôn ngữ (polyglot). Tác giả hướng dẫn chi tiết cách cấu hình, sử dụng GitHub Actions để tự động hóa và viết script đồng bộ phiên bản sang Python và Rust.
Quản lý phiên bản cho Polyglot Monorepo hiệu quả với Changesets
Một trong những lợi ích khi làm việc tại các doanh nghiệp quy mô nhỏ là bạn có thể tận dụng các công cụ không cần phải mở rộng quy mô đến mức cực đại. Trong thế giới phần mềm, ví dụ điển hình chính là monorepo (kho chứa mã nguồn đơn nhất).
Mặc dù monorepo có thể mở rộng rất tốt (như trường hợp của Google hay Facebook), việc làm vậy đòi hỏi cơ sở hạ tầng và công cụ đặc biệt. Với git thuần túy, bạn chỉ có thể đi được một chặng đường nhất định. Tuy nhiên, việc sử dụng monorepo mang lại những ưu điểm lớn, cho phép thực hiện các thay đổi nguyên tử (atomic changes) ảnh hưởng đến nhiều phần của hệ thống trong một lần commit duy nhất, giúp loại bỏ các vấn đề về tính tương thích và tích hợp.
Vậy giả sử bạn là một đội ngũ nhỏ đến vừa phải sử dụng monorepo, và kho chứa này lưu trữ toàn bộ mã nguồn của công ty với nhiều ngôn ngữ lập trình khác nhau—đó là một polyglot monorepo. Vậy công cụ nào sẽ giúp bạn quản lý phiên bản một cách nhất quán? Changesets là một lựa chọn vững chắc, ngay cả khi công cụ này chủ yếu tập trung vào hệ sinh thái JavaScript/TypeScript.
Tại sao chọn Changesets?
Đối với bất kỳ công cụ quản lý phiên bản nào, bạn thường cần giải quyết các vấn đề: định nghĩa nội dung changelog (nhật ký thay đổi), ảnh hưởng đến số phiên bản của các gói phần mềm, tự động hóa các commit nâng cấp metadata và gắn thẻ (tag), cũng như tự động hóa quy trình build.
Changesets hoạt động dựa trên nguyên tắc mỗi gói có một phiên bản riêng (semantic versioning) và một file CHANGELOG.md riêng biệt. Điểm mấu chốt giúp Changesets hỗ trợ polyglot repository nằm ở khả năng tùy biến thông qua GitHub Action changesets/action. Action này cho phép chỉ định các script tùy chỉnh cho các lệnh phiên bản và xuất bản.
Trong Changesets, các kỹ sư sẽ commit các file "changeset" để định nghĩa nội dung changelog và xác định các gói nào cần được nâng phiên bản (major, minor, hay patch).
Thiết lập Repository
Cách tổ chức được khuyến nghị là đặt tất cả các gói trong thư mục packages/, bất kể chúng sử dụng ngôn ngữ nào. Bạn cũng có thể có thư mục docs/ riêng biệt.
Để Changesets hoạt động với các ngôn ngữ không phải JavaScript, chúng ta cần các file package.json "proxy" cho tất cả các gói. Changesets sử dụng các file này để thực hiện việc nâng cấp phiên bản. Ví dụ, một gói Python có thể có file packages/python-one/package.json đơn giản như sau:
{
"name": "python-one",
"version": "0.1.0",
"private": true
}
Bạn cũng cần cấu hình pnpm-workspace.yaml tại gốc để bao gồm các gói này và cấu hình Changesets trong .changeset/config.json.
Tự động hóa quy trình phát hành với GitHub
Chìa khóa để hỗ trợ đa ngôn ngữ nằm ở việc cấu hình GitHub Workflow. Thay vì sử dụng trigger on.push.tags (vốn không đáng tin cậy khi đẩy nhiều tag cùng lúc), chúng ta nên sử dụng workflow_call.
Trong workflow, bước quan trọng là lệnh version. Thay vì chạy lệnh mặc định, chúng ta chỉ định một script tùy chỉnh:
- name: Create Release Pull Request or Tag
id: changesets
uses: changesets/action@v1
with:
version: just version
publish: npx @changesets/cli publish
Lệnh just version sẽ thực hiện việc chạy Changesets để cập nhật package.json, sau đó chạy một script Python để đồng bộ các phiên bản này sang các tệp cấu hình gốc của ngôn ngữ tương ứng.
Đồng bộ phiên bản với Script tùy chỉnh
Vì Changesets chỉ cập nhật file package.json, chúng ta cần một script "keo" (glue script) để truyền phiên bản đó sang Cargo.toml (cho Rust) hoặc pyproject.toml (cho Python).
Logic của script này khá đơn giản:
- Đọc phiên bản mới từ file
package.jsoncủa từng gói. - Tìm file cấu hình gốc (ví dụ:
Cargo.tomlhoặcpyproject.toml). - Cập nhật trường
versiontrong các file đó bằng phiên bản mới đọc được. - Làm mới các file lockfile (như
Cargo.lockhoặcuv.lock) để đảm bảo tính nhất quán.
Ví dụ, với Rust, script sẽ tìm phần [package] trong Cargo.toml và cập nhật dòng version = "...". Tương tự cho Python với phần [project] trong pyproject.toml.
Tổng kết
Changesets hoàn toàn có khả năng quản lý semantic versioning và changelog cho từng gói trong các polyglot monorepo ngay cả khi chưa có hỗ trợ đa ngôn ngữ gốc. Mẹo ở đây là coi các tệp kê khai JavaScript (package.json) là nguồn chính xác của các thay đổi phiên bản, sau đó đồng bộ những thay đổi đó sang các tệp kê khai gốc của ngôn ngữ thông qua script riêng.
Mặc dù có một số điểm cần lưu ý (như việc tách biệt các pnpm-workspace.yaml cho các thư mục con độc lập), nhưng không có trở ngại nào ngăn cản bạn tận dụng quy trình làm việc tiện lợi của Changesets. Việc này cho phép tách biệt rõ ràng giữa thông điệp commit dành cho kỹ sư nội bộ và ghi chú changelog dành cho người dùng cuối—hai đối tượng thường có nhu cầu khác nhau.


