開発者として、あなたはほとんどの時間をターミナルで過ごし、いくつかのタスクを回避するのに役立つコマンドを入力する可能性があります。

これらのコマンドの一部はオペレーティングシステムに組み込まれていますが、npmやbrewなどのサードパーティのヘルパーを介してインストールしたり、バイナリをダウンロードして$PATHに追加したりすることもできます。

一般的に使用されるアプリケーションの良い例には、npm、eslint、typescript、および Angle CLI Vue CLI Create ReactAppなどのプロジェクトジェネレーターが含まれます。

このチュートリアルでは、Node.jsで2つの小さなCLIアプリケーションを構築します。

  1. https://quotes.rest/qodからその日の見積もりを取得するQuoteOfTheDayツール。
  2. JSONを使用してデータを保存するTo-Doリストアプリ。

前提条件

このチュートリアルを完了するには、次のものが必要です。

ステップ1-シェバンを理解する

スクリプトファイルを見ると、ファイルの先頭に次のような文字が表示されます。

file.sh
#!/usr/bin/env sh

またはこれ:

file.py
#!/usr/bin/env python -c

これらは、オペレーティングシステムのプログラムローダーが実行可能ファイルの正しいインタープリターを見つけて使用するための方法として機能します。 ただし、これはUnixシステムでのみ機能します。

ウィキペディアから:

コンピューティングでは、シバンは、スクリプトの先頭にある文字の番号記号と感嘆符(#!)で構成される文字シーケンスです。

NodeJSには、サポートされている独自のシバン文字があります。

logger.jsという名前の新しいファイルをエディターで作成します。

  1. nano logger.js

次のコードを追加します。

#!/usr/bin/env node

console.log("I am a logger")

最初の行は、NodeJSでこのファイルを解析するようにプログラムローダーに指示しています。 2行目は、画面にテキストを印刷します。

ターミナルにこれを入力すると、ファイルを実行してみることができます。 実行が拒否された権限を取得します。

  1. ./logger
Output
zsh: permission denied: ./logger

ファイルの実行権限を付与する必要があります。 あなたはそれをすることができます。

  1. chmod +x logger
  2. ./logger

今回は出力が表示されます。

Output
I am a logger

このプログラムはnode loggerで実行できますが、シバンを追加してプログラムを実行可能にする独自のコマンドを使用すると、nodeと入力して実行する必要がなくなります。

今日の見積もりアプリを作成する

ディレクトリを作成してqodと呼びましょう。 そして内部で、NodeJsアプリをインスタンス化します。

  1. mkdir qod
  2. cd qod
  3. npm init -y

次に、quotesサーバーにリクエストを送信する必要があることがわかっているので、既存のライブラリを使用してこれを行うことができます。 axiosを使用します

npm install --save axios

また、ターミナルで色を印刷するのに役立つライブラリであるチョークを追加します。

npm install --save chalk

次に、これらの引用符を取得するために必要なロジックを記述します。

qodという名前の新しいファイルを作成します。

  1. nano qod

次のコードをqodファイルに追加して、シェバンを指定し、ライブラリをロードして、APIURLを保存します。

qod
#!/usr/bin/env node

const axios = require('axios');
const chalk = require('chalk');

const url = "https://quotes.rest/qod";

次に、次のコードを追加して、APIにGETリクエストを作成します。

[label qod]// make a get request to the url
axios({
  method: 'get',
  url: url,
  headers: { 'Accept': 'application/json' }, // this api needs this header set for the request
}).then(res => {
  const quote = res.data.contents.quotes[0].quote
  const author = res.data.contents.quotes[0].author
  const log = chalk.green(`${quote} - ${author}`) // we use chalk to set the color green on successful response
  console.log(log)
}).catch(err => {
  const log = chalk.red(err) // we set the color red here for errors.
  console.log(log)
})

ファイルを保存します。

ファイルが実行可能になるようにファイルのアクセス許可を変更します。

  1. chmod +x qod

次に、アプリケーションを実行します。

./qod

見積もりが表示されます:

Output
The best way to not feel hopeless is to get up and do something. Don’t wait for good things to happen to you. If you go out and make some good things happen, you will fill the world with hope, you will fill yourself with hope. - Barack Obama

この例は、CLIアプリケーションで外部ライブラリを使用できることを示しています。

次に、データを保存するCLIプログラムを作成しましょう。

ToDoリストの作成

これは、データの保存と取得を伴うため、もう少し複雑になります。 これが私たちが達成しようとしていることです。

  1. todoというコマンドが必要です
  2. コマンドは4つの引数を取ります。 newgetcomplete、およびhelp

したがって、使用可能なコマンドは次のようになります

./todo new // create a new todo
./todo get // get a list of all your todos
./todo complete // complete a todo item.
./todo help // print the help text

todoというディレクトリを作成し、Node.jsアプリをインスタンス化します。

  1. mkdir todo
  2. cd todo
  3. npm install -y

次に、 chalk を再度インストールして、色でログに記録できるようにします。

npm install --save chalk

最初に行うことは、これらのコマンドが使用可能であることを確認することです。 コマンドを機能させるには、NodeJの process / argv を使用します。これは、コマンドライン引数の文字列配列を返します。process.argvプロパティは、ノードで渡されたコマンドライン引数を含む配列を返します。 .jsプロセスが起動されました。

ファイルtodoを作成します。

  1. nano todo

これをtodoファイルに追加します。

やること
#!/usr/bin/env node

console.log(process.argv)

ファイルに実行可能ファイルのアクセス許可を与えてから、新しいコマンドで実行します。

  1. chmod +x ./todo
  2. ./todo new

次の出力が得られます。

Output
[ '/Users/sammy/.nvm/versions/node/v8.11.2/bin/node', '/Users/sammy/Dev/scotch/todo/todo', 'new' ]

配列の最初の2つの文字列は、インタプリタとプログラムへのファイルのフルパスであることに注意してください。 配列の残りの部分には、渡された引数が含まれています。 この場合はnewです。

安全のために、これらを制限して、正しい数の引数(1つ)のみを受け入れることができ、newget、およびcompleteのみを受け入れることができるようにします。

todoファイルを次のように変更します。

やること
#!/usr/bin/env node

const chalk = require('chalk')
const args = process.argv

// usage represents the help guide
const usage = function() {
  const usageText = `
  todo helps you manage you todo tasks.

  usage:
    todo <command>

    commands can be:

    new:      used to create a new todo
    get:      used to retrieve your todos
    complete: used to mark a todo as complete
    help:     used to print the usage guide
  `

  console.log(usageText)
}

// used to log errors to the console in red color
function errorLog(error) {
  const eLog = chalk.red(error)
  console.log(eLog)
}

// we make sure the length of the arguments is exactly three
if (args.length > 3) {
  errorLog(`only one argument can be accepted`)
  usage()
}

最初にコマンドライン引数を変数に割り当ててから、下部で長さが3以下であることを確認します。

usage文字列も追加しました。これは、コマンドラインアプリが期待するものを出力します。 以下のような間違ったパラメータでアプリを実行します。

  1. ./todo new app
Output
only one argument can be accepted todo helps you manage you todo tasks. usage: todo <command> commands can be: new: used to create a new todo get: used to retrieve your todos complete: used to mark a todo as complete help: used to print the usage guide

1つのパラメーターを指定して実行すると、何も出力されません。つまり、コードはパスします。

次に、4つのコマンドのみが予期されていることを確認する必要があり、それ以外はすべて無効として出力されます。

ファイルの先頭にコマンドのリストを追加します。

やること
const commands = ['new', 'get', 'complete', 'help']

そして、長さを確認した後、渡されたコマンドで確認します。

やること
...
if (commands.indexOf(args[2]) == -1) {
  errorLog('invalid command passed')
  usage()
}

ここで、無効なコマンドを使用してアプリを実行すると、これが発生します。

  1. ./todo ne
Output
invalid command passed todo helps you manage you todo tasks. usage: todo <command> commands can be: new: used to create a new todo get: used to retrieve your todos complete: used to mark a todo as complete help: used to print the usage guide

次に、usage関数を呼び出して、helpコマンドを実装しましょう。 これをtodoファイルに追加しましょう:

やること

//...
switch(args[2]) {
  case 'help':
    usage()
    break
  case 'new':
    break
  case 'get':
    break
  case 'complete':
    break
  default:
    errorLog('invalid command passed')
    usage()
}
//...

switchステートメントがあり、呼び出されたコマンドに基づいて関数を呼び出します。 よく見ると、helpの場合はusage関数を呼び出すだけであることがわかります。

newコマンドは、新しいToDoアイテムを作成し、それをjsonファイルに保存します。 使用するライブラリはlowdbです。 必要に応じて、jsonファイルの読み取りと書き込みを行う関数を簡単に作成できます。

lowdbをインストールします

  1. npm install --save lowdb

[readline](https://nodejs.org/api/readline.html)lowdbの依存関係を追加して、データの保存に役立てましょう。 lowdbコードは、githubページの標準です。

やること

//...
const rl = require('readline');

const low = require('lowdb')
const FileSync = require('lowdb/adapters/FileSync')

const adapter = new FileSync('db.json')
const db = low(adapter)

// Set some defaults (required if your JSON file is empty)
db.defaults({ todos: []}).write()
//...

次に、ユーザーにデータの入力を求める関数を追加します。

やること

//...
function prompt(question) {
  const r = rl.createInterface({
    input: process.stdin,
    output: process.stdout,
    terminal: false
  });
  return new Promise((resolve, error) => {
    r.question(question, answer => {
      r.close()
      resolve(answer)
    });
  })
}
//...

ここでは、readlineライブラリを使用して、ユーザーにプロンプトを表示して出力を読み取るのに役立つインターフェイスを作成しています。

次に、ユーザーがnewコマンドを入力したときに呼び出される関数を追加する必要があります。

やること

//...
function newTodo() {
  const q = chalk.blue('Type in your todo\n')
  prompt(q).then(todo => {
    console.log(todo)
  })
}
//...

プロンプトの青色を取得するためにチョークを使用しています。 そして、結果をログに記録します。

最後に、newの場合に関数を呼び出します。

やること

// ...
switch(args[2]) {
  //...
  case 'new':
    newTodo()
    break
	// ...
}
// ...

新しいコマンドを使用してアプリを実行すると、todoを追加するように求められます。 入力してEnterキーを押します。

  1. ./todo new
Output
Type in your todo This my todo aaaaaaw yeah This my todo aaaaaaw yeah

これに似たものが表示されるはずです。

また、db.jsonファイルがファイルシステムに作成されており、todosプロパティがあることにも注意してください。

次に、todoを追加するためのロジックを追加しましょう。 newTodo関数を変更します。

やること

//...
function newTodo() {
  const q = chalk.blue('Type in your todo\n')
  prompt(q).then(todo => {
    // add todo
    db.get('todos')
      .push({
	  title: todo,
	  complete: false
	  })
      .write()
  })
}
//...

コードを再度実行します。

  1. ./todo new
Output
Type in your todo Take a Scotch course

db.jsonを見ると、todoが追加されていることがわかります。 次のgetコマンドで取得できるように、さらに2つ追加します。 db.jsonファイルのレコード数が増えると次のようになります。

db.json

{
  "todos": [
    {
      "title": "Take a Scotch course",
      "complete": false
    },
    {
      "title": "Travel the world",
      "complete": false
    },
    {
      "title": "Rewatch Avengers",
      "complete": false
    }
  ]
}

newコマンドを作成した後、getコマンドを実装する方法をすでに理解しているはずです。

ToDoを取得する関数を作成します。

やること

//...
function getTodos() {
  const todos = db.get('todos').value()
  let index = 1;
  todos.forEach(todo => {
    const todoText = `${index++}. ${todo.title}`
    console.log(todoText)
  })
}
//...

// switch statements
switch(args[2]) {
	//...
	case 'get':
		getTodos()
		break
	//...
}
//....

コマンドを再度実行します。

  1. ./todo get

今すぐアプリを実行すると、次の出力が得られます。

Output
1. Take a Scotch course 2. Travel the world 3. Rewatch Avengers

chalk.greenを使用して緑色にすることができます。

次に、少し複雑なcompleteコマンドを追加します。

あなたは2つの方法でそれを行うことができます。

  1. ユーザーが./todo completeと入力するたびに、すべてのToDoを一覧表示し、Todoに完了としてマークするための番号/キーを入力するように依頼できます。
  2. 別のパラメーターを追加して、ユーザーが./todo getと入力し、./todo complete 1などのパラメーターで完了としてマークするタスクを選択できるようにすることができます。

newコマンドを実装したときに最初の方法を実行する方法を学習したので、オプション2を見ていきます。

このオプションを使用すると、コマンド./todo complete 1は、指定されたコマンド数の有効性チェックに失敗します。 したがって、最初にこれを処理する必要があります。 引数の長さをチェックする関数を次のように変更します。

やること

//...
// we make sure the length of the arguments is exactly three
if (args.length > 3 && args[2] != 'complete') {
  errorLog('only one argument can be accepted')
  usage()
  return
}
///...

このアプローチは真理値表を使用します。ここで、TRUE && FALSEFALSEと等しく、completeが渡されるとコードはスキップされます。

次に、新しい引数の値を取得し、todoの値を完了したものにします。

やること

//...
function completeTodo() {
  // check that length
  if (args.length != 4) {
    errorLog("invalid number of arguments passed for complete command")
    return
  }

  let n = Number(args[3])
  // check if the value is a number
  if (isNaN(n)) {
    errorLog("please provide a valid number for complete command")
    return
  }

  // check if correct length of values has been passed
  let todosLength = db.get('todos').value().length
  if (n > todosLength) {
    errorLog("invalid number passed for complete command.")
    return
  }

  // update the todo item marked as complete
  db.set(`todos[${n-1}].complete`, true).write()
}
//...

また、switchステートメントを更新して、completeコマンドを含めます。

やること

//...
case 'complete':
    completeTodo()
    break
//...

./todo complete 2でこれを実行すると、db.jsonがこれに変更され、2番目のタスクが完了としてマークされていることがわかります。

db.json

{
  "todos": [
    {
      "title": "Take a Scotch course",
      "complete": false
    },
    {
      "title": "Travel the world",
      "complete": true
    },
    {
      "title": "Rewatch Avengers",
      "complete": false
    }
  ]
}

最後に行う必要があるのは、./todo getを変更して、実行されたタスクのみを表示することです。 これには絵文字を使用します。 次のコードでgetTodosを変更します。

やること

//...
function getTodos() {
  const todos = db.get('todos').value()
  let index = 1;
  todos.forEach(todo => {
    let todoText = `${index++}. ${todo.title}`
    if (todo.complete) {
      todoText += ' ✔ ️' // add a check mark
    }
    console.log(chalk.strikethrough(todoText))
  })
  return
}
//...

./todo getと入力すると、これが表示されます。

Output
1. Take a Scotch course 2. Travel the world ✔ ️ 3. Rewatch Avengers

結論

Node.jsで2つのCLIアプリケーションを作成しました。

アプリが機能したら、ファイルをbinフォルダーに配置します。 このように、npmは、実行可能ファイルを配布するときに実行可能ファイルを操作する方法を知っています。 また、実行可能ファイルを配置する場所に関係なく、package.json binプロパティを更新する必要があります。

この記事の焦点は、CLIアプリケーションがvanilla nodejsを使用してどのように構築されるかを調べることでしたが、現実の世界で作業する場合は、ライブラリを使用する方が生産性が高くなります。

npmに公開できる素晴らしいCLIアプリケーションを作成するのに役立つライブラリのリストを次に示します。

  1. vopral-フル機能のインタラクティブCLIフレームワーク
  2. meow-CLIヘルパーライブラリ
  3. commanderjs-CLIライブラリ
  4. minimist-引数の解析用
  5. yargs-引数の解析

そして、色を手伝ってくれたチョークのようなライブラリは言うまでもありません。

追加の演習として、CLIにDeleteコマンドを追加してみてください。