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

Issue:
Apply node presets per @types
Generates ESLint configs should have environment settings set up per `tsconfig.json` settings. 
Apply env/browser presets per tsconfig
ESLint settings such as `browser: true` can be determined from `tsconfig.json` if they don't already exist.

Pull request:
Applied env and node presets per existing configurations
Adds a package configuration lookup in addition to ESLint, TSLint, and TypeScript, and has them all fill in minimal defaults.

Fixes #33. Fixes #35.

Patch:
diff --git a/src/cli/main.ts b/src/cli/main.ts
index 30e91ab..15ae45b 100644
--- a/src/cli/main.ts
+++ b/src/cli/main.ts
@@ -24,6 +24,7 @@ import {
 import { converters } from "../rules/converters";
 import { convertRules } from "../rules/convertRules";
 import { mergers } from "../rules/mergers";
+import { findPackagesConfiguration } from "../input/findPackagesConfiguration";
 
 const convertRulesDependencies = {
     converters,
@@ -36,6 +37,7 @@ const findConfigurationDependencies = {
 
 const findOriginalConfigurationsDependencies: FindOriginalConfigurationsDependencies = {
     findESLintConfiguration: bind(findESLintConfiguration, findConfigurationDependencies),
+    findPackagesConfiguration: bind(findPackagesConfiguration, findConfigurationDependencies),
     findTypeScriptConfiguration: bind(findTypeScriptConfiguration, findConfigurationDependencies),
     findTSLintConfiguration: bind(findTSLintConfiguration, findConfigurationDependencies),
 };
diff --git a/src/conversion/conversionResults.stubs.ts b/src/conversion/conversionResults.stubs.ts
index 17fc5b3..0acb2d7 100644
--- a/src/conversion/conversionResults.stubs.ts
+++ b/src/conversion/conversionResults.stubs.ts
@@ -1,8 +1,8 @@
-import { ConfigConversionResults } from "../rules/convertRules";
+import { RuleConversionResults } from "../rules/convertRules";
 
 export const createEmptyConversionResults = (
-    overrides: Partial<ConfigConversionResults>,
-): ConfigConversionResults => ({
+    overrides: Partial<RuleConversionResults>,
+): RuleConversionResults => ({
     converted: new Map(),
     failed: [],
     missing: [],
diff --git a/src/conversion/convertConfig.ts b/src/conversion/convertConfig.ts
index 7933872..fc4046a 100644
--- a/src/conversion/convertConfig.ts
+++ b/src/conversion/convertConfig.ts
@@ -21,15 +21,12 @@ export const convertConfig = async (
         return originalConfigurations;
     }
 
-    const configConversonResults = dependencies.convertRules(
+    const ruleConversionResults = dependencies.convertRules(
         originalConfigurations.data.tslint.rules,
     );
 
-    await dependencies.writeConversionResults(
-        configConversonResults,
-        originalConfigurations.data.tslint,
-    );
-    dependencies.reportConversionResults(configConversonResults);
+    await dependencies.writeConversionResults(ruleConversionResults, originalConfigurations.data);
+    dependencies.reportConversionResults(ruleConversionResults);
 
     return {
         status: ResultStatus.Succeeded,
diff --git a/src/creation/eslint/createEnv.ts b/src/creation/eslint/createEnv.ts
new file mode 100644
index 0000000..4022ad4
--- /dev/null
+++ b/src/creation/eslint/createEnv.ts
@@ -0,0 +1,27 @@
+import { OriginalConfigurations } from "../../input/findOriginalConfigurations";
+
+export const createEnv = ({
+    packages,
+    typescript,
+}: Pick<OriginalConfigurations, "packages" | "typescript">) => {
+    const browser =
+        typescript === undefined ||
+        typescript.compilerOptions.lib === undefined ||
+        typescript.compilerOptions.lib.includes("dom");
+
+    const es6 =
+        typescript === undefined ||
+        !["es3", "es5"].includes(typescript.compilerOptions.target.toLowerCase());
+
+    const node =
+        packages === undefined ||
+        [...Object.keys(packages.dependencies), ...Object.keys(packages.devDependencies)].some(
+            dependency => dependency.toLowerCase() === "@types/node",
+        );
+
+    return {
+        ...(browser && { browser }),
+        ...(es6 && { es6 }),
+        ...(node && { node }),
+    };
+};
diff --git a/src/creation/formatConvertedRules.ts b/src/creation/formatConvertedRules.ts
index 8712b21..8836410 100644
--- a/src/creation/formatConvertedRules.ts
+++ b/src/creation/formatConvertedRules.ts
@@ -1,10 +1,10 @@
 import { TSLintConfiguration } from "../input/findTSLintConfiguration";
-import { ConfigConversionResults } from "../rules/convertRules";
+import { RuleConversionResults } from "../rules/convertRules";
 import { ESLintRuleOptions } from "../rules/types";
 import { formatMissingRules } from "./formatMissingRules";
 
 export const formatConvertedRules = (
-    conversionResults: ConfigConversionResults,
+    conversionResults: RuleConversionResults,
     tslintConfiguration: TSLintConfiguration,
 ) => {
     const output: { [i: string]: string | any[] } = {};
diff --git a/src/creation/writeConversionResults.ts b/src/creation/writeConversionResults.ts
index 451f57f..378b680 100644
--- a/src/creation/writeConversionResults.ts
+++ b/src/creation/writeConversionResults.ts
@@ -1,7 +1,8 @@
 import { FileSystem } from "../adapters/fileSystem";
-import { TSLintConfiguration } from "../input/findTSLintConfiguration";
-import { ConfigConversionResults } from "../rules/convertRules";
+import { RuleConversionResults } from "../rules/convertRules";
 import { formatConvertedRules } from "./formatConvertedRules";
+import { OriginalConfigurations } from "../input/findOriginalConfigurations";
+import { createEnv } from "./eslint/createEnv";
 
 export type WriteConversionResultsDependencies = {
     fileSystem: Pick<FileSystem, "writeFile">;
@@ -9,18 +10,19 @@ export type WriteConversionResultsDependencies = {
 
 export const writeConversionResults = async (
     dependencies: WriteConversionResultsDependencies,
-    conversionResults: ConfigConversionResults,
-    tslintConfiguration: TSLintConfiguration,
+    ruleConversionResults: RuleConversionResults,
+    originalConfigurations: OriginalConfigurations,
 ) => {
     const output = {
+        env: createEnv(originalConfigurations),
         parser: "@typescript-eslint/parser",
         parserOptions: {
             project: "tsconfig.json",
         },
-        ...(conversionResults.missing.length && {
+        ...(ruleConversionResults.missing.length && {
             plugins: ["@typescript-eslint/tslint"],
         }),
-        rules: formatConvertedRules(conversionResults, tslintConfiguration),
+        rules: formatConvertedRules(ruleConversionResults, originalConfigurations.tslint),
     };
 
     await dependencies.fileSystem.writeFile(".eslintrc.json", JSON.stringify(output, undefined, 4));
diff --git a/src/input/findLintConfiguration.ts b/src/input/findConfiguration.ts
similarity index 66%
rename from src/input/findLintConfiguration.ts
rename to src/input/findConfiguration.ts
index d382cfc..61260dd 100644
--- a/src/input/findLintConfiguration.ts
+++ b/src/input/findConfiguration.ts
@@ -1,22 +1,27 @@
 import { Exec } from "../adapters/exec";
 
-export const findLintConfiguration = async <Configuration>(
+export type DeepPartial<T> = {
+    [P in keyof T]: T[P] extends {} ? DeepPartial<T[P]> : T[P];
+};
+
+export type FindConfigurationDependencies = {
+    exec: Exec;
+};
+
+export const findConfiguration = async <Configuration>(
     exec: Exec,
     command: string,
     config: string,
-    defaultValues: Configuration,
-): Promise<Configuration | Error> => {
+): Promise<DeepPartial<Configuration> | Error> => {
     const fullCommand = `${command} ${config}`;
     const stdout = await execAndCatch(exec, fullCommand);
+
     if (stdout instanceof Error) {
         return stdout;
     }
 
     try {
-        return {
-            ...defaultValues,
-            ...(JSON.parse(stdout) as Partial<Configuration>),
-        };
+        return JSON.parse(stdout) as DeepPartial<Configuration>;
     } catch (error) {
         return new Error(`Error parsing configuration: ${error}`);
     }
diff --git a/src/input/findESLintConfiguration.ts b/src/input/findESLintConfiguration.ts
index e544cfa..179c82c 100644
--- a/src/input/findESLintConfiguration.ts
+++ b/src/input/findESLintConfiguration.ts
@@ -1,22 +1,33 @@
-import { Exec } from "../adapters/exec";
-import { findLintConfiguration } from "./findLintConfiguration";
+import { findConfiguration, FindConfigurationDependencies } from "./findConfiguration";
 
-// Soon, this will be filled out with real information...
-export type ESLintConfiguration = unknown;
-
-const defaultESLintConfiguration = {};
+export type ESLintConfiguration = {
+    env: {
+        [i: string]: boolean;
+    };
+    rules: {
+        [i: string]: number | [string, any];
+    };
+};
 
-export type FindESLintConfigurationDependencies = {
-    exec: Exec;
+const defaultESLintConfiguration = {
+    env: {},
+    rules: {},
 };
 
-export const findESLintConfiguration = (
-    dependencies: FindESLintConfigurationDependencies,
+export const findESLintConfiguration = async (
+    dependencies: FindConfigurationDependencies,
     config: string | undefined,
-) =>
-    findLintConfiguration<ESLintConfiguration>(
+): Promise<ESLintConfiguration | Error> => {
+    const rawConfiguration = await findConfiguration<ESLintConfiguration>(
         dependencies.exec,
         "eslint --print-config",
         config || "./eslintrc.js",
-        defaultESLintConfiguration,
     );
+
+    return rawConfiguration instanceof Error
+        ? rawConfiguration
+        : {
+              ...defaultESLintConfiguration,
+              ...rawConfiguration,
+          };
+};
diff --git a/src/input/findOriginalConfigurations.ts b/src/input/findOriginalConfigurations.ts
index e969891..08a3567 100644
--- a/src/input/findOriginalConfigurations.ts
+++ b/src/input/findOriginalConfigurations.ts
@@ -1,6 +1,7 @@
 import { SansDependencies } from "../binding";
 import { ResultStatus, TSLintToESLintSettings, ResultWithDataStatus } from "../types";
 import { findESLintConfiguration, ESLintConfiguration } from "./findESLintConfiguration";
+import { PackagesConfiguration, findPackagesConfiguration } from "./findPackagesConfiguration";
 import {
     findTypeScriptConfiguration,
     TypeScriptConfiguration,
@@ -9,12 +10,14 @@ import { findTSLintConfiguration, TSLintConfiguration } from "./findTSLintConfig
 
 export type FindOriginalConfigurationsDependencies = {
     findESLintConfiguration: SansDependencies<typeof findESLintConfiguration>;
+    findPackagesConfiguration: SansDependencies<typeof findPackagesConfiguration>;
     findTypeScriptConfiguration: SansDependencies<typeof findTypeScriptConfiguration>;
     findTSLintConfiguration: SansDependencies<typeof findTSLintConfiguration>;
 };
 
-export type OriginalConfigurationsData = {
+export type OriginalConfigurations = {
     eslint?: ESLintConfiguration;
+    packages?: PackagesConfiguration;
     tslint: TSLintConfiguration;
     typescript?: TypeScriptConfiguration;
 };
@@ -22,9 +25,10 @@ export type OriginalConfigurationsData = {
 export const findOriginalConfigurations = async (
     dependencies: FindOriginalConfigurationsDependencies,
     rawSettings: TSLintToESLintSettings,
-): Promise<ResultWithDataStatus<OriginalConfigurationsData>> => {
-    const [eslint, tslint, typescript] = await Promise.all([
+): Promise<ResultWithDataStatus<OriginalConfigurations>> => {
+    const [eslint, packages, tslint, typescript] = await Promise.all([
         dependencies.findESLintConfiguration(rawSettings.eslintConfig),
+        dependencies.findPackagesConfiguration(rawSettings.packages),
         dependencies.findTSLintConfiguration(rawSettings.tslintConfig),
         dependencies.findTypeScriptConfiguration(rawSettings.typescriptConfig),
     ]);
@@ -39,6 +43,7 @@ export const findOriginalConfigurations = async (
     return {
         data: {
             ...(!(eslint instanceof Error) && { eslint }),
+            ...(!(packages instanceof Error) && { packages }),
             tslint,
             ...(!(typescript instanceof Error) && { typescript }),
         },
diff --git a/src/input/findPackagesConfiguration.ts b/src/input/findPackagesConfiguration.ts
new file mode 100644
index 0000000..54ab887
--- /dev/null
+++ b/src/input/findPackagesConfiguration.ts
@@ -0,0 +1,39 @@
+import { findConfiguration, FindConfigurationDependencies } from "./findConfiguration";
+
+export type PackagesConfiguration = {
+    dependencies: {
+        [i: string]: string;
+    };
+    devDependencies: {
+        [i: string]: string;
+    };
+};
+
+const defaultPackagesConfiguration = {
+    dependencies: {},
+    devDependencies: {},
+};
+
+export const findPackagesConfiguration = async (
+    dependencies: FindConfigurationDependencies,
+    config: string | undefined,
+): Promise<PackagesConfiguration | Error> => {
+    const rawConfiguration = await findConfiguration<PackagesConfiguration>(
+        dependencies.exec,
+        "cat",
+        config || "./package.json",
+    );
+
+    return rawConfiguration instanceof Error
+        ? rawConfiguration
+        : {
+              dependencies: {
+                  ...rawConfiguration.dependencies,
+                  ...defaultPackagesConfiguration.dependencies,
+              },
+              devDependencies: {
+                  ...rawConfiguration.devDependencies,
+                  ...defaultPackagesConfiguration.devDependencies,
+              },
+          };
+};
diff --git a/src/input/findTSLintConfiguration.ts b/src/input/findTSLintConfiguration.ts
index ea02605..87e92e5 100644
--- a/src/input/findTSLintConfiguration.ts
+++ b/src/input/findTSLintConfiguration.ts
@@ -1,5 +1,5 @@
 import { Exec } from "../adapters/exec";
-import { findLintConfiguration } from "./findLintConfiguration";
+import { findConfiguration } from "./findConfiguration";
 
 export type TSLintConfiguration = {
     ruleDirectories: string[];
@@ -19,13 +19,20 @@ export type FindTSLintConfigurationDependencies = {
     exec: Exec;
 };
 
-export const findTSLintConfiguration = (
+export const findTSLintConfiguration = async (
     dependencies: FindTSLintConfigurationDependencies,
     config: string | undefined,
-) =>
-    findLintConfiguration<TSLintConfiguration>(
+): Promise<TSLintConfiguration | Error> => {
+    const rawConfiguration = await findConfiguration<TSLintConfiguration>(
         dependencies.exec,
         "tslint --print-config",
         config || "./tslint.json",
-        defaultTSLintConfiguration,
     );
+
+    return rawConfiguration instanceof Error
+        ? rawConfiguration
+        : {
+              ...defaultTSLintConfiguration,
+              ...rawConfiguration,
+          };
+};
diff --git a/src/input/findTypeScriptConfiguration.ts b/src/input/findTypeScriptConfiguration.ts
index 9e15b53..f0e33ca 100644
--- a/src/input/findTypeScriptConfiguration.ts
+++ b/src/input/findTypeScriptConfiguration.ts
@@ -1,22 +1,34 @@
-import { Exec } from "../adapters/exec";
-import { findLintConfiguration } from "./findLintConfiguration";
+import { findConfiguration, FindConfigurationDependencies } from "./findConfiguration";
 
-// Soon, this will be filled out with real information...
-export type TypeScriptConfiguration = unknown;
-
-const defaultTypeScriptConfiguration = {};
+export type TypeScriptConfiguration = {
+    compilerOptions: {
+        lib?: string[];
+        target: string;
+    };
+};
 
-export type FindTypeScriptConfigurationDependencies = {
-    exec: Exec;
+const defaultTypeScriptConfiguration = {
+    compilerOptions: {
+        target: "es3",
+    },
 };
 
-export const findTypeScriptConfiguration = (
-    dependencies: FindTypeScriptConfigurationDependencies,
+export const findTypeScriptConfiguration = async (
+    dependencies: FindConfigurationDependencies,
     config: string | undefined,
-) =>
-    findLintConfiguration<TypeScriptConfiguration>(
+): Promise<TypeScriptConfiguration | Error> => {
+    const rawConfiguration = await findConfiguration<TypeScriptConfiguration>(
         dependencies.exec,
         "cat",
         config || "./tsconfig.json",
-        defaultTypeScriptConfiguration,
     );
+
+    return rawConfiguration instanceof Error
+        ? rawConfiguration
+        : {
+              compilerOptions: {
+                  ...defaultTypeScriptConfiguration.compilerOptions,
+                  ...rawConfiguration.compilerOptions,
+              },
+          };
+};
diff --git a/src/reporting/reportConversionResults.ts b/src/reporting/reportConversionResults.ts
index 62ada89..dd32b42 100644
--- a/src/reporting/reportConversionResults.ts
+++ b/src/reporting/reportConversionResults.ts
@@ -3,7 +3,7 @@ import { EOL } from "os";
 
 import { Logger } from "../adapters/logger";
 import { ConversionError } from "../rules/conversionError";
-import { ConfigConversionResults } from "../rules/convertRules";
+import { RuleConversionResults } from "../rules/convertRules";
 import { TSLintRuleOptions, ESLintRuleOptions } from "../rules/types";
 
 export type ReportConversionResultsDependencies = {
@@ -12,22 +12,22 @@ export type ReportConversionResultsDependencies = {
 
 export const reportConversionResults = (
     dependencies: ReportConversionResultsDependencies,
-    conversionResults: ConfigConversionResults,
+    ruleConversionResults: RuleConversionResults,
 ) => {
-    if (conversionResults.converted.size !== 0) {
-        logSuccessfulConversions(conversionResults.converted, dependencies.logger);
+    if (ruleConversionResults.converted.size !== 0) {
+        logSuccessfulConversions(ruleConversionResults.converted, dependencies.logger);
     }
 
-    if (conversionResults.failed.length !== 0) {
-        logFailedConversions(conversionResults.failed, dependencies.logger);
+    if (ruleConversionResults.failed.length !== 0) {
+        logFailedConversions(ruleConversionResults.failed, dependencies.logger);
     }
 
-    if (conversionResults.missing.length !== 0) {
-        logMissingRules(conversionResults.missing, dependencies.logger);
+    if (ruleConversionResults.missing.length !== 0) {
+        logMissingRules(ruleConversionResults.missing, dependencies.logger);
     }
 
-    if (conversionResults.packages.size !== 0) {
-        logMissingPackages(conversionResults.packages, dependencies.logger);
+    if (ruleConversionResults.packages.size !== 0) {
+        logMissingPackages(ruleConversionResults.packages, dependencies.logger);
     }
 };
 
diff --git a/src/rules/convertRules.ts b/src/rules/convertRules.ts
index b42bf65..0c29e7c 100644
--- a/src/rules/convertRules.ts
+++ b/src/rules/convertRules.ts
@@ -11,7 +11,7 @@ export type ConvertRulesDependencies = {
     mergers: Map<string, RuleMerger>;
 };
 
-export type ConfigConversionResults = {
+export type RuleConversionResults = {
     converted: Map<string, ESLintRuleOptions>;
     failed: ConversionError[];
     missing: TSLintRuleOptions[];
@@ -21,7 +21,7 @@ export type ConfigConversionResults = {
 export const convertRules = (
     dependencies: ConvertRulesDependencies,
     rawTslintRules: TSLintConfigurationRules,
-): ConfigConversionResults => {
+): RuleConversionResults => {
     const converted = new Map<string, ESLintRuleOptions>();
     const failed: ConversionError[] = [];
     const missing: TSLintRuleOptions[] = [];
diff --git a/src/types.ts b/src/types.ts
index 4faf571..d04b5ac 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -4,6 +4,11 @@ export type TSLintToESLintSettings = {
      */
     eslintConfig?: string;
 
+    /**
+     * Original packages configuration file path, such as `package.json`.
+     */
+    packages?: string;
+
     /**
      * Original TSLint configuration file path, such as `tslint.json`.
      */


Test patch:
diff --git a/src/conversion/convertConfig.test.ts b/src/conversion/convertConfig.test.ts
index 179c96c..bb603d9 100644
--- a/src/conversion/convertConfig.test.ts
+++ b/src/conversion/convertConfig.test.ts
@@ -1,6 +1,6 @@
 import { ResultStatus, FailedResult, SucceededDataResult } from "../types";
 import { convertConfig, ConvertConfigDependencies } from "./convertConfig";
-import { OriginalConfigurationsData } from "../input/findOriginalConfigurations";
+import { OriginalConfigurations } from "../input/findOriginalConfigurations";
 
 const createStubDependencies = (
     overrides: Pick<ConvertConfigDependencies, "findOriginalConfigurations">,
@@ -12,12 +12,10 @@ const createStubDependencies = (
 });
 
 const createStubOriginalConfigurationsData = () => ({
-    eslint: {},
     tslint: {
         rules: [],
         ruleDirectories: [],
     },
-    typescript: {},
 });
 
 describe("convertConfig", () => {
@@ -41,7 +39,7 @@ describe("convertConfig", () => {
 
     it("returns a successful result when finding the original configurations succeeds", async () => {
         // Arrange
-        const findSuccess: SucceededDataResult<OriginalConfigurationsData> = {
+        const findSuccess: SucceededDataResult<OriginalConfigurations> = {
             data: createStubOriginalConfigurationsData(),
             status: ResultStatus.Succeeded,
         };
diff --git a/src/creation/eslint/createEnv.test.ts b/src/creation/eslint/createEnv.test.ts
new file mode 100644
index 0000000..c648ca5
--- /dev/null
+++ b/src/creation/eslint/createEnv.test.ts
@@ -0,0 +1,188 @@
+import { createEnv } from "./createEnv";
+import { TypeScriptConfiguration } from "../../input/findTypeScriptConfiguration";
+
+const createTypeScriptCompilerOptions = (
+    overrides: Partial<TypeScriptConfiguration["compilerOptions"]> = {},
+) => ({
+    target: "es3",
+    ...overrides,
+});
+
+describe("createEnv", () => {
+    it("returns node, browser, and es6 as true if typescript is undefined", () => {
+        // Arrange
+        const packages = undefined;
+        const typescript = undefined;
+
+        // Act
+        const env = createEnv({ packages, typescript });
+
+        // Assert
+        expect(env).toEqual(
+            expect.objectContaining({
+                browser: true,
+                es6: true,
+                node: true,
+            }),
+        );
+    });
+
+    it("returns browser as true if no typescript libs are provided", () => {
+        // Arrange
+        const packages = undefined;
+        const typescript = {
+            compilerOptions: createTypeScriptCompilerOptions(),
+        };
+
+        // Act
+        const env = createEnv({ packages, typescript });
+
+        // Assert
+        expect(env).toEqual(
+            expect.objectContaining({
+                browser: true,
+            }),
+        );
+    });
+
+    it("returns browser as false if typescript libs don't include dom", () => {
+        // Arrange
+        const packages = undefined;
+        const typescript = {
+            compilerOptions: createTypeScriptCompilerOptions({
+                lib: ["esnext"],
+            }),
+        };
+
+        // Act
+        const env = createEnv({ packages, typescript });
+
+        // Assert
+        expect(env).not.toContain({
+            browser: expect.any(Boolean),
+        });
+    });
+
+    it("returns browser as true if a typescript lib is dom", () => {
+        // Arrange
+        const packages = undefined;
+        const typescript = {
+            compilerOptions: createTypeScriptCompilerOptions({
+                lib: ["dom"],
+            }),
+        };
+
+        // Act
+        const env = createEnv({ packages, typescript });
+
+        // Assert
+        expect(env).toEqual(
+            expect.objectContaining({
+                browser: true,
+            }),
+        );
+    });
+
+    it("returns es6 as false if the typescript target is lower than es6", () => {
+        // Arrange
+        const packages = undefined;
+        const typescript = {
+            compilerOptions: createTypeScriptCompilerOptions({
+                target: "es5",
+            }),
+        };
+
+        // Act
+        const env = createEnv({ packages, typescript });
+
+        // Assert
+        expect(env).not.toEqual(
+            expect.objectContaining({
+                es6: expect.any(Boolean),
+            }),
+        );
+    });
+
+    it("returns es6 as true if the typescript target includes es6", () => {
+        // Arrange
+        const packages = undefined;
+        const typescript = {
+            compilerOptions: createTypeScriptCompilerOptions({
+                target: "es2015",
+            }),
+        };
+
+        // Act
+        const env = createEnv({ packages, typescript });
+
+        // Assert
+        expect(env).toEqual(
+            expect.objectContaining({
+                es6: true,
+            }),
+        );
+    });
+
+    it("returns node as false if package dependencies and devDependencies don't include @types/node", () => {
+        // Arrange
+        const packages = {
+            dependencies: {},
+            devDependencies: {
+                "@types/other": "1.2.3",
+            },
+        };
+        const typescript = undefined;
+
+        // Act
+        const env = createEnv({ packages, typescript });
+
+        // Assert
+        expect(env).not.toEqual(
+            expect.objectContaining({
+                node: expect.any(Boolean),
+            }),
+        );
+    });
+
+    it("returns node as true if package dependencies include @types/node", () => {
+        // Arrange
+        const packages = {
+            dependencies: {
+                "@types/node": "1.2.3",
+            },
+            devDependencies: {},
+        };
+        const typescript = undefined;
+
+        // Act
+        const env = createEnv({ packages, typescript });
+
+        // Assert
+        expect(env).toEqual(
+            expect.objectContaining({
+                node: true,
+            }),
+        );
+    });
+
+    it("returns node as true if package devDependencies include @types/node", () => {
+        // Arrange
+        const packages = {
+            dependencies: {},
+            devDependencies: {
+                "@types/node": "1.2.3",
+            },
+        };
+        const typescript = undefined;
+
+        // Act
+        const env = createEnv({ packages, typescript });
+
+        // Assert
+        expect(env).toEqual(
+            expect.objectContaining({
+                node: true,
+            }),
+        );
+    });
+});
diff --git a/src/creation/writeConversionResults.test.ts b/src/creation/writeConversionResults.test.ts
index 4e5bfc4..64deb35 100644
--- a/src/creation/writeConversionResults.test.ts
+++ b/src/creation/writeConversionResults.test.ts
@@ -1,9 +1,11 @@
 import { createEmptyConversionResults } from "../conversion/conversionResults.stubs";
 import { writeConversionResults } from "./writeConversionResults";
 
-const tslintConfiguration = {
-    ruleDirectories: [],
-    rules: {},
+const originalConfigurations = {
+    tslint: {
+        ruleDirectories: [],
+        rules: {},
+    },
 };
 
 describe("writeConversionResults", () => {
@@ -15,13 +17,18 @@ describe("writeConversionResults", () => {
         const fileSystem = { writeFile: jest.fn().mockReturnValue(Promise.resolve()) };
 
         // Act
-        await writeConversionResults({ fileSystem }, conversionResults, tslintConfiguration);
+        await writeConversionResults({ fileSystem }, conversionResults, originalConfigurations);
 
         // Assert
         expect(fileSystem.writeFile).toHaveBeenLastCalledWith(
             ".eslintrc.json",
             JSON.stringify(
                 {
+                    env: {
+                        browser: true,
+                        es6: true,
+                        node: true,
+                    },
                     parser: "@typescript-eslint/parser",
                     parserOptions: {
                         project: "tsconfig.json",
@@ -49,13 +56,18 @@ describe("writeConversionResults", () => {
         const fileSystem = { writeFile: jest.fn().mockReturnValue(Promise.resolve()) };
 
         // Act
-        await writeConversionResults({ fileSystem }, conversionResults, tslintConfiguration);
+        await writeConversionResults({ fileSystem }, conversionResults, originalConfigurations);
 
         // Assert
         expect(fileSystem.writeFile).toHaveBeenLastCalledWith(
             ".eslintrc.json",
             JSON.stringify(
                 {
+                    env: {
+                        browser: true,
+                        es6: true,
+                        node: true,
+                    },
                     parser: "@typescript-eslint/parser",
                     parserOptions: {
                         project: "tsconfig.json",
diff --git a/src/input/findLintConfiguration.test.ts b/src/input/findConfiguration.test.ts
similarity index 55%
rename from src/input/findLintConfiguration.test.ts
rename to src/input/findConfiguration.test.ts
index 131eeaa..d2a9f21 100644
--- a/src/input/findLintConfiguration.test.ts
+++ b/src/input/findConfiguration.test.ts
@@ -1,14 +1,14 @@
 import { createStubExec, createStubThrowingExec } from "../adapters/exec.stubs";
-import { findLintConfiguration } from "./findLintConfiguration";
+import { findConfiguration } from "./findConfiguration";
 
-describe("findLintConfiguration", () => {
+describe("findConfiguration", () => {
     it("returns stderr as an error when the command fails with a zero exit code", async () => {
         // Arrange
         const stderr = "error";
         const exec = createStubExec({ stderr });
 
         // Act
-        const result = await findLintConfiguration(exec, "command", "sample.json", {});
+        const result = await findConfiguration(exec, "command", "sample.json");
 
         // Assert
         expect(result).toEqual(new Error(stderr));
@@ -20,7 +20,7 @@ describe("findLintConfiguration", () => {
         const exec = createStubThrowingExec({ stderr });
 
         // Act
-        const result = await findLintConfiguration(exec, "command", "sample.json", {});
+        const result = await findConfiguration(exec, "command", "sample.json");
 
         // Assert
         expect(result).toEqual(new Error(stderr));
@@ -32,7 +32,7 @@ describe("findLintConfiguration", () => {
         const exec = createStubExec({ stdout });
 
         // Act
-        const result = await findLintConfiguration(exec, "command", "sample.json", {});
+        const result = await findConfiguration(exec, "command", "sample.json");
 
         // Assert
         expect(result).toEqual(
@@ -49,37 +49,11 @@ describe("findLintConfiguration", () => {
         const exec = createStubExec({ stdout });
 
         // Act
-        const result = await findLintConfiguration(exec, "command", "sample.json", {});
+        const result = await findConfiguration(exec, "command", "sample.json");
 
         // Assert
         expect(result).toEqual({
             rules,
         });
     });
-
-    it("fills in configuration defaults the command returns valid but empty JSON", async () => {
-        // Arrange
-        const defaultConfiguration = {
-            default: true,
-        };
-        const originalConfiguration = {
-            original: true,
-        };
-        const stdout = JSON.stringify(originalConfiguration);
-        const exec = createStubExec({ stdout });
-
-        // Act
-        const result = await findLintConfiguration(
-            exec,
-            "command",
-            "sample.json",
-            defaultConfiguration,
-        );
-
-        // Assert
-        expect(result).toEqual({
-            ...defaultConfiguration,
-            ...originalConfiguration,
-        });
-    });
 });
diff --git a/src/input/findESLintConfiguration.test.ts b/src/input/findESLintConfiguration.test.ts
index b6cd82f..0615c99 100644
--- a/src/input/findESLintConfiguration.test.ts
+++ b/src/input/findESLintConfiguration.test.ts
@@ -1,7 +1,23 @@
 import { findESLintConfiguration } from "./findESLintConfiguration";
-import { createStubExec } from "../adapters/exec.stubs";
+import { createStubExec, createStubThrowingExec } from "../adapters/exec.stubs";
 
 describe("findESLintConfiguration", () => {
+    it("returns an error when one occurs", async () => {
+        // Arrange
+        const message = "error";
+        const dependencies = { exec: createStubThrowingExec({ stderr: message }) };
+
+        // Act
+        const result = await findESLintConfiguration(dependencies, undefined);
+
+        // Assert
+        expect(result).toEqual(
+            expect.objectContaining({
+                message,
+            }),
+        );
+    });
+
     it("defaults the configuration file when one isn't provided", async () => {
         // Arrange
         const dependencies = { exec: createStubExec() };
@@ -36,6 +52,9 @@ describe("findESLintConfiguration", () => {
         const result = await findESLintConfiguration(dependencies, config);
 
         // Assert
-        expect(result).toEqual({});
+        expect(result).toEqual({
+            env: {},
+            rules: {},
+        });
     });
 });
diff --git a/src/input/findOriginalConfigurations.test.ts b/src/input/findOriginalConfigurations.test.ts
index c18c6a3..4ace440 100644
--- a/src/input/findOriginalConfigurations.test.ts
+++ b/src/input/findOriginalConfigurations.test.ts
@@ -1,4 +1,7 @@
-import { findOriginalConfigurations } from "./findOriginalConfigurations";
+import {
+    findOriginalConfigurations,
+    FindOriginalConfigurationsDependencies,
+} from "./findOriginalConfigurations";
 import { ResultStatus } from "../types";
 
 const createRawSettings = () => ({
@@ -7,15 +10,34 @@ const createRawSettings = () => ({
     typescriptConfig: "",
 });
 
+const createDependencies = (overrides: Partial<FindOriginalConfigurationsDependencies> = {}) => ({
+    findESLintConfiguration: async () => ({
+        env: {},
+        rules: {},
+    }),
+    findPackagesConfiguration: async () => ({
+        dependencies: {},
+        devDependencies: {},
+    }),
+    findTSLintConfiguration: async () => ({
+        ruleDirectories: [],
+        rules: {},
+    }),
+    findTypeScriptConfiguration: async () => ({
+        compilerOptions: {
+            target: "es3",
+        },
+    }),
+    ...overrides,
+});
+
 describe("findOriginalConfigurations", () => {
     it("returns an errors when the tslint finder returns an error", async () => {
         // Arrange
         const complaint = "Complaint from TSLint";
-        const dependencies = {
-            findESLintConfiguration: async () => ({}),
+        const dependencies = createDependencies({
             findTSLintConfiguration: async () => new Error(complaint),
-            findTypeScriptConfiguration: async () => ({}),
-        };
+        });
 
         // Act
         const result = await findOriginalConfigurations(dependencies, createRawSettings());
@@ -29,15 +51,11 @@ describe("findOriginalConfigurations", () => {
 
     it("returns only tslint results when the other finders return errors", async () => {
         // Arrange
-        const complaints = ["Complaint from ESLint", "Complaint from TypeScript"];
-        const dependencies = {
-            findESLintConfiguration: async () => new Error(complaints[0]),
-            findTSLintConfiguration: async () => ({
-                ruleDirectories: [],
-                rules: {},
-            }),
-            findTypeScriptConfiguration: async () => new Error(complaints[1]),
-        };
+        const dependencies = createDependencies({
+            findESLintConfiguration: async () => new Error("one"),
+            findPackagesConfiguration: async () => new Error("two"),
+            findTypeScriptConfiguration: async () => new Error("three"),
+        });
 
         // Act
         const result = await findOriginalConfigurations(dependencies, createRawSettings());
@@ -56,14 +74,7 @@ describe("findOriginalConfigurations", () => {
 
     it("returns successful results when all finders succeed", async () => {
         // Arrange
-        const dependencies = {
-            findESLintConfiguration: async () => ({}),
-            findTSLintConfiguration: async () => ({
-                ruleDirectories: [],
-                rules: {},
-            }),
-            findTypeScriptConfiguration: async () => ({}),
-        };
+        const dependencies = createDependencies();
 
         // Act
         const result = await findOriginalConfigurations(dependencies, createRawSettings());
@@ -71,12 +82,23 @@ describe("findOriginalConfigurations", () => {
         // Assert
         expect(result).toEqual({
             data: {
-                eslint: {},
+                eslint: {
+                    env: {},
+                    rules: {},
+                },
+                packages: {
+                    dependencies: {},
+                    devDependencies: {},
+                },
                 tslint: {
                     ruleDirectories: [],
                     rules: {},
                 },
-                typescript: {},
+                typescript: {
+                    compilerOptions: {
+                        target: "es3",
+                    },
+                },
             },
             status: ResultStatus.Succeeded,
         });
diff --git a/src/input/findPackagesConfiguration.test.ts b/src/input/findPackagesConfiguration.test.ts
new file mode 100644
index 0000000..01d23b4
--- /dev/null
+++ b/src/input/findPackagesConfiguration.test.ts
@@ -0,0 +1,42 @@
+import { findPackagesConfiguration } from "./findPackagesConfiguration";
+import { createStubExec } from "../adapters/exec.stubs";
+
+describe("findPackagesConfiguration", () => {
+    it("defaults the configuration file when one isn't provided", async () => {
+        // Arrange
+        const dependencies = { exec: createStubExec() };
+
+        // Act
+        await findPackagesConfiguration(dependencies, undefined);
+
+        // Assert
+        expect(dependencies.exec).toHaveBeenLastCalledWith("cat ./package.json");
+    });
+
+    it("includes a configuration file in the packages command when one is provided", async () => {
+        // Arrange
+        const dependencies = { exec: createStubExec() };
+        const config = "./custom/package.json";
+
+        // Act
+        await findPackagesConfiguration(dependencies, config);
+
+        // Assert
+        expect(dependencies.exec).toHaveBeenLastCalledWith("cat ./custom/package.json");
+    });
+
+    it("applies packages defaults when none are provided", async () => {
+        // Arrange
+        const dependencies = { exec: createStubExec({ stdout: "{}" }) };
+        const config = "./package.json";
+
+        // Act
+        const result = await findPackagesConfiguration(dependencies, config);
+
+        // Assert
+        expect(result).toEqual({
+            dependencies: {},
+            devDependencies: {},
+        });
+    });
+});
diff --git a/src/input/findTslintConfiguration.test.ts b/src/input/findTslintConfiguration.test.ts
index f2a64cd..ccccf61 100644
--- a/src/input/findTslintConfiguration.test.ts
+++ b/src/input/findTslintConfiguration.test.ts
@@ -1,7 +1,23 @@
 import { findTSLintConfiguration } from "./findTSLintConfiguration";
-import { createStubExec } from "../adapters/exec.stubs";
+import { createStubExec, createStubThrowingExec } from "../adapters/exec.stubs";
 
 describe("findTSLintConfiguration", () => {
+    it("returns an error when one occurs", async () => {
+        // Arrange
+        const stderr = "error";
+        const dependencies = { exec: createStubThrowingExec({ stderr }) };
+
+        // Act
+        const result = await findTSLintConfiguration(dependencies, undefined);
+
+        // Assert
+        expect(result).toEqual(
+            expect.objectContaining({
+                message: stderr,
+            }),
+        );
+    });
+
     it("defaults the configuration file when one isn't provided", async () => {
         // Arrange
         const dependencies = { exec: createStubExec() };
diff --git a/src/input/findTypeScriptConfiguration.test.ts b/src/input/findTypeScriptConfiguration.test.ts
index 7303f70..4604faf 100644
--- a/src/input/findTypeScriptConfiguration.test.ts
+++ b/src/input/findTypeScriptConfiguration.test.ts
@@ -1,7 +1,23 @@
 import { findTypeScriptConfiguration } from "./findTypeScriptConfiguration";
-import { createStubExec } from "../adapters/exec.stubs";
+import { createStubExec, createStubThrowingExec } from "../adapters/exec.stubs";
 
 describe("findTypeScriptConfiguration", () => {
+    it("returns an error when one occurs", async () => {
+        // Arrange
+        const message = "error";
+        const dependencies = { exec: createStubThrowingExec({ stderr: message }) };
+
+        // Act
+        const result = await findTypeScriptConfiguration(dependencies, undefined);
+
+        // Assert
+        expect(result).toEqual(
+            expect.objectContaining({
+                message,
+            }),
+        );
+    });
+
     it("defaults the configuration file when one isn't provided", async () => {
         // Arrange
         const dependencies = { exec: createStubExec() };
@@ -34,6 +50,10 @@ describe("findTypeScriptConfiguration", () => {
         const result = await findTypeScriptConfiguration(dependencies, config);
 
         // Assert
-        expect(result).toEqual({});
+        expect(result).toEqual({
+            compilerOptions: {
+                target: "es3",
+            },
+        });
     });
 });