1#!/usr/bin/env node 2 3/** 4 * @fileoverview Main CLI that is run via the eslint command. 5 * @author Nicholas C. Zakas 6 */ 7 8/* eslint no-console:off */ 9 10"use strict"; 11 12// to use V8's code cache to speed up instantiation time 13require("v8-compile-cache"); 14 15// must do this initialization *before* other requires in order to work 16if (process.argv.includes("--debug")) { 17 require("debug").enable("eslint:*,-eslint:code-path"); 18} 19 20//------------------------------------------------------------------------------ 21// Helpers 22//------------------------------------------------------------------------------ 23 24/** 25 * Read data from stdin til the end. 26 * 27 * Note: See 28 * - https://github.com/nodejs/node/blob/master/doc/api/process.md#processstdin 29 * - https://github.com/nodejs/node/blob/master/doc/api/process.md#a-note-on-process-io 30 * - https://lists.gnu.org/archive/html/bug-gnu-emacs/2016-01/msg00419.html 31 * - https://github.com/nodejs/node/issues/7439 (historical) 32 * 33 * On Windows using `fs.readFileSync(STDIN_FILE_DESCRIPTOR, "utf8")` seems 34 * to read 4096 bytes before blocking and never drains to read further data. 35 * 36 * The investigation on the Emacs thread indicates: 37 * 38 * > Emacs on MS-Windows uses pipes to communicate with subprocesses; a 39 * > pipe on Windows has a 4K buffer. So as soon as Emacs writes more than 40 * > 4096 bytes to the pipe, the pipe becomes full, and Emacs then waits for 41 * > the subprocess to read its end of the pipe, at which time Emacs will 42 * > write the rest of the stuff. 43 * @returns {Promise<string>} The read text. 44 */ 45function readStdin() { 46 return new Promise((resolve, reject) => { 47 let content = ""; 48 let chunk = ""; 49 50 process.stdin 51 .setEncoding("utf8") 52 .on("readable", () => { 53 while ((chunk = process.stdin.read()) !== null) { 54 content += chunk; 55 } 56 }) 57 .on("end", () => resolve(content)) 58 .on("error", reject); 59 }); 60} 61 62/** 63 * Get the error message of a given value. 64 * @param {any} error The value to get. 65 * @returns {string} The error message. 66 */ 67function getErrorMessage(error) { 68 69 // Lazy loading because those are used only if error happened. 70 const fs = require("fs"); 71 const path = require("path"); 72 const util = require("util"); 73 const lodash = require("lodash"); 74 75 // Foolproof -- thirdparty module might throw non-object. 76 if (typeof error !== "object" || error === null) { 77 return String(error); 78 } 79 80 // Use templates if `error.messageTemplate` is present. 81 if (typeof error.messageTemplate === "string") { 82 try { 83 const templateFilePath = path.resolve( 84 __dirname, 85 `../messages/${error.messageTemplate}.txt` 86 ); 87 88 // Use sync API because Node.js should exit at this tick. 89 const templateText = fs.readFileSync(templateFilePath, "utf-8"); 90 const template = lodash.template(templateText); 91 92 return template(error.messageData || {}); 93 } catch { 94 95 // Ignore template error then fallback to use `error.stack`. 96 } 97 } 98 99 // Use the stacktrace if it's an error object. 100 if (typeof error.stack === "string") { 101 return error.stack; 102 } 103 104 // Otherwise, dump the object. 105 return util.format("%o", error); 106} 107 108/** 109 * Catch and report unexpected error. 110 * @param {any} error The thrown error object. 111 * @returns {void} 112 */ 113function onFatalError(error) { 114 process.exitCode = 2; 115 116 const { version } = require("../package.json"); 117 const message = getErrorMessage(error); 118 119 console.error(` 120Oops! Something went wrong! :( 121 122ESLint: ${version} 123 124${message}`); 125} 126 127//------------------------------------------------------------------------------ 128// Execution 129//------------------------------------------------------------------------------ 130 131(async function main() { 132 process.on("uncaughtException", onFatalError); 133 process.on("unhandledRejection", onFatalError); 134 135 // Call the config initializer if `--init` is present. 136 if (process.argv.includes("--init")) { 137 await require("../lib/init/config-initializer").initializeConfig(); 138 return; 139 } 140 141 // Otherwise, call the CLI. 142 process.exitCode = await require("../lib/cli").execute( 143 process.argv, 144 process.argv.includes("--stdin") ? await readStdin() : null 145 ); 146}()).catch(onFatalError); 147