Đừng để Terminal của bạn trở thành “bãi rác” script
Nếu bạn là kỹ sư DevOps hoặc Backend, chắc hẳn bạn đã quá quen với việc lặp lại các tác vụ như dọn dẹp database hay kiểm tra health hệ thống. Thông thường, chúng ta hay viết vài dòng shell script hoặc copy-paste những câu lệnh dài dằng dặc. Cách này nhanh lúc đầu nhưng về lâu dài sẽ cực kỳ khó quản lý.
Trước đây, mình thường chạy script bằng lệnh node script.js. Khi tham số tăng lên, việc xử lý process.argv thủ công thực sự là một thảm họa. Bạn sẽ cần một framework thực thụ để quản lý cấu trúc lệnh và tự động hóa phần tài liệu hướng dẫn (help documentation).
Ba con đường xây dựng CLI trong hệ sinh thái Node.js
Dưới đây là 3 phương pháp phổ biến mình từng thử nghiệm qua nhiều dự án khác nhau:
1. Sử dụng process.argv (Vanilla Node.js)
Đây là cách tiếp cận thô sơ nhất bằng việc parse mảng tham số mặc định của Node.js.
- Ưu điểm: Chạy ngay không cần cài cắm thư viện. Phù hợp cho script cá nhân dưới 20 dòng code.
- Nhược điểm: Bạn phải tự viết logic bắt lỗi và hướng dẫn sử dụng. Code sẽ rất “rác” nếu bạn có từ 3 tham số trở lên.
2. Commander.js hoặc Yargs
Hai thư viện này là lựa chọn quốc dân trong cộng đồng Node.js.
- Ưu điểm: Xử lý flag (
-f,--force) rất mượt. Tự động tạo trang help đẹp mắt. - Nhược điểm: Khi CLI của bạn phình to với hàng chục lệnh con, file chính sẽ trở nên khổng lồ và cực kỳ khó bảo trì.
3. Oclif (Open CLI Framework)
Đây là “vũ khí” hạng nặng từ Salesforce, được dùng để xây dựng Heroku CLI. Khác với các thư viện đơn thuần, Oclif cung cấp một kiến trúc thư mục chặt chẽ.
- Ưu điểm: Hỗ trợ TypeScript mặc định, tự động load lệnh theo file và có hệ thống testing cực kỳ chuyên nghiệp.
- Nhược điểm: Bạn sẽ mất khoảng 30 phút ban đầu để làm quen với cấu trúc của nó.
Tại sao Oclif là lựa chọn hàng đầu cho dự án thực tế?
Trong một dự án gần đây, team mình xây dựng công cụ itfz-cli để hỗ trợ 5 developer. Thay vì phải nhớ hàng tá câu lệnh Docker hay AWS phức tạp, anh em chỉ cần gõ itfz-cli deploy --stage=staging. Kết quả là thời gian khởi tạo môi trường mới giảm từ 15 phút xuống còn chưa đầy 30 giây.
Oclif giúp giải quyết bài toán mở rộng (scalability). Mỗi câu lệnh là một file riêng biệt trong thư mục src/commands. Khi cần thêm tính năng, bạn chỉ việc tạo file mới mà không sợ làm hỏng các logic cũ đang chạy ổn định.
Bắt tay vào triển khai CLI đầu tiên
Bước 1: Khởi tạo Project
Sử dụng npx để khởi tạo nhanh mà không cần cài đặt global generator rườm rà:
npx oclif generate my-cli
Mình khuyên bạn nên chọn TypeScript. Việc có gợi ý kiểu dữ liệu sẽ giúp bạn tránh được những lỗi ngớ ngẩn khi xử lý tham số từ người dùng.
Bước 2: Cấu trúc thư mục chuẩn
Sau khi cài đặt, bạn sẽ thấy cấu trúc project được phân bổ rất khoa học:
my-cli/
├── src/
│ └── commands/
│ └── check/index.ts (Lệnh: my-cli check)
├── package.json
└── tsconfig.json
Mỗi file trong src/commands/ tương ứng với một lệnh. Ví dụ, file src/commands/db/migrate.ts sẽ tự động tạo ra lệnh my-cli db:migrate.
Bước 3: Viết logic cho Command
Thử tạo một công cụ kiểm tra trạng thái website tại src/commands/check.ts:
import {Command, Flags} from '@oclif/core'
import axios from 'axios'
export default class CheckStatus extends Command {
static description = 'Kiểm tra xem website có đang "sống" hay không'
static flags = {
timeout: Flags.integer({char: 't', description: 'Thời gian chờ (ms)', default: 3000}),
}
static args = [{name: 'url', description: 'Link website cần check', required: true}]
public async run(): Promise<void> {
const {args, flags} = await this.parse(CheckStatus)
this.log(`🚀 Đang kết nối tới: ${args.url}...`)
try {
const start = Date.now()
await axios.get(args.url, {timeout: flags.timeout})
this.log(`✅ OK! Phản hồi sau ${Date.now() - start}ms`)
} catch (error) {
this.error(`❌ Lỗi: ${error.message}`)
}
}
}
Giải thích nhanh:
- static flags: Định nghĩa các tùy chọn có gạch ngang như
--timeout. - static args: Các tham số truyền trực tiếp, ví dụ như URL.
- this.log / this.error: Cách chuẩn để in thông tin ra terminal mà không làm hỏng luồng dữ liệu của hệ thống.
Bước 4: Chạy thử và cài đặt
Trong lúc phát triển, bạn có thể test nhanh bằng lệnh:
./bin/run.js check https://google.com
Để sử dụng tool này như một lệnh hệ thống (giống ls hay cd), hãy dùng npm link. Sau đó, bạn có thể gõ my-cli check ... ở bất cứ thư mục nào trong máy tính.
Mẹo xử lý logic phức tạp
Khi CLI lớn dần, bạn sẽ gặp tình trạng lặp code ở nhiều lệnh khác nhau. Giải pháp là sử dụng Base Class. Hãy tạo một class BaseCommand để quản lý kết nối Database hoặc Log tập trung, sau đó cho các command khác kế thừa từ nó.
// src/base.ts
import {Command} from '@oclif/core'
export abstract class BaseCommand extends Command {
async init() {
this.log('--- Đang thiết lập kết nối hệ thống ---')
}
async finally(err: Error | undefined) {
this.log('--- Đã đóng các kết nối an toàn ---')
return super.finally(err)
}
}
Lời kết
Xây dựng CLI không chỉ đơn thuần là viết code, mà là thiết kế trải nghiệm làm việc cho chính bạn và đồng nghiệp. Oclif giúp biến những script rời rạc thành một bộ công cụ chuyên nghiệp, dễ bảo trì và mở rộng.
Nếu team bạn vẫn đang loay hoay với hàng tá file .sh hay .js chạy lẻ tẻ, hãy thử dành một buổi chiều để quy hoạch lại chúng bằng Oclif. Hiệu quả công việc chắc chắn sẽ khiến bạn bất ngờ.

