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

Paolo Greppi paolog-guest at moszumanska.debian.org
Mon Jan 8 09:51:47 UTC 2018


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 2ed0dd5f2e7bbec580f716b4462095911d64ead0
Author: Paolo Greppi <paolo.greppi at libpf.com>
Date:   Mon Jan 8 10:40:05 2018 +0100

    New upstream version 1.3.2
---
 .circleci/config.yml                               | 141 +++++++++++++++++++++
 .travis.yml                                        |   2 +-
 CONTRIBUTING.md                                    |  19 +++
 __tests__/commands/__snapshots__/init.js.snap      |  11 ++
 __tests__/commands/__snapshots__/licenses.js.snap  |   2 +-
 __tests__/commands/_helpers.js                     |  35 +++--
 __tests__/commands/add.js                          | 136 ++++++++++++++++++++
 __tests__/commands/info.js                         |  21 ++-
 __tests__/commands/init.js                         |  23 ++++
 __tests__/commands/install/integration.js          | 100 +++++++++++----
 __tests__/commands/install/resolutions.js          |  13 ++
 __tests__/commands/install/workspaces-install.js   |  33 ++++-
 __tests__/commands/licenses.js                     |   2 +-
 __tests__/commands/link.js                         |  14 +-
 __tests__/commands/list.js                         |   5 +-
 __tests__/commands/remove.js                       |  28 ++++
 __tests__/commands/upgrade.js                      |   6 +
 __tests__/config.js                                |  44 +++++++
 __tests__/constants.js                             |  17 +--
 .../add/add-already-added-dependency/package.json  |   7 +
 .../add-already-added-dev-dependency/package.json  |   7 +
 __tests__/fixtures/init/init-config/.yarnrc        |   6 +
 .../install-dont-overwrite-linked-scoped/.npmrc    |   1 -
 .../dir-to-link/package.json                       |   7 -
 .../@fakescope-fake-dependency-1.0.1.tgz           | Bin 576 -> 0 bytes
 .../package.json                                   |   5 -
 .../install-dont-overwrite-linked-scoped/yarn.lock |   5 -
 .../install-dont-overwrite-linked/package.json     |   8 ++
 .../install/install-with-comments/package.json     |   3 +
 .../resolutions/frozen-lockfile/package.json       |  11 ++
 .../install/resolutions/frozen-lockfile/yarn.lock  |  12 ++
 .../install/workspaces-install-bin/file.js         |   0
 .../install/workspaces-install-bin/package.json    |  12 ++
 .../packages/workspace-1/bin.js                    |   0
 .../packages/workspace-1/package.json              |   8 ++
 .../packages/workspace-2/package.json              |  10 ++
 .../install/workspaces-install-bin/yarn.lock       |  90 +++++++++++++
 .../link/package-with-name-scoped/package.json     |   3 +
 .../abbrev/-/abbrev-1.1.1.tgz.bin                  | Bin 0 -> 2998 bytes
 .../hoist-non-react-statics.bin                    | Bin 0 -> 1791 bytes
 .../-/hoist-non-react-statics-1.2.0.tgz.bin        | Bin 0 -> 7908 bytes
 .../-/hoist-non-react-statics-2.3.1.tgz.bin        | Bin 0 -> 3678 bytes
 .../GET/registry.yarnpkg.com/react-refetch.bin     | Bin 0 -> 19328 bytes
 .../react-refetch/-/react-refetch-1.0.1.tgz.bin    | Bin 0 -> 21852 bytes
 .../react-refetch/-/react-refetch-1.0.3-0.tgz.bin  | Bin 0 -> 21859 bytes
 .../rimraf/-/rimraf-2.6.2.tgz.bin                  | Bin 0 -> 6231 bytes
 .../GET/registry.yarnpkg.com/ui-select.bin         | Bin 0 -> 2810 bytes
 .../ui-select/-/ui-select-0.19.8.tgz.bin           | Bin 0 -> 337664 bytes
 .../ui-select/-/ui-select-0.20.0.tgz.bin           | Bin 0 -> 334007 bytes
 .../GET/registry.yarnpkg.com/warning.bin           | Bin 0 -> 1136 bytes
 .../warning/-/warning-2.1.0.tgz.bin                | Bin 0 -> 3453 bytes
 .../warning/-/warning-3.0.0.tgz.bin                | Bin 0 -> 3547 bytes
 __tests__/fixtures/upgrade/using-beta/package.json |   5 +
 __tests__/fixtures/upgrade/using-beta/yarn.lock    |  42 ++++++
 __tests__/package-request.js                       |  41 ++++--
 __tests__/registries/npm-registry.js               |  14 +-
 __tests__/util/git/git-spawn.js                    |   2 +
 circle.yml                                         |  57 ---------
 package.json                                       |   2 +-
 src/cli/commands/add.js                            |   4 +
 src/cli/commands/autoclean.js                      |   8 +-
 src/cli/commands/create.js                         |   2 +-
 src/cli/commands/info.js                           |   2 +-
 src/cli/commands/init.js                           |   4 +-
 src/cli/commands/install.js                        |   7 +
 src/cli/commands/list.js                           |   3 +-
 src/cli/commands/run.js                            |   4 +-
 src/cli/commands/upgrade.js                        |  11 +-
 src/cli/index.js                                   |  21 ++-
 src/config.js                                      |   2 +-
 src/constants.js                                   |  15 ++-
 src/integrity-checker.js                           |   8 ++
 src/package-linker.js                              |  53 +++++---
 src/package-request.js                             |  11 +-
 src/package-resolver.js                            |   4 +-
 src/registries/npm-registry.js                     |  12 +-
 src/reporters/console/console-reporter.js          |   2 +-
 src/reporters/console/util.js                      |   8 ++
 src/reporters/lang/en.js                           |   4 +-
 src/resolution-map.js                              |   6 +
 src/resolvers/registries/npm-resolver.js           |   9 ++
 src/util/env-replace.js                            |   4 +-
 src/util/git/git-spawn.js                          |   4 +
 src/util/normalize-manifest/fix.js                 |   4 +-
 src/util/request-manager.js                        |   4 +-
 85 files changed, 995 insertions(+), 231 deletions(-)

diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 0000000..ad90805
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,141 @@
+version: 2
+
+defaults: &defaults
+  working_directory: ~/project/yarn
+  docker:
+    - image: yarnpkg/dev:latest
+
+default_filters: &default_filters
+  tags:
+    only: /^v[0-9]+\.[0-9]+\.[0-9]+$/
+
+restore_node_modules: &restore_node_modules
+  restore_cache:
+    name: Restore node_modules cache
+    keys:
+      - v1-node-{{ .Branch }}-{{ checksum "yarn.lock" }}
+      - v1-node-{{ .Branch }}-
+      - v1-node-
+
+jobs:
+  install:
+    <<: *defaults
+    steps:
+      - checkout
+      - *restore_node_modules
+      - run:
+          name: Install Dependencies
+          command: yarn install
+      - save_cache:
+          name: Save yarn cache
+          key: v1-yarn-{{ .Branch }}-{{ checksum "yarn.lock" }}
+          paths:
+            - .cache/yarn
+      - save_cache:
+          name: Save node_modules cache
+          key: v1-node-{{ .Branch }}-{{ checksum "yarn.lock" }}
+          paths:
+            - node_modules/
+      - run:
+          name: Remove node_modules to cleanup workspace
+          command: rm -r node_modules/
+      - persist_to_workspace:
+          root: ~/project
+          paths:
+            - yarn
+  test:
+    <<: *defaults
+    steps:
+      - attach_workspace:
+          at: ~/project
+      - *restore_node_modules
+      - run:
+          name: Tests
+          command: |
+            node -v
+            if [ "$CIRCLE_BRANCH" == 'master' ]; then
+              ./scripts/set-dev-version.js
+            fi;
+            # Limit maxWorkers to 3 to avoid OOM on CircleCI
+            yarn test-ci --maxWorkers 3
+            yarn check-lockfile
+  lint:
+    <<: *defaults
+    steps:
+      - attach_workspace:
+          at: ~/project
+      - *restore_node_modules
+      - run:
+          name: Lint
+          command: yarn lint
+  build:
+    <<: *defaults
+    steps:
+      - attach_workspace:
+          at: ~/project
+      - *restore_node_modules
+      - run:
+          name: Build distribution
+          command: |
+            node -v
+            if [ "$CIRCLE_BRANCH" == 'master' ]; then
+              ./scripts/set-dev-version.js
+            fi;
+            yarn build-dist
+            ./scripts/build-deb.sh
+      - store_artifacts:
+          path: artifacts/
+          destination: yarnpkg
+      - persist_to_workspace:
+          root: ~/project
+          paths:
+            - yarn
+  publish:
+    <<: *defaults
+    steps:
+      - attach_workspace:
+          at: ~/project
+      - *restore_node_modules
+      - run:
+          name: Publish
+          command: |
+            # Only NPM is handled here - All other release files are handled in a webhook.
+            if [ "${CIRCLE_PROJECT_USERNAME}" == "yarnpkg" ]; then
+              echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
+              ./scripts/update-npm.sh
+            fi
+
+notify:
+  webhooks:
+    # Handles uploading stable/RC releases to GitHub
+    - url: https://nightly.yarnpkg.com/release_circleci
+    # Handles archiving all builds onto the nightly build site
+    - url: https://nightly.yarnpkg.com/archive_circleci
+
+workflows:
+  version: 2
+  install-test-build-and-publish:
+    jobs:
+      - install:
+          filters: *default_filters
+      - test:
+          filters: *default_filters
+          requires:
+            - install
+      - lint:
+          filters: *default_filters
+          requires:
+            - install
+      - build:
+          filters: *default_filters
+          requires:
+            - install
+      - publish:
+          filters:
+            <<: *default_filters
+            branches:
+              ignore: /.*/
+          requires:
+            - test
+            - lint
+            - build
diff --git a/.travis.yml b/.travis.yml
index 6fab908..b80f339 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -55,7 +55,7 @@ matrix:
 
 before_install:
   - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
-      RAMDISK_SIZE=330;
+      RAMDISK_SIZE=350;
       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 c72e5d8..6c54077 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -3,6 +3,25 @@
 Contributions are always welcome, no matter how large or small. Substantial feature requests should be proposed as an [RFC](https://github.com/yarnpkg/rfcs). Before contributing,
 please read the [code of conduct](CODE_OF_CONDUCT.md).
 
+## Find things to work on
+
+We label issues that we need help with the `help wanted` tag. We also categorize them with the following tags:
+
+ - cat-bug
+ - cat-feature
+ - cat-chore
+ - cat-performance
+
+These are the main categories that you can work on. We further mark issues with a `high-priority` tag or a `good first issue` tag to indicate their importance to the project and subjective level of easiness to get started on respectively. If you don't see the `triaged` tag or you see any of the `needs-confirmation`, `needs-repro-script`, `needs-discussion` tags, it may not be wise to start working on these issues.
+
+Here are a few quick links to get you started:
+
+ - [Good first bugs](https://github.com/yarnpkg/yarn/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22+label%3Atriaged+label%3Acat-bug+label%3A%22good+first+issue%22)
+ - [Good first features](https://github.com/yarnpkg/yarn/issues?utf8=%E2%9C%93&q=is%3Aopen%20is%3Aissue%20label%3A%22help%20wanted%22%20label%3Atriaged%20label%3Acat-feature%20label%3A%22good%20first%20issue%22%20)
+ - [High impact issue that need help](https://github.com/yarnpkg/yarn/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22+label%3Ahigh-priority+label%3Atriaged)
+ - [Issues need reproduction scripts](https://github.com/yarnpkg/yarn/issues?utf8=%E2%9C%93&q=is%3Aopen%20is%3Aissue%20label%3A%22needs-repro-script%22)
+ - [Issues need triaging](https://github.com/yarnpkg/yarn/issues?utf8=%E2%9C%93&q=is%3Aopen%20is%3Aissue%20-label%3Atriaged)
+
 ## Setup
 
 You need at least the latest version of Node 6 to work on Yarn.
diff --git a/__tests__/commands/__snapshots__/init.js.snap b/__tests__/commands/__snapshots__/init.js.snap
index 0961fd6..7f5a00a 100644
--- a/__tests__/commands/__snapshots__/init.js.snap
+++ b/__tests__/commands/__snapshots__/init.js.snap
@@ -1,5 +1,16 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`init-config 1`] = `
+Object {
+  "author": "Kittens McKitty <yarn at yarnpkg.com> (https://yarnpkg.com)",
+  "license": "BSD",
+  "main": "index.js",
+  "name": "init-config",
+  "private": true,
+  "version": "0.0.0-alpha",
+}
+`;
+
 exports[`init-github 1`] = `
 Object {
   "license": "MIT",
diff --git a/__tests__/commands/__snapshots__/licenses.js.snap b/__tests__/commands/__snapshots__/licenses.js.snap
index 934b526..594ed7e 100644
--- a/__tests__/commands/__snapshots__/licenses.js.snap
+++ b/__tests__/commands/__snapshots__/licenses.js.snap
@@ -7,7 +7,7 @@ exports[`lists all licenses of the dependencies with the --json argument 1`] = `
 "
 `;
 
-exports[`should genereate disclaimer on demand 1`] = `
+exports[`should generate disclaimer on demand 1`] = `
 "{\\"type\\":\\"warning\\",\\"data\\":\\"package.json: No license field\\"}
 {\\"type\\":\\"warning\\",\\"data\\":\\"No license field\\"}
 {\\"type\\":\\"warning\\",\\"data\\":\\"package.json: No license field\\"}
diff --git a/__tests__/commands/_helpers.js b/__tests__/commands/_helpers.js
index b73f39e..d8fd782 100644
--- a/__tests__/commands/_helpers.js
+++ b/__tests__/commands/_helpers.js
@@ -10,16 +10,17 @@ import * as fs from '../../src/util/fs.js';
 import {Install} from '../../src/cli/commands/install.js';
 import Config from '../../src/config.js';
 import parsePackagePath from '../../src/util/parse-package-path.js';
-
+import type {CLIFunctionReturn} from '../../src/types.js';
+import {run as link} from '../../src/cli/commands/link.js';
 const stream = require('stream');
 const path = require('path');
 
-const fixturesLoc = path.join(__dirname, '..', 'fixtures', 'install');
+const installFixturesLoc = path.join(__dirname, '..', 'fixtures', 'install');
 
 export const runInstall = run.bind(
   null,
   ConsoleReporter,
-  fixturesLoc,
+  installFixturesLoc,
   async (args, flags, config, reporter, lockfile): Promise<Install> => {
     const install = new Install(flags, config, reporter, lockfile);
     await install.init();
@@ -30,6 +31,17 @@ export const runInstall = run.bind(
   [],
 );
 
+const linkFixturesLoc = path.join(__dirname, '..', 'fixtures', 'link');
+
+export const runLink = run.bind(
+  null,
+  ConsoleReporter,
+  linkFixturesLoc,
+  (args, flags, config, reporter): CLIFunctionReturn => {
+    return link(config, reporter, flags, args);
+  },
+);
+
 export async function createLockfile(dir: string): Promise<Lockfile> {
   const lockfileLoc = path.join(dir, constants.LOCKFILE_FILENAME);
   let lockfile;
@@ -94,7 +106,7 @@ export async function run<T, R>(
   ) => Promise<T> | T,
   args: Array<string>,
   flags: Object,
-  name: string | {source: string, cwd: string},
+  name: string | {source?: string, cwd: string},
   checkInstalled: ?(config: Config, reporter: R, install: T, getStdout: () => string) => ?Promise<void>,
   beforeInstall: ?(cwd: string) => ?Promise<void>,
 ): Promise<void> {
@@ -113,11 +125,16 @@ export async function run<T, R>(
   if (fixturesLoc) {
     const source = typeof name === 'string' ? name : name.source;
 
-    const dir = path.join(fixturesLoc, source);
-    cwd = await fs.makeTempDir(path.basename(dir));
-    await fs.copy(dir, cwd, reporter);
-    if (typeof name !== 'string') {
-      cwd = path.join(cwd, name.cwd);
+    // if source wasn't set then assume we were given a complete path
+    if (typeof source === 'undefined') {
+      cwd = typeof name !== 'string' ? name.cwd : await fs.makeTempDir();
+    } else {
+      const dir = path.join(fixturesLoc, source);
+      cwd = await fs.makeTempDir(path.basename(dir));
+      await fs.copy(dir, cwd, reporter);
+      if (typeof name !== 'string') {
+        cwd = path.join(cwd, name.cwd);
+      }
     }
   } else {
     // if fixture loc is not set then CWD is some empty temp dir
diff --git a/__tests__/commands/add.js b/__tests__/commands/add.js
index 2182bad..e3c89d8 100644
--- a/__tests__/commands/add.js
+++ b/__tests__/commands/add.js
@@ -147,6 +147,75 @@ test.concurrent('install with --optional flag', (): Promise<void> => {
   });
 });
 
+// Test if moduleAlreadyInManifest warning is displayed
+const moduleAlreadyInManifestChecker = ({expectWarnings}: {expectWarnings: boolean}) => 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');
+
+  expect(warnings.some(warning => warning.data.toString().toLowerCase().indexOf('is already in') > -1)).toEqual(
+    expectWarnings,
+  );
+
+  expect(
+    warnings.some(
+      warning => warning.data.toString().toLowerCase().indexOf('please remove existing entry first before adding') > -1,
+    ),
+  ).toEqual(expectWarnings);
+};
+
+test.concurrent('warns when adding a devDependency as dependency', (): Promise<void> => {
+  return buildRun(
+    reporters.BufferReporter,
+    fixturesLoc,
+    moduleAlreadyInManifestChecker({expectWarnings: true}),
+    ['is-online'],
+    {},
+    'add-already-added-dev-dependency',
+  );
+});
+
+test.concurrent("doesn't warn when adding a devDependency as devDependency", (): Promise<void> => {
+  return buildRun(
+    reporters.BufferReporter,
+    fixturesLoc,
+    moduleAlreadyInManifestChecker({expectWarnings: false}),
+    ['is-online'],
+    {dev: true},
+    'add-already-added-dev-dependency',
+  );
+});
+
+test.concurrent('warns when adding a dependency as devDependency', (): Promise<void> => {
+  return buildRun(
+    reporters.BufferReporter,
+    fixturesLoc,
+    moduleAlreadyInManifestChecker({expectWarnings: true}),
+    ['is-online'],
+    {dev: true},
+    'add-already-added-dependency',
+  );
+});
+
+test.concurrent("doesn't warn when adding a dependency as dependency", (): Promise<void> => {
+  return buildRun(
+    reporters.BufferReporter,
+    fixturesLoc,
+    moduleAlreadyInManifestChecker({expectWarnings: false}),
+    ['is-online'],
+    {},
+    'add-already-added-dependency',
+  );
+});
+
 test.concurrent('install with link: specifier', (): Promise<void> => {
   return runAdd(['link:../left-pad'], {dev: true}, 'add-with-flag', async config => {
     const lockfile = explodeLockfile(await fs.readFile(path.join(config.cwd, 'yarn.lock')));
@@ -957,3 +1026,70 @@ test.concurrent('installing with --pure-lockfile and then adding should keep bui
     expect(await fs.exists(path.join(config.cwd, 'node_modules', 'package-a', 'temp.txt'))).toBe(true);
   });
 });
+
+test.concurrent('preserves unaffected bin links after adding to workspace package', async () => {
+  await runInstall({binLinks: true}, 'workspaces-install-bin', async (config): Promise<void> => {
+    const reporter = new ConsoleReporter({});
+
+    expect(await fs.exists(`${config.cwd}/node_modules/.bin/rimraf`)).toEqual(true);
+    expect(await fs.exists(`${config.cwd}/node_modules/.bin/touch`)).toEqual(true);
+    expect(await fs.exists(`${config.cwd}/node_modules/.bin/workspace-1`)).toEqual(true);
+    expect(await fs.exists(`${config.cwd}/packages/workspace-2/node_modules/.bin/rimraf`)).toEqual(true);
+    expect(await fs.exists(`${config.cwd}/packages/workspace-2/node_modules/.bin/workspace-1`)).toEqual(true);
+
+    // add package
+    const childConfig = await makeConfigFromDirectory(`${config.cwd}/packages/workspace-1`, reporter, {binLinks: true});
+    await add(childConfig, reporter, {}, ['max-safe-integer at 1.0.0']);
+
+    expect(
+      JSON.parse(await fs.readFile(path.join(config.cwd, 'packages/workspace-1/package.json'))).dependencies,
+    ).toEqual({
+      'max-safe-integer': '1.0.0',
+    });
+
+    // bin links should be preserved
+    expect(await fs.exists(`${config.cwd}/node_modules/.bin/rimraf`)).toEqual(true);
+    expect(await fs.exists(`${config.cwd}/node_modules/.bin/touch`)).toEqual(true);
+    expect(await fs.exists(`${config.cwd}/node_modules/.bin/workspace-1`)).toEqual(true);
+    expect(await fs.exists(`${config.cwd}/packages/workspace-2/node_modules/.bin/rimraf`)).toEqual(true);
+    expect(await fs.exists(`${config.cwd}/packages/workspace-2/node_modules/.bin/workspace-1`)).toEqual(true);
+  });
+});
+
+test.concurrent('installs "latest" instead of maxSatisfying if it satisfies requested pattern', (): Promise<void> => {
+  // Scenario:
+  // If a registry contains versions [1.0.0, 1.0.1, 1.0.2] and latest:1.0.1
+  // (note that "latest" is not the "newest" version)
+  // If yarn add ^1.0.0 is run, it should choose `1.0.1` because it is "latest" and satisfies the range,
+  // not `1.0.2` even though it is newer.
+  // This is behavior defined by the NPM implementation. See:
+  //  * https://github.com/yarnpkg/yarn/issues/3560
+  //  * https://git.io/vFmau
+  //
+  // In this test, `ui-select` has a max version of `0.20.0` but a `latest:0.19.8`
+  return runAdd(['ui-select@^0.X'], {}, 'latest-version-in-package', async (config, reporter, previousAdd) => {
+    const lockfile = explodeLockfile(await fs.readFile(path.join(config.cwd, 'yarn.lock')));
+    const patternIndex = lockfile.indexOf('ui-select@^0.X:');
+    const versionIndex = patternIndex + 1;
+    const actualVersion = lockfile[versionIndex];
+
+    expect(actualVersion).toContain('0.19.8');
+  });
+});
+
+test.concurrent('installs "latest" instead of maxSatisfying if no requested pattern', (): Promise<void> => {
+  // Scenario:
+  // If a registry contains versions [1.0.0, 1.0.1, 1.0.2] and latest:1.0.1
+  // If `yarn add` is run, it should choose `1.0.1` because it is "latest", not `1.0.2` even though it is newer.
+  // In other words, when no range is explicitely given, Yarn should choose "latest".
+  //
+  // In this test, `ui-select` has a max version of `0.20.0` but a `latest:0.19.8`
+  return runAdd(['ui-select'], {}, 'latest-version-in-package', async (config, reporter, previousAdd) => {
+    const lockfile = explodeLockfile(await fs.readFile(path.join(config.cwd, 'yarn.lock')));
+    const patternIndex = lockfile.indexOf('ui-select@^0.19.8:');
+    const versionIndex = patternIndex + 1;
+    const actualVersion = lockfile[versionIndex];
+
+    expect(actualVersion).toContain('0.19.8');
+  });
+});
diff --git a/__tests__/commands/info.js b/__tests__/commands/info.js
index c61b1a2..b123d67 100644
--- a/__tests__/commands/info.js
+++ b/__tests__/commands/info.js
@@ -7,8 +7,6 @@ import Config from '../../src/config.js';
 import path from 'path';
 
 jasmine.DEFAULT_TIMEOUT_INTERVAL = 90000;
-// the mocked requests have stripped metadata, don't use it in the following tests
-jest.unmock('request');
 
 const fixturesLoc = path.join(__dirname, '..', 'fixtures', 'info');
 
@@ -48,6 +46,11 @@ const expectedKeys = [
 // yarn now ships as built, single JS files so it has no dependencies and no scripts
 const unexpectedKeys = ['dependencies', 'devDependencies', 'scripts'];
 
+beforeEach(() => {
+  // the mocked requests have stripped metadata, don't use it in the following tests
+  jest.unmock('request');
+});
+
 test.concurrent('without arguments and in directory containing a valid package file', (): Promise<void> => {
   return runInfo([], {}, 'local', (config, output): ?Promise<void> => {
     const actualKeys = Object.keys(output);
@@ -88,6 +91,20 @@ test.concurrent('with two arguments and second argument "readme" shows readme st
   });
 });
 
+test.concurrent('with two arguments and second argument "version" shows `latest` version', (): Promise<void> => {
+  // Scenario:
+  // If a registry contains versions [1.0.0, 1.0.1, 1.0.2] and latest:1.0.1
+  // If `yarn info` is run, it should choose `1.0.1` because it is "latest", not `1.0.2` even though it is newer.
+  // In other words, when no range is explicitely given, Yarn should choose "latest".
+  //
+  // In this test, `ui-select` has a max version of `0.20.0` but a `latest:0.19.8`
+  jest.mock('../__mocks__/request.js');
+
+  return runInfo(['ui-select', 'version'], {}, '', (config, output): ?Promise<void> => {
+    expect(output).toEqual('0.19.8');
+  });
+});
+
 test.concurrent('with two arguments and second argument as a simple field', (): Promise<void> => {
   return runInfo(['yarn', 'repository'], {}, '', (config, output): ?Promise<void> => {
     expect(output).toEqual({
diff --git a/__tests__/commands/init.js b/__tests__/commands/init.js
index b37d3ef..e2463f8 100644
--- a/__tests__/commands/init.js
+++ b/__tests__/commands/init.js
@@ -84,6 +84,29 @@ test.concurrent('init --yes --private should create package.json with defaults a
   );
 });
 
+test.concurrent('init should use init-* configs when defined', (): Promise<void> => {
+  return buildRun(
+    ConsoleReporter,
+    fixturesLoc,
+    (args, flags, config, reporter, lockfile): Promise<void> => {
+      return runInit(config, reporter, flags, args);
+    },
+    [],
+    {yes: true},
+    'init-config',
+    async (config): Promise<void> => {
+      const {cwd} = config;
+      const manifestFile = await fs.readFile(path.join(cwd, 'package.json'));
+      const manifest = JSON.parse(manifestFile);
+
+      // Name is derived from directory name which is dynamic so check
+      // that separately and then remove from snapshot
+      expect(manifest.name).toEqual(path.basename(cwd));
+      expect({...manifest, name: 'init-config'}).toMatchSnapshot('init-config');
+    },
+  );
+});
+
 test.concurrent('init using Github shorthand should resolve to full repository URL', (): Promise<void> => {
   const questionMap = Object.freeze({
     name: 'hi-github',
diff --git a/__tests__/commands/install/integration.js b/__tests__/commands/install/integration.js
index 7fd8051..c8523bf 100644
--- a/__tests__/commands/install/integration.js
+++ b/__tests__/commands/install/integration.js
@@ -11,7 +11,7 @@ import {parse} from '../../../src/lockfile';
 import {Install, run as install} from '../../../src/cli/commands/install.js';
 import Lockfile from '../../../src/lockfile';
 import * as fs from '../../../src/util/fs.js';
-import {getPackageVersion, explodeLockfile, runInstall, createLockfile, run as buildRun} from '../_helpers.js';
+import {getPackageVersion, explodeLockfile, runInstall, runLink, createLockfile, run as buildRun} from '../_helpers.js';
 
 jasmine.DEFAULT_TIMEOUT_INTERVAL = 150000;
 
@@ -640,6 +640,30 @@ test.concurrent('install with comments in manifest', (): Promise<void> => {
   });
 });
 
+test.concurrent('install with comments in manifest resolutions does not result in warning', (): Promise<void> => {
+  const fixturesLoc = path.join(__dirname, '..', '..', 'fixtures', 'install');
+
+  return buildRun(
+    reporters.BufferReporter,
+    fixturesLoc,
+    async (args, flags, config, reporter): Promise<void> => {
+      await install(config, reporter, flags, args);
+
+      const output = reporter.getBuffer();
+      const warnings = output.filter(entry => entry.type === 'warning');
+
+      expect(
+        warnings.some(warning => {
+          return warning.data.toString().indexOf(reporter.lang('invalidResolutionName', '//')) > -1;
+        }),
+      ).toEqual(false);
+    },
+    [],
+    {lockfile: false},
+    'install-with-comments',
+  );
+});
+
 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);
@@ -842,29 +866,6 @@ test('install a scoped module from authed private registry with a missing traili
   });
 });
 
-test.concurrent('install will not overwrite files in symlinked scoped directories', async (): Promise<void> => {
-  await runInstall(
-    {},
-    'install-dont-overwrite-linked-scoped',
-    async (config): Promise<void> => {
-      const dependencyPath = path.join(config.cwd, 'node_modules', '@fakescope', 'fake-dependency');
-      expect('Symlinked scoped package test').toEqual(
-        (await fs.readJson(path.join(dependencyPath, 'package.json'))).description,
-      );
-      expect(await fs.exists(path.join(dependencyPath, 'index.js'))).toEqual(false);
-    },
-    async cwd => {
-      const dirToLink = path.join(cwd, 'dir-to-link');
-
-      await fs.mkdirp(path.join(cwd, '.yarn-link', '@fakescope'));
-      await fs.symlink(dirToLink, path.join(cwd, '.yarn-link', '@fakescope', 'fake-dependency'));
-
-      await fs.mkdirp(path.join(cwd, 'node_modules', '@fakescope'));
-      await fs.symlink(dirToLink, path.join(cwd, 'node_modules', '@fakescope', 'fake-dependency'));
-    },
-  );
-});
-
 test.concurrent('install of scoped package with subdependency conflict should pass check', (): Promise<void> => {
   return runInstall({}, 'install-scoped-package-with-subdependency-conflict', async (config, reporter) => {
     let allCorrect = true;
@@ -1110,3 +1111,54 @@ test.concurrent('warns for missing bundledDependencies', (): Promise<void> => {
     'missing-bundled-dep',
   );
 });
+
+test.concurrent('install will not overwrite linked scoped dependencies', async (): Promise<void> => {
+  // install only dependencies
+  await runInstall({production: true}, 'install-dont-overwrite-linked', async (installConfig): Promise<void> => {
+    // link our fake dep to the registry
+    await runLink([], {}, 'package-with-name-scoped', async (linkConfig): Promise<void> => {
+      // link our fake dependency in our node_modules
+      await runLink(
+        ['@fakescope/a-package'],
+        {linkFolder: linkConfig.linkFolder},
+        {cwd: installConfig.cwd},
+        async (): Promise<void> => {
+          // check that it exists (just in case)
+          const existed = await fs.exists(path.join(installConfig.cwd, 'node_modules', '@fakescope', 'a-package'));
+          expect(existed).toEqual(true);
+
+          // run install to install dev deps which would remove the linked dep if the bug was present
+          await runInstall({linkFolder: linkConfig.linkFolder}, {cwd: installConfig.cwd}, async (): Promise<void> => {
+            // if the linked dep is still there is a win :)
+            const existed = await fs.exists(path.join(installConfig.cwd, 'node_modules', '@fakescope', 'a-package'));
+            expect(existed).toEqual(true);
+          });
+        },
+      );
+    });
+  });
+});
+
+test.concurrent('install will not overwrite linked dependencies', async (): Promise<void> => {
+  // install only dependencies
+  await runInstall({production: true}, 'install-dont-overwrite-linked', async (installConfig): Promise<void> => {
+    // link our fake dep to the registry
+    await runLink([], {}, 'package-with-name', async (linkConfig): Promise<void> => {
+      // link our fake dependency in our node_modules
+      await runLink(['a-package'], {linkFolder: linkConfig.linkFolder}, {cwd: installConfig.cwd}, async (): Promise<
+        void,
+      > => {
+        // check that it exists (just in case)
+        const existed = await fs.exists(path.join(installConfig.cwd, 'node_modules', 'a-package'));
+        expect(existed).toEqual(true);
+
+        // run install to install dev deps which would remove the linked dep if the bug was present
+        await runInstall({linkFolder: linkConfig.linkFolder}, {cwd: installConfig.cwd}, async (): Promise<void> => {
+          // if the linked dep is still there is a win :)
+          const existed = await fs.exists(path.join(installConfig.cwd, 'node_modules', 'a-package'));
+          expect(existed).toEqual(true);
+        });
+      });
+    });
+  });
+});
diff --git a/__tests__/commands/install/resolutions.js b/__tests__/commands/install/resolutions.js
index 69b6c6f..92cb994 100644
--- a/__tests__/commands/install/resolutions.js
+++ b/__tests__/commands/install/resolutions.js
@@ -1,6 +1,7 @@
 /* @flow */
 
 import {getPackageVersion, isPackagePresent, runInstall} from '../_helpers.js';
+import {ConsoleReporter} from '../../../src/reporters/index.js';
 
 jasmine.DEFAULT_TIMEOUT_INTERVAL = 150000;
 
@@ -27,6 +28,18 @@ test.concurrent('install with subtree exact resolutions should override subtree
   });
 });
 
+test.concurrent('install with --frozen-lockfile with resolutions', async (): Promise<void> => {
+  const reporter = new ConsoleReporter({});
+
+  try {
+    await runInstall({frozenLockfile: true}, {source: 'resolutions', cwd: 'frozen-lockfile'}, async config => {
+      expect(await getPackageVersion(config, 'left-pad')).toEqual('1.1.3');
+    });
+  } catch (err) {
+    expect(err.message).not.toContain(reporter.lang('frozenLockfileError'));
+  }
+});
+
 test.concurrent('install with exotic resolutions should override versions', (): Promise<void> => {
   return runInstall({}, {source: 'resolutions', cwd: 'exotic-version'}, async config => {
     expect(await getPackageVersion(config, 'left-pad')).toEqual('1.1.1');
diff --git a/__tests__/commands/install/workspaces-install.js b/__tests__/commands/install/workspaces-install.js
index e2117e4..55ed6ce 100644
--- a/__tests__/commands/install/workspaces-install.js
+++ b/__tests__/commands/install/workspaces-install.js
@@ -1,10 +1,10 @@
 /* @flow */
 
 import {run as check} from '../../../src/cli/commands/check.js';
-import {Install} from '../../../src/cli/commands/install.js';
+import {Install, run as install} from '../../../src/cli/commands/install.js';
 import * as reporters from '../../../src/reporters/index.js';
 import * as fs from '../../../src/util/fs.js';
-import {runInstall, run as buildRun} from '../_helpers.js';
+import {runInstall, run as buildRun, makeConfigFromDirectory} from '../_helpers.js';
 
 jasmine.DEFAULT_TIMEOUT_INTERVAL = 150000;
 
@@ -242,4 +242,33 @@ test.concurrent('install should ignore node_modules in workspaces when used with
   });
 });
 
+test.concurrent('install should link binaries properly when run from child workspace', async () => {
+  await runInstall({binLinks: true}, 'workspaces-install-bin', async (config, reporter): Promise<void> => {
+    // initial install
+    expect(await fs.exists(`${config.cwd}/node_modules/.bin/rimraf`)).toEqual(true);
+    expect(await fs.exists(`${config.cwd}/node_modules/.bin/touch`)).toEqual(true);
+    expect(await fs.exists(`${config.cwd}/node_modules/.bin/workspace-1`)).toEqual(true);
+    expect(await fs.exists(`${config.cwd}/packages/workspace-2/node_modules/.bin/rimraf`)).toEqual(true);
+    expect(await fs.exists(`${config.cwd}/packages/workspace-2/node_modules/.bin/workspace-1`)).toEqual(true);
+
+    // reset package folders to simulate running 'install' from
+    // child workspace _before_ running it in the root (this is not
+    // possible to do without an initial install using the current
+    // testing infrastructure)
+    await fs.unlink(`${config.cwd}/node_modules`);
+    await fs.unlink(`${config.cwd}/packages/workspace-1/node_modules`);
+    await fs.unlink(`${config.cwd}/packages/workspace-2/node_modules`);
+
+    // run "install" in child package
+    const childConfig = await makeConfigFromDirectory(`${config.cwd}/packages/workspace-1`, reporter, {binLinks: true});
+    await install(childConfig, reporter, {}, []);
+
+    expect(await fs.exists(`${config.cwd}/node_modules/.bin/rimraf`)).toEqual(true);
+    expect(await fs.exists(`${config.cwd}/node_modules/.bin/touch`)).toEqual(true);
+    expect(await fs.exists(`${config.cwd}/node_modules/.bin/workspace-1`)).toEqual(true);
+    expect(await fs.exists(`${config.cwd}/packages/workspace-2/node_modules/.bin/rimraf`)).toEqual(true);
+    expect(await fs.exists(`${config.cwd}/packages/workspace-2/node_modules/.bin/workspace-1`)).toEqual(true);
+  });
+});
+
 // TODO need more thorough tests for all kinds of checks: integrity, verify-tree
diff --git a/__tests__/commands/licenses.js b/__tests__/commands/licenses.js
index e37e4e4..c9660cd 100644
--- a/__tests__/commands/licenses.js
+++ b/__tests__/commands/licenses.js
@@ -25,7 +25,7 @@ test('lists all licenses of the dependencies with the --json argument', async ()
   });
 });
 
-test('should genereate disclaimer on demand', async (): Promise<void> => {
+test('should generate disclaimer on demand', async (): Promise<void> => {
   await runLicenses(['generate-disclaimer'], {}, '', (config, reporter, stdout) => {
     expect(stdout).toMatchSnapshot();
   });
diff --git a/__tests__/commands/link.js b/__tests__/commands/link.js
index 59fd7db..a3c1129 100644
--- a/__tests__/commands/link.js
+++ b/__tests__/commands/link.js
@@ -1,24 +1,12 @@
 /* @flow */
 
-import {run as buildRun} from './_helpers.js';
-import {run as link} from '../../src/cli/commands/link.js';
+import {runLink} from './_helpers.js';
 import {ConsoleReporter} from '../../src/reporters/index.js';
-import type {CLIFunctionReturn} from '../../src/types.js';
 import mkdir from './../_temp.js';
 import * as fs from '../../src/util/fs.js';
 
 const path = require('path');
 
-const fixturesLoc = path.join(__dirname, '..', 'fixtures', 'link');
-const runLink = buildRun.bind(
-  null,
-  ConsoleReporter,
-  fixturesLoc,
-  (args, flags, config, reporter): CLIFunctionReturn => {
-    return link(config, reporter, flags, args);
-  },
-);
-
 test.concurrent('creates folder in linkFolder', async (): Promise<void> => {
   const linkFolder = await mkdir('link-folder');
   await runLink([], {linkFolder}, 'package-with-name', async (config, reporter): Promise<void> => {
diff --git a/__tests__/commands/list.js b/__tests__/commands/list.js
index 0805073..b134d40 100644
--- a/__tests__/commands/list.js
+++ b/__tests__/commands/list.js
@@ -166,10 +166,7 @@ describe('list', () => {
   });
 
   test('does not list devDependencies when production', (): Promise<void> => {
-    const isProduction: $FlowFixMe = require('../../src/constants').isProduction;
-    isProduction.mockReturnValue(true);
-
-    return runList([], {}, 'dev-deps-prod', (config, reporter): ?Promise<void> => {
+    return runList([], {production: true}, 'dev-deps-prod', (config, reporter): ?Promise<void> => {
       expect(reporter.getBuffer()).toMatchSnapshot();
     });
   });
diff --git a/__tests__/commands/remove.js b/__tests__/commands/remove.js
index c2b71ae..40ef91e 100644
--- a/__tests__/commands/remove.js
+++ b/__tests__/commands/remove.js
@@ -171,3 +171,31 @@ test.concurrent('removes from workspace packages', async () => {
     expect(lockFileLines[0]).toEqual('left-pad at 1.1.3:');
   });
 });
+
+test.concurrent('preserves unaffected bin links after removing workspace packages', async () => {
+  await runInstall({binLinks: true}, 'workspaces-install-bin', async (config): Promise<void> => {
+    const reporter = new ConsoleReporter({});
+
+    expect(await fs.exists(`${config.cwd}/node_modules/.bin/rimraf`)).toEqual(true);
+    expect(await fs.exists(`${config.cwd}/node_modules/.bin/touch`)).toEqual(true);
+    expect(await fs.exists(`${config.cwd}/node_modules/.bin/workspace-1`)).toEqual(true);
+    expect(await fs.exists(`${config.cwd}/packages/workspace-2/node_modules/.bin/rimraf`)).toEqual(true);
+    expect(await fs.exists(`${config.cwd}/packages/workspace-2/node_modules/.bin/workspace-1`)).toEqual(true);
+
+    // remove package
+    const childConfig = await makeConfigFromDirectory(`${config.cwd}/packages/workspace-1`, reporter, {binLinks: true});
+    await remove(childConfig, reporter, {}, ['left-pad']);
+    await check(childConfig, reporter, {verifyTree: true}, []);
+
+    expect(
+      JSON.parse(await fs.readFile(path.join(config.cwd, 'packages/workspace-1/package.json'))).devDependencies,
+    ).toEqual({});
+
+    // bin links should be preserved
+    expect(await fs.exists(`${config.cwd}/node_modules/.bin/rimraf`)).toEqual(true);
+    expect(await fs.exists(`${config.cwd}/node_modules/.bin/touch`)).toEqual(true);
+    expect(await fs.exists(`${config.cwd}/node_modules/.bin/workspace-1`)).toEqual(true);
+    expect(await fs.exists(`${config.cwd}/packages/workspace-2/node_modules/.bin/rimraf`)).toEqual(true);
+    expect(await fs.exists(`${config.cwd}/packages/workspace-2/node_modules/.bin/workspace-1`)).toEqual(true);
+  });
+});
diff --git a/__tests__/commands/upgrade.js b/__tests__/commands/upgrade.js
index 166cc98..c70a327 100644
--- a/__tests__/commands/upgrade.js
+++ b/__tests__/commands/upgrade.js
@@ -404,3 +404,9 @@ test.concurrent('upgrade to workspace child preserves root dependencies', (): Pr
     expect(childBPkg.dependencies['right-pad']).toEqual('1.0.0');
   });
 });
+
+test.concurrent('latest flag does not downgrade from a beta', (): Promise<void> => {
+  return runUpgrade([], {latest: true}, 'using-beta', async (config): ?Promise<void> => {
+    await expectInstalledDependency(config, 'react-refetch', '^1.0.3-0', '1.0.3-0');
+  });
+});
diff --git a/__tests__/config.js b/__tests__/config.js
new file mode 100644
index 0000000..b188305
--- /dev/null
+++ b/__tests__/config.js
@@ -0,0 +1,44 @@
+/* @flow */
+
+import Config from '../src/config.js';
+import {ConsoleReporter} from '../src/reporters/index.js';
+
+const stream = require('stream');
+
+const initConfig = async cfg => {
+  const stdout = new stream.Writable({
+    decodeStrings: false,
+    write(data, encoding, cb) {
+      cb();
+    },
+  });
+
+  const reporter = new ConsoleReporter({stdout, stderr: stdout});
+  const config = new Config(reporter);
+  await config.init(cfg);
+  return config;
+};
+
+test('getOption changes ~ to cwd when resolve=true', async () => {
+  const config = await initConfig({});
+  config.registries.yarn.config.cafile = '~/';
+  expect(config.getOption('cafile', true)).not.toContain('~');
+});
+
+test('getOption does not change ~ when resolve=false', async () => {
+  const config = await initConfig({});
+  config.registries.yarn.config.cafile = '~/';
+  expect(config.getOption('cafile', false)).toEqual('~/');
+});
+
+test('getOption does not change empty-string when resolve=true', async () => {
+  const config = await initConfig({});
+  config.registries.yarn.config.cafile = '';
+  expect(config.getOption('cafile', true)).toEqual('');
+});
+
+test('getOption does not change empty-string when resolve=false', async () => {
+  const config = await initConfig({});
+  config.registries.yarn.config.cafile = '';
+  expect(config.getOption('cafile', false)).toEqual('');
+});
diff --git a/__tests__/constants.js b/__tests__/constants.js
index 456f30f..1a36165 100644
--- a/__tests__/constants.js
+++ b/__tests__/constants.js
@@ -1,6 +1,6 @@
 /* @flow */
 
-import {getPathKey, isProduction} from '../src/constants.js';
+import {getPathKey} from '../src/constants.js';
 
 test('getPathKey', () => {
   expect(getPathKey('win32', {PATH: 'foobar'})).toBe('PATH');
@@ -10,18 +10,3 @@ test('getPathKey', () => {
   expect(getPathKey('linux', {})).toBe('PATH');
   expect(getPathKey('darwin', {})).toBe('PATH');
 });
-
-describe('isProduction', () => {
-  const env = {
-    NODE_ENV: '',
-  };
-  test('passing "development" should return false', () => {
-    env.NODE_ENV = 'development';
-    expect(isProduction(env)).toBe(false);
-  });
-
-  test('passing "production" should return true', () => {
-    env.NODE_ENV = 'production';
-    expect(isProduction(env)).toBe(true);
-  });
-});
diff --git a/__tests__/fixtures/add/add-already-added-dependency/package.json b/__tests__/fixtures/add/add-already-added-dependency/package.json
new file mode 100644
index 0000000..ea7c7db
--- /dev/null
+++ b/__tests__/fixtures/add/add-already-added-dependency/package.json
@@ -0,0 +1,7 @@
+{
+  "name": "foo",
+  "version": "1.0.0",
+  "dependencies": {
+    "is-online": "^7.0.0"
+  }
+}
diff --git a/__tests__/fixtures/add/add-already-added-dev-dependency/package.json b/__tests__/fixtures/add/add-already-added-dev-dependency/package.json
new file mode 100644
index 0000000..18b9223
--- /dev/null
+++ b/__tests__/fixtures/add/add-already-added-dev-dependency/package.json
@@ -0,0 +1,7 @@
+{
+  "name": "foo",
+  "version": "1.0.0",
+  "devDependencies": {
+    "is-online": "^7.0.0"
+  }
+}
diff --git a/__tests__/fixtures/init/init-config/.yarnrc b/__tests__/fixtures/init/init-config/.yarnrc
new file mode 100644
index 0000000..99cf7bd
--- /dev/null
+++ b/__tests__/fixtures/init/init-config/.yarnrc
@@ -0,0 +1,6 @@
+init-private true
+init-license BSD
+init-version "0.0.0-alpha"
+init-author-name "Kittens McKitty"
+init-author-email "yarn at yarnpkg.com"
+init-author-url "https://yarnpkg.com"
diff --git a/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/.npmrc b/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/.npmrc
deleted file mode 100644
index 9465b97..0000000
--- a/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/.npmrc
+++ /dev/null
@@ -1 +0,0 @@
-yarn-offline-mirror=./mirror-for-offline
diff --git a/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/dir-to-link/package.json b/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/dir-to-link/package.json
deleted file mode 100644
index 5de00be..0000000
--- a/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/dir-to-link/package.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "name": "@fakescope/fake-dependency",
-  "description": "Symlinked scoped package test",
-  "version": "1.0.1",
-  "dependencies": {},
-  "license": "MIT"
-}
diff --git a/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/mirror-for-offline/@fakescope-fake-dependency-1.0.1.tgz b/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/mirror-for-offline/@fakescope-fake-dependency-1.0.1.tgz
deleted file mode 100644
index 29e48dd..0000000
Binary files a/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/mirror-for-offline/@fakescope-fake-dependency-1.0.1.tgz and /dev/null differ
diff --git a/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/package.json b/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/package.json
deleted file mode 100644
index a0cf66c..0000000
--- a/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/package.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-  "dependencies": {
-    "@fakescope/fake-dependency": "1.0.1"
-  }
-}
diff --git a/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/yarn.lock b/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/yarn.lock
deleted file mode 100644
index ca46137..0000000
--- a/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/yarn.lock
+++ /dev/null
@@ -1,5 +0,0 @@
-# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
-# yarn lockfile v1
-"@fakescope/fake-dependency at 1.0.1":
-  version "1.0.1"
-  resolved "@fakescope-fake-dependency-1.0.1.tgz#477dafd486d856af0b3faf5a5f1c895001221609"
diff --git a/__tests__/fixtures/install/install-dont-overwrite-linked/package.json b/__tests__/fixtures/install/install-dont-overwrite-linked/package.json
new file mode 100644
index 0000000..86bcb81
--- /dev/null
+++ b/__tests__/fixtures/install/install-dont-overwrite-linked/package.json
@@ -0,0 +1,8 @@
+{
+  "dependencies": {
+    "left-pad": "1.1.3"
+  },
+  "devDependencies": {
+    "is-buffer": "^1.1.5"
+  }
+}
diff --git a/__tests__/fixtures/install/install-with-comments/package.json b/__tests__/fixtures/install/install-with-comments/package.json
index 01dea4a..60647f1 100644
--- a/__tests__/fixtures/install/install-with-comments/package.json
+++ b/__tests__/fixtures/install/install-with-comments/package.json
@@ -6,5 +6,8 @@
       "comment"
     ],
     "foo": "file:bar"
+  },
+  "resolutions": {
+    "//": "This is a comment."
   }
 }
diff --git a/__tests__/fixtures/install/resolutions/frozen-lockfile/package.json b/__tests__/fixtures/install/resolutions/frozen-lockfile/package.json
new file mode 100644
index 0000000..8c1b9a3
--- /dev/null
+++ b/__tests__/fixtures/install/resolutions/frozen-lockfile/package.json
@@ -0,0 +1,11 @@
+{
+  "name": "project",
+  "version": "1.0.0",
+  "dependencies": {
+    "left-pad": "^1.0.0",
+    "d2": "file:../d2-1"
+  },
+  "resolutions": {
+    "left-pad": "^1.0.0"
+  }
+}
diff --git a/__tests__/fixtures/install/resolutions/frozen-lockfile/yarn.lock b/__tests__/fixtures/install/resolutions/frozen-lockfile/yarn.lock
new file mode 100644
index 0000000..a7b5307
--- /dev/null
+++ b/__tests__/fixtures/install/resolutions/frozen-lockfile/yarn.lock
@@ -0,0 +1,12 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"d2 at file:../d2-1":
+  version "1.0.0"
+  dependencies:
+    left-pad "^1.0.0"
+
+left-pad@^1.0.0:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.1.3.tgz#612f61c033f3a9e08e939f1caebeea41b6f3199a"
diff --git a/__tests__/fixtures/install/workspaces-install-bin/file.js b/__tests__/fixtures/install/workspaces-install-bin/file.js
new file mode 100644
index 0000000..e69de29
diff --git a/__tests__/fixtures/install/workspaces-install-bin/package.json b/__tests__/fixtures/install/workspaces-install-bin/package.json
new file mode 100644
index 0000000..3f03c73
--- /dev/null
+++ b/__tests__/fixtures/install/workspaces-install-bin/package.json
@@ -0,0 +1,12 @@
+{
+  "name": "my-project",
+  "private": true,
+  "bin": {"file": "file.js"},
+  "dependencies": {},
+  "devDependencies": {
+    "touch": "^1.0.0"
+  },
+  "workspaces": [
+    "packages/*"
+  ]
+}
diff --git a/__tests__/fixtures/install/workspaces-install-bin/packages/workspace-1/bin.js b/__tests__/fixtures/install/workspaces-install-bin/packages/workspace-1/bin.js
new file mode 100755
index 0000000..e69de29
diff --git a/__tests__/fixtures/install/workspaces-install-bin/packages/workspace-1/package.json b/__tests__/fixtures/install/workspaces-install-bin/packages/workspace-1/package.json
new file mode 100644
index 0000000..216d9c1
--- /dev/null
+++ b/__tests__/fixtures/install/workspaces-install-bin/packages/workspace-1/package.json
@@ -0,0 +1,8 @@
+{
+  "name": "workspace-1",
+  "version": "1.0.0",
+  "bin": "./bin.js",
+  "devDependencies": {
+    "left-pad": "1.0.0"
+  }
+}
diff --git a/__tests__/fixtures/install/workspaces-install-bin/packages/workspace-2/package.json b/__tests__/fixtures/install/workspaces-install-bin/packages/workspace-2/package.json
new file mode 100644
index 0000000..99b6e83
--- /dev/null
+++ b/__tests__/fixtures/install/workspaces-install-bin/packages/workspace-2/package.json
@@ -0,0 +1,10 @@
+{
+  "name": "workspace-2",
+  "version": "1.0.0",
+  "dependencies": {
+    "workspace-1": "^1.0.0"
+  },
+  "devDependencies": {
+    "rimraf": "^2.6.2"
+  }
+}
diff --git a/__tests__/fixtures/install/workspaces-install-bin/yarn.lock b/__tests__/fixtures/install/workspaces-install-bin/yarn.lock
new file mode 100644
index 0000000..eccf796
--- /dev/null
+++ b/__tests__/fixtures/install/workspaces-install-bin/yarn.lock
@@ -0,0 +1,90 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+abbrev at 1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
+
+balanced-match@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
+
+brace-expansion@^1.1.7:
+  version "1.1.8"
+  resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292"
+  dependencies:
+    balanced-match "^1.0.0"
+    concat-map "0.0.1"
+
+concat-map at 0.0.1:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+
+fs.realpath@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+
+glob@^7.0.5:
+  version "7.1.2"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
+  dependencies:
+    fs.realpath "^1.0.0"
+    inflight "^1.0.4"
+    inherits "2"
+    minimatch "^3.0.4"
+    once "^1.3.0"
+    path-is-absolute "^1.0.0"
+
+inflight@^1.0.4:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+  dependencies:
+    once "^1.3.0"
+    wrappy "1"
+
+inherits at 2:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
+
+left-pad at 1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.0.0.tgz#c84e2417581bbb8eaf2b9e3d7a122e572ab1af37"
+
+minimatch@^3.0.4:
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
+  dependencies:
+    brace-expansion "^1.1.7"
+
+nopt@~1.0.10:
+  version "1.0.10"
+  resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
+  dependencies:
+    abbrev "1"
+
+once@^1.3.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+  dependencies:
+    wrappy "1"
+
+path-is-absolute@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+
+rimraf@^2.6.2:
+  version "2.6.2"
+  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
+  dependencies:
+    glob "^7.0.5"
+
+touch@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/touch/-/touch-1.0.0.tgz#449cbe2dbae5a8c8038e30d71fa0ff464947c4de"
+  dependencies:
+    nopt "~1.0.10"
+
+wrappy at 1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
diff --git a/__tests__/fixtures/link/package-with-name-scoped/package.json b/__tests__/fixtures/link/package-with-name-scoped/package.json
new file mode 100644
index 0000000..203ee2b
--- /dev/null
+++ b/__tests__/fixtures/link/package-with-name-scoped/package.json
@@ -0,0 +1,3 @@
+{
+  "name": "@fakescope/a-package"
+}
diff --git a/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz.bin b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz.bin
new file mode 100644
index 0000000..ee73ef8
Binary files /dev/null and b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz.bin differ
diff --git a/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/hoist-non-react-statics.bin b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/hoist-non-react-statics.bin
new file mode 100644
index 0000000..b51a414
Binary files /dev/null and b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/hoist-non-react-statics.bin differ
diff --git a/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz.bin b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz.bin
new file mode 100644
index 0000000..14c5ea8
Binary files /dev/null and b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz.bin differ
diff --git a/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz.bin b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz.bin
new file mode 100644
index 0000000..c82cb7c
Binary files /dev/null and b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz.bin differ
diff --git a/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/react-refetch.bin b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/react-refetch.bin
new file mode 100644
index 0000000..81e4904
Binary files /dev/null and b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/react-refetch.bin differ
diff --git a/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/react-refetch/-/react-refetch-1.0.1.tgz.bin b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/react-refetch/-/react-refetch-1.0.1.tgz.bin
new file mode 100644
index 0000000..02c66e9
Binary files /dev/null and b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/react-refetch/-/react-refetch-1.0.1.tgz.bin differ
diff --git a/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/react-refetch/-/react-refetch-1.0.3-0.tgz.bin b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/react-refetch/-/react-refetch-1.0.3-0.tgz.bin
new file mode 100644
index 0000000..7157ab0
Binary files /dev/null and b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/react-refetch/-/react-refetch-1.0.3-0.tgz.bin differ
diff --git a/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz.bin b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz.bin
new file mode 100644
index 0000000..a13b93d
Binary files /dev/null and b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz.bin differ
diff --git a/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/ui-select.bin b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/ui-select.bin
new file mode 100644
index 0000000..12d49f7
Binary files /dev/null and b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/ui-select.bin differ
diff --git a/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/ui-select/-/ui-select-0.19.8.tgz.bin b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/ui-select/-/ui-select-0.19.8.tgz.bin
new file mode 100644
index 0000000..0d28de6
Binary files /dev/null and b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/ui-select/-/ui-select-0.19.8.tgz.bin differ
diff --git a/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/ui-select/-/ui-select-0.20.0.tgz.bin b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/ui-select/-/ui-select-0.20.0.tgz.bin
new file mode 100644
index 0000000..fc9052b
Binary files /dev/null and b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/ui-select/-/ui-select-0.20.0.tgz.bin differ
diff --git a/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/warning.bin b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/warning.bin
new file mode 100644
index 0000000..f70b696
Binary files /dev/null and b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/warning.bin differ
diff --git a/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/warning/-/warning-2.1.0.tgz.bin b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/warning/-/warning-2.1.0.tgz.bin
new file mode 100644
index 0000000..9a4176f
Binary files /dev/null and b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/warning/-/warning-2.1.0.tgz.bin differ
diff --git a/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/warning/-/warning-3.0.0.tgz.bin b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/warning/-/warning-3.0.0.tgz.bin
new file mode 100644
index 0000000..9f8a822
Binary files /dev/null and b/__tests__/fixtures/request-cache/GET/registry.yarnpkg.com/warning/-/warning-3.0.0.tgz.bin differ
diff --git a/__tests__/fixtures/upgrade/using-beta/package.json b/__tests__/fixtures/upgrade/using-beta/package.json
new file mode 100644
index 0000000..8d9f8d9
--- /dev/null
+++ b/__tests__/fixtures/upgrade/using-beta/package.json
@@ -0,0 +1,5 @@
+{
+  "dependencies": {
+    "react-refetch": "^1.0.3-0"
+  }
+}
diff --git a/__tests__/fixtures/upgrade/using-beta/yarn.lock b/__tests__/fixtures/upgrade/using-beta/yarn.lock
new file mode 100644
index 0000000..ca571fb
--- /dev/null
+++ b/__tests__/fixtures/upgrade/using-beta/yarn.lock
@@ -0,0 +1,42 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+hoist-non-react-statics@^2.0.0:
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0"
+
+invariant@^2.0.0:
+  version "2.2.2"
+  resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360"
+  dependencies:
+    loose-envify "^1.0.0"
+
+js-tokens@^3.0.0:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
+
+lodash@^4.11.0:
+  version "4.17.4"
+  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
+
+loose-envify@^1.0.0:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
+  dependencies:
+    js-tokens "^3.0.0"
+
+react-refetch@^1.0.3-0:
+  version "1.0.3-0"
+  resolved "https://registry.yarnpkg.com/react-refetch/-/react-refetch-1.0.3-0.tgz#e1e71bd98282e468b59aae05239d8a9523494f19"
+  dependencies:
+    hoist-non-react-statics "^2.0.0"
+    invariant "^2.0.0"
+    lodash "^4.11.0"
+    warning "^3.0.0"
+
+warning@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c"
+  dependencies:
+    loose-envify "^1.0.0"
diff --git a/__tests__/package-request.js b/__tests__/package-request.js
index 814ceed..7b9afae 100644
--- a/__tests__/package-request.js
+++ b/__tests__/package-request.js
@@ -32,7 +32,7 @@ async function prepareRequest(pattern: string, version: string, resolved: string
   return {request, reporter};
 }
 
-test('Produce valid remote type for a git private dep', async () => {
+test('Produce valid remote type for a git-over-ssh dep', async () => {
   const {request, reporter} = await prepareRequest(
     'private-dep at github:yarnpkg/private-dep#1.0.0',
     '1.0.0',
@@ -45,6 +45,32 @@ test('Produce valid remote type for a git private dep', async () => {
   await reporter.close();
 });
 
+test('Produce valid remote type for a git-over-https dep', async () => {
+  const {request, reporter} = await prepareRequest(
+    'public-dep at yarnpkg/public-dep#1.0.0',
+    '1.0.0',
+    'git+https://github.com/yarnpkg/public-dep#1fde368',
+  );
+
+  expect(request.getLocked('git')._remote.type).toBe('git');
+  expect(request.getLocked('tarball')._remote.type).toBe('git');
+
+  await reporter.close();
+});
+
+test('Produce valid remote type for a git public dep', async () => {
+  const {request, reporter} = await prepareRequest(
+    'public-dep at yarnpkg/public-dep#1fde368',
+    '1.0.0',
+    'https://codeload.github.com/yarnpkg/public-dep/tar.gz/1fde368',
+  );
+
+  expect(request.getLocked('git')._remote.type).toBe('git');
+  expect(request.getLocked('tarball')._remote.type).toBe('tarball');
+
+  await reporter.close();
+});
+
 test('Check parentNames flowing in the request', async () => {
   const {request: parentRequest, reporter: parentReporter} = await prepareRequest(
     'parent at 1.0.0',
@@ -63,16 +89,3 @@ test('Check parentNames flowing in the request', async () => {
   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',
-    '1.0.0',
-    'https://codeload.github.com/yarnpkg/public-dep/tar.gz/1fde368',
-  );
-
-  expect(request.getLocked('git')._remote.type).toBe('git');
-  expect(request.getLocked('tarball')._remote.type).toBe('tarball');
-
-  await reporter.close();
-});
diff --git a/__tests__/registries/npm-registry.js b/__tests__/registries/npm-registry.js
index bf28d73..ab51e1b 100644
--- a/__tests__/registries/npm-registry.js
+++ b/__tests__/registries/npm-registry.js
@@ -4,7 +4,7 @@ import {resolve, join as pathJoin} from 'path';
 
 import NpmRegistry from '../../src/registries/npm-registry.js';
 import {BufferReporter} from '../../src/reporters/index.js';
-import homeDir from '../../src/util/user-home-dir.js';
+import homeDir, {home} from '../../src/util/user-home-dir.js';
 
 describe('normalizeConfig', () => {
   beforeAll(() => {
@@ -40,6 +40,18 @@ describe('normalizeConfig', () => {
     const normalized = NpmRegistry.normalizeConfig({cafile: rooted})['cafile'];
     expect(normalized).toEqual(rooted);
   });
+
+  test('handles missing HOME', () => {
+    const realHome = process.env.HOME;
+    delete process.env.HOME;
+
+    try {
+      const normalized = NpmRegistry.normalizeConfig({cafile: '${HOME}/foo'})['cafile'];
+      expect(normalized).toEqual(resolve(home, 'foo'));
+    } finally {
+      process.env.HOME = realHome;
+    }
+  });
 });
 
 function createMocks(): Object {
diff --git a/__tests__/util/git/git-spawn.js b/__tests__/util/git/git-spawn.js
index 22b2b8e..7054488 100644
--- a/__tests__/util/git/git-spawn.js
+++ b/__tests__/util/git/git-spawn.js
@@ -34,6 +34,7 @@ describe('spawn', () => {
       GIT_ASKPASS: '',
       GIT_TERMINAL_PROMPT: 0,
       GIT_SSH_COMMAND: '"ssh" -oBatchMode=yes',
+      GIT_SSH_VARIANT: 'ssh',
       ...process.env,
     });
   });
@@ -53,6 +54,7 @@ describe('spawn', () => {
       GIT_ASKPASS: '',
       GIT_TERMINAL_PROMPT: 0,
       GIT_SSH_COMMAND: `"${plinkPath}" -batch`,
+      GIT_SSH_VARIANT: 'plink',
       ...process.env,
     });
   });
diff --git a/circle.yml b/circle.yml
deleted file mode 100644
index bfde41f..0000000
--- a/circle.yml
+++ /dev/null
@@ -1,57 +0,0 @@
-general:
-  branches:
-    ignore:
-      - gh-pages
-  artifacts:
-    - "artifacts/"
-
-machine:
-  node:
-    version: 8
-
-dependencies:
-  cache_directories:
-    - "~/.cache/yarn"
-
-  override:
-    - which node
-
-    # install dependencies
-    - ./scripts/bootstrap-env-ubuntu.sh
-
-    - yarn install
-
-test:
-  override:
-    - node -v
-    - >
-      if [ "$CIRCLE_BRANCH" == 'master' ]; then
-        ./scripts/set-dev-version.js
-      fi;
-    - yarn lint
-    # Limit maxWorkers to 3 to avoid OOM on CircleCI
-    - yarn test-ci -- -- --maxWorkers 3
-    - yarn check-lockfile
-    - yarn run build-dist
-    - yarn run build-deb
-
-    # Test that installing as root works and that it also works
-    # behind a user namespace which Circle CI tests are run under
-    - sudo env "PATH=$PATH" bin/yarn install --force
-
-deployment:
-  release:
-    tag: /v[0-9]+(\.[0-9]+)*/
-    owner: yarnpkg
-    commands:
-      # Only NPM is handled here - All other release files are handled in a webhook.
-      - echo "Releasing $CIRCLE_TAG..."
-      - echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
-      - ./scripts/update-npm.sh
-
-notify:
-  webhooks:
-    # Handles uploading stable/RC releases to GitHub
-    - url: https://nightly.yarnpkg.com/release_circleci
-    # Handles archiving all builds onto the nightly build site
-    - url: https://nightly.yarnpkg.com/archive_circleci
diff --git a/package.json b/package.json
index 43ac51b..18b73e1 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "yarn",
   "installationMethod": "unknown",
-  "version": "1.2.1",
+  "version": "1.3.2",
   "license": "BSD-2-Clause",
   "preferGlobal": true,
   "description": "📦🐈 Fast, reliable, and secure dependency management.",
diff --git a/src/cli/commands/add.js b/src/cli/commands/add.js
index 20309a1..a2f315c 100644
--- a/src/cli/commands/add.js
+++ b/src/cli/commands/add.js
@@ -195,6 +195,10 @@ export class Add extends Install {
 
       object[target] = object[target] || {};
       object[target][pkg.name] = version;
+
+      if (target !== this.flagToOrigin) {
+        this.reporter.warn(this.reporter.lang('moduleAlreadyInManifest', pkg.name, depType, this.flagToOrigin));
+      }
     }
 
     await this.config.saveRootManifests(manifests);
diff --git a/src/cli/commands/autoclean.js b/src/cli/commands/autoclean.js
index e97e65d..178aee3 100644
--- a/src/cli/commands/autoclean.js
+++ b/src/cli/commands/autoclean.js
@@ -40,6 +40,11 @@ Gulpfile.js
 Gruntfile.js
 
 # configs
+appveyor.yml
+circle.yml
+codeship-services.yml
+codeship-steps.yml
+wercker.yml
 .tern-project
 .gitattributes
 .editorconfig
@@ -49,8 +54,7 @@ Gruntfile.js
 .flowconfig
 .documentup.json
 .yarn-metadata.json
-.*.yml
-*.yml
+.travis.yml
 
 # misc
 *.md
diff --git a/src/cli/commands/create.js b/src/cli/commands/create.js
index 19e8b6a..e3983d7 100644
--- a/src/cli/commands/create.js
+++ b/src/cli/commands/create.js
@@ -29,5 +29,5 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
   const binFolder = await getBinFolder(config, {});
   const command = path.resolve(binFolder, path.basename(commandName));
 
-  await child.spawn(command, [...rest], {stdio: `inherit`, shell: true});
+  await child.spawn(command, rest, {stdio: `inherit`, shell: true});
 }
diff --git a/src/cli/commands/info.js b/src/cli/commands/info.js
index b89bac0..db1f6e8 100644
--- a/src/cli/commands/info.js
+++ b/src/cli/commands/info.js
@@ -72,7 +72,7 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
   const versions = result.versions;
   // $FlowFixMe
   result.versions = Object.keys(versions).sort(semver.compareLoose);
-  result.version = version || result.versions[result.versions.length - 1];
+  result.version = version || result['dist-tags'].latest;
   result = Object.assign(result, versions[result.version]);
 
   const fieldPath = args.shift();
diff --git a/src/cli/commands/init.js b/src/cli/commands/init.js
index 7d568aa..85f5951 100644
--- a/src/cli/commands/init.js
+++ b/src/cli/commands/init.js
@@ -96,7 +96,7 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
     {
       key: 'private',
       question: 'private',
-      default: '',
+      default: config.getOption('init-private') || '',
       inputFormatter: yn,
     },
   ];
@@ -129,7 +129,7 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
     }
 
     if (def) {
-      question += ` (${def.toString()})`;
+      question += ` (${String(def)})`;
     }
 
     let answer;
diff --git a/src/cli/commands/install.js b/src/cli/commands/install.js
index 3488b80..66c8986 100644
--- a/src/cli/commands/install.js
+++ b/src/cli/commands/install.js
@@ -424,6 +424,12 @@ export class Install {
       return false;
     }
 
+    if (match.hardRefreshRequired) {
+      // e.g. node version doesn't match, force script installations
+      this.scripts.setForce(true);
+      return false;
+    }
+
     if (!patterns.length && !match.integrityFileMissing) {
       this.reporter.success(this.reporter.lang('nothingToInstall'));
       await this.createEmptyManifestFolders();
@@ -504,6 +510,7 @@ export class Install {
 
     steps.push(async (curr: number, total: number) => {
       this.reporter.step(curr, total, this.reporter.lang('resolvingPackages'), emoji.get('mag'));
+      this.resolutionMap.setTopLevelPatterns(rawPatterns);
       await this.resolver.init(this.prepareRequests(depRequests), {
         isFlat: this.flags.flat,
         isFrozen: this.flags.frozenLockfile,
diff --git a/src/cli/commands/list.js b/src/cli/commands/list.js
index 41d913c..ba29a13 100644
--- a/src/cli/commands/list.js
+++ b/src/cli/commands/list.js
@@ -8,7 +8,6 @@ import type {Tree, Trees} from '../../reporters/types.js';
 import {Install} from './install.js';
 
 import Lockfile from '../../lockfile';
-import {isProduction} from '../../constants';
 
 const invariant = require('invariant');
 const micromatch = require('micromatch');
@@ -199,7 +198,7 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
   });
 
   let activePatterns = [];
-  if (isProduction()) {
+  if (config.production) {
     const devDeps = getDevDeps(manifest);
     activePatterns = patterns.filter(pattern => !devDeps.has(pattern));
   } else {
diff --git a/src/cli/commands/run.js b/src/cli/commands/run.js
index f3c941a..e18d033 100644
--- a/src/cli/commands/run.js
+++ b/src/cli/commands/run.js
@@ -74,8 +74,8 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
     }
 
     if (cmds.length) {
-      // propagate YARN_SILENT env variable to executed commands
-      process.env.YARN_SILENT = '1';
+      // Disable wrapper in executed commands
+      process.env.YARN_WRAP_OUTPUT = 'false';
       for (const [stage, cmd] of cmds) {
         // only tack on trailing arguments for default script, ignore for pre and post - #1595
         const cmdWithArgs = stage === action ? sh`${unquoted(cmd)} ${args}` : cmd;
diff --git a/src/cli/commands/upgrade.js b/src/cli/commands/upgrade.js
index c7d6886..ac1ff97 100644
--- a/src/cli/commands/upgrade.js
+++ b/src/cli/commands/upgrade.js
@@ -199,7 +199,6 @@ export async function getOutdated(
 ): Promise<Array<Dependency>> {
   const install = new Install(flags, config, reporter, lockfile);
   const outdatedFieldName = flags.latest ? 'latest' : 'wanted';
-  const updateAll = patterns.length === 0;
 
   // ensure scope is of the form `@scope/`
   const normalizeScope = function() {
@@ -227,15 +226,7 @@ export async function getOutdated(
 
   normalizeScope();
 
-  const deps = (await PackageRequest.getOutdatedPackages(
-    lockfile,
-    install,
-    config,
-    reporter,
-    patterns,
-    flags,
-    updateAll,
-  ))
+  const deps = (await PackageRequest.getOutdatedPackages(lockfile, install, config, reporter, patterns, flags))
     .filter(versionFilter)
     .filter(scopeFilter.bind(this, flags));
   deps.forEach(dep => {
diff --git a/src/cli/index.js b/src/cli/index.js
index 1b3d4c9..5024530 100644
--- a/src/cli/index.js
+++ b/src/cli/index.js
@@ -42,6 +42,14 @@ function findProjectRoot(base: string): string {
 
 const boolify = val => val.toString().toLowerCase() !== 'false' && val !== '0';
 
+function boolifyWithDefault(val: any, defaultResult: boolean): boolean {
+  if (val === undefined || val === null || val === '') {
+    return defaultResult;
+  } else {
+    return boolify(val);
+  }
+}
+
 export function main({
   startArgs,
   args,
@@ -164,7 +172,7 @@ export function main({
 
   let warnAboutRunDashDash = false;
   // we are using "yarn <script> -abc" or "yarn run <script> -abc", we want -abc to be script options, not yarn options
-  if (command === commands.run) {
+  if (command === commands.run || command === commands.create) {
     if (endArgs.length === 0) {
       endArgs = ['--', ...args.splice(1)];
     } else {
@@ -196,7 +204,7 @@ export function main({
     emoji: process.stdout.isTTY && commander.emoji,
     verbose: commander.verbose,
     noProgress: !commander.progress,
-    isSilent: process.env.YARN_SILENT === '1' || commander.silent,
+    isSilent: boolifyWithDefault(process.env.YARN_SILENT, false) || commander.silent,
   });
 
   const exit = exitCode => {
@@ -207,9 +215,10 @@ export function main({
   reporter.initPeakMemoryCounter();
 
   const config = new Config(reporter);
-  const outputWrapper = !commander.json && command.hasWrapper(commander, commander.args);
+  const outputWrapperEnabled = boolifyWithDefault(process.env.YARN_WRAP_OUTPUT, true);
+  const shouldWrapOutput = outputWrapperEnabled && !commander.json && command.hasWrapper(commander, commander.args);
 
-  if (outputWrapper) {
+  if (shouldWrapOutput) {
     reporter.header(commandName, {name: 'yarn', version});
   }
 
@@ -243,7 +252,7 @@ export function main({
     }
 
     return command.run(config, reporter, commander, commander.args).then(exitCode => {
-      if (outputWrapper) {
+      if (shouldWrapOutput) {
         reporter.footer(false);
       }
       return exitCode;
@@ -541,7 +550,7 @@ async function start(): Promise<void> {
   const rc = getRcConfigForCwd(process.cwd());
   const yarnPath = rc['yarn-path'];
 
-  if (yarnPath && process.env.YARN_IGNORE_PATH !== '1') {
+  if (yarnPath && !boolifyWithDefault(process.env.YARN_IGNORE_PATH, false)) {
     const argv = process.argv.slice(2);
     const opts = {stdio: 'inherit', env: Object.assign({}, process.env, {YARN_IGNORE_PATH: 1})};
     let exitCode = 0;
diff --git a/src/config.js b/src/config.js
index 3a70831..7d6f084 100644
--- a/src/config.js
+++ b/src/config.js
@@ -195,7 +195,7 @@ export default class Config {
   getOption(key: string, resolve: boolean = false): mixed {
     const value = this.registries.yarn.getOption(key);
 
-    if (resolve && typeof value === 'string') {
+    if (resolve && typeof value === 'string' && value.length) {
       return resolveWithHome(value);
     }
 
diff --git a/src/constants.js b/src/constants.js
index 40c82b6..62c980c 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -10,8 +10,10 @@ type Env = {
 };
 
 export const DEPENDENCY_TYPES = ['devDependencies', 'dependencies', 'optionalDependencies', 'peerDependencies'];
+export const RESOLUTIONS = 'resolutions';
+export const MANIFEST_FIELDS = [RESOLUTIONS, ...DEPENDENCY_TYPES];
 
-export const SUPPORTED_NODE_VERSIONS = '^4.8.0 || ^5.7.0 || ^6.2.2 || ^8.0.0';
+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';
 
@@ -59,7 +61,12 @@ function getPreferredCacheDirectories(): Array<string> {
     preferredCacheDirectories.push(getDirectory('cache'));
   }
 
-  preferredCacheDirectories.push(path.join(os.tmpdir(), '.yarn-cache'));
+  if (process.getuid) {
+    // $FlowFixMe: process.getuid exists, dammit
+    preferredCacheDirectories.push(path.join(os.tmpdir(), `.yarn-cache-${process.getuid()}`));
+  }
+
+  preferredCacheDirectories.push(path.join(os.tmpdir(), `.yarn-cache`));
 
   return preferredCacheDirectories;
 }
@@ -101,10 +108,6 @@ export const SINGLE_INSTANCE_FILENAME = '.yarn-single-instance';
 
 export const ENV_PATH_KEY = getPathKey(process.platform, process.env);
 
-export function isProduction(env: Object = process.env): boolean {
-  return env.NODE_ENV === 'production';
-}
-
 export function getPathKey(platform: string, env: Env): string {
   let pathKey = 'PATH';
 
diff --git a/src/integrity-checker.js b/src/integrity-checker.js
index 7e0e0ad..3dcd33c 100644
--- a/src/integrity-checker.js
+++ b/src/integrity-checker.js
@@ -19,6 +19,7 @@ export const integrityErrors = {
   LINKED_MODULES_DONT_MATCH: 'integrityCheckLinkedModulesDontMatch',
   PATTERNS_DONT_MATCH: 'integrityPatternsDontMatch',
   MODULES_FOLDERS_MISSING: 'integrityModulesFoldersMissing',
+  NODE_VERSION_DOESNT_MATCH: 'integrityNodeDoesntMatch',
 };
 
 type IntegrityError = $Keys<typeof integrityErrors>;
@@ -37,6 +38,7 @@ type IntegrityHashLocation = {
 };
 
 type IntegrityFile = {
+  nodeVersion: string,
   flags: Array<string>,
   modulesFolders: Array<string>,
   linkedModules: Array<string>,
@@ -54,6 +56,7 @@ type IntegrityFlags = {
 };
 
 const INTEGRITY_FILE_DEFAULTS = () => ({
+  nodeVersion: process.version,
   modulesFolders: [],
   flags: [],
   linkedModules: [],
@@ -295,6 +298,10 @@ export default class InstallationIntegrityChecker {
       return 'LINKED_MODULES_DONT_MATCH';
     }
 
+    if (actual.nodeVersion !== expected.nodeVersion) {
+      return 'NODE_VERSION_DOESNT_MATCH';
+    }
+
     let relevantExpectedFlags = expected.flags.slice();
 
     // If we run "yarn" after "yarn --check-files", we shouldn't fail the less strict validation
@@ -386,6 +393,7 @@ export default class InstallationIntegrityChecker {
       integrityMatches: integrityMatches === 'OK',
       integrityError: integrityMatches === 'OK' ? undefined : integrityMatches,
       missingPatterns,
+      hardRefreshRequired: integrityMatches === 'NODE_VERSION_DOESNT_MATCH',
     };
   }
 
diff --git a/src/package-linker.js b/src/package-linker.js
index 8f9a75f..e32fc24 100644
--- a/src/package-linker.js
+++ b/src/package-linker.js
@@ -304,8 +304,13 @@ export default class PackageLinker {
       const stat = await fs.lstat(entryPath);
 
       if (stat.isSymbolicLink()) {
-        const packageName = entry;
-        linkTargets.set(packageName, await fs.readlink(entryPath));
+        try {
+          const entryTarget = await fs.realpath(entryPath);
+          linkTargets.set(entry, entryTarget);
+        } catch (err) {
+          this.reporter.warn(this.reporter.lang('linkTargetMissing', entry));
+          await fs.unlink(entryPath);
+        }
       } else if (stat.isDirectory() && entry[0] === '@') {
         // if the entry is directory beginning with '@', then we're dealing with a package scope, which
         // means we must iterate inside to retrieve the package names it contains
@@ -317,7 +322,13 @@ export default class PackageLinker {
 
           if (stat2.isSymbolicLink()) {
             const packageName = `${scopeName}/${entry2}`;
-            linkTargets.set(packageName, await fs.readlink(entryPath2));
+            try {
+              const entryTarget = await fs.realpath(entryPath2);
+              linkTargets.set(packageName, entryTarget);
+            } catch (err) {
+              this.reporter.warn(this.reporter.lang('linkTargetMissing', packageName));
+              await fs.unlink(entryPath2);
+            }
           }
         }
       }
@@ -334,7 +345,7 @@ export default class PackageLinker {
       if (
         (await fs.lstat(loc)).isSymbolicLink() &&
         linkTargets.has(packageName) &&
-        linkTargets.get(packageName) === (await fs.readlink(loc))
+        linkTargets.get(packageName) === (await fs.realpath(loc))
       ) {
         possibleExtraneous.delete(loc);
         copyQueue.delete(loc);
@@ -411,7 +422,7 @@ export default class PackageLinker {
         topLevelDependencies,
         async ([dest, pkg]) => {
           if (pkg._reference && pkg._reference.location && pkg.bin && Object.keys(pkg.bin).length) {
-            const binLoc = path.join(this.config.cwd, this.config.getFolder(pkg));
+            const binLoc = path.join(this.config.lockfileFolder, this.config.getFolder(pkg));
             await this.linkSelfDependencies(pkg, dest, binLoc);
             tickBin();
           }
@@ -468,7 +479,7 @@ export default class PackageLinker {
 
         let peerError = 'unmetPeer';
         let resolvedLevelDistance = Infinity;
-        let resolvedPeerPkgPattern;
+        let resolvedPeerPkg;
         for (const peerPkg of peerPkgs) {
           const peerPkgRef = peerPkg._reference;
           if (!(peerPkgRef && peerPkgRef.patterns)) {
@@ -478,25 +489,31 @@ export default class PackageLinker {
           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,
-                ),
-              );
+              resolvedPeerPkg = peerPkgRef;
             } else {
               peerError = 'incorrectPeer';
             }
           }
         }
 
-        if (resolvedPeerPkgPattern) {
-          ref.addDependencies(resolvedPeerPkgPattern);
+        if (resolvedPeerPkg) {
+          ref.addDependencies(resolvedPeerPkg.patterns);
+          this.reporter.verbose(
+            this.reporter.lang(
+              'selectedPeer',
+              `${pkg.name}@${pkg.version}`,
+              `${peerDepName}@${resolvedPeerPkg.version}`,
+              resolvedPeerPkg.level,
+            ),
+          );
         } else {
-          this.reporter.warn(this.reporter.lang(peerError, `${pkg.name}@${pkg.version}`, `${peerDepName}@${range}`));
+          this.reporter.warn(
+            this.reporter.lang(
+              peerError,
+              `${refTree.join(' > ')} > ${pkg.name}@${pkg.version}`,
+              `${peerDepName}@${range}`,
+            ),
+          );
         }
       }
     }
diff --git a/src/package-request.js b/src/package-request.js
index 5a36c19..25a1bbd 100644
--- a/src/package-request.js
+++ b/src/package-request.js
@@ -60,8 +60,9 @@ export default class PackageRequest {
 
     if (shrunk && shrunk.resolved) {
       const resolvedParts = versionUtil.explodeHashedUrl(shrunk.resolved);
-      // If it's a private git url set remote to 'git'.
-      const preferredRemoteType = resolvedParts.url.startsWith('git+ssh://') ? 'git' : remoteType;
+
+      // Detect Git protocols (git://HOST/PATH or git+PROTOCOL://HOST/PATH)
+      const preferredRemoteType = /^git(\+[a-z0-9]+)?:\/\//.test(resolvedParts.url) ? 'git' : remoteType;
 
       return {
         name: shrunk.name,
@@ -342,7 +343,6 @@ export default class PackageRequest {
     reporter: Reporter,
     filterByPatterns: ?Array<string>,
     flags: ?Object,
-    returnAllPackages: ?boolean,
   ): Promise<Array<Dependency>> {
     const {requests: reqPatterns, workspaceLayout} = await install.fetchRequestFromCwd();
 
@@ -402,9 +402,8 @@ export default class PackageRequest {
 
     // Make sure to always output `exotic` versions to be compatible with npm
     const isDepOld = ({current, latest, wanted}) =>
-      latest === 'exotic' || (latest !== 'exotic' && (semver.lt(current, wanted) || semver.lt(current, latest)));
+      latest === 'exotic' || (semver.lt(current, wanted) || semver.lt(current, latest));
     const orderByName = (depA, depB) => depA.name.localeCompare(depB.name);
-
-    return returnAllPackages ? deps.sort(orderByName) : deps.filter(isDepOld).sort(orderByName);
+    return deps.filter(isDepOld).sort(orderByName);
   }
 }
diff --git a/src/package-resolver.js b/src/package-resolver.js
index 8a76987..19b7f92 100644
--- a/src/package-resolver.js
+++ b/src/package-resolver.js
@@ -623,7 +623,9 @@ export default class PackageResolver {
         invariant(resolutionManifest._reference, 'resolutions should have a resolved reference');
         resolutionManifest._reference.patterns.push(pattern);
         this.addPattern(pattern, resolutionManifest);
-        this.lockfile.removePattern(pattern);
+        if (!this.resolutionMap.topLevelPatterns.has(pattern)) {
+          this.lockfile.removePattern(pattern);
+        }
       } else {
         this.resolutionMap.addToDelayQueue(req);
       }
diff --git a/src/registries/npm-registry.js b/src/registries/npm-registry.js
index 124c777..4b72f43 100644
--- a/src/registries/npm-registry.js
+++ b/src/registries/npm-registry.js
@@ -5,6 +5,7 @@ import type RequestManager from '../util/request-manager.js';
 import type {RegistryRequestOptions, CheckOutdatedReturn} from './base-registry.js';
 import type Config from '../config.js';
 import type {ConfigRegistries} from './index.js';
+import type {Env} from '../util/env-replace.js';
 import {YARN_REGISTRY} from '../constants.js';
 import * as fs from '../util/fs.js';
 import NpmResolver from '../resolvers/registries/npm-resolver.js';
@@ -206,11 +207,20 @@ export default class NpmRegistry extends Registry {
     return actuals;
   }
 
+  static getConfigEnv(env: Env = process.env): Env {
+    // To match NPM's behavior, HOME is always the user's home directory.
+    const overrideEnv = {
+      HOME: home,
+    };
+    return Object.assign({}, env, overrideEnv);
+  }
+
   static normalizeConfig(config: Object): Object {
+    const env = NpmRegistry.getConfigEnv();
     config = Registry.normalizeConfig(config);
 
     for (const key: string in config) {
-      config[key] = envReplace(config[key]);
+      config[key] = envReplace(config[key], env);
       if (isPathConfigOption(key)) {
         config[key] = normalizePath(config[key]);
       }
diff --git a/src/reporters/console/console-reporter.js b/src/reporters/console/console-reporter.js
index bf64bca..a1e047c 100644
--- a/src/reporters/console/console-reporter.js
+++ b/src/reporters/console/console-reporter.js
@@ -30,7 +30,7 @@ type Row = Array<string>;
 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)) {
+if (process.platform === 'win32' && !(process.env.TERM && /^xterm/i.test(process.env.TERM))) {
   chalk.bold._styles[0].close += '\u001b[m';
 }
 
diff --git a/src/reporters/console/util.js b/src/reporters/console/util.js
index 668566a..730f5a3 100644
--- a/src/reporters/console/util.js
+++ b/src/reporters/console/util.js
@@ -1,5 +1,6 @@
 /* @flow */
 
+import tty from 'tty';
 import type {Stdout} from '../types.js';
 
 const readline = require('readline');
@@ -10,6 +11,12 @@ const CLEAR_RIGHT_OF_CURSOR = 1;
 
 export function clearLine(stdout: Stdout) {
   if (!supportsColor) {
+    if (stdout instanceof tty.WriteStream) {
+      if (stdout.columns > 0) {
+        stdout.write(`\r${' '.repeat(stdout.columns - 1)}`);
+      }
+      stdout.write(`\r`);
+    }
     return;
   }
 
@@ -19,6 +26,7 @@ export function clearLine(stdout: Stdout) {
 
 export function toStartOfLine(stdout: Stdout) {
   if (!supportsColor) {
+    stdout.write('\r');
     return;
   }
 
diff --git a/src/reporters/lang/en.js b/src/reporters/lang/en.js
index ac0233d..8ab68cf 100644
--- a/src/reporters/lang/en.js
+++ b/src/reporters/lang/en.js
@@ -69,6 +69,7 @@ const messages = {
   couldntFindVersionThatMatchesRange: "Couldn't find any versions for $0 that matches $1",
   chooseVersionFromList: 'Please choose a version of $0 from this list:',
   moduleNotInManifest: "This module isn't specified in a manifest.",
+  moduleAlreadyInManifest: '$0 is already in $1. Please remove existing entry first before adding it to $2.',
   unknownFolderOrTarball: "Passed folder/tarball doesn't exist,",
   unknownPackage: "Couldn't find package $0.",
   unknownPackageName: "Couldn't find package name.",
@@ -172,6 +173,7 @@ const messages = {
   linkUsing: 'Using linked module for $0.',
   linkDisusing: 'Removed linked module $0.',
   linkDisusingMessage: 'You will need to run `yarn` to re-install the package that was linked.',
+  linkTargetMissing: 'The target of linked module $0 is missing. Removing link.',
 
   createInvalidBin: 'Invalid bin entry found in package $0.',
   createMissingPackage:
@@ -361,7 +363,7 @@ const messages = {
   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 "./".',
+    'Using the "file:" protocol implicitly is deprecated. Please either prepend 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',
 
diff --git a/src/resolution-map.js b/src/resolution-map.js
index fbc0af7..12e548d 100644
--- a/src/resolution-map.js
+++ b/src/resolution-map.js
@@ -33,12 +33,14 @@ export default class ResolutionMap {
     this.config = config;
     this.reporter = config.reporter;
     this.delayQueue = new Set();
+    this.topLevelPatterns = new Set();
   }
 
   resolutionsByPackage: ResolutionInternalMap;
   config: Config;
   reporter: Reporter;
   delayQueue: Set<DependencyRequestPattern>;
+  topLevelPatterns: Set<string>;
 
   init(resolutions: ?ResolutionEntry = {}) {
     for (const globPattern in resolutions) {
@@ -55,6 +57,10 @@ export default class ResolutionMap {
     this.delayQueue.add(req);
   }
 
+  setTopLevelPatterns(patterns: Array<string>) {
+    this.topLevelPatterns = new Set(patterns);
+  }
+
   parsePatternInfo(globPattern: string, range: string): ?Object {
     if (!isValidPackagePath(globPattern)) {
       this.reporter.warn(this.reporter.lang('invalidResolutionName', globPattern));
diff --git a/src/resolvers/registries/npm-resolver.js b/src/resolvers/registries/npm-resolver.js
index b17fa7c..a2ed065 100644
--- a/src/resolvers/registries/npm-resolver.js
+++ b/src/resolvers/registries/npm-resolver.js
@@ -14,6 +14,7 @@ const inquirer = require('inquirer');
 const tty = require('tty');
 const invariant = require('invariant');
 const path = require('path');
+const semver = require('semver');
 
 const NPM_REGISTRY = /http[s]:\/\/registry.npmjs.org/g;
 const NPM_REGISTRY_ID = 'npm';
@@ -41,6 +42,14 @@ export default class NpmResolver extends RegistryResolver {
       range = body['dist-tags'][range];
     }
 
+    // If the latest tag in the registry satisfies the requested range, then use that.
+    // Otherwise we will fall back to semver maxSatisfying.
+    // This mimics logic in NPM. See issue #3560
+    const latestVersion = body['dist-tags'] ? body['dist-tags'].latest : undefined;
+    if (latestVersion && semver.satisfies(latestVersion, range)) {
+      return body.versions[latestVersion];
+    }
+
     const satisfied = await config.resolveConstraints(Object.keys(body.versions), range);
     if (satisfied) {
       return body.versions[satisfied];
diff --git a/src/util/env-replace.js b/src/util/env-replace.js
index 8e2bb62..dcee78a 100644
--- a/src/util/env-replace.js
+++ b/src/util/env-replace.js
@@ -1,7 +1,9 @@
 /* @flow */
 const ENV_EXPR = /(\\*)\$\{([^}]+)\}/g;
 
-export default function envReplace(value: string, env: {[key: string]: ?string} = process.env): string {
+export type Env = {[key: string]: ?string};
+
+export default function envReplace(value: string, env: Env = process.env): string {
   if (typeof value !== 'string' || !value) {
     return value;
   }
diff --git a/src/util/git/git-spawn.js b/src/util/git/git-spawn.js
index b445bbd..50acd83 100644
--- a/src/util/git/git-spawn.js
+++ b/src/util/git/git-spawn.js
@@ -18,6 +18,10 @@ const sshExecutable = path.basename(sshCommand.toLowerCase(), '.exe');
 const sshBatchArgs = BATCH_MODE_ARGS.get(sshExecutable);
 
 if (!env.GIT_SSH_COMMAND && sshBatchArgs) {
+  // We have to manually specify `GIT_SSH_VARIANT`,
+  // because it's not automatically set when using `GIT_SSH_COMMAND` instead of `GIT_SSH`
+  // See: https://github.com/yarnpkg/yarn/issues/4729
+  env.GIT_SSH_VARIANT = sshExecutable;
   env.GIT_SSH_COMMAND = `"${sshCommand}" ${sshBatchArgs}`;
 }
 
diff --git a/src/util/normalize-manifest/fix.js b/src/util/normalize-manifest/fix.js
index 7adb848..8ce398e 100644
--- a/src/util/normalize-manifest/fix.js
+++ b/src/util/normalize-manifest/fix.js
@@ -1,6 +1,6 @@
 /* @flow */
 
-import {DEPENDENCY_TYPES} from '../../constants';
+import {MANIFEST_FIELDS} from '../../constants';
 import type {Reporter} from '../../reporters/index.js';
 import {isValidLicense} from './util.js';
 import {normalizePerson, extractDescription} from './util.js';
@@ -308,7 +308,7 @@ export default (async function(
     }
   }
 
-  for (const dependencyType of DEPENDENCY_TYPES) {
+  for (const dependencyType of MANIFEST_FIELDS) {
     const dependencyList = info[dependencyType];
     if (dependencyList && typeof dependencyList === 'object') {
       delete dependencyList['//'];
diff --git a/src/util/request-manager.js b/src/util/request-manager.js
index b8049e8..5c0f65f 100644
--- a/src/util/request-manager.js
+++ b/src/util/request-manager.js
@@ -412,9 +412,7 @@ export default class RequestManager {
     if (params.url.startsWith('https:')) {
       proxy = this.httpsProxy;
     }
-    if (proxy) {
-      params.proxy = String(proxy);
-    }
+    params.proxy = String(proxy);
 
     if (this.ca != null) {
       params.ca = this.ca;

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