Repository: typescript-eslint/tslint-to-eslint-config (ts)

Issue:
Fails to read ESLint settings if packages haven't been installed
### 🐛 Bug Report

-   `tslint-to-eslint-config` version: 0.6.0
-   ESLint version: N/A
-   Node version: N/A

#### Actual Behavior

Logging the `eslint` loaded in `findOriginalConfigurations` gives:

```
 Error: Command failed: eslint --print-config "./.eslintrc.js"
  Error: Failed to load parser '@typescript-eslint/parser' declared in '.eslintrc.js': Cannot find module '@typescript-eslint/parser'
  Require stack:
  - C:\Code\sample-tslint-to-eslint-comment\.eslintrc.js
      at Function.Module._resolveFilename (internal/modules/cjs/loader.js:610:15)
      at Function.resolve (internal/modules/cjs/helpers.js:21:19)
      at Object.resolve (C:\Users\Dioge\AppData\Roaming\npm\node_modules\eslint\lib\shared\relative-module-resolver.js:44:50)
      at ConfigArrayFactory._loadParser (C:\Users\Dioge\AppData\Roaming\npm\node_modules\eslint\lib\cli-engine\config-array-factory.js:870:45)
      at ConfigArrayFactory._normalizeObjectConfigDataBody (C:\Users\Dioge\AppData\Roaming\npm\node_modules\eslint\lib\cli-engine\config-array-factory.js:665:32)
      at _normalizeObjectConfigDataBody.next (<anonymous>)
      at ConfigArrayFactory._normalizeObjectConfigData (C:\Users\Dioge\AppData\Roaming\npm\node_modules\eslint\lib\cli-engine\config-array-factory.js:596:20)
      at _normalizeObjectConfigData.next (<anonymous>)
      at createConfigArray (C:\Users\Dioge\AppData\Roaming\npm\node_modules\eslint\lib\cli-engine\config-array-factory.js:340:25)
      at ConfigArrayFactory.loadInDirectory (C:\Users\Dioge\AppData\Roaming\npm\node_modules\eslint\lib\cli-engine\config-array-factory.js:433:16)
```

#### Expected Behavior

If there already exists an ESLint file, its packages may be required to determine which output rules are necessary (for example, in #412). 

#### Reproduction

The second time `tslint-to-eslint-config` is run, it'll try to read the already-generated `.eslintrc.js`, and fail.

```
git clone https://github.com/KingDarBoja/sample-tslint-to-eslint-comment
cd sample-tslint-to-eslint-comment
tslint-to-eslint-config
tslint-to-eslint-config 
```

Pull request:
Intentionally fail fast on module lookup failures
## PR Checklist

-   [x] Addresses an existing issue: fixes #414
-   [x] That issue was marked as [`status: accepting prs`](https://github.com/typescript-eslint/tslint-to-eslint-config/labels/status%3A%20accepting%20prs)

## Overview

Per the issue, if a configuration is failed to be read from because package fails to resolve, `tslint-to-eslint-config` will request it be installed instead of ignoring the package.

Patch:
diff --git a/src/input/findOriginalConfigurations.ts b/src/input/findOriginalConfigurations.ts
index b112aae..872709f 100644
--- a/src/input/findOriginalConfigurations.ts
+++ b/src/input/findOriginalConfigurations.ts
@@ -1,5 +1,6 @@
 import { SansDependencies } from "../binding";
 import { ResultStatus, TSLintToESLintSettings, ResultWithDataStatus } from "../types";
+import { isDefined } from "../utils";
 import { findESLintConfiguration, ESLintConfiguration } from "./findESLintConfiguration";
 import { PackagesConfiguration, findPackagesConfiguration } from "./findPackagesConfiguration";
 import {
@@ -54,20 +55,42 @@ export const findOriginalConfigurations = async (
     // Out of those configurations, only TSLint's is always required to run
     if (tslint instanceof Error) {
         return {
-            complaints: [tslint.message],
+            complaints: [getMissingPackageMessage(tslint) ?? tslint.message],
             status: ResultStatus.ConfigurationError,
         };
     }
 
-    // Other configuration errors only halt the program if the user asked for them
-    const configurationErrors = [
-        [eslint, rawSettings.eslint],
-        [packages, rawSettings.package],
-        [typescript, rawSettings.typescript],
-    ].filter(configurationResultIsError);
-    if (configurationErrors.length !== 0) {
+    const configurationResults = [
+        [eslint, "eslint"],
+        [packages, "package"],
+        [typescript, "typescript"],
+    ] as const;
+
+    // Other configuration errors only halt the program if...
+    const errorMessages = configurationResults
+        .map(([error, key]) => {
+            if (!(error instanceof Error)) {
+                return undefined;
+            }
+
+            // * Their failure was caused by a missing package that needs to be installed
+            const missingPackageMessage = getMissingPackageMessage(error);
+            if (missingPackageMessage !== undefined) {
+                return missingPackageMessage;
+            }
+
+            // * The user explicitly asked for them
+            if (typeof rawSettings[key] === "string") {
+                return error.message;
+            }
+
+            return undefined;
+        })
+        .filter(isDefined);
+
+    if (errorMessages.length !== 0) {
         return {
-            complaints: configurationErrors.map(([configuration]) => configuration.message),
+            complaints: errorMessages,
             status: ResultStatus.ConfigurationError,
         };
     }
@@ -83,6 +106,13 @@ export const findOriginalConfigurations = async (
     };
 };
 
-const configurationResultIsError = (result: unknown[]): result is [Error, string] => {
-    return result[0] instanceof Error && typeof result[1] === "string";
+const getMissingPackageMessage = (error: Error) => {
+    const match = /(Cannot find module|could not require|couldn't find the plugin) ([a-zA-Z0-9-_"'@/]+)/.exec(
+        error.message,
+    );
+    if (match === null) {
+        return undefined;
+    }
+
+    return `Could not import the ${match[2]} module. Do you need to install packages?`;
 };


Test patch:
diff --git a/src/input/findOriginalConfigurations.test.ts b/src/input/findOriginalConfigurations.test.ts
index 7c47671..f509636 100644
--- a/src/input/findOriginalConfigurations.test.ts
+++ b/src/input/findOriginalConfigurations.test.ts
@@ -47,7 +47,7 @@ const createStubDependencies = (
 });
 
 describe("findOriginalConfigurations", () => {
-    it("returns errors when the tslint finder returns an error", async () => {
+    it("returns the error when the tslint finder returns an error", async () => {
         // Arrange
         const complaint = "Complaint from TSLint";
         const dependencies = createStubDependencies({
@@ -64,6 +64,23 @@ describe("findOriginalConfigurations", () => {
         });
     });
 
+    it("returns a package install error when the tslint finder returns a package install error", async () => {
+        // Arrange
+        const complaint = "could not require 'tslint'";
+        const dependencies = createStubDependencies({
+            findTSLintConfiguration: async () => new Error(complaint),
+        });
+
+        // Act
+        const result = await findOriginalConfigurations(dependencies, createRawSettings());
+
+        // Assert
+        expect(result).toEqual({
+            complaints: ["Could not import the 'tslint' module. Do you need to install packages?"],
+            status: ResultStatus.ConfigurationError,
+        });
+    });
+
     it("returns only tslint results when the other finders return errors", async () => {
         // Arrange
         const dependencies = createStubDependencies({
@@ -90,6 +107,33 @@ describe("findOriginalConfigurations", () => {
         });
     });
 
+    it.each(["Cannot find module", "could not require", "couldn't find the plugin"])(
+        "returns an error when an optional configuration returns a '%s' error",
+        async (message) => {
+            // Arrange
+            const eslint = new Error(`${message} "example"`);
+            const dependencies = createStubDependencies({
+                findESLintConfiguration: async () => eslint,
+            });
+
+            // Act
+            const result = await findOriginalConfigurations(
+                dependencies,
+                createRawSettings({
+                    eslint: "./eslintrc.js",
+                }),
+            );
+
+            // Assert
+            expect(result).toEqual({
+                complaints: [
+                    `Could not import the "example" module. Do you need to install packages?`,
+                ],
+                status: ResultStatus.ConfigurationError,
+            });
+        },
+    );
+
     it("returns an error when an optional configuration returns an error and the user asked for it", async () => {
         // Arrange
         const eslint = new Error("one");