JavaScript >> Javascript 文檔 >  >> Node.js

如何創建 Node.js CLI 應用程序

關於 Node,我最喜歡的一件事是創建簡單的命令行界面 (CLI) 工具是多麼容易。在使用 yargs 解析參數到使用 npm 管理工具之間,Node 讓一切變得簡單。

我所指的工具種類的一些例子是:

  • 永遠
  • uglifyjs
  • is-up-cli
  • jshint
  • 速度測試

安裝時(使用 -g 選項),這些包可以從命令行的任何地方執行,並且工作起來很像內置的 Unix 工具。

我最近一直在為命令行創建一些 Node.js 應用程序,並認為在上面寫一篇文章以幫助您入門可能會有所幫助。因此,在本文中,我將向您展示如何創建一個命令行工具來獲取 IP 地址和 URL 的位置數據。

如果您看過關於學習 Node.js 的 Stack Abuse 文章,您可能還記得我們創建了一個名為 twenty 的包 具有類似的功能。我們將在該項目的基礎上進行構建,並將其轉化為具有更多功能的適當 CLI 工具。

設置項目

讓我們首先創建一個新目錄並使用 npm 設置項目:

$ mkdir twenty
$ npm init

在最後一個命令中按回車鍵,你應該有你的 package.json 文件。

請注意,因為我已經取了包名 twenty 在 npm 上,如果您真的想發布,則必須將其重命名為其他名稱。或者您也可以確定您的項目範圍。

然後,創建 index.js 文件:

$ touch index.js

這是我們現在真正需要開始的所有內容,我們將在繼續進行時添加到項目中。

解析參數

大多數 CLI 應用程序都接受用戶的參數,這是獲取輸入的最常見方式。在大多數情況下,解析參數並不太難,因為通常只有少數命令和標誌。但是隨著工具變得越來越複雜,會添加更多的標誌和命令,並且參數解析會變得異常困難。

為了幫助我們解決這個問題,我們將使用一個名為 yargs 的包 ,它是流行的 optimist package 的繼承者。

yargs 創建是為了幫助您解析來自用戶的命令,如下所示:

var argv = require('yargs').argv;

現在復雜的 optstrings 像 node index.js install -v --a=22 -cde -x derp 可以輕鬆訪問:

var argv = require('yargs').argv;

argv._[0]   // 'install'
argv.v      // true
argv.a      // 22
argv.c      // true
argv.d      // true
argv.e      // true
argv.x      // 'derp'

yargs 甚至會幫助您指定命令界面,因此如果用戶的輸入不滿足某些要求,它會向他們顯示錯誤消息。因此,例如,我們可以告訴 yargs 我們至少需要 2 個參數:

var argv = require('yargs')
    .demand(2)
    .argv

如果用戶沒有提供至少兩個,他們會看到這個默認的錯誤信息:

$ node index.js foo

Not enough non-option arguments: got 1, need at least 2

yargs 還有很多 不僅如此,請查看自述文件以獲取更多信息。

免費電子書:Git Essentials

查看我們的 Git 學習實踐指南,其中包含最佳實踐、行業認可的標準以及隨附的備忘單。停止谷歌搜索 Git 命令並真正學習 它!

對於 twenty ,我們將接受一些可選參數,例如 IP 地址和一些標誌。現在,我們將使用 yargs 像這樣:

var argv = require('yargs')
    .alias('d', 'distance')
    .alias('j', 'json')
    .alias('i', 'info')
    .usage('Usage: $0 [options]')
    .example('$0 -d 8.8.8.8', 'find the distance (km) between you and Google DNS')
    .describe('d', 'Get distance between IP addresses')
    .describe('j', 'Print location data as JSON')
    .describe('i', 'Print location data in human readable form')
    .help('h')
    .alias('h', 'help')
    .argv;

由於不需要我們的任何參數,我們不會使用 .demand() ,但我們確實使用 .alias() ,它告訴 yargs 用戶可以使用每個標誌的短格式或長格式。我們還添加了一些幫助文檔,以便在用戶需要時向他們展示。

構建應用程序

現在我們可以從用戶那裡獲得輸入,我們如何獲取該輸入並將其轉換為帶有可選參數的命令?有一些模塊可以幫助您做到這一點,包括:

  • 帶有 CLI 插件的熨斗
  • 指揮官
  • Vorpal

使用其中許多框架,參數解析實際上是為您完成的,因此您甚至不需要使用 yargs .而在 commander 的情況下,它的大部分功能很像 yargs ,儘管它確實提供了將命令路由到函數的方法。

由於我們的應用程序相當簡單,我們將堅持使用 yargs 暫時。

添加代碼

我們不會在這里花太多時間,因為它只針對我們的 CLI 應用程序,但這裡是特定於我們應用程序的代碼:

var dns = require('dns');
var request = require('request');

var ipRegex = /(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/;

var toRad = function(num) {
    return num * (Math.PI / 180);
};

var getIpInfo = function(server, callback) {
    var ipinfo = function(p, cb) {
        request('http://ipinfo.io/' + p, function(err, response, body) {
            var json = JSON.parse(body);
            cb(err, json);
        });
    };

    if (!server) {
        return ipinfo('json', callback);
    } else if (!server.match(ipRegex)) {
        return dns.lookup(server, function(err, data) {
            ipinfo(data, callback);
        });
    } else {
        return ipinfo(server, callback);
    }
};

var ipDistance = function(lat1, lon1, lat2, lon2) {
    // Earth radius in km
    var r = 6371;

    var dLat = toRad(lat2 - lat1);
    var dLon = toRad(lon2 - lon1);
    lat1 = toRad(lat1);
    lat2 = toRad(lat2);

    var a = Math.sin(dLat / 2.0) * Math.sin(dLat / 2.0) + 
        Math.sin(dLon / 2.0) * Math.sin(dLon / 2.0) * Math.cos(lat1) * Math.cos(lat2);
    var c = 2.0 * Math.atan2(Math.sqrt(a), Math.sqrt(1.0 - a));
    return r * c;
};

var findLocation = function(server, callback) {
    getIpInfo(server, function(err, data) {
        callback(null, data.city + ', ' + data.region);
    });
};

var findDistance = function(ip1, ip2, callback) {
    var lat1, lon1, lat2, lon2;

    getIpInfo(ip1, function(err, data1) {
        var coords1 = data1.loc.split(',');
        lat1 = Number(coords1[0]);
        lon1 =  Number(coords1[1]);
        getIpInfo(ip2, function(err, data2) {
            var coords2 = data2.loc.split(',');
            lat2 =  Number(coords2[0]);
            lon2 =  Number(coords2[1]);

            var dist = ipDistance(lat1, lon1, lat2, lon2);
            callback(null, dist);
        });
    });
};

如需完整的源代碼,您可以在此處找到存儲庫。

我們對代碼唯一要做的就是將 CLI 參數與上面的應用程序代碼掛鉤。為方便起見,我們將把所有這些都放在一個名為 cli() 的函數中 ,我們稍後會用到。

cli()中封裝參數解析和命令映射 有助於保持應用程序代碼分離,從而允許將此代碼作為帶有 require() 的庫導入 .

var cli = function() {
    var argv = require('yargs')
        .alias('d', 'distance')
        .alias('j', 'json')
        .alias('i', 'info')
        .usage('Usage: $0 [IP | URL] [--d=IP | URL] [-ij]')
        .example('$0 -d 8.8.8.8', 'find the distance (km) between you and Google DNS')
        .describe('d', 'Get distance between IP addresses')
        .describe('j', 'Print location data as JSON')
        .describe('i', 'Print location data in human readable form')
        .help('h')
        .alias('h', 'help')
        .argv;

    var path = 'json';
    if (argv._[0]) {
        path = argv._[0];
    }

    if (argv.d) {
        findDistance(path, argv.d, function(err, distance) {
            console.log(distance);
        });
    } else if (argv.j) {
        getIpInfo(path, function(err, data) {
            console.log(JSON.stringify(data, null, 4));
        });
    } else if (argv.i) {
        getIpInfo(path, function(err, data) {
            console.log('IP:', data.ip);
            console.log('Hostname:', data.hostname);
            console.log('City:', data.city);
            console.log('Region:', data.region);
            console.log('Postal:', data.postal);
            console.log('Country:', data.country);
            console.log('Coordinates:', data.loc);
            console.log('ISP:', data.org);
        });
    } else {
        findLocation(path, function(err, location) {
            console.log(location);
        });
    }
};

exports.info = getIpInfo;
exports.location = findLocation;
exports.distance = findDistance;
exports.cli = cli;

在這裡你可以看到我們基本上只使用 if...else 語句來確定要運行的命令。您可以變得更花哨並使用 Flatiron 將正則表達式字符串映射到命令,但這對於我們在這裡所做的事情來說有點矯枉過正。

使其可執行

為了讓我們能夠執行應用程序,我們需要在 package.json 中指定一些內容 文件,例如可執行文件所在的位置。但首先,讓我們創建可執行文件及其代碼。創建一個名為 twenty 的文件 在目錄 twenty/bin/ 並將其添加到其中:

#!/usr/bin/env node
require('../index').cli();

shebang (#!/usr/bin/env node ) 告訴 Unix 如何執行文件,允許我們省略 node 字首。第二行只是從上面加載代碼並調用 cli() 功能。

package.json ,添加以下JSON:

"bin": {
    "twenty": "./bin/twenty"
}

這只是告訴 npm 在使用 -g 安裝軟件包時在哪裡可以找到可執行文件 (全局)標誌。

所以現在,如果你安裝 twenty 作為全球...

$ npm install -g twenty

...然後您可以獲取服務器的位置和IP地址:

$ twenty 198.41.209.141 #reddit
San Francisco, California

$ twenty rackspace.com
San Antonio, Texas

$ twenty usa.gov --j
{
    "ip": "216.128.241.47",
    "hostname": "No Hostname",
    "city": "Phoenix",
    "region": "Arizona",
    "country": "US",
    "loc": "33.3413,-112.0598",
    "org": "AS40289 CGI TECHNOLOGIES AND SOLUTIONS INC.",
    "postal": "85044"
}

$ twenty stackabuse.com
Ashburn, Virginia

就是這樣,Stack Abuse 服務器位於弗吉尼亞州的 Asburn。有趣=)

如需完整源代碼,請查看 Github 上的項目。


Tutorial JavaScript 教程
  1. 酷 ES6 代理黑客

  2. 如何在 JavaScript 中通過提供動態組並對值求和來創建層次結構數據?

  3. 教授前端工程微型碩士學位的經驗教訓——亞馬遜之路

  4. 我如何以及為什麼在 Tailwind 中使用情感

  5. 簡單但非常有效的作品集

  6. 使用 GraphQL、Node.js、SQLite 和(VUE、Angular 或 ReactJS)構建一個簡單的博客 - 第 2 部分

  7. 編寫 React Hooks

  1. 技術指南,第 1 部分:為 Apple 平台編譯 Hermes

  2. HTML中的屬性和屬性有什麼區別?

  3. 如何存儲對象的鍵值並將其傳遞給函數,該函數是同一對像中另一個鍵的值

  4. 一個簡單的 React 異步替代方案 useReducer

  5. 大 O 表示法、時間和空間複雜性概述

  6. 使用 NX 將業務邏輯與 UI Presenational Logic 分離

  7. Angular 中的簡單狀態管理

  1. 學習 Vue:一個 3 分鐘的交互式 Vue JS 教程

  2. 從 API 獲取的對象未出現在映射中

  3. 慣用的 JavaScript 後端。第1部分

  4. 項目參觀:麵包比例計算器