🤖

Denoでversion manager cretを作ってみた話

2021/05/02に公開

新しくDenoのversion managerを作りました

https://github.com/lion-man44/cret

version managerというと他の言語にはいろいろあったりします(特にnode.jsの世界においては顕著ですね)

Denoにもご多分に洩れずdvmというのがあります

始めは自分もそれを使おうとしていたのですが、最初の使用感としての何かが自分を困惑させました(今ではもうすっかり忘れてしまいましたが 😂)

なのでDenoの勉強のためにも(ほとんどはTypeScriptの再学習になりましたが)作ってみようと思いました

今回の記事は「どういう事があったかなどの開発編」と「このpackageの機能編」の2部構成で行こうと思います

開発編

Denoに関してはこれを探してきた時点で特に説明は必要ないと思うので省きます

さて、Deno自体はとても便利で軽量ですし、デフォルトでtscを内蔵しているので(今後swcに移行していくらしいですが)node.jsのようにボイラーテンプレートから用意しないといけない、ということは特にないです

実際にshell自体のコマンドを使いたい場合は Deno.run というコマンドを使います

Deno.run({
  cmd: ["ls", "-l", "."]
})

最も簡単なパターンです

しかし大抵何かの返り値が欲しい場合が多いのでこうして使うケースはそうそう無いです

const p: Deno.Process = Deno.run({
  cmd: ["ls", "-l", "."]
})

さて、こうする事で受け取れるかというと残念ながら違います

これを実行すると分かると思うのですが、 ls -l . の実行結果がstdoutから流れてしまいます

では、受け取るためにはどうするかと言うと cmd のpropertyの他に stdout, stderr, stdin があります

// こうすることによって、実行されず一旦実行計画だけが p の中に保存されている状態になります
const p: Deno.Process = Deno.run({
  cmd: ["ls", "-l", "."],
  stdout: "piped",
})

/**
 * それからoutputすることによって、返り値をUint8Arrayで得ることができます
 */
p.output();
// =>
// Promise {
//   Uint8Array(1024) [
//     116, 111, ...
//   ]
// }

始めこれが全てこのメソッドで完結していけばいいのかと勘違いしていました

まぁそんなわけないですよね、というのをリファレンス見ていたら知ったんですが、この手のを探す時は基本的にその名称の英単語の類義語などを知っておく必要があるので(例えば symbolic linksymlink, deleteremove など)(他の人はどういう風に探しているんですかね?)知っていれば知っているほど探しやすいってやつですね

完全にこの時間ロスがあってしまったので簡易な仕様書を書くべきだと判断したのでsubcommandを下記の5つに制定しました

- install
  - Github API download(Deno.fetch)
  - mkdir(Deno.mkdirSync)
  - unzip(Deno.run)
  - rm(Deno.removeSync)
- uninstall
  - ls(Deno.readDirSync)
  - rm(Deno.removeSync)
- use
  - ln(Deno.symlinkSync)
- ls
  - readlink(Deno.realPathSync)
  - ls(Deno.readDirSync)
- ls-remote
  - Github API download(Deno.fetch)

もちろんこの通りにできてる部分もありますが、その大部分は余計な処理は入ってるのでこの通りではないです(ユーザからのinputを受け取りますしね)

他にちょっとしたハマりどころは removeSync, lstat です

removeSync でdirectoryを削除しようとした時、 Deno.removeSync(dirPath, { recursive: true }) が必要です

シンボリックリンクの情報を得ようとした時に Deno.stat に最初たどり着くかもしれませんが、こちらはfile system上で必ず isSymlink: false となりますが仕様です

Deno.lstat を活用します

しかし、例えばfilepathを取得したい場合、どちらも使えません

lstatFileInfo を返してくれるのですが、残念ながらfilepathのpropertyは削除されてしまいました

なのでfilepathを得たい場合は Deno.realPathSync を使う必要があります

それとは別に悩んでしまったこととして *Sync 系のmethodをTS上だけで自作できないか、どうかというところを捻っていたのですが(どうしてもこちらで async で包む必要があるのでシンプルでなくなってしまう)作ることができなくて断念しました

Deno 自体で苦戦することは昨今のTypeScript事情などを鑑みるに詰まるところは特にあるという感じはないですね、むしろTypeScriptをそんなに書いたことない人の方がハマる可能性はありますね(例えば default parameter and optional parameter with object destructuring のような)

あとはcli toolなどを作ろうとした時にshellのcommandの類などを知っているのと知らないのでは詰まるところが違う、といった感じです(optionで指定できることをdeno上からはどうするか、など)

Deno以外で詰まってしまった部分として gh release commandや brew create でbinaryを配布する時に使う bottle などはどういう風に使うかなどがよく分からなかった部分でした

機能編

このpackageですが、 brew でinstallできるようにしてあります(windowsも配布しているのですがテストはしてません、すみません)

brew install lion-man44/cret/cret

正直自分自身がversion managerに期待することは多くないのでOSSとしても作ろうと思った経緯があります

大抵この手のツールはshell scriptで作られていることが多かったりするのですが、正直それだとテストされてない部分もあったりするのでそこが自分の中でネックでした(shell scriptが便利とは言え、やはりあの構文に対するモチベはそこまで高いものでもないので)

Deno にはデフォルトで test commandがあるので テストを書いてみたかったというのもあります(デフォルトで書いていくのはしんどいのでassertion系やtest系のツール入れていかないとcallback地獄にすぐ陥ります)

subcommandは5つです

  • install
  • uninstall
  • use
  • ls
  • ls-remote

簡単な作りにしてあるので誰でも読めますし、誰でも使えると思います

cret install を実行するとversionが聞かれるので入力してください

installが終わったら export PATH=$HOME/.cret/bin:$PATH.bash_profile, .zprofile などに追加していただければPATHが通るようになります

まとめ

最後に、Denoを今回初めて触ってみていろいろとJS界隈でもまた時代の流れがありそうでワクワクしています

今回作ったのはToyみたいなものですが、もし使ってくだされば嬉しいです(あとdebugしてくれてもいいんじゃよ?🤗)

Discussion