タイムリーに is-promise 2.2.0 破損に巻き込まれた話

Node.jsの環境をSSDに引越しして動作を確認していたところ、`yo`がインストールできない・動かないという謎の現象に見舞われた。実は自分の環境が悪いのではなく、タイムリーに`yo`が依存するたった1行の関数を提供する`is-promise`が壊れた直後にインストールしたことが原因と分かった。

別のモジュールではNode.jsのバージョンが上がって動かなくなるケースでは対応が遅れることがあったので、今回はすぐ処置されてラッキーだった。(知らないだけで日常的に起きているのかもしれないが)

# 起きた現象
`yo`をインストールすると、ちゃんと動くかをチェックするYeoman Doctorが実行される。ここで、`yo --version`でエラーが起きてしまっている。この後`yo`を実行しても、同じエラーで起動すらしない状況となった。

```
> yo@3.1.1 postinstall K:\nodejs\npm_global\node_modules\yo
> yodoctor


Yeoman Doctor
Running sanity checks on your system

√ No .bowerrc file in home directory
√ Global configuration file is valid
√ NODE_PATH matches the npm root
√ No .yo-rc.json file in home directory
√ Node.js version
Error: Command failed: C:\WINDOWS\system32\cmd.exe /s /c "yo "--version""
internal/modules/cjs/loader.js:584
            if (e.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') throw e;
                                                            ^

Error [ERR_INVALID_PACKAGE_TARGET]: Invalid "exports" main target "index.js" defined in the package config /K:/nodejs/npm_global/node_modules/yo/node_modules/is-promise\package.json
    at resolveExportsTarget (internal/modules/cjs/loader.js:542:13)
    at resolveExportsTarget (internal/modules/cjs/loader.js:581:20)
    at applyExports (internal/modules/cjs/loader.js:455:14)
    at resolveExports (internal/modules/cjs/loader.js:508:23)
    at Function.Module._findPath (internal/modules/cjs/loader.js:632:31)
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:1001:27)
    at Function.Module._load (internal/modules/cjs/loader.js:884:27)
    at Module.require (internal/modules/cjs/loader.js:1074:19)
    at require (internal/modules/cjs/helpers.js:72:18)
    at Object.<anonymous> (K:\nodejs\npm_global\node_modules\yo\node_modules\run-async\index.js:3:17) {
  code: 'ERR_INVALID_PACKAGE_TARGET'
}
```

#エラーの原因
エラーメッセージにある通り、`run-async`が利用する`is-promise`で、`ERR_INVALID_PACKAGE_TARGET`が起きてしまっているのが原因。[npmのis-promiseのページ](https://www.npmjs.com/package/is-promise)を見てみると、以下のようにビルドエラーが起きたと表示されている。30分ほど前にPublishされたバージョンで何が起きたのか…
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296170/d3aa8d0e-9799-67a1-2fb7-5c0412533f78.png)

`is-promise`のGitHubにはIssueが既に投稿されていて、同じエラーが起きていることが分かった。(ここで初めて、自分の環境が問題ではないかも…と分かった)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296170/6d48279b-c2d8-9455-84e8-d04075eea76b.png)

原因は[Package.json incorrectly formatted #12](https://github.com/then/is-promise/issues/12)に投稿されていた(ejs/cjsといった、Javascriptのモジュールの形式の指定が間違っていたとのこと…あまり理解できていないが、ejsをサポートするNode.jsのバージョンでNGになるそう)。今回のバージョンで追加された機能が悪さをしてしまった模様。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296170/16022dd0-70af-50ea-4aab-3fdf06f6bf5b.png)

その後、[[BUGFIX] Use correct paths for CJS and ESM compatibility #15](https://github.com/then/is-promise/pull/15)で修正があり、プルリクエストが発行されていた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296170/17fa9689-d5a0-c68a-ebca-79c52ddd2633.png)

とりあえず、少し待てば反映されるかもしれないので待ってみる。Weekly download 1000万超の定番モジュールがほんの少しのミスで色々なモジュールに影響を与えてしまうとは…

# 暫定対策
とりあえず問題が起きているのは`yo`のグローバルインストールが使っている`run-async`なので、`yo`のnode_modules以下の`run-async`の`package.json`を変更して、`is-promise`のバージョンを2.1.0に固定する。`package.json`を変更したら`run-async`のフォルダで`npm install`を実行し、`is-promise`の2.1.0をインストールする。

```json:package.json
  "dependencies": {
    "is-promise": "2.1.0" ← 元々は "^2.1.0" だった
  },
```

この処置の後、`yo`を実行したら動くようになった。

```
>yo
? 'Allo! What would you like to do? (Use arrow keys)
  Run a generator
> Code
  ──────────────
  Update your generators
  Install a generator
  Find some help
  Get me out of here!
(Move up and down to reveal more choices)
```

# その後
2.2.0から3時間後に2.2.2がリリースされた。もう一度`npm install -g yo`してみたところ、正常にインストールできた。早く対応してくれて良かった…

```
Yeoman Doctor
Running sanity checks on your system

√ No .bowerrc file in home directory
√ Global configuration file is valid
√ NODE_PATH matches the npm root
√ No .yo-rc.json file in home directory
√ Node.js version
√ npm version
√ yo version

Everything looks all right!
```