diff --git a/README.md b/README.md index 476a2ba909..15c0ec3ce6 100644 --- a/README.md +++ b/README.md @@ -523,3 +523,9 @@ $ lerna publish --only-explicit-updates ``` Ex: in Babel, `babel-types` is depended upon by all packages in the monorepo (over 100). However, Babel uses `^` for most of it's dependencies so it isn't necessary to bump the versions of all packages if only `babel-types` is updated. This option allows only the packages that have been explicitly updated to make a new version. + +#### --loglevel [silent|error|warn|success|info|verbose|silly] + +What level of logs to report. On failure, all logs are written to lerna-debug.log in the current working directory. + +Any logs of a higher level than the setting are shown. The default is "info". diff --git a/bin/lerna.js b/bin/lerna.js index fe26d8e2e7..bc721b2998 100755 --- a/bin/lerna.js +++ b/bin/lerna.js @@ -1,6 +1,7 @@ #!/usr/bin/env node var lerna = require("../lib/index"); +var logger = require("../lib/logger"); var chalk = require("chalk"); var meow = require("meow"); @@ -31,7 +32,8 @@ var cli = meow([ " --force-publish Force publish for the specified packages (comma-separated) or all packages using * (skips the git diff check for changed packages)", " --yes Skip all confirmation prompts", " --repo-version Specify repo version to publish", - " --concurrency How many threads to use if lerna parallelises the tasks (defaults to 4)" + " --concurrency How many threads to use if lerna parallelises the tasks (defaults to 4)", + " --loglevel What level of logs to report (defaults to \"info\"). On failure, all logs are written to lerna-debug.log in the current working directory.", ], { alias: { independent: "i", @@ -42,6 +44,8 @@ var cli = meow([ require("signal-exit").unload(); +logger.setLogLevel(cli.flags.loglevel); + var commandName = cli.input[0]; var Command = lerna.__commands__[commandName]; diff --git a/src/Command.js b/src/Command.js index e7700cde78..c370219569 100644 --- a/src/Command.js +++ b/src/Command.js @@ -34,31 +34,31 @@ export default class Command { runValidations() { if (this.concurrency < 1) { - this.logger.warning("command must be run with at least one thread."); + this.logger.warn("command must be run with at least one thread."); this._complete(null, 1); return; } if (!FileSystemUtilities.existsSync(this.repository.packagesLocation)) { - this.logger.warning("`packages/` directory does not exist, have you run `lerna init`?"); + this.logger.warn("`packages/` directory does not exist, have you run `lerna init`?"); this._complete(null, 1); return; } if (!FileSystemUtilities.existsSync(this.repository.packageJsonLocation)) { - this.logger.warning("`package.json` does not exist, have you run `lerna init`?"); + this.logger.warn("`package.json` does not exist, have you run `lerna init`?"); this._complete(null, 1); return; } if (!FileSystemUtilities.existsSync(this.repository.lernaJsonLocation)) { - this.logger.warning("`lerna.json` does not exist, have you run `lerna init`?"); + this.logger.warn("`lerna.json` does not exist, have you run `lerna init`?"); this._complete(null, 1); return; } if (this.flags.independent && !this.repository.isIndependent()) { - this.logger.warning( + this.logger.warn( "You ran lerna with `--independent` or `-i`, but the repository is not set to independent mode. " + "To use independent mode you need to set your `lerna.json` \"version\" to \"independent\". " + "Then you won't need to pass the `--independent` or `-i` flags." @@ -71,7 +71,7 @@ export default class Command { process.env.NODE_ENV !== "test" && this.lernaVersion !== this.repository.lernaVersion ) { - this.logger.warning( + this.logger.warn( `Lerna version mismatch: The current version of lerna is ${this.lernaVersion}, ` + `but the Lerna version in \`lerna.json\` is ${this.repository.lernaVersion}. ` + `You can either run \`lerna init\` again or install \`lerna@${this.repository.lernaVersion}\`.` @@ -81,19 +81,19 @@ export default class Command { } if (FileSystemUtilities.existsSync(this.repository.versionLocation)) { - this.logger.warning("You have a `VERSION` file in your repository, this is leftover from a previous "); + this.logger.warn("You have a `VERSION` file in your repository, this is leftover from a previous "); this._complete(null, 1); return; } if (process.env.NPM_DIST_TAG !== undefined) { - this.logger.warning("`NPM_DIST_TAG=[tagname] lerna publish` is deprecated, please use `lerna publish --tag [tagname]` instead."); + this.logger.warn("`NPM_DIST_TAG=[tagname] lerna publish` is deprecated, please use `lerna publish --tag [tagname]` instead."); this._complete(null, 1); return; } if (process.env.FORCE_VERSION !== undefined) { - this.logger.warning("`FORCE_VERSION=[package/*] lerna updated/publish` is deprecated, please use `lerna updated/publish --force-publish [package/*]` instead."); + this.logger.warn("`FORCE_VERSION=[package/*] lerna updated/publish` is deprecated, please use `lerna updated/publish --force-publish [package/*]` instead."); this._complete(null, 1); return; } @@ -122,17 +122,17 @@ export default class Command { const methodName = `${this.constructor.name}.${method}`; try { - this.logger.debug(`Attempting running ${methodName}`); + this.logger.verbose(`Attempting running ${methodName}`); this[method]((err, completed) => { if (err) { this.logger.error(`Errored while running ${methodName}`, err); this._complete(err, 1, callback); } else if (!completed) { - this.logger.debug(`Exited early while running ${methodName}`); + this.logger.verbose(`Exited early while running ${methodName}`); this._complete(null, 1, callback); } else { - this.logger.debug(`Successfully ran ${methodName}`); + this.logger.verbose(`Successfully ran ${methodName}`); next(); } }); diff --git a/src/commands/BootstrapCommand.js b/src/commands/BootstrapCommand.js index cab65e1e0c..e81ac38063 100644 --- a/src/commands/BootstrapCommand.js +++ b/src/commands/BootstrapCommand.js @@ -91,7 +91,7 @@ export default class BootstrapCommand extends Command { // then we've encountered a cycle in the dependency graph. Run a // single-package batch with the package that has the most dependents. if (todoPackages.length && !batch.length) { - this.logger.warning( + this.logger.warn( "Encountered a cycle in the dependency graph. " + "This may cause instability if dependencies are used during `prepublish`." ); diff --git a/src/commands/PublishCommand.js b/src/commands/PublishCommand.js index 5fdc2445b3..d031baecde 100644 --- a/src/commands/PublishCommand.js +++ b/src/commands/PublishCommand.js @@ -334,7 +334,7 @@ export default class PublishCommand extends Command { if (FileSystemUtilities.existsSync(scriptLocation)) { require(scriptLocation); } else { - this.logger.debug(`No ${script} script found at ${scriptLocation}`); + this.logger.verbose(`No ${script} script found at ${scriptLocation}`); } } @@ -349,7 +349,7 @@ export default class PublishCommand extends Command { let attempts = 0; const run = (cb) => { - this.logger.debug("Publishing " + pkg.name + "..."); + this.logger.verbose("Publishing " + pkg.name + "..."); NpmUtilities.publishTaggedInDir("lerna-temp", pkg.location, (err) => { err = err && err.stack || err; diff --git a/src/logger.js b/src/logger.js index 4dbf75ea03..9afe892ad4 100644 --- a/src/logger.js +++ b/src/logger.js @@ -4,19 +4,39 @@ import pad from "pad"; const cwd = process.cwd(); +const DEFAULT_LOGLEVEL = "info"; + +const LEVELS = [ + [ "silly", "magenta" ], + [ "verbose", "blue" ], + [ "info", "white" ], + [ "success", "green" ], + [ "warn", "yellow" ], + [ "error", "red" ], + [ "silent", ], +]; + +const TYPE_TO_LEVEL = LEVELS + .reduce((map, [type], index) => (map[type] = index, map), {}); + class Logger { constructor() { + this.setLogLevel(); this.logs = []; } - _log(type, verbose, style, message, error) { + setLogLevel(type) { + this.loglevel = TYPE_TO_LEVEL[type || DEFAULT_LOGLEVEL]; + } + + _log(type, style, level, message, error) { this.logs.push({ type, message, error }); - if (verbose) { + if (level < this.loglevel) { return; } @@ -29,34 +49,18 @@ class Logger { } progressBar.clear(); - if (process.env.NODE_ENV !== "test") { - console.log(message); - } + this._emit(message); progressBar.restore(); } - debug(message, verbose = true) { - this._log("debug", verbose, chalk.blue, message); - } - - info(message, verbose = false) { - this._log("info", verbose, chalk.white, message); - } - - success(message, verbose = false) { - this._log("success", verbose, chalk.green, message); - } - - warning(message, verbose = false) { - this._log("warning", verbose, chalk.yellow, message); - } - - error(message, error, verbose = false) { - this._log("error", verbose, chalk.red, message, error); + _emit(message) { + if (process.env.NODE_ENV !== "test") { + console.log(message); + } } newLine() { - this.info(""); + this._emit(""); } logifyAsync(target, property, descriptor) { @@ -121,4 +125,13 @@ class Logger { } } +LEVELS.forEach(([type, color]) => { + if (!color) return; // "silent" + const style = chalk[color]; + const level = TYPE_TO_LEVEL[type]; + Logger.prototype[type] = function(message, error) { + this._log(type, style, level, message, error); + }; +}); + export default new Logger(); diff --git a/test/UpdatedCommand.js b/test/UpdatedCommand.js index 2b5daf92be..2ce6e69abe 100644 --- a/test/UpdatedCommand.js +++ b/test/UpdatedCommand.js @@ -40,9 +40,7 @@ describe("UpdatedCommand", () => { let calls = 0; stub(logger, "info", (message) => { if (calls === 0) assert.equal(message, "Checking for updated packages..."); - if (calls === 1) assert.equal(message, ""); if (calls === 2) assert.equal(message, "- package-2\n- package-3"); - if (calls === 3) assert.equal(message, ""); calls++; }); @@ -65,9 +63,7 @@ describe("UpdatedCommand", () => { let calls = 0; stub(logger, "info", (message) => { if (calls === 0) assert.equal(message, "Checking for updated packages..."); - if (calls === 1) assert.equal(message, ""); if (calls === 2) assert.equal(message, "- package-1\n- package-2\n- package-3\n- package-4"); - if (calls === 3) assert.equal(message, ""); calls++; }); @@ -90,9 +86,7 @@ describe("UpdatedCommand", () => { let calls = 0; stub(logger, "info", (message) => { if (calls === 0) assert.equal(message, "Checking for updated packages..."); - if (calls === 1) assert.equal(message, ""); if (calls === 2) assert.equal(message, "- package-2\n- package-3\n- package-4"); - if (calls === 3) assert.equal(message, ""); calls++; }); @@ -121,9 +115,7 @@ describe("UpdatedCommand", () => { let calls = 0; stub(logger, "info", (message) => { if (calls === 0) assert.equal(message, "Checking for updated packages..."); - if (calls === 1) assert.equal(message, ""); if (calls === 2) assert.equal(message, "- package-3"); - if (calls === 3) assert.equal(message, ""); calls++; }); @@ -146,9 +138,7 @@ describe("UpdatedCommand", () => { let calls = 0; stub(logger, "info", (message) => { if (calls === 0) assert.equal(message, "Checking for updated packages..."); - if (calls === 1) assert.equal(message, ""); if (calls === 2) assert.equal(message, "- package-2"); - if (calls === 3) assert.equal(message, ""); calls++; }); @@ -181,9 +171,7 @@ describe("UpdatedCommand", () => { let calls = 0; stub(logger, "info", (message) => { if (calls === 0) assert.equal(message, "Checking for updated packages..."); - if (calls === 1) assert.equal(message, ""); if (calls === 2) assert.equal(message, "- package-3\n- package-4"); - if (calls === 3) assert.equal(message, ""); calls++; }); @@ -206,9 +194,7 @@ describe("UpdatedCommand", () => { let calls = 0; stub(logger, "info", (message) => { if (calls === 0) assert.equal(message, "Checking for updated packages..."); - if (calls === 1) assert.equal(message, ""); if (calls === 2) assert.equal(message, "- package-1\n- package-2\n- package-3\n- package-4"); - if (calls === 3) assert.equal(message, ""); calls++; }); @@ -231,9 +217,7 @@ describe("UpdatedCommand", () => { let calls = 0; stub(logger, "info", (message) => { if (calls === 0) assert.equal(message, "Checking for updated packages..."); - if (calls === 1) assert.equal(message, ""); if (calls === 2) assert.equal(message, "- package-2\n- package-3\n- package-4"); - if (calls === 3) assert.equal(message, ""); calls++; }); @@ -262,9 +246,7 @@ describe("UpdatedCommand", () => { let calls = 0; stub(logger, "info", (message) => { if (calls === 0) assert.equal(message, "Checking for updated packages..."); - if (calls === 1) assert.equal(message, ""); if (calls === 2) assert.equal(message, "- package-3\n- package-4"); - if (calls === 3) assert.equal(message, ""); calls++; }); diff --git a/test/logger.js b/test/logger.js index 1a3e34a3b4..0fdfa6b19b 100644 --- a/test/logger.js +++ b/test/logger.js @@ -1,4 +1,5 @@ import assert from "assert"; +import stub from "./_stub"; import logger from "../src/logger"; @@ -6,4 +7,31 @@ describe("logger", () => { it("should exist", () => { assert.ok(logger); }); + describe("log levels", () => { + let called; + beforeEach(() => { + called = false; + stub(logger, "_emit", () => called = true); + }); + afterEach(() => logger.setLogLevel()); + + it("should suppress verbose by default", () => { + logger.verbose("test"); + assert.ok(!called); + }); + it("should emit verbose if configured", () => { + logger.setLogLevel("verbose"); + logger.verbose("test"); + assert.ok(called); + }); + it("should emit error by default", () => { + logger.error("test"); + assert.ok(called); + }); + it("should suppress error if silent", () => { + logger.setLogLevel("silent"); + logger.error("test"); + assert.ok(!called); + }); + }); });