AWS CDKをDenoで動かす4 CDK CLIも編


https://qiita.com/rinfield/items/284fae0e1e55b28789e0

の続きでDenoでCDKを動かしていきます

## 要約
- CDK CLIをDenoで動かすのは**ダメでした**。
- 問題を回避しても非対応なNode APIが使われているため現状Denoでは動かなそう
- 以下書いてあるのは試行錯誤のメモです


## CDK CLIもDenoにしたい
現状の構成ではCDKコード(なんと言うのが正しいのか不明)はDenoでしたが、
CDK CLIはNodeで動作しています。
CDK CLIもCDKコードの移行と同じノリで動かせないか試してみます。

## トライアンドエラーの記録

### 素朴なインポート

確認のため単純に https://esm.sh/aws-cdk@2.26.0 をブラウザで開いてみます

```js
/* esm.sh - error */
throw new Error("[esm.sh] " + "Could not resolve \"aws-cdk\"");
export default null;
```

うーんエラーですね。
ライブラリじゃないから`index.js`とかがないからですかね？

### cdkコマンドをたどってみる

では実体の`cdk`コマンドの中身はどうなっているのか。

```js
#!/usr/bin/env node
require('./cdk.js');
```

`cdk.js`をrequireしているだけでした。
では https://esm.sh/aws-cdk@2.26.0/bin/cdk.js ではどうでしょうか

```js
/* esm.sh - error */
throw new Error("[esm.sh] " + "esbuild: Do not know how to load path: esm-build-6d6e3a2749ffb4e9ef8c71f2514eb00cdd2b9cf3-8f306217/node_modules/aws-cdk/bin/cdk");
export default null;
```

内容が変わりましたがエラーのままですね。
`bin/`の下は取れないとかあるのでしょうか。

### 実装をたどる

うーんじゃぁ `aws-cdk/bin/cdk.js`の中身は？

```ts
import { cli } from '../lib';

cli();
```

カンタンな実装で安心しました。
ここでimportしている https://esm.sh/aws-cdk@2.26.0/lib はどうでしょう?

```js
/* esm.sh - aws-cdk@2.26.0/lib */
export * from "https://esm.sh/v85/aws-cdk@2.26.0/es2022/lib.js";
export { default } from "https://esm.sh/v85/aws-cdk@2.26.0/es2022/lib.js";
```

おっ、いけました。じゃぁこれを使うよう作れば行けるかも？

```ts
// cdk.ts (自前)
import lib from "https://esm.sh/aws-cdk@2.26.0/lib";
lib.cli();
```
import以外ほぼ同じコードを書いて実行します。

```console
$ deno run --allow-all cdk.ts
<略>
error: Import 'https://esm.sh/v85/fsevents@2.3.2/deno/fsevents.js' failed: 500 Internal Server Error
    at https://esm.sh/v85/aws-cdk@2.26.0/deno/lib.js:2:1763
```

エラーですね…
`fsevents`ってOptional DependencyでLinuxで必要なくなかったですか？
(試している環境はGitHub CodespacesのDevcontainer上のLinuxです)

Optional Dependencyを除外できるといいんだけどな…
esm.shのIssueに上がっていましたので既知の問題のようです。

https://github.com/esm-dev/esm.sh/issues/157

### fseventsをダマす

通常Linuxでは必要ないんだからimportが満足したらなんとかなるのでは？
うまく騙せる方法はないでしょうか。

調べてみたところDenoには[Import maps](https://deno.land/manual/linking_to_external_code/import_maps)という機能があり、import参照先を差し替えられるようです。
これは使えそうなので試してみます。

まず、差し替え先となる`fsevents-stub.js`を空ファイルで作ります。
```js
// fsevents-stub.js
```

そして、それに差し替えるimport mapファイルを作成します。
(バージョンベタ書きですぐ壊れそう…)

```jsonc
// importmap.json
{
  "imports": {
    "https://esm.sh/v85/fsevents@2.3.2/deno/fsevents.js": "./fsevents-stub.js"
  }
}
```

import mapオプションを渡して実行します。

```console
$ deno run --allow-all --import-map=importmap.json cdk.ts
<略>
Check file:///workspaces/cdk-deno/cdk.ts
error: TS2300 [ERROR]: Duplicate identifier 'URLSearchParams'.
declare class URLSearchParams {
              ~~~~~~~~~~~~~~~
    at asset:///lib.deno.url.d.ts:8:15

    'URLSearchParams' was also declared here.
            interface URLSearchParams extends _URLSearchParams {}
                      ~~~~~~~~~~~~~~~
        at https://cdn.esm.sh/v85/@types/node@16.11.33/url.d.ts:822:19    and here.
            var URLSearchParams:
                ~~~~~~~~~~~~~~~
        at https://cdn.esm.sh/v85/@types/node@16.11.33/url.d.ts:843:13

TS2300 [ERROR]: Duplicate identifier 'URL'.
declare class URL {
              ~~~
    at asset:///lib.deno.url.d.ts:155:15

    'URL' was also declared here.
            interface URL extends _URL {}
                      ~~~
        at https://cdn.esm.sh/v85/@types/node@16.11.33/url.d.ts:823:19    and here.
            var URL:
                ~~~
        at https://cdn.esm.sh/v85/@types/node@16.11.33/url.d.ts:833:13

Found 2 errors.
```

おっ進んだ！！
でもTypeScriptのエラーですね…

そもそも`@types/node@16.11.33`にエラーがあるのかビミョウですが、
これなら型チェックすっ飛ばせばなんとかなるのでは…

そもそもCLIは型チェックしなくていいと思うし。

### 型チェックを省略

TypeScriptの型チェックを省略する`--no-check`オプションをつけて実行してみます。

```console
$ deno run --allow-all --import-map=importmap.json --no-check cdk.ts
error: Uncaught SyntaxError: The requested module '/v85/fsevents@2.3.2/deno/fsevents.js' does not provide an export named 'default'
    at <anonymous> (https://esm.sh/v85/aws-cdk@2.26.0/deno/lib/index.js:2:332)
```

また進んだ！
今度は差し替えたニセモノの`fsevents`の問題のようです。
エラーからするとdefault exportしたなにかしらがあれば通りそう…
さすがに空じゃマズかったか…

### fseventsをダマす その2

ということで修正。

```js
// fsevents-stub.js
export default class A {}
```

そして実行。

```console
$ deno run --allow-all --import-map=importmap.json --no-check cdk.ts
error: Uncaught Error: Not implemented: See https://github.com/denoland/deno_std/issues/1436
  throw new Error(message);
        ^
    at <anonymous> (https://deno.land/std@0.141.0/node/_utils.ts:22:9)
```

おーまた進んだ！
けど今度はdenoが明示的に投げてるエラーです…

### DenoのNode互換はまだまだ

エラーメッセージで出てきたIssueは[nodeのos API](https://nodejs.org/dist/latest-v18.x/docs/api/os.html)がまだ実装されていないものがある、というものでした。

https://github.com/denoland/deno_std/issues/1436

CDK CLIはDenoがサポートしていないNode APIを呼び出してしまったのでしょう。

コールスタックが出ていないのでどこでなんのAPIの呼び出しでエラーになったのかわからないですね…
AWSのクレデンシャル取るためのホームディレクトリの取得あたりが怪しいか？

まぁ今回はここらで諦めます…


## 感想
今までさっくり動いて来たけど、流石にそうカンタンに全部は動きませんよね。
むしろこうでなくっちゃ(？)

メモリ消費量がハンパなく試してる途中もOOM Killerに(？)Denoが殺されたり
動いたとしてもいいことがほぼなさそうです。
(aws-sdkとかの依存が重すぎる？)

当面はnodeをインストールするくらいの手間は受け入れましょう…

Nodeの互換性が改善された日にはまた試してみます。