[Pkg-javascript-commits] [node-yarnpkg] 01/03: New upstream version 1.2.1

Paolo Greppi paolog-guest at moszumanska.debian.org
Tue Oct 31 07:36:04 UTC 2017


This is an automated email from the git hooks/post-receive script.

paolog-guest pushed a commit to branch master
in repository node-yarnpkg.

commit 8dfe7d4a776a1c8aba807124df050cb8d4da876a
Author: Paolo Greppi <paolo.greppi at libpf.com>
Date:   Tue Oct 31 08:22:15 2017 +0100

    New upstream version 1.2.1
---
 .flowconfig                                        |   1 +
 .travis.yml                                        |  18 +-
 CONTRIBUTING.md                                    |   2 +
 Dockerfile.dev                                     |   3 +-
 README.md                                          |   2 +-
 __tests__/__snapshots__/integration.js.snap        |  12 +
 __tests__/commands/add.js                          |  63 ++++-
 __tests__/commands/cache.js                        |  66 +++++
 __tests__/commands/info.js                         |   3 +-
 __tests__/commands/init.js                         |  24 ++
 __tests__/commands/install/integration.js          |  37 ++-
 __tests__/commands/list.js                         |  33 ++-
 __tests__/commands/outdated.js                     |  49 ++++
 __tests__/commands/pack.js                         |  18 ++
 __tests__/commands/remove.js                       |  40 ++-
 __tests__/commands/run.js                          |  14 +-
 __tests__/commands/upgrade.js                      |  89 ++++++
 .../add-with-deep-peer-dependencies/a/package.json |   8 +
 .../add-with-deep-peer-dependencies/b/package.json |   7 +
 .../add-with-deep-peer-dependencies/c/package.json |   4 +
 .../add-with-deep-peer-dependencies/package.json   |   7 +
 __tests__/fixtures/add/install-strict-all/.yarnrc  |   2 +
 .../fixtures/global/add-with-prefix-env/.yarnrc    |   2 +-
 .../init/init-nested/inner-folder/.gitkeep         |   0
 __tests__/fixtures/init/init-nested/package.json   |   3 +
 .../install/install-file-no-package/bar/bar.js     |   0
 .../install/install-file-no-package/package.json   |   6 +
 .../dep-a/package.json                             |   3 +
 .../dep-c/package.json                             |  10 +
 .../dep-d/package.json                             |   7 +
 .../dep-e/package.json                             |   4 +
 .../install/install-with-null-version/package.json |   5 +
 .../b/package.json                                 |   6 +
 .../optimize-version-with-alternation/package.json |   6 +
 .../peer-dep-same-subtree/a-1.0.0/package.json     |   1 +
 .../peer-dep-same-subtree/a-1.1.0/package.json     |   1 +
 .../install/peer-dep-same-subtree/b/package.json   |   7 +
 .../install/peer-dep-same-subtree/c/package.json   |   7 +
 .../install/peer-dep-same-subtree/d/package.json   |   7 +
 .../install/peer-dep-same-subtree/package.json     |   9 +
 .../install/workspaces-install-basic/yarn.lock     |  19 ++
 .../npm_config_argv_env_vars/log-command.js        |   8 +-
 .../outdated/workspaces/child-a/package.json       |   7 +
 .../outdated/workspaces/child-b/package.json       |   8 +
 .../fixtures/outdated/workspaces/package.json      |  11 +
 __tests__/fixtures/outdated/workspaces/yarn.lock   |  19 ++
 .../pack/bundled-dependencies/a/package.json       |   7 +
 .../pack/bundled-dependencies/b/package.json       |   4 +
 .../fixtures/pack/bundled-dependencies/index.js    |   1 +
 .../pack/bundled-dependencies/package.json         |  14 +
 .../without-manifest/node_modules/.yarn-integrity  |   1 +
 .../without-manifest/node_modules/dep-a/index.js   |  33 +++
 .../fixtures/remove/without-manifest/package.json  |   5 +
 .../fixtures/remove/without-manifest/yarn.lock     |   5 +
 .../ansi-regex/-/ansi-regex-1.0.0.tgz.bin          | Bin 0 -> 1544 bytes
 .../array-union/-/array-union-1.0.1.tgz.bin        | Bin 0 -> 1311 bytes
 .../array-uniq/-/array-uniq-1.0.1.tgz.bin          | Bin 0 -> 1805 bytes
 .../lodash/-/lodash-2.4.2.tgz.bin                  | Bin 0 -> 197417 bytes
 .../request-cache/GET/registry.yarnpkg.com/pn.bin  | Bin 0 -> 1012 bytes
 .../right-pad/-/right-pad-1.0.0.tgz.bin            | Bin 0 -> 2089 bytes
 .../upgrade/with-subdep-also-direct/package.json   |   6 +
 .../upgrade/with-subdep-also-direct/yarn.lock      |  10 +
 .../fixtures/upgrade/with-subdep/package.json      |   6 +
 __tests__/fixtures/upgrade/with-subdep/yarn.lock   |  18 ++
 .../upgrade/workspaces/child-a/package.json        |   7 +
 .../upgrade/workspaces/child-b/package.json        |   7 +
 __tests__/fixtures/upgrade/workspaces/package.json |  11 +
 __tests__/fixtures/upgrade/workspaces/yarn.lock    |  15 +
 __tests__/index.js                                 |   9 +-
 __tests__/integration.js                           | 304 +++++++++++++++++----
 __tests__/lifecycle-scripts.js                     |  17 +-
 __tests__/lockfile.js                              |  29 ++
 __tests__/package-request.js                       |  32 ++-
 __tests__/package-resolver.js                      |   3 +-
 .../__snapshots__/console-reporter.js.snap         |  10 +-
 __tests__/reporters/console-reporter.js            |   6 +-
 __tests__/util/git.js                              |   4 +-
 __tests__/util/path.js                             |  40 +--
 __tests__/util/root-user.js                        |  17 +-
 appveyor.yml                                       |   1 +
 bin/yarn                                           |   2 +-
 circle.yml                                         |   2 +-
 package.json                                       |  20 +-
 scripts/update-homebrew.sh                         |  14 +-
 scripts/update-npm.sh                              |   2 +-
 src/cli/commands/add.js                            |  26 +-
 src/cli/commands/autoclean.js                      |   1 -
 src/cli/commands/cache.js                          |  13 +-
 src/cli/commands/config.js                         |  45 +++
 src/cli/commands/global.js                         |   9 +-
 src/cli/commands/init.js                           |   2 +
 src/cli/commands/install.js                        |  93 +++++--
 src/cli/commands/list.js                           |  12 +-
 src/cli/commands/outdated.js                       |  26 +-
 src/cli/commands/publish.js                        |  10 +-
 src/cli/commands/remove.js                         |  10 +-
 src/cli/commands/run.js                            |  11 +-
 src/cli/commands/upgrade-interactive.js            |  62 +++--
 src/cli/commands/upgrade.js                        | 196 ++++++++-----
 src/cli/index.js                                   |  80 +++---
 src/config.js                                      |  36 +--
 src/constants.js                                   |   2 +
 src/lockfile/parse.js                              |  21 +-
 src/package-hoister.js                             |   2 +-
 src/package-linker.js                              | 126 ++++++---
 src/package-reference.js                           |   8 +-
 src/package-request.js                             |  30 +-
 src/package-resolver.js                            |  67 ++++-
 src/rc.js                                          |  21 +-
 src/registries/base-registry.js                    |  10 +-
 src/registries/npm-registry.js                     |   6 +-
 src/reporters/console/console-reporter.js          |   6 +-
 src/reporters/console/progress-bar.js              |   6 +-
 src/reporters/lang/en.js                           |  15 +-
 src/resolvers/exotics/bitbucket-resolver.js        |  14 +
 src/types.js                                       |   4 +
 src/util/blocking-queue.js                         |   5 +
 src/util/execute-lifecycle-script.js               |   4 +-
 src/util/fs.js                                     | 154 ++++++-----
 src/util/git.js                                    |  71 +++--
 src/util/misc.js                                   |  18 --
 src/util/normalize-manifest/fix.js                 |   8 +-
 src/util/path.js                                   |  10 +-
 src/util/request-manager.js                        |  48 ++--
 yarn.lock                                          | 292 +++++++++++++++++++-
 125 files changed, 2296 insertions(+), 604 deletions(-)

diff --git a/.flowconfig b/.flowconfig
index c9ded29..7eb6cb8 100644
--- a/.flowconfig
+++ b/.flowconfig
@@ -4,6 +4,7 @@
 <PROJECT_ROOT>/lib-legacy/.*
 <PROJECT_ROOT>/lib/.*
 <PROJECT_ROOT>/node_modules/babel.*
+<PROJECT_ROOT>/node_modules/graphql.*
 <PROJECT_ROOT>/node_modules/y18n/test/.*
 <PROJECT_ROOT>/updates/.*
 
diff --git a/.travis.yml b/.travis.yml
index 6f4b065..6fab908 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,6 @@
 ---
-# Without sudo, Travis CI uses AUFS which causes issues with our
-# tests when we write stuff and try to read it back right after to
-# verfify our expectations.
+# Without sudo, Travis CI uses AUFS which is too slow for our I/O heavy tests.
+# With AUFS, we get lots of timeouts and increased build times.
 # See https://docs.travis-ci.com/user/ci-environment/#Virtualization-environments
 sudo: required
 
@@ -10,6 +9,7 @@ git:
 language: node_js
 
 node_js:
+  - "8"
   - "6"
   - "4"
 
@@ -38,14 +38,24 @@ matrix:
       node_js: "4"
     - env: TEST_TYPE="lint"
       node_js: "4"
+    - env: TEST_TYPE="build-dist"
+      node_js: "6"
+    - env: TEST_TYPE="check-lockfile"
+      node_js: "6"
+    - env: TEST_TYPE="lint"
+      node_js: "6"
+
   include:
     - os: osx
       node_js: "6"
       env: TEST_TYPE="test-only"
+    - os: osx
+      node_js: "8"
+      env: TEST_TYPE="test-only"
 
 before_install:
   - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
-      RAMDISK_SIZE=320;
+      RAMDISK_SIZE=330;
       RAMDISK_SECTORS=$(( $RAMDISK_SIZE * 1024 * 1024 / 512 ));
       RAMDISK=$(hdiutil attach -nomount ram://$RAMDISK_SECTORS);
       newfs_hfs -v yarn_ramfs $RAMDISK;
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 97c2cd3..c72e5d8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -5,6 +5,8 @@ please read the [code of conduct](CODE_OF_CONDUCT.md).
 
 ## Setup
 
+You need at least the latest version of Node 6 to work on Yarn.
+
 1. Install yarn on your system: https://yarnpkg.com/en/docs/install
 1. Fork the repo: https://github.com/yarnpkg/yarn
 1. Run the following commands:
diff --git a/Dockerfile.dev b/Dockerfile.dev
index 64b3d77..46168e0 100644
--- a/Dockerfile.dev
+++ b/Dockerfile.dev
@@ -1,8 +1,7 @@
 # Dockerfile for building Yarn.
 # docker build -t yarnpkg/dev -f Dockerfile.dev .
 
-FROM node:7
-MAINTAINER Daniel Lo Nigro <yarn at dan.cx>
+FROM node:8
 
 # Debian packages
 RUN apt-get -y update && \
diff --git a/README.md b/README.md
index 5c91a5c..bdc6d6c 100644
--- a/README.md
+++ b/README.md
@@ -13,8 +13,8 @@
   <a href="https://circleci.com/gh/yarnpkg/yarn"><img alt="Circle Status" src="https://circleci.com/gh/yarnpkg/yarn.svg?style=shield&circle-token=5f0a78473b0f440afb218bf2b82323cc6b3cb43f"></a>
   <a href="https://ci.appveyor.com/project/kittens/yarn/branch/master"><img alt="Appveyor Status" src="https://ci.appveyor.com/api/projects/status/0xdv8chwe2kmk463?svg=true"></a>
   <a href="https://discord.gg/yarnpkg"><img alt="Discord Chat" src="https://img.shields.io/discord/226791405589233664.svg"></a>
+  <a href="http://commitizen.github.io/cz-cli/"><img alt="Commitizen friendly" src="https://img.shields.io/badge/commitizen-friendly-brightgreen.svg"></a>
 </p>
-
 ---
 
 **Fast:** Yarn caches every package it has downloaded, so it never needs to download the same package again. It also does almost everything concurrently to maximize resource utilization. This means even faster installs.
diff --git a/__tests__/__snapshots__/integration.js.snap b/__tests__/__snapshots__/integration.js.snap
new file mode 100644
index 0000000..88b894d
--- /dev/null
+++ b/__tests__/__snapshots__/integration.js.snap
@@ -0,0 +1,12 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`yarnrc-args 1`] = `
+"{\\"type\\":\\"info\\",\\"data\\":\\"No lockfile found.\\"}
+{\\"type\\":\\"step\\",\\"data\\":{\\"message\\":\\"Resolving packages\\",\\"current\\":1,\\"total\\":4}}
+{\\"type\\":\\"step\\",\\"data\\":{\\"message\\":\\"Fetching packages\\",\\"current\\":2,\\"total\\":4}}
+{\\"type\\":\\"step\\",\\"data\\":{\\"message\\":\\"Linking dependencies\\",\\"current\\":3,\\"total\\":4}}
+{\\"type\\":\\"step\\",\\"data\\":{\\"message\\":\\"Building fresh packages\\",\\"current\\":4,\\"total\\":4}}
+{\\"type\\":\\"success\\",\\"data\\":\\"Saved lockfile.\\"}
+{\\"type\\":\\"success\\",\\"data\\":\\"Saved 1 new dependency.\\"}
+{\\"type\\":\\"tree\\",\\"data\\":{\\"type\\":\\"newDependencies\\",\\"trees\\":[{\\"name\\":\\"left-pad at 1.1.3\\",\\"children\\":[],\\"hint\\":null,\\"color\\":null,\\"depth\\":0}]}}"
+`;
diff --git a/__tests__/commands/add.js b/__tests__/commands/add.js
index 9f69035..2182bad 100644
--- a/__tests__/commands/add.js
+++ b/__tests__/commands/add.js
@@ -42,15 +42,32 @@ const runAdd = buildRun.bind(
   },
 );
 
-test.concurrent('add without --dev should fail on the workspace root', async () => {
+test.concurrent('add without --ignore-workspace-root-check should fail on the workspace root', async () => {
   await runInstall({}, 'simple-worktree', async (config, reporter): Promise<void> => {
     await expect(add(config, reporter, {}, ['left-pad'])).rejects.toBeDefined();
   });
 });
 
-test.concurrent("add with --dev shouldn't fail on the workspace root", async () => {
+test.concurrent("add with --ignore-workspace-root-check shouldn't fail on the workspace root", async () => {
   await runInstall({}, 'simple-worktree', async (config, reporter): Promise<void> => {
-    await expect(add(config, reporter, {dev: true}, ['left-pad']));
+    await expect(add(config, reporter, {ignoreWorkspaceRootCheck: true}, ['left-pad'])).resolves.toBeUndefined();
+  });
+});
+
+test.concurrent('adding to the workspace root should preserve workspace packages in lockfile', async () => {
+  await runInstall({}, 'workspaces-install-basic', async (config, reporter): Promise<void> => {
+    await add(config, reporter, {ignoreWorkspaceRootCheck: true}, ['max-safe-integer at 1.0.0']);
+
+    expect(await fs.exists(`${config.cwd}/yarn.lock`)).toEqual(true);
+
+    const pkg = await fs.readJson(path.join(config.cwd, 'package.json'));
+    expect(pkg.dependencies).toEqual({'left-pad': '1.1.3', 'max-safe-integer': '1.0.0'});
+
+    const lockfile = explodeLockfile(await fs.readFile(path.join(config.cwd, 'yarn.lock')));
+    expect(lockfile).toHaveLength(15);
+    expect(lockfile.indexOf('isarray at 2.0.1:')).toEqual(0);
+    expect(lockfile.indexOf('left-pad at 1.1.3:')).toEqual(3);
+    expect(lockfile.indexOf('max-safe-integer at 1.0.0:')).toEqual(6);
   });
 });
 
@@ -223,6 +240,17 @@ test.concurrent('add save-prefix should not expand ~ to home dir', (): Promise<v
   });
 });
 
+test.concurrent('add save-exact should make all package.json strict', (): Promise<void> => {
+  return runAdd(['left-pad'], {}, 'install-strict-all', async config => {
+    const lockfile = explodeLockfile(await fs.readFile(path.join(config.cwd, 'yarn.lock')));
+
+    expect(lockfile[0]).toMatch(/^left-pad@\d+\.\d+\.\d+:$/);
+    expect(JSON.parse(await fs.readFile(path.join(config.cwd, 'package.json'))).dependencies['left-pad']).toMatch(
+      /^\d+\.\d+\.\d+$/,
+    );
+  });
+});
+
 test.concurrent('add with new dependency should be deterministic 3', (): Promise<void> => {
   return runAdd([], {}, 'install-should-cleanup-when-package-json-changed-3', async (config, reporter) => {
     // expecting yarn check after installation not to fail
@@ -825,7 +853,7 @@ test.concurrent('warns when peer dependency is incorrect during add', (): Promis
   );
 });
 
-test.concurrent('should only refer to root to satisfy peer dependency', (): Promise<void> => {
+test.concurrent('should only refer to higher levels to satisfy peer dependency', (): Promise<void> => {
   return buildRun(
     reporters.BufferReporter,
     fixturesLoc,
@@ -834,13 +862,8 @@ test.concurrent('should only refer to root to satisfy peer dependency', (): Prom
       await add.init();
 
       const output = reporter.getBuffer();
-      const warnings = output.filter(entry => entry.type === 'warning');
-
-      expect(
-        warnings.some(warning => {
-          return warning.data.toString().toLowerCase().indexOf('incorrect peer') > -1;
-        }),
-      ).toEqual(true);
+      const warnings = output.filter(entry => entry.type === 'warning').map(entry => entry.data);
+      expect(warnings).toEqual(expect.arrayContaining([expect.stringContaining('incorrect peer')]));
     },
     ['file:c'],
     {},
@@ -848,6 +871,24 @@ test.concurrent('should only refer to root to satisfy peer dependency', (): Prom
   );
 });
 
+test.concurrent('should refer to deeper dependencies to satisfy peer dependency', (): Promise<void> => {
+  return buildRun(
+    reporters.BufferReporter,
+    fixturesLoc,
+    async (args, flags, config, reporter, lockfile): Promise<void> => {
+      const add = new Add(args, flags, config, reporter, lockfile);
+      await add.init();
+
+      const output = reporter.getBuffer();
+      const warnings = output.filter(entry => entry.type === 'warning').map(entry => entry.data);
+      expect(warnings).not.toEqual(expect.arrayContaining([expect.stringContaining('peer')]));
+    },
+    [],
+    {},
+    'add-with-deep-peer-dependencies',
+  );
+});
+
 test.concurrent('should retain build artifacts after add when missing integrity file', (): Promise<void> => {
   return buildRun(
     reporters.BufferReporter,
diff --git a/__tests__/commands/cache.js b/__tests__/commands/cache.js
index 7def4df..3122883 100644
--- a/__tests__/commands/cache.js
+++ b/__tests__/commands/cache.js
@@ -42,6 +42,72 @@ test('ls with scoped package', async (): Promise<void> => {
   });
 });
 
+test('ls with filter that matches cache', async (): Promise<void> => {
+  await runInstall({}, 'artifacts-finds-and-saves', async (config): Promise<void> => {
+    const out = new stream.PassThrough();
+    const reporter = new reporters.JSONReporter({stdout: out});
+    await run(config, reporter, {pattern: 'dummy'}, ['list']);
+    const stdout = String(out.read());
+    expect(stdout).toContain('dummy');
+    expect(stdout).toContain('0.0.0');
+  });
+});
+
+test('ls with filter that matches cache with wildcard', async (): Promise<void> => {
+  await runInstall({}, 'artifacts-finds-and-saves', async (config): Promise<void> => {
+    const out = new stream.PassThrough();
+    const reporter = new reporters.JSONReporter({stdout: out});
+    await run(config, reporter, {pattern: 'dum*'}, ['list']);
+    const stdout = String(out.read());
+    expect(stdout).toContain('dummy');
+    expect(stdout).toContain('0.0.0');
+  });
+});
+
+test('ls with multiple patterns, one matching', async (): Promise<void> => {
+  await runInstall({}, 'artifacts-finds-and-saves', async (config): Promise<void> => {
+    const out = new stream.PassThrough();
+    const reporter = new reporters.JSONReporter({stdout: out});
+    await run(config, reporter, {pattern: 'dum|dummy'}, ['list']);
+    const stdout = String(out.read());
+    expect(stdout).toContain('dummy');
+    expect(stdout).toContain('0.0.0');
+  });
+});
+
+test('ls with pattern that only partially matches', async (): Promise<void> => {
+  await runInstall({}, 'artifacts-finds-and-saves', async (config): Promise<void> => {
+    const out = new stream.PassThrough();
+    const reporter = new reporters.JSONReporter({stdout: out});
+    await run(config, reporter, {pattern: 'dum'}, ['list']);
+    const stdout = String(out.read());
+    expect(stdout).toContain('dummy');
+    expect(stdout).toContain('0.0.0');
+  });
+});
+
+test('ls with filter that does not match', async (): Promise<void> => {
+  await runInstall({}, 'artifacts-finds-and-saves', async (config): Promise<void> => {
+    const out = new stream.PassThrough();
+    const reporter = new reporters.JSONReporter({stdout: out});
+    await run(config, reporter, {pattern: 'noMatch'}, ['list']);
+    const stdout = String(out.read());
+    expect(stdout).not.toContain('dummy');
+    expect(stdout).not.toContain('0.0.0');
+  });
+});
+
+test('ls filter by pattern with scoped package', async (): Promise<void> => {
+  await runInstall({}, 'install-from-authed-private-registry', async (config): Promise<void> => {
+    const out = new stream.PassThrough();
+    const reporter = new reporters.JSONReporter({stdout: out});
+    await run(config, reporter, {pattern: '@types/*'}, ['list']);
+    const stdout = String(out.read());
+    expect(stdout).toContain('@types/lodash');
+    expect(stdout).toContain('4.14.37');
+  });
+});
+
 test('dir', async (): Promise<void> => {
   await runCache(['dir'], {}, '', (config, reporter, stdout) => {
     expect(stdout).toContain(JSON.stringify(config.cacheFolder));
diff --git a/__tests__/commands/info.js b/__tests__/commands/info.js
index a34e89a..c61b1a2 100644
--- a/__tests__/commands/info.js
+++ b/__tests__/commands/info.js
@@ -43,11 +43,10 @@ const expectedKeys = [
   'license',
   'dist',
   'directories',
-  'scripts',
 ];
 
 // yarn now ships as built, single JS files so it has no dependencies and no scripts
-const unexpectedKeys = ['dependencies', 'devDependencies'];
+const unexpectedKeys = ['dependencies', 'devDependencies', 'scripts'];
 
 test.concurrent('without arguments and in directory containing a valid package file', (): Promise<void> => {
   return runInfo([], {}, 'local', (config, output): ?Promise<void> => {
diff --git a/__tests__/commands/init.js b/__tests__/commands/init.js
index 32defa5..b37d3ef 100644
--- a/__tests__/commands/init.js
+++ b/__tests__/commands/init.js
@@ -12,6 +12,30 @@ jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000;
 
 const fixturesLoc = path.join(__dirname, '..', 'fixtures', 'init');
 
+test.concurrent('init should create package.json on current cwd', (): Promise<void> => {
+  let initialParentManifest;
+
+  return buildRun(
+    ConsoleReporter,
+    fixturesLoc,
+    (args, flags, config, reporter, lockfile): Promise<void> => {
+      return runInit(config, reporter, flags, args);
+    },
+    [],
+    {yes: true},
+    {source: 'init-nested', cwd: 'inner-folder'},
+    async (config): Promise<void> => {
+      const {cwd} = config;
+
+      expect(await fs.exists(path.join(cwd, 'package.json'))).toEqual(true);
+      expect(await fs.readFile(path.join(cwd, '..', 'package.json'))).toEqual(initialParentManifest);
+    },
+    async (cwd): Promise<void> => {
+      initialParentManifest = await fs.readFile(path.join(cwd, '..', 'package.json'));
+    },
+  );
+});
+
 test.concurrent('init --yes should create package.json with defaults', (): Promise<void> => {
   return buildRun(
     ConsoleReporter,
diff --git a/__tests__/commands/install/integration.js b/__tests__/commands/install/integration.js
index 07278e0..7fd8051 100644
--- a/__tests__/commands/install/integration.js
+++ b/__tests__/commands/install/integration.js
@@ -55,6 +55,16 @@ test.concurrent('install should not hoist packages above their peer dependencies
   });
 });
 
+test.concurrent('install should resolve peer dependencies from same subtrees', async () => {
+  await runInstall({}, 'peer-dep-same-subtree', async (config): Promise<void> => {
+    expect(JSON.parse(await fs.readFile(`${config.cwd}/node_modules/d/node_modules/a/package.json`)).version).toEqual(
+      '1.0.0',
+    );
+    expect(JSON.parse(await fs.readFile(`${config.cwd}/node_modules//a/package.json`)).version).toEqual('1.1.0');
+    expect(await fs.exists(`${config.cwd}/node_modules/c/node_modules/a`)).toEqual(false);
+  });
+});
+
 test.concurrent('install optional subdependencies by default', async () => {
   await runInstall({}, 'install-optional-dependencies', async (config): Promise<void> => {
     expect(await fs.exists(`${config.cwd}/node_modules/dep-b`)).toEqual(true);
@@ -64,6 +74,9 @@ test.concurrent('install optional subdependencies by default', async () => {
 test.concurrent('installing with --ignore-optional should not install optional subdependencies', async () => {
   await runInstall({ignoreOptional: true}, 'install-optional-dependencies', async (config): Promise<void> => {
     expect(await fs.exists(`${config.cwd}/node_modules/dep-b`)).toEqual(false);
+    expect(await fs.exists(`${config.cwd}/node_modules/dep-c`)).toEqual(true);
+    expect(await fs.exists(`${config.cwd}/node_modules/dep-d`)).toEqual(true);
+    expect(await fs.exists(`${config.cwd}/node_modules/dep-e`)).toEqual(true);
   });
 });
 
@@ -483,13 +496,20 @@ test.concurrent("don't install with file: protocol as default if target is a fil
   return expect(runInstall({lockfile: false}, 'install-file-as-default-no-file')).rejects.toBeDefined();
 });
 
-test.concurrent("don't install with file: protocol as default if target does not have package.json", (): Promise<
+test.concurrent("don't install with implicit file: protocol if target does not have package.json", (): Promise<
   void,
 > => {
   // $FlowFixMe
   return expect(runInstall({lockfile: false}, 'install-file-as-default-no-package')).rejects.toBeDefined();
 });
 
+test.concurrent('install with explicit file: protocol if target does not have package.json', (): Promise<void> => {
+  return runInstall({}, 'install-file-no-package', async config => {
+    expect(await fs.exists(path.join(config.cwd, 'node_modules', 'foo', 'bar.js'))).toEqual(true);
+    expect(await fs.exists(path.join(config.cwd, 'node_modules', 'bar', 'bar.js'))).toEqual(true);
+  });
+});
+
 test.concurrent("don't install with file: protocol as default if target is valid semver", (): Promise<void> => {
   return runInstall({}, 'install-file-as-default-no-semver', async config => {
     expect(await fs.readFile(path.join(config.cwd, 'node_modules', 'foo', 'package.json'))).toMatchSnapshot(
@@ -620,6 +640,12 @@ test.concurrent('install with comments in manifest', (): Promise<void> => {
   });
 });
 
+test.concurrent('install with null versions in manifest', (): Promise<void> => {
+  return runInstall({}, 'install-with-null-version', async config => {
+    expect(await fs.exists(path.join(config.cwd, 'node_modules', 'left-pad'))).toEqual(true);
+  });
+});
+
 test.concurrent('run install scripts in the order when one dependency does not have install script', (): Promise<
   void,
 > => {
@@ -1033,8 +1059,7 @@ test.concurrent('transitive file: dependencies should work', (): Promise<void> =
   });
 });
 
-// Unskip once https://github.com/yarnpkg/yarn/issues/3778 is resolved
-test.skip('unbound transitive dependencies should not conflict with top level dependency', async () => {
+test('unbound transitive dependencies should not conflict with top level dependency', async () => {
   await runInstall({flat: true}, 'install-conflicts', async config => {
     expect((await fs.readJson(path.join(config.cwd, 'node_modules', 'left-pad', 'package.json'))).version).toEqual(
       '1.0.0',
@@ -1042,6 +1067,12 @@ test.skip('unbound transitive dependencies should not conflict with top level de
   });
 });
 
+test('manifest optimization respects versions with alternation', async () => {
+  await runInstall({flat: true}, 'optimize-version-with-alternation', async config => {
+    expect(await getPackageVersion(config, 'lodash')).toEqual('2.4.2');
+  });
+});
+
 test.concurrent('top level patterns should match after install', (): Promise<void> => {
   return runInstall({}, 'top-level-pattern-check', async (config, reporter) => {
     let integrityError = false;
diff --git a/__tests__/commands/list.js b/__tests__/commands/list.js
index bc1aca7..0805073 100644
--- a/__tests__/commands/list.js
+++ b/__tests__/commands/list.js
@@ -81,6 +81,18 @@ describe('list', () => {
     });
   });
 
+  test.concurrent('accepts a pattern', (): Promise<void> => {
+    return runList([], {pattern: 'is-plain-obj'}, 'one-arg', (config, reporter): ?Promise<void> => {
+      const rprtr = new BufferReporter({});
+      const tree = reporter.getBuffer().slice(-1);
+      const trees = [makeTree('is-plain-obj at 1.1.0')];
+
+      rprtr.tree('list', trees);
+
+      expect(tree).toEqual(rprtr.getBuffer());
+    });
+  });
+
   test.concurrent('should not throw when list is called with resolutions field', (): Promise<void> => {
     return runList([], {}, {source: '', cwd: 'resolutions'}, (config, reporter): ?Promise<void> => {
       const rprtr = new BufferReporter({});
@@ -106,19 +118,24 @@ describe('list', () => {
     });
   });
 
-  test.concurrent('matches exactly without glob', (): Promise<void> => {
+  test.concurrent('matches exactly without glob in argument', (): Promise<void> => {
     return runList(['gulp'], {}, 'glob-arg', (config, reporter): ?Promise<void> => {
       const rprtr = new BufferReporter({});
       const tree = reporter.getBuffer().slice(-1);
       const trees = [makeTree('gulp at 3.9.1', {color: 'bold'})];
 
+      const messageParts = reporter.lang('deprecatedListArgs').split('undefined');
+      const output = reporter.getBuffer();
+      const hasWarningMessage = output.some(messages => messageParts.indexOf(String(messages.data)) > -1);
+      expect(hasWarningMessage).toBe(true);
+
       rprtr.tree('list', trees);
 
       expect(tree).toEqual(rprtr.getBuffer());
     });
   });
 
-  test.concurrent('expands a glob', (): Promise<void> => {
+  test.concurrent('expands a glob in argument', (): Promise<void> => {
     return runList(['gulp*'], {}, 'glob-arg', (config, reporter): ?Promise<void> => {
       const rprtr = new BufferReporter({});
       const tree = reporter.getBuffer().slice(-1);
@@ -130,6 +147,18 @@ describe('list', () => {
     });
   });
 
+  test.concurrent('expands a glob in pattern', (): Promise<void> => {
+    return runList([], {pattern: 'gulp*'}, 'glob-arg', (config, reporter): ?Promise<void> => {
+      const rprtr = new BufferReporter({});
+      const tree = reporter.getBuffer().slice(-1);
+      const trees = [makeTree('gulp at 3.9.1', {color: 'bold'}), makeTree('gulp-babel at 6.1.2', {color: 'bold'})];
+
+      rprtr.tree('list', trees);
+
+      expect(tree).toEqual(rprtr.getBuffer());
+    });
+  });
+
   test('lists all dependencies when not production', (): Promise<void> => {
     return runList([], {}, 'dev-deps-prod', (config, reporter): ?Promise<void> => {
       expect(reporter.getBuffer()).toMatchSnapshot();
diff --git a/__tests__/commands/outdated.js b/__tests__/commands/outdated.js
index 1bfe449..27f63c1 100644
--- a/__tests__/commands/outdated.js
+++ b/__tests__/commands/outdated.js
@@ -22,6 +22,18 @@ const runOutdated = buildRun.bind(
           write() {},
         }),
       });
+
+      // mock all formatters so we can assert on all of them
+      const mockFormat = {};
+      Object.keys(this.format).forEach(key => {
+        mockFormat[key] = jest.fn(this.format[key]);
+      });
+      // $FlowFixMe
+      this.format = mockFormat;
+    }
+
+    info(msg: string) {
+      // Overwrite to not interfere with the table output
     }
   },
   fixturesLoc,
@@ -56,6 +68,7 @@ test.concurrent('works with no arguments', (): Promise<void> => {
     const json: Object = JSON.parse(out);
 
     expect(json.data.body.length).toBe(1);
+    expect(reporter.format.green).toHaveBeenCalledWith('left-pad');
   });
 });
 
@@ -65,6 +78,7 @@ test.concurrent('works with single argument', (): Promise<void> => {
 
     expect(json.data.body.length).toBe(1);
     expect(json.data.body[0][0]).toBe('max-safe-integer');
+    expect(reporter.format.green).toHaveBeenCalledWith('max-safe-integer');
   });
 });
 
@@ -77,6 +91,8 @@ test.concurrent('works with multiple arguments', (): Promise<void> => {
     expect(json.data.body.length).toBe(2);
     expect(json.data.body[0][0]).toBe('left-pad');
     expect(json.data.body[1][0]).toBe('max-safe-integer');
+    expect(reporter.format.yellow).toHaveBeenCalledWith('left-pad');
+    expect(reporter.format.green).toHaveBeenCalledWith('max-safe-integer');
   });
 });
 
@@ -95,7 +111,9 @@ test.concurrent('works with exotic resolvers', (): Promise<void> => {
 
     expect(json.data.body.length).toBe(2);
     expect(json.data.body[0]).toEqual(first);
+    expect(reporter.format.red).toHaveBeenCalledWith('max-safe-integer');
     expect(json.data.body[1]).toEqual(second);
+    expect(reporter.format.red).toHaveBeenCalledWith('yarn');
   });
 });
 
@@ -112,6 +130,7 @@ test.concurrent('shows when wanted > current and current > latest', (): Promise<
     expect(json.data.body.length).toBe(1);
     expect(json.data.body[0][0]).toBe('webpack');
     expect(semver.lt(json.data.body[0][1], json.data.body[0][2])).toBe(true);
+    expect(reporter.format.yellow).toHaveBeenCalledWith('webpack');
   });
 });
 
@@ -124,9 +143,39 @@ test.concurrent('displays correct dependency types', (): Promise<void> => {
     expect(json.data.body.length).toBe(3);
     expect(body[0][0]).toBe('is-online');
     expect(body[0][4]).toBe('optionalDependencies');
+    expect(reporter.format.red).toHaveBeenCalledWith('is-online');
     expect(body[1][0]).toBe('left-pad');
     expect(body[1][4]).toBe('dependencies');
+    expect(reporter.format.yellow).toHaveBeenCalledWith('left-pad');
     expect(body[2][0]).toBe('max-safe-integer');
     expect(body[2][4]).toBe('devDependencies');
+    expect(reporter.format.green).toHaveBeenCalledWith('max-safe-integer');
+  });
+});
+
+test.concurrent('shows dependencies from entire workspace', async (): Promise<void> => {
+  await runOutdated([], {}, 'workspaces', (config, reporter, out): ?Promise<void> => {
+    const json: Object = JSON.parse(out);
+
+    expect(json.data.body).toHaveLength(4);
+    expect(json.data.body[0][0]).toBe('left-pad');
+    expect(json.data.body[0][1]).toBe('1.0.0');
+    expect(json.data.body[1][0]).toBe('left-pad');
+    expect(json.data.body[1][1]).toBe('1.0.1');
+    expect(json.data.body[2][0]).toBe('max-safe-integer');
+    expect(json.data.body[3][0]).toBe('right-pad');
+  });
+
+  const childFixture = {source: 'workspaces', cwd: 'child-a'};
+  return runOutdated([], {}, childFixture, (config, reporter, out): ?Promise<void> => {
+    const json: Object = JSON.parse(out);
+
+    expect(json.data.body).toHaveLength(4);
+    expect(json.data.body[0][0]).toBe('left-pad');
+    expect(json.data.body[0][1]).toBe('1.0.0');
+    expect(json.data.body[1][0]).toBe('left-pad');
+    expect(json.data.body[1][1]).toBe('1.0.1');
+    expect(json.data.body[2][0]).toBe('max-safe-integer');
+    expect(json.data.body[3][0]).toBe('right-pad');
   });
 });
diff --git a/__tests__/commands/pack.js b/__tests__/commands/pack.js
index f0c9c11..b3ebc26 100644
--- a/__tests__/commands/pack.js
+++ b/__tests__/commands/pack.js
@@ -146,3 +146,21 @@ test.concurrent('pack should exclude all files in dot-directories if not in file
     expect(files.indexOf('a.js')).toEqual(-1);
   });
 });
+
+// Broken: https://github.com/yarnpkg/yarn/issues/2636
+test.skip('pack should include bundled dependencies', (): Promise<void> => {
+  return runPack([], {}, 'bundled-dependencies', async (config): Promise<void> => {
+    const {cwd} = config;
+    const files = await getFilesFromArchive(
+      path.join(cwd, 'bundled-dependencies-v1.0.0.tgz'),
+      path.join(cwd, 'bundled-dependencies-v1.0.0'),
+    );
+    const expected = [
+      'index.js',
+      'package.json',
+      path.join('node_modules', 'a', 'package.json'),
+      path.join('node_modules', 'b', 'package.json'),
+    ];
+    expect(files.sort()).toEqual(expected.sort());
+  });
+});
diff --git a/__tests__/commands/remove.js b/__tests__/commands/remove.js
index 61746ed..c2b71ae 100644
--- a/__tests__/commands/remove.js
+++ b/__tests__/commands/remove.js
@@ -1,7 +1,7 @@
 /* @flow */
 
 import {ConsoleReporter} from '../../src/reporters/index.js';
-import {run as buildRun, explodeLockfile} from './_helpers.js';
+import {explodeLockfile, makeConfigFromDirectory, run as buildRun, runInstall} from './_helpers.js';
 import {run as check} from '../../src/cli/commands/check.js';
 import {run as remove} from '../../src/cli/commands/remove.js';
 import * as fs from '../../src/util/fs.js';
@@ -133,3 +133,41 @@ test.concurrent('can prune the offline mirror', (): Promise<void> => {
     expect(await fs.exists(path.join(config.cwd, `${mirrorPath}/dep-c-1.0.0.tgz`))).toEqual(true);
   });
 });
+
+test.concurrent('removes package installed without a manifest', (): Promise<void> => {
+  return runRemove(['dep-a'], {}, 'without-manifest', async (config): Promise<void> => {
+    expect(await fs.exists(path.join(config.cwd, 'node_modules/dep-a'))).toEqual(false);
+
+    expect(JSON.parse(await fs.readFile(path.join(config.cwd, 'package.json'))).dependencies).toEqual({});
+
+    const lockFileContent = await fs.readFile(path.join(config.cwd, 'yarn.lock'));
+    const lockFileLines = explodeLockfile(lockFileContent);
+    expect(lockFileLines).toHaveLength(0);
+  });
+});
+
+test.concurrent('removes from workspace packages', async () => {
+  await runInstall({}, 'workspaces-install-basic', async (config): Promise<void> => {
+    const reporter = new ConsoleReporter({});
+
+    expect(await fs.exists(`${config.cwd}/node_modules/isarray`)).toEqual(true);
+    expect(await fs.exists(`${config.cwd}/workspace-child/node_modules/isarray`)).toEqual(false);
+
+    const childConfig = await makeConfigFromDirectory(`${config.cwd}/workspace-child`, reporter);
+    await remove(childConfig, reporter, {}, ['isarray']);
+    await check(childConfig, reporter, {verifyTree: true}, []);
+
+    expect(JSON.parse(await fs.readFile(path.join(config.cwd, 'workspace-child/package.json'))).dependencies).toEqual(
+      {},
+    );
+
+    expect(await fs.exists(`${config.cwd}/node_modules/isarray`)).toEqual(false);
+    expect(await fs.exists(`${config.cwd}/workspace-child/node_modules/isarray`)).toEqual(false);
+
+    const lockFileContent = await fs.readFile(path.join(config.cwd, 'yarn.lock'));
+    const lockFileLines = explodeLockfile(lockFileContent);
+
+    expect(lockFileLines).toHaveLength(9);
+    expect(lockFileLines[0]).toEqual('left-pad at 1.1.3:');
+  });
+});
diff --git a/__tests__/commands/run.js b/__tests__/commands/run.js
index 386c7e3..7477289 100644
--- a/__tests__/commands/run.js
+++ b/__tests__/commands/run.js
@@ -64,7 +64,7 @@ test('properly handles extra arguments and pre/post scripts', (): Promise<void>
     const pkg = await fs.readJson(path.join(config.cwd, 'package.json'));
     const poststart = ['poststart', config, pkg.scripts.poststart, config.cwd];
     const prestart = ['prestart', config, pkg.scripts.prestart, config.cwd];
-    const start = ['start', config, pkg.scripts.start + ' "--hello"', config.cwd];
+    const start = ['start', config, pkg.scripts.start + ' --hello', config.cwd];
 
     expect(execCommand.mock.calls[0]).toEqual(prestart);
     expect(execCommand.mock.calls[1]).toEqual(start);
@@ -75,7 +75,7 @@ test('properly handles extra arguments and pre/post scripts', (): Promise<void>
 test('properly handle bin scripts', (): Promise<void> => {
   return runRun(['cat-names'], {}, 'bin', config => {
     const script = path.join(config.cwd, 'node_modules', '.bin', 'cat-names');
-    const args = ['cat-names', config, `"${script}"`, config.cwd];
+    const args = ['cat-names', config, script, config.cwd];
 
     expect(execCommand).toBeCalledWith(...args);
   });
@@ -114,19 +114,21 @@ test('properly handle env command', (): Promise<void> => {
   });
 });
 
-test('retains string delimiters if args have spaces', (): Promise<void> => {
+test('adds string delimiters if args have spaces', (): Promise<void> => {
   return runRun(['cat-names', '--filter', 'cat names'], {}, 'bin', config => {
     const script = path.join(config.cwd, 'node_modules', '.bin', 'cat-names');
-    const args = ['cat-names', config, `"${script}" "--filter" "cat names"`, config.cwd];
+    const q = process.platform === 'win32' ? '"' : "'";
+    const args = ['cat-names', config, `${script} --filter ${q}cat names${q}`, config.cwd];
 
     expect(execCommand).toBeCalledWith(...args);
   });
 });
 
-test('retains quotes if args have spaces and quotes', (): Promise<void> => {
+test('adds quotes if args have spaces and quotes', (): Promise<void> => {
   return runRun(['cat-names', '--filter', '"cat names"'], {}, 'bin', config => {
     const script = path.join(config.cwd, 'node_modules', '.bin', 'cat-names');
-    const args = ['cat-names', config, `"${script}" "--filter" "\\"cat names\\""`, config.cwd];
+    const quotedCatNames = process.platform === 'win32' ? '^"\\^"cat^ names\\^"^"' : `'"cat names"'`;
+    const args = ['cat-names', config, `${script} --filter ${quotedCatNames}`, config.cwd];
 
     expect(execCommand).toBeCalledWith(...args);
   });
diff --git a/__tests__/commands/upgrade.js b/__tests__/commands/upgrade.js
index 16b3bf1..166cc98 100644
--- a/__tests__/commands/upgrade.js
+++ b/__tests__/commands/upgrade.js
@@ -31,6 +31,11 @@ const expectInstalledDevDependency = async (config, name, range, expectedVersion
   await _expectDependency('devDependencies', config, name, range, expectedVersion);
 };
 
+const expectInstalledTransitiveDependency = async (config, name, range, expectedVersion) => {
+  const lockfile = explodeLockfile(await fs.readFile(path.join(config.cwd, 'yarn.lock')));
+  expect(lockfile).toContainPackage(`${name}@${range}:`, expectedVersion);
+};
+
 expect.extend({
   toContainPackage(lockfile, ...args): Object {
     const [pattern, expectedVersion] = args;
@@ -68,6 +73,32 @@ test.concurrent('works with no arguments', (): Promise<void> => {
   });
 });
 
+test.concurrent('upgrades transitive deps when no arguments', (): Promise<void> => {
+  return runUpgrade([], {}, 'with-subdep', async (config): ?Promise<void> => {
+    await expectInstalledDependency(config, 'strip-ansi', '^2.0.1', '2.0.1');
+    await expectInstalledTransitiveDependency(config, 'ansi-regex', '^1.0.0', '1.1.1');
+    await expectInstalledDependency(config, 'array-union', '^1.0.1', '1.0.2');
+    await expectInstalledTransitiveDependency(config, 'array-uniq', '^1.0.1', '1.0.3');
+  });
+});
+
+test.concurrent('does not upgrade transitive deps that are also a direct dependency', (): Promise<void> => {
+  return runUpgrade(['strip-ansi'], {}, 'with-subdep-also-direct', async (config): ?Promise<void> => {
+    await expectInstalledDependency(config, 'strip-ansi', '^2.0.1', '2.0.1');
+    await expectInstalledTransitiveDependency(config, 'ansi-regex', '^1.0.0', '1.0.0');
+    await expectInstalledDependency(config, 'ansi-regex', '^1.0.0', '1.0.0');
+  });
+});
+
+test.concurrent('does not upgrade transitive deps when specific package upgraded', (): Promise<void> => {
+  return runUpgrade(['strip-ansi'], {}, 'with-subdep', async (config): ?Promise<void> => {
+    await expectInstalledDependency(config, 'strip-ansi', '^2.0.1', '2.0.1');
+    await expectInstalledTransitiveDependency(config, 'ansi-regex', '^1.0.0', '1.1.1');
+    await expectInstalledDependency(config, 'array-union', '^1.0.1', '1.0.1');
+    await expectInstalledTransitiveDependency(config, 'array-uniq', '^1.0.1', '1.0.1');
+  });
+});
+
 test.concurrent('works with single argument', (): Promise<void> => {
   return runUpgrade(['max-safe-integer'], {}, 'single-package', async (config): ?Promise<void> => {
     await expectInstalledDependency(config, 'left-pad', '^1.0.0', '1.0.0');
@@ -112,6 +143,21 @@ test.concurrent('upgrades from fixed version to latest with workspaces', (): Pro
   });
 });
 
+test.concurrent('works with just a pattern', (): Promise<void> => {
+  return runUpgrade([], {pattern: 'max'}, 'multiple-packages', async (config): ?Promise<void> => {
+    await expectInstalledDependency(config, 'max-safe-integer', '^1.0.0', '1.0.1');
+    await expectInstalledDependency(config, 'is-negative-zero', '^1.0.0', '1.0.0');
+  });
+});
+
+test.concurrent('works with arguments and a pattern', (): Promise<void> => {
+  return runUpgrade(['left-pad'], {pattern: 'max'}, 'multiple-packages', async (config): ?Promise<void> => {
+    await expectInstalledDependency(config, 'left-pad', '^1.0.0', '1.1.3');
+    await expectInstalledDependency(config, 'max-safe-integer', '^1.0.0', '1.0.1');
+    await expectInstalledDependency(config, 'is-negative-zero', '^1.0.0', '1.0.0');
+  });
+});
+
 test.concurrent('upgrades to latest matching package.json semver when no package name passed', (): Promise<void> => {
   return runUpgrade([], {}, 'range-to-latest', async (config): ?Promise<void> => {
     await expectInstalledDependency(config, 'left-pad', '<=1.1.1', '1.1.1');
@@ -315,3 +361,46 @@ test.concurrent('--latest works if there is an install script on a hoisted depen
     'latest-with-install-script',
   );
 });
+
+test.concurrent('upgrade to workspace root preserves child dependencies', (): Promise<void> => {
+  return runUpgrade(['max-safe-integer at 1.0.1'], {latest: true}, 'workspaces', async (config): ?Promise<void> => {
+    const lockfile = explodeLockfile(await fs.readFile(path.join(config.cwd, 'yarn.lock')));
+
+    // child workspace deps
+    expect(lockfile.indexOf('left-pad at 1.0.0:')).toBeGreaterThanOrEqual(0);
+    expect(lockfile.indexOf('right-pad at 1.0.0:')).toBeGreaterThanOrEqual(0);
+    // root dep
+    expect(lockfile.indexOf('max-safe-integer at 1.0.0:')).toBe(-1);
+    expect(lockfile.indexOf('max-safe-integer at 1.0.1:')).toBeGreaterThanOrEqual(0);
+
+    const rootPkg = await fs.readJson(path.join(config.cwd, 'package.json'));
+    expect(rootPkg.devDependencies['max-safe-integer']).toEqual('1.0.1');
+
+    const childAPkg = await fs.readJson(path.join(config.cwd, 'child-a/package.json'));
+    const childBPkg = await fs.readJson(path.join(config.cwd, 'child-b/package.json'));
+    expect(childAPkg.dependencies['left-pad']).toEqual('1.0.0');
+    expect(childBPkg.dependencies['right-pad']).toEqual('1.0.0');
+  });
+});
+
+test.concurrent('upgrade to workspace child preserves root dependencies', (): Promise<void> => {
+  const fixture = {source: 'workspaces', cwd: 'child-a'};
+  return runUpgrade(['left-pad at 1.1.0'], {latest: true}, fixture, async (config): ?Promise<void> => {
+    const lockfile = explodeLockfile(await fs.readFile(path.join(config.lockfileFolder, 'yarn.lock')));
+
+    // untouched deps
+    expect(lockfile.indexOf('right-pad at 1.0.0:')).toBeGreaterThanOrEqual(0);
+    expect(lockfile.indexOf('max-safe-integer at 1.0.0:')).toBeGreaterThanOrEqual(0);
+    // upgraded child workspace
+    expect(lockfile.indexOf('left-pad at 1.0.0:')).toBe(-1);
+    expect(lockfile.indexOf('left-pad at 1.1.0:')).toBeGreaterThanOrEqual(0);
+
+    const childAPkg = await fs.readJson(path.join(config.cwd, 'package.json'));
+    expect(childAPkg.dependencies['left-pad']).toEqual('1.1.0');
+
+    const rootPkg = await fs.readJson(path.join(config.lockfileFolder, 'package.json'));
+    const childBPkg = await fs.readJson(path.join(config.lockfileFolder, 'child-b/package.json'));
+    expect(rootPkg.devDependencies['max-safe-integer']).toEqual('1.0.0');
+    expect(childBPkg.dependencies['right-pad']).toEqual('1.0.0');
+  });
+});
diff --git a/__tests__/fixtures/add/add-with-deep-peer-dependencies/a/package.json b/__tests__/fixtures/add/add-with-deep-peer-dependencies/a/package.json
new file mode 100644
index 0000000..040e8fd
--- /dev/null
+++ b/__tests__/fixtures/add/add-with-deep-peer-dependencies/a/package.json
@@ -0,0 +1,8 @@
+{
+  "name": "a",
+  "version": "0.0.0",
+  "dependencies": {
+    "b": "file:../b",
+    "c": "file:../c"
+  }
+}
diff --git a/__tests__/fixtures/add/add-with-deep-peer-dependencies/b/package.json b/__tests__/fixtures/add/add-with-deep-peer-dependencies/b/package.json
new file mode 100644
index 0000000..600cb6e
--- /dev/null
+++ b/__tests__/fixtures/add/add-with-deep-peer-dependencies/b/package.json
@@ -0,0 +1,7 @@
+{
+  "name": "b",
+  "version": "1.0.0",
+  "peerDependencies": {
+    "c": "1.0.0"
+  }
+}
diff --git a/__tests__/fixtures/add/add-with-deep-peer-dependencies/c/package.json b/__tests__/fixtures/add/add-with-deep-peer-dependencies/c/package.json
new file mode 100644
index 0000000..abd3384
--- /dev/null
+++ b/__tests__/fixtures/add/add-with-deep-peer-dependencies/c/package.json
@@ -0,0 +1,4 @@
+{
+  "name": "c",
+  "version": "1.0.0"
+}
diff --git a/__tests__/fixtures/add/add-with-deep-peer-dependencies/package.json b/__tests__/fixtures/add/add-with-deep-peer-dependencies/package.json
new file mode 100644
index 0000000..dcd18ba
--- /dev/null
+++ b/__tests__/fixtures/add/add-with-deep-peer-dependencies/package.json
@@ -0,0 +1,7 @@
+{
+  "name": "d",
+  "version": "3.0.0",
+  "dependencies": {
+    "a": "file:a"
+  }
+}
diff --git a/__tests__/fixtures/add/install-strict-all/.yarnrc b/__tests__/fixtures/add/install-strict-all/.yarnrc
new file mode 100644
index 0000000..2e2098e
--- /dev/null
+++ b/__tests__/fixtures/add/install-strict-all/.yarnrc
@@ -0,0 +1,2 @@
+yarn-offline-mirror "./mirror-for-offline"
+save-exact true
diff --git a/__tests__/fixtures/global/add-with-prefix-env/.yarnrc b/__tests__/fixtures/global/add-with-prefix-env/.yarnrc
index 3100b0f..b91fc0d 100644
--- a/__tests__/fixtures/global/add-with-prefix-env/.yarnrc
+++ b/__tests__/fixtures/global/add-with-prefix-env/.yarnrc
@@ -1 +1 @@
-prefix ""
+prefix false
diff --git a/__tests__/fixtures/init/init-nested/inner-folder/.gitkeep b/__tests__/fixtures/init/init-nested/inner-folder/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/__tests__/fixtures/init/init-nested/package.json b/__tests__/fixtures/init/init-nested/package.json
new file mode 100644
index 0000000..38d16b8
--- /dev/null
+++ b/__tests__/fixtures/init/init-nested/package.json
@@ -0,0 +1,3 @@
+{
+  "name": "yarn"
+}
diff --git a/__tests__/fixtures/install/install-file-no-package/bar/bar.js b/__tests__/fixtures/install/install-file-no-package/bar/bar.js
new file mode 100644
index 0000000..e69de29
diff --git a/__tests__/fixtures/install/install-file-no-package/package.json b/__tests__/fixtures/install/install-file-no-package/package.json
new file mode 100644
index 0000000..80d05da
--- /dev/null
+++ b/__tests__/fixtures/install/install-file-no-package/package.json
@@ -0,0 +1,6 @@
+{
+  "dependencies": {
+    "foo": "file:bar",
+    "bar": "./bar"
+  }
+}
diff --git a/__tests__/fixtures/install/install-optional-dependencies/dep-a/package.json b/__tests__/fixtures/install/install-optional-dependencies/dep-a/package.json
index 941f6a8..8cfdbed 100644
--- a/__tests__/fixtures/install/install-optional-dependencies/dep-a/package.json
+++ b/__tests__/fixtures/install/install-optional-dependencies/dep-a/package.json
@@ -1,6 +1,9 @@
 {
     "name": "dep-a",
     "version": "1.0.0",
+    "dependencies": {
+        "dep-c": "file:../dep-c"
+    },
     "optionalDependencies": {
         "dep-b": "file:../dep-b"
     }
diff --git a/__tests__/fixtures/install/install-optional-dependencies/dep-c/package.json b/__tests__/fixtures/install/install-optional-dependencies/dep-c/package.json
new file mode 100644
index 0000000..c3c33d8
--- /dev/null
+++ b/__tests__/fixtures/install/install-optional-dependencies/dep-c/package.json
@@ -0,0 +1,10 @@
+{
+    "name": "dep-c",
+    "version": "1.0.0",
+    "optionalDependencies": {
+        "dep-e": "file:../dep-e"
+    },
+    "dependencies": {
+        "dep-d": "file:../dep-d"
+    }
+}
diff --git a/__tests__/fixtures/install/install-optional-dependencies/dep-d/package.json b/__tests__/fixtures/install/install-optional-dependencies/dep-d/package.json
new file mode 100644
index 0000000..e813deb
--- /dev/null
+++ b/__tests__/fixtures/install/install-optional-dependencies/dep-d/package.json
@@ -0,0 +1,7 @@
+{
+    "name": "dep-d",
+    "version": "1.0.0",
+    "dependencies": {
+        "dep-e": "file:../dep-e"
+    }
+}
diff --git a/__tests__/fixtures/install/install-optional-dependencies/dep-e/package.json b/__tests__/fixtures/install/install-optional-dependencies/dep-e/package.json
new file mode 100644
index 0000000..04f8319
--- /dev/null
+++ b/__tests__/fixtures/install/install-optional-dependencies/dep-e/package.json
@@ -0,0 +1,4 @@
+{
+    "name": "dep-e",
+    "version": "1.0.0"
+}
diff --git a/__tests__/fixtures/install/install-with-null-version/package.json b/__tests__/fixtures/install/install-with-null-version/package.json
new file mode 100644
index 0000000..ed66274
--- /dev/null
+++ b/__tests__/fixtures/install/install-with-null-version/package.json
@@ -0,0 +1,5 @@
+{
+  "dependencies": {
+    "left-pad": null
+  }
+}
diff --git a/__tests__/fixtures/install/optimize-version-with-alternation/b/package.json b/__tests__/fixtures/install/optimize-version-with-alternation/b/package.json
new file mode 100644
index 0000000..ac56bbf
--- /dev/null
+++ b/__tests__/fixtures/install/optimize-version-with-alternation/b/package.json
@@ -0,0 +1,6 @@
+{
+  "version": "0.0.1",
+  "dependencies": {
+    "lodash": "^3.0.1 || ^2.0.0"
+  }
+}
diff --git a/__tests__/fixtures/install/optimize-version-with-alternation/package.json b/__tests__/fixtures/install/optimize-version-with-alternation/package.json
new file mode 100644
index 0000000..d248c4e
--- /dev/null
+++ b/__tests__/fixtures/install/optimize-version-with-alternation/package.json
@@ -0,0 +1,6 @@
+{
+  "dependencies": {
+    "b": "file:b",
+    "lodash": "2.4.2"
+  }
+}
diff --git a/__tests__/fixtures/install/peer-dep-same-subtree/a-1.0.0/package.json b/__tests__/fixtures/install/peer-dep-same-subtree/a-1.0.0/package.json
new file mode 100644
index 0000000..d1fe0d5
--- /dev/null
+++ b/__tests__/fixtures/install/peer-dep-same-subtree/a-1.0.0/package.json
@@ -0,0 +1 @@
+{"name":"a", "version":"1.0.0"}
diff --git a/__tests__/fixtures/install/peer-dep-same-subtree/a-1.1.0/package.json b/__tests__/fixtures/install/peer-dep-same-subtree/a-1.1.0/package.json
new file mode 100644
index 0000000..22e2844
--- /dev/null
+++ b/__tests__/fixtures/install/peer-dep-same-subtree/a-1.1.0/package.json
@@ -0,0 +1 @@
+{"name":"a", "version":"1.1.0"}
diff --git a/__tests__/fixtures/install/peer-dep-same-subtree/b/package.json b/__tests__/fixtures/install/peer-dep-same-subtree/b/package.json
new file mode 100644
index 0000000..6c0e5c3
--- /dev/null
+++ b/__tests__/fixtures/install/peer-dep-same-subtree/b/package.json
@@ -0,0 +1,7 @@
+{
+  "name":"b",
+  "version":"1.0.0",
+  "dependencies": {
+    "c": "file:../c"
+  }
+}
diff --git a/__tests__/fixtures/install/peer-dep-same-subtree/c/package.json b/__tests__/fixtures/install/peer-dep-same-subtree/c/package.json
new file mode 100644
index 0000000..36b451e
--- /dev/null
+++ b/__tests__/fixtures/install/peer-dep-same-subtree/c/package.json
@@ -0,0 +1,7 @@
+{
+  "name":"c",
+  "version":"1.0.0",
+  "peerDependencies": {
+    "a": "^1.0.0"
+  }
+}
diff --git a/__tests__/fixtures/install/peer-dep-same-subtree/d/package.json b/__tests__/fixtures/install/peer-dep-same-subtree/d/package.json
new file mode 100644
index 0000000..cf19c1c
--- /dev/null
+++ b/__tests__/fixtures/install/peer-dep-same-subtree/d/package.json
@@ -0,0 +1,7 @@
+{
+  "name":"d",
+  "version":"1.0.0",
+  "dependencies": {
+    "a": "file:../a-1.0.0"
+  }
+}
diff --git a/__tests__/fixtures/install/peer-dep-same-subtree/package.json b/__tests__/fixtures/install/peer-dep-same-subtree/package.json
new file mode 100644
index 0000000..053cb27
--- /dev/null
+++ b/__tests__/fixtures/install/peer-dep-same-subtree/package.json
@@ -0,0 +1,9 @@
+{
+  "name": "peer-dep-same-subtree",
+  "dependencies": {
+    "a": "file:a-1.1.0",
+    "b": "file:b",
+    "c": "file:c",
+    "d": "file:d"
+  }
+}
diff --git a/__tests__/fixtures/install/workspaces-install-basic/yarn.lock b/__tests__/fixtures/install/workspaces-install-basic/yarn.lock
new file mode 100644
index 0000000..c5e1777
--- /dev/null
+++ b/__tests__/fixtures/install/workspaces-install-basic/yarn.lock
@@ -0,0 +1,19 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+isarray at 2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e"
+
+left-pad at 1.1.3:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.1.3.tgz#612f61c033f3a9e08e939f1caebeea41b6f3199a"
+
+repeat-string at 1.6.1:
+  version "1.6.1"
+  resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
+
+right-pad at 1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/right-pad/-/right-pad-1.0.1.tgz#8ca08c2cbb5b55e74dafa96bf7fd1a27d568c8d0"
diff --git a/__tests__/fixtures/lifecycle-scripts/npm_config_argv_env_vars/log-command.js b/__tests__/fixtures/lifecycle-scripts/npm_config_argv_env_vars/log-command.js
index 7250055..85e2748 100644
--- a/__tests__/fixtures/lifecycle-scripts/npm_config_argv_env_vars/log-command.js
+++ b/__tests__/fixtures/lifecycle-scripts/npm_config_argv_env_vars/log-command.js
@@ -1,7 +1 @@
-try {
-  const argv = JSON.parse(process.env['npm_config_argv']);
-
-  console.log(`##${argv.cooked[0]}##`);
-} catch (err) {
-  console.log(`##${err.toString()}##`);
-}
+console.log(process.env['npm_config_argv']);
diff --git a/__tests__/fixtures/outdated/workspaces/child-a/package.json b/__tests__/fixtures/outdated/workspaces/child-a/package.json
new file mode 100644
index 0000000..664cc2b
--- /dev/null
+++ b/__tests__/fixtures/outdated/workspaces/child-a/package.json
@@ -0,0 +1,7 @@
+{
+  "name": "child-a",
+  "version": "1.0.0",
+  "dependencies": {
+    "max-safe-integer": "1.0.0"
+  }
+}
diff --git a/__tests__/fixtures/outdated/workspaces/child-b/package.json b/__tests__/fixtures/outdated/workspaces/child-b/package.json
new file mode 100644
index 0000000..b56f2f9
--- /dev/null
+++ b/__tests__/fixtures/outdated/workspaces/child-b/package.json
@@ -0,0 +1,8 @@
+{
+  "name": "child-b",
+  "version": "1.0.0",
+  "dependencies": {
+    "left-pad": "1.0.1",
+    "right-pad": "1.0.0"
+  }
+}
diff --git a/__tests__/fixtures/outdated/workspaces/package.json b/__tests__/fixtures/outdated/workspaces/package.json
new file mode 100644
index 0000000..4416f9c
--- /dev/null
+++ b/__tests__/fixtures/outdated/workspaces/package.json
@@ -0,0 +1,11 @@
+{
+  "name": "my-project",
+  "private": true,
+  "devDependencies": {
+    "left-pad": "1.0.0"
+  },
+  "workspaces": [
+    "child-a",
+    "child-b"
+  ]
+}
diff --git a/__tests__/fixtures/outdated/workspaces/yarn.lock b/__tests__/fixtures/outdated/workspaces/yarn.lock
new file mode 100644
index 0000000..05ca873
--- /dev/null
+++ b/__tests__/fixtures/outdated/workspaces/yarn.lock
@@ -0,0 +1,19 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+left-pad at 1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.0.0.tgz#c84e2417581bbb8eaf2b9e3d7a122e572ab1af37"
+
+left-pad at 1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.0.1.tgz#d11b8e17e70e6ecb3b6bf2858fa99c40f819d13a"
+
+max-safe-integer at 1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/max-safe-integer/-/max-safe-integer-1.0.0.tgz#4662073a02c7e02d38153e25795489b20be6f01a"
+
+right-pad at 1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/right-pad/-/right-pad-1.0.0.tgz#5ba6e56c0d7ec162d3626315c27a61f8aff42f15"
diff --git a/__tests__/fixtures/pack/bundled-dependencies/a/package.json b/__tests__/fixtures/pack/bundled-dependencies/a/package.json
new file mode 100644
index 0000000..be5b70f
--- /dev/null
+++ b/__tests__/fixtures/pack/bundled-dependencies/a/package.json
@@ -0,0 +1,7 @@
+{
+  "name": "a",
+  "version": "1.0.0",
+  "dependencies": {
+    "b": "file:../b"
+  }
+}
diff --git a/__tests__/fixtures/pack/bundled-dependencies/b/package.json b/__tests__/fixtures/pack/bundled-dependencies/b/package.json
new file mode 100644
index 0000000..c2d84cc
--- /dev/null
+++ b/__tests__/fixtures/pack/bundled-dependencies/b/package.json
@@ -0,0 +1,4 @@
+{
+  "name": "b",
+  "version": "1.0.0"
+}
diff --git a/__tests__/fixtures/pack/bundled-dependencies/index.js b/__tests__/fixtures/pack/bundled-dependencies/index.js
new file mode 100644
index 0000000..6be0237
--- /dev/null
+++ b/__tests__/fixtures/pack/bundled-dependencies/index.js
@@ -0,0 +1 @@
+console.log('hello world');
diff --git a/__tests__/fixtures/pack/bundled-dependencies/package.json b/__tests__/fixtures/pack/bundled-dependencies/package.json
new file mode 100644
index 0000000..d897a75
--- /dev/null
+++ b/__tests__/fixtures/pack/bundled-dependencies/package.json
@@ -0,0 +1,14 @@
+{
+  "name": "bundled-dependencies",
+  "version": "1.0.0",
+  "license": "MIT",
+  "files": [
+    "index.js"
+  ],
+  "dependencies": {
+    "a": "file:a"
+  },
+  "bundledDependencies": [
+    "a"
+  ]
+}
diff --git a/__tests__/fixtures/remove/without-manifest/node_modules/.yarn-integrity b/__tests__/fixtures/remove/without-manifest/node_modules/.yarn-integrity
new file mode 100644
index 0000000..cada8b0
--- /dev/null
+++ b/__tests__/fixtures/remove/without-manifest/node_modules/.yarn-integrity
@@ -0,0 +1 @@
+9e2ad8749063f99425ffdc4b6994b33fa222bb657dced1d7e5d272d3cc330185
\ No newline at end of file
diff --git a/__tests__/fixtures/remove/without-manifest/node_modules/dep-a/index.js b/__tests__/fixtures/remove/without-manifest/node_modules/dep-a/index.js
new file mode 100644
index 0000000..24e9126
--- /dev/null
+++ b/__tests__/fixtures/remove/without-manifest/node_modules/dep-a/index.js
@@ -0,0 +1,33 @@
+
+/**
+ * isArray
+ */
+
+var isArray = Array.isArray;
+
+/**
+ * toString
+ */
+
+var str = Object.prototype.toString;
+
+/**
+ * Whether or not the given `val`
+ * is an array.
+ *
+ * example:
+ *
+ *        isArray([]);
+ *        // > true
+ *        isArray(arguments);
+ *        // > false
+ *        isArray('');
+ *        // > false
+ *
+ * @param {mixed} val
+ * @return {bool}
+ */
+
+module.exports = isArray || function (val) {
+  return !! val && '[object Array]' == str.call(val);
+};
diff --git a/__tests__/fixtures/remove/without-manifest/package.json b/__tests__/fixtures/remove/without-manifest/package.json
new file mode 100644
index 0000000..7970859
--- /dev/null
+++ b/__tests__/fixtures/remove/without-manifest/package.json
@@ -0,0 +1,5 @@
+{
+  "dependencies": {
+    "dep-a": "^1.0.0"
+  }
+}
diff --git a/__tests__/fixtures/remove/without-manifest/yarn.lock b/__tests__/fixtures/remove/without-manifest/yarn.lock
new file mode 100644
index 0000000..84fd230
--- /dev/null
+++ b/__tests__/fixtures/remove/without-manifest/yarn.lock
@@ -0,0 +1,5 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+dep-a@^1.0.0:
+  version "1.0.0"
+  resolved dep-a-1.0.0.tgz#a0055ce8265cf0d2dbfac15233c9a5f44f9c4939
diff --git a/__tests__/fixtures/request-cache/GET/registry.npmjs.org/ansi-regex/-/ansi-regex-1.0.0.tgz.bin b/__tests__/fixtures/request-cache/GET/registry.npmjs.org/ansi-regex/-/ansi-regex-1.0.0.tgz.bin
new file mode 100644
index 0000000..0b5bd39
Binary files /dev/null and b/__tests__/fixtures/request-cache/GET/registry.npmjs.org/ansi-regex/-/ansi-regex-1.0.0.tgz.bin differ
diff --git a/__tests__/fixtures/request-cache/GET/registry.npmjs.org/array-union/-/array-union-1.0.1.tgz.bin b/__tests__/fixtures/request-cache/GET/registry.npmjs.org/array-union/-/array-union-1.0.1.tgz.bin
new file mode 100644
index 0000000..8eeb39a
Binary files /dev/null and b/__tests__/fixtures/request-cache/GET/registry.npmjs.org/array-union/-/array-union-1.0.1.tgz.bin differ
diff --git a/__tests__/fixtures/request-cache/GET/registry.npmjs.org/array-uniq/-/array-uniq-1.0.1.tgz.bin b/__tests__/fixtures/request-cache/GET/registry.npmjs.org/array-uniq/-/array-uniq-1.0.1.tgz.bin
new file mode 100644
index 0000000..832e992
Binary files /dev/null and b/__tests__/fixtures/request-cache/GET/registry.npmjs.org/array-uniq/-/array-uniq-1.0.1.tgz.bin differ
diff --git a/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/lodash/-/lodash-2.4.2.tgz.bin b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/lodash/-/lodash-2.4.2.tgz.bin
new file mode 100644
index 0000000..e92d4c6
Binary files /dev/null and b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/lodash/-/lodash-2.4.2.tgz.bin differ
diff --git a/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/pn.bin b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/pn.bin
new file mode 100644
index 0000000..fea6490
Binary files /dev/null and b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/pn.bin differ
diff --git a/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/right-pad/-/right-pad-1.0.0.tgz.bin b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/right-pad/-/right-pad-1.0.0.tgz.bin
new file mode 100644
index 0000000..a81bf3c
Binary files /dev/null and b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/right-pad/-/right-pad-1.0.0.tgz.bin differ
diff --git a/__tests__/fixtures/upgrade/with-subdep-also-direct/package.json b/__tests__/fixtures/upgrade/with-subdep-also-direct/package.json
new file mode 100644
index 0000000..acea527
--- /dev/null
+++ b/__tests__/fixtures/upgrade/with-subdep-also-direct/package.json
@@ -0,0 +1,6 @@
+{
+  "dependencies": {
+    "strip-ansi": "^2.0.1",
+    "ansi-regex": "^1.0.0"
+  }
+}
diff --git a/__tests__/fixtures/upgrade/with-subdep-also-direct/yarn.lock b/__tests__/fixtures/upgrade/with-subdep-also-direct/yarn.lock
new file mode 100644
index 0000000..fceef7c
--- /dev/null
+++ b/__tests__/fixtures/upgrade/with-subdep-also-direct/yarn.lock
@@ -0,0 +1,10 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+ansi-regex@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.0.0.tgz#54c7ce13af71e436348666484c44516ab9bc144e"
+strip-ansi@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-2.0.1.tgz#df62c1aa94ed2f114e1d0f21fd1d50482b79a60e"
+  dependencies:
+    ansi-regex "^1.0.0"
diff --git a/__tests__/fixtures/upgrade/with-subdep/package.json b/__tests__/fixtures/upgrade/with-subdep/package.json
new file mode 100644
index 0000000..6529ea7
--- /dev/null
+++ b/__tests__/fixtures/upgrade/with-subdep/package.json
@@ -0,0 +1,6 @@
+{
+  "dependencies": {
+    "strip-ansi": "^2.0.1",
+    "array-union": "^1.0.1"
+  }
+}
diff --git a/__tests__/fixtures/upgrade/with-subdep/yarn.lock b/__tests__/fixtures/upgrade/with-subdep/yarn.lock
new file mode 100644
index 0000000..5c34d36
--- /dev/null
+++ b/__tests__/fixtures/upgrade/with-subdep/yarn.lock
@@ -0,0 +1,18 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+ansi-regex@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.0.0.tgz#54c7ce13af71e436348666484c44516ab9bc144e"
+array-union@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.npmjs.org/array-union/-/array-union-1.0.1.tgz#4d410fc8395cb247637124bade9e3f547d5d55f2"
+  dependencies:
+    array-uniq "^1.0.1"
+array-uniq@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.1.tgz#25e1d96853d7f6f77cecf693f86cac4052046790"
+strip-ansi@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-2.0.1.tgz#df62c1aa94ed2f114e1d0f21fd1d50482b79a60e"
+  dependencies:
+    ansi-regex "^1.0.0"
diff --git a/__tests__/fixtures/upgrade/workspaces/child-a/package.json b/__tests__/fixtures/upgrade/workspaces/child-a/package.json
new file mode 100644
index 0000000..5520b53
--- /dev/null
+++ b/__tests__/fixtures/upgrade/workspaces/child-a/package.json
@@ -0,0 +1,7 @@
+{
+  "name": "child-a",
+  "version": "1.0.0",
+  "dependencies": {
+    "left-pad": "1.0.0"
+  }
+}
diff --git a/__tests__/fixtures/upgrade/workspaces/child-b/package.json b/__tests__/fixtures/upgrade/workspaces/child-b/package.json
new file mode 100644
index 0000000..7e22906
--- /dev/null
+++ b/__tests__/fixtures/upgrade/workspaces/child-b/package.json
@@ -0,0 +1,7 @@
+{
+  "name": "child-b",
+  "version": "1.0.0",
+  "dependencies": {
+    "right-pad": "1.0.0"
+  }
+}
diff --git a/__tests__/fixtures/upgrade/workspaces/package.json b/__tests__/fixtures/upgrade/workspaces/package.json
new file mode 100644
index 0000000..998fddf
--- /dev/null
+++ b/__tests__/fixtures/upgrade/workspaces/package.json
@@ -0,0 +1,11 @@
+{
+  "name": "my-project",
+  "private": true,
+  "devDependencies": {
+    "max-safe-integer": "1.0.0"
+  },
+  "workspaces": [
+    "child-a",
+    "child-b"
+  ]
+}
diff --git a/__tests__/fixtures/upgrade/workspaces/yarn.lock b/__tests__/fixtures/upgrade/workspaces/yarn.lock
new file mode 100644
index 0000000..4867664
--- /dev/null
+++ b/__tests__/fixtures/upgrade/workspaces/yarn.lock
@@ -0,0 +1,15 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+left-pad at 1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.0.0.tgz#c84e2417581bbb8eaf2b9e3d7a122e572ab1af37"
+
+max-safe-integer at 1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/max-safe-integer/-/max-safe-integer-1.0.0.tgz#4662073a02c7e02d38153e25795489b20be6f01a"
+
+right-pad at 1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/right-pad/-/right-pad-1.0.0.tgz#5ba6e56c0d7ec162d3626315c27a61f8aff42f15"
diff --git a/__tests__/index.js b/__tests__/index.js
index 8b415b1..58458c1 100644
--- a/__tests__/index.js
+++ b/__tests__/index.js
@@ -32,14 +32,15 @@ async function execCommand(
   const cacheDir = path.join(workingDir, '.yarn-cache');
 
   return new Promise((resolve, reject) => {
+    const cleanedEnv = {...process.env};
+    cleanedEnv['YARN_SILENT'] = 0;
+    delete cleanedEnv['FORCE_COLOR'];
+
     exec(
       `node "${yarnBin}" --cache-folder="${cacheDir}" ${cmd} ${args.join(' ')}`,
       {
         cwd: workingDir,
-        env: {
-          ...process.env,
-          YARN_SILENT: 0,
-        },
+        env: cleanedEnv,
       },
       (error, stdout) => {
         if (error) {
diff --git a/__tests__/integration.js b/__tests__/integration.js
index fdd213d..a8173a2 100644
--- a/__tests__/integration.js
+++ b/__tests__/integration.js
@@ -2,29 +2,38 @@
 /* eslint max-len: 0 */
 
 import execa from 'execa';
+import {sh} from 'puka';
 import makeTemp from './_temp.js';
 import * as fs from '../src/util/fs.js';
-import * as misc from '../src/util/misc.js';
 import * as constants from '../src/constants.js';
+import {explodeLockfile} from './commands/_helpers.js';
 
-jasmine.DEFAULT_TIMEOUT_INTERVAL = 90000;
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 120000;
 
 const path = require('path');
 
-function addTest(pattern) {
-  // concurrently network requests tend to stall
-  test(`yarn add ${pattern}`, async () => {
+function addTest(pattern, {strict} = {strict: false}, yarnArgs: Array<string> = []) {
+  test.concurrent(`yarn add ${pattern}`, async () => {
     const cwd = await makeTemp();
     const cacheFolder = path.join(cwd, 'cache');
 
     const command = path.resolve(__dirname, '../bin/yarn');
-    const args = ['--cache-folder', cacheFolder, '--verbose'];
+    const args = ['--cache-folder', cacheFolder, ...yarnArgs];
 
     const options = {cwd};
 
-    await fs.writeFile(path.join(cwd, 'package.json'), JSON.stringify({name: 'test'}));
+    await fs.writeFile(
+      path.join(cwd, 'package.json'),
+      JSON.stringify({
+        name: 'test',
+        license: 'MIT',
+      }),
+    );
 
-    await execa(command, ['add', pattern].concat(args), options);
+    const result = await execa(command, ['add', pattern].concat(args), options);
+    if (strict) {
+      expect(result.stderr).not.toMatch(/^warning /gm);
+    }
 
     await fs.unlink(cwd);
   });
@@ -41,7 +50,7 @@ function addTest(pattern) {
 //       path.join(folder, constants.METADATA_FILENAME),
 //       '{"remote": {"hash": "cafebabecafebabecafebabecafebabecafebabe"}}',
 //     );
-//     await fs.writeFile(path.join(folder, 'package.json'), '{"name": "@foo/bar", "version": "1.2.3"}');
+//     await fs.writeFile(path.join(foldresolve gitlab:leanlabsio/kanbaner, 'package.json'), '{"name": "@foo/bar", "version": "1.2.3"}');
 //   },
 //   true,
 // ); // offline npm scoped package
@@ -51,6 +60,7 @@ addTest('https://git@github.com/stevemao/left-pad.git'); // git url, with userna
 addTest('https://github.com/yarnpkg/yarn/releases/download/v0.18.1/yarn-v0.18.1.tar.gz'); // tarball
 addTest('https://github.com/bestander/chrome-app-livereload.git'); // no package.json
 addTest('bestander/chrome-app-livereload'); // no package.json, github, tarball
+addTest('react-scripts at 1.0.13', {strict: true}, ['--no-node-version-check', '--ignore-engines']); // many peer dependencies, there shouldn't be any peerDep warnings
 
 const MIN_PORT_NUM = 56000;
 const MAX_PORT_NUM = 65535;
@@ -59,11 +69,63 @@ const PORT_RANGE = MAX_PORT_NUM - MIN_PORT_NUM;
 const getRandomPort = () => Math.floor(Math.random() * PORT_RANGE) + MIN_PORT_NUM;
 
 async function runYarn(args: Array<string> = [], options: Object = {}): Promise<Array<Buffer>> {
-  const {stdout, stderr} = await execa(path.resolve(__dirname, '../bin/yarn'), args, options);
+  if (!options['env']) {
+    options['env'] = {...process.env};
+    options['extendEnv'] = false;
+  }
+  options['env']['FORCE_COLOR'] = 0;
+  const {stdout, stderr} = await execa.shell(sh`${path.resolve(__dirname, '../bin/yarn')} ${args}`, options);
 
   return [stdout, stderr];
 }
 
+describe('production', () => {
+  test('it should be true when NODE_ENV=production', async () => {
+    const cwd = await makeTemp();
+    const options = {cwd, env: {YARN_SILENT: 1, NODE_ENV: 'production'}};
+
+    const [stdoutOutput, _] = await runYarn(['config', 'current'], options);
+
+    expect(JSON.parse(stdoutOutput.toString())).toHaveProperty('production', true);
+  });
+
+  test('it should default to false', async () => {
+    const cwd = await makeTemp();
+    const options = {cwd, env: {YARN_SILENT: 1, NODE_ENV: ''}};
+
+    const [stdoutOutput, _] = await runYarn(['config', 'current'], options);
+
+    expect(JSON.parse(stdoutOutput.toString())).toHaveProperty('production', false);
+  });
+
+  test('it should prefer CLI over NODE_ENV', async () => {
+    const cwd = await makeTemp();
+    const options = {cwd, env: {YARN_SILENT: 1, NODE_ENV: 'production'}};
+
+    const [stdoutOutput, _] = await runYarn(['--prod', 'false', 'config', 'current'], options);
+
+    expect(JSON.parse(stdoutOutput.toString())).toHaveProperty('production', false);
+  });
+
+  test('it should prefer YARN_PRODUCTION over NODE_ENV', async () => {
+    const cwd = await makeTemp();
+    const options = {cwd, env: {YARN_SILENT: 1, YARN_PRODUCTION: 'false', NODE_ENV: 'production'}};
+
+    const [stdoutOutput, _] = await runYarn(['config', 'current'], options);
+
+    expect(JSON.parse(stdoutOutput.toString())).toHaveProperty('production', false);
+  });
+
+  test('it should prefer CLI over YARN_PRODUCTION', async () => {
+    const cwd = await makeTemp();
+    const options = {cwd, env: {YARN_SILENT: 1, YARN_PRODUCTION: 'false', NODE_ENV: 'production'}};
+
+    const [stdoutOutput, _] = await runYarn(['--prod', '1', 'config', 'current'], options);
+
+    expect(JSON.parse(stdoutOutput.toString())).toHaveProperty('production', true);
+  });
+});
+
 test('--mutex network', async () => {
   const cwd = await makeTemp();
 
@@ -89,6 +151,73 @@ test('--mutex network', async () => {
   await Promise.all(promises);
 });
 
+describe('--registry option', () => {
+  test('--registry option with npm registry', async () => {
+    const cwd = await makeTemp();
+
+    const registry = 'https://registry.npmjs.org';
+    const packageJsonPath = path.join(cwd, 'package.json');
+    await fs.writeFile(packageJsonPath, JSON.stringify({}));
+
+    await runYarn(['add', 'left-pad', '--registry', registry], {cwd});
+
+    const packageJson = JSON.parse(await fs.readFile(packageJsonPath));
+    const lockfile = explodeLockfile(await fs.readFile(path.join(cwd, 'yarn.lock')));
+
+    expect(packageJson.dependencies['left-pad']).toBeDefined();
+    expect(lockfile).toHaveLength(3);
+    expect(lockfile[2]).toContain(registry);
+  });
+
+  test('--registry option with yarn registry', async () => {
+    const cwd = await makeTemp();
+
+    const registry = 'https://registry.yarnpkg.com';
+    const packageJsonPath = path.join(cwd, 'package.json');
+    await fs.writeFile(packageJsonPath, JSON.stringify({}));
+
+    await runYarn(['add', 'is-array', '--registry', registry], {cwd});
+
+    const packageJson = JSON.parse(await fs.readFile(packageJsonPath));
+    const lockfile = explodeLockfile(await fs.readFile(path.join(cwd, 'yarn.lock')));
+
+    expect(packageJson.dependencies['is-array']).toBeDefined();
+    expect(lockfile).toHaveLength(3);
+    expect(lockfile[2]).toContain(registry);
+  });
+
+  test('--registry option with non-exiting registry and show an error', async () => {
+    const cwd = await makeTemp();
+    const registry = 'https://example-registry-doesnt-exist.com';
+
+    try {
+      await runYarn(['add', 'is-array', '--registry', registry], {cwd});
+    } catch (err) {
+      const stdoutOutput = err.message;
+      expect(stdoutOutput.toString()).toMatch(/getaddrinfo ENOTFOUND example-registry-doesnt-exist\.com/g);
+    }
+  });
+
+  test('registry option from yarnrc', async () => {
+    const cwd = await makeTemp();
+
+    const registry = 'https://registry.npmjs.org';
+    await fs.writeFile(`${cwd}/.yarnrc`, 'registry "' + registry + '"\n');
+
+    const packageJsonPath = path.join(cwd, 'package.json');
+    await fs.writeFile(packageJsonPath, JSON.stringify({}));
+
+    await runYarn(['add', 'left-pad'], {cwd});
+
+    const packageJson = JSON.parse(await fs.readFile(packageJsonPath));
+    const lockfile = explodeLockfile(await fs.readFile(path.join(cwd, 'yarn.lock')));
+
+    expect(packageJson.dependencies['left-pad']).toBeDefined();
+    expect(lockfile).toHaveLength(3);
+    expect(lockfile[2]).toContain(registry);
+  });
+});
+
 test('--cwd option', async () => {
   const cwd = await makeTemp();
 
@@ -104,61 +233,92 @@ test('--cwd option', async () => {
   expect(packageJson.dependencies['left-pad']).toBeDefined();
 });
 
-test('yarnrc binary path (js)', async () => {
+test('yarnrc arguments', async () => {
   const cwd = await makeTemp();
 
-  await fs.writeFile(`${cwd}/.yarnrc`, 'yarn-path "./override.js"\n');
-  await fs.writeFile(`${cwd}/override.js`, 'console.log("override called")\n');
+  await fs.writeFile(
+    `${cwd}/.yarnrc`,
+    ['--emoji false', '--json true', '--add.exact true', '--no-progress true', '--cache-folder "./yarn-cache"'].join(
+      '\n',
+    ),
+  );
+  await fs.writeFile(`${cwd}/package.json`, JSON.stringify({name: 'test', license: 'ISC', version: '1.0.0'}));
 
-  const [stdoutOutput] = await runYarn([], {cwd});
-  expect(stdoutOutput.toString().trim()).toEqual('override called');
+  const [stdoutOutput] = await runYarn(['add', 'left-pad'], {cwd});
+  expect(stdoutOutput).toMatchSnapshot('yarnrc-args');
+  expect(JSON.parse(await fs.readFile(`${cwd}/package.json`)).dependencies['left-pad']).toMatch(/^\d+\./);
+  expect((await fs.stat(`${cwd}/yarn-cache`)).isDirectory()).toBe(true);
 });
 
-test('yarnrc binary path (executable)', async () => {
-  const cwd = await makeTemp();
+describe('yarnrc path', () => {
+  test('js file', async () => {
+    const cwd = await makeTemp();
 
-  if (process.platform === 'win32') {
-    await fs.writeFile(`${cwd}/.yarnrc`, 'yarn-path "./override.cmd"\n');
-    await fs.writeFile(`${cwd}/override.cmd`, '@echo override called\n');
-  } else {
-    await fs.writeFile(`${cwd}/.yarnrc`, 'yarn-path "./override"\n');
-    await fs.writeFile(`${cwd}/override`, '#!/usr/bin/env sh\necho override called\n');
-    await fs.chmod(`${cwd}/override`, 0o755);
-  }
+    await fs.writeFile(`${cwd}/.yarnrc`, 'yarn-path "./override.js"\n');
+    await fs.writeFile(`${cwd}/override.js`, 'console.log("override called")\n');
 
-  const [stdoutOutput] = await runYarn([], {cwd});
-  expect(stdoutOutput.toString().trim()).toEqual('override called');
-});
+    const [stdoutOutput] = await runYarn([], {cwd});
+    expect(stdoutOutput.toString().trim()).toEqual('override called');
+  });
 
-// Windows could run these tests, but we currently suffer from an escaping issue that breaks them (#4135)
-if (process.platform !== 'win32') {
-  test('yarn run <script> --opt', async () => {
+  test('executable file', async () => {
     const cwd = await makeTemp();
 
-    await fs.writeFile(
-      path.join(cwd, 'package.json'),
-      JSON.stringify({
-        scripts: {echo: `echo`},
-      }),
-    );
+    if (process.platform === 'win32') {
+      await fs.writeFile(`${cwd}/.yarnrc`, 'yarn-path "./override.cmd"\n');
+      await fs.writeFile(`${cwd}/override.cmd`, '@echo override called\n');
+    } else {
+      await fs.writeFile(`${cwd}/.yarnrc`, 'yarn-path "./override"\n');
+      await fs.writeFile(`${cwd}/override`, '#!/usr/bin/env sh\necho override called\n');
+      await fs.chmod(`${cwd}/override`, 0o755);
+    }
+
+    const [stdoutOutput] = await runYarn([], {cwd});
+    expect(stdoutOutput.toString().trim()).toEqual('override called');
+  });
 
-    const command = path.resolve(__dirname, '../bin/yarn');
-    const options = {cwd, env: {YARN_SILENT: 1}};
+  test('js file exit code', async () => {
+    const cwd = await makeTemp();
 
-    const {stderr: stderr, stdout: stdout} = execa(command, ['run', 'echo', '--opt'], options);
+    await fs.writeFile(`${cwd}/.yarnrc`, 'yarn-path "./override.js"\n');
+    await fs.writeFile(`${cwd}/override.js`, 'process.exit(123);');
 
-    const stdoutPromise = misc.consumeStream(stdout);
-    const stderrPromise = misc.consumeStream(stderr);
+    let error = false;
+    try {
+      await runYarn([], {cwd});
+    } catch (err) {
+      error = err.code;
+    }
 
-    const [stdoutOutput, stderrOutput] = await Promise.all([stdoutPromise, stderrPromise]);
+    expect(error).toEqual(123);
+  });
 
-    expect(stdoutOutput.toString().trim()).toEqual('--opt');
-    expect(stderrOutput.toString()).not.toMatch(
-      /From Yarn 1\.0 onwards, scripts don't require "--" for options to be forwarded/,
-    );
+  test('sh file exit code', async () => {
+    const cwd = await makeTemp();
+
+    if (process.platform !== 'win32') {
+      await fs.writeFile(`${cwd}/.yarnrc`, 'yarn-path "./override.sh"\n');
+      await fs.writeFile(`${cwd}/override.sh`, '#!/usr/bin/env sh\n\nexit 123\n');
+
+      await fs.chmod(`${cwd}/override.sh`, 0o755);
+    } else {
+      await fs.writeFile(`${cwd}/.yarnrc`, 'yarn-path "./override.cmd"\r\n');
+      await fs.writeFile(`${cwd}/override.cmd`, 'exit /b 123\r\n');
+    }
+
+    let error = false;
+    try {
+      await runYarn([], {cwd});
+    } catch (err) {
+      error = err.code;
+    }
+
+    expect(error).toEqual(123);
   });
+});
 
-  test('yarn run <script> -- --opt', async () => {
+for (const withDoubleDash of [false, true]) {
+  test(`yarn run <script> ${withDoubleDash ? '-- ' : ''}--opt`, async () => {
     const cwd = await makeTemp();
 
     await fs.writeFile(
@@ -168,23 +328,38 @@ if (process.platform !== 'win32') {
       }),
     );
 
-    const command = path.resolve(__dirname, '../bin/yarn');
     const options = {cwd, env: {YARN_SILENT: 1}};
 
-    const {stderr: stderr, stdout: stdout} = execa(command, ['run', 'echo', '--', '--opt'], options);
-
-    const stdoutPromise = misc.consumeStream(stdout);
-    const stderrPromise = misc.consumeStream(stderr);
-
-    const [stdoutOutput, stderrOutput] = await Promise.all([stdoutPromise, stderrPromise]);
+    const [stdoutOutput, stderrOutput] = await runYarn(
+      ['run', 'echo', ...(withDoubleDash ? ['--'] : []), '--opt'],
+      options,
+    );
 
     expect(stdoutOutput.toString().trim()).toEqual('--opt');
-    expect(stderrOutput.toString()).toMatch(
+    (exp => (withDoubleDash ? exp : exp.not))(expect(stderrOutput.toString())).toMatch(
       /From Yarn 1\.0 onwards, scripts don't require "--" for options to be forwarded/,
     );
   });
 }
 
+test('yarn run <script> <strings that need escaping>', async () => {
+  const cwd = await makeTemp();
+
+  await fs.writeFile(
+    path.join(cwd, 'package.json'),
+    JSON.stringify({
+      scripts: {stringify: `node -p "JSON.stringify(process.argv.slice(1))"`},
+    }),
+  );
+
+  const options = {cwd, env: {YARN_SILENT: 1}};
+
+  const trickyStrings = ['$PWD', '%CD%', '^', '!', '\\', '>', '<', '|', '&', "'", '"', '`', '  '];
+  const [stdout] = await runYarn(['stringify', ...trickyStrings], options);
+
+  expect(stdout.toString().trim()).toEqual(JSON.stringify(trickyStrings));
+});
+
 test('cache folder fallback', async () => {
   const cwd = await makeTemp();
   const cacheFolder = path.join(cwd, '.cache');
@@ -220,3 +395,20 @@ test('yarn create', async () => {
 
   expect(stdoutOutput.toString()).toMatch(/<!doctype html>/);
 });
+
+test('yarn init -y', async () => {
+  const cwd = await makeTemp();
+  const innerDir = path.join(cwd, 'inner');
+  const initialManifestFile = JSON.stringify({name: 'test', license: 'ISC', version: '1.0.0'});
+
+  await fs.writeFile(`${cwd}/package.json`, initialManifestFile);
+  await fs.mkdirp(innerDir);
+
+  const options = {cwd: innerDir};
+  await runYarn(['init', '-y'], options);
+
+  expect(await fs.exists(path.join(innerDir, 'package.json'))).toEqual(true);
+
+  const manifestFile = await fs.readFile(path.join(cwd, 'package.json'));
+  expect(manifestFile).toEqual(initialManifestFile);
+});
diff --git a/__tests__/lifecycle-scripts.js b/__tests__/lifecycle-scripts.js
index 20d1bfb..1132a70 100644
--- a/__tests__/lifecycle-scripts.js
+++ b/__tests__/lifecycle-scripts.js
@@ -19,14 +19,15 @@ async function execCommand(cmd: string, packageName: string, env = process.env):
   await fs.copy(srcPackageDir, packageDir, new NoopReporter());
 
   return new Promise((resolve, reject) => {
+    const cleanedEnv = {...env};
+    cleanedEnv['YARN_SILENT'] = 0;
+    delete cleanedEnv['FORCE_COLOR'];
+
     exec(
       `node "${yarnBin}" ${cmd}`,
       {
         cwd: packageDir,
-        env: {
-          ...env,
-          YARN_SILENT: 0,
-        },
+        env: cleanedEnv,
       },
       (err, stdout) => {
         if (err) {
@@ -81,10 +82,10 @@ test.concurrent(
       execCommand('test', 'npm_config_argv_env_vars', env),
     ]);
 
-    expect(stdouts[0]).toContain('##install##');
-    expect(stdouts[1]).toContain('##install##');
-    expect(stdouts[2]).toContain('##test##');
-    expect(stdouts[3]).toContain('##test##');
+    expect(stdouts[0]).toContain('"install"');
+    expect(stdouts[1]).toContain('"install"');
+    expect(stdouts[2]).toContain('"run","test"');
+    expect(stdouts[3]).toContain('"run","test"');
   },
 );
 
diff --git a/__tests__/lockfile.js b/__tests__/lockfile.js
index 4f238b0..2c5a782 100644
--- a/__tests__/lockfile.js
+++ b/__tests__/lockfile.js
@@ -23,6 +23,7 @@ test('parse', () => {
   expect(parse(`foo:\n  bar "bar"`).object).toEqual(nullify({foo: {bar: 'bar'}}));
   expect(parse(`foo:\n  bar:\n  foo "bar"`).object).toEqual(nullify({foo: {bar: {}, foo: 'bar'}}));
   expect(parse(`foo:\n  bar:\n    foo "bar"`).object).toEqual(nullify({foo: {bar: {foo: 'bar'}}}));
+  expect(parse(`foo:\r\n  bar:\r\n    foo "bar"`).object).toEqual(nullify({foo: {bar: {foo: 'bar'}}}));
   expect(parse('foo:\n  bar:\n    yes no\nbar:\n  yes no').object).toEqual(
     nullify({
       foo: {
@@ -35,6 +36,18 @@ test('parse', () => {
       },
     }),
   );
+  expect(parse('foo:\r\n  bar:\r\n    yes no\r\nbar:\r\n  yes no').object).toEqual(
+    nullify({
+      foo: {
+        bar: {
+          yes: 'no',
+        },
+      },
+      bar: {
+        yes: 'no',
+      },
+    }),
+  );
 });
 
 test('stringify', () => {
@@ -227,6 +240,22 @@ d:
   });
 });
 
+test('parse single merge conflict with CRLF', () => {
+  const file =
+    'a:\r\n  no "yes"\r\n\r\n<<<<<<< HEAD\r\nb:\r\n  foo "bar"' +
+    '\r\n=======\r\nc:\r\n  bar "foo"\r\n>>>>>>> branch-a' +
+    '\r\n\r\nd:\r\n  yes "no"\r\n';
+
+  const {type, object} = parse(file);
+  expect(type).toEqual('merge');
+  expect(object).toEqual({
+    a: {no: 'yes'},
+    b: {foo: 'bar'},
+    c: {bar: 'foo'},
+    d: {yes: 'no'},
+  });
+});
+
 test('parse multiple merge conflicts', () => {
   const file = `
 a:
diff --git a/__tests__/package-request.js b/__tests__/package-request.js
index bc883b0..814ceed 100644
--- a/__tests__/package-request.js
+++ b/__tests__/package-request.js
@@ -6,16 +6,25 @@ import PackageResolver from '../src/package-resolver.js';
 import Lockfile from '../src/lockfile';
 import Config from '../src/config.js';
 
-async function prepareRequest(pattern, version, resolved): Object {
+async function prepareRequest(pattern: string, version: string, resolved: string, parentRequest?: Object): Object {
   const privateDepCache = {[pattern]: {version, resolved}};
-  const lockfile = new Lockfile({cache: privateDepCache});
   const reporter = new reporters.NoopReporter({});
   const depRequestPattern = {
     pattern,
     registry: 'npm',
     hint: null,
     optional: false,
+    parentNames: [],
+    parentRequest,
   };
+  if (parentRequest) {
+    depRequestPattern.parentRequest = parentRequest;
+    depRequestPattern.parentNames = [...parentRequest.parentNames, parentRequest.pattern];
+
+    const lock = parentRequest.getLocked();
+    privateDepCache[parentRequest.pattern] = {version: lock.version, resolved: lock._remote.resolved};
+  }
+  const lockfile = new Lockfile({cache: privateDepCache});
   const config = await Config.create({}, reporter);
   const resolver = new PackageResolver(config, lockfile);
   const request = new PackageRequest(depRequestPattern, resolver);
@@ -36,6 +45,25 @@ test('Produce valid remote type for a git private dep', async () => {
   await reporter.close();
 });
 
+test('Check parentNames flowing in the request', async () => {
+  const {request: parentRequest, reporter: parentReporter} = await prepareRequest(
+    'parent at 1.0.0',
+    '1.0.0',
+    'git+ssh://git@github.com/yarnpkg/parent.git',
+  );
+  expect(parentRequest).not.toBeNull();
+  const {request: childRequest, reporter: childReporter} = await prepareRequest(
+    'child at 1.0.0',
+    '1.0.0',
+    'git+ssh://git@github.com/yarnpkg/child.git',
+    parentRequest,
+  );
+
+  expect(childRequest.parentNames).toContain(parentRequest.pattern);
+  await parentReporter.close();
+  await childReporter.close();
+});
+
 test('Produce valid remote type for a git public dep', async () => {
   const {request, reporter} = await prepareRequest(
     'public-dep at yarnpkg/public-dep#1fde368',
diff --git a/__tests__/package-resolver.js b/__tests__/package-resolver.js
index 4e7c3f2..5e8f2b9 100644
--- a/__tests__/package-resolver.js
+++ b/__tests__/package-resolver.js
@@ -17,8 +17,7 @@ const path = require('path');
 const cachePathRe = /-\d+\.\d+\.\d+-[\dabcdef]{40}$/;
 
 function addTest(pattern, registry = 'npm', init: ?(cacheFolder: string) => Promise<any>, offline = false) {
-  // concurrently network requests tend to stall
-  test(`${offline ? 'offline ' : ''}resolve ${pattern}`, async () => {
+  test.concurrent(`${offline ? 'offline ' : ''}resolve ${pattern}`, async () => {
     const lockfile = new Lockfile();
     const reporter = new reporters.NoopReporter({});
 
diff --git a/__tests__/reporters/__snapshots__/console-reporter.js.snap b/__tests__/reporters/__snapshots__/console-reporter.js.snap
index ebd8420..ad869a2 100644
--- a/__tests__/reporters/__snapshots__/console-reporter.js.snap
+++ b/__tests__/reporters/__snapshots__/console-reporter.js.snap
@@ -58,7 +58,7 @@ Object {
 
 exports[`ConsoleReporter.progress 1`] = `
 Object {
-  "stderr": "░░ 0/2█░ 1/2",
+  "stderr": "[--] 0/2[#-] 1/2",
   "stdout": "",
 }
 `;
@@ -132,11 +132,11 @@ Object {
 }
 `;
 
-exports[`ProgressBar 1`] = `"░░ 0/2"`;
+exports[`ProgressBar 1`] = `"[--] 0/2"`;
 
-exports[`ProgressBar 2`] = `"░░ 0/2█░ 1/2"`;
+exports[`ProgressBar 2`] = `"[--] 0/2[#-] 1/2"`;
 
-exports[`ProgressBar 3`] = `"░░ 0/2█░ 1/2██ 2/2"`;
+exports[`ProgressBar 3`] = `"[--] 0/2[#-] 1/2[##] 2/2"`;
 
 exports[`Spinner 1`] = `"⠁ "`;
 
@@ -148,7 +148,7 @@ exports[`Spinner 4`] = `"⠁ ⠂ foo⠄ bar"`;
 
 exports[`close 1`] = `
 Object {
-  "stderr": "░░ 0/2█░ 1/2⠁ ",
+  "stderr": "[--] 0/2[#-] 1/2⠁ ",
   "stdout": "",
 }
 `;
diff --git a/__tests__/reporters/console-reporter.js b/__tests__/reporters/console-reporter.js
index 628bc23..ddee4f1 100644
--- a/__tests__/reporters/console-reporter.js
+++ b/__tests__/reporters/console-reporter.js
@@ -12,8 +12,10 @@ const stream = require('stream');
 // ensures consistency across environments
 require('chalk').enabled = true;
 require('chalk').supportsColor = true;
-require('chalk').styles.blue.open = '\u001b[34m';
-require('chalk').styles.bold.close = '\u001b[22m';
+// $FlowFixMe - flow-typed doesn't have definitions for Chalk 2.x.x
+require('chalk').level = 2;
+require('chalk').blue._styles[0].open = '\u001b[34m';
+require('chalk').bold._styles[0].close = '\u001b[22m';
 
 test('ConsoleReporter.step', async () => {
   expect(
diff --git a/__tests__/util/git.js b/__tests__/util/git.js
index d561bef..f698145 100644
--- a/__tests__/util/git.js
+++ b/__tests__/util/git.js
@@ -59,12 +59,12 @@ test('npmUrlToGitUrl', () => {
   expect(Git.npmUrlToGitUrl('github:npm-opam/ocamlfind.git#v1.2.3')).toEqual({
     protocol: 'ssh:',
     hostname: 'github.com',
-    repository: 'ssh://git@github.com/npm-opam/ocamlfind.git#v1.2.3',
+    repository: 'ssh://git@github.com/npm-opam/ocamlfind.git',
   });
   expect(Git.npmUrlToGitUrl('github:npm-opam/ocamlfind#v1.2.3')).toEqual({
     protocol: 'ssh:',
     hostname: 'github.com',
-    repository: 'ssh://git@github.com/npm-opam/ocamlfind#v1.2.3',
+    repository: 'ssh://git@github.com/npm-opam/ocamlfind',
   });
   expect(Git.npmUrlToGitUrl('github:npm-opam/ocamlfind.git')).toEqual({
     protocol: 'ssh:',
diff --git a/__tests__/util/path.js b/__tests__/util/path.js
index 0abfeac..7ad3664 100644
--- a/__tests__/util/path.js
+++ b/__tests__/util/path.js
@@ -12,45 +12,7 @@ jest.mock('path', () => {
   return path;
 });
 
-import {expandPath, resolveWithHome} from '../../src/util/path.js';
-
-describe('expandPath', () => {
-  const realPlatform = process.platform;
-
-  describe('!win32', () => {
-    beforeAll(() => {
-      process.platform = 'notWin32';
-    });
-
-    afterAll(() => {
-      process.platform = realPlatform;
-    });
-
-    test('Paths get expanded', () => {
-      expect(expandPath('~/bar/baz/q')).toEqual('/home/foo/bar/baz/q');
-      expect(expandPath('  ~/bar/baz')).toEqual('/home/foo/bar/baz');
-      expect(expandPath('./~/bar/baz')).toEqual('./~/bar/baz');
-      expect(expandPath('~/~/bar/baz')).toEqual('/home/foo/~/bar/baz');
-    });
-  });
-
-  describe('win32', () => {
-    beforeAll(() => {
-      process.platform = 'win32';
-    });
-
-    afterAll(() => {
-      process.platform = realPlatform;
-    });
-
-    test('Paths never get expanded', () => {
-      expect(expandPath('~/bar/baz/q')).toEqual('~/bar/baz/q');
-      expect(expandPath('  ~/bar/baz')).toEqual('  ~/bar/baz');
-      expect(expandPath('./~/bar/baz')).toEqual('./~/bar/baz');
-      expect(expandPath('~/~/bar/baz')).toEqual('~/~/bar/baz');
-    });
-  });
-});
+import {resolveWithHome} from '../../src/util/path.js';
 
 describe('resolveWithHome', () => {
   const realPlatform = process.platform;
diff --git a/__tests__/util/root-user.js b/__tests__/util/root-user.js
index 4e21426..e06a4d2 100644
--- a/__tests__/util/root-user.js
+++ b/__tests__/util/root-user.js
@@ -1,9 +1,24 @@
 /* @flow */
 
-import {isRootUser} from '../../src/util/root-user.js';
+import {isRootUser, isFakeRoot} from '../../src/util/root-user.js';
 
 test('isRootUser', () => {
   expect(isRootUser(null)).toBe(false);
   expect(isRootUser(1001)).toBe(false);
   expect(isRootUser(0)).toBe(true);
 });
+
+test('isFakeRoot', () => {
+  const hasFakerootPreviously = 'FAKEROOTKEY' in process.env;
+  const oldValue = process.env.FAKEROOTKEY;
+  delete process.env.FAKEROOTKEY;
+
+  expect(isFakeRoot()).toBe(false);
+
+  process.env.FAKEROOTKEY = '15574641';
+  expect(isFakeRoot()).toBe(true);
+
+  if (hasFakerootPreviously) {
+    process.env.FAKEROOTKEY = oldValue;
+  }
+});
diff --git a/appveyor.yml b/appveyor.yml
index d84c0d4..abca8a0 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,5 +1,6 @@
 environment:
   matrix:
+    - node_version: "8"
     - node_version: "6"
     - node_version: "4"
 
diff --git a/bin/yarn b/bin/yarn
index 5e9985c..84e0f13 100755
--- a/bin/yarn
+++ b/bin/yarn
@@ -12,7 +12,7 @@ command_exists() {
 }
 
 if command_exists node; then
-  if [ "$YARN_FORCE_WINPTY" = 1 ]; then
+  if [ "$YARN_FORCE_WINPTY" = 1 ] || command_exists winpty && test -t 1; then
     winpty node "$basedir/yarn.js" "$@"
   else
     exec node "$basedir/yarn.js" "$@"
diff --git a/circle.yml b/circle.yml
index 93f57af..bfde41f 100644
--- a/circle.yml
+++ b/circle.yml
@@ -7,7 +7,7 @@ general:
 
 machine:
   node:
-    version: 6
+    version: 8
 
 dependencies:
   cache_directories:
diff --git a/package.json b/package.json
index d562ef6..43ac51b 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "yarn",
   "installationMethod": "unknown",
-  "version": "1.0.2",
+  "version": "1.2.1",
   "license": "BSD-2-Clause",
   "preferGlobal": true,
   "description": "📦🐈 Fast, reliable, and secure dependency management.",
@@ -10,12 +10,13 @@
     "babel-runtime": "^6.0.0",
     "bytes": "^2.4.0",
     "camelcase": "^4.0.0",
-    "chalk": "^1.1.1",
+    "chalk": "^2.1.0",
     "cmd-shim": "^2.0.1",
     "commander": "^2.9.0",
     "death": "^1.0.0",
     "debug": "^2.2.0",
     "detect-indent": "^5.0.0",
+    "dnscache": "^1.0.1",
     "eslint-plugin-jest": "^20.0.3",
     "eslint-plugin-jsx-a11y": "^6.0.2",
     "eslint-plugin-relay": "0.0.8",
@@ -35,11 +36,13 @@
     "normalize-url": "^1.9.1",
     "object-path": "^0.11.2",
     "proper-lockfile": "^2.0.0",
+    "puka": "^1.0.0",
     "read": "^1.0.7",
     "request": "^2.81.0",
     "request-capture-har": "^1.2.2",
     "rimraf": "^2.5.0",
     "semver": "^5.1.0",
+    "strip-ansi": "^4.0.0",
     "strip-bom": "^3.0.0",
     "tar-fs": "^1.15.1",
     "tar-stream": "^1.5.2",
@@ -58,6 +61,8 @@
     "babel-preset-node5": "^10.2.0",
     "babel-preset-stage-0": "^6.0.0",
     "babylon": "^6.5.0",
+    "commitizen": "^2.9.6",
+    "cz-conventional-changelog": "^2.0.0",
     "eslint": "^4.3.0",
     "eslint-config-fb-strict": "^20.1.0-delta.3",
     "eslint-plugin-babel": "^4.0.0",
@@ -106,8 +111,10 @@
     "release-branch": "./scripts/release-branch.sh",
     "test": "yarn lint && yarn test-only",
     "test-ci": "yarn build && yarn test-only",
-    "test-only": "node --max_old_space_size=4096 node_modules/jest/bin/jest.js --coverage --verbose",
-    "watch": "gulp watch"
+    "test-only": "node --max_old_space_size=4096 node_modules/jest/bin/jest.js --verbose",
+    "test-coverage": "node --max_old_space_size=4096 node_modules/jest/bin/jest.js --coverage --verbose",
+    "watch": "gulp watch",
+    "commit": "git-cz"
   },
   "jest": {
     "collectCoverageFrom": [
@@ -122,5 +129,10 @@
       "updates/",
       "/_(temp|mock|install|init|helpers).js$"
     ]
+  },
+  "config": {
+    "commitizen": {
+      "path": "./node_modules/cz-conventional-changelog"
+    }
   }
 }
diff --git a/scripts/update-homebrew.sh b/scripts/update-homebrew.sh
index 77199da..4a8e603 100755
--- a/scripts/update-homebrew.sh
+++ b/scripts/update-homebrew.sh
@@ -21,14 +21,18 @@ fi
 PATH=$PATH:$HOME/.linuxbrew/bin/
 # Ensure homebrew-core is pointing to Homebrew rather than Linuxbrew
 pushd ~/.linuxbrew/Library/Taps/homebrew/homebrew-core
-#git remote set-url origin https://github.com/Daniel15/homebrew-core # for testing
-git remote set-url origin https://github.com/homebrew/homebrew-core
-git reset --hard HEAD
+git checkout master
 git clean -fd
-git fetch --prune origin
 # Remove any existing branch (eg. if the previous attempt failed)
-git checkout master
 git branch -D yarn-$version || true
+
+#git remote set-url origin https://github.com/Daniel15/homebrew-core # for testing
+git remote set-url origin https://github.com/homebrew/homebrew-core
+git fetch --prune origin
+# Use `git reset` instead of pull since we don't want a merge etc., we just want
+# local master to exactly reflect origin/master
+git reset --hard origin/master
+git clean -fd
 popd
 
 # Grab latest Yarn release so we can hash it
diff --git a/scripts/update-npm.sh b/scripts/update-npm.sh
index 5da8734..24125ea 100755
--- a/scripts/update-npm.sh
+++ b/scripts/update-npm.sh
@@ -27,4 +27,4 @@ if [ "$release_type" = "rc" ]; then
   npm_flags='--tag rc'
 fi;
 
-eval "npm publish '$tarball' --access public $npm_flags"
+eval "npx npm at 4 publish '$tarball' --access public $npm_flags"
diff --git a/src/cli/commands/add.js b/src/cli/commands/add.js
index 68f82e3..20309a1 100644
--- a/src/cli/commands/add.js
+++ b/src/cli/commands/add.js
@@ -32,16 +32,6 @@ export class Add extends Install {
     ]
       .filter(Boolean)
       .shift();
-
-    if (this.config.workspaceRootFolder && this.config.cwd === this.config.workspaceRootFolder) {
-      this.setIgnoreWorkspaces(true);
-      // flagsToOrgin defaults to being a hard `dependency` when no flags are passed (see above),
-      // so it incorrectly throws a warning when upgrading existing devDependencies in workspace root
-      // To allow for a successful upgrade, override flagsToOrigin when `existing` flag is passed by `upgrade` command
-      if (flags.existing) {
-        this.flagToOrigin = '';
-      }
-    }
   }
 
   args: Array<string>;
@@ -69,7 +59,9 @@ export class Add extends Install {
    * returns version for a pattern based on Manifest
    */
   getPatternVersion(pattern: string, pkg: Manifest): string {
-    const {exact, tilde} = this.flags;
+    const tilde = this.flags.tilde;
+    const configPrefix = String(this.config.getOption('save-prefix'));
+    const exact = this.flags.exact || Boolean(this.config.getOption('save-exact')) || configPrefix === '';
     const {hasVersion, range} = normalizePattern(pattern);
     let version;
 
@@ -86,7 +78,7 @@ export class Add extends Install {
       } else if (exact) {
         prefix = '';
       } else {
-        prefix = String(this.config.getOption('save-prefix')) || '^';
+        prefix = configPrefix || '^';
       }
 
       version = `${prefix}${pkg.version}`;
@@ -130,10 +122,11 @@ export class Add extends Install {
    */
 
   async init(): Promise<Array<string>> {
-    if (this.ignoreWorkspaces) {
-      if (this.flagToOrigin === 'dependencies') {
-        throw new MessageError(this.reporter.lang('workspacesPreferDevDependencies'));
-      }
+    const isWorkspaceRoot = this.config.workspaceRootFolder && this.config.cwd === this.config.workspaceRootFolder;
+
+    // running "yarn add something" in a workspace root is often a mistake
+    if (isWorkspaceRoot && !this.flags.ignoreWorkspaceRootCheck) {
+      throw new MessageError(this.reporter.lang('workspacesAddRootCheck'));
     }
 
     this.addedPatterns = [];
@@ -214,6 +207,7 @@ export function hasWrapper(commander: Object): boolean {
 
 export function setFlags(commander: Object) {
   commander.usage('add [packages ...] [flags]');
+  commander.option('-W, --ignore-workspace-root-check', 'required to run yarn add inside a workspace root');
   commander.option('-D, --dev', 'save package to your `devDependencies`');
   commander.option('-P, --peer', 'save package to your `peerDependencies`');
   commander.option('-O, --optional', 'save package to your `optionalDependencies`');
diff --git a/src/cli/commands/autoclean.js b/src/cli/commands/autoclean.js
index 49362ed..e97e65d 100644
--- a/src/cli/commands/autoclean.js
+++ b/src/cli/commands/autoclean.js
@@ -53,7 +53,6 @@ Gruntfile.js
 *.yml
 
 # misc
-*.gz
 *.md
 `.trim();
 
diff --git a/src/cli/commands/cache.js b/src/cli/commands/cache.js
index 9f09363..61f9ed0 100644
--- a/src/cli/commands/cache.js
+++ b/src/cli/commands/cache.js
@@ -7,6 +7,7 @@ import * as fs from '../../util/fs.js';
 import {METADATA_FILENAME} from '../../constants';
 
 const path = require('path');
+const micromatch = require('micromatch');
 
 export function hasWrapper(flags: Object, args: Array<string>): boolean {
   return args[0] !== 'dir';
@@ -32,6 +33,9 @@ async function list(config: Config, reporter: Reporter, flags: Object, args: Arr
         packagesMetadata.push(...(await readCacheMetadata(loc)));
       } else {
         const {registry, package: manifest, remote} = await config.readPackageMetadata(loc);
+        if (flags.pattern && !micromatch.contains(manifest.name, flags.pattern)) {
+          continue;
+        }
         packagesMetadata.push([manifest.name, manifest.version, registry, (remote && remote.resolved) || '']);
       }
     }
@@ -44,7 +48,7 @@ async function list(config: Config, reporter: Reporter, flags: Object, args: Arr
   reporter.table(['Name', 'Version', 'Registry', 'Resolved'], body);
 }
 
-export const {run, setFlags, examples} = buildSubCommands('cache', {
+const {run, setFlags: _setFlags, examples} = buildSubCommands('cache', {
   async ls(config: Config, reporter: Reporter, flags: Object, args: Array<string>): Promise<void> {
     reporter.warn(`\`yarn cache ls\` is deprecated. Please use \`yarn cache list\`.`);
     await list(config, reporter, flags, args);
@@ -116,3 +120,10 @@ export const {run, setFlags, examples} = buildSubCommands('cache', {
     }
   },
 });
+
+export {run, examples};
+
+export function setFlags(commander: Object) {
+  _setFlags(commander);
+  commander.option('--pattern [pattern]', 'filter cached packages by pattern');
+}
diff --git a/src/cli/commands/config.js b/src/cli/commands/config.js
index 0ba9965..03bba28 100644
--- a/src/cli/commands/config.js
+++ b/src/cli/commands/config.js
@@ -5,6 +5,41 @@ import type {Reporter} from '../../reporters/index.js';
 import type Config from '../../config.js';
 import buildSubCommands from './_build-sub-commands.js';
 
+const CONFIG_KEYS = [
+  // 'reporter',
+  'rootModuleFolders',
+  'registryFolders',
+  'linkedModules',
+  // 'registries',
+  'cache',
+  'cwd',
+  'looseSemver',
+  'commandName',
+  'preferOffline',
+  'modulesFolder',
+  'globalFolder',
+  'linkFolder',
+  'offline',
+  'binLinks',
+  'ignorePlatform',
+  'ignoreScripts',
+  'disablePrepublish',
+  'nonInteractive',
+  'workspaceRootFolder',
+  'lockfileFolder',
+  'networkConcurrency',
+  'childConcurrency',
+  'networkTimeout',
+  'workspacesEnabled',
+  'pruneOfflineMirror',
+  'enableMetaFolder',
+  'enableLockfileVersions',
+  'linkFileDependencies',
+  'cacheFolder',
+  'tempFolder',
+  'production',
+];
+
 export function hasWrapper(flags: Object, args: Array<string>): boolean {
   return args[0] !== 'get';
 }
@@ -55,4 +90,14 @@ export const {run, setFlags, examples} = buildSubCommands('config', {
 
     return true;
   },
+
+  current(config: Config, reporter: Reporter, flags: Object, args: Array<string>): boolean {
+    if (args.length) {
+      return false;
+    }
+
+    reporter.log(JSON.stringify(config, CONFIG_KEYS, 2), {force: true});
+
+    return true;
+  },
 });
diff --git a/src/cli/commands/global.js b/src/cli/commands/global.js
index 54de2f6..7a8879e 100644
--- a/src/cli/commands/global.js
+++ b/src/cli/commands/global.js
@@ -40,7 +40,7 @@ class GlobalAdd extends Add {
 const path = require('path');
 
 export function hasWrapper(flags: Object, args: Array<string>): boolean {
-  return args[0] !== 'bin';
+  return args[0] !== 'bin' && args[0] !== 'dir';
 }
 
 async function updateCwd(config: Config): Promise<void> {
@@ -228,7 +228,12 @@ const {run, setFlags: _setFlags} = buildSubCommands('global', {
   },
 
   async bin(config: Config, reporter: Reporter, flags: Object, args: Array<string>): Promise<void> {
-    reporter.log(await getBinFolder(config, flags));
+    reporter.log(await getBinFolder(config, flags), {force: true});
+  },
+
+  dir(config: Config, reporter: Reporter, flags: Object, args: Array<string>): Promise<void> {
+    reporter.log(config.globalFolder, {force: true});
+    return Promise.resolve();
   },
 
   async ls(config: Config, reporter: Reporter, flags: Object, args: Array<string>): Promise<void> {
diff --git a/src/cli/commands/init.js b/src/cli/commands/init.js
index a34055f..7d568aa 100644
--- a/src/cli/commands/init.js
+++ b/src/cli/commands/init.js
@@ -22,6 +22,8 @@ export function hasWrapper(commander: Object, args: Array<string>): boolean {
   return true;
 }
 
+export const shouldRunInCurrentCwd = true;
+
 export async function run(config: Config, reporter: Reporter, flags: Object, args: Array<string>): Promise<void> {
   const manifests = await config.getRootManifests();
 
diff --git a/src/cli/commands/install.js b/src/cli/commands/install.js
index 6bcec63..3488b80 100644
--- a/src/cli/commands/install.js
+++ b/src/cli/commands/install.js
@@ -67,6 +67,13 @@ type Flags = {
   optional: boolean,
   exact: boolean,
   tilde: boolean,
+  ignoreWorkspaceRootCheck: boolean,
+
+  // outdated, update-interactive
+  includeWorkspaceDeps: boolean,
+
+  // remove, upgrade
+  workspaceRootIsCwd: boolean,
 };
 
 /**
@@ -91,7 +98,7 @@ function getUpdateCommand(installationMethod: InstallationMethod): ?string {
   }
 
   if (installationMethod === 'npm') {
-    return 'npm update --global yarn';
+    return 'npm install --global yarn';
   }
 
   if (installationMethod === 'chocolatey') {
@@ -137,6 +144,13 @@ function normalizeFlags(config: Config, rawFlags: Object): Flags {
     optional: !!rawFlags.optional,
     exact: !!rawFlags.exact,
     tilde: !!rawFlags.tilde,
+    ignoreWorkspaceRootCheck: !!rawFlags.ignoreWorkspaceRootCheck,
+
+    // outdated, update-interactive
+    includeWorkspaceDeps: !!rawFlags.includeWorkspaceDeps,
+
+    // remove, update
+    workspaceRootIsCwd: rawFlags.workspaceRootIsCwd !== false,
   };
 
   if (config.getOption('ignore-scripts')) {
@@ -176,7 +190,6 @@ export class Install {
     this.integrityChecker = new InstallationIntegrityChecker(config);
     this.linker = new PackageLinker(config, this.resolver);
     this.scripts = new PackageInstallScripts(config, this.resolver, this.flags.force);
-    this.ignoreWorkspaces = false;
   }
 
   flags: Flags;
@@ -192,7 +205,6 @@ export class Install {
   rootPatternsToOrigin: {[pattern: string]: string};
   integrityChecker: InstallationIntegrityChecker;
   resolutionMap: ResolutionMap;
-  ignoreWorkspaces: boolean;
 
   /**
    * Create a list of dependency requests from the current directories manifests.
@@ -211,6 +223,13 @@ export class Install {
     const usedPatterns = [];
     let workspaceLayout;
 
+    // some commands should always run in the context of the entire workspace
+    const cwd =
+      this.flags.includeWorkspaceDeps || this.flags.workspaceRootIsCwd ? this.config.lockfileFolder : this.config.cwd;
+
+    // non-workspaces are always root, otherwise check for workspace root
+    const cwdIsRoot = !this.config.workspaceRootFolder || this.config.lockfileFolder === cwd;
+
     // exclude package names that are in install args
     const excludeNames = [];
     for (const pattern of excludePatterns) {
@@ -224,9 +243,23 @@ export class Install {
       excludeNames.push(parts.name);
     }
 
+    const stripExcluded = (manifest: Manifest) => {
+      for (const exclude of excludeNames) {
+        if (manifest.dependencies && manifest.dependencies[exclude]) {
+          delete manifest.dependencies[exclude];
+        }
+        if (manifest.devDependencies && manifest.devDependencies[exclude]) {
+          delete manifest.devDependencies[exclude];
+        }
+        if (manifest.optionalDependencies && manifest.optionalDependencies[exclude]) {
+          delete manifest.optionalDependencies[exclude];
+        }
+      }
+    };
+
     for (const registry of Object.keys(registries)) {
       const {filename} = registries[registry];
-      const loc = path.join(this.config.lockfileFolder, filename);
+      const loc = path.join(cwd, filename);
       if (!await fs.exists(loc)) {
         continue;
       }
@@ -234,7 +267,7 @@ export class Install {
       this.rootManifestRegistries.push(registry);
 
       const projectManifestJson = await this.config.readJson(loc);
-      await normalizeManifest(projectManifestJson, this.config.lockfileFolder, this.config, true);
+      await normalizeManifest(projectManifestJson, cwd, this.config, cwdIsRoot);
 
       Object.assign(this.resolutions, projectManifestJson.resolutions);
       Object.assign(manifest, projectManifestJson);
@@ -278,7 +311,7 @@ export class Install {
 
           this.rootPatternsToOrigin[pattern] = depType;
           patterns.push(pattern);
-          deps.push({pattern, registry, hint, optional});
+          deps.push({pattern, registry, hint, optional, workspaceName: manifest.name, workspaceLoc: manifest._loc});
         }
       };
 
@@ -286,28 +319,51 @@ export class Install {
       pushDeps('devDependencies', projectManifestJson, {hint: 'dev', optional: false}, !this.config.production);
       pushDeps('optionalDependencies', projectManifestJson, {hint: 'optional', optional: true}, true);
 
-      if (this.config.workspaceRootFolder && !this.ignoreWorkspaces) {
-        const workspacesRoot = path.dirname(loc);
-        const workspaces = await this.config.resolveWorkspaces(workspacesRoot, projectManifestJson);
+      if (this.config.workspaceRootFolder) {
+        const workspaceLoc = cwdIsRoot ? loc : path.join(this.config.lockfileFolder, filename);
+        const workspacesRoot = path.dirname(workspaceLoc);
+
+        let workspaceManifestJson = projectManifestJson;
+        if (!cwdIsRoot) {
+          // the manifest we read before was a child workspace, so get the root
+          workspaceManifestJson = await this.config.readJson(workspaceLoc);
+          await normalizeManifest(workspaceManifestJson, workspacesRoot, this.config, true);
+        }
+
+        const workspaces = await this.config.resolveWorkspaces(workspacesRoot, workspaceManifestJson);
         workspaceLayout = new WorkspaceLayout(workspaces, this.config);
+
         // add virtual manifest that depends on all workspaces, this way package hoisters and resolvers will work fine
+        const workspaceDependencies = {...workspaceManifestJson.dependencies};
+        for (const workspaceName of Object.keys(workspaces)) {
+          const workspaceManifest = workspaces[workspaceName].manifest;
+          workspaceDependencies[workspaceName] = workspaceManifest.version;
+
+          // include dependencies from all workspaces
+          if (this.flags.includeWorkspaceDeps) {
+            pushDeps('dependencies', workspaceManifest, {hint: null, optional: false}, true);
+            pushDeps('devDependencies', workspaceManifest, {hint: 'dev', optional: false}, !this.config.production);
+            pushDeps('optionalDependencies', workspaceManifest, {hint: 'optional', optional: true}, true);
+          }
+        }
         const virtualDependencyManifest: Manifest = {
           _uid: '',
           name: `workspace-aggregator-${uuid.v4()}`,
           version: '1.0.0',
           _registry: 'npm',
           _loc: workspacesRoot,
-          dependencies: {},
+          dependencies: workspaceDependencies,
+          devDependencies: {...workspaceManifestJson.devDependencies},
+          optionalDependencies: {...workspaceManifestJson.optionalDependencies},
         };
         workspaceLayout.virtualManifestName = virtualDependencyManifest.name;
-        virtualDependencyManifest.dependencies = {};
-        for (const workspaceName of Object.keys(workspaces)) {
-          virtualDependencyManifest.dependencies[workspaceName] = workspaces[workspaceName].manifest.version;
-        }
         const virtualDep = {};
         virtualDep[virtualDependencyManifest.name] = virtualDependencyManifest.version;
         workspaces[virtualDependencyManifest.name] = {loc: workspacesRoot, manifest: virtualDependencyManifest};
 
+        // ensure dependencies that should be excluded are stripped from the correct manifest
+        stripExcluded(cwdIsRoot ? virtualDependencyManifest : workspaces[projectManifestJson.name].manifest);
+
         pushDeps('workspaces', {workspaces: virtualDep}, {hint: 'workspaces', optional: false}, true);
       }
 
@@ -330,13 +386,6 @@ export class Install {
   }
 
   /**
-   * Sets the value of `ignoreWorkspaces` for install commands that should skip workspaces
-   */
-  setIgnoreWorkspaces(ignoreWorkspaces: boolean) {
-    this.ignoreWorkspaces = ignoreWorkspaces;
-  }
-
-  /**
    * TODO description
    */
 
@@ -914,7 +963,7 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
     let command = 'add';
     if (flags.global) {
       error = 'globalFlagRemoved';
-      command = 'global';
+      command = 'global add';
     }
     throw new MessageError(reporter.lang(error, `yarn ${command} ${exampleArgs.join(' ')}`));
   }
diff --git a/src/cli/commands/list.js b/src/cli/commands/list.js
index 2753dc2..41d913c 100644
--- a/src/cli/commands/list.js
+++ b/src/cli/commands/list.js
@@ -163,21 +163,22 @@ export function hasWrapper(commander: Object, args: Array<string>): boolean {
 
 export function setFlags(commander: Object) {
   commander.option('--depth [depth]', 'Limit the depth of the shown dependencies');
+  commander.option('--pattern [pattern]', 'Filter dependencies by pattern');
 }
 
 export function getReqDepth(inputDepth: string): number {
   return inputDepth && /^\d+$/.test(inputDepth) ? Number(inputDepth) : -1;
 }
 
-export function filterTree(tree: Tree, filters: Array<string>): boolean {
+export function filterTree(tree: Tree, filters: Array<string>, pattern: string = ''): boolean {
   if (tree.children) {
-    tree.children = tree.children.filter(child => filterTree(child, filters));
+    tree.children = tree.children.filter(child => filterTree(child, filters, pattern));
   }
 
   const notDim = tree.color !== 'dim';
   const hasChildren = tree.children == null ? false : tree.children.length > 0;
   const name = tree.name.slice(0, tree.name.lastIndexOf('@'));
-  const found = micromatch.any(name, filters);
+  const found = micromatch.any(name, filters) || micromatch.contains(name, pattern);
 
   return notDim && (found || hasChildren);
 }
@@ -212,7 +213,10 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
   let {trees}: {trees: Trees} = await buildTree(install.resolver, install.linker, activePatterns, opts);
 
   if (args.length) {
-    trees = trees.filter(tree => filterTree(tree, args));
+    reporter.warn(reporter.lang('deprecatedListArgs'));
+  }
+  if (args.length || flags.pattern) {
+    trees = trees.filter(tree => filterTree(tree, args, flags.pattern));
   }
 
   reporter.tree('list', trees);
diff --git a/src/cli/commands/outdated.js b/src/cli/commands/outdated.js
index 667b397..879dead 100644
--- a/src/cli/commands/outdated.js
+++ b/src/cli/commands/outdated.js
@@ -19,8 +19,8 @@ export function hasWrapper(commander: Object, args: Array<string>): boolean {
 }
 
 export async function run(config: Config, reporter: Reporter, flags: Object, args: Array<string>): Promise<number> {
-  const lockfile = await Lockfile.fromDirectory(config.cwd);
-  const install = new Install(flags, config, reporter, lockfile);
+  const lockfile = await Lockfile.fromDirectory(config.lockfileFolder);
+  const install = new Install({...flags, includeWorkspaceDeps: true}, config, reporter, lockfile);
   let deps = await PackageRequest.getOutdatedPackages(lockfile, install, config, reporter);
 
   if (args.length) {
@@ -30,21 +30,37 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
   }
 
   const getNameFromHint = hint => (hint ? `${hint}Dependencies` : 'dependencies');
-  const colorizeName = ({current, wanted, name}) => reporter.format[colorForVersions(current, wanted)](name);
+  const colorizeName = ({current, latest, name}) => reporter.format[colorForVersions(current, latest)](name);
 
   if (deps.length) {
+    const usesWorkspaces = !!config.workspaceRootFolder;
     const body = deps.map((info): Array<string> => {
-      return [
+      const row = [
         colorizeName(info),
         info.current,
         colorizeDiff(info.current, info.wanted, reporter),
         reporter.format.magenta(info.latest),
+        info.workspaceName || '',
         getNameFromHint(info.hint),
         reporter.format.cyan(info.url),
       ];
+      if (!usesWorkspaces) {
+        row.splice(4, 1);
+      }
+      return row;
     });
 
-    reporter.table(['Package', 'Current', 'Wanted', 'Latest', 'Package Type', 'URL'], body);
+    const red = reporter.format.red('<red>');
+    const yellow = reporter.format.yellow('<yellow>');
+    const green = reporter.format.green('<green>');
+    reporter.info(reporter.lang('legendColorsForVersionUpdates', red, yellow, green));
+
+    const header = ['Package', 'Current', 'Wanted', 'Latest', 'Workspace', 'Package Type', 'URL'];
+    if (!usesWorkspaces) {
+      header.splice(4, 1);
+    }
+    reporter.table(header, body);
+
     return 1;
   }
   return 0;
diff --git a/src/cli/commands/publish.js b/src/cli/commands/publish.js
index 20c566c..c2ab1e1 100644
--- a/src/cli/commands/publish.js
+++ b/src/cli/commands/publish.js
@@ -32,6 +32,11 @@ async function publish(config: Config, pkg: any, flags: Object, dir: string): Pr
     throw new MessageError(config.reporter.lang('invalidAccess'));
   }
 
+  // TODO this might modify package.json, do we need to reload it?
+  await config.executeLifecycleScript('prepublish');
+  await config.executeLifecycleScript('prepare');
+  await config.executeLifecycleScript('prepublishOnly');
+
   // get tarball stream
   const stat = await fs.lstat(dir);
   let stream;
@@ -60,11 +65,6 @@ async function publish(config: Config, pkg: any, flags: Object, dir: string): Pr
   const tbName = `${pkg.name}-${pkg.version}.tgz`;
   const tbURI = `${pkg.name}/-/${tbName}`;
 
-  // TODO this might modify package.json, do we need to reload it?
-  await config.executeLifecycleScript('prepublish');
-  await config.executeLifecycleScript('prepublishOnly');
-  await config.executeLifecycleScript('prepare');
-
   // create body
   const root = {
     _id: pkg.name,
diff --git a/src/cli/commands/remove.js b/src/cli/commands/remove.js
index 151cf96..53eea0b 100644
--- a/src/cli/commands/remove.js
+++ b/src/cli/commands/remove.js
@@ -29,7 +29,7 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
   let step = 0;
 
   // load manifests
-  const lockfile = await Lockfile.fromDirectory(config.cwd);
+  const lockfile = await Lockfile.fromDirectory(config.lockfileFolder);
   const rootManifests = await config.getRootManifests();
   const manifests = [];
 
@@ -52,7 +52,10 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
 
       const possibleManifestLoc = path.join(config.cwd, registry.folder, name);
       if (await fs.exists(possibleManifestLoc)) {
-        manifests.push([possibleManifestLoc, await config.readManifest(possibleManifestLoc, registryName)]);
+        const manifest = await config.maybeReadManifest(possibleManifestLoc, registryName);
+        if (manifest) {
+          manifests.push([possibleManifestLoc, manifest]);
+        }
       }
     }
 
@@ -73,7 +76,8 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
 
   // reinstall so we can get the updated lockfile
   reporter.step(++step, totalSteps, reporter.lang('uninstallRegenerate'));
-  const reinstall = new Install({force: true, ...flags}, config, new NoopReporter(), lockfile);
+  const installFlags = {force: true, workspaceRootIsCwd: true, ...flags};
+  const reinstall = new Install(installFlags, config, new NoopReporter(), lockfile);
   await reinstall.init();
 
   //
diff --git a/src/cli/commands/run.js b/src/cli/commands/run.js
index a26a663..f3c941a 100644
--- a/src/cli/commands/run.js
+++ b/src/cli/commands/run.js
@@ -10,11 +10,7 @@ import map from '../../util/map.js';
 
 const leven = require('leven');
 const path = require('path');
-
-// Copied from https://github.com/npm/npm/blob/63f153c743f9354376bfb9dad42bd028a320fd1f/lib/run-script.js#L175
-function joinArgs(args: Array<string>): string {
-  return args.reduce((joinedArgs, arg) => joinedArgs + ' "' + arg.replace(/"/g, '\\"') + '"', '');
-}
+const {quoteForShell, sh, unquoted} = require('puka');
 
 export function setFlags(commander: Object) {}
 
@@ -35,7 +31,7 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
       if (await fs.exists(binFolder)) {
         for (const name of await fs.readdir(binFolder)) {
           binCommands.push(name);
-          scripts[name] = `"${path.join(binFolder, name)}"`;
+          scripts[name] = quoteForShell(path.join(binFolder, name));
         }
       }
       visitedBinFolders.add(binFolder);
@@ -82,8 +78,7 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
       process.env.YARN_SILENT = '1';
       for (const [stage, cmd] of cmds) {
         // only tack on trailing arguments for default script, ignore for pre and post - #1595
-        const defaultScriptCmd = cmd + joinArgs(args);
-        const cmdWithArgs = stage === action ? defaultScriptCmd : cmd;
+        const cmdWithArgs = stage === action ? sh`${unquoted(cmd)} ${args}` : cmd;
         await execCommand(stage, config, cmdWithArgs, config.cwd);
       }
     } else if (action === 'env') {
diff --git a/src/cli/commands/upgrade-interactive.js b/src/cli/commands/upgrade-interactive.js
index aaa6f30..c68c454 100644
--- a/src/cli/commands/upgrade-interactive.js
+++ b/src/cli/commands/upgrade-interactive.js
@@ -6,9 +6,12 @@ import type Config from '../../config.js';
 import inquirer from 'inquirer';
 import Lockfile from '../../lockfile';
 import {Add} from './add.js';
-import {getOutdated} from './upgrade.js';
+import {getOutdated, cleanLockfile} from './upgrade.js';
 import colorForVersions from '../../util/color-for-versions';
 import colorizeDiff from '../../util/colorize-diff.js';
+import {Install} from './install.js';
+
+const path = require('path');
 
 export const requireLockfile = true;
 
@@ -35,18 +38,21 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
   const outdatedFieldName = flags.latest ? 'latest' : 'wanted';
   const lockfile = await Lockfile.fromDirectory(config.lockfileFolder);
 
-  const deps = await getOutdated(config, reporter, flags, lockfile, args);
+  const deps = await getOutdated(config, reporter, {...flags, includeWorkspaceDeps: true}, lockfile, args);
 
   if (deps.length === 0) {
     reporter.success(reporter.lang('allDependenciesUpToDate'));
     return;
   }
 
+  const usesWorkspaces = !!config.workspaceRootFolder;
+
   const maxLengthArr = {
     name: 'name'.length,
     current: 'from'.length,
     range: 'latest'.length,
     [outdatedFieldName]: 'to'.length,
+    workspaceName: 'workspace'.length,
   };
 
   const keysWithDynamicLength = ['name', 'current', outdatedFieldName];
@@ -56,6 +62,10 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
     keysWithDynamicLength.push('range');
   }
 
+  if (usesWorkspaces) {
+    keysWithDynamicLength.push('workspaceName');
+  }
+
   deps.forEach(dep =>
     keysWithDynamicLength.forEach(key => {
       maxLengthArr[key] = Math.max(maxLengthArr[key], dep[key].length);
@@ -78,7 +88,12 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
     const latest = colorizeDiff(dep.current, padding(outdatedFieldName), reporter);
     const url = reporter.format.cyan(dep.url);
     const range = reporter.format.blue(flags.latest ? 'latest' : padding('range'));
-    return `${name}  ${range}  ${current}  ❯  ${latest}  ${url}`;
+    if (usesWorkspaces) {
+      const workspace = padding('workspaceName');
+      return `${name}  ${range}  ${current}  ❯  ${latest}  ${workspace}  ${url}`;
+    } else {
+      return `${name}  ${range}  ${current}  ❯  ${latest}  ${url}`;
+    }
   };
 
   const makeHeaderRow = () => {
@@ -87,7 +102,12 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
     const from = headerPadding('from', 'current');
     const to = headerPadding('to', outdatedFieldName);
     const url = reporter.format.bold.underline('url');
-    return `  ${name}  ${range}  ${from}     ${to}  ${url}`;
+    if (usesWorkspaces) {
+      const workspace = headerPadding('workspace', 'workspaceName');
+      return `  ${name}  ${range}  ${from}     ${to}  ${workspace}  ${url}`;
+    } else {
+      return `  ${name}  ${range}  ${from}     ${to}  ${url}`;
+    }
   };
 
   const groupedDeps = deps.reduce((acc, dep) => {
@@ -119,7 +139,7 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
     const red = reporter.format.red('<red>');
     const yellow = reporter.format.yellow('<yellow>');
     const green = reporter.format.green('<green>');
-    reporter.info(reporter.lang('legendColorsForUpgradeInteractive', red, yellow, green));
+    reporter.info(reporter.lang('legendColorsForVersionUpdates', red, yellow, green));
 
     const answers: Array<Dependency> = await reporter.prompt('Choose which packages to update.', choices, {
       name: 'packages',
@@ -130,24 +150,34 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
     const getPattern = ({upgradeTo}) => upgradeTo;
     const isHint = x => ({hint}) => hint === x;
 
-    await [null, 'dev', 'optional', 'peer'].reduce(async (promise, hint) => {
-      // Wait for previous promise to resolve
-      await promise;
+    for (const hint of [null, 'dev', 'optional', 'peer']) {
       // Reset dependency flags
       flags.dev = hint === 'dev';
       flags.peer = hint === 'peer';
       flags.optional = hint === 'optional';
-      const deps = answers.filter(isHint(hint)).map(getPattern);
+      flags.ignoreWorkspaceRootCheck = true;
+      flags.includeWorkspaceDeps = false;
+      flags.workspaceRootIsCwd = false;
+      const deps = answers.filter(isHint(hint));
       if (deps.length) {
-        for (const pattern of deps) {
-          lockfile.removePattern(pattern);
+        const install = new Install(flags, config, reporter, lockfile);
+        const {requests: packagePatterns} = await install.fetchRequestFromCwd();
+        const depsByWorkspace = deps.reduce((acc, dep) => {
+          const {workspaceLoc} = dep;
+          const xs = acc[workspaceLoc] || [];
+          acc[workspaceLoc] = xs.concat(dep);
+          return acc;
+        }, {});
+        for (const loc of Object.keys(depsByWorkspace)) {
+          const patterns = depsByWorkspace[loc].map(getPattern);
+          cleanLockfile(lockfile, deps, packagePatterns, reporter);
+          reporter.info(reporter.lang('updateInstalling', getNameFromHint(hint)));
+          config.cwd = path.resolve(path.dirname(loc));
+          const add = new Add(patterns, flags, config, reporter, lockfile);
+          await add.init();
         }
-        reporter.info(reporter.lang('updateInstalling', getNameFromHint(hint)));
-        const add = new Add(deps, flags, config, reporter, lockfile);
-        return add.init();
       }
-      return Promise.resolve();
-    }, Promise.resolve());
+    }
   } catch (e) {
     Promise.reject(e);
   }
diff --git a/src/cli/commands/upgrade.js b/src/cli/commands/upgrade.js
index 106fae4..c7d6886 100644
--- a/src/cli/commands/upgrade.js
+++ b/src/cli/commands/upgrade.js
@@ -3,6 +3,7 @@
 import type {Dependency} from '../../types.js';
 import type {Reporter} from '../../reporters/index.js';
 import type Config from '../../config.js';
+import type {DependencyRequestPatterns} from '../../types.js';
 import {Add} from './add.js';
 import Lockfile from '../../lockfile';
 import PackageRequest from '../../package-request.js';
@@ -14,23 +15,46 @@ import {Install} from './install.js';
 const basicSemverOperatorRegex = new RegExp('^(\\^|~|>|<=|>=)?[^ |&,]+$');
 
 // used to detect if a passed parameter is a scope or a package name.
-const validScopeRegex = /^@[a-zA-Z0-9-][a-zA-Z0-9_.-]*\/$/g;
+const validScopeRegex = /^@[a-zA-Z0-9-][a-zA-Z0-9_.-]*\/$/;
 
 // If specific versions were requested for packages, override what getOutdated reported as the latest to install
 // Also add ones that are missing, since the requested packages may not have been outdated at all.
-function setUserRequestedPackageVersions(deps: Array<Dependency>, args: Array<string>) {
+function setUserRequestedPackageVersions(
+  deps: Array<Dependency>,
+  args: Array<string>,
+  latest: boolean,
+  packagePatterns,
+  reporter: Reporter,
+) {
   args.forEach(requestedPattern => {
-    const normalized = normalizePattern(requestedPattern);
-    const newPattern = `${normalized.name}@${normalized.range}`;
     let found = false;
+    let normalized = normalizePattern(requestedPattern);
+
+    // if the user specified a package name without a version range, then that implies "latest"
+    // but if the latest flag is not passed then we need to use the version range from package.json
+    if (!normalized.hasVersion && !latest) {
+      packagePatterns.forEach(packagePattern => {
+        const packageNormalized = normalizePattern(packagePattern.pattern);
+        if (packageNormalized.name === normalized.name) {
+          normalized = packageNormalized;
+        }
+      });
+    }
+
+    const newPattern = `${normalized.name}@${normalized.range}`;
 
+    // if this dependency is already in the outdated list,
+    // just update the upgradeTo to whatever version the user requested.
     deps.forEach(dep => {
       if (normalized.hasVersion && dep.name === normalized.name) {
         found = true;
         dep.upgradeTo = newPattern;
+        reporter.verbose(reporter.lang('verboseUpgradeBecauseRequested', requestedPattern, newPattern));
       }
     });
 
+    // if this dependency was not in the outdated list,
+    // then add a new entry
     if (normalized.hasVersion && !found) {
       deps.push({
         name: normalized.name,
@@ -41,16 +65,90 @@ function setUserRequestedPackageVersions(deps: Array<Dependency>, args: Array<st
         range: '',
         current: '',
         upgradeTo: newPattern,
+        workspaceName: '',
+        workspaceLoc: '',
       });
+      reporter.verbose(reporter.lang('verboseUpgradeBecauseRequested', requestedPattern, newPattern));
     }
   });
 }
 
+// this function attempts to determine the range operator on the semver range.
+// this will only handle the simple cases of a semver starting with '^', '~', '>', '>=', '<=', or an exact version.
+// "exotic" semver ranges will not be handled.
+function getRangeOperator(version): string {
+  const result = basicSemverOperatorRegex.exec(version);
+  return result ? result[1] || '' : '^';
+}
+
+// Attempt to preserve the range operator from the package.json specified semver range.
+// If an explicit operator was specified using --exact, --tilde, --caret, then that will take precedence.
+function buildPatternToUpgradeTo(dep, flags): string {
+  if (dep.latest === 'exotic') {
+    return dep.url;
+  }
+
+  const toLatest = flags.latest;
+  const toVersion = toLatest ? dep.latest : dep.range;
+  let rangeOperator = '';
+
+  if (toLatest) {
+    if (flags.caret) {
+      rangeOperator = '^';
+    } else if (flags.tilde) {
+      rangeOperator = '~';
+    } else if (flags.exact) {
+      rangeOperator = '';
+    } else {
+      rangeOperator = getRangeOperator(dep.range);
+    }
+  }
+
+  return `${dep.name}@${rangeOperator}${toVersion}`;
+}
+
+function scopeFilter(flags: Object, dep: Dependency): boolean {
+  if (validScopeRegex.test(flags.scope)) {
+    return dep.name.startsWith(flags.scope);
+  }
+  return true;
+}
+
+// Remove deps being upgraded from the lockfile, or else Add will use the already-installed version
+// instead of the latest for the range.
+// We do this recursively so that when Yarn installs the potentially updated transitive deps,
+// it may upgrade them too instead of just using the "locked" version from the lockfile.
+// Transitive dependencies that are also a direct dependency are skipped.
+export function cleanLockfile(
+  lockfile: Lockfile,
+  deps: Array<Dependency>,
+  packagePatterns: DependencyRequestPatterns,
+  reporter: Reporter,
+) {
+  function cleanDepFromLockfile(pattern: string, depth: number) {
+    const lockManifest = lockfile.getLocked(pattern);
+    if (!lockManifest || (depth > 1 && packagePatterns.some(packagePattern => packagePattern.pattern === pattern))) {
+      reporter.verbose(reporter.lang('verboseUpgradeNotUnlocking', pattern));
+      return;
+    }
+
+    const dependencies = Object.assign({}, lockManifest.dependencies || {}, lockManifest.optionalDependencies || {});
+    const depPatterns = Object.keys(dependencies).map(key => `${key}@${dependencies[key]}`);
+    reporter.verbose(reporter.lang('verboseUpgradeUnlocking', pattern));
+    lockfile.removePattern(pattern);
+    depPatterns.forEach(pattern => cleanDepFromLockfile(pattern, depth + 1));
+  }
+
+  const patterns = deps.map(dep => dep.upgradeTo);
+  patterns.forEach(pattern => cleanDepFromLockfile(pattern, 1));
+}
+
 export function setFlags(commander: Object) {
   commander.usage('upgrade [flags]');
   commander.option('-S, --scope <scope>', 'upgrade packages under the specified scope');
   commander.option('-L, --latest', 'list the latest version of packages, ignoring version ranges in package.json');
   commander.option('-E, --exact', 'install exact version. Only used when --latest is specified.');
+  commander.option('-P, --pattern [pattern]', 'upgrade packages that match pattern');
   commander.option(
     '-T, --tilde',
     'install most recent release with the same minor version. Only used when --latest is specified.',
@@ -68,25 +166,27 @@ export function hasWrapper(commander: Object, args: Array<string>): boolean {
 export const requireLockfile = true;
 
 export async function run(config: Config, reporter: Reporter, flags: Object, args: Array<string>): Promise<void> {
-  const lockfile = await Lockfile.fromDirectory(config.lockfileFolder);
+  let addArgs = [];
+  const upgradeAll = args.length === 0;
+  const addFlags = Object.assign({}, flags, {
+    force: true,
+    ignoreWorkspaceRootCheck: true,
+    workspaceRootIsCwd: config.cwd === config.lockfileFolder,
+  });
+  const lockfile = await Lockfile.fromDirectory(config.lockfileFolder, reporter);
   const deps = await getOutdated(config, reporter, flags, lockfile, args);
+  const install = new Install(flags, config, reporter, lockfile);
+  const {requests: packagePatterns} = await install.fetchRequestFromCwd();
 
-  // do not pass the --latest flag to add, otherwise it may ignore the version ranges we already determined.
-  const addFlags = Object.assign({}, flags, {force: true, latest: false, existing: true});
-
-  setUserRequestedPackageVersions(deps, args);
+  setUserRequestedPackageVersions(deps, args, flags.latest, packagePatterns, reporter);
+  cleanLockfile(lockfile, deps, packagePatterns, reporter);
+  addArgs = deps.map(dep => dep.upgradeTo);
 
-  if (!deps.length) {
-    reporter.success(reporter.lang('allDependenciesUpToDate'));
-    return;
+  if (flags.scope && validScopeRegex.test(flags.scope)) {
+    addArgs = addArgs.filter(depName => depName.startsWith(flags.scope));
   }
 
-  // remove deps being upgraded from the lockfile, or else Add will use the already-installed version
-  // instead of the latest for the range.
-  deps.forEach(dep => lockfile.removePattern(dep.upgradeTo));
-
-  const addArgs = deps.map(dep => dep.upgradeTo);
-  const add = new Add(addArgs, addFlags, config, reporter, lockfile);
+  const add = new Add(addArgs, addFlags, config, reporter, upgradeAll ? new Lockfile() : lockfile);
   await add.init();
 }
 
@@ -99,40 +199,7 @@ export async function getOutdated(
 ): Promise<Array<Dependency>> {
   const install = new Install(flags, config, reporter, lockfile);
   const outdatedFieldName = flags.latest ? 'latest' : 'wanted';
-
-  // this function attempts to determine the range operator on the semver range.
-  // this will only handle the simple cases of a semver starting with '^', '~', '>', '>=', '<=', or an exact version.
-  // "exotic" semver ranges will not be handled.
-  const getRangeOperator = version => {
-    const result = basicSemverOperatorRegex.exec(version);
-    return result ? result[1] || '' : '^';
-  };
-
-  // Attempt to preserve the range operator from the package.json specified semver range.
-  // If an explicit operator was specified using --exact, --tilde, --caret, then that will take precedence.
-  const buildPatternToUpgradeTo = (dep, flags) => {
-    if (dep.latest === 'exotic') {
-      return dep.url;
-    }
-
-    const toLatest = flags.latest;
-    const toVersion = toLatest ? dep.latest : dep.range;
-    let rangeOperator = '';
-
-    if (toLatest) {
-      if (flags.caret) {
-        rangeOperator = '^';
-      } else if (flags.tilde) {
-        rangeOperator = '~';
-      } else if (flags.exact) {
-        rangeOperator = '';
-      } else {
-        rangeOperator = getRangeOperator(dep.range);
-      }
-    }
-
-    return `${dep.name}@${rangeOperator}${toVersion}`;
-  };
+  const updateAll = patterns.length === 0;
 
   // ensure scope is of the form `@scope/`
   const normalizeScope = function() {
@@ -151,14 +218,6 @@ export async function getOutdated(
     return dep.current !== dep[outdatedFieldName];
   };
 
-  const scopeFilter = function(dep: Dependency): boolean {
-    if (validScopeRegex.test(flags.scope)) {
-      return dep.name.startsWith(flags.scope);
-    }
-
-    return true;
-  };
-
   if (!flags.latest) {
     // these flags only have an affect when --latest is used
     flags.tilde = false;
@@ -168,10 +227,21 @@ export async function getOutdated(
 
   normalizeScope();
 
-  const deps = (await PackageRequest.getOutdatedPackages(lockfile, install, config, reporter, patterns))
+  const deps = (await PackageRequest.getOutdatedPackages(
+    lockfile,
+    install,
+    config,
+    reporter,
+    patterns,
+    flags,
+    updateAll,
+  ))
     .filter(versionFilter)
-    .filter(scopeFilter);
-  deps.forEach(dep => (dep.upgradeTo = buildPatternToUpgradeTo(dep, flags)));
+    .filter(scopeFilter.bind(this, flags));
+  deps.forEach(dep => {
+    dep.upgradeTo = buildPatternToUpgradeTo(dep, flags);
+    reporter.verbose(reporter.lang('verboseUpgradeBecauseOutdated', dep.name, dep.upgradeTo));
+  });
 
   return deps;
 }
diff --git a/src/cli/index.js b/src/cli/index.js
index 2c5cb74..1b3d4c9 100644
--- a/src/cli/index.js
+++ b/src/cli/index.js
@@ -1,5 +1,17 @@
 /* @flow */
 
+import http from 'http';
+import net from 'net';
+import path from 'path';
+
+import commander from 'commander';
+import fs from 'fs';
+import invariant from 'invariant';
+import lockfile from 'proper-lockfile';
+import loudRejection from 'loud-rejection';
+import onDeath from 'death';
+import semver from 'semver';
+
 import {ConsoleReporter, JSONReporter} from '../reporters/index.js';
 import {registries, registryNames} from '../registries/index.js';
 import commands from './commands/index.js';
@@ -12,16 +24,6 @@ import {spawnp, forkp} from '../util/child.js';
 import {version} from '../util/yarn-version.js';
 import handleSignals from '../util/signal-handler.js';
 
-const commander = require('commander');
-const fs = require('fs');
-const invariant = require('invariant');
-const lockfile = require('proper-lockfile');
-const loudRejection = require('loud-rejection');
-const http = require('http');
-const net = require('net');
-const onDeath = require('death');
-const path = require('path');
-
 function findProjectRoot(base: string): string {
   let prev = null;
   let dir = base;
@@ -38,6 +40,8 @@ function findProjectRoot(base: string): string {
   return base;
 }
 
+const boolify = val => val.toString().toLowerCase() !== 'false' && val !== '0';
+
 export function main({
   startArgs,
   args,
@@ -68,7 +72,7 @@ export function main({
   commander.option('--check-files', 'install will verify file tree of packages for consistency');
   commander.option('--no-bin-links', "don't generate bin links when setting up packages");
   commander.option('--flat', 'only allow one version of a package');
-  commander.option('--prod, --production [prod]', '');
+  commander.option('--prod, --production [prod]', '', boolify);
   commander.option('--no-lockfile', "don't read or generate a lockfile");
   commander.option('--pure-lockfile', "don't generate a lockfile");
   commander.option('--frozen-lockfile', "don't generate a lockfile and fail if an update is needed");
@@ -82,16 +86,22 @@ export function main({
   commander.option('--preferred-cache-folder <path>', 'specify a custom folder to store the yarn cache if possible');
   commander.option('--cache-folder <path>', 'specify a custom folder that must be used to store the yarn cache');
   commander.option('--mutex <type>[:specifier]', 'use a mutex to ensure only one yarn instance is executing');
-  commander.option('--emoji [bool]', 'enable emoji in output', process.platform === 'darwin');
+  commander.option('--emoji [bool]', 'enable emoji in output', boolify, process.platform === 'darwin');
   commander.option('-s, --silent', 'skip Yarn console logs, other types of logs (script output) will be printed');
   commander.option('--cwd <cwd>', 'working directory to use', process.cwd());
   commander.option('--proxy <host>', '');
   commander.option('--https-proxy <host>', '');
+  commander.option('--registry <url>', 'override configuration registry');
   commander.option('--no-progress', 'disable progress bar');
   commander.option('--network-concurrency <number>', 'maximum number of concurrent network requests', parseInt);
   commander.option('--network-timeout <milliseconds>', 'TCP timeout for network requests', parseInt);
   commander.option('--non-interactive', 'do not show interactive prompts');
-  commander.option('--scripts-prepend-node-path [bool]', 'prepend the node executable dir to the PATH in scripts');
+  commander.option(
+    '--scripts-prepend-node-path [bool]',
+    'prepend the node executable dir to the PATH in scripts',
+    boolify,
+  );
+  commander.option('--no-node-version-check', 'do not warn when using a potentially unsupported Node version');
 
   // if -v is the first command, then always exit after returning the version
   if (args[0] === '-v') {
@@ -105,7 +115,7 @@ export function main({
     const isOption = arg.startsWith('-');
     const prev = idx > 0 && arr[idx - 1];
     const prevOption = prev && prev.startsWith('-') && commander.optionFor(prev);
-    const boundToPrevOption = prevOption && prevOption.required;
+    const boundToPrevOption = prevOption && (prevOption.optional || prevOption.required);
 
     return !isOption && !boundToPrevOption;
   });
@@ -140,19 +150,17 @@ export function main({
     setHelpMode();
   }
 
-  let command;
   if (!commandName) {
     commandName = 'install';
     isKnownCommand = true;
   }
 
-  if (isKnownCommand) {
-    command = commands[commandName];
-  } else {
+  if (!isKnownCommand) {
     // if command is not recognized, then set default to `run`
     args.unshift(commandName);
-    command = commands.run;
+    commandName = 'run';
   }
+  const command = commands[commandName];
 
   let warnAboutRunDashDash = false;
   // we are using "yarn <script> -abc" or "yarn run <script> -abc", we want -abc to be script options, not yarn options
@@ -205,6 +213,10 @@ export function main({
     reporter.header(commandName, {name: 'yarn', version});
   }
 
+  if (commander.nodeVersionCheck && !semver.satisfies(process.versions.node, constants.SUPPORTED_NODE_VERSIONS)) {
+    reporter.warn(reporter.lang('unsupportedNodeVersion', process.versions.node, constants.SUPPORTED_NODE_VERSIONS));
+  }
+
   if (command.noArguments && commander.args.length) {
     reporter.error(reporter.lang('noArguments'));
     reporter.info(command.getDocsInfo);
@@ -223,13 +235,6 @@ export function main({
   }
 
   //
-  if (command.requireLockfile && !fs.existsSync(path.join(config.cwd, constants.LOCKFILE_FILENAME))) {
-    reporter.error(reporter.lang('noRequiredLockfile'));
-    exit(1);
-    return;
-  }
-
-  //
   const run = (): Promise<void> => {
     invariant(command, 'missing command');
 
@@ -413,7 +418,10 @@ export function main({
     }
 
     // lockfile
-    const lockLoc = path.join(config.cwd, constants.LOCKFILE_FILENAME);
+    const lockLoc = path.join(
+      config.lockfileFolder || config.cwd, // lockfileFolder might not be set at this point
+      constants.LOCKFILE_FILENAME,
+    );
     const lockfile = fs.existsSync(lockLoc) ? fs.readFileSync(lockLoc, 'utf8') : 'No lockfile';
     log.push(`Lockfile: ${indent(lockfile)}`);
 
@@ -443,11 +451,12 @@ export function main({
     return errorReportLoc;
   }
 
-  const cwd = findProjectRoot(commander.cwd);
+  const cwd = command.shouldRunInCurrentCwd ? commander.cwd : findProjectRoot(commander.cwd);
 
   config
     .init({
       cwd,
+      commandName,
 
       binLinks: commander.binLinks,
       modulesFolder: commander.modulesFolder,
@@ -465,14 +474,18 @@ export function main({
       production: commander.production,
       httpProxy: commander.proxy,
       httpsProxy: commander.httpsProxy,
+      registry: commander.registry,
       networkConcurrency: commander.networkConcurrency,
       networkTimeout: commander.networkTimeout,
       nonInteractive: commander.nonInteractive,
       scriptsPrependNodePath: commander.scriptsPrependNodePath,
-
-      commandName: commandName === 'run' ? commander.args[0] : commandName,
     })
     .then(() => {
+      // lockfile check must happen after config.init sets lockfileFolder
+      if (command.requireLockfile && !fs.existsSync(path.join(config.lockfileFolder, constants.LOCKFILE_FILENAME))) {
+        throw new MessageError(reporter.lang('noRequiredLockfile'));
+      }
+
       // option "no-progress" stored in yarn config
       const noProgressConfig = config.registries.yarn.getOption('no-progress');
 
@@ -531,16 +544,19 @@ async function start(): Promise<void> {
   if (yarnPath && process.env.YARN_IGNORE_PATH !== '1') {
     const argv = process.argv.slice(2);
     const opts = {stdio: 'inherit', env: Object.assign({}, process.env, {YARN_IGNORE_PATH: 1})};
+    let exitCode = 0;
 
     try {
-      await spawnp(yarnPath, argv, opts);
+      exitCode = await spawnp(yarnPath, argv, opts);
     } catch (firstError) {
       try {
-        await forkp(yarnPath, argv, opts);
+        exitCode = await forkp(yarnPath, argv, opts);
       } catch (error) {
         throw firstError;
       }
     }
+
+    process.exitCode = exitCode;
   } else {
     // ignore all arguments after a --
     const doubleDashIndex = process.argv.findIndex(element => element === '--');
diff --git a/src/config.js b/src/config.js
index e969321..3a70831 100644
--- a/src/config.js
+++ b/src/config.js
@@ -5,7 +5,7 @@ import type {Reporter} from './reporters/index.js';
 import type {Manifest, PackageRemote, WorkspacesManifestMap} from './types.js';
 import type PackageReference from './package-reference.js';
 import {execFromManifest} from './util/execute-lifecycle-script.js';
-import {expandPath} from './util/path.js';
+import {resolveWithHome} from './util/path.js';
 import normalizeManifest from './util/normalize-manifest/index.js';
 import {MessageError} from './errors.js';
 import * as fs from './util/fs.js';
@@ -56,6 +56,7 @@ export type ConfigOptions = {
   httpsProxy?: ?string,
 
   commandName?: ?string,
+  registry?: ?string,
 };
 
 type PackageMetadata = {
@@ -191,11 +192,11 @@ export default class Config {
    * Get a config option from our yarn config.
    */
 
-  getOption(key: string, expand: boolean = false): mixed {
+  getOption(key: string, resolve: boolean = false): mixed {
     const value = this.registries.yarn.getOption(key);
 
-    if (expand && typeof value === 'string') {
-      return expandPath(value);
+    if (resolve && typeof value === 'string') {
+      return resolveWithHome(value);
     }
 
     return value;
@@ -249,7 +250,9 @@ export default class Config {
 
       // instantiate registry
       const registry = new Registry(this.cwd, this.registries, this.requestManager, this.reporter);
-      await registry.init();
+      await registry.init({
+        registry: opts.registry,
+      });
 
       this.registries[key] = registry;
       this.registryFolders.push(registry.folder);
@@ -270,10 +273,12 @@ export default class Config {
 
     this.networkTimeout = opts.networkTimeout || Number(this.getOption('network-timeout')) || constants.NETWORK_TIMEOUT;
 
+    const httpProxy = opts.httpProxy || this.getOption('proxy');
+    const httpsProxy = opts.httpsProxy || this.getOption('https-proxy');
     this.requestManager.setOptions({
       userAgent: String(this.getOption('user-agent')),
-      httpProxy: String(opts.httpProxy || this.getOption('proxy') || ''),
-      httpsProxy: String(opts.httpsProxy || this.getOption('https-proxy') || ''),
+      httpProxy: httpProxy === false ? false : String(httpProxy || ''),
+      httpsProxy: httpsProxy === false ? false : String(httpsProxy || ''),
       strictSSL: Boolean(this.getOption('strict-ssl')),
       ca: Array.prototype.concat(opts.ca || this.getOption('ca') || []).map(String),
       cafile: String(opts.cafile || this.getOption('cafile', true) || ''),
@@ -325,17 +330,14 @@ export default class Config {
     await fs.mkdirp(this.cacheFolder);
     await fs.mkdirp(this.tempFolder);
 
-    if (opts.production === 'false') {
-      this.production = false;
-    } else if (
-      this.getOption('production') ||
-      (process.env.NODE_ENV === 'production' &&
-        process.env.NPM_CONFIG_PRODUCTION !== 'false' &&
-        process.env.YARN_PRODUCTION !== 'false')
-    ) {
-      this.production = true;
+    if (opts.production !== undefined) {
+      this.production = Boolean(opts.production);
     } else {
-      this.production = !!opts.production;
+      this.production =
+        Boolean(this.getOption('production')) ||
+        (process.env.NODE_ENV === 'production' &&
+          process.env.NPM_CONFIG_PRODUCTION !== 'false' &&
+          process.env.YARN_PRODUCTION !== 'false');
     }
 
     if (this.workspaceRootFolder && !this.workspacesEnabled) {
diff --git a/src/constants.js b/src/constants.js
index 3784e87..40c82b6 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -11,6 +11,8 @@ type Env = {
 
 export const DEPENDENCY_TYPES = ['devDependencies', 'dependencies', 'optionalDependencies', 'peerDependencies'];
 
+export const SUPPORTED_NODE_VERSIONS = '^4.8.0 || ^5.7.0 || ^6.2.2 || ^8.0.0';
+
 export const YARN_REGISTRY = 'https://registry.yarnpkg.com';
 
 export const YARN_DOCS = 'https://yarnpkg.com/en/docs/cli/';
diff --git a/src/lockfile/parse.js b/src/lockfile/parse.js
index 2808549..4db0830 100644
--- a/src/lockfile/parse.js
+++ b/src/lockfile/parse.js
@@ -1,13 +1,14 @@
 /* @flow */
 /* eslint quotes: 0 */
 
+import util from 'util';
+import invariant from 'invariant';
+import stripBOM from 'strip-bom';
+
 import {LOCKFILE_VERSION} from '../constants.js';
 import {MessageError} from '../errors.js';
 import map from '../util/map.js';
 
-const invariant = require('invariant');
-const stripBOM = require('strip-bom');
-
 type Token = {
   line: number,
   col: number,
@@ -56,8 +57,12 @@ function* tokenise(input: string): Iterator<Token> {
   while (input.length) {
     let chop = 0;
 
-    if (input[0] === '\n') {
+    if (input[0] === '\n' || input[0] === '\r') {
       chop++;
+      // If this is a \r\n line, ignore both chars but only add one new line
+      if (input[1] === '\n') {
+        chop++;
+      }
       line++;
       col = 0;
       yield buildToken(TOKEN_TYPES.newline);
@@ -136,7 +141,7 @@ function* tokenise(input: string): Iterator<Token> {
       let name = '';
       for (let i = 0; i < input.length; i++) {
         const char = input[i];
-        if (char === ':' || char === ' ' || char === '\n' || char === ',') {
+        if (char === ':' || char === ' ' || char === '\n' || char === '\r' || char === ',') {
           break;
         } else {
           name += char;
@@ -155,7 +160,7 @@ function* tokenise(input: string): Iterator<Token> {
     }
 
     col += chop;
-    lastNewline = input[0] === '\n';
+    lastNewline = input[0] === '\n' || (input[0] === '\r' && input[1] === '\n');
     input = input.slice(chop);
   }
 
@@ -313,7 +318,7 @@ class Parser {
           this.unexpected('Invalid value type');
         }
       } else {
-        this.unexpected('Unknown token');
+        this.unexpected(`Unknown token: ${util.inspect(propToken)}`);
       }
     }
 
@@ -331,7 +336,7 @@ const MERGE_CONFLICT_START = '<<<<<<<';
  */
 function extractConflictVariants(str: string): [string, string] {
   const variants = [[], []];
-  const lines = str.split(/\n/g);
+  const lines = str.split(/\r?\n/g);
   let skip = false;
 
   while (lines.length) {
diff --git a/src/package-hoister.js b/src/package-hoister.js
index b46aa58..3ac70dc 100644
--- a/src/package-hoister.js
+++ b/src/package-hoister.js
@@ -242,7 +242,7 @@ export default class PackageHoister {
           continue;
         }
 
-        const isMarkedAsOptional = !depinfo.pkg._reference || this.ignoreOptional;
+        const isMarkedAsOptional = depinfo.pkg._reference && depinfo.pkg._reference.optional && this.ignoreOptional;
         if (!depinfo.isRequired && !depinfo.isIncompatible && !isMarkedAsOptional) {
           depinfo.isRequired = true;
           depinfo.addHistory(`Mark as non-ignored because of usage by ${info.key}`);
diff --git a/src/package-linker.js b/src/package-linker.js
index 0c20f03..8f9a75f 100644
--- a/src/package-linker.js
+++ b/src/package-linker.js
@@ -244,31 +244,39 @@ export default class PackageLinker {
       }
     }
 
-    // keep track of all scoped paths to remove empty scopes after copy
-    const scopedPaths = new Set();
-
-    // register root & scoped packages as being possibly extraneous
     const possibleExtraneous: Set<string> = new Set();
-    for (const folder of this.config.registryFolders) {
-      const loc = path.join(this.config.cwd, folder);
-
-      if (await fs.exists(loc)) {
-        const files = await fs.readdir(loc);
-        let filepath;
-        for (const file of files) {
-          filepath = path.join(loc, file);
-          if (file[0] === '@') {
+    const scopedPaths: Set<string> = new Set();
+
+    const findExtraneousFiles = async basePath => {
+      for (const folder of this.config.registryFolders) {
+        const loc = path.join(basePath, folder);
+
+        if (await fs.exists(loc)) {
+          const files = await fs.readdir(loc);
+
+          for (const file of files) {
+            const filepath = path.join(loc, file);
+
             // it's a scope, not a package
-            scopedPaths.add(filepath);
-            const subfiles = await fs.readdir(filepath);
-            for (const subfile of subfiles) {
-              possibleExtraneous.add(path.join(filepath, subfile));
+            if (file[0] === '@') {
+              scopedPaths.add(filepath);
+
+              for (const subfile of await fs.readdir(filepath)) {
+                possibleExtraneous.add(path.join(filepath, subfile));
+              }
+            } else {
+              possibleExtraneous.add(filepath);
             }
-          } else {
-            possibleExtraneous.add(filepath);
           }
         }
       }
+    };
+
+    await findExtraneousFiles(this.config.lockfileFolder);
+    if (workspaceLayout) {
+      for (const workspaceName of Object.keys(workspaceLayout.workspaces)) {
+        await findExtraneousFiles(workspaceLayout.workspaces[workspaceName].loc);
+      }
     }
 
     // If an Extraneous is an entry created via "yarn link", we prevent it from being overwritten.
@@ -432,34 +440,64 @@ export default class PackageLinker {
 
   resolvePeerModules() {
     for (const pkg of this.resolver.getManifests()) {
-      this._resolvePeerModules(pkg);
-    }
-  }
+      const peerDeps = pkg.peerDependencies;
+      if (!peerDeps) {
+        continue;
+      }
+      const ref = pkg._reference;
+      invariant(ref, 'Package reference is missing');
+      // TODO: We are taking the "shortest" ref tree but there may be multiple ref trees with the same length
+      const refTree = ref.requests.map(req => req.parentNames).sort((arr1, arr2) => arr1.length - arr2.length)[0];
 
-  _resolvePeerModules(pkg: Manifest) {
-    const peerDeps = pkg.peerDependencies;
-    if (!peerDeps) {
-      return;
-    }
+      const getLevelDistance = pkgRef => {
+        let minDistance = Infinity;
+        for (const req of pkgRef.requests) {
+          const distance = refTree.length - req.parentNames.length;
 
-    const ref = pkg._reference;
-    invariant(ref, 'Package reference is missing');
+          if (distance >= 0 && distance < minDistance && req.parentNames.every((name, idx) => name === refTree[idx])) {
+            minDistance = distance;
+          }
+        }
 
-    for (const name in peerDeps) {
-      const range = peerDeps[name];
-      const pkgs = this.resolver.getAllInfoForPackageName(name);
-      const found = pkgs.find(pkg => {
-        const {root, version} = pkg._reference || {};
-        return root && this._satisfiesPeerDependency(range, version);
-      });
-      const foundPattern = found && found._reference && found._reference.patterns;
-
-      if (foundPattern) {
-        ref.addDependencies(foundPattern);
-      } else {
-        const depError = pkgs.length > 0 ? 'incorrectPeer' : 'unmetPeer';
-        const [pkgHuman, depHuman] = [`${pkg.name}@${pkg.version}`, `${name}@${range}`];
-        this.reporter.warn(this.reporter.lang(depError, pkgHuman, depHuman));
+        return minDistance;
+      };
+
+      for (const peerDepName in peerDeps) {
+        const range = peerDeps[peerDepName];
+        const peerPkgs = this.resolver.getAllInfoForPackageName(peerDepName);
+
+        let peerError = 'unmetPeer';
+        let resolvedLevelDistance = Infinity;
+        let resolvedPeerPkgPattern;
+        for (const peerPkg of peerPkgs) {
+          const peerPkgRef = peerPkg._reference;
+          if (!(peerPkgRef && peerPkgRef.patterns)) {
+            continue;
+          }
+          const levelDistance = getLevelDistance(peerPkgRef);
+          if (isFinite(levelDistance) && levelDistance < resolvedLevelDistance) {
+            if (this._satisfiesPeerDependency(range, peerPkgRef.version)) {
+              resolvedLevelDistance = levelDistance;
+              resolvedPeerPkgPattern = peerPkgRef.patterns;
+              this.reporter.verbose(
+                this.reporter.lang(
+                  'selectedPeer',
+                  `${pkg.name}@${pkg.version}`,
+                  `${peerDepName}@${peerPkgRef.version}`,
+                  peerPkgRef.level,
+                ),
+              );
+            } else {
+              peerError = 'incorrectPeer';
+            }
+          }
+        }
+
+        if (resolvedPeerPkgPattern) {
+          ref.addDependencies(resolvedPeerPkgPattern);
+        } else {
+          this.reporter.warn(this.reporter.lang(peerError, `${pkg.name}@${pkg.version}`, `${peerDepName}@${range}`));
+        }
       }
     }
   }
diff --git a/src/package-reference.js b/src/package-reference.js
index 20037d4..06995d9 100644
--- a/src/package-reference.js
+++ b/src/package-reference.js
@@ -27,7 +27,7 @@ export default class PackageReference {
     this.permissions = {};
     this.patterns = [];
     this.optional = null;
-    this.root = false;
+    this.level = Infinity;
     this.ignore = false;
     this.incompatible = false;
     this.fresh = false;
@@ -39,7 +39,7 @@ export default class PackageReference {
   lockfile: Lockfile;
   config: Config;
 
-  root: boolean;
+  level: number;
   name: string;
   version: string;
   uid: string;
@@ -66,9 +66,7 @@ export default class PackageReference {
   addRequest(request: PackageRequest) {
     this.requests.push(request);
 
-    if (!request.parentRequest) {
-      this.root = true;
-    }
+    this.level = Math.min(this.level, request.parentNames.length);
   }
 
   prune() {
diff --git a/src/package-request.js b/src/package-request.js
index 472e957..5a36c19 100644
--- a/src/package-request.js
+++ b/src/package-request.js
@@ -25,10 +25,12 @@ import {normalizePattern} from './util/normalize-pattern.js';
 
 type ResolverRegistryNames = $Keys<typeof registryResolvers>;
 
+const micromatch = require('micromatch');
+
 export default class PackageRequest {
   constructor(req: DependencyRequestPattern, resolver: PackageResolver) {
     this.parentRequest = req.parentRequest;
-    this.parentNames = [];
+    this.parentNames = req.parentNames || [];
     this.lockfile = resolver.lockfile;
     this.registry = req.registry;
     this.reporter = resolver.reporter;
@@ -193,6 +195,7 @@ export default class PackageRequest {
     invariant(ref, 'Resolved package info has no package reference');
     ref.addRequest(this);
     ref.addPattern(this.pattern, resolved);
+    ref.addOptional(this.optional);
   }
 
   /**
@@ -338,6 +341,8 @@ export default class PackageRequest {
     config: Config,
     reporter: Reporter,
     filterByPatterns: ?Array<string>,
+    flags: ?Object,
+    returnAllPackages: ?boolean,
   ): Promise<Array<Dependency>> {
     const {requests: reqPatterns, workspaceLayout} = await install.fetchRequestFromCwd();
 
@@ -350,11 +355,15 @@ export default class PackageRequest {
     // prevents us from having to query the metadata for all packages.
     if (filterByPatterns && filterByPatterns.length) {
       const filterByNames = filterByPatterns.map(pattern => normalizePattern(pattern).name);
-      depReqPatterns = depReqPatterns.filter(dep => filterByNames.indexOf(normalizePattern(dep.pattern).name) >= 0);
+      depReqPatterns = depReqPatterns.filter(
+        dep =>
+          filterByNames.indexOf(normalizePattern(dep.pattern).name) >= 0 ||
+          (flags && flags.pattern && micromatch.contains(normalizePattern(dep.pattern).name, flags.pattern)),
+      );
     }
 
     const deps = await Promise.all(
-      depReqPatterns.map(async ({pattern, hint}): Promise<Dependency> => {
+      depReqPatterns.map(async ({pattern, hint, workspaceName, workspaceLoc}): Promise<Dependency> => {
         const locked = lockfile.getLocked(pattern);
         if (!locked) {
           throw new MessageError(reporter.lang('lockfileOutdated'));
@@ -376,7 +385,18 @@ export default class PackageRequest {
           ({latest, wanted, url} = await registry.checkOutdated(config, name, normalized.range));
         }
 
-        return {name, current, wanted, latest, url, hint, range: normalized.range, upgradeTo: ''};
+        return {
+          name,
+          current,
+          wanted,
+          latest,
+          url,
+          hint,
+          range: normalized.range,
+          upgradeTo: '',
+          workspaceName: workspaceName || '',
+          workspaceLoc: workspaceLoc || '',
+        };
       }),
     );
 
@@ -385,6 +405,6 @@ export default class PackageRequest {
       latest === 'exotic' || (latest !== 'exotic' && (semver.lt(current, wanted) || semver.lt(current, latest)));
     const orderByName = (depA, depB) => depA.name.localeCompare(depB.name);
 
-    return deps.filter(isDepOld).sort(orderByName);
+    return returnAllPackages ? deps.sort(orderByName) : deps.filter(isDepOld).sort(orderByName);
   }
 }
diff --git a/src/package-resolver.js b/src/package-resolver.js
index 8a2bd16..8a76987 100644
--- a/src/package-resolver.js
+++ b/src/package-resolver.js
@@ -27,7 +27,7 @@ export type ResolverOptions = {|
 export default class PackageResolver {
   constructor(config: Config, lockfile: Lockfile, resolutionMap: ResolutionMap = new ResolutionMap(config)) {
     this.patternsByPackage = map();
-    this.fetchingPatterns = map();
+    this.fetchingPatterns = new Set();
     this.fetchingQueue = new BlockingQueue('resolver fetching');
     this.patterns = map();
     this.resolutionMap = resolutionMap;
@@ -59,9 +59,7 @@ export default class PackageResolver {
   };
 
   // patterns we've already resolved or are in the process of resolving
-  fetchingPatterns: {
-    [key: string]: true,
-  };
+  fetchingPatterns: Set<string>;
 
   // TODO
   fetchingQueue: BlockingQueue;
@@ -230,6 +228,14 @@ export default class PackageResolver {
 
   getAllInfoForPackageName(name: string): Array<Manifest> {
     const patterns = this.patternsByPackage[name] || [];
+    return this.getAllInfoForPatterns(patterns);
+  }
+
+  /**
+   * Retrieve all the package info stored for a list of patterns.
+   */
+
+  getAllInfoForPatterns(patterns: string[]): Array<Manifest> {
     const infos = [];
     const seen = new Set();
 
@@ -286,6 +292,13 @@ export default class PackageResolver {
 
   collapseAllVersionsOfPackage(name: string, version: string): string {
     const patterns = this.dedupePatterns(this.patternsByPackage[name]);
+    return this.collapsePackageVersions(name, version, patterns);
+  }
+
+  /**
+   * Make all given patterns resolve to version.
+   */
+  collapsePackageVersions(name: string, version: string, patterns: string[]): string {
     const human = `${name}@${version}`;
 
     // get manifest that matches the version we're collapsing too
@@ -477,13 +490,11 @@ export default class PackageResolver {
       return;
     }
 
-    const fetchKey = `${req.registry}:${req.pattern}`;
-
-    if (this.fetchingPatterns[fetchKey]) {
+    const fetchKey = `${req.registry}:${req.pattern}:${String(req.optional)}`;
+    if (this.fetchingPatterns.has(fetchKey)) {
       return;
-    } else {
-      this.fetchingPatterns[fetchKey] = true;
     }
+    this.fetchingPatterns.add(fetchKey);
 
     if (this.activity) {
       this.activity.tick(req.pattern);
@@ -534,10 +545,47 @@ export default class PackageResolver {
       this.resolveToResolution(req);
     }
 
+    if (isFlat) {
+      for (const dep of deps) {
+        const name = normalizePattern(dep.pattern).name;
+        this.optimizeResolutions(name);
+      }
+    }
+
     activity.end();
     this.activity = null;
   }
 
+  // for a given package, see if a single manifest can satisfy all ranges
+  optimizeResolutions(name: string) {
+    const patterns: Array<string> = this.dedupePatterns(this.patternsByPackage[name] || []);
+
+    // don't optimize things that already have a lockfile entry:
+    // https://github.com/yarnpkg/yarn/issues/79
+    const collapsablePatterns = patterns.filter(pattern => {
+      const remote = this.patterns[pattern]._remote;
+      return !this.lockfile.getLocked(pattern) && (!remote || remote.type !== 'workspace');
+    });
+    if (collapsablePatterns.length < 2) {
+      return;
+    }
+
+    // reverse sort, so we'll find the maximum satisfying version first
+    const availableVersions = this.getAllInfoForPatterns(collapsablePatterns).map(manifest => manifest.version);
+    availableVersions.sort(semver.rcompare);
+
+    const ranges = collapsablePatterns.map(pattern => normalizePattern(pattern).range);
+
+    // find the most recent version that satisfies all patterns (if one exists), and
+    // collapse to that version.
+    for (const version of availableVersions) {
+      if (ranges.every(range => semver.satisfies(version, range))) {
+        this.collapsePackageVersions(name, version, collapsablePatterns);
+        return;
+      }
+    }
+  }
+
   /**
     * Called by the package requester for packages that this resolver already had
     * a matching version for. Delay the resolve, because better matches can still be
@@ -579,7 +627,6 @@ export default class PackageResolver {
       } else {
         this.resolutionMap.addToDelayQueue(req);
       }
-
       return null;
     }
 
diff --git a/src/rc.js b/src/rc.js
index 6f4ac65..34634c4 100644
--- a/src/rc.js
+++ b/src/rc.js
@@ -1,11 +1,14 @@
 /* @flow */
 
 import {dirname, resolve} from 'path';
+
+import commander from 'commander';
+
 import {parse} from './lockfile';
 import * as rcUtil from './util/rc.js';
 
 // Keys that will get resolved relative to the path of the rc file they belong to
-const PATH_KEYS = ['yarn-path', 'cache-folder', 'global-folder', 'modules-folder', 'cwd'];
+const PATH_KEYS = new Set(['yarn-path', 'cache-folder', 'global-folder', 'modules-folder', 'cwd']);
 
 // given a cwd, load all .yarnrc files relative to it
 export function getRcConfigForCwd(cwd: string): {[key: string]: string} {
@@ -14,10 +17,8 @@ export function getRcConfigForCwd(cwd: string): {[key: string]: string} {
 
     // some keys reference directories so keep their relativity
     for (const key in values) {
-      for (const pathKey of PATH_KEYS) {
-        if (key.replace(/^(--)?([^.]+\.)*/, '') === pathKey) {
-          values[key] = resolve(dirname(filePath), values[key]);
-        }
+      if (PATH_KEYS.has(key.replace(/^(--)?([^.]+\.)*/, ''))) {
+        values[key] = resolve(dirname(filePath), values[key]);
       }
     }
 
@@ -48,12 +49,14 @@ function buildRcArgs(cwd: string): Map<string, Array<string>> {
     argsForCommands.set(commandName, args);
 
     // turn config value into appropriate cli flag
-    if (typeof value === 'string') {
+    const option = commander.optionFor(`--${arg}`);
+
+    // If commander doesn't recognize the option or it takes a value after it
+    if (!option || option.optional || option.required) {
       args.push(`--${arg}`, value);
     } else if (value === true) {
+      // we can't force remove an arg from cli
       args.push(`--${arg}`);
-    } else if (value === false) {
-      args.push(`--no-${arg}`);
     }
   }
 
@@ -82,7 +85,7 @@ export function getRcArgs(commandName: string, args: Array<string>, previousCwds
   const argMap = buildRcArgs(origCwd);
 
   // concat wildcard arguments and arguments meant for this specific command
-  const newArgs = [].concat(argMap.get('*') || [], argMap.get(commandName) || []);
+  const newArgs = [...(argMap.get('*') || []), ...(argMap.get(commandName) || [])];
 
   // check if the .yarnrc args specified a cwd
   const newCwd = extractCwdArg(newArgs);
diff --git a/src/registries/base-registry.js b/src/registries/base-registry.js
index b744a4f..aa42d2d 100644
--- a/src/registries/base-registry.js
+++ b/src/registries/base-registry.js
@@ -99,9 +99,17 @@ export default class BaseRegistry {
     });
   }
 
-  async init(): Promise<void> {
+  async init(overrides: Object = {}): Promise<void> {
     this.mergeEnv('yarn_');
     await this.loadConfig();
+
+    for (const override of Object.keys(overrides)) {
+      const val = overrides[override];
+
+      if (val !== undefined) {
+        this.config[override] = val;
+      }
+    }
     this.loc = path.join(this.cwd, this.folder);
   }
 
diff --git a/src/registries/npm-registry.js b/src/registries/npm-registry.js
index 90879d9..124c777 100644
--- a/src/registries/npm-registry.js
+++ b/src/registries/npm-registry.js
@@ -53,10 +53,10 @@ function getGlobalPrefix(): string {
   }
 }
 
-const PATH_CONFIG_OPTIONS = ['cache', 'cafile', 'prefix', 'userconfig'];
+const PATH_CONFIG_OPTIONS = new Set(['cache', 'cafile', 'prefix', 'userconfig']);
 
 function isPathConfigOption(key: string): boolean {
-  return PATH_CONFIG_OPTIONS.indexOf(key) >= 0;
+  return PATH_CONFIG_OPTIONS.has(key);
 }
 
 function normalizePath(val: mixed): ?string {
@@ -65,7 +65,7 @@ function normalizePath(val: mixed): ?string {
   }
 
   if (typeof val !== 'string') {
-    val = '' + (val: any);
+    val = String(val);
   }
 
   return resolveWithHome(val);
diff --git a/src/reporters/console/console-reporter.js b/src/reporters/console/console-reporter.js
index cd4d00a..bf64bca 100644
--- a/src/reporters/console/console-reporter.js
+++ b/src/reporters/console/console-reporter.js
@@ -22,6 +22,7 @@ import inquirer from 'inquirer';
 const {inspect} = require('util');
 const readline = require('readline');
 const chalk = require('chalk');
+const stripAnsi = require('strip-ansi');
 const read = require('read');
 const tty = require('tty');
 
@@ -30,7 +31,7 @@ type InquirerResponses<K, T> = {[key: K]: Array<T>};
 
 // fixes bold on windows
 if (process.platform === 'win32' && process.env.TERM && !/^xterm/i.test(process.env.TERM)) {
-  chalk.styles.bold.close += '\u001b[m';
+  chalk.bold._styles[0].close += '\u001b[m';
 }
 
 export default class ConsoleReporter extends BaseReporter {
@@ -40,6 +41,7 @@ export default class ConsoleReporter extends BaseReporter {
     this._lastCategorySize = 0;
     this._spinners = new Set();
     this.format = (chalk: any);
+    this.format.stripColor = stripAnsi;
     this.isSilent = !!opts.isSilent;
   }
 
@@ -134,7 +136,7 @@ export default class ConsoleReporter extends BaseReporter {
 
     if (hints) {
       for (const item of items) {
-        this._log(`${' '.repeat(gutterWidth)}- ${item}`);
+        this._log(`${' '.repeat(gutterWidth)}- ${this.format.bold(item)}`);
         this._log(`  ${' '.repeat(gutterWidth)} ${hints[item]}`);
       }
     } else {
diff --git a/src/reporters/console/progress-bar.js b/src/reporters/console/progress-bar.js
index 10ecff8..499fc69 100644
--- a/src/reporters/console/progress-bar.js
+++ b/src/reporters/console/progress-bar.js
@@ -23,7 +23,7 @@ export default class ProgressBar {
   id: ?number;
   _callback: ?(progressBar: ProgressBar) => void;
 
-  static bars = [['█', '░']];
+  static bars = [['#', '-']];
 
   tick() {
     if (this.curr >= this.total) {
@@ -68,12 +68,12 @@ export default class ProgressBar {
 
     // calculate size of actual bar
     // $FlowFixMe: investigate process.stderr.columns flow error
-    const availableSpace = Math.max(0, this.stdout.columns - bar.length - 1);
+    const availableSpace = Math.max(0, this.stdout.columns - bar.length - 3);
     const width = Math.min(this.total, availableSpace);
     const completeLength = Math.round(width * ratio);
     const complete = this.chars[0].repeat(completeLength);
     const incomplete = this.chars[1].repeat(width - completeLength);
-    bar = `${complete}${incomplete}${bar}`;
+    bar = `[${complete}${incomplete}]${bar}`;
 
     toStartOfLine(this.stdout);
     this.stdout.write(bar);
diff --git a/src/reporters/lang/en.js b/src/reporters/lang/en.js
index 85d9a53..ac0233d 100644
--- a/src/reporters/lang/en.js
+++ b/src/reporters/lang/en.js
@@ -112,7 +112,7 @@ const messages = {
   noPermission: 'Cannot create $0 due to insufficient permissions.',
   noGlobalFolder: 'Cannot find a suitable global folder. Tried these: $0',
   allDependenciesUpToDate: 'All of your dependencies are up to date.',
-  legendColorsForUpgradeInteractive:
+  legendColorsForVersionUpdates:
     'Color legend : \n $0    : Major Update backward-incompatible updates \n $1 : Minor Update backward-compatible features \n $2  : Patch Update backward-compatible bug fixes',
   frozenLockfileError: 'Your lockfile needs to be updated, but yarn was run with `--frozen-lockfile`.',
   fileWriteError: 'Could not write file $0: $1',
@@ -177,8 +177,8 @@ const messages = {
   createMissingPackage:
     'Package not found - this is probably an internal error, and should be reported at https://github.com/yarnpkg/yarn/issues.',
 
-  workspacesPreferDevDependencies:
-    "You're trying to add a regular dependency to a workspace root, which is probably a mistake (do you want to run this command inside a workspace?). If this dependency really should be in your workspace root, use the --dev flag to add it to your devDependencies.",
+  workspacesAddRootCheck:
+    'Running this command will add the dependency to the workspace root rather than workspace itself, which might not be what you want - if you really meant it, make it explicit by running this command again with the -W flag (or --ignore-workspace-root-check).',
   workspacesRequirePrivateProjects: 'Workspaces can only be enabled in private projects',
   workspacesDisabled:
     'Your project root defines workspaces but the feature is disabled in your Yarn config. Please check "workspaces-experimental" in your .yarnrc file.',
@@ -223,6 +223,7 @@ const messages = {
 
   unmetPeer: '$0 has unmet peer dependency $1.',
   incorrectPeer: '$0 has incorrect peer dependency $1.',
+  selectedPeer: 'Selecting $1 at level $2 as the peer dependency of $0.',
   missingBundledDependency: '$0 is missing a bundled dependency $1. This should be reported to the package maintainer.',
 
   savedNewDependency: 'Saved 1 new dependency.',
@@ -358,8 +359,16 @@ const messages = {
   scopeNotValid: 'The specified scope is not valid.',
 
   deprecatedCommand: '$0 is deprecated. Please use $1.',
+  deprecatedListArgs: 'Filtering by arguments is deprecated. Please use the pattern option instead.',
   implicitFileDeprecated:
     'Using the "file:" protocol implicitly is deprecated. Please either the protocol or prepend the path $0 with "./".',
+  unsupportedNodeVersion:
+    'You are using Node $0 which is not supported and may encounter bugs or unexpected behavior. Yarn supports the following semver range: $1',
+
+  verboseUpgradeBecauseRequested: 'Considering upgrade of $0 to $1 because it was directly requested.',
+  verboseUpgradeBecauseOutdated: 'Considering upgrade of $0 to $1 because a newer version exists in the registry.',
+  verboseUpgradeNotUnlocking: 'Not unlocking $0 in the lockfile because it is a new or direct dependency.',
+  verboseUpgradeUnlocking: 'Unlocking $0 in the lockfile.',
 };
 
 export type LanguageKeys = $Keys<typeof messages>;
diff --git a/src/resolvers/exotics/bitbucket-resolver.js b/src/resolvers/exotics/bitbucket-resolver.js
index edc97c5..9c32bb7 100644
--- a/src/resolvers/exotics/bitbucket-resolver.js
+++ b/src/resolvers/exotics/bitbucket-resolver.js
@@ -29,4 +29,18 @@ export default class BitbucketResolver extends HostedGitResolver {
   static getHTTPFileUrl(parts: ExplodedFragment, filename: string, commit: string): string {
     return `https://${this.hostname}/${parts.user}/${parts.repo}/raw/${commit}/${filename}`;
   }
+
+  async hasHTTPCapability(url: string): Promise<boolean> {
+    // We don't follow redirects and reject a 302 since this means BitBucket
+    // won't allow us to use the HTTP protocol for `git` access.
+    // Most probably a private repo and this 302 is to a login page.
+    const bitbucketHTTPSupport = await this.config.requestManager.request({
+      url,
+      method: 'HEAD',
+      queue: this.resolver.fetchingQueue,
+      followRedirect: false,
+      rejectStatusCode: 302,
+    });
+    return bitbucketHTTPSupport !== false;
+  }
 }
diff --git a/src/types.js b/src/types.js
index b259d9a..63b3a45 100644
--- a/src/types.js
+++ b/src/types.js
@@ -21,6 +21,8 @@ export type DependencyRequestPattern = {
   hint?: ?string,
   parentNames?: Array<string>,
   parentRequest?: ?PackageRequest,
+  workspaceName?: string,
+  workspaceLoc?: string,
 };
 export type DependencyRequestPatterns = Array<DependencyRequestPattern>;
 
@@ -156,6 +158,8 @@ export type Dependency = {
   hint: ?string,
   range: string,
   upgradeTo: string,
+  workspaceName: string,
+  workspaceLoc: string,
 };
 
 export type WorkspacesManifestMap = {
diff --git a/src/util/blocking-queue.js b/src/util/blocking-queue.js
index f640dd4..d10cd92 100644
--- a/src/util/blocking-queue.js
+++ b/src/util/blocking-queue.js
@@ -45,6 +45,10 @@ export default class BlockingQueue {
     }
 
     this.stuckTimer = setTimeout(this.stuckTick, 5000);
+
+    // We need to check the existense of unref because of https://github.com/facebook/jest/issues/4559
+    // $FlowFixMe: Node's setInterval returns a Timeout, not a Number
+    this.stuckTimer.unref && this.stuckTimer.unref();
   }
 
   stuckTick() {
@@ -79,6 +83,7 @@ export default class BlockingQueue {
     if (this.running[key]) {
       delete this.running[key];
       this.runningCount--;
+      clearTimeout(this.stuckTimer);
 
       if (this.warnedStuck) {
         this.warnedStuck = false;
diff --git a/src/util/execute-lifecycle-script.js b/src/util/execute-lifecycle-script.js
index 9253457..2e44d6e 100644
--- a/src/util/execute-lifecycle-script.js
+++ b/src/util/execute-lifecycle-script.js
@@ -61,8 +61,8 @@ export async function makeEnv(
   // parser used by npm. Since we use other parser, we just roughly emulate it's output. (See: #684)
   env.npm_config_argv = JSON.stringify({
     remain: [],
-    cooked: [config.commandName],
-    original: [config.commandName],
+    cooked: config.commandName === 'run' ? [config.commandName, stage] : [config.commandName],
+    original: process.argv.slice(2),
   });
 
   const manifest = await config.maybeReadManifest(cwd);
diff --git a/src/util/fs.js b/src/util/fs.js
index 0a280a7..714da3f 100644
--- a/src/util/fs.js
+++ b/src/util/fs.js
@@ -40,8 +40,37 @@ export const chmod: (path: string, mode: number | string) => Promise<void> = pro
 export const link: (src: string, dst: string) => Promise<fs.Stats> = promisify(fs.link);
 export const glob: (path: string, options?: Object) => Promise<Array<string>> = promisify(globModule);
 
-const CONCURRENT_QUEUE_ITEMS = 4;
-
+// fs.copyFile uses the native file copying instructions on the system, performing much better
+// than any JS-based solution and consumes fewer resources. Repeated testing to fine tune the
+// concurrency level revealed 128 as the sweet spot on a quad-core, 16 CPU Intel system with SSD.
+const CONCURRENT_QUEUE_ITEMS = fs.copyFile ? 128 : 4;
+
+const open: (path: string, flags: string | number, mode: number) => Promise<number> = promisify(fs.open);
+const close: (fd: number) => Promise<void> = promisify(fs.close);
+const write: (
+  fd: number,
+  buffer: Buffer,
+  offset: ?number,
+  length: ?number,
+  position: ?number,
+) => Promise<void> = promisify(fs.write);
+const futimes: (fd: number, atime: number, mtime: number) => Promise<void> = promisify(fs.futimes);
+const copyFile: (src: string, dest: string, flags: number, data: CopyFileAction) => Promise<void> = fs.copyFile
+  ? // Don't use `promisify` to avoid passing  the last, argument `data`, to the native method
+    (src, dest, flags, data) =>
+      new Promise((resolve, reject) => fs.copyFile(src, dest, flags, err => (err ? reject(err) : resolve(err))))
+  : async (src, dest, flags, data) => {
+      // Use open -> write -> futimes -> close sequence to avoid opening the file twice:
+      // one with writeFile and one with utimes
+      const fd = await open(dest, 'w', data.mode);
+      try {
+        const buffer = await readFileBuffer(src);
+        await write(fd, buffer, 0, buffer.length);
+        await futimes(fd, data.atime, data.mtime);
+      } finally {
+        await close(fd);
+      }
+    };
 const fsSymlink: (target: string, path: string, type?: 'dir' | 'file' | 'junction') => Promise<void> = promisify(
   fs.symlink,
 );
@@ -61,7 +90,6 @@ export type CopyQueueItem = {
 type CopyQueue = Array<CopyQueueItem>;
 
 type CopyFileAction = {
-  type: 'file',
   src: string,
   dest: string,
   atime: number,
@@ -70,19 +98,21 @@ type CopyFileAction = {
 };
 
 type LinkFileAction = {
-  type: 'link',
   src: string,
   dest: string,
   removeDest: boolean,
 };
 
 type CopySymlinkAction = {
-  type: 'symlink',
   dest: string,
   linkname: string,
 };
 
-type CopyActions = Array<CopyFileAction | CopySymlinkAction | LinkFileAction>;
+type CopyActions = {
+  file: Array<CopyFileAction>,
+  symlink: Array<CopySymlinkAction>,
+  link: Array<LinkFileAction>,
+};
 
 type CopyOptions = {
   onProgress: (dest: string) => void,
@@ -154,7 +184,11 @@ async function buildActionsForCopy(
   events.onStart(queue.length);
 
   // start building actions
-  const actions: CopyActions = [];
+  const actions: CopyActions = {
+    file: [],
+    symlink: [],
+    link: [],
+  };
 
   // custom concurrency logic as we're always executing stacks of CONCURRENT_QUEUE_ITEMS queue items
   // at a time due to the requirement to push items onto the queue
@@ -180,7 +214,7 @@ async function buildActionsForCopy(
   return actions;
 
   //
-  async function build(data): Promise<void> {
+  async function build(data: CopyQueueItem): Promise<void> {
     const {src, dest, type} = data;
     const onFresh = data.onFresh || noop;
     const onDone = data.onDone || noop;
@@ -188,7 +222,7 @@ async function buildActionsForCopy(
     // TODO https://github.com/yarnpkg/yarn/issues/3751
     // related to bundled dependencies handling
     if (files.has(dest.toLowerCase())) {
-      reporter.warn(`The case-insensitive file ${dest} shouldn't be copied twice in one bulk copy`);
+      reporter.verbose(`The case-insensitive file ${dest} shouldn't be copied twice in one bulk copy`);
     } else {
       files.add(dest.toLowerCase());
     }
@@ -196,8 +230,7 @@ async function buildActionsForCopy(
     if (type === 'symlink') {
       await mkdirp(path.dirname(dest));
       onFresh();
-      actions.push({
-        type: 'symlink',
+      actions.symlink.push({
         dest,
         linkname: src,
       });
@@ -288,10 +321,9 @@ async function buildActionsForCopy(
     if (srcStat.isSymbolicLink()) {
       onFresh();
       const linkname = await readlink(src);
-      actions.push({
+      actions.symlink.push({
         dest,
         linkname,
-        type: 'symlink',
       });
       onDone();
     } else if (srcStat.isDirectory()) {
@@ -326,8 +358,7 @@ async function buildActionsForCopy(
       }
     } else if (srcStat.isFile()) {
       onFresh();
-      actions.push({
-        type: 'file',
+      actions.file.push({
         src,
         dest,
         atime: srcStat.atime,
@@ -352,18 +383,20 @@ async function buildActionsForHardlink(
 
   // initialise events
   for (const item of queue) {
-    const onDone = item.onDone;
+    const onDone = item.onDone || noop;
     item.onDone = () => {
       events.onProgress(item.dest);
-      if (onDone) {
-        onDone();
-      }
+      onDone();
     };
   }
   events.onStart(queue.length);
 
   // start building actions
-  const actions: CopyActions = [];
+  const actions: CopyActions = {
+    file: [],
+    symlink: [],
+    link: [],
+  };
 
   // custom concurrency logic as we're always executing stacks of CONCURRENT_QUEUE_ITEMS queue items
   // at a time due to the requirement to push items onto the queue
@@ -389,7 +422,7 @@ async function buildActionsForHardlink(
   return actions;
 
   //
-  async function build(data): Promise<void> {
+  async function build(data: CopyQueueItem): Promise<void> {
     const {src, dest} = data;
     const onFresh = data.onFresh || noop;
     const onDone = data.onDone || noop;
@@ -474,8 +507,7 @@ async function buildActionsForHardlink(
     if (srcStat.isSymbolicLink()) {
       onFresh();
       const linkname = await readlink(src);
-      actions.push({
-        type: 'symlink',
+      actions.symlink.push({
         dest,
         linkname,
       });
@@ -510,8 +542,7 @@ async function buildActionsForHardlink(
       }
     } else if (srcStat.isFile()) {
       onFresh();
-      actions.push({
-        type: 'link',
+      actions.link.push({
         src,
         dest,
         removeDest: destExists,
@@ -527,6 +558,23 @@ export function copy(src: string, dest: string, reporter: Reporter): Promise<voi
   return copyBulk([{src, dest}], reporter);
 }
 
+/**
+ * Unlinks the destination to force a recreation. This is needed on case-insensitive file systems
+ * to force the correct naming when the filename has changed only in charater-casing. (Jest -> jest).
+ * It also calls a cleanup function once it is done.
+ *
+ * `data` contains target file attributes like mode, atime and mtime. Built-in copyFile copies these
+ * automatically but our polyfill needs the do this manually, thus needs the info.
+ */
+const safeCopyFile = async function(data: CopyFileAction, cleanup: () => mixed): Promise<void> {
+  try {
+    await unlink(data.dest);
+    await copyFile(data.src, data.dest, 0, data);
+  } finally {
+    cleanup();
+  }
+};
+
 export async function copyBulk(
   queue: CopyQueue,
   reporter: Reporter,
@@ -547,57 +595,31 @@ export async function copyBulk(
   };
 
   const actions: CopyActions = await buildActionsForCopy(queue, events, events.possibleExtraneous, reporter);
-  events.onStart(actions.length);
+  events.onStart(actions.file.length + actions.symlink.length + actions.link.length);
 
-  const fileActions: Array<CopyFileAction> = (actions.filter(action => action.type === 'file'): any);
+  const fileActions: Array<CopyFileAction> = actions.file;
 
-  const currentlyWriting: {[dest: string]: Promise<void>} = {};
+  const currentlyWriting: Map<string, Promise<void>> = new Map();
 
   await promise.queue(
     fileActions,
-    async (data): Promise<void> => {
-      let writePromise: Promise<void>;
-      while ((writePromise = currentlyWriting[data.dest])) {
+    async (data: CopyFileAction): Promise<void> => {
+      let writePromise;
+      while ((writePromise = currentlyWriting.get(data.dest))) {
         await writePromise;
       }
 
-      const cleanup = () => delete currentlyWriting[data.dest];
       reporter.verbose(reporter.lang('verboseFileCopy', data.src, data.dest));
-      return (currentlyWriting[data.dest] = readFileBuffer(data.src)
-        .then(async d => {
-          // we need to do this because of case-insensitive filesystems, which wouldn't properly
-          // change the file name in case of a file being renamed
-          await unlink(data.dest);
-
-          return writeFile(data.dest, d, {mode: data.mode});
-        })
-        .then(() => {
-          return new Promise((resolve, reject) => {
-            fs.utimes(data.dest, data.atime, data.mtime, err => {
-              if (err) {
-                reject(err);
-              } else {
-                resolve();
-              }
-            });
-          });
-        })
-        .then(
-          () => {
-            events.onProgress(data.dest);
-            cleanup();
-          },
-          err => {
-            cleanup();
-            throw err;
-          },
-        ));
+      const copier = safeCopyFile(data, () => currentlyWriting.delete(data.dest));
+      currentlyWriting.set(data.dest, copier);
+      events.onProgress(data.dest);
+      return copier;
     },
     CONCURRENT_QUEUE_ITEMS,
   );
 
   // we need to copy symlinks last as they could reference files we were copying
-  const symlinkActions: Array<CopySymlinkAction> = (actions.filter(action => action.type === 'symlink'): any);
+  const symlinkActions: Array<CopySymlinkAction> = actions.symlink;
   await promise.queue(symlinkActions, (data): Promise<void> => {
     const linkname = path.resolve(path.dirname(data.dest), data.linkname);
     reporter.verbose(reporter.lang('verboseFileSymlink', data.dest, linkname));
@@ -624,9 +646,9 @@ export async function hardlinkBulk(
   };
 
   const actions: CopyActions = await buildActionsForHardlink(queue, events, events.possibleExtraneous, reporter);
-  events.onStart(actions.length);
+  events.onStart(actions.file.length + actions.symlink.length + actions.link.length);
 
-  const fileActions: Array<LinkFileAction> = (actions.filter(action => action.type === 'link'): any);
+  const fileActions: Array<LinkFileAction> = actions.link;
 
   await promise.queue(
     fileActions,
@@ -641,7 +663,7 @@ export async function hardlinkBulk(
   );
 
   // we need to copy symlinks last as they could reference files we were copying
-  const symlinkActions: Array<CopySymlinkAction> = (actions.filter(action => action.type === 'symlink'): any);
+  const symlinkActions: Array<CopySymlinkAction> = actions.symlink;
   await promise.queue(symlinkActions, (data): Promise<void> => {
     const linkname = path.resolve(path.dirname(data.dest), data.linkname);
     reporter.verbose(reporter.lang('verboseFileSymlink', data.dest, linkname));
@@ -836,7 +858,7 @@ export async function writeFilePreservingEol(path: string, data: string): Promis
   if (eol !== '\n') {
     data = data.replace(/\n/g, eol);
   }
-  await promisify(fs.writeFile)(path, data);
+  await writeFile(path, data);
 }
 
 export async function hardlinksWork(dir: string): Promise<boolean> {
diff --git a/src/util/git.js b/src/util/git.js
index 70a5819..b9ccc68 100644
--- a/src/util/git.js
+++ b/src/util/git.js
@@ -1,5 +1,12 @@
 /* @flow */
 
+import invariant from 'invariant';
+import {StringDecoder} from 'string_decoder';
+import tarFs from 'tar-fs';
+import tarStream from 'tar-stream';
+import url from 'url';
+import {createWriteStream} from 'fs';
+
 import type Config from '../config.js';
 import type {Reporter} from '../reporters/index.js';
 import type {ResolvedSha, GitRefResolvingInterface, GitRefs} from './git/git-ref-resolver.js';
@@ -10,12 +17,9 @@ import * as crypto from './crypto.js';
 import * as fs from './fs.js';
 import map from './map.js';
 
-const invariant = require('invariant');
-const StringDecoder = require('string_decoder').StringDecoder;
-const tarFs = require('tar-fs');
-const tarStream = require('tar-stream');
-const url = require('url');
-import {createWriteStream} from 'fs';
+const GIT_PROTOCOL_PREFIX = 'git+';
+const SSH_PROTOCOL = 'ssh:';
+const SCP_PATH_PREFIX = '/:';
 
 type GitUrl = {
   protocol: string, // parsed from URL
@@ -33,6 +37,27 @@ const handleSpawnError = err => {
   }
 };
 
+const SHORTHAND_SERVICES: {[key: string]: url.parse} = map({
+  'github:': parsedUrl => ({
+    ...parsedUrl,
+    slashes: true,
+    auth: 'git',
+    protocol: SSH_PROTOCOL,
+    host: 'github.com',
+    hostname: 'github.com',
+    pathname: `/${parsedUrl.hostname}${parsedUrl.pathname}`,
+  }),
+  'bitbucket:': parsedUrl => ({
+    ...parsedUrl,
+    slashes: true,
+    auth: 'git',
+    protocol: SSH_PROTOCOL,
+    host: 'bitbucket.com',
+    hostname: 'bitbucket.com',
+    pathname: `/${parsedUrl.hostname}${parsedUrl.pathname}`,
+  }),
+});
+
 export default class Git implements GitRefResolvingInterface {
   constructor(config: Config, gitUrl: GitUrl, hash: string) {
     this.supportsArchive = false;
@@ -60,27 +85,39 @@ export default class Git implements GitRefResolvingInterface {
    */
   static npmUrlToGitUrl(npmUrl: string): GitUrl {
     // Expand shortened format first if needed
-    npmUrl = npmUrl.replace(/^github:/, 'git+ssh://git@github.com/');
+    let parsed = url.parse(npmUrl);
+    const expander = parsed.protocol && SHORTHAND_SERVICES[parsed.protocol];
+    if (expander) {
+      parsed = expander(parsed);
+    }
+
+    if (parsed.protocol && parsed.protocol.startsWith(GIT_PROTOCOL_PREFIX)) {
+      parsed.protocol = parsed.protocol.slice(GIT_PROTOCOL_PREFIX.length);
+    }
 
     // Special case in npm, where ssh:// prefix is stripped to pass scp-like syntax
     // which in git works as remote path only if there are no slashes before ':'.
-    const match = npmUrl.match(/^git\+ssh:\/\/((?:[^@:\/]+@)?([^@:\/]+):([^/]*).*)/);
-    // Additionally, if the host part is digits-only, npm falls back to
-    // interpreting it as an SSH URL with a port number.
-    if (match && /[^0-9]/.test(match[3])) {
+    // See #3146.
+    if (
+      parsed.protocol === SSH_PROTOCOL &&
+      parsed.hostname &&
+      parsed.path &&
+      parsed.path.startsWith(SCP_PATH_PREFIX) &&
+      parsed.port === null
+    ) {
+      const auth = parsed.auth ? parsed.auth + '@' : '';
+      const pathname = parsed.path.slice(SCP_PATH_PREFIX.length);
       return {
-        hostname: match[2],
-        protocol: 'ssh:',
-        repository: match[1],
+        hostname: parsed.hostname,
+        protocol: parsed.protocol,
+        repository: `${auth}${parsed.hostname}:${pathname}`,
       };
     }
 
-    const repository = npmUrl.replace(/^git\+/, '');
-    const parsed = url.parse(repository);
     return {
       hostname: parsed.hostname || null,
       protocol: parsed.protocol || 'file:',
-      repository,
+      repository: url.format({...parsed, hash: ''}),
     };
   }
 
diff --git a/src/util/misc.js b/src/util/misc.js
index 1725a66..440ec2f 100644
--- a/src/util/misc.js
+++ b/src/util/misc.js
@@ -2,24 +2,6 @@
 
 const _camelCase = require('camelcase');
 
-export function consumeStream(stream: Object): Promise<Buffer> {
-  return new Promise((resolve, reject) => {
-    const buffers = [];
-
-    stream.on(`data`, buffer => {
-      buffers.push(buffer);
-    });
-
-    stream.on(`end`, () => {
-      resolve(Buffer.concat(buffers));
-    });
-
-    stream.on(`error`, error => {
-      reject(error);
-    });
-  });
-}
-
 export function sortAlpha(a: string, b: string): number {
   // sort alphabetically in a deterministic way
   const shortLen = Math.min(a.length, b.length);
diff --git a/src/util/normalize-manifest/fix.js b/src/util/normalize-manifest/fix.js
index 106dccf..7adb848 100644
--- a/src/util/normalize-manifest/fix.js
+++ b/src/util/normalize-manifest/fix.js
@@ -309,8 +309,12 @@ export default (async function(
   }
 
   for (const dependencyType of DEPENDENCY_TYPES) {
-    if (info[dependencyType] && typeof info[dependencyType] === 'object') {
-      delete info[dependencyType]['//'];
+    const dependencyList = info[dependencyType];
+    if (dependencyList && typeof dependencyList === 'object') {
+      delete dependencyList['//'];
+      for (const name in dependencyList) {
+        dependencyList[name] = dependencyList[name] || '';
+      }
     }
   }
 });
diff --git a/src/util/path.js b/src/util/path.js
index 66e53cf..277835e 100644
--- a/src/util/path.js
+++ b/src/util/path.js
@@ -8,17 +8,9 @@ export function getPosixPath(path: string): string {
   return path.replace(/\\/g, '/');
 }
 
-export function expandPath(path: string): string {
-  if (process.platform !== 'win32') {
-    path = path.replace(/^\s*~(?=$|\/|\\)/, userHome);
-  }
-
-  return path;
-}
-
 export function resolveWithHome(path: string): string {
   const homePattern = process.platform === 'win32' ? /^~(\/|\\)/ : /^~\//;
-  if (path.match(homePattern)) {
+  if (homePattern.test(path)) {
     return resolve(userHome, path.substr(2));
   }
 
diff --git a/src/util/request-manager.js b/src/util/request-manager.js
index 8b7476b..b8049e8 100644
--- a/src/util/request-manager.js
+++ b/src/util/request-manager.js
@@ -1,5 +1,11 @@
 /* @flow */
 
+import fs from 'fs';
+import url from 'url';
+import dnscache from 'dnscache';
+import invariant from 'invariant';
+import RequestCaptureHar from 'request-capture-har';
+
 import type {Reporter} from '../reporters/index.js';
 import {MessageError} from '../errors.js';
 import BlockingQueue from './blocking-queue.js';
@@ -9,11 +15,14 @@ import map from '../util/map.js';
 
 import typeof * as RequestModuleT from 'request';
 
-const RequestCaptureHar = require('request-capture-har');
-const invariant = require('invariant');
-const url = require('url');
-const fs = require('fs');
-
+// Initialize DNS cache so we don't look up the same
+// domains like registry.yarnpkg.com over and over again
+// for each request.
+dnscache({
+  enable: true,
+  ttl: 300,
+  cachesize: 10,
+});
 const successHosts = map();
 const controlOffline = network.isOffline();
 
@@ -54,6 +63,7 @@ type RequestParams<T> = {
   retryAttempts?: number,
   maxRetryAttempts?: number,
   followRedirect?: boolean,
+  rejectStatusCode?: number | Array<number>,
 };
 
 type RequestOptions = {
@@ -69,9 +79,9 @@ export default class RequestManager {
     this._requestModule = null;
     this.offlineQueue = [];
     this.captureHar = false;
-    this.httpsProxy = null;
+    this.httpsProxy = '';
     this.ca = null;
-    this.httpProxy = null;
+    this.httpProxy = '';
     this.strictSSL = true;
     this.userAgent = '';
     this.reporter = reporter;
@@ -87,8 +97,8 @@ export default class RequestManager {
   userAgent: string;
   reporter: Reporter;
   running: number;
-  httpsProxy: ?string;
-  httpProxy: ?string;
+  httpsProxy: string | boolean;
+  httpProxy: string | boolean;
   strictSSL: boolean;
   ca: ?Array<string>;
   cert: ?string;
@@ -109,8 +119,8 @@ export default class RequestManager {
     userAgent?: string,
     offline?: boolean,
     captureHar?: boolean,
-    httpProxy?: string,
-    httpsProxy?: string,
+    httpProxy?: string | boolean,
+    httpsProxy?: string | boolean,
     strictSSL?: boolean,
     ca?: Array<string>,
     cafile?: string,
@@ -133,11 +143,13 @@ export default class RequestManager {
     }
 
     if (opts.httpProxy != null) {
-      this.httpProxy = opts.httpProxy;
+      this.httpProxy = opts.httpProxy || '';
     }
 
-    if (opts.httpsProxy != null) {
-      this.httpsProxy = opts.httpsProxy;
+    if (opts.httpsProxy === '') {
+      this.httpsProxy = opts.httpProxy || '';
+    } else {
+      this.httpsProxy = opts.httpsProxy || '';
     }
 
     if (opts.strictSSL !== null && typeof opts.strictSSL !== 'undefined') {
@@ -299,7 +311,7 @@ export default class RequestManager {
 
   queueForOffline(opts: RequestOptions) {
     if (!this.offlineQueue.length) {
-      this.reporter.warn(this.reporter.lang('offlineRetrying'));
+      this.reporter.info(this.reporter.lang('offlineRetrying'));
       this.initOfflineRetry();
     }
 
@@ -384,7 +396,7 @@ export default class RequestManager {
           const errMsg = (body && body.message) || reporter.lang('requestError', params.url, res.statusCode);
           reject(new Error(errMsg));
         } else {
-          if (res.statusCode === 400 || res.statusCode === 404 || res.statusCode === 401) {
+          if ([400, 401, 404].concat(params.rejectStatusCode || []).indexOf(res.statusCode) !== -1) {
             body = false;
           }
           resolve(body);
@@ -398,10 +410,10 @@ export default class RequestManager {
 
     let proxy = this.httpProxy;
     if (params.url.startsWith('https:')) {
-      proxy = this.httpsProxy || proxy;
+      proxy = this.httpsProxy;
     }
     if (proxy) {
-      params.proxy = proxy;
+      params.proxy = String(proxy);
     }
 
     if (this.ca != null) {
diff --git a/yarn.lock b/yarn.lock
index 6ddb14b..01da87c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -97,6 +97,10 @@ ansi-regex@^2.0.0, ansi-regex@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
 
+ansi-regex@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
+
 ansi-styles@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
@@ -107,6 +111,12 @@ ansi-styles@^3.0.0:
   dependencies:
     color-convert "^1.0.0"
 
+ansi-styles@^3.1.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88"
+  dependencies:
+    color-convert "^1.9.0"
+
 anymatch@^1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.0.tgz#a3e52fa39168c825ff57b0248126ce5a8ff95507"
@@ -194,6 +204,10 @@ arrify@^1.0.0, arrify@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
 
+asap@~2.0.3:
+  version "2.0.6"
+  resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
+
 asn1.js@^4.0.0:
   version "4.9.1"
   resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.9.1.tgz#48ba240b45a9280e94748990ba597d216617fd40"
@@ -992,6 +1006,12 @@ bytes@^2.4.0:
   version "2.5.0"
   resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.5.0.tgz#4c9423ea2d252c270c41b2bdefeff9bb6b62c06a"
 
+cachedir@^1.1.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-1.1.1.tgz#e1363075ea206a12767d92bb711c8a2f76a10f62"
+  dependencies:
+    os-homedir "^1.0.1"
+
 caller-path@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f"
@@ -1029,7 +1049,7 @@ center-align@^0.1.1:
     align-text "^0.1.3"
     lazy-cache "^1.0.3"
 
-chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
+chalk at 1.1.3, chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
   dependencies:
@@ -1039,6 +1059,14 @@ chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
     strip-ansi "^3.0.0"
     supports-color "^2.0.0"
 
+chalk@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.1.0.tgz#ac5becf14fa21b99c6c92ca7a7d7cfd5b17e743e"
+  dependencies:
+    ansi-styles "^3.1.0"
+    escape-string-regexp "^1.0.5"
+    supports-color "^4.0.0"
+
 chokidar@^1.4.3, chokidar@^1.6.1:
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468"
@@ -1072,6 +1100,12 @@ circular-json@^0.3.1:
   version "0.3.1"
   resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d"
 
+cli-cursor@^1.0.1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987"
+  dependencies:
+    restore-cursor "^1.0.1"
+
 cli-cursor@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
@@ -1125,7 +1159,7 @@ code-point-at@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
 
-color-convert@^1.0.0:
+color-convert@^1.0.0, color-convert@^1.9.0:
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a"
   dependencies:
@@ -1147,6 +1181,26 @@ commander@^2.9.0:
   dependencies:
     graceful-readlink ">= 1.0.0"
 
+commitizen@^2.9.6:
+  version "2.9.6"
+  resolved "https://registry.yarnpkg.com/commitizen/-/commitizen-2.9.6.tgz#c0d00535ef264da7f63737edfda4228983fa2291"
+  dependencies:
+    cachedir "^1.1.0"
+    chalk "1.1.3"
+    cz-conventional-changelog "1.2.0"
+    dedent "0.6.0"
+    detect-indent "4.0.0"
+    find-node-modules "1.0.4"
+    find-root "1.0.0"
+    fs-extra "^1.0.0"
+    glob "7.1.1"
+    inquirer "1.2.3"
+    lodash "4.17.2"
+    minimist "1.2.0"
+    path-exists "2.1.0"
+    shelljs "0.7.6"
+    strip-json-comments "2.0.1"
+
 commondir@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
@@ -1155,7 +1209,7 @@ concat-map at 0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
 
-concat-stream@^1.6.0:
+concat-stream@^1.4.7, concat-stream@^1.6.0:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7"
   dependencies:
@@ -1181,6 +1235,10 @@ content-type-parser@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.1.tgz#c3e56988c53c65127fb46d4032a3a900246fdc94"
 
+conventional-commit-types@^2.0.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/conventional-commit-types/-/conventional-commit-types-2.2.0.tgz#5db95739d6c212acbe7b6f656a11b940baa68946"
+
 convert-source-map at 1.X, convert-source-map@^1.1.0, convert-source-map@^1.4.0:
   version "1.5.0"
   resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5"
@@ -1274,6 +1332,28 @@ currently-unhandled@^0.4.1:
   dependencies:
     array-find-index "^1.0.1"
 
+cz-conventional-changelog at 1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/cz-conventional-changelog/-/cz-conventional-changelog-1.2.0.tgz#2bca04964c8919b23f3fd6a89ef5e6008b31b3f8"
+  dependencies:
+    conventional-commit-types "^2.0.0"
+    lodash.map "^4.5.1"
+    longest "^1.0.1"
+    pad-right "^0.2.2"
+    right-pad "^1.0.1"
+    word-wrap "^1.0.3"
+
+cz-conventional-changelog@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/cz-conventional-changelog/-/cz-conventional-changelog-2.0.0.tgz#55a979afdfe95e7024879d2a0f5924630170b533"
+  dependencies:
+    conventional-commit-types "^2.0.0"
+    lodash.map "^4.5.1"
+    longest "^1.0.1"
+    pad-right "^0.2.2"
+    right-pad "^1.0.1"
+    word-wrap "^1.0.3"
+
 damerau-levenshtein@^1.0.0:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz#03191c432cb6eea168bb77f3a55ffdccb8978514"
@@ -1313,6 +1393,10 @@ decamelize@^1.0.0, decamelize@^1.1.1:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
 
+dedent at 0.6.0:
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.6.0.tgz#0e6da8f0ce52838ef5cec5c8f9396b0c1b64a3cb"
+
 deep-extend@~0.4.0:
   version "0.4.2"
   resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f"
@@ -1377,7 +1461,7 @@ detect-file@^0.1.0:
   dependencies:
     fs-exists-sync "^0.1.0"
 
-detect-indent@^4.0.0:
+detect-indent at 4.0.0, detect-indent@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208"
   dependencies:
@@ -1403,6 +1487,13 @@ diffie-hellman@^5.0.0:
     miller-rabin "^4.0.0"
     randombytes "^2.0.0"
 
+dnscache@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/dnscache/-/dnscache-1.0.1.tgz#42cb2b9bfb5e8fbdfa395aac74e127fc05074d31"
+  dependencies:
+    asap "~2.0.3"
+    lodash.clone "~4.3.2"
+
 doctrine@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63"
@@ -1714,6 +1805,10 @@ execa@^0.7.0:
     signal-exit "^3.0.0"
     strip-eof "^1.0.0"
 
+exit-hook@^1.0.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8"
+
 expand-brackets@^0.1.4:
   version "0.1.5"
   resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b"
@@ -1736,6 +1831,14 @@ extend@^3.0.0, extend@~3.0.0:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444"
 
+external-editor@^1.1.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-1.1.1.tgz#12d7b0db850f7ff7e7081baf4005700060c4600b"
+  dependencies:
+    extend "^3.0.0"
+    spawn-sync "^1.0.15"
+    tmp "^0.0.29"
+
 external-editor@^2.0.1:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.0.2.tgz#384f6d8ae02054235c19e0a142c6d6fc36ad9363"
@@ -1783,6 +1886,13 @@ fb-watchman@^2.0.0:
   dependencies:
     bser "^2.0.0"
 
+figures@^1.3.5:
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
+  dependencies:
+    escape-string-regexp "^1.0.5"
+    object-assign "^4.1.0"
+
 figures@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
@@ -1829,6 +1939,17 @@ find-index@^0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/find-index/-/find-index-0.1.1.tgz#675d358b2ca3892d795a1ab47232f8b6e2e0dde4"
 
+find-node-modules at 1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/find-node-modules/-/find-node-modules-1.0.4.tgz#b6deb3cccb699c87037677bcede2c5f5862b2550"
+  dependencies:
+    findup-sync "0.4.2"
+    merge "^1.2.0"
+
+find-root at 1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.0.0.tgz#962ff211aab25c6520feeeb8d6287f8f6e95807a"
+
 find-up@^1.0.0:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
@@ -1842,6 +1963,15 @@ find-up@^2.1.0:
   dependencies:
     locate-path "^2.0.0"
 
+findup-sync at 0.4.2:
+  version "0.4.2"
+  resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.4.2.tgz#a8117d0f73124f5a4546839579fe52d7129fb5e5"
+  dependencies:
+    detect-file "^0.1.0"
+    is-glob "^2.0.1"
+    micromatch "^2.3.7"
+    resolve-dir "^0.1.0"
+
 findup-sync@^0.4.2:
   version "0.4.3"
   resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.4.3.tgz#40043929e7bc60adf0b7f4827c4c6e75a0deca12"
@@ -1924,6 +2054,14 @@ fs-exists-sync@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add"
 
+fs-extra@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-1.0.0.tgz#cd3ce5f7e7cb6145883fcae3191e9877f8587950"
+  dependencies:
+    graceful-fs "^4.1.2"
+    jsonfile "^2.1.0"
+    klaw "^1.0.0"
+
 fs.realpath@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@@ -2036,6 +2174,17 @@ glob2base@^0.0.12:
   dependencies:
     find-index "^0.1.1"
 
+glob at 7.1.1:
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
+  dependencies:
+    fs.realpath "^1.0.0"
+    inflight "^1.0.4"
+    inherits "2"
+    minimatch "^3.0.2"
+    once "^1.3.0"
+    path-is-absolute "^1.0.0"
+
 glob@^4.3.1:
   version "4.5.3"
   resolved "https://registry.yarnpkg.com/glob/-/glob-4.5.3.tgz#c6cb73d3226c1efef04de3c56d012f03377ee15f"
@@ -2045,7 +2194,7 @@ glob@^4.3.1:
     minimatch "^2.0.1"
     once "^1.3.0"
 
-glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2:
+glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2:
   version "7.1.2"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
   dependencies:
@@ -2109,7 +2258,7 @@ glogg@^1.0.0:
   dependencies:
     sparkles "^1.0.0"
 
-graceful-fs at 4.X, graceful-fs@^4.1.11, graceful-fs@^4.1.2:
+graceful-fs at 4.X, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9:
   version "4.1.11"
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
 
@@ -2298,6 +2447,10 @@ has-flag@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa"
 
+has-flag@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51"
+
 has-gulplog@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/has-gulplog/-/has-gulplog-0.1.0.tgz#6414c82913697da51590397dafb12f22967811ce"
@@ -2425,6 +2578,25 @@ ini@^1.3.4, ini@~1.3.0:
   version "1.3.4"
   resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e"
 
+inquirer at 1.2.3:
+  version "1.2.3"
+  resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-1.2.3.tgz#4dec6f32f37ef7bb0b2ed3f1d1a5c3f545074918"
+  dependencies:
+    ansi-escapes "^1.1.0"
+    chalk "^1.0.0"
+    cli-cursor "^1.0.1"
+    cli-width "^2.0.0"
+    external-editor "^1.1.0"
+    figures "^1.3.5"
+    lodash "^4.3.0"
+    mute-stream "0.0.6"
+    pinkie-promise "^2.0.0"
+    run-async "^2.2.0"
+    rx "^4.1.0"
+    string-width "^1.0.1"
+    strip-ansi "^3.0.0"
+    through "^2.3.6"
+
 inquirer@^3.0.1, inquirer@^3.0.6:
   version "3.0.6"
   resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.0.6.tgz#e04aaa9d05b7a3cb9b0f407d04375f0447190347"
@@ -3033,6 +3205,12 @@ json5@^0.5.0, json5@^0.5.1:
   version "0.5.1"
   resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
 
+jsonfile@^2.1.0:
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8"
+  optionalDependencies:
+    graceful-fs "^4.1.6"
+
 jsonify@~0.0.0:
   version "0.0.0"
   resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
@@ -3060,6 +3238,12 @@ kind-of@^3.0.2:
   dependencies:
     is-buffer "^1.1.5"
 
+klaw@^1.0.0:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439"
+  optionalDependencies:
+    graceful-fs "^4.1.9"
+
 lazy-cache@^1.0.3:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e"
@@ -3125,6 +3309,10 @@ locate-path@^2.0.0:
     p-locate "^2.0.0"
     path-exists "^3.0.0"
 
+lodash._baseclone@~4.5.0:
+  version "4.5.7"
+  resolved "https://registry.yarnpkg.com/lodash._baseclone/-/lodash._baseclone-4.5.7.tgz#ce42ade08384ef5d62fa77c30f61a46e686f8434"
+
 lodash._basecopy@^3.0.0:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36"
@@ -3165,6 +3353,12 @@ lodash.assignwith@^4.0.7:
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/lodash.assignwith/-/lodash.assignwith-4.2.0.tgz#127a97f02adc41751a954d24b0de17e100e038eb"
 
+lodash.clone@~4.3.2:
+  version "4.3.2"
+  resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.3.2.tgz#e56b176b6823a7dde38f7f2bf58de7d5971200e9"
+  dependencies:
+    lodash._baseclone "~4.5.0"
+
 lodash.escape@^3.0.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-3.2.0.tgz#995ee0dc18c1b48cc92effae71a10aab5b487698"
@@ -3199,6 +3393,10 @@ lodash.keys@^3.0.0:
     lodash.isarguments "^3.0.0"
     lodash.isarray "^3.0.0"
 
+lodash.map@^4.5.1:
+  version "4.6.0"
+  resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3"
+
 lodash.mapvalues@^4.4.0:
   version "4.6.0"
   resolved "https://registry.yarnpkg.com/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz#1bafa5005de9dd6f4f26668c30ca37230cc9689c"
@@ -3232,6 +3430,10 @@ lodash.templatesettings@^3.0.0:
     lodash._reinterpolate "^3.0.0"
     lodash.escape "^3.0.0"
 
+lodash at 4.17.2:
+  version "4.17.2"
+  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.2.tgz#34a3055babe04ce42467b607d700072c7ff6bf42"
+
 lodash@^4.0.0, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0:
   version "4.17.4"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
@@ -3291,7 +3493,7 @@ merge-stream@^1.0.0:
   dependencies:
     readable-stream "^2.0.1"
 
-merge@^1.1.3:
+merge@^1.1.3, merge@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da"
 
@@ -3365,7 +3567,7 @@ minimist at 0.0.8, minimist@~0.0.1:
   version "0.0.8"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
 
-minimist@^1.1.0, minimist@^1.1.1, minimist@^1.2.0:
+minimist at 1.2.0, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
 
@@ -3389,6 +3591,10 @@ multipipe@^0.1.2:
   dependencies:
     duplexer2 "0.0.2"
 
+mute-stream at 0.0.6:
+  version "0.0.6"
+  resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.6.tgz#48962b19e169fd1dfc240b3f1e7317627bbc47db"
+
 mute-stream at 0.0.7, mute-stream@~0.0.4:
   version "0.0.7"
   resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
@@ -3563,6 +3769,10 @@ once@~1.3.0:
   dependencies:
     wrappy "1"
 
+onetime@^1.0.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789"
+
 onetime@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4"
@@ -3613,6 +3823,10 @@ os-locale@^1.4.0:
   dependencies:
     lcid "^1.0.0"
 
+os-shim@^0.1.2:
+  version "0.1.3"
+  resolved "https://registry.yarnpkg.com/os-shim/-/os-shim-0.1.3.tgz#6b62c3791cf7909ea35ed46e17658bb417cb3917"
+
 os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
@@ -3642,6 +3856,12 @@ p-map@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.1.1.tgz#05f5e4ae97a068371bc2a5cc86bfbdbc19c4ae7a"
 
+pad-right@^0.2.2:
+  version "0.2.2"
+  resolved "https://registry.yarnpkg.com/pad-right/-/pad-right-0.2.2.tgz#6fbc924045d244f2a2a244503060d3bfc6009774"
+  dependencies:
+    repeat-string "^1.5.2"
+
 pako@~0.2.0:
   version "0.2.9"
   resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75"
@@ -3695,7 +3915,7 @@ path-dirname@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0"
 
-path-exists@^2.0.0:
+path-exists at 2.1.0, path-exists@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b"
   dependencies:
@@ -3852,6 +4072,10 @@ public-encrypt@^4.0.0:
     parse-asn1 "^5.0.0"
     randombytes "^2.0.1"
 
+puka@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/puka/-/puka-1.0.0.tgz#1dd92f9f81f6c53390a17529b7aebaa96604ad97"
+
 pump@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.2.tgz#3b3ee6512f94f0e575538c17995f9f16990a5d51"
@@ -4106,6 +4330,13 @@ resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.2:
   dependencies:
     path-parse "^1.0.5"
 
+restore-cursor@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541"
+  dependencies:
+    exit-hook "^1.0.0"
+    onetime "^1.0.0"
+
 restore-cursor@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
@@ -4123,6 +4354,10 @@ right-align@^0.1.1:
   dependencies:
     align-text "^0.1.1"
 
+right-pad@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/right-pad/-/right-pad-1.0.1.tgz#8ca08c2cbb5b55e74dafa96bf7fd1a27d568c8d0"
+
 rimraf at 2, rimraf@^2.2.8, rimraf@^2.5.0, rimraf@^2.5.1, rimraf@^2.6.1:
   version "2.6.1"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d"
@@ -4210,6 +4445,14 @@ shebang-regex@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
 
+shelljs at 0.7.6:
+  version "0.7.6"
+  resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.6.tgz#379cccfb56b91c8601e4793356eb5382924de9ad"
+  dependencies:
+    glob "^7.0.0"
+    interpret "^1.0.0"
+    rechoir "^0.6.2"
+
 shellwords@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.0.tgz#66afd47b6a12932d9071cbfd98a52e785cd0ba14"
@@ -4291,6 +4534,13 @@ sparkles@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3"
 
+spawn-sync@^1.0.15:
+  version "1.0.15"
+  resolved "https://registry.yarnpkg.com/spawn-sync/-/spawn-sync-1.0.15.tgz#b00799557eb7fb0c8376c29d44e8a1ea67e57476"
+  dependencies:
+    concat-stream "^1.4.7"
+    os-shim "^0.1.2"
+
 spdx-correct@~1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40"
@@ -4398,6 +4648,12 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1:
   dependencies:
     ansi-regex "^2.0.0"
 
+strip-ansi@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
+  dependencies:
+    ansi-regex "^3.0.0"
+
 strip-bom-stream@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz#f87db5ef2613f6968aa545abfe1ec728b6a829ca"
@@ -4430,7 +4686,7 @@ strip-eof@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
 
-strip-json-comments@~2.0.1:
+strip-json-comments at 2.0.1, strip-json-comments@~2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
 
@@ -4444,6 +4700,12 @@ supports-color@^3.1.0, supports-color@^3.1.2:
   dependencies:
     has-flag "^1.0.0"
 
+supports-color@^4.0.0:
+  version "4.4.0"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e"
+  dependencies:
+    has-flag "^2.0.0"
+
 symbol-tree@^3.2.1:
   version "3.2.2"
   resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"
@@ -4570,6 +4832,12 @@ timers-browserify@^2.0.2:
   dependencies:
     setimmediate "^1.0.4"
 
+tmp@^0.0.29:
+  version "0.0.29"
+  resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.29.tgz#f25125ff0dd9da3ccb0c2dd371ee1288bb9128c0"
+  dependencies:
+    os-tmpdir "~1.0.1"
+
 tmp@^0.0.31:
   version "0.0.31"
   resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7"
@@ -4858,6 +5126,10 @@ window-size at 0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
 
+word-wrap@^1.0.3:
+  version "1.2.3"
+  resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
+
 wordwrap at 0.0.2, wordwrap@~0.0.2:
   version "0.0.2"
   resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-yarnpkg.git



More information about the Pkg-javascript-commits mailing list