diff --git a/package.json b/package.json index b543cdf2d8..159cf7385f 100644 --- a/package.json +++ b/package.json @@ -42,13 +42,14 @@ "is-ci": "^1.0.10", "is-inner-link": "^2.0.0", "is-windows": "^1.0.0", - "link-dir": "^2.0.0", + "link-dir": "^2.0.1", "load-json-file": "^2.0.0", "load-yaml-file": "^0.1.0", "loud-rejection": "^1.6.0", "mem": "^1.1.0", "mkdirp-promise": "^5.0.1", "mz": "^2.6.0", + "ncp": "^2.0.0", "ndjson": "^1.5.0", "node-gyp": "^3.6.0", "nopt": "^4.0.1", @@ -96,7 +97,6 @@ "in-publish": "^2.0.0", "isexe": "^2.0.0", "mkdirp": "^0.5.1", - "ncp": "^2.0.0", "npm-run-all": "^4.0.1", "npm-scripts-info": "^0.3.6", "pnpm-registry-mock": "^0.9.0", diff --git a/shrinkwrap.yaml b/shrinkwrap.yaml index 9e3c2eb6ac..8d7bc3e6b6 100644 --- a/shrinkwrap.yaml +++ b/shrinkwrap.yaml @@ -38,7 +38,7 @@ dependencies: is-inner-link@^2.0.0: 2.0.0 is-windows@^1.0.0: 1.0.1 isexe@^2.0.0: 2.0.0 - link-dir@^2.0.0: 2.0.0 + link-dir@^2.0.1: 2.0.1 load-json-file@^2.0.0: 2.0.0 load-yaml-file@^0.1.0: 0.1.0 loud-rejection@^1.6.0: 1.6.0 @@ -1135,7 +1135,7 @@ packages: /lazy-cache/1.0.4: a1d78fc3a50474cb80845d3b3b6e1da49a446e8e /lazy-req/2.0.0: c9450a363ecdda2e6f0c70132ad4f37f8f06f2b4 /levenshtein/1.0.2: cd02e8b32075645ddcf5d059e02efa918f9fb3ec - /link-dir/2.0.0: + /link-dir/2.0.1: dependencies: '@types/mz': 0.0.30 '@types/node': 6.0.77 @@ -1144,7 +1144,7 @@ packages: mkdirp-promise: 5.0.1 mz: 2.6.0 rimraf-then: 1.0.1 - resolution: 109f177704943a3796ce49b0b518da8757955ac2 + resolution: ca8c686d6f8f75d1708b9e166c5dafb7c8ba0f04 /linkify-it/1.2.4: dependencies: uc.micro: 1.0.3 diff --git a/src/link/index.ts b/src/link/index.ts index d22e378fa3..edb6b25406 100644 --- a/src/link/index.ts +++ b/src/link/index.ts @@ -1,6 +1,6 @@ import fs = require('mz/fs') import path = require('path') -import linkDir from 'link-dir' +import linkDir = require('link-dir') import symlinkDir from 'symlink-dir' import exists = require('path-exists') import logger from 'pnpm-logger' @@ -16,6 +16,10 @@ import logStatus from '../logging/logInstallStatus' import updateShrinkwrap from './updateShrinkwrap' import {Shrinkwrap, shortIdToFullId, DependencyShrinkwrap} from '../fs/shrinkwrap' import removeOrphanPkgs from '../api/removeOrphanPkgs' +import ncpCB = require('ncp') +import thenify = require('thenify') + +const ncp = thenify(ncpCB) export default async function ( topPkgs: InstalledPackage[], @@ -127,7 +131,14 @@ async function linkNewPackages ( const newPkgs = R.props(newPkgResolvedIds, pkgsToLink) - await linkAllPkgs(newPkgs, opts) + try { + await linkAllPkgs(linkPkg, newPkgs, opts) + } catch (err) { + if (!err.message.startsWith('EXDEV: cross-device link not permitted')) throw err + logger.warn(err.message) + logger.info('Falling back to copying packages from store') + await linkAllPkgs(copyPkg, newPkgs, opts) + } if (!opts.force) { // add subdependencies that have been updated @@ -149,6 +160,10 @@ async function linkNewPackages ( const limitLinking = pLimit(16) async function linkAllPkgs ( + linkPkg: (dependency: DependencyTreeNode, opts: { + force: boolean, + baseNodeModules: string, + }) => Promise, alldeps: DependencyTreeNode[], opts: { force: boolean, @@ -185,6 +200,21 @@ async function linkPkg ( } } +async function copyPkg ( + dependency: DependencyTreeNode, + opts: { + force: boolean, + baseNodeModules: string, + } +) { + const newlyFetched = await dependency.fetchingFiles + + const pkgJsonPath = path.join(dependency.hardlinkedLocation, 'package.json') + if (newlyFetched || opts.force || !await exists(pkgJsonPath)) { + await ncp(dependency.path, dependency.hardlinkedLocation) + } +} + async function pkgLinkedToStore (pkgJsonPath: string, dependency: DependencyTreeNode) { const pkgJsonPathInStore = path.join(dependency.path, 'package.json') if (await isSameFile(pkgJsonPath, pkgJsonPathInStore)) return true