Skip to content

Commit

Permalink
New: add --env-info flag to CLI (#12270)
Browse files Browse the repository at this point in the history
  • Loading branch information
kaicataldo committed Sep 25, 2019
1 parent 61392ff commit a7894eb
Show file tree
Hide file tree
Showing 7 changed files with 417 additions and 27 deletions.
13 changes: 6 additions & 7 deletions bin/eslint.js
Expand Up @@ -16,9 +16,9 @@ require("v8-compile-cache");
// Helpers
//------------------------------------------------------------------------------

const useStdIn = (process.argv.indexOf("--stdin") > -1),
init = (process.argv.indexOf("--init") > -1),
debug = (process.argv.indexOf("--debug") > -1);
const useStdIn = process.argv.includes("--stdin"),
init = process.argv.includes("--init"),
debug = process.argv.includes("--debug");

// must do this initialization *before* other requires in order to work
if (debug) {
Expand All @@ -30,9 +30,9 @@ if (debug) {
//------------------------------------------------------------------------------

// now we can safely include the other modules that use debug
const cli = require("../lib/cli"),
path = require("path"),
fs = require("fs");
const path = require("path"),
fs = require("fs"),
cli = require("../lib/cli");

//------------------------------------------------------------------------------
// Execution
Expand All @@ -50,7 +50,6 @@ process.once("uncaughtException", err => {
console.error("\nOops! Something went wrong! :(");
console.error(`\nESLint: ${pkg.version}.\n\n${template(err.messageData || {})}`);
} else {

console.error(err.stack);
}

Expand Down
5 changes: 5 additions & 0 deletions docs/user-guide/command-line-interface.md
Expand Up @@ -78,6 +78,7 @@ Caching:
Miscellaneous:
--init Run config initialization wizard - default: false
--env-info Output execution environment information - default: false
--debug Output debugging information
-h, --help Show help
-v, --version Output the version number
Expand Down Expand Up @@ -446,6 +447,10 @@ This option will start config initialization wizard. It's designed to help new u

The resulting configuration file will be created in the current directory.

#### `--env-info`

This option outputs information about the execution environment, including the version of Node, npm, and local and global installations of ESLint. The ESLint team may ask for this information to help solve bugs.

#### `--debug`

This option outputs debugging information to the console. This information is useful when you're seeing a problem and having a hard time pinpointing it. The ESLint team may ask for this debugging information to help solve bugs.
Expand Down
25 changes: 13 additions & 12 deletions lib/cli.js
Expand Up @@ -20,7 +20,8 @@ const fs = require("fs"),
mkdirp = require("mkdirp"),
{ CLIEngine } = require("./cli-engine"),
options = require("./options"),
log = require("./shared/logging");
log = require("./shared/logging"),
RuntimeInfo = require("./shared/runtime-info");

const debug = require("debug")("eslint:cli");

Expand Down Expand Up @@ -159,13 +160,18 @@ const cli = {
}

const files = currentOptions._;

const useStdin = typeof text === "string";

if (currentOptions.version) { // version from package.json

log.info(`v${require("../package.json").version}`);

if (currentOptions.version) {
log.info(RuntimeInfo.version());
} else if (currentOptions.envInfo) {
try {
log.info(RuntimeInfo.environment());
return 0;
} catch (err) {
log.error(err.message);
return 2;
}
} else if (currentOptions.printConfig) {
if (files.length) {
log.error("The --print-config option must be used with exactly one file name.");
Expand All @@ -177,17 +183,13 @@ const cli = {
}

const engine = new CLIEngine(translateOptions(currentOptions));

const fileConfig = engine.getConfigForFile(currentOptions.printConfig);

log.info(JSON.stringify(fileConfig, null, " "));
return 0;
} else if (currentOptions.help || (!files.length && !useStdin)) {

log.info(options.generateHelp());

} else {

debug(`Running on ${useStdin ? "text" : "files"}`);

if (currentOptions.fix && currentOptions.fixDryRun) {
Expand Down Expand Up @@ -227,9 +229,8 @@ const cli = {

return (report.errorCount || tooManyWarnings) ? 1 : 0;
}
return 2;


return 2;
}

return 0;
Expand Down
6 changes: 6 additions & 0 deletions lib/options.js
Expand Up @@ -224,6 +224,12 @@ module.exports = optionator({
default: "false",
description: "Run config initialization wizard"
},
{
option: "env-info",
type: "Boolean",
default: "false",
description: "Output execution environment information"
},
{
option: "debug",
type: "Boolean",
Expand Down
152 changes: 152 additions & 0 deletions lib/shared/runtime-info.js
@@ -0,0 +1,152 @@
/**
* @fileoverview Utility to get information about the execution environment.
* @author Kai Cataldo
*/

"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const path = require("path");
const spawn = require("cross-spawn");
const { isEmpty } = require("lodash");
const log = require("../shared/logging");
const packageJson = require("../../package.json");

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

/**
* Checks if a path is a child of a directory.
* @param {string} parentPath - The parent path to check.
* @param {string} childPath - The path to check.
* @returns {boolean} Whether or not the given path is a child of a directory.
*/
function isChildOfDirectory(parentPath, childPath) {
return !path.relative(parentPath, childPath).startsWith("..");
}

/**
* Synchronously executes a shell command and formats the result.
* @param {string} cmd - The command to execute.
* @param {Array} args - The arguments to be executed with the command.
* @returns {string} The version returned by the command.
*/
function execCommand(cmd, args) {
const process = spawn.sync(cmd, args, { encoding: "utf8" });

if (process.error) {
throw process.error;
}

return process.stdout.trim();
}

/**
* Normalizes a version number.
* @param {string} versionStr - The string to normalize.
* @returns {string} The normalized version number.
*/
function normalizeVersionStr(versionStr) {
return versionStr.startsWith("v") ? versionStr : `v${versionStr}`;
}

/**
* Gets bin version.
* @param {string} bin - The bin to check.
* @returns {string} The normalized version returned by the command.
*/
function getBinVersion(bin) {
const binArgs = ["--version"];

try {
return normalizeVersionStr(execCommand(bin, binArgs));
} catch (e) {
log.error(`Error finding ${bin} version running the command \`${bin} ${binArgs.join(" ")}\``);
throw e;
}
}

/**
* Gets installed npm package version.
* @param {string} pkg - The package to check.
* @param {boolean} global - Whether to check globally or not.
* @returns {string} The normalized version returned by the command.
*/
function getNpmPackageVersion(pkg, { global = false } = {}) {
const npmBinArgs = ["bin", "-g"];
const npmLsArgs = ["ls", "--depth=0", "--json", "eslint"];

if (global) {
npmLsArgs.push("-g");
}

try {
const parsedStdout = JSON.parse(execCommand("npm", npmLsArgs));

/*
* Checking globally returns an empty JSON object, while local checks
* include the name and version of the local project.
*/
if (isEmpty(parsedStdout) || !(parsedStdout.dependencies && parsedStdout.dependencies.eslint)) {
return "Not found";
}

const [, processBinPath] = process.argv;
let npmBinPath;

try {
npmBinPath = execCommand("npm", npmBinArgs);
} catch (e) {
log.error(`Error finding npm binary path when running command \`npm ${npmBinArgs.join(" ")}\``);
throw e;
}

const isGlobal = isChildOfDirectory(npmBinPath, processBinPath);
let pkgVersion = parsedStdout.dependencies.eslint.version;

if ((global && isGlobal) || (!global && !isGlobal)) {
pkgVersion += " (Currently used)";
}

return normalizeVersionStr(pkgVersion);
} catch (e) {
log.error(`Error finding ${pkg} version running the command \`npm ${npmLsArgs.join(" ")}\``);
throw e;
}
}

/**
* Generates and returns execution environment information.
* @returns {string} A string that contains execution environment information.
*/
function environment() {
return [
"Environment Info:",
"",
`Node version: ${getBinVersion("node")}`,
`npm version: ${getBinVersion("npm")}`,
`Local ESLint version: ${getNpmPackageVersion("eslint", { global: false })}`,
`Global ESLint version: ${getNpmPackageVersion("eslint", { global: true })}`
].join("\n");
}

/**
* Returns version of currently executing ESLint.
* @returns {string} The version from the currently executing ESLint's package.json.
*/
function version() {
return `v${packageJson.version}`;
}

//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------

module.exports = {
environment,
version
};
27 changes: 19 additions & 8 deletions tests/lib/cli.js
Expand Up @@ -29,14 +29,18 @@ const proxyquire = require("proxyquire").noCallThru().noPreserveCache();
//------------------------------------------------------------------------------

describe("cli", () => {

let fixtureDir;
const log = {
info: sinon.spy(),
error: sinon.spy()
};
const RuntimeInfo = {
environment: sinon.stub(),
version: sinon.stub()
};
const cli = proxyquire("../../lib/cli", {
"./shared/logging": log
"./shared/logging": log,
"./shared/runtime-info": RuntimeInfo
});

/**
Expand Down Expand Up @@ -324,15 +328,27 @@ describe("cli", () => {
describe("when executing with version flag", () => {
it("should print out current version", () => {
assert.strictEqual(cli.execute("-v"), 0);
assert.strictEqual(log.info.callCount, 1);
});
});

describe("when executing with env-info flag", () => {
it("should print out environment information", () => {
assert.strictEqual(cli.execute("--env-info"), 0);
assert.strictEqual(log.info.callCount, 1);
});

it("should print error message and return error code", () => {
RuntimeInfo.environment.throws("There was an error!");

assert.strictEqual(cli.execute("--env-info"), 2);
assert.strictEqual(log.error.callCount, 1);
});
});

describe("when executing with help flag", () => {
it("should print out help", () => {
assert.strictEqual(cli.execute("-h"), 0);

assert.strictEqual(log.info.callCount, 1);
});
});
Expand All @@ -349,7 +365,6 @@ describe("cli", () => {
});

describe("when given a file in excluded files list", () => {

it("should not process the file", () => {
const ignorePath = getFixturePath(".eslintignore");
const filePath = getFixturePath("passing.js");
Expand Down Expand Up @@ -398,7 +413,6 @@ describe("cli", () => {
});

describe("when executing a file with a shebang", () => {

it("should execute without error", () => {
const filePath = getFixturePath("shebang.js");
const exit = cli.execute(`--no-ignore ${filePath}`);
Expand All @@ -408,7 +422,6 @@ describe("cli", () => {
});

describe("when loading a custom rule", () => {

it("should return an error when rule isn't found", () => {
const rulesPath = getFixturePath("rules", "wrong");
const configPath = getFixturePath("rules", "eslint.json");
Expand Down Expand Up @@ -551,7 +564,6 @@ describe("cli", () => {
});

describe("when supplied with report output file path", () => {

afterEach(() => {
sh.rm("-rf", "tests/output");
});
Expand Down Expand Up @@ -594,7 +606,6 @@ describe("cli", () => {
});

describe("when supplied with a plugin", () => {

it("should pass plugins to CLIEngine", () => {
const examplePluginName = "eslint-plugin-example";

Expand Down

0 comments on commit a7894eb

Please sign in to comment.