dtsmのご紹介


**2017/03/19 現在、dtsmは[@typesの登場](http://www.typescriptlang.org/docs/handbook/declaration-files/consumption.html)により、(結構前から)tsdと同じくdeprecatedになっています**

---

どうも、[わかめ](https://twitter.com/vvakame)です。
[冬コミ 3日目 西く02a](http://techbooster.github.io/c87/#typescript)よろしくね！
1月で1人で104Pほど書いて死ぬかと思いました。
しかも仕事がクソ忙しいんですよ！！なんでじゃ！！！
本自体について冬コミに前後して全文を公開する予定ですが、表紙データは配布しないこと、紙版が売れなくて赤字が出ると夏コミなどのやる気に大幅に影響することを鑑みてみなさん買ってくださると嬉しいです！ｵﾅｼｬｽ！

さて、TypeScriptアドベントカレンダー18日目ということでご紹介するのはdtsm(.d.ts manager)です。
TypeScriptには型定義ファイルという、既存のJavaScript資産をTypeScriptで利用するための型のヒントファイルがあります。
その型定義ファイルは[DefinitelyTyped](https://github.com/borisyankov/DefinitelyTyped)に集積されています。
これを手軽に扱うための、さながら[npm](http://npmjs.org/package/npm)や[bower](https://www.npmjs.com/package/bower)のようなツールがあると便利です。

そこで、dtsmというツールを作りました。
今までこそこそ公開してたのですが、この機会に広く紹介して皆さんに使ってもらえると嬉しいなー、と思います。

## dtsmの使い方

### dtsmのインストール

```
$ npm install -g dtsm
```

### 設定ファイルの生成

```
$ dtsm init
write to dtsm.json
{
  "repos": [
    {
      "url": "https://github.com/borisyankov/DefinitelyTyped.git",
      "ref": "master"
    }
  ],
  "path": "typings",
  "bundle": "typings/bundle.d.ts",
  "dependencies": {}
}
```

### 型定義ファイルの検索

初回はDefinitelyTypedのリポジトリをまるごと`~/.dtsm`に落としてくるので結構時間がかかります。

```
$ dtsm search atom
dtsm search atom
Search results.

	atom/atom.d.ts
	dojo/dojox.atom.d.ts
```

### 型定義ファイルのダウンロード及び保存

依存関係の解決は自動(必ず)ONになります。

```
$ dtsm install atom --save
atom/atom.d.ts
q/Q.d.ts
jquery/jquery.d.ts
space-pen/space-pen.d.ts
emissary/emissary.d.ts
pathwatcher/pathwatcher.d.ts
text-buffer/text-buffer.d.ts
status-bar/status-bar.d.ts
mixto/mixto.d.ts
node/node.d.ts
$ cat dtsm.json
{
  "repos": [
    {
      "url": "https://github.com/borisyankov/DefinitelyTyped.git",
      "ref": "master"
    }
  ],
  "path": "typings",
  "bundle": "typings/bundle.d.ts",
  "dependencies": {
    "atom/atom.d.ts": {
      "ref": "0d595e843d872c24d8f45dd7df9c15404e6daff4"
    }
  }
}
```

リポジトリ上の構造が`atom/atom.d.ts`のような場合、単に`dtsm search atom`でいいのですが、リポジトリ上で`angularjs/angular.d.ts`のような困ったちゃんは`dtsm install angularjs/angular.d.ts`とかやらないとダメです。
めんどい場合は、[peco](https://github.com/peco/peco)を入れたうえで`dtsm search --raw | peco | dtsm install --stdin`とかやるといいです。

### 設定ファイルからの型定義ファイルのダウンロード

```
$ dtsm install
atom/atom.d.ts
q/Q.d.ts
jquery/jquery.d.ts
space-pen/space-pen.d.ts
emissary/emissary.d.ts
pathwatcher/pathwatcher.d.ts
text-buffer/text-buffer.d.ts
status-bar/status-bar.d.ts
mixto/mixto.d.ts
node/node.d.ts
```

### DefinitelyTyped以外のリポジトリを使う

```
$ cat dtsm
{
  "repos": [
    {
      "url": "https://github.com/borisyankov/DefinitelyTyped.git",
      "ref": "master"
    }
  ],
  "path": "typings",
  "bundle": "typings/bundle.d.ts",
  "dependencies": {
  }
}

$ dtsm --remote https://github.com/vvakame/gapidts.git install \
       test/valid/bigquery-v2-nodejs.d.ts --save
test/valid/bigquery-v2-nodejs.d.ts
test/valid/googleapis-nodejs-common.d.ts
$ cat dtsm.json
{
  "repos": [
    {
      "url": "https://github.com/borisyankov/DefinitelyTyped.git",
      "ref": "master"
    }
  ],
  "path": "typings",
  "bundle": "typings/bundle.d.ts",
  "dependencies": {
    "test/valid/bigquery-v2-nodejs.d.ts": {
      "repo": "https://github.com/vvakame/gapidts.git",
      "ref": "4edbcca555936a931407d667f8687f175ecbd5ed"
    }
  }
}
```

なお、ダウンロード先パスを変更したい場合、キー名をダウンロード先ファイルパスに変更し、実際のリポジトリ上のパスを`"path"`に書くことで構成を変更することができます(手動編集が必要)。

```
$ cat dtsm.json
{
  "repos": [
    {
      "url": "https://github.com/borisyankov/DefinitelyTyped.git",
      "ref": "master"
    }
  ],
  "path": "typings",
  "bundle": "typings/bundle.d.ts",
  "dependencies": {
    "gapi/bigquery-v2-nodejs.d.ts": {
      "repo": "https://github.com/vvakame/gapidts.git",
      "ref": "4edbcca555936a931407d667f8687f175ecbd5ed",
      "path": "test/valid/bigquery-v2-nodejs.d.ts"
    }
  }
}
```

reposを変更するとデフォルト検索先を変更したりすることもできます。
[gistの型定義ファイルを参照する例](https://gist.github.com/vvakame/af214275c55ea5e9067e)

### リモートリポジトリから最新を取得する

最後にfetchした時から15分以上経過している場合、新規にinstallやsearchする前に自動でfetchしようとするので明示的にこの操作をやらなくても大丈夫です。
明示的にfetchを抑制したい時は`--offline`を利用できます。

```
$ dtsm fetch
# git fetch --all 相当の挙動を行う
```

### その他

uninstallとかはまだ実装してないので適当にdtsm.jsonを編集してください。
運用上のコツとして、`.gitignore`で`typings`(型定義ファイルのダウンロード先)は無視しておくことをおすすめします。
[grunt用プラグイン](https://www.npmjs.com/package/grunt-dtsm)をリリースしてあるのでそちらを使うと楽です。

## なぜdtsmを作ろうと思ったのか

型定義ファイルマネージャとして、既にtsd(TypeScript Definition Manager)というデファクトスタンダードっぽいものがあります。
ですが、tsdには以下のような不満があります。

* サブコマンドの設計がnpm, bowerと違いすぎていて覚えるのが大変。
* 1つの設定ファイルにつき1つの型定義ファイル集積リポジトリしか対応していないので複数箇所を参照するのが難しい。
* GitHubに完全に依存していて、社内リポジトリと組み合わせた運用が難しい。
  * ついでに、社内で同時に使いすぎるとGitHubのrate limitにひっかかる場合がある。それを回避するためにAccessTokenを設定するのがめんどくさい。
* 依存するライブラリが多く、npm installでコケることがたびたびありCIで辛かったりする。
* 毎回インターネットアクセスが発生するため、単純な再インストールにも時間がかかる場合が多い。

この辺りを改善したいと思ったんですね。

### なぜ複数リポジトリに対応する必要があるのか

単純に述べると、DefinitelyTyped一極集中型の構造を脱するチャンスを作ろうと思った。

現状の仕組みとして、DefinitelyTypedにファイルの追加・更新がしたい場合、[pull requestを介して送る](http://qiita.com/vvakame/items/1980d4b6cc222e03fdcb)必要があります。
スナック感覚で利用したい場合、この仕組みはなかなかめんどくさい場合があります。
ざっくりと、メンテナがmergeするまでtsdなどから参照できないこと、メンテナがmergeするまで時間がかかること、秘密にしておきたいファイル(社内ライブラリなど)を扱えないこと。があります。
つまり、DefinitelyTyped+tsdの組み合わせは、npmやbowerとは全く違う、どちらかというとHomebrew的な仕組みの基、運用されているわけです。
まとめます。

DefinitelyTypedで運用するメリット

* メンテナがレビューを行ってからmergeするため、ある程度品質が担保される
	* 非互換な変更がホイホイ入らない など
		* 使ってる型定義ファイルのインタフェース名がころころ変わったりしたら困るっしょ
	* 型定義ファイルの品質向上のための改修やアドバイスをメンテナが行う場合があること
* 一極集中するので探しやすい
	* 利用者の目が増えるためバグが修正される可能性がそれなりに高い

DefinitelyTypedで運用するデメリット

* mergeまで時間がかかる
* 下手をすると拒否されて取り込まれない場合がある
* 内緒のファイル(社内ライブラリなど)をホストできない
	* 今後、企業内での利用が増える時にこの辺りのフォローがないのは辛いよね
* 未リリースのTypeScriptコンパイラに対応した機能はまだ取り込まれない
	* 例えばunion typesとか
	* 単に運営コストの問題ではある

そこで、緊急回避のため、もしくは自由なforkのため、内緒を内緒のまま取り扱うため、複数リポジトリに対応する必要があると考えました。

特に、現在DefinitelyTypedのpull requestのレビューを行っているメンバが少ない(ほぼ1名=俺)ため、このままTypeScriptの規模が成長していくとそのうち死ぬな！！という感想があります。
金貰って仕事としてやるとかじゃないと追いつかないよな…！(現にここ4〜5日ほど仕事やアドベントカレンダーに追われて滞っている)

その辺りの負荷軽減のための工夫は思いついているのですがまぁ時間か金がないのが辛いところ(閑話休題

## dtsmの構造

### 設計

dtsmは今のところ、利用先リポジトリがgitリポジトリであることにのみ依存しています。
GitHubであることは仮定していません。

dtsmのためにいくつかの内部ライブラリを作成しているため、それについて簡単に解説を行います。

dtsmの動作として、`~/.dtsm`配下にgitリポジトリをまるまるcloneしてきて、後はgit showコマンドなどを内部的に利用してファイルの取得などを行います。

### fs-git

gitコマンドは、以下のような使い方を許します。

```
$ git ls-tree -r -z --full-name master
# masterブランチ内の全てのファイル一覧を取得
$ git show eff35ada6028795ff39ec6b85c58e9d28980cfdf
# 指定したオブジェクトの内容を表示
$ git rev-parse master
# 指定したブランチをコミットハッシュへ変換
```

これらを組み合わせることで、gitのbare repositoryがあれば任意のコミットのファイル一覧や任意のコミット時点での特定のファイルの内容を簡単に得ることができます。

これをライブラリ化したものが[fs-git](https://www.npmjs.com/package/fs-git)です。
[APIはこんな感じ](https://github.com/vvakame/fs-git/blob/master/fs-git.d.ts)。とりあえずファイル一覧取得と指定したファイルの存在確認と指定したファイルの内容取得とブランチ名などからコミットハッシュへの変換ができればいいや。という作りです。

### packagemanager-backend

fs-gitをバックエンドに、レシピファイルを与えると特定のファイルを拾ってくるようなパッケージマネージャを作るためのバックエンドライブラリです。
dtsmの実装の半分くらいはコイツが受け持っています。

概ね以下のような仕事をする。
.d.tsに特化した実装は持たない(dtsm側が受け持つ)

* gitリポジトリのclone, fetchなどの管理
* gitリポジトリ内の検索の提供
* レシピベースのファイルのピックアップ機能の提供
* 設定ファイルの保存・読み出しの仕組みの提供

[packagemanager-backend](https://www.npmjs.com/package/packagemanager-backend)にある。
[APIはこんな感じ](https://github.com/vvakame/packagemanager-backend/blob/master/packagemanager-backend.d.ts#L24)。

### dtsm

fs-git, packagemanager-backendが行わない全てを受け持つ。

* コマンドラインクライアントの提供
* レシピファイルのフォーマットの定義・保存など
* .d.tsの依存関係の解決

## 困ってること

dtsmの実装上などで困っていることを書いておきます。
何か知見などありましたら是非教えてください。ｵﾅｼｬｽ！

* コンソール出力の --verbose とかの実装方法
	* azuパイセンおすすめのbunyanを使う？
		* 若干用途に対してオーバーキル感がある
		* fs-git, packagemanager-backend, dtsm で個別にbunyan持つのダルい…
			* dtsm配下はdedupe みたいな設定ないのかな
	* yoeman, bower, grunt などを調べてみたがあんまりリッチじゃなかったり実装がライブラリとして切りだされてなかったりする
* pecoのようなインクリメンタルサーチを実装したいけどめんどくさいので何かのライブラリにのっかっていきたい
	* なんかいいものないだろうか
* 最近会社でもGitHubで仕事してるのでGitHub以外をサポートする意欲が…
	* 例えばSamba上のファイルをコピー…とか file:/// くらいはサポートしたい気がしなくもないけど…
* どういう便利機能が欲しいのかよくわからない
	* わかめ的には今必要なだけの機能がある
	* json手編集するのはnpm, bower, tsdで慣れてるしなぁ… 欲しい？

## 今後あるべき姿

こういう機能は欲しいなぁ…と思っているけど実装大変そうだとかそもそもどうやるんだ！とか。

* ダウンロードした型定義ファイルを手編集したものをそのままpull requestに送る機能
	* テストが通ることを確認せずに送って来られても辛いぞ！という話がある
	* spamっぽく送られても困る…！まぁ気軽にnackすればいいだけかもしれない
* git clone --depth xxx を使って初回cloneの時間を短縮したい
	* --depth 50 した後に70コミット前のデータを要求された時のリカバリの仕組みってgit側で持ってるのかなぁ？
* 追加リポジトリに設定ファイルが置いてあったらそれを参照したい
	* プロジェクトルートにいきなり型定義ファイルの設定が置いてあるプロジェクトは案外少ないのでは？ e.g. [gapidts](https://github.com/vvakame/gapidts/tree/master/test/valid) …ぐらい？
* Windowsで動作確認してない
	* VMwareにWindows 8.1とか入ってるけど開発環境とかキーボードとかが慣れなくて…さーぷよ3欲しい
* `dtsm upgrade --save`的なのが欲しい
* node_modules配下を探索したい
	* tsdは既にそういうのを持ってて、package.jsonのtypescriptの項目を読んでtsd.d.tsにまとめる
	* JSのライブラリオーナーが自分で型定義ファイルを作ってメンテしてくれるならそのほうがいいよね
* DefinitelyTyped以外のリポジトリ情報を利用情報から収集してサーバ上に自動アップロードしたい
	* 適当にオプトインの仕組みをつける

---

disclaimer 筆者はDefinitelyTypedのメンバーですが、ここに書いてある事は個人の考えであり他のメンバーがどう思っているかは明らかではありません。
(kazunoriさんがよく末尾になんか書いてるので真似してみたかっただけ説)