TypeScriptとDenoを使用してCLIを構築する方法

May 20, 2021
執筆者
Maciej Treder
寄稿者
Twilio の寄稿者によって表明された意見は彼ら自身のものです
レビュー担当者

How-to-Use-TypeScript-and-Deno-to-Build-a-CLI-jp

このBlogはMaciej Trederこちらで公開した記事を日本語化したものです。

Twilio Programmable SMSには、SMS APIとのやりとりを容易にするHTTP REST APIが含まれます。REST APIを使用してSMSメッセージのリクエストを作成すると、そのステータスは他のエンドポイントを使用して取得できます。REST APIを組み込むことにより、メッセージと配信についてのレポートを送信するDeno CLIアプリケーションを構築できます。

DenoはJavaScript用の新しいランタイム環境です。この環境ではNode.jsの機能を提供しますが、Node.jsアプリケーションで必要な重いパッケージの展開や、複雑なパッケージ管理は不要です。

Denoが提供する新たな機能は、JavaScriptを使用して開発されている現代のサーバー側アプリケーションをサポートします。そのアプリケーションの幅広さは、Node.jsが開発されたときには想定されていなかったものです。

DenoはTypeScriptをネイティブでサポートするため、追加のコンポーネントは必要ありません。Denoを使用して書かれたアプリケーションは「リモート」でも起動できます。つまり、ユーザーが実行するスクリプトは、URIで指定され、そのURIはWeb上にホストされているファイルを示しているということです。Denoがスクリプトやその他の必要な依存関係を自動的にダウンロードしプログラムを実行します。

Denoはモジュール性に優れています。既存のコードを活用したプログラムの構築が可能であり、SMSメッセージのリクエストを作成してステータスを監視します。また、他のプログラミング言語と同様にコマンドライン引数をサポートしているため、CLIプログラムを作成してあらゆるユースケースに対応できます。

コンプライアンスに関する重要なお知らせ: SMSメッセージの利用については、国によりルールが異なります。メッセージ送信先国のルールについては、Twilio利用規定のSMS向けガイドラインをご確認ください。

チュートリアルのプロジェクトについて

このチュートリアルでは、Denoを使用してCLIプログラムを作成する方法について説明します。Denoの機能を試すため、電話番号にSMSメッセージを送信し、メッセージの配信ステータスをレポートするCLIプログラムを記述します。Twilio CLIと異なる点として、このプログラムはメッセージの配信ステータスをレポートする際に他のコマンドを実行する必要がない点が挙げることができます。チュートリアルで使用するものはTwilioSMSHelperです。このヘルパーはTwilio Programmable SMSを利用してTwilioのREST API経由でメッセージを送信します。

さらにyargsライブラリを利用し、Denoでコマンドライン引数を読み込む方法を学習します。引数エイリアスのショートカットを登録する方法(--argument, -a)についても理解しましょう。また、ユーザーが引数の一部を指定しない場合に、環境変数にフォールバックする方法も学習します(ここでlodashを利用します)。さらに、lodashのdifference()メソッドを利用して2つのオブジェクトキーを比較し、入力値が不足していないことを確認する方法も学習します。

前提条件

このチュートリアルで説明するプロジェクトを完成させるには、次のツールとリソースが必要です。

  • Deno – インストール手順に則りパッケージをインストール。さまざまなオペレーティングシステムとパッケージ管理ツールに対応
  • Twilioアカウント – このリンクから無料でサインアップすると、通常版アカウントにアップグレードする際に利用できる10ドルのクレジットを進呈
  • Twilio電話番号 -  Twilio CLIを使用して電話番号を登録する方法を確認
  • Git – コンパニオンリポジトリのクローン作成、またはソースコードをGit repoとして管理する際に

Visual Studio Codeを利用している場合は、次のdenoland.vscode-deno拡張機能がインストールされていることを確認してください。

vscode - deno extension

Deno拡張機能はインテリジェントモジュールインポートをはじめとする多数の機能を提供し、IntelliSenseを完全サポートしています。

また、TypeScript、非同期JavaScriptのメカニズム、ReactiveXプログラミングのコア要素に関する実践的知識やDenoの要点に関する知識も有益です。はじめてDenoを利用する場合は、Twilioブログのこちらの投稿「Hello Deno」で基礎を学習できます。

さらに、この投稿に関するコンパニオンリポジトリがGitHubで提供されています。このチュートリアルで説明するプロジェクトに使用できる完全なソースコードを含み、MITライセンスで利用できます。ご自身のプロジェクトでご使用ください。

Twilioアカウントの資格情報の取得

Twilio CLIを使用し、Twilio APIで情報をやりとりするには、3つの重要な情報が必要です。このアカウントSIDAPIキー APIシークレットの情報はTwilioコンソールのダッシュボードで確認できます。[Account ID](アカウントSID)は、ダッシュボードの右上にあります。APIキーとAPIシークレットを生成するには、[Settings](設定)の[API Keys](APIキー)に移動します。ここで赤い[+]ボタンをクリックすると新しいキーが生成されます。

以上の情報を手元にご用意ください。次のステップでご自身のCLIアプリをテストする際に必要です。

Deno CLIの構築

Denoプロジェクトの初期化

電話番号の登録とテストが済むと、一連のコマンドラインの命令によりプロジェクトとGitリポジトリを初期化できます。

コンソール画面を開き、プロジェクトディレクトリを作成するディレクトリで次の命令を実行します。

mkdir twilio-sms-deno
cd twilio-sms-deno
git init
touch twilioSMSCLI.ts
git add -A
git commit -m "Initial commit"

このコマンドでプロジェクトディレクトリと最初のコードファイルを作成しプロジェクトのGitリポジトリを初期化します。

Visual Studio Code用のDeno拡張機能の有効化

前提条件」のセクションで述べたVisual Studio CodeとDeno拡張機能を使用している場合は、このプロジェクト用に拡張機能を初期化する必要があります。

プロジェクトフォルダで.vscode/settings.jsonファイルを作成し、次のJSONコードを追加します。

// .vscode/settings.json
{
 "deno.enable": true,
}

VS Codeのユーザーインターフェイスを使用し、ワークスペースの設定を変更することもできます。Deno拡張機能は、ユーザーに対して有効にはしないでください。

コマンドライン引数の読み込み

ユーザーが指定した引数を読み込むには、yargsライブラリを使用します。Denoの性質上、依存関係をインストールする必要はありません。import文を追加するだけで十分です。  

twilioSMSCLI.tsファイルを開き、次のコードをコピーします。

import yargs from 'https://cdn.deno.land/yargs/versions/yargs-v16.2.1-deno/raw/deno.ts';

interface Arguments {
  from: string;
  to: string;
  body: string;
  sid: string;
  apikey: string;
  secret: string;
}

let inputArgs: Arguments = yargs(Deno.args)
.alias('f', 'from')
.alias('t', 'to')
.alias('b', 'body')
.alias('i', 'sid')
.alias('k', 'apikey')
.alias('s', 'secret').argv;

console.log(inputArgs);

yargsライブラリをインポートする場合を除き、上記のコードはArgumentsインターフェイスを取り込みます。このインターフェイスは、プログラムが受け付けた引数のリストです。リストには次の引数が含まれます。

  • from - Twilio番号。メッセージの送信に使用
  • to - 受信者の電話番号
  • body - メッセージ本文
  • sid - TwilioのアカウントSID
  • apikey - Twilio APIキー(API SID)
  • secret - 指定されたAPIキーに対応するシークレット

後ほど、Deno.args下に保存されているコマンドライン引数を、yargsライブラリを使用して解析し、inputArgs変数に割り当てます。なお、各パラメーターのショートカットを定義することにより、完全なパラメーター名または1文字の短縮形を使用できます。

  • --from => -f
  • --to => -t
  • --body => -b
  • --sid => -i
  • --apikey => -k
  • --secret => -s

最後に、受け取った入力値をコンソールに表示します。

次のコマンドを使用してプログラムのテストを実行します。

deno run twilioSMSCLI.ts --from +123456788 --to +987654321 --body "Hello Deno" --sid ABCD --apikey EFGH --secret IJKL
deno run twilioSMSCLI.ts -f +123456788 -t +987654321 -b "Hello Deno" -i ABCD -k EFGH -s IJKL
deno run twilioSMSCLI.ts
deno run twilioSMSCLI.ts --to abc  

次のように表示されます。

{
  _: [],
  from: "+123456788",
  f: "+123456788",
  to: "+987654321",
  t: "+987654321",
  body: "Hello Deno",
  b: "Hello Deno",
  sid: "ABCD",
  i: "ABCD",
  apikey: "EFGH",
  k: "EFGH",
  secret: "IJKL",
  s: "IJKL",
  $0: "deno run"
}
{
  _: [],
  from: "+123456788",
  f: "+123456788",
  to: "+987654321",
  t: "+987654321",
  body: "Hello Deno",
  b: "Hello Deno",
  sid: "ABCD",
  i: "ABCD",
  apikey: "EFGH",
  k: "EFGH",
  secret: "IJKL",
  s: "IJKL",
  $0: "deno run"
}
{ _: [], $0: "deno run" }
{ _: [], to: "abc", t: "abc", $0: "deno run" }

このようにフルネームと短縮形のいずれを使用しても、引数を渡すことができます。

残念ながらこのプログラムはユーザーが必要な情報をすべて入力したかどうかを検証していません。この点については、エラー処理によるプログラムの機能強化で改善するとよいでしょう。

最初に、lodashライブラリを取り込むために次のimport文をtwilioSMSCLI.tsファイルの最上部に入力します。

import * as _ from 'https://deno.land/x/lodash@4.17.15-es/lodash.js';

続けて次のコードを追加します。追加する場所は、最下行のconsole.log(inputArgs);文のすぐ上です。

let errorMessages: {[k: string]: string} = {
   from: 'Provide the message sender (From:) value using --from [-f] parameter',
   to: 'Provide the message receiver (To:) value using --to [-t] parameter',
   body: 'Provide the message body value using --body [-b] parameter',
   apikey: 'Provide your Twilio API key SID using --apikey [-k] parameter',
   sid: 'Provide your Twilio account SID using --sid [-i] parameter',
   secret: 'Provide your Twilio API key secret using --secret [-s] parameter'
};

let errors: string[] = _.difference(_.keys(errorMessages), _.keys(inputArgs));
if (errors.length > 0) {
   errors.forEach(error => console.log(errorMessages[error]));
   console.log('Proper program usage is: deno run --allow-env --allow-net twilioSMSCLI.ts --from +123456788 --to +987654321 --body "Hello Deno" --sid ABCD --apikey EFGH --secret IJKL');
   Deno.exit(1)
}

errorMessagesマップオブジェクトの取り込みが終わりました。このオブジェクトは、引数の一部が不足している場合に表示されるメッセージを保持しています。

オブジェクト宣言の:{[k:string]: string}型は、このオブジェクトが、string型プロパティをstringキーの下に保持しているという情報を、TypeScriptコンパイラに伝えます。そのおかげで、stringリテラルの代わりにエントリーキーを表す変数を使用しこれらのオブジェクトのプロパティに動的にアクセスできます。

次にerrors配列を取り込みました。この配列には、不足しているユーザー入力値に関する情報が保持されています。この配列を完成するために、difference()メソッド(lodashライブラリ)を使用しました。このメソッドは2つの配列(A and B)をパラメーターとして受け取り、3つ目の配列を返します。3つ目の配列に含まれる要素は、A配列にあり、B配列にはない要素です(A minus B: A\B)。この配列(A + B)は、オブジェクトerrorMessagesinputArgsのキーセットです。

つまり、inputArgsに含まれないすべてのキーを、errorMessagesから探していることになります。

最後に、errorsの配列長を確認します。0より大きい場合は一部の入力が不足していることを意味し、エラーメッセージが表示されます。if文の中では、errors配列を用いて反復処理を行い、対応するエラーメッセージをerrorMessagesから出力します。次に、ユーザーにサンプルプログラムを提供し、Deno.exit(1)文を用いてDenoのプロセスを終了します。

プログラムに戻り、入力値が不足値に対して検証されているかどうかを確認します。

deno run twilioSMSCLI.ts

次のように出力されます。

{ _: [], $0: "deno run" }
Provide the message sender (From:) value using --from [-f] parameter
Provide the message receiver (To:) value using --to [-t] parameter
Provide the message body value using --body [-b] parameter
Provide your Twilio API key SID using --apikey [-k] parameter
Provide your Twilio account SID using --sid [-i] parameter
Provide your Twilio API key secret using --secret [-s] parameter
Proper program usage is: deno run --allow-env --allow-net twilioSMSCLI.ts --from +123456788 --to +987654321 --body "Hello Deno" --sid ABCD --apikey EFGH --secret IJKL

この時点ですべての引数が指定されていることを検証しています。

フォールバック: 引数が指定されていない場合に環境変数を参照

指定されるはずの多くの引数を、省略したがるユーザーもいます。そこで多くの場合、アカウントSIDAPIキーAPIシークレットTwilioの電話番号などの値を、環境変数に保管する方法がとられます。この手法では保管されている引数を必須のものとして扱い、値が提供されない場合には環境変数の値にフォールバックします。

この環境変数フォールバック機能を導入するには、lodashライブラリを使用します。

defaults()メソッド(lodashライブラリから)と環境変数の値を使用して不足している値をinputArgsに渡すことができます(例: Deno.env.get('VARIABLE_NAME'))。

errorMessages宣言の前に次のコードを入れてください。次のコードを、errorMessagesオブジェクトとinputArgsオブジェクトのキーの違いを調べる行の前に挿入します: let errors: string[] = _.difference(_.keys(errorMessages), _.keys(inputArgs));

inputArgs = _.defaults(inputArgs, {
   sid: Deno.env.get('TWILIO_ACCOUNT_SID'),
   apikey: Deno.env.get('TWILIO_API_KEY'),
   secret: Deno.env.get('TWILIO_API_SECRET'),
   from: Deno.env.get('TWILIO_PHONE_NUMBER')
});
inputArgs = <any> _.pickBy(inputArgs, _.identity);

この段階で、完成したコードは次の内容となります。

import yargs from 'https://cdn.deno.land/yargs/versions/yargs-v16.2.1-deno/raw/deno.ts';
import * as _ from 'https://deno.land/x/lodash@4.17.15-es/lodash.js';

interface Arguments {
   from: string;
   to: string;
   body: string;
   sid: string;
   apikey: string;
   secret: string;
}

let inputArgs: Arguments = yargs(Deno.args)
   .alias('f', 'from')
   .alias('t', 'to')
   .alias('b', 'body')
   .alias('i', 'sid')
   .alias('k', 'apikey')
   .alias('s', 'secret').argv;

let errorMessages: {[k: string]: string} = {
   from: 'Provide the message sender (From:) value using --from [-f] parameter',
   to: 'Provide the message receiver (To:) value using --to [-t] parameter',
   body: 'Provide the message body value using --body [-b] parameter',
   apikey: 'Provide your Twilio API key SID using --apikey [-k] parameter',
   sid: 'Provide your Twilio account SID using --sid [-i] parameter',
   secret: 'Provide your Twilio API key secret using --secret [-s] parameter'
};

inputArgs = _.defaults(inputArgs, {
   sid: Deno.env.get('TWILIO_ACCOUNT_SID'),
   apikey: Deno.env.get('TWILIO_API_KEY'),
   secret: Deno.env.get('TWILIO_API_SECRET'),
   from: Deno.env.get('TWILIO_PHONE_NUMBER')
});
inputArgs = <any> _.pickBy(inputArgs, _.identity);


let errors: string[] = _.difference(_.keys(errorMessages), _.keys(inputArgs));
if (errors.length > 0) {
   errors.forEach(error => console.log(errorMessages[error]));
   console.log('Proper program usage is: deno run --allow-env --allow-net twilioSMSCLI.ts --from +123456788 --to +987654321 --body "Hello Deno" --sid ABCD --apikey EFGH --secret IJKL');
   Deno.exit(1)
}

console.log(inputArgs);

フォールバックの機能をテストするには、アカウントの資格情報とTwilio電話番号を環境変数に保存します。

LinuxやmacOSなどのUnixベースのオペレーティングシステムを使用している場合は、次のコマンドで環境変数を設定します。

export TWILIO_ACCOUNT_SID=<your account sid>
export TWILIO_API_KEY=<your API key>
export TWILIO_API_SECRET=<your API secret>
export TWILIO_PHONE_NUMBER=<your Twilio phone number - including + and country code>

Windowsを使用している場合は次のコマンドを使用します。

setx TWILIO_ACCOUNT_SID <your account sid>
setx TWILIO_API_KEY=<your API key>
setx TWILIO_API_SECRET=<your API secret>
setx TWILIO_PHONE_NUMBER <your Twilio phone number>

プログラムを実行し、環境変数へのフォールバックが正しく動作することを確認します。

deno run --allow-env twilioSMSCLI.ts --to +987654321 --body "Hello Deno"

次のような出力が表示されます。

{
  _: [ "Deno"" ],
  to: "+987654321",
  t: "+987654321",
  body: ""Hello",
  b: ""Hello",
  $0: "deno run",
  sid: "<your-sid>",
  apikey: "<your-api-key>",
  secret: "<your-secret>",
  from: "<your-twilio-phone-number>"
}

Twilio APIを使用してSMSを送信する

いよいよプログラムの本質について説明します。Denoの再利用可能性と、「Sending SMS Messages with Deno, TypeScript, and Twilio Messaging(Deno,TypeScript、Twilio Messagingを利用したSMSメッセージの送信)」の投稿で紹介されているコードを活用します。

次の文をtwilioSMSCLI.tsファイルの最初に追加し、TwilioSMSオブジェクトとSMSRequestオブジェクトをインポートします。

import { TwilioSMS, SMSRequest } from 'https://raw.githubusercontent.com/maciejtreder/deno-twilio-messaging/step1/twilioSMS.ts';

twilioSMSCLI.tsファイルの末尾のconsole.log()の前に、inputArgsオブジェクトの値に基づきSMSRequestオブジェクトを作成します。そしてTwilioSMSヘルパーメソッドを初期化します。次に、sendSms()メソッドをhelperオブジェクトで呼び出します。

const message: SMSRequest = {
   From: inputArgs.from,
   To: inputArgs.to,
   Body: inputArgs.body,
};
const helper = new TwilioSMS(inputArgs.sid, inputArgs.apikey, inputArgs.secret);
helper.sendSms(message).subscribe(console.log);

最終的に、console.log()の呼び出しを含むこの行は、不要になれば削除できます。

SMS対応の電話番号を使用してプログラムを実行します(<phone_number>を、このSMS対応の電話番号に置き換えます)。

deno run --allow-env --allow-net twilioSMSCLI.ts --to <phone_number> --body "Hello Deno"

携帯端末にSMSが送信されます。

hello deno sms

コードの完成

これでプログラムの準備ができました。次の内容であることを確認してください。

import yargs from 'https://cdn.deno.land/yargs/versions/yargs-v16.2.1-deno/raw/deno.ts';
import * as _ from 'https://deno.land/x/lodash@4.17.15-es/lodash.js';
import { TwilioSMS, SMSRequest } from 'https://raw.githubusercontent.com/maciejtreder/deno-twilio-messaging/step1/twilioSMS.ts';

interface Arguments {
   from: string;
   to: string;
   body: string;
   sid: string;
   apikey: string;
   secret: string;
}

let inputArgs: Arguments = yargs(Deno.args)
   .alias('f', 'from')
   .alias('t', 'to')
   .alias('b', 'body')
   .alias('i', 'sid')
   .alias('k', 'apikey')
   .alias('s', 'secret').argv;

let errorMessages: {[k: string]: string} = {
   from: 'Provide the message sender (From:) value using --from [-f] parameter',
   to: 'Provide the message receiver (To:) value using --to [-t] parameter',
   body: 'Provide the message body value using --body [-b] parameter',
   apikey: 'Provide your Twilio API key SID using --apikey [-k] parameter',
   sid: 'Provide your Twilio account SID using --sid [-i] parameter',
   secret: 'Provide your Twilio API key secret using --secret [-s] parameter'
};

inputArgs = _.defaults(inputArgs, {
   sid: Deno.env.get('TWILIO_ACCOUNT_SID'),
   apikey: Deno.env.get('TWILIO_API_KEY'),
   secret: Deno.env.get('TWILIO_API_SECRET'),
   from: Deno.env.get('TWILIO_PHONE_NUMBER')
});
inputArgs = <any> _.pickBy(inputArgs, _.identity);


let errors: string[] = _.difference(_.keys(errorMessages), _.keys(inputArgs));
if (errors.length > 0) {
   errors.forEach(error => console.log(errorMessages[error]));
   console.log('Proper program usage is: deno run --allow-env --allow-net twilioSMSCLI.ts --from +123456788 --to +987654321 --body "Hello Deno" --sid ABCD --apikey EFGH --secret IJKL');
   Deno.exit(1)
}

const message: SMSRequest = {
   From: inputArgs.from,
   To: inputArgs.to,
   Body: inputArgs.body,
};
const helper = new TwilioSMS(inputArgs.sid, inputArgs.apikey, inputArgs.secret);
helper.sendSms(message).subscribe(console.log);

これまでの手順を実行してきてはいないが、このチュートリアルのプログラムを実行したい場合は、コンパニオンリポジトリに完全なコードがありますのでご利用ください。このリポジトリをクローニングするには、プロジェクトのルートディレクトリを作成するディレクトリで、次のコマンドラインの手順を実行します(<phone_number>をSMS対応の電話番号に置き換えてください)。

git clone https://github.com/maciejtreder/deno-twilio-messaging.git
cd deno-twilio-messaging
git checkout step3
deno run --allow-env --allow-net twilioSMSCLI.ts --to <phone_number> --body "Hello Deno"

この記事では、yargsライブラリを使用してDenoにコマンドライン引数を読み込む方法について学びました。引数エイリアスのショートカットの登録方法も習得しました(--argument -a)。さらに、ユーザーから引数の一部が提供されない場合に、lodashを利用して環境変数へフォールバックする方法も分かりました。

Denoエコシステムの設計は、コードの再利用性に優れています。オンラインで公開されているコードは、他のDenoプログラムの中でも、スタンドアロンでも使用できます。次のコマンドを実行し、ぜひお試しください(<phone_number>をSMS対応の電話番号に置き換えてください)。

deno run --allow-env --allow-net https://raw.githubusercontent.com/maciejtreder/deno-twilio-messaging/f13de1b1d0392c63e23857127872a86fffc6a87e/twilioSMSCLI.ts -t <phone_number> -b "Hello Deno"

その他のリソース

この投稿で紹介した内容について詳しくは、次のリソースを参照してください。

Hello Deno - Denoの基本について学習できます。

Asynchronous JavaScript: Introducing ReactiveX and RxJS Observables(非同期JavaScript: ReactiveXとRxJS Observablesの紹介) – ReactiveX Observablesを使用するプログラム方法を学びます。

Confirming SMS Message Delivery with RxJS Observables, Node.js, and Twilio Programmable SMS(RxJS Observables、Node.js、Twilio Programmable SMSを使用するSMSメッセージ配信の確認) - Node.jsでTwilioを使用してSMSメッセージを送信する方法について学びます。

TwilioQuest – 16ビットスタイルのアドベンチャーゲームでレガシーシステムの呪縛から解放されます。

Maciej Trederは、Akamai TechnologiesのSenior Software Development Engineerです。国際会議のスピーカーを務め、@ng-toolkitの作者でもあります。詳しくは、https://www.maciejtreder.comをご覧ください。ご連絡をいただく際は、contact@maciejtreder.com、@maciejtreder(GitHub)、Twitter、StackOverflow、LinkedInからお願いいたします。

この投稿はGabriela Rogowskaの協力によるものです。