| /** | 
|  * @fileoverview Config initialization wizard. | 
|  * @author Ilya Volodin | 
|  */ | 
|   | 
|   | 
| "use strict"; | 
|   | 
| //------------------------------------------------------------------------------ | 
| // Requirements | 
| //------------------------------------------------------------------------------ | 
|   | 
| const util = require("util"), | 
|     path = require("path"), | 
|     enquirer = require("enquirer"), | 
|     ProgressBar = require("progress"), | 
|     semver = require("semver"), | 
|     espree = require("espree"), | 
|     recConfig = require("../../conf/eslint-recommended"), | 
|     ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops"), | 
|     log = require("../shared/logging"), | 
|     naming = require("@eslint/eslintrc/lib/shared/naming"), | 
|     ModuleResolver = require("../shared/relative-module-resolver"), | 
|     autoconfig = require("./autoconfig.js"), | 
|     ConfigFile = require("./config-file"), | 
|     npmUtils = require("./npm-utils"), | 
|     { getSourceCodeOfFiles } = require("./source-code-utils"); | 
|   | 
| const debug = require("debug")("eslint:config-initializer"); | 
|   | 
| //------------------------------------------------------------------------------ | 
| // Private | 
| //------------------------------------------------------------------------------ | 
|   | 
| /* istanbul ignore next: hard to test fs function */ | 
| /** | 
|  * Create .eslintrc file in the current working directory | 
|  * @param {Object} config object that contains user's answers | 
|  * @param {string} format The file format to write to. | 
|  * @returns {void} | 
|  */ | 
| function writeFile(config, format) { | 
|   | 
|     // default is .js | 
|     let extname = ".js"; | 
|   | 
|     if (format === "YAML") { | 
|         extname = ".yml"; | 
|     } else if (format === "JSON") { | 
|         extname = ".json"; | 
|     } | 
|   | 
|     const installedESLint = config.installedESLint; | 
|   | 
|     delete config.installedESLint; | 
|   | 
|     ConfigFile.write(config, `./.eslintrc${extname}`); | 
|     log.info(`Successfully created .eslintrc${extname} file in ${process.cwd()}`); | 
|   | 
|     if (installedESLint) { | 
|         log.info("ESLint was installed locally. We recommend using this local copy instead of your globally-installed copy."); | 
|     } | 
| } | 
|   | 
| /** | 
|  * Get the peer dependencies of the given module. | 
|  * This adds the gotten value to cache at the first time, then reuses it. | 
|  * In a process, this function is called twice, but `npmUtils.fetchPeerDependencies` needs to access network which is relatively slow. | 
|  * @param {string} moduleName The module name to get. | 
|  * @returns {Object} The peer dependencies of the given module. | 
|  * This object is the object of `peerDependencies` field of `package.json`. | 
|  * Returns null if npm was not found. | 
|  */ | 
| function getPeerDependencies(moduleName) { | 
|     let result = getPeerDependencies.cache.get(moduleName); | 
|   | 
|     if (!result) { | 
|         log.info(`Checking peerDependencies of ${moduleName}`); | 
|   | 
|         result = npmUtils.fetchPeerDependencies(moduleName); | 
|         getPeerDependencies.cache.set(moduleName, result); | 
|     } | 
|   | 
|     return result; | 
| } | 
| getPeerDependencies.cache = new Map(); | 
|   | 
| /** | 
|  * Return necessary plugins, configs, parsers, etc. based on the config | 
|  * @param   {Object} config  config object | 
|  * @param   {boolean} [installESLint=true]  If `false` is given, it does not install eslint. | 
|  * @returns {string[]} An array of modules to be installed. | 
|  */ | 
| function getModulesList(config, installESLint) { | 
|     const modules = {}; | 
|   | 
|     // Create a list of modules which should be installed based on config | 
|     if (config.plugins) { | 
|         for (const plugin of config.plugins) { | 
|             const moduleName = naming.normalizePackageName(plugin, "eslint-plugin"); | 
|   | 
|             modules[moduleName] = "latest"; | 
|         } | 
|     } | 
|     if (config.extends) { | 
|         const extendList = Array.isArray(config.extends) ? config.extends : [config.extends]; | 
|   | 
|         for (const extend of extendList) { | 
|             if (extend.startsWith("eslint:") || extend.startsWith("plugin:")) { | 
|                 continue; | 
|             } | 
|             const moduleName = naming.normalizePackageName(extend, "eslint-config"); | 
|   | 
|             modules[moduleName] = "latest"; | 
|             Object.assign( | 
|                 modules, | 
|                 getPeerDependencies(`${moduleName}@latest`) | 
|             ); | 
|         } | 
|     } | 
|   | 
|     const parser = config.parser || (config.parserOptions && config.parserOptions.parser); | 
|   | 
|     if (parser) { | 
|         modules[parser] = "latest"; | 
|     } | 
|   | 
|     if (installESLint === false) { | 
|         delete modules.eslint; | 
|     } else { | 
|         const installStatus = npmUtils.checkDevDeps(["eslint"]); | 
|   | 
|         // Mark to show messages if it's new installation of eslint. | 
|         if (installStatus.eslint === false) { | 
|             log.info("Local ESLint installation not found."); | 
|             modules.eslint = modules.eslint || "latest"; | 
|             config.installedESLint = true; | 
|         } | 
|     } | 
|   | 
|     return Object.keys(modules).map(name => `${name}@${modules[name]}`); | 
| } | 
|   | 
| /** | 
|  * Set the `rules` of a config by examining a user's source code | 
|  * | 
|  * Note: This clones the config object and returns a new config to avoid mutating | 
|  * the original config parameter. | 
|  * @param   {Object} answers  answers received from enquirer | 
|  * @param   {Object} config   config object | 
|  * @returns {Object}          config object with configured rules | 
|  */ | 
| function configureRules(answers, config) { | 
|     const BAR_TOTAL = 20, | 
|         BAR_SOURCE_CODE_TOTAL = 4, | 
|         newConfig = Object.assign({}, config), | 
|         disabledConfigs = {}; | 
|     let sourceCodes, | 
|         registry; | 
|   | 
|     // Set up a progress bar, as this process can take a long time | 
|     const bar = new ProgressBar("Determining Config: :percent [:bar] :elapseds elapsed, eta :etas ", { | 
|         width: 30, | 
|         total: BAR_TOTAL | 
|     }); | 
|   | 
|     bar.tick(0); // Shows the progress bar | 
|   | 
|     // Get the SourceCode of all chosen files | 
|     const patterns = answers.patterns.split(/[\s]+/u); | 
|   | 
|     try { | 
|         sourceCodes = getSourceCodeOfFiles(patterns, { baseConfig: newConfig, useEslintrc: false }, total => { | 
|             bar.tick((BAR_SOURCE_CODE_TOTAL / total)); | 
|         }); | 
|     } catch (e) { | 
|         log.info("\n"); | 
|         throw e; | 
|     } | 
|     const fileQty = Object.keys(sourceCodes).length; | 
|   | 
|     if (fileQty === 0) { | 
|         log.info("\n"); | 
|         throw new Error("Automatic Configuration failed.  No files were able to be parsed."); | 
|     } | 
|   | 
|     // Create a registry of rule configs | 
|     registry = new autoconfig.Registry(); | 
|     registry.populateFromCoreRules(); | 
|   | 
|     // Lint all files with each rule config in the registry | 
|     registry = registry.lintSourceCode(sourceCodes, newConfig, total => { | 
|         bar.tick((BAR_TOTAL - BAR_SOURCE_CODE_TOTAL) / total); // Subtract out ticks used at beginning | 
|     }); | 
|     debug(`\nRegistry: ${util.inspect(registry.rules, { depth: null })}`); | 
|   | 
|     // Create a list of recommended rules, because we don't want to disable them | 
|     const recRules = Object.keys(recConfig.rules).filter(ruleId => ConfigOps.isErrorSeverity(recConfig.rules[ruleId])); | 
|   | 
|     // Find and disable rules which had no error-free configuration | 
|     const failingRegistry = registry.getFailingRulesRegistry(); | 
|   | 
|     Object.keys(failingRegistry.rules).forEach(ruleId => { | 
|   | 
|         // If the rule is recommended, set it to error, otherwise disable it | 
|         disabledConfigs[ruleId] = (recRules.indexOf(ruleId) !== -1) ? 2 : 0; | 
|     }); | 
|   | 
|     // Now that we know which rules to disable, strip out configs with errors | 
|     registry = registry.stripFailingConfigs(); | 
|   | 
|     /* | 
|      * If there is only one config that results in no errors for a rule, we should use it. | 
|      * createConfig will only add rules that have one configuration in the registry. | 
|      */ | 
|     const singleConfigs = registry.createConfig().rules; | 
|   | 
|     /* | 
|      * The "sweet spot" for number of options in a config seems to be two (severity plus one option). | 
|      * Very often, a third option (usually an object) is available to address | 
|      * edge cases, exceptions, or unique situations. We will prefer to use a config with | 
|      * specificity of two. | 
|      */ | 
|     const specTwoConfigs = registry.filterBySpecificity(2).createConfig().rules; | 
|   | 
|     // Maybe a specific combination using all three options works | 
|     const specThreeConfigs = registry.filterBySpecificity(3).createConfig().rules; | 
|   | 
|     // If all else fails, try to use the default (severity only) | 
|     const defaultConfigs = registry.filterBySpecificity(1).createConfig().rules; | 
|   | 
|     // Combine configs in reverse priority order (later take precedence) | 
|     newConfig.rules = Object.assign({}, disabledConfigs, defaultConfigs, specThreeConfigs, specTwoConfigs, singleConfigs); | 
|   | 
|     // Make sure progress bar has finished (floating point rounding) | 
|     bar.update(BAR_TOTAL); | 
|   | 
|     // Log out some stats to let the user know what happened | 
|     const finalRuleIds = Object.keys(newConfig.rules); | 
|     const totalRules = finalRuleIds.length; | 
|     const enabledRules = finalRuleIds.filter(ruleId => (newConfig.rules[ruleId] !== 0)).length; | 
|     const resultMessage = [ | 
|         `\nEnabled ${enabledRules} out of ${totalRules}`, | 
|         `rules based on ${fileQty}`, | 
|         `file${(fileQty === 1) ? "." : "s."}` | 
|     ].join(" "); | 
|   | 
|     log.info(resultMessage); | 
|   | 
|     ConfigOps.normalizeToStrings(newConfig); | 
|     return newConfig; | 
| } | 
|   | 
| /** | 
|  * process user's answers and create config object | 
|  * @param {Object} answers answers received from enquirer | 
|  * @returns {Object} config object | 
|  */ | 
| function processAnswers(answers) { | 
|     let config = { | 
|         rules: {}, | 
|         env: {}, | 
|         parserOptions: {}, | 
|         extends: [] | 
|     }; | 
|   | 
|     config.parserOptions.ecmaVersion = espree.latestEcmaVersion; | 
|     config.env.es2021 = true; | 
|   | 
|     // set the module type | 
|     if (answers.moduleType === "esm") { | 
|         config.parserOptions.sourceType = "module"; | 
|     } else if (answers.moduleType === "commonjs") { | 
|         config.env.commonjs = true; | 
|     } | 
|   | 
|     // add in browser and node environments if necessary | 
|     answers.env.forEach(env => { | 
|         config.env[env] = true; | 
|     }); | 
|   | 
|     // add in library information | 
|     if (answers.framework === "react") { | 
|         config.parserOptions.ecmaFeatures = { | 
|             jsx: true | 
|         }; | 
|         config.plugins = ["react"]; | 
|         config.extends.push("plugin:react/recommended"); | 
|     } else if (answers.framework === "vue") { | 
|         config.plugins = ["vue"]; | 
|         config.extends.push("plugin:vue/essential"); | 
|     } | 
|   | 
|     if (answers.typescript) { | 
|         if (answers.framework === "vue") { | 
|             config.parserOptions.parser = "@typescript-eslint/parser"; | 
|         } else { | 
|             config.parser = "@typescript-eslint/parser"; | 
|         } | 
|   | 
|         if (Array.isArray(config.plugins)) { | 
|             config.plugins.push("@typescript-eslint"); | 
|         } else { | 
|             config.plugins = ["@typescript-eslint"]; | 
|         } | 
|     } | 
|   | 
|     // setup rules based on problems/style enforcement preferences | 
|     if (answers.purpose === "problems") { | 
|         config.extends.unshift("eslint:recommended"); | 
|     } else if (answers.purpose === "style") { | 
|         if (answers.source === "prompt") { | 
|             config.extends.unshift("eslint:recommended"); | 
|             config.rules.indent = ["error", answers.indent]; | 
|             config.rules.quotes = ["error", answers.quotes]; | 
|             config.rules["linebreak-style"] = ["error", answers.linebreak]; | 
|             config.rules.semi = ["error", answers.semi ? "always" : "never"]; | 
|         } else if (answers.source === "auto") { | 
|             config = configureRules(answers, config); | 
|             config = autoconfig.extendFromRecommended(config); | 
|         } | 
|     } | 
|     if (answers.typescript && config.extends.includes("eslint:recommended")) { | 
|         config.extends.push("plugin:@typescript-eslint/recommended"); | 
|     } | 
|   | 
|     // normalize extends | 
|     if (config.extends.length === 0) { | 
|         delete config.extends; | 
|     } else if (config.extends.length === 1) { | 
|         config.extends = config.extends[0]; | 
|     } | 
|   | 
|     ConfigOps.normalizeToStrings(config); | 
|     return config; | 
| } | 
|   | 
| /** | 
|  * Get the version of the local ESLint. | 
|  * @returns {string|null} The version. If the local ESLint was not found, returns null. | 
|  */ | 
| function getLocalESLintVersion() { | 
|     try { | 
|         const eslintPath = ModuleResolver.resolve("eslint", path.join(process.cwd(), "__placeholder__.js")); | 
|         const eslint = require(eslintPath); | 
|   | 
|         return eslint.linter.version || null; | 
|     } catch { | 
|         return null; | 
|     } | 
| } | 
|   | 
| /** | 
|  * Get the shareable config name of the chosen style guide. | 
|  * @param {Object} answers The answers object. | 
|  * @returns {string} The shareable config name. | 
|  */ | 
| function getStyleGuideName(answers) { | 
|     if (answers.styleguide === "airbnb" && answers.framework !== "react") { | 
|         return "airbnb-base"; | 
|     } | 
|     return answers.styleguide; | 
| } | 
|   | 
| /** | 
|  * Check whether the local ESLint version conflicts with the required version of the chosen shareable config. | 
|  * @param {Object} answers The answers object. | 
|  * @returns {boolean} `true` if the local ESLint is found then it conflicts with the required version of the chosen shareable config. | 
|  */ | 
| function hasESLintVersionConflict(answers) { | 
|   | 
|     // Get the local ESLint version. | 
|     const localESLintVersion = getLocalESLintVersion(); | 
|   | 
|     if (!localESLintVersion) { | 
|         return false; | 
|     } | 
|   | 
|     // Get the required range of ESLint version. | 
|     const configName = getStyleGuideName(answers); | 
|     const moduleName = `eslint-config-${configName}@latest`; | 
|     const peerDependencies = getPeerDependencies(moduleName) || {}; | 
|     const requiredESLintVersionRange = peerDependencies.eslint; | 
|   | 
|     if (!requiredESLintVersionRange) { | 
|         return false; | 
|     } | 
|   | 
|     answers.localESLintVersion = localESLintVersion; | 
|     answers.requiredESLintVersionRange = requiredESLintVersionRange; | 
|   | 
|     // Check the version. | 
|     if (semver.satisfies(localESLintVersion, requiredESLintVersionRange)) { | 
|         answers.installESLint = false; | 
|         return false; | 
|     } | 
|   | 
|     return true; | 
| } | 
|   | 
| /** | 
|  * Install modules. | 
|  * @param   {string[]} modules Modules to be installed. | 
|  * @returns {void} | 
|  */ | 
| function installModules(modules) { | 
|     log.info(`Installing ${modules.join(", ")}`); | 
|     npmUtils.installSyncSaveDev(modules); | 
| } | 
|   | 
| /* istanbul ignore next: no need to test enquirer */ | 
| /** | 
|  * Ask user to install modules. | 
|  * @param   {string[]} modules Array of modules to be installed. | 
|  * @param   {boolean} packageJsonExists Indicates if package.json is existed. | 
|  * @returns {Promise} Answer that indicates if user wants to install. | 
|  */ | 
| function askInstallModules(modules, packageJsonExists) { | 
|   | 
|     // If no modules, do nothing. | 
|     if (modules.length === 0) { | 
|         return Promise.resolve(); | 
|     } | 
|   | 
|     log.info("The config that you've selected requires the following dependencies:\n"); | 
|     log.info(modules.join(" ")); | 
|     return enquirer.prompt([ | 
|         { | 
|             type: "toggle", | 
|             name: "executeInstallation", | 
|             message: "Would you like to install them now with npm?", | 
|             enabled: "Yes", | 
|             disabled: "No", | 
|             initial: 1, | 
|             skip() { | 
|                 return !(modules.length && packageJsonExists); | 
|             }, | 
|             result(input) { | 
|                 return this.skipped ? null : input; | 
|             } | 
|         } | 
|     ]).then(({ executeInstallation }) => { | 
|         if (executeInstallation) { | 
|             installModules(modules); | 
|         } | 
|     }); | 
| } | 
|   | 
| /* istanbul ignore next: no need to test enquirer */ | 
| /** | 
|  * Ask use a few questions on command prompt | 
|  * @returns {Promise} The promise with the result of the prompt | 
|  */ | 
| function promptUser() { | 
|   | 
|     return enquirer.prompt([ | 
|         { | 
|             type: "select", | 
|             name: "purpose", | 
|             message: "How would you like to use ESLint?", | 
|   | 
|             // The returned number matches the name value of nth in the choices array. | 
|             initial: 1, | 
|             choices: [ | 
|                 { message: "To check syntax only", name: "syntax" }, | 
|                 { message: "To check syntax and find problems", name: "problems" }, | 
|                 { message: "To check syntax, find problems, and enforce code style", name: "style" } | 
|             ] | 
|         }, | 
|         { | 
|             type: "select", | 
|             name: "moduleType", | 
|             message: "What type of modules does your project use?", | 
|             initial: 0, | 
|             choices: [ | 
|                 { message: "JavaScript modules (import/export)", name: "esm" }, | 
|                 { message: "CommonJS (require/exports)", name: "commonjs" }, | 
|                 { message: "None of these", name: "none" } | 
|             ] | 
|         }, | 
|         { | 
|             type: "select", | 
|             name: "framework", | 
|             message: "Which framework does your project use?", | 
|             initial: 0, | 
|             choices: [ | 
|                 { message: "React", name: "react" }, | 
|                 { message: "Vue.js", name: "vue" }, | 
|                 { message: "None of these", name: "none" } | 
|             ] | 
|         }, | 
|         { | 
|             type: "toggle", | 
|             name: "typescript", | 
|             message: "Does your project use TypeScript?", | 
|             enabled: "Yes", | 
|             disabled: "No", | 
|             initial: 0 | 
|         }, | 
|         { | 
|             type: "multiselect", | 
|             name: "env", | 
|             message: "Where does your code run?", | 
|             hint: "(Press <space> to select, <a> to toggle all, <i> to invert selection)", | 
|             initial: 0, | 
|             choices: [ | 
|                 { message: "Browser", name: "browser" }, | 
|                 { message: "Node", name: "node" } | 
|             ] | 
|         }, | 
|         { | 
|             type: "select", | 
|             name: "source", | 
|             message: "How would you like to define a style for your project?", | 
|             choices: [ | 
|                 { message: "Use a popular style guide", name: "guide" }, | 
|                 { message: "Answer questions about your style", name: "prompt" }, | 
|                 { message: "Inspect your JavaScript file(s)", name: "auto" } | 
|             ], | 
|             skip() { | 
|                 return this.state.answers.purpose !== "style"; | 
|             }, | 
|             result(input) { | 
|                 return this.skipped ? null : input; | 
|             } | 
|         }, | 
|         { | 
|             type: "select", | 
|             name: "styleguide", | 
|             message: "Which style guide do you want to follow?", | 
|             choices: [ | 
|                 { message: "Airbnb: https://github.com/airbnb/javascript", name: "airbnb" }, | 
|                 { message: "Standard: https://github.com/standard/standard", name: "standard" }, | 
|                 { message: "Google: https://github.com/google/eslint-config-google", name: "google" } | 
|             ], | 
|             skip() { | 
|                 this.state.answers.packageJsonExists = npmUtils.checkPackageJson(); | 
|                 return !(this.state.answers.source === "guide" && this.state.answers.packageJsonExists); | 
|             }, | 
|             result(input) { | 
|                 return this.skipped ? null : input; | 
|             } | 
|         }, | 
|         { | 
|             type: "input", | 
|             name: "patterns", | 
|             message: "Which file(s), path(s), or glob(s) should be examined?", | 
|             skip() { | 
|                 return this.state.answers.source !== "auto"; | 
|             }, | 
|             validate(input) { | 
|                 if (!this.skipped && input.trim().length === 0 && input.trim() !== ",") { | 
|                     return "You must tell us what code to examine. Try again."; | 
|                 } | 
|                 return true; | 
|             } | 
|         }, | 
|         { | 
|             type: "select", | 
|             name: "format", | 
|             message: "What format do you want your config file to be in?", | 
|             initial: 0, | 
|             choices: ["JavaScript", "YAML", "JSON"] | 
|         }, | 
|         { | 
|             type: "toggle", | 
|             name: "installESLint", | 
|             message(answers) { | 
|                 const verb = semver.ltr(answers.localESLintVersion, answers.requiredESLintVersionRange) | 
|                     ? "upgrade" | 
|                     : "downgrade"; | 
|   | 
|                 return `The style guide "${answers.styleguide}" requires eslint@${answers.requiredESLintVersionRange}. You are currently using eslint@${answers.localESLintVersion}.\n  Do you want to ${verb}?`; | 
|             }, | 
|             enabled: "Yes", | 
|             disabled: "No", | 
|             initial: 1, | 
|             skip() { | 
|                 return !(this.state.answers.source === "guide" && this.state.answers.packageJsonExists && hasESLintVersionConflict(this.state.answers)); | 
|             }, | 
|             result(input) { | 
|                 return this.skipped ? null : input; | 
|             } | 
|         } | 
|     ]).then(earlyAnswers => { | 
|   | 
|         // early exit if no style guide is necessary | 
|         if (earlyAnswers.purpose !== "style") { | 
|             const config = processAnswers(earlyAnswers); | 
|             const modules = getModulesList(config); | 
|   | 
|             return askInstallModules(modules, earlyAnswers.packageJsonExists) | 
|                 .then(() => writeFile(config, earlyAnswers.format)); | 
|         } | 
|   | 
|         // early exit if you are using a style guide | 
|         if (earlyAnswers.source === "guide") { | 
|             if (!earlyAnswers.packageJsonExists) { | 
|                 log.info("A package.json is necessary to install plugins such as style guides. Run `npm init` to create a package.json file and try again."); | 
|                 return void 0; | 
|             } | 
|             if (earlyAnswers.installESLint === false && !semver.satisfies(earlyAnswers.localESLintVersion, earlyAnswers.requiredESLintVersionRange)) { | 
|                 log.info(`Note: it might not work since ESLint's version is mismatched with the ${earlyAnswers.styleguide} config.`); | 
|             } | 
|             if (earlyAnswers.styleguide === "airbnb" && earlyAnswers.framework !== "react") { | 
|                 earlyAnswers.styleguide = "airbnb-base"; | 
|             } | 
|   | 
|             const config = processAnswers(earlyAnswers); | 
|   | 
|             if (Array.isArray(config.extends)) { | 
|                 config.extends.push(earlyAnswers.styleguide); | 
|             } else if (config.extends) { | 
|                 config.extends = [config.extends, earlyAnswers.styleguide]; | 
|             } else { | 
|                 config.extends = [earlyAnswers.styleguide]; | 
|             } | 
|   | 
|             const modules = getModulesList(config); | 
|   | 
|             return askInstallModules(modules, earlyAnswers.packageJsonExists) | 
|                 .then(() => writeFile(config, earlyAnswers.format)); | 
|   | 
|         } | 
|   | 
|         if (earlyAnswers.source === "auto") { | 
|             const combinedAnswers = Object.assign({}, earlyAnswers); | 
|             const config = processAnswers(combinedAnswers); | 
|             const modules = getModulesList(config); | 
|   | 
|             return askInstallModules(modules).then(() => writeFile(config, earlyAnswers.format)); | 
|         } | 
|   | 
|         // continue with the style questions otherwise... | 
|         return enquirer.prompt([ | 
|             { | 
|                 type: "select", | 
|                 name: "indent", | 
|                 message: "What style of indentation do you use?", | 
|                 initial: 0, | 
|                 choices: [{ message: "Tabs", name: "tab" }, { message: "Spaces", name: 4 }] | 
|             }, | 
|             { | 
|                 type: "select", | 
|                 name: "quotes", | 
|                 message: "What quotes do you use for strings?", | 
|                 initial: 0, | 
|                 choices: [{ message: "Double", name: "double" }, { message: "Single", name: "single" }] | 
|             }, | 
|             { | 
|                 type: "select", | 
|                 name: "linebreak", | 
|                 message: "What line endings do you use?", | 
|                 initial: 0, | 
|                 choices: [{ message: "Unix", name: "unix" }, { message: "Windows", name: "windows" }] | 
|             }, | 
|             { | 
|                 type: "toggle", | 
|                 name: "semi", | 
|                 message: "Do you require semicolons?", | 
|                 enabled: "Yes", | 
|                 disabled: "No", | 
|                 initial: 1 | 
|             } | 
|         ]).then(answers => { | 
|             const totalAnswers = Object.assign({}, earlyAnswers, answers); | 
|   | 
|             const config = processAnswers(totalAnswers); | 
|             const modules = getModulesList(config); | 
|   | 
|             return askInstallModules(modules).then(() => writeFile(config, earlyAnswers.format)); | 
|         }); | 
|     }); | 
| } | 
|   | 
| //------------------------------------------------------------------------------ | 
| // Public Interface | 
| //------------------------------------------------------------------------------ | 
|   | 
| const init = { | 
|     getModulesList, | 
|     hasESLintVersionConflict, | 
|     installModules, | 
|     processAnswers, | 
|     /* istanbul ignore next */initializeConfig() { | 
|         return promptUser(); | 
|     } | 
| }; | 
|   | 
| module.exports = init; |