Monacaのアップロードやダウンロードが完了したら通知してくれるコマンドを作った(monaca-cli-notification)

# はじめに

みなさんmonaca使ってますか？
巷ではReactNativeやFlutterが流行っていますがmonacaもReactのソースを(ほぼ)そのまま転用出来るので悪くないと思っています。

**ただ....**
**ただ.....!!**

#### **ビルドが遅い！！！！**

RNやFlutterが実機でもホットリロードで即反映なんて言われていますが、monacaはuploadしてからremote buildするまで結構時間がかかります。
私のプロジェクトでも大体15分くらいかかります。
そのため、1回のビルドに

1. `monaca upload`を実行してからQiitaを30分くらい巡回してアップロードの終了に気づく
1. `monaca remote build`を実行してからQiitaを30分くらい巡回してビルドの終了に気づく

合計1時間くらいかかります:innocent:
~~ちゃんと見てろって話なんですが、~~なにかと作業効率が悪いのでmonaca-cliの結果を通知してくれるコマンド、[monaca-cli-notification](https://www.npmjs.com/package/monaca-cli-notification)を作りました。

Github: [https://github.com/masaki-shinkawa/monaca-cli-notification](https://github.com/masaki-shinkawa/monaca-cli-notification)
npm: [https://www.npmjs.com/package/monaca-cli-notification](https://www.npmjs.com/package/monaca-cli-notification)

# monaca-cli-notification

```
動作確認環境：macOS Mojave v10.14.5
Windowsは動作未検証のため動かない可能性があります
```

使用方法は、グローバルにインストールするか、package.jsonに追加してnpm scriptsから起動するかの２パターンです
(現状npxに対応していません)

```bash:グローバルインストール
npm i -g monaca-cli-notification
```

```bash:package.jsonに追加
npm i -D monaca-cli-notification
```

```json:package.json
"scripts": {
  "remote-build": "mcn remote build ios --build-type=debug",
  "upload": "mcn upload --delete"
},
```

基本的にはmonaca-cliをラップしているだけなのでmonacaのコマンドは何でも使えます。

```bash:example
$ mcn
$ mcn upload --delete
$ mcn remote build ios --build-type=debug
```

コマンド結果によりデスクトップ通知が表示されます。
成功した場合
<img width="382" alt="成功.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327801/6098f709-83d0-781d-810f-86a25a116151.png">
失敗した場合
<img width="382" alt="失敗.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327801/5ca739ef-400b-0005-854e-51cad064dd42.png">

# 実装の話

紹介だけも何なのでどのような構成で作成したか紹介していきます。

## 開発環境

開発環境は Typescript + Node.js で作成しました。
今回特に型を意識する必要もないのですが、babelを設定しなくていいというだけでTypescriptを使う価値があると思います。

## 依存関係のパッケージ

実現するために下記のライブラリを使用しました。
- [node-pty](https://github.com/microsoft/node-pty)
- [node-notifier](https://github.com/mikaelbr/node-notifier)

## 対話式コマンドの対応

monaca-cliは随所で対話が発生します。そのため、対話式のcliに対応する必要がありました。
その解決策として[node-pty](https://github.com/microsoft/node-pty)を使用します。
node-ptyはプロセスを仮想端末として扱ってくれるようで、標準出力と標準入力をやり取りすることができます。

使用方法は簡単で、`pty.spawn`にコマンドと引数を渡すとインスタンスが生成されので、
そこから生えている`.on("data", (e) =>{})`に標準出力内容が流れてきます。

```javascript:インスタンスの準備
// ptyのインスタンスの生成
const ptyProcess = pty.spawn(command, argv, {
  name: "xterm-color",
  cols: 80,
  rows: 30,
  cwd: process.env.PWD,
  env: process.env
```

```javascript:標準出力が発生すると呼ばれる
ptyProcess.on("data", (data: any) => {
  process.stdout.write(data); // コマンドの結果がdataに代入されている
});
```

また、`.on("exit", (e) => {})`にはコマンドのexitCodeが設定されるため、
その結果を見て正常終了、異常終了の判定が可能です。

```javascript:コマンド終了時に呼ばれる
ptyProcess.on("exit", (exitCode: number) => {
  if (exitCode === 0) {
    // 成功の通知を出す
  } else {
    // 失敗の通知を出す
  }
});
```

対話が発生した場合は`.write(string)`を実行することで入力を送信することができます。
これでmonacaの対話にも対応することが出来ました。

```javascript:ptyに入力を送信
ptyProcess.write(str + "\n");
```

## ユーザーからの入力受付

入力を送信することは出来る様になったのですが、ユーザーからの入力を受け入れれていないのでその対応に`readline`を使用します。
この処理は[「Node.jsでの標準入力(コンソール)について」](https://qiita.com/yosgspec/items/6a1e18890510f49fef12)が非常に参考になりました。ありがとうございます。

単純にユーザーからの入力を無限ループで待受続け、入力が合った場合に`.write(string)`を呼び出すだけです。

```javascript:ユーザーからの入力受付処理
//keypressイベントを使用可能にする
readline.emitKeypressEvents(process.stdin);

//readlineのインスタンスの生成
const rl = readline.createInterface({
  input: process.stdin, //標準入力を指定
  output: process.stdout, //標準出力を指定
  prompt: "", //デフォルトのプロンプトを指定(指定なしで"> ")
  terminal: true //process.stdin.setRawModeでエコーバックの切り替えを可能にする
});

(async function() {
  while (true) {
    const str = await new Promise(res => rl.once("line", res));
    ptyProcess.write(str + "\n");
  }
})();
```

**注:readlineが生き続けているとプロセスが終了しません。**
そのため、ptyの`.on("exit", (e) => {})`の最後に`readline`をcloseさせる処理を追加してあげます。

```javascript
ptyProcess.on("exit", (exitCode: number) => {
  if (exitCode === 0) {
    // 成功の通知を出す
  } else {
    // 失敗の通知を出す
  }
  rl.close(); // readlineをcloseする
});
```

## デスクトップ通知を出す

対話の処理が出来たので後はデスクトップ通知を出すだけです！
デスクトップ通知は非常に簡単で、[node-notifier](https://github.com/mikaelbr/node-notifier)を使用すると簡単に実装できます。

```javascript
const notifier = require("node-notifier");

module.exports = {
  failedNotification: () => {
    notifier.notify({
      title: "Failed",
      message: "コマンドが失敗しました..."
    });
  },
  successfulNotification: () => {
    notifier.notify({
      title: "Successful",
      message: "コマンドが成功しました！"
    });
  }
};
```

**簡単すぎる・・・！**
これだけでこのメソッドが呼ばれた時にデスクトップ通知が行われます。

## npmに公開

npmに公開する前にwebpackで圧縮した`mcn.js`を実行しようとしましたが下記のようなエラーが発生してコマンドラインから実行できませんでした。

```
bin/mcn.js: line 1: syntax error near unexpected token `e'
bin/mcn.js: line 1: ...
```

nodeで実行する分には動くのになぜ・・・？と思いましたが単純にnode.jsで実行する記述がないため、bashで起動されているのが原因でした。
shellやperlでも先頭行によく書く、あれです。

```bash:アレ
#!/bin/bash
#!/usr/bin/perl
```

nodejsの場合はこう書きます。

```
#!/usr/bin/env node
```

ただし、.tsや.jsにそのまま書くとwebpackに怒られるため、`webpack.BannerPlugin`というプラグインを使います。

```javascript
const webpack = require('webpack');
module.exports = {
    :
  plugins: [
    new webpack.BannerPlugin({ banner: "#!/usr/bin/env node", raw: true }),
  ]
}
```

こうすることでビルド後のファイルの先頭に`#!/usr/bin/env node`を追記してくれ、無事にコマンドラインから実行することが出来ます。

# おわりに

今回1,2時間程度で突貫で作ったツールのため、テスト環境なんかも全く用意していないのですが、よければ使ってみてください。 ~~この記事の方が時間がかかったパターン~~
monacaはまだまだ戦えるプラットフォームだと思うのですが、実機テストがもう少し簡単になるといいですね・・・
このツールもせっかくなのでオリジナルコマンドとかを追加していければ良いなと思います。

```bash:アップロードしてからリモートビルドするみたいな(長い？)
mcn remote build ios --build-type=debug --upload --delete
```

# 参考
## node-pty
[node-pty](https://github.com/microsoft/node-pty)

## node-notifier
[Node.jsでデスクトップ通知（gulpもね）](https://qiita.com/kyosuke5_20/items/0e36c168d36eef29c23a)
[node-notifier](https://github.com/mikaelbr/node-notifier)

## readline(標準入力)
[Node.jsでの標準入力(コンソール)について](https://qiita.com/yosgspec/items/6a1e18890510f49fef12)

## npmに公開
[コマンドラインで動作する npm ライブラリを作る](https://qiita.com/star__hoshi/items/51c6cc719793abfb337d)