Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New: add --env-info flag to CLI #12270

Merged
merged 10 commits into from Sep 25, 2019
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");
kaicataldo marked this conversation as resolved.
Show resolved Hide resolved
kaicataldo marked this conversation as resolved.
Show resolved Hide resolved

//------------------------------------------------------------------------------
// 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}`;
kaicataldo marked this conversation as resolved.
Show resolved Hide resolved
}

//------------------------------------------------------------------------------
// 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