0

monorepo(lerna)の各packages下でコマンドを実行する

lernaで管理してるmonorepoの各packagesディレクトリの下に移動しながらコマンド実行するやつ作った

https://www.npmjs.com/package/lerna-run


使用例


各packagesに移動してbabelでコンパイル
順番に実行していく
% lerna-run babel src/ --out-dir lib/


各packagesに移動してbabelでwatchしてコンパイル
–parallelオプションをつけると順番にではなく一気に全packagesで実行する
% lerna-run --parallel babel src/ --out-dir lib/ --watch


lernaについては
lernaでmonorepoした
に書いた

0

Edisonに最新のNode.jsを入れた

Raspberry Piにnode.jsインストール
と同じ方法

Edisonのopkgにあるnodeが0.12とかで古すぎるので、自分で5.5を入れた。
CPUはAtomなので、linux-x86のビルド済みバイナリをダウンロードしてくればすぐ使える

http://nodejs.org/dist/

% wget http://nodejs.org/dist/v5.5.0/node-v5.5.0-linux-x86.tar.gz
% tar -zxvf node-v5.5.0-linux-x86.tar.gz
% mv node-v5.5.0-linux-x86 ~/node

最初から入っている/usr/bin/nodeを消す方法がよくわからなかったのでホームディレクトリに置いてしまった

PATHを前の方に通した

.bashrc
export PATH=$HOME/node/bin:$PATH


0

lernaでmonorepoした

monorepoという1つのリポジトリに複数プロジェクトを入れる開発手法がある。
この記事で知った。

単一リポジトリで複数package|projectを管理することをmonorepoというそう – なっく日報

そういえばRocketIOやnode-lindaを作っていた時は自作のライブラリ/パッケージが3,4層に積み上がっていて、単一パッケージ内で生じるバグはテストを書けば潰せるけどパッケージ間で起こるバグは非常に解決が難しかった。イベント発火のタイミングによるものとか。パッケージをまたがったテストをどこに書くのかという問題がある。

複数パッケージで起こったバグはどっちのissueに書けばいいのかわからないし、両方でブランチ切って同時に修正してリリースしたりとか超面倒だった。
バージョン毎の依存関係も、serverの1.3に対応してるのはclientの1.5で・・とか依存関係を書くのがややこしい。
テスト・ビルド用ツールも、それぞれのnpmに.babelrcとか.eslintrcとか置いて微妙に内容が違ったりすると混乱するし、統一したい。

と色々思う所があったのだが、1つのgitリポジトリに複数npmをまとめて入れて管理すればこの辺の悩みは解決しそうなのでmonorepoを試してみる事にした。


lerna

https://github.com/kittens/lerna

babelはmonorepoでやってる
Why is Babel a monorepo?

babel 6系になってからbabel-cliとかbabel-polyfillとかbabel-preset-es2015とかめちゃくちゃパッケージが分かれてて、でもバージョン番号は合っているのでこれどうやって管理してるんだ?と思ったらlernaというNode.jsでmonorepoするためのツールを使っていた。

コマンドはbootstrap、updated、publishの3つだけ。
リポジトリ内のpackage間の依存関係を解決したり、バージョン合わせながら一括npm publishしてくれたりする。
ドキュメントが無いのでソース読んで理解した

以下はlerna v1.1.0時点についてのメモ。


% lerna bootstrap

lib/commands/bootstrap.js
bootstrapすると必要なファイルが作られる。
packagesディレクトリが作られるので、その下に複数のnpmを置ける。
それぞれの中身は個別にnpm publishできるようにpackage.jsonや.npmignoreなどを置く(自分で)

rootディレクトリには開発用のpackage.jsonが作られ、devDependenceisにlernaがインストールされる。

rootにあるVERSIONというファイルにバージョン番号が書いてある。これはpublishに使われる。

また、ファイルが作られるだけでなくpackages下の各npmでnpm installが行われ(4並列でchild_process.execしている)、monorepo内で依存関係があればnpmjs.comからインストールせずにリンクが作られる。
module.exports = require("/Users/sho/src/nodejs/weather-yahoo-jp/packages/weather-yahoo-jp");
1行だけ書いてあるindex.jsが生成されて、リポジトリ内のローカルリンクになる。

ただしpackage.jsonのdependenciesに { “weather-yahoo-jp”: “^0.1.2” } のように^で始まるバージョン番号で指定していないとリンクを作ってくれない。
“*” や “0.1.2” のようなバージョン指定ではnpmjs.comからインストールしてしまうという罠がある。


bootstrapは新しくlernaを使っているリポジトリをgit cloneしてきた時に一気にnpm installするのにも使う。CIとか。
プルリクしたい人にもわかるように、「まずlerna bootstrapしろ」とREADMEに書いておくべき。


% lerna updated

lib/commands/updated.js
packages下の各npmについて、前回のpublishから更新があるか確認する



% lerna publish

lib/commands/publish.js
updatedなnpmを全てpublishする。
VERSIONファイルのパッチレベルが0.2.3→0.2.4のように1つ上がる。publishは対話式なので自分で番号入力したり、major,minor,patchでインクリメントもできる。

また、packagesの下の依存しあっているnpmのdependenciesで指定しているバージョン番号をVERSIONに置換してくれる。
これもbootstrapでのリンクと同じように、”^0.1.2″みたいな^が頭につく形式で書いてないと置換してくれない。

最後にgit tagを打って、remoteにgit pushしてくれる。


一回目は自分でset-upstreamしないとだめだった気がする
% git push --set-upstream origin master

release時にCHANGELOGなどを手書きしている場合は、git addだけしてcommitしていない状態でlerna publishすればcommitに含めてくれる。
特に変更が無くてupdatedに検知されていないpackages下のnpmも、package.jsonのdependenciesのバージョンをこれからpublishするバージョンに変更してgit addしておけばまとめてnpm publishとcommitしてくれる。


実際やってみる

weather-yahoo-jpという天気を取得するNodeライブラリと、CLIで天気を表示するツールを1つのリポジトリにまとめてmonorepo化してみた。

こういうディレクトリ構成になった
├── .babelrc # test/lint関係はrootにだけ置く
├── .eslintrc
├── .gitignore
├── README.md
├── VERSION # バージョン番号が書いてあるファイル
├── bin
│   └── run-each-packages # 全packagesで一気にコマンドを実行するツール(自作)
├── circle.yml # CircleCIの設定
├── package.json
└── packages # この下にnpmを複数置く
├── weather-yahoo-jp
│   ├── .npmignore # publish時に.es6ファイルなどを除外する
│   ├── History.txt
│   ├── README.md
│   ├── forecast-url.json
│   ├── package.json
│   ├── samples
│   │   ├── forecast.js
│   │   └── yolp.js
│   ├── src
│   │   ├── forecast.es6
│   │   ├── index.es6
│   │   ├── util.es6
│   │   └── yolp.es6
│   ├── test
│   │   ├── test_forecast.es6
│   │   ├── test_forecasturl.es6
│   │   ├── test_helper.es6
│   │   └── test_yolp.es6
│   └── tool
│   └── create-forecast-url-list.es6
└── weather-yahoo-jp-cli
├── .npmignore
├── README.md
├── bin
│   └── cli.js
├── package.json
├── src
│   ├── main.es6
│   └── util.es6
└── test
├── test_cli.es6
└── test_helper.es6
rootのpackage.jsonのdependenciesにビルド/テスト関係のnpmを全部入れて、各packages内はdevDependencies不要という構成にしたらすっきりした。ただしtestコードは各packagesの下に置く。
npmごとにbabelやeslint入れて微妙にバージョンずれて変な感じになったりしない。

testやlintの実行

rootから一気に実行すればいい
% eslint packages/*/*/*.es6
% mocha packages/*/test/*.es6 --compilers js:babel-register
test/lint関係のnpmはrootのpackage.jsonにインストールするようにした。


CI

CircleCIは一番上のディレクトリでのnpm installは自動的にやってくれる。
packagesの下のnpm installは自分でやらなければならないので、lerna bootstrapする。
ちなみにpackage.jsonのscriptsの中は./node_modules/.bin/にPATHが通っているのでnpm runで呼び出せばlernaをグローバルインストールしなくていい。


CircleCIではrootのnode_modulesしかcacheしないので、cache_directoriesにpackages/*/node_modulesを追加した。


各packages内でそれぞれコマンドを実行する

run-each-packagesというコマンドを作った。

% ./bin/run-each-packages babel src/ --out-dir lib/ # 全package内に移動してからbabel
% ./bin/run-each-packages --parallel babel src/ --out-dir lib/ --watch # 全packageでbabelをwatch
–parallelつけると並列実行する。
babelはrootディレクトリから実行すると書き出し先をpackages下のそれぞれに指定できなかったので、run-each-packagesが必要になった。

lerna boostrapがnpm installの経過を表示してくれなくて不安なので使ったりもする。
% ./bin/run-each-packages npm install
npm installはdependenciesが多すぎるとたまにエラー吐いて死ぬので経過を表示したい。


感想

lerna便利。
ディレクトリ構成も実装も処理内容もシンプルで理解しやすい。シンプルなのでかゆいところは自分で掻ける。

packages下のdependenciesを “^0.1.2” の形式で頭に^つけて書かなければローカルリンクしてくれない事以外はだいたい直感通りに動くので混乱なかった。

0

CLIで天気を確認する

天気が表示されるだけのコマンド
shokai/weather-yahoo-jp-cli: CLI tool for weather-yahoo-jp npm


npmjs.comに公開するほどでもないのでgithubからインストールする方式でいいや

% npm i shokai/weather-yahoo-jp-cli -g
% weather-yahoo-jp 東京


コマンド名が長いけど、shellのhistoryから補完するし東京神奈川以外を見ることもないから長くてかまわん

0

Node.jsでYahooから天気予報とリアルタイム降雨データを取得する

Node.jsでYahooから天気を取得するnpmを作った。

https://www.npmjs.com/package/weather-yahoo-jp

YOLP(Yahoo Open Local Platform)のリアルタイム降雨情報のAPIと、Yahoo天気の予報の取得ができる。

Nodeに日本の天気関係のnpmが無かったのと、今まで使っていたRubyのお天気系Rubygemが全て動かなくなっていたので自作した。
天気をスクレイピングして取ってくる部分がHTMLの変更により動かなくなってしまう事が多いみたいなので、CircleCIで毎日テストを走らせる事により異常にすぐ気づけるようにしてある。


現時点でv0.2.0
forecastの方はまだ多少項目追加する予定なので(気温の前日との差や降水確率など)最新情報はREADMEを見ると良い
エラーや要望はissuetwitterにどうぞ

インストール


% npm install weather-yahoo-jp


天気予報の取得

地名、もしくは天気ページのURLでgetすると天気が得られる。
import {forecast} from "weather-yahoo-jp";

forecast
.get("横浜")
.then((forecast) => {
console.log(forecast); // 取得した天気
})
.catch((err) => {
console.error(err.stack || err);
});


{
where: '神奈川県 東部(横浜)',
today: { text: '曇後雨', temperature: { high: 9, low: 4 } },
tomorrow: { text: '晴れ', temperature: { high: 8, low: 3 } },
url: 'http://weather.yahoo.co.jp/weather/jp/14/4610.html'
}

これを使ってhubot scriptも作ってみた


forecast.getはわりと適当に地名を入れても天気を返してくれる。事前に地名と天気のURLのリストを作ってforecast-url.jsonに保存してあって、この中から適当にそれっぽい地点の天気を返す。

このリストの作成にcoを使ったらdelayをいれながらゆっくりリンクをたどる処理を普通のfor文で書けたのでcoすごいと思った。
JavaSciptでディレイを入れながらゆっくり1つずつクロールするのってqueueを使うか、async.jsとかで変な書き方しないと駄目だと思ってたんだけどRubyみたいな普通な感じに書けるのでcoすごい。


@neoyokohama の天気予報にも使ってる



YOLP APIで現在の降雨状況を取得する

Yahoo Open Local PlatformのAPIに緯度経度を渡すと今そこにどれだけ雨が降ってるか取得できる。
数分後の予報も付いてくる。単位はmm/hらしい。

YOLP(地図):気象情報API – Yahoo!デベロッパーネットワーク


先にアプリケーションIDを取得する必要がある
https://e.developer.yahoo.co.jp/register

coordinatesに10個まで地名と緯度経度のペアを設定してgetWeatherすると、地名がkeyでvalueが降雨データのオブジェクトが取得できる。

試しにobservation(観測値)とforecast配列(予測値)を単純に比較して人間語で出力してみる
import {Yolp} from "weather-yahoo-jp";
var yolp = new Yolp("取得したAPPID");

var query = {
coordinates: {
東京: "139.7667157,35.6810851",
京都: "135.7605917,35.0075224",
沖縄: "128.0150716,26.5918277",
新潟: "139.0618657,37.9123509"
}
};

yolp.getWeather(query)
.then(function(data){
for(var where in data){
var w = data[where];
if(w.observation.rain > 0){
if(w.forecast[0].rain > 0){
console.log(where + "は雨が" + w.observation.rain + "降っています");
}
else{
console.log(where + "でもうすぐ雨が止みます");
}
}
else{
if(w.forecast[0].rain === 0){
console.log(where + "は雨が降っていません");
}
else{
console.log(where + "でもうすぐ雨が" + w.forecast[0].rain + "降ります");
}
}
}
})
.catch(function(err){
console.error(err.stack || err);
});

こうなる
東京でもうすぐ雨が止みます
京都でもうすぐ雨が0.25降ります
沖縄は雨が1.65降っています
新潟は雨が降っていません

YOLP APIのレスポンスはXML形式がプライマリみたいなので、JSON形式で取得するとマークアップがJSONなだけで構造がXMLっぽい超入り組んだ変なフォーマットで返ってくる。さらに複数地点の降雨データが配列で返ってきてどれがどこなのかわからない。
このままだと厳しいのでJavaScriptから使いやすいようにgetWeather関数内で直してある。
もし元データをそのまま取得したい場合はyolp.get(query)を呼べば加工前のフォーマットで取得できる。


これを使って定期的に降雨データを取得してnode-lindaにtupleで流し、それを色々な所に通知すると、hubot-lindaが反応してslackに通知が来たり、うちのMacminiが「雨が降ります」とか喋り出したり、Hueが青や赤に点滅したりする。

雨止みますという通知を俺が受信してタイミングよく買い物に行ったりできる。