Repository: atlassian/changesets
Pull request #182 (2019-10-05 22:37:53)

Internal logging package and a test-utils package to help them
re: #142 

Adding two packages, **@changesets/logger** and **@changesets/test-utils**.

### @changesets/logger

Log messages to stdout.

Usage:

```
import { warn } from "@changesets/logger";

warn('message part 1', 'message part 2');
```

---

### @changesets/test-utils

All test utilities, at the moment only one - temporarilySilenceLogs, it will silence all logs done in the changesets file. **It will not highjack console, therefore console.*(xxx) will still. Which in my perspective is needed to not accidentally silence the error message.

```
// In tests
import { temporarilySilenceLogs } from "@changesets/test-utils";

temporarilySilenceLogs();
```

- [x] Add a logger
   - [x] add package.
   - [x] add tests and documentation.
   - [x] change usage in the cli package.
   - [x] Method to silent the internal logging in tests.
   - [x] Document this internal logging.

--- 

This PR is in progress, but inputs at any step are welcome.

changeset-bot[bot]: ###  💥  No Changeset

Latest commit: 756c36dceba94cd7616d774941730740aae7bc92

Merging this PR will not cause any packages to be released. If these changes should not cause updates to packages in this repo, this is fine 🙂

**If these changes should be published to npm, you need to add a changeset.**

[Click here to learn what changesets are, and how to add one](https://github.com/Noviny/changesets/blob/master/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/atlassian/changesets/new/issue/142-logger?filename=.changeset/olive-hornets-march.md)


mitchellhamilton: Also: needs a changeset
ajaymathur: The commit history is not pretty at the moment. I feel some of the messages can be improved and some commits be squashed. I am spend time in fixing the messages. If we do a merge. Else we can update message on squash and commit. 🙂 
ajaymathur: Hey @mitchellhamilton, Thanks for reviewing the PR. Do you think we can merge it?
mitchellhamilton: I was going to wait for @Noviny to ✅ it but it's not really a change that's likely to cause problems so let's merge it.

Patch:
diff --git a/.changeset/hip-mirrors-unite.md b/.changeset/hip-mirrors-unite.md
new file mode 100644
index 0000000..605fae4
--- /dev/null
+++ b/.changeset/hip-mirrors-unite.md
@@ -0,0 +1,6 @@
+---
+"@changesets/logger": patch
+"@changesets/test-utils": patch
+---
+
+- Initial release of the package.
diff --git a/.changeset/smart-teachers-brake.md b/.changeset/smart-teachers-brake.md
new file mode 100644
index 0000000..93f09fd
--- /dev/null
+++ b/.changeset/smart-teachers-brake.md
@@ -0,0 +1,5 @@
+---
+"@changesets/cli": minor
+---
+
+Updated the package to use the new `@changesets/logger` for logging.
diff --git a/package.json b/package.json
index 67bd5fa..f6473b4 100644
--- a/package.json
+++ b/package.json
@@ -40,6 +40,7 @@
     "@types/js-yaml": "^3.12.1",
     "@types/lodash": "^4.14.136",
     "@types/meow": "^5.0.0",
+    "@types/node": "^12.7.11",
     "@types/prettier": "^1.18.0",
     "@types/semver": "^6.0.0",
     "@typescript-eslint/eslint-plugin": "^1.9.0",
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 0100062..fb113ef 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -29,6 +29,7 @@
     "@changesets/config": "^0.2.1",
     "@changesets/get-release-plan": "^0.1.3",
     "@changesets/git": "^0.2.3",
+    "@changesets/logger": "^0.0.0",
     "@changesets/read": "^0.3.0",
     "@changesets/types": "^0.3.0",
     "@types/semver": "^6.0.0",
@@ -57,6 +58,7 @@
   },
   "devDependencies": {
     "@changesets/parse": "^0.3.0",
+    "@changesets/test-utils": "^0.0.0",
     "jest-fixtures": "^0.5.0",
     "strip-ansi": "^5.2.0"
   }
diff --git a/packages/cli/src/commands/add/createChangeset.ts b/packages/cli/src/commands/add/createChangeset.ts
index c1f93d4..62c49b7 100644
--- a/packages/cli/src/commands/add/createChangeset.ts
+++ b/packages/cli/src/commands/add/createChangeset.ts
@@ -5,8 +5,8 @@ import chalk from "chalk";
 import semver from "semver";
 
 import * as cli from "../../utils/cli";
-import logger from "../../utils/logger";
 import getWorkspaces from "../../utils/getWorkspaces";
+import { error, log } from "@changesets/logger";
 import { Release, Workspace } from "@changesets/types";
 
 const { green, yellow, red, bold, blue, cyan } = chalk;
@@ -55,8 +55,8 @@ async function getPackagesToRelease(
 
     if (packagesToRelease.length === 0) {
       do {
-        logger.error("You must select at least one package to release");
-        logger.error("(You most likely hit enter instead of space!)");
+        error("You must select at least one package to release");
+        error("(You most likely hit enter instead of space!)");
 
         packagesToRelease = await askInitialReleaseQuestion(defaultChoiceList);
       } while (packagesToRelease.length === 0);
@@ -111,8 +111,8 @@ export default async function createChangeset(
     let { version } = pkgJsonsByName.get(pkgName)!;
     if (semver.lt(version, "1.0.0")) {
       // prettier-ignore
-      logger.log(yellow(`WARNING: Releasing a major version for ${green(pkgName)} will be its ${red('first major release')}.`))
-      logger.log(
+      log(yellow(`WARNING: Releasing a major version for ${green(pkgName)} will be its ${red('first major release')}.`))
+      log(
         yellow(
           `If you are unsure if this is correct, contact the package's maintainers$ ${red(
             "before committing this changeset"
@@ -158,9 +158,9 @@ export default async function createChangeset(
   }
 
   if (pkgsLeftToGetBumpTypeFor.size !== 0) {
-    logger.log(`The following packages will be ${blue("patch")} bumped:`);
+    log(`The following packages will be ${blue("patch")} bumped:`);
     pkgsLeftToGetBumpTypeFor.forEach(pkgName => {
-      logger.log(
+      log(
         formatPkgNameAndVersion(pkgName, pkgJsonsByName.get(pkgName)!.version)
       );
     });
@@ -170,13 +170,13 @@ export default async function createChangeset(
     }
   }
 
-  logger.log(
+  log(
     "Please enter a summary for this change (this will be in the changelogs)"
   );
 
   let summary = await cli.askQuestion("Summary");
   while (summary.length === 0) {
-    logger.error("A summary is required for the changelog! 😪");
+    error("A summary is required for the changelog! 😪");
     summary = await cli.askQuestion("Summary");
   }
 
diff --git a/packages/cli/src/commands/add/index.ts b/packages/cli/src/commands/add/index.ts
index 61b5381..0fb5f32 100644
--- a/packages/cli/src/commands/add/index.ts
+++ b/packages/cli/src/commands/add/index.ts
@@ -4,8 +4,8 @@ import fs from "fs-extra";
 
 import * as cli from "../../utils/cli";
 import * as git from "@changesets/git";
+import { info, log, warn } from "@changesets/logger";
 import { Config } from "@changesets/types";
-import logger from "../../utils/logger";
 
 import writeChangeset from "./writeChangeset";
 import createChangeset from "./createChangeset";
@@ -20,11 +20,11 @@ export default async function add(
   const changesetBase = await getChangesetBase(cwd);
 
   if (!fs.existsSync(changesetBase)) {
-    logger.warn("There is no .changeset folder. ");
-    logger.warn(
+    warn("There is no .changeset folder. ");
+    warn(
       "If this is the first time `changesets` have been used in this project, run `yarn changeset init` to get set up."
     );
-    logger.warn(
+    warn(
       "If you expected there to be changesets, you should check git history for when the folder was removed to ensure you do not lose any configuration."
     );
     return;
@@ -56,11 +56,9 @@ export default async function add(
         `CHANGESET: ${changesetID}. ${newChangeset.summary}`,
         cwd
       );
-      logger.log(
-        chalk.green(`${empty ? "Empty " : ""}Changeset added and committed`)
-      );
+      log(chalk.green(`${empty ? "Empty " : ""}Changeset added and committed`));
     } else {
-      logger.log(
+      log(
         chalk.green(
           `${empty ? "Empty " : ""}Changeset added! - you can now commit it\n`
         )
@@ -72,19 +70,19 @@ export default async function add(
     );
 
     if (hasMajorChange) {
-      logger.warn(
+      warn(
         "This Changeset includes a major change and we STRONGLY recommend adding more information to the changeset:"
       );
-      logger.warn("WHAT the breaking change is");
-      logger.warn("WHY the change was made");
-      logger.warn("HOW a consumer should update their code");
+      warn("WHAT the breaking change is");
+      warn("WHY the change was made");
+      warn("HOW a consumer should update their code");
     } else {
-      logger.log(
+      log(
         chalk.green(
           "If you want to modify or expand on the changeset summary, you can find it here"
         )
       );
     }
-    logger.info(chalk.blue(path.resolve(changesetBase, `${changesetID}.md`)));
+    info(chalk.blue(path.resolve(changesetBase, `${changesetID}.md`)));
   }
 }
diff --git a/packages/cli/src/commands/add/messages.ts b/packages/cli/src/commands/add/messages.ts
index 1aa7a16..ded4aba 100644
--- a/packages/cli/src/commands/add/messages.ts
+++ b/packages/cli/src/commands/add/messages.ts
@@ -1,8 +1,8 @@
 import chalk from "chalk";
 import boxen from "boxen";
 import outdent from "outdent";
+import { log } from "@changesets/logger";
 import { Release, VersionType } from "@changesets/types";
-import logger from "../../utils/logger";
 
 export default function printConfirmationMessage(changeset: {
   releases: Array<Release>;
@@ -13,17 +13,17 @@ export default function printConfirmationMessage(changeset: {
       .filter(release => release.type === type)
       .map(release => release.name);
   }
-  logger.log("=== Releasing the following packages ===");
+  log("=== Releasing the following packages ===");
   const majorReleases = getReleasesOfType("major");
   const minorReleases = getReleasesOfType("minor");
   const patchReleases = getReleasesOfType("patch");
 
   if (majorReleases.length > 0)
-    logger.log(`${chalk.green("[Major]")}\n  ${majorReleases.join(", ")}`);
+    log(`${chalk.green("[Major]")}\n  ${majorReleases.join(", ")}`);
   if (minorReleases.length > 0)
-    logger.log(`${chalk.green("[Minor]")}\n  ${minorReleases.join(", ")}`);
+    log(`${chalk.green("[Minor]")}\n  ${minorReleases.join(", ")}`);
   if (patchReleases.length > 0)
-    logger.log(`${chalk.green("[Patch]")}\n  ${patchReleases.join(", ")}`);
+    log(`${chalk.green("[Patch]")}\n  ${patchReleases.join(", ")}`);
 
   const message = outdent`
       ${chalk.red("========= NOTE ========")}
@@ -34,5 +34,5 @@ export default function printConfirmationMessage(changeset: {
     borderStyle: "double",
     align: "center"
   });
-  logger.log(prettyMessage);
+  log(prettyMessage);
 }
diff --git a/packages/cli/src/commands/init/index.ts b/packages/cli/src/commands/init/index.ts
index e854487..92c8a04 100644
--- a/packages/cli/src/commands/init/index.ts
+++ b/packages/cli/src/commands/init/index.ts
@@ -2,9 +2,9 @@ import path from "path";
 import fs from "fs-extra";
 import chalk from "chalk";
 
-import logger from "../../utils/logger";
 import getChangesetBase from "../../utils/getChangesetBase";
 import { defaultWrittenConfig } from "@changesets/config";
+import { info, log, warn, error } from "@changesets/logger";
 
 const pkgPath = path.dirname(require.resolve("@changesets/cli/package.json"));
 
@@ -14,21 +14,21 @@ export default async function init(cwd: string) {
   if (fs.existsSync(changesetBase)) {
     if (!fs.existsSync(path.join(changesetBase, "config.json"))) {
       if (fs.existsSync(path.join(changesetBase, "config.js"))) {
-        logger.error(
+        error(
           "It looks like you're using the version 1 `.changeset/config.js` file"
         );
-        logger.error(
+        error(
           "The format of the config object has significantly changed in v2 as well"
         );
-        logger.error(
+        error(
           " - we thoroughly recommend looking at the changelog for this package for what has changed"
         );
-        logger.error(
+        error(
           "Changesets will write the defaults for the new config, remember to transfer your options into the new config at `.changeset/config.json`"
         );
       } else {
-        logger.error("It looks like you don't have a config file");
-        logger.info(
+        error("It looks like you don't have a config file");
+        info(
           "The default config file will be written at `.changeset/config.json`"
         );
       }
@@ -37,7 +37,7 @@ export default async function init(cwd: string) {
         JSON.stringify(defaultWrittenConfig, null, 2)
       );
     } else {
-      logger.warn(
+      warn(
         "It looks like you already have changesets initialized. You should be able to run changeset commands no problems."
       );
     }
@@ -48,17 +48,17 @@ export default async function init(cwd: string) {
       JSON.stringify(defaultWrittenConfig, null, 2)
     );
 
-    logger.log(
+    log(
       chalk`Thanks for choosing {green changesets} to help manage your versioning and publishing\n`
     );
-    logger.log("You should be set up to start using changesets now!\n");
+    log("You should be set up to start using changesets now!\n");
 
-    logger.info(
+    info(
       "We have added a `.changeset` folder, and a couple of files to help you out:"
     );
-    logger.info(
+    info(
       chalk`- {blue .changeset/README.md} contains information about using changesets`
     );
-    logger.info(chalk`- {blue .changeset/config.json} is our default config`);
+    info(chalk`- {blue .changeset/config.json} is our default config`);
   }
 }
diff --git a/packages/cli/src/commands/publish/index.ts b/packages/cli/src/commands/publish/index.ts
index 8253708..3b5a78f 100644
--- a/packages/cli/src/commands/publish/index.ts
+++ b/packages/cli/src/commands/publish/index.ts
@@ -1,12 +1,12 @@
 import publishPackages from "./publishPackages";
-import logger from "../../utils/logger";
+import { error, log, success } from "@changesets/logger";
 import * as git from "@changesets/git";
 import { ExitError } from "../../utils/errors";
 import { Config } from "@changesets/types";
 
 function logReleases(pkgs: Array<{ name: string; newVersion: string }>) {
   const mappedPkgs = pkgs.map(p => `${p.name}@${p.newVersion}`).join("\n");
-  logger.log(mappedPkgs);
+  log(mappedPkgs);
 }
 
 export default async function run(
@@ -25,21 +25,21 @@ export default async function run(
   const unsuccessful = response.filter(p => !p.published);
 
   if (successful.length > 0) {
-    logger.success("packages published successfully:");
+    success("packages published successfully:");
     logReleases(successful);
     // We create the tags after the push above so that we know that HEAD wont change and that pushing
     // wont suffer from a race condition if another merge happens in the mean time (pushing tags wont
     // fail if we are behind master).
-    logger.log("Creating tags...");
+    log("Creating tags...");
     for (const pkg of successful) {
       const tag = `${pkg.name}@${pkg.newVersion}`;
-      logger.log("New tag: ", tag);
+      log("New tag: ", tag);
       await git.tag(tag, cwd);
     }
   }
 
   if (unsuccessful.length > 0) {
-    logger.error("packages failed to publish:");
+    error("packages failed to publish:");
 
     logReleases(unsuccessful);
     throw new ExitError(1);
diff --git a/packages/cli/src/commands/publish/npm-utils.ts b/packages/cli/src/commands/publish/npm-utils.ts
index 76cd1d5..14e9bd0 100644
--- a/packages/cli/src/commands/publish/npm-utils.ts
+++ b/packages/cli/src/commands/publish/npm-utils.ts
@@ -1,4 +1,4 @@
-import logger from "../../utils/logger";
+import { error, info, warn } from "@changesets/logger";
 import pLimit from "p-limit";
 import chalk from "chalk";
 import spawn from "spawndamnit";
@@ -29,19 +29,19 @@ export async function getTokenIsRequired() {
   });
   let json = JSON.parse(result.stdout.toString());
   if (json.error) {
-    logger.error(
+    error(
       `an error occurred while running \`npm profile get\`: ${json.error.code}`
     );
-    logger.error(json.error.summary);
-    if (json.error.summary) logger.error(json.error.summary);
+    error(json.error.summary);
+    if (json.error.summary) error(json.error.summary);
     throw new ExitError(1);
   }
   return json.tfa.mode === "auth-and-writes";
 }
 
-export function info(pkgName: string) {
+export function getPackageInfo(pkgName: string) {
   return npmRequestLimit(async () => {
-    logger.info(`npm info ${pkgName}`);
+    info(`npm info ${pkgName}`);
 
     // Due to a couple of issues with yarnpkg, we also want to override the npm registry when doing
     // npm info.
@@ -62,19 +62,19 @@ export function info(pkgName: string) {
 }
 
 export async function infoAllow404(pkgName: string) {
-  let pkgInfo = await info(pkgName);
+  let pkgInfo = await getPackageInfo(pkgName);
   if (pkgInfo.error && pkgInfo.error.code === "E404") {
-    logger.warn(`Recieved 404 for npm info ${chalk.cyan(`"${pkgName}"`)}`);
+    warn(`Recieved 404 for npm info ${chalk.cyan(`"${pkgName}"`)}`);
     return { published: false, pkgInfo: {} };
   }
   if (pkgInfo.error) {
-    logger.error(
+    error(
       `Recieved an unknown error code: ${
         pkgInfo.error.code
       } for npm info ${chalk.cyan(`"${pkgName}"`)}`
     );
-    logger.error(pkgInfo.error.summary);
-    if (pkgInfo.error.detail) logger.error(pkgInfo.error.detail);
+    error(pkgInfo.error.summary);
+    if (pkgInfo.error.detail) error(pkgInfo.error.detail);
 
     throw new ExitError(1);
   }
@@ -86,7 +86,7 @@ let otpAskLimit = pLimit(1);
 let askForOtpCode = (twoFactorState: TwoFactorState) =>
   otpAskLimit(async () => {
     if (twoFactorState.token !== null) return twoFactorState.token;
-    logger.info(
+    info(
       "This operation requires a one-time password from your authenticator."
     );
 
@@ -132,7 +132,12 @@ async function internalPublish(
 
   if (json.error) {
     // The first case is no 2fa provided, the second is when the 2fa is wrong (timeout or wrong words)
-    if ((json.error.code === "EOTP" || (json.error.code === 'E401' && json.error.detail.includes('--otp=<code>')  )) && !isCI) {
+    if (
+      (json.error.code === "EOTP" ||
+        (json.error.code === "E401" &&
+          json.error.detail.includes("--otp=<code>"))) &&
+      !isCI
+    ) {
       if (twoFactorState.token !== null) {
         // the current otp code must be invalid since it errored
         twoFactorState.token = null;
@@ -141,7 +146,7 @@ async function internalPublish(
       twoFactorState.isRequired = Promise.resolve(true);
       return internalPublish(pkgName, opts, twoFactorState);
     }
-    logger.error(
+    error(
       `an error occurred while publishing ${pkgName}: ${json.error.code}`,
       json.error.summary,
       json.error.detail ? "\n" + json.error.detail : ""
diff --git a/packages/cli/src/commands/publish/publishPackages.ts b/packages/cli/src/commands/publish/publishPackages.ts
index 5adb7ab..9a5f74b 100644
--- a/packages/cli/src/commands/publish/publishPackages.ts
+++ b/packages/cli/src/commands/publish/publishPackages.ts
@@ -3,7 +3,7 @@ import chalk from "chalk";
 import getWorkspaces from "../../utils/getWorkspaces";
 import { Workspace } from "get-workspaces";
 import * as npmUtils from "./npm-utils";
-import logger from "../../utils/logger";
+import { info, warn } from "@changesets/logger";
 import { TwoFactorState } from "../../utils/types";
 
 export default async function publishPackages({
@@ -34,7 +34,7 @@ export default async function publishPackages({
   });
 
   if (unpublishedPackagesInfo.length === 0) {
-    logger.warn("No unpublished packages to publish");
+    warn("No unpublished packages to publish");
   }
 
   const publishedPackages = await Promise.all(
@@ -50,7 +50,7 @@ async function publishAPackage(
   twoFactorState: TwoFactorState
 ) {
   const { name, version } = pkg.config;
-  logger.info(
+  info(
     `Publishing ${chalk.cyan(`"${name}"`)} at ${chalk.green(`"${version}"`)}`
   );
 
@@ -101,12 +101,12 @@ async function getUnpublishedPackages(packages: Array<Workspace>) {
       packagesToPublish.push(pkgInfo);
     } else if (semver.gt(localVersion, publishedVersion)) {
       packagesToPublish.push(pkgInfo);
-      logger.info(
+      info(
         `${name} is being published because our local version (${localVersion}) is ahead of npm's (${publishedVersion})`
       );
     } else if (semver.lt(localVersion, publishedVersion)) {
       // If the local version is behind npm, something is wrong, we warn here, and by not getting published later, it will fail
-      logger.warn(
+      warn(
         `${name} is not being published because version ${publishedVersion} is already published on npm and we are trying to publish version ${localVersion}`
       );
     }
diff --git a/packages/cli/src/commands/status/index.ts b/packages/cli/src/commands/status/index.ts
index db34d30..1926f27 100644
--- a/packages/cli/src/commands/status/index.ts
+++ b/packages/cli/src/commands/status/index.ts
@@ -3,13 +3,13 @@ import table from "tty-table";
 import fs from "fs-extra";
 import path from "path";
 import getReleasePlan from "@changesets/get-release-plan";
+import { error, log, info } from "@changesets/logger";
 import {
   VersionType,
   Release,
   ComprehensiveRelease,
   Config
 } from "@changesets/types";
-import logger from "../../utils/logger";
 
 export default async function getStatus(
   cwd: string,
@@ -26,7 +26,7 @@ export default async function getStatus(
   const { changesets, releases } = releasePlan;
 
   if (changesets.length < 1) {
-    logger.error("No changesets present");
+    error("No changesets present");
     process.exit(1);
   }
 
@@ -40,9 +40,9 @@ export default async function getStatus(
 
   const print = verbose ? verbosePrint : SimplePrint;
   print("patch", releases);
-  logger.log("---");
+  log("---");
   print("minor", releases);
-  logger.log("---");
+  log("---");
   print("major", releases);
 
   return releasePlan;
@@ -51,12 +51,12 @@ export default async function getStatus(
 function SimplePrint(type: VersionType, releases: Array<Release>) {
   const packages = releases.filter(r => r.type === type);
   if (packages.length) {
-    logger.info(chalk`Packages to be bumped at {green ${type}}:\n`);
+    info(chalk`Packages to be bumped at {green ${type}}:\n`);
 
     const pkgs = packages.map(({ name }) => `- ${name}`).join("\n");
-    logger.log(chalk.green(pkgs));
+    log(chalk.green(pkgs));
   } else {
-    logger.info(chalk`{red NO} packages to be bumped at {green ${type}}`);
+    info(chalk`{red NO} packages to be bumped at {green ${type}}`);
   }
 }
 
@@ -66,7 +66,7 @@ function verbosePrint(
 ) {
   const packages = releases.filter(r => r.type === type);
   if (packages.length) {
-    logger.info(chalk`Packages to be bumped at {green ${type}}`);
+    info(chalk`Packages to be bumped at {green ${type}}`);
 
     const columns = packages.map(
       ({ name, newVersion: version, changesets }) => [
@@ -87,9 +87,9 @@ function verbosePrint(
       columns,
       { paddingLeft: 1, paddingRight: 0, headerAlign: "center", align: "left" }
     );
-    logger.log(t1.render() + "\n");
+    log(t1.render() + "\n");
   } else {
-    logger.info(
+    info(
       chalk`Running release would release {red NO} packages as a {green ${type}}`
     );
   }
diff --git a/packages/cli/src/commands/version/index.ts b/packages/cli/src/commands/version/index.ts
index e3f7736..2e40338 100644
--- a/packages/cli/src/commands/version/index.ts
+++ b/packages/cli/src/commands/version/index.ts
@@ -1,4 +1,5 @@
 import chalk from "chalk";
+import { log, warn } from "@changesets/logger";
 import { Config, NewChangeset } from "@changesets/types";
 import applyReleasePlan from "@changesets/apply-release-plan";
 import readChangesets from "@changesets/read";
@@ -6,7 +7,6 @@ import getDependentsgraph from "get-dependents-graph";
 import assembleReleasePlan from "@changesets/assemble-release-plan";
 import getWorkspaces from "get-workspaces";
 
-import logger from "../../utils/logger";
 import { removeEmptyFolders } from "../../utils/v1-legacy/removeFolders";
 import getOldChangesets from "../../utils/v1-legacy/getChangesets";
 
@@ -27,16 +27,16 @@ async function getOldChangesetsAndWarn(
   if (unreleasedChangesets.length === 0) {
     return [];
   }
-  logger.warn(importantSeparator);
-  logger.warn("There were old changesets from version 1 found");
-  logger.warn(
+  warn(importantSeparator);
+  warn("There were old changesets from version 1 found");
+  warn(
     "Theses are being applied now but the dependents graph may have changed"
   );
-  logger.warn("Make sure you validate all your dependencies");
-  logger.warn(
+  warn("Make sure you validate all your dependencies");
+  warn(
     "In a future major version, we will no longer apply these old changesets, and will instead throw here"
   );
-  logger.warn(
+  warn(
     "----------------------------------------------------------------------"
   );
 
@@ -56,7 +56,7 @@ export default async function version(cwd: string, config: Config) {
   let changesets = [...oldChangesets, ...newChangesets];
 
   if (changesets.length === 0) {
-    logger.warn("No unreleased changesets found, exiting.");
+    warn("No unreleased changesets found, exiting.");
     return;
   }
 
@@ -83,12 +83,8 @@ export default async function version(cwd: string, config: Config) {
   await applyReleasePlan(releasePlan, cwd, config);
 
   if (config.commit) {
-    logger.log(
-      "All files have been updated and committed. You're ready to publish!"
-    );
+    log("All files have been updated and committed. You're ready to publish!");
   } else {
-    logger.log(
-      "All files have been updated. Review them and commit at your leisure"
-    );
+    log("All files have been updated. Review them and commit at your leisure");
   }
 }
diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts
index 467267e..187d2ac 100644
--- a/packages/cli/src/index.ts
+++ b/packages/cli/src/index.ts
@@ -1,12 +1,11 @@
 import meow from "meow";
 import { read } from "@changesets/config";
+import { error } from "@changesets/logger";
 import { Config } from "@changesets/types";
 import fs from "fs-extra";
 import path from "path";
 import getWorkspaces from "get-workspaces";
 
-import logger from "./utils/logger";
-
 import init from "./commands/init";
 import add from "./commands/add";
 import version from "./commands/version";
@@ -75,16 +74,14 @@ const cwd = process.cwd();
       path.resolve(cwd, ".changeset/config.js")
     );
     if (oldConfigExists) {
-      logger.error(
+      error(
         "It looks like you're using the version 1 `.changeset/config.js` file"
       );
-      logger.error(
-        "You'll need to convert it to a `.changeset/config.json` file"
-      );
-      logger.error(
+      error("You'll need to convert it to a `.changeset/config.json` file");
+      error(
         "The format of the config object has significantly changed in v2 as well"
       );
-      logger.error(
+      error(
         " - we thoroughly recommend looking at the changelog for this package for what has changed"
       );
       process.exit(1);
@@ -98,7 +95,7 @@ const cwd = process.cwd();
     // @ts-ignore if this is undefined, we have already exited
     await add(cwd, { empty }, config);
   } else if (input.length > 1) {
-    logger.error(
+    error(
       "Too many arguments passed to changesets - we only accept the command name as an argument"
     );
   } else {
@@ -107,11 +104,11 @@ const cwd = process.cwd();
 
     deadFlags.forEach(flag => {
       if (flags[flag]) {
-        logger.error(
+        error(
           `the flag ${flag} has been removed from changesets for version 2`
         );
-        logger.error(`Please encode the desired value into your config`);
-        logger.error(`See our changelog for more details`);
+        error(`Please encode the desired value into your config`);
+        error(`See our changelog for more details`);
         throw new ExitError(1);
       }
     });
@@ -143,25 +140,25 @@ const cwd = process.cwd();
         return;
       }
       case "bump": {
-        logger.error(
+        error(
           'In version 2 of changesets, "bump" has been renamed to "version" - see our changelog for an explanation'
         );
-        logger.error(
+        error(
           "To fix this, use `changeset version` instead, and update any scripts that use changesets"
         );
         throw new ExitError(1);
       }
       case "release": {
-        logger.error(
+        error(
           'In version 2 of changesets, "release" has been renamed to "publish" - see our changelog for an explanation'
         );
-        logger.error(
+        error(
           "To fix this, use `changeset publish` instead, and update any scripts that use changesets"
         );
         throw new ExitError(1);
       }
       default: {
-        logger.error(`Invalid command ${input[0]} was provided`);
+        error(`Invalid command ${input[0]} was provided`);
         throw new ExitError(1);
       }
     }
@@ -170,6 +167,6 @@ const cwd = process.cwd();
   if (err instanceof ExitError) {
     return process.exit(err.code);
   }
-  logger.error(err);
+  error(err);
   process.exit(1);
 });
diff --git a/packages/cli/src/utils/cli.ts b/packages/cli/src/utils/cli.ts
index b6f834a..9734acd 100644
--- a/packages/cli/src/utils/cli.ts
+++ b/packages/cli/src/utils/cli.ts
@@ -1,6 +1,6 @@
 // @ts-ignore it's not worth writing a TS declaration file in this repo for a tiny module we use once like this
 import termSize from "term-size";
-import logger, { prefix } from "./logger";
+import { error, prefix, success } from "@changesets/logger";
 // @ts-ignore
 import { prompt } from "enquirer";
 
@@ -17,7 +17,7 @@ const serialId: () => number = (function() {
 const limit = Math.max(termSize().rows - 5, 10);
 
 let cancelFlow = () => {
-  logger.success("Cancelled... 👋 ");
+  success("Cancelled... 👋 ");
   process.exit();
 };
 
@@ -42,7 +42,7 @@ async function askCheckboxPlus(
   })
     .then((responses: any) => responses[name])
     .catch((err: unknown) => {
-      logger.error(err);
+      error(err);
     });
 }
 
@@ -61,7 +61,7 @@ async function askQuestion(message: string): Promise<string> {
   ])
     .then((responses: any) => responses[name])
     .catch((err: unknown) => {
-      logger.error(err);
+      error(err);
     });
 }
 
@@ -81,7 +81,7 @@ async function askConfirm(message: string): Promise<boolean> {
   ])
     .then((responses: any) => responses[name])
     .catch((err: unknown) => {
-      logger.error(err);
+      error(err);
     });
 }
 
@@ -104,7 +104,7 @@ async function askList(
   ])
     .then((responses: any) => responses[name])
     .catch((err: unknown) => {
-      logger.error(err);
+      error(err);
     });
 }
 
diff --git a/packages/logger/README.md b/packages/logger/README.md
new file mode 100644
index 0000000..0f52b3b
--- /dev/null
+++ b/packages/logger/README.md
@@ -0,0 +1,40 @@
+## @changesets/logger
+
+### Usage
+
+```js
+import { error } from '@changesets/logger";
+
+error('message part 1', 'message part 2');
+```
+
+### Package Exports
+
+**error**: Use `error` to print error messages upon which users which immediately action to complete the task.
+
+**info**: Use  `info` to print informational messages to user.
+
+**log**: Use `log` to print messages don't fall in anyother specific category. For example, message to show title of the step being performed by the changesets tool.
+
+**success**: Use `success` to assert to users that their instructions have completed succesfully.
+
+**warn**: Use `warn` to print warning messages, something that user could action on now or later without much impact of their work.
+
+### Silencing Messages In Tests
+
+Use the `@changesets/test-utils` package to silence the logs in test cases.
+
+For example:
+
+```
+import { temporarilySilenceLogs } from "@changesets/test-utils";
+import { log } from "@changesets/logger";
+
+temporarilySilenceLogs();
+
+// Now the logs in this test file are not actually logged to std out
+log("I am not logged");
+
+// Use console.log to log messages in tests if required
+console.log("Yiey, I am logged");
+```
diff --git a/packages/logger/package.json b/packages/logger/package.json
new file mode 100644
index 0000000..ae3e591
--- /dev/null
+++ b/packages/logger/package.json
@@ -0,0 +1,18 @@
+{
+  "name": "@changesets/logger",
+  "version": "0.0.0",
+  "description": "Console log alias",
+  "main": "dist/logger.cjs.js",
+  "module": "dist/logger.esm.js",
+  "license": "MIT",
+  "repository": "https://github.com/changesets/changesets/tree/master/packages/logger",
+  "dependencies": {
+    "@changesets/types": "^0.3.0",
+    "@types/node": "^12.7.11",
+    "chalk": "^2.1.0"
+  },
+  "devDependencies": {
+    "jest-fixtures": "^0.5.0",
+    "outdent": "^0.5.0"
+  }
+}
diff --git a/packages/cli/src/utils/logger.ts b/packages/logger/src/index.ts
similarity index 58%
rename from packages/cli/src/utils/logger.ts
rename to packages/logger/src/index.ts
index 50a207e..d0d3f51 100644
--- a/packages/cli/src/utils/logger.ts
+++ b/packages/logger/src/index.ts
@@ -15,31 +15,22 @@ function format(args: Array<any>, customPrefix?: string) {
   );
 }
 
-function log(...args: Array<any>) {
-  console.log(format(args));
-}
-
-function info(...args: Array<any>) {
-  console.error(format(args, chalk.cyan("info")));
+export function error(...args: Array<any>) {
+  console.error(format(args, chalk.red("error")));
 }
 
-function warn(...args: Array<any>) {
-  console.error(format(args, chalk.yellow("warn")));
+export function info(...args: Array<any>) {
+  console.info(format(args, chalk.cyan("info")));
 }
 
-function error(...args: Array<any>) {
-  console.error(format(args, chalk.red("error")));
+export function log(...args: Array<any>) {
+  console.log(format(args));
 }
 
-function success(...args: Array<any>) {
+export function success(...args: Array<any>) {
   console.log(format(args, chalk.green("success")));
 }
 
-export default {
-  log,
-  info,
-  warn,
-  error,
-  success,
-  format
-};
+export function warn(...args: Array<any>) {
+  console.warn(format(args, chalk.yellow("warn")));
+}
diff --git a/yarn.lock b/yarn.lock
index e059b1b..6e94c83 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1004,6 +1004,11 @@
   resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.5.tgz#e19436e7f8e9b4601005d73673b6dc4784ffcc2f"
   integrity sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w==
 
+"@types/node@^12.7.11":
+  version "12.7.11"
+  resolved "https://registry.npmjs.org/@types/node/-/node-12.7.11.tgz#be879b52031cfb5d295b047f5462d8ef1a716446"
+  integrity sha512-Otxmr2rrZLKRYIybtdG/sgeO+tHY20GxeDjcGmUnmmlCWyEnv2a2x1ZXBo3BTec4OiTXMQCiazB8NMBf0iRlFw==
+
 "@types/prettier@^1.18.0":
   version "1.18.0"
   resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.18.0.tgz#d2dbe4d5f76b455138f13a2d881278e2c06a733d"


Test patch:
diff --git a/packages/cli/src/commands/add/__tests__/add.ts b/packages/cli/src/commands/add/__tests__/add.ts
index e7bab3d..0f974f1 100644
--- a/packages/cli/src/commands/add/__tests__/add.ts
+++ b/packages/cli/src/commands/add/__tests__/add.ts
@@ -3,12 +3,12 @@ import { copyFixtureIntoTempDir } from "jest-fixtures";
 import stripAnsi from "strip-ansi";
 import * as git from "@changesets/git";
 import { defaultConfig } from "@changesets/config";
+import { temporarilySilenceLogs } from "@changesets/test-utils";
 
 import { askCheckboxPlus, askConfirm, askQuestion } from "../../../utils/cli";
 import addChangeset from "..";
 import writeChangeset from "../writeChangeset";
 
-jest.mock("../../../utils/logger");
 jest.mock("../../../utils/cli");
 jest.mock("@changesets/git");
 jest.mock("../writeChangeset");
@@ -64,6 +64,7 @@ const mockUserResponses = mockResponses => {
 };
 
 describe("Changesets", () => {
+  temporarilySilenceLogs();
   afterEach(() => {
     jest.clearAllMocks();
   });
diff --git a/packages/cli/src/commands/init/__tests__/command.ts b/packages/cli/src/commands/init/__tests__/command.ts
index cb69faf..fc68af6 100644
--- a/packages/cli/src/commands/init/__tests__/command.ts
+++ b/packages/cli/src/commands/init/__tests__/command.ts
@@ -2,17 +2,17 @@ import { copyFixtureIntoTempDir } from "jest-fixtures";
 import fs from "fs-extra";
 import path from "path";
 import { defaultWrittenConfig } from "@changesets/config";
+import { temporarilySilenceLogs } from "@changesets/test-utils";
 
 import initializeCommand from "..";
 
-jest.mock("../../../utils/logger");
-
 const getPaths = (cwd: string) => ({
   readmePath: path.join(cwd, ".changeset/README.md"),
   configPath: path.join(cwd, ".changeset/config.json")
 });
 
 describe("init", () => {
+  temporarilySilenceLogs();
   it("should initialize in a project without a .changeset folder", async () => {
     const cwd = await copyFixtureIntoTempDir(
       __dirname,
diff --git a/packages/cli/src/commands/publish/__tests__/releaseCommand.test.ts b/packages/cli/src/commands/publish/__tests__/releaseCommand.test.ts
index 4e50a11..f376851 100644
--- a/packages/cli/src/commands/publish/__tests__/releaseCommand.test.ts
+++ b/packages/cli/src/commands/publish/__tests__/releaseCommand.test.ts
@@ -3,11 +3,11 @@ import { copyFixtureIntoTempDir } from "jest-fixtures";
 import publishPackages from "../publishPackages";
 import * as git from "@changesets/git";
 import { defaultConfig } from "@changesets/config";
+import { temporarilySilenceLogs } from "@changesets/test-utils";
 import runRelease from "..";
 
 jest.mock("../../../utils/cli");
 jest.mock("@changesets/git");
-jest.mock("../../../utils/logger");
 jest.mock("../publishPackages");
 
 // @ts-ignore
@@ -27,6 +27,7 @@ publishPackages.mockImplementation(() =>
 );
 
 describe("running release", () => {
+  temporarilySilenceLogs();
   let cwd: string;
 
   beforeEach(async () => {
diff --git a/packages/cli/src/commands/status/__tests__/status.ts b/packages/cli/src/commands/status/__tests__/status.ts
index c7b6f75..a81d738 100644
--- a/packages/cli/src/commands/status/__tests__/status.ts
+++ b/packages/cli/src/commands/status/__tests__/status.ts
@@ -2,6 +2,7 @@ import { copyFixtureIntoTempDir } from "jest-fixtures";
 import fs from "fs-extra";
 import path from "path";
 import { defaultConfig } from "@changesets/config";
+import { temporarilySilenceLogs } from "@changesets/test-utils";
 
 import writeChangeset from "../../add/writeChangeset";
 import status from "..";
@@ -46,7 +47,6 @@ const simpleReleasePlan: ReleasePlan = {
   ]
 };
 
-jest.mock("../../../utils/logger");
 jest.mock("@changesets/git");
 
 const writeChangesets = (changesets: NewChangeset[], cwd: string) => {
@@ -54,6 +54,7 @@ const writeChangesets = (changesets: NewChangeset[], cwd: string) => {
 };
 
 describe("status", () => {
+  temporarilySilenceLogs();
   let cwd: string;
 
   beforeEach(async () => {
diff --git a/packages/cli/src/commands/version/version.test.ts b/packages/cli/src/commands/version/version.test.ts
index 115e234..b28c251 100644
--- a/packages/cli/src/commands/version/version.test.ts
+++ b/packages/cli/src/commands/version/version.test.ts
@@ -4,7 +4,8 @@ import fs from "fs-extra";
 import path from "path";
 import versionCommand from "./index";
 import * as git from "@changesets/git";
-import logger from "../../utils/logger";
+import { warn } from "@changesets/logger";
+import { temporarilySilenceLogs } from "@changesets/test-utils";
 import writeChangeset from "../add/writeChangeset";
 import { NewChangeset, Config } from "@changesets/types";
 import { defaultConfig } from "@changesets/config";
@@ -21,7 +22,6 @@ const consoleError = console.error;
 
 jest.mock("../../utils/cli");
 jest.mock("@changesets/git");
-jest.mock("../../utils/logger");
 
 // @ts-ignore
 git.add.mockImplementation(() => Promise.resolve(true));
@@ -65,6 +65,7 @@ const getPkgJSON = (pkgName: string, calls: any) => {
 const writeEmptyChangeset = (cwd: string) => writeChangesets([], cwd);
 
 describe("running version in a simple project", () => {
+  temporarilySilenceLogs();
   let cwd: string;
 
   beforeEach(async () => {
@@ -82,7 +83,7 @@ describe("running version in a simple project", () => {
       await writeEmptyChangeset(cwd);
       await versionCommand(cwd, modifiedDefaultConfig);
       // @ts-ignore
-      const loggerWarnCalls = logger.warn.mock.calls;
+      const loggerWarnCalls = warn.mock.calls;
       expect(loggerWarnCalls.length).toEqual(1);
       expect(loggerWarnCalls[0][0]).toEqual(
         "No unreleased changesets found, exiting."
diff --git a/packages/logger/src/index.test.ts b/packages/logger/src/index.test.ts
new file mode 100644
index 0000000..788b10b
--- /dev/null
+++ b/packages/logger/src/index.test.ts
@@ -0,0 +1,86 @@
+import { log, error, info, warn, success } from "./index";
+
+describe("@changesets/logger", () => {
+  const logMessageOne = "Message 1";
+  const logMessageTwo = "Message 2";
+  const expectedLoggedRegex = new RegExp(
+    `(${logMessageOne})\\s(${logMessageTwo})`
+  );
+  describe("log", () => {
+    let originalConsoleLog = console.log;
+    beforeEach(() => {
+      console.log = jest.fn();
+    });
+    afterEach(() => {
+      console.log = originalConsoleLog;
+    });
+    it("should  call console.log", () => {
+      log(logMessageOne, logMessageTwo);
+      expect(console.log).toHaveBeenCalledWith(
+        expect.stringMatching(expectedLoggedRegex)
+      );
+    });
+  });
+  describe("error", () => {
+    let originalConsoleError = console.error;
+    beforeEach(() => {
+      console.error = jest.fn();
+    });
+    afterEach(() => {
+      console.error = originalConsoleError;
+    });
+    it("should  call console.error", () => {
+      error(logMessageOne, logMessageTwo);
+      expect(console.error).toHaveBeenCalledWith(
+        expect.stringMatching(expectedLoggedRegex)
+      );
+    });
+  });
+  describe("info", () => {
+    let originalConsoleInfo = console.info;
+    beforeEach(() => {
+      console.info = jest.fn();
+    });
+    afterEach(() => {
+      console.info = originalConsoleInfo;
+    });
+    it("should  call console.error", () => {
+      info(logMessageOne, logMessageTwo);
+      expect(console.info).toHaveBeenCalledWith(
+        expect.stringMatching(expectedLoggedRegex)
+      );
+    });
+  });
+
+  describe("warn", () => {
+    let originalConsoleWarn = console.warn;
+    beforeEach(() => {
+      console.warn = jest.fn();
+    });
+    afterEach(() => {
+      console.warn = originalConsoleWarn;
+    });
+    it("should  call console.info", () => {
+      warn(logMessageOne, logMessageTwo);
+      expect(console.warn).toHaveBeenCalledWith(
+        expect.stringMatching(expectedLoggedRegex)
+      );
+    });
+  });
+
+  describe("success", () => {
+    let originalConsoleLog = console.log;
+    beforeEach(() => {
+      console.log = jest.fn();
+    });
+    afterEach(() => {
+      console.log = originalConsoleLog;
+    });
+    it("should  call console.info", () => {
+      success(logMessageOne, logMessageTwo);
+      expect(console.log).toHaveBeenCalledWith(
+        expect.stringMatching(expectedLoggedRegex)
+      );
+    });
+  });
+});
diff --git a/packages/test-utils/README.md b/packages/test-utils/README.md
new file mode 100644
index 0000000..a8c8c0d
--- /dev/null
+++ b/packages/test-utils/README.md
@@ -0,0 +1,18 @@
+## @changesets/test-utils
+
+> Utilities for testing @changesets/* packages
+
+### Utilities
+
+#### temporarilySilenceLogs
+
+Silence the logs created but the `@changesets/logger` packages.
+
+**Usage**
+
+```
+// index.test.ts
+import { temporarilySilenceLogs } from "@changesets/test-utils";
+
+temporarilySilenceLogs();
+```
diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json
new file mode 100644
index 0000000..1cc0829
--- /dev/null
+++ b/packages/test-utils/package.json
@@ -0,0 +1,17 @@
+{
+  "name": "@changesets/test-utils",
+  "version": "0.0.0",
+  "description": "Utils for testing changesets packages",
+  "main": "dist/test-utils.cjs.js",
+  "module": "dist/test-utils.esm.js",
+  "license": "MIT",
+  "repository": "https://github.com/changesets/changesets/tree/master/packages/test-utils",
+  "dependencies": {
+    "@changesets/logger": "^0.0.0",
+    "@changesets/types": "^0.3.0"
+  },
+  "devDependencies": {
+    "jest-fixtures": "^0.5.0",
+    "outdent": "^0.5.0"
+  }
+}
diff --git a/packages/test-utils/src/index.test.ts b/packages/test-utils/src/index.test.ts
new file mode 100644
index 0000000..e20da89
--- /dev/null
+++ b/packages/test-utils/src/index.test.ts
@@ -0,0 +1,19 @@
+import { log } from "@changesets/logger";
+import { temporarilySilenceLogs } from "./";
+
+describe("temporarilySilenceLogs", () => {
+  temporarilySilenceLogs();
+  describe("log", () => {
+    let originalConsoleLog = console.log;
+    beforeEach(() => {
+      console.log = jest.fn();
+    });
+    afterEach(() => {
+      console.log = originalConsoleLog;
+    });
+    it("should not call console.log", () => {
+      log("Log message");
+      expect(console.log).not.toHaveBeenCalled();
+    });
+  });
+});
diff --git a/packages/test-utils/src/index.ts b/packages/test-utils/src/index.ts
new file mode 100644
index 0000000..01f01b5
--- /dev/null
+++ b/packages/test-utils/src/index.ts
@@ -0,0 +1,31 @@
+/**
+ * Reason for eslint disable import/no-commonjs
+ * Technically reassigning imports is not allowed and
+ * Rollup errors at compile time on this(but the Babel
+ * transform that's running in jest makes it work there),
+ * making this a require should be fine.
+ */
+// eslint-disable-next-line import/no-commonjs
+const logger = require("@changesets/logger");
+
+export const temporarilySilenceLogs = () => {
+  const originalError = logger.error;
+  const originalInfo = logger.info;
+  const originalLog = logger.log;
+  const originalWarn = logger.warn;
+  const originalSuccess = logger.success;
+  beforeEach(() => {
+    logger.error = jest.fn();
+    logger.info = jest.fn();
+    logger.log = jest.fn();
+    logger.warn = jest.fn();
+    logger.success = jest.fn();
+  });
+  afterEach(() => {
+    logger.error = originalError;
+    logger.info = originalInfo;
+    logger.log = originalLog;
+    logger.warn = originalWarn;
+    logger.success = originalSuccess;
+  });
+};