Viết Command-line Applications trong NodeJS

1921

Với các package phù hợp, việc viết các command-line ứng dụng trong NodeJS trở nên dễ dàng hơn.

Một package đặc biệt đơn giản hơn cả là Commander.

Hãy thiết lập các stage để tìm hiểu làm thế nào viết một giao diện command-line (CLI) ứng dụng trong NodeJS với Commander. Mục tiêu của chúng ta là viết một ứng dụng CLI liệt kê các file và thư mục.

Chuẩn bị

IDE

Tôi thích các IDE online. Chúng giúp giảm đi nhiều vấn đề nhức đầu khi thiết lập môi trường. Cá nhân tôi sử dụng Cloud9 vì những lý do sau:

  • Bố cục trực quan
  • Editor rất đẹp và dễ sử dụng
  • Với 1GB RAM và 5GB, nhiêu đó dư cho một ứng dụng NodeJS đầy đủ.
  • Không giới hạn số lượng trạm làm việc.
  • Đó là một môi trường hoàn hảo để thử nghiệm các dự án, ý tưởng mới mà không sợ phá vỡ môi trường của bạn.

Node/NPM Version

Bài viết được viết, Node ở phiên bản 5.3.0 và NPM là phiên bản 3.3.12.

Khởi tạo

Chúng ta bắt đầu bằng cách khởi tạo dự án, chấp nhận tất cả các giá trị mặc định của NPM, và cài đặt commander package:

Lưu ý:

  • Bạn sẽ phải thêm bin thủ công, để NodeJS biết ứng dụng CLI của bạn được gọi là gì và điểm truy cập vào ứng dụng của bạn là gì.
  • Hãy chắc chắn rằng bạn không sử dụng một tên command đã tồn tại trong hệ thống.

Index.js

Bây giờ chúng ta đã khởi tạo dự án và chỉ ra chỉ ra rằng điểm vào của chúng ta là index.js, hãy tạo index.js:

Bây giờ, đối với phần code thực tế:

Thông thường, khi thực hiện các file NodeJS, chúng ta sẽ sử dụng trình phiên dịch thích hợp bằng cách đặt node trước file. Tuy nhiên, chúng ta muốn thực hiện ứng dụng CLI từ bất cứ nơi nào trong hệ thống và không phải chỉ định Node interpreter mỗi lần.

Vì vậy, dòng đầu tiên là biểu thức shebang:

Điều này không chỉ thông báo cho hệ thống của chúng ta sử dụng trình phiên dịch thích hợp, mà còn để hệ thống sử dụng phiên bản trình phiên dịch thích hợp.

Từ đây, chúng ta viết code JavaScript thuần.
Vì tôi sẽ viết code tương thích với ES6, tôi sẽ bắt đầu với biểu thức chính xác:

Điều này sẽ thông báo với trình biên dịch sử dụng một biến thể của javascript [1] và cho phép chúng ta viết code ES6 trên Cloud9.

Hãy bắt đầu bằng cách yêu cầu commander package:

Bây giờ, viết ứng dụng CLI với commander đơn giản và tài liệu là điều tuyệt vời, nhưng ta phải “vật lộn” với một vài khái niệm mà tôi sẽ cố gắng để làm rõ ở đây.

Lấy 2 thiết kế cho các ứng dụng CLI  ls và git ví dụ.

Với ls, bạn sẽ trải qua một hoặc nhiều 1 flag:

  • ls -l
  • ls -al

Với git, bạn sẽ trải qua các command phụ, nhưng bạn cũng có một số flag:

  • git commit -am <message>
  • git remote add origin <repo-url>

Chúng ta sẽ khám phá sự linh hoạt của Commander cho phép thiết kế cả hai loại giao diện command-line.

Commander API

Các Commander API dẫn thẳng về các tài liệu liên quan

Có 3 cách cơ bản chúng ta có thể viết chương trình:

Cách 1: Command-line ứng dụng chỉ có Flag

Lưu ý:

  • Các lựa chọn ngắn hạn và dài hạn nằm trong cùng một string.
  • -o-m sẽ trả về giá trị boolean khi users pass bởi vì chúng ta không chỉ định bất kỳ người dùng đầu vào tùy chọn hoặc bắt buộc nào.
  • -i-I sẽ bắt buộc user phải input và pass giá trị vào ứng dụng CLI của chúng ta.
  • Bất kỳ giá trị nào được bao gồm trong dấu ngoặc vuông ([]) được coi là tùy chọn. Người dùng có thể cần hoặc không cần cung cấp giá trị.
  • Bất kỳ giá trị nào được bao gồm trong dấu ngoặc nhọn (ví dụ: <>) được coi là bắt buộc. Người dùng phải cung cấp giá trị.

Ví dụ trên cho chúng ta công cụ tiếp cận ứng dụng CLI chỉ bằng flag. Người dùng sẽ được tương tác với ứng dụng của chúng ta như sau:

Phương pháp #2: Sub-command và flag dựa trên command-line của ứng dụng

Lưu ý:

  • Nếu chúng ta sử dụng .command(‘command…’).description(‘description…’), chúng ta phải sử dụng .action() để truyền một hàm và thực thi code của chúng ta dựa vào input của user. (Tôi chỉ ra điều này bởi vì có một phương pháp khác để sử dụng .command() mà chúng ta sẽ tìm hiểu ở phần tiếp theo.)
  • Nếu chúng ta sử dụng .command(‘command…’), chúng ta không thể sử dụng .parse(process.argv) ở cuối như chúng ta đã làm trong ví dụ trước. Chúng ta phải bỏ qua parse() trong một statement mới

Người dùng tương tác với ứng dụng CLI của chúng tôi như sau:

Phương pháp 3: Tương tự như phương pháp 2, nhưng cho phép kết nối code thành modul

Cuối cùng, chúng ta không cần phải dồn lên một tập tin JavaScript với tất cả các .command().description().action() logic. Chúng ta có thể kết nối dự án CLI của chúng ta như sau:

Lưu ý:

  • Nếu chúng ta sử dụng .command(‘command’, ‘description’) để truyền trong command và description, chúng ta không thể có .action()Commander nhắc nhở rằng chúng ta có các file riêng biệt với một quy ước đặt tên cụ thể nơi chúng ta có thể xử lý từng command. Quy ước đặt tên là index-command.js, index-command2.js, index-command3.js. Xem ví dụ về điều này trên Github (cụ thể là: các file pm, pm-install, pm-publish).
  • Nếu chúng ta đi theo con đường này, chúng ta chỉ có thể thêm vào .parse() ở phần cuối.

Quay lại bản mô tả dự án của chúng tôi …

Nãy giờ chúng ta chỉ mới đề cập đến những điều cơ bản. Và bây giờ hãy bắt đầu những thứ thú vị hơn

Nhớ lại trong bản mô tả dự án, chúng tôi sẽ viết một ứng dụng CLI để liệt kê các file và thư mục. Vì vậy, chúng ta hãy bắt đầu viết code nào.

Chúng ta muốn cung cấp cho người dùng khả năng quyết định xem họ muốn xem “tất cả” các tập tin (kể cả các file ẩn) hay họ muốn xem danh sách format (bao gồm rights/permissions của các file/folder).

Vì vậy, chúng ta bắt đầu bằng cách viết các shell cơ bản cho chương trình để theo dõi tiến trình phát triển (chúng ta sẽ làm theo cách của phương pháp #2 vì những lợi ích đã giới thiệu ở trên):

Hãy bắt đầu lấp đầy những khoảng trống:

Lưu ý:

  • Chúng tôi quyết định đặt tên cho danh sách command của chúng tôi.
  • Đối số Directory là phần tùy chọn, vì vậy người dùng có thể bỏ qua để vào thư mục, trong trường hợp đó chúng ta sẽ liệt kê các file / thư mục trong thư mục hiện tại.

Vì vậy, trong bản mô tả của chúng tôi, các lệnh call sau đây là hợp lệ:

Bây giờ, chúng ta hãy bắt đầu viết code cho .action().

Chúng ta sẽ “gian lận” ở đây bằng cách sử dụng command ls tích hợp có sẵn trong tất cả các hệ điều hành Unix.

Hãy suy nghĩ về đoạn code này.

  1. Trước tiên, chúng tôi yêu cầu thư viện child_process để thực hiện các shell command * (* sẽ mở ra một lỗ hỏng bảo mật lớn mà tôi sẽ thảo luận sau)
  2. Chúng ta khai báo một biến không đổi chứa root của command.
  3. Chúng tôi khai báo một mảng sẽ giữ bất kỳ tham số nào được người dùng nhập (ví dụ -a, -l)
  4. Chúng tôi kiểm tra để xem liệu người dùng đã qua các flag ngắn (-a) hoặc dài (— all). Nếu vậy, thì options.all hoặc options.long sẽ ước lượng giá trị true, trong trường hợp đó chúng ta sẽ đẩy command flag tương ứng vào mảng của chúng ta. Mục tiêu của chúng tôi là xây dựng shell command mà chúng ta sẽ truyền sau đó đến child_process.
  5. Chúng ta khai báo một biến mới để giữ toàn bộ command shell. Nếu mảng param có chứa bất kỳ flag nào, chúng ta nối các flag với nhau và với root command. Nếu mảng param trống, thì chúng ta sử dụng như root command.
  6. Cuối cùng, chúng tôi kiểm tra xem người dùng có thông qua bất kỳ giá trị directory tùy chọn nào không.

Bây giờ, chúng ta muốn thực hiện command hoàn toàn được xây dựng trong shell. Child_Process.exec() cho chúng ta khả năng làm như vậy và các tài liệu của NodeJS cung cấp cho chúng ta chữ ký

Vì vậy, chúng ta hãy sử dụng điều này:

Chính là nó!

Đây là ứng dụng CLI mẫu của tôi.

Tất nhiên, chúng ta có thể thêm một vài chi tiết nhỏ, như:

  • Màu output (Tôi sử dụng thư viện chalk bên dưới)
  • Các ứng dụng CLI hiện đại đủ thông minh để hiển thị văn bản trợ giúp khi người dùng gọi chương trình mà không có tham số hoặc tranh luận (giống như git), vì vậy tôi đã thêm chức năng đó ở cuối.

Cuối cùng, chúng ta có thể tận dụng NPM để liên kết tượng trưng ứng dụng CLI để chúng ta có thể sử dụng nó trên toàn hệ thống. Đơn giản, trong terminal, cd vào thư mục gốc của ứng dụng CLI của chúng ta và gõ:

Kết

Code của dự án này không phải là code tối ưu nhất. Tôi hiểu rằng luôn có chỗ cho sự cải tiến, vì vậy những ý kiến đóng góp luôn được hoan nghênh!

Ngoài ra, tôi muốn chỉ ra một lỗ hổng bảo mật trong ứng dụng của chúng tôi. Code của chúng ta không lọc hoặc xác nhận input của người dùng. Điều này vi phạm các phương pháp cơ bản nhất về bảo mật. Xem xét các tình huống sau đây mà người dùng có thể vượt qua bước đăng nhập:

Nếu bạn muốn xử lý sự cố này hoặc khắc phục bất kỳ vấn đề tiềm ẩn nào khác, hãy chắc chắn hiển thị cho chúng tôi code của bạn bằng cách để lại command bên dưới.

Techtalk via freeCodecamp

CHIA SẺ