`Deno.run`は非推奨になるので代替手段（`Deno.Command`、dax）

## `Deno.run`が非推奨になった

Deno v1.31以降、`Deno.run`に代わる新しいAPI、`Deno.Command`が登場しています。
今後は`Deno.Command`を使うのが推奨されると共に、`Deno.run`は非推奨化＆削除予定です。

https://deno.com/blog/v1.31#api-stabilizations

#### 時系列

|||
|---|---|
2018年11月|`Deno.run()`が導入される [#1156](https://github.com/denoland/deno/pull/1156)
2022年4月|`Deno.run()`の代替として`Deno.spawn()`と`Deno.spawnChild()`が導入される（実験的API扱い） [#11618](https://github.com/denoland/deno/pull/11618)
2022年11月|`Deno.spawn()`と`Deno.spawnChild()`の代替として、`Deno.Command()`が導入される（実験的API扱い） [#16516](https://github.com/denoland/deno/pull/16516)
2022年12月|`Deno.spawn()`と`Deno.spawnChild()`が削除される [#16893](https://github.com/denoland/deno/pull/16893)
2023年2月|`Deno.Command()`が安定化する [#17628](https://github.com/denoland/deno/pull/17628)
現在|`Deno.run()`は非推奨化予定、`Deno.Command()`の使用を推奨

…と若干迷走している感じもあったサブプロセスAPIの改修ですが、最終的に導入された`Deno.Command()`はRustライクなAPIになっており、これで確定する可能性が高いです。

https://twitter.com/deno_land/status/1623344605524631553

なお、ここまでの経緯はDenoコアチームのkt3kさんの記事が詳しいです。

https://qiita.com/kt3k/items/4ac4b6f21895041927aa

非推奨化される`Deno.run`の代替手段としては、主に2つの方法がありますので、紹介したいと思います。

## 方法1：`Deno.Command` APIを使う

上で紹介しているように、`Deno.run`の後継は`Deno.Command` APIになります。
試しに`Deno.Command`を使ってサブプロセスで`deno eval`コマンドを起動してみたいと思います。

```ts
const decoder = new TextDecoder();

// 第1引数にはコマンド名を文字列で指定
const command = new Deno.Command(Deno.execPath(), {
  args: ["eval", "console.log('hello'); console.error('hello from stderr');"],
  /* 子プロセスを動かすカレントディレクトリを指定 */
    // cwd: "path/to/cwd",
  /* stdin / stdout / stderrをどこから渡すかを制御する */
    // stdin: "inherit",
  /* stdout: "inherit",
    // stderr: "inherit",
  /* 環境変数を渡す */
    // env: { MY_ENV: "hello" },
  /* trueにすると、子プロセスから親プロセスの環境変数を読み取れない（デフォルトはfalse） */
    // clearEnv: true,
  /* AbordContorollerやAbortSignalを使用して途中でサブプロセスを中断できる */
    // signal: AbortSignal.timeout(5000),
  /* Windowsでの引用符のエスケープなどの挙動を制御する */
    // windowsRawArguments: true,
});
const { code, success, signal, stdout, stderr } = await command.output();
console.log(code); // => 0
console.log(success); // => true
console.log(signal); // => null
console.log(decoder.decode(stdout)); // => hello
console.log(decoder.decode(stderr)); // => hello from stderr
```

無事コマンドを実行してstdout(標準出力)とstderr(標準エラー出力)を受け取ることができました。

:::note
stdin / stdout / stderrについては、それぞれに対して

- `"inherit"`：親プロセスから継承する
  - 親プロセスのstdinは直接子プロセスのstdinへ、子プロセスのstdoutは親プロセスのstdoutへ、stderrはstderrへ流れる。
- `"piped"`：プログラム側で制御する
  - stdinやstdoutの入出力をプログラム側から操作するときに指定する
- `"null"`： 何もしない

というオプションを渡すことができます。

デフォルトではstdinが`"inherit"`、stdoutとstderrが`"piped"`になっています。
ただし後述のストリーミング処理を行う場合、デフォルト値は全て`"inherit"`になります。
:::

上記の例は単純にコマンドを実行して結果を受け取るだけでした。
`command.spawn()`を呼ぶことで、コマンドの入出力をストリーミングすることもできます。

```ts
const command = new Deno.Command(Deno.execPath(), {
  args: ["eval", "console.log('hello'); console.error('hello from stderr');"],
  // stdin: "piped",
  stdout: "piped",
  stderr: "piped",
});

// spawn()を呼ぶとストリーミング開始
const process = command.spawn();

// process.stdout や process.stderr は ReadableStream なので、ファイルにパイプしたりResponseに渡したりすることができる
process.stdout.pipeTo((await Deno.open("./tmp.txt")).writable);
new Response(process.stderr);

// プロセス終了時のステータスなどを取得
const { code, success, signal } = await process.status;
console.log(code); // => 0
console.log(success); // => true
console.log(signal); // => null
```

ストリーミング処理は[Web Stream API](https://developer.mozilla.org/ja/docs/Web/API/Streams_API)を使って行うようになっています。DenoのIO系は全てWeb Stream APIに統一されているため、そのままファイルに書き込んだり、HTTPレスポンスに流したりといった事が可能です。

## 方法2：daxを使う

[dax](https://deno.land/x/dax)はシェルコマンド用のユーティリティライブラリです。
`Deno.Command`の代わりにサブプロセスを起動するために使うことができます。
作者はDenoコアチームのdavidさんです。

https://deno.land/x/dax

まずは基本的な使い方です。`Deno.Command`の例と比べると大分コード量が減っている感じがします。

```ts:dax使用例
import $ from "https://deno.land/x/dax@0.28.0/mod.ts";

const result = await $`deno eval 'console.log(1); console.error(2);'`;
console.log(result.code);
```

stdoutとstderrをプログラムで受け取るには、以下のようにします。

```ts:stdoutとstderrの受け取り方
import $ from "https://deno.land/x/dax@0.28.0/mod.ts";

const result = await $`deno eval 'console.log(1); console.error(2);'`
  .stdout("piped")
  .stderr("piped");

console.log(result.stdout); // 1\n
console.log(result.stderr); // 2\n
```

以上を見て分かる通り、実体としては上で紹介した`Deno.Command`のラッパーですが、それに加えて**クロスプラットフォーム対応**も入っています。
通常、`echo`などのコマンドを使ってしまうとWindowsで`command not found`と怒られたりしますが、daxを使うと**それを回避することができます**。

```ts
import $ from "https://deno.land/x/dax@0.28.0/mod.ts";

// これはlinuxでもMac OSでもWindowsでも動く！
await $`echo 5`;
```

現在は`cd` / `echo` / `exit` / `cp` / `mv` / `rm` / `mkdir` / `pwd` / `sleep` / `test` / `touch`が[クロスプラットフォーム対応されています](https://deno.land/x/dax@0.28.0#custom-cross-platform-shell-commands)。

:::note
なぜこれらが動くのかというと、Denoのタスクランナーの仕組み（こちらもクロスプラットフォーム）をそのまま持ってきて動かしてるからです。
:::

なお、daxにはその他のユーティリティも付属しています。

```ts
// ディレクトリ移動
$.cd("path/to/dir");

// nミリ秒待機
await $.sleep(100);

// コマンドの絶対パスを見つける
console.log(await $.which("deno"));

// HTTPリクエストを送る
await $.request("https://example.com").json();
```

他のユーティリティも付属しているので、ぜひ[公式ドキュメント](https://deno.land/x/dax@0.28.0)をご覧ください。

## まとめ

- `Deno.run()`は非推奨になる予定
  - 長い間使用されていたこともあり移行期間がとられる予定のよう
- 代わりに`Deno.Command` APIの使用が推奨されている
- [dax](https://deno.land/x/dax)を使うと便利
  - クロスプラットフォーム対応なので、CIなど別環境で動かす用のスクリプトで便利そう

daxは手元でちょっと動かすやつとかCI用のスクリプトを動かすのにかなり便利そうなので使っていきたいと思います。

ちなみにdaxについてDenoコアチームのhashrockさんが解説した記事もあるので貼っておきます。

https://zenn.dev/hashrock/articles/5dae2e171533a6