diff --git a/src/cli/commands/add.js b/src/cli/commands/add.js index 3e9b1bb49b..cde074bfe3 100644 --- a/src/cli/commands/add.js +++ b/src/cli/commands/add.js @@ -189,6 +189,7 @@ export function setFlags(commander: Object) { commander.option('-O, --optional', 'save package to your `optionalDependencies`'); commander.option('-E, --exact', 'install exact version'); commander.option('-T, --tilde', 'install most recent release with the same minor version'); + commander.option('--link', 'link local dependencies'); } export async function run(config: Config, reporter: Reporter, flags: Object, args: Array): Promise { diff --git a/src/cli/commands/install.js b/src/cli/commands/install.js index e71ac6b1dc..64eb8a3f56 100644 --- a/src/cli/commands/install.js +++ b/src/cli/commands/install.js @@ -171,7 +171,7 @@ export class Install { this.config = config; this.flags = normalizeFlags(config, flags); - this.resolver = new PackageResolver(config, lockfile); + this.resolver = new PackageResolver(config, flags, lockfile); this.integrityChecker = new InstallationIntegrityChecker(config); this.linker = new PackageLinker(config, this.resolver); this.scripts = new PackageInstallScripts(config, this.resolver, this.flags.force); @@ -846,6 +846,7 @@ export function setFlags(commander: Object) { commander.option('-O, --save-optional', 'DEPRECATED - save package to your `optionalDependencies`'); commander.option('-E, --save-exact', 'DEPRECATED'); commander.option('-T, --save-tilde', 'DEPRECATED'); + commander.option('--link', 'link local dependencies'); } export async function install(config: Config, reporter: Reporter, flags: Object, lockfile: Lockfile): Promise { diff --git a/src/cli/commands/link.js b/src/cli/commands/link.js index 693f1f4b47..6e56bafeea 100644 --- a/src/cli/commands/link.js +++ b/src/cli/commands/link.js @@ -5,6 +5,7 @@ import type Config from '../../config.js'; import {MessageError} from '../../errors.js'; import * as fs from '../../util/fs.js'; import {getBinFolder as getGlobalBinFolder} from './global'; +import {getPackageFilters} from './pack'; const invariant = require('invariant'); const path = require('path'); @@ -26,7 +27,9 @@ export function hasWrapper(commander: Object, args: Array): boolean { return true; } -export function setFlags(commander: Object) {} +export function setFlags(commander: Object) { + commander.option('--deep', 'create a link for each file inside the current directory instead of the directory itself'); +} export async function run(config: Config, reporter: Reporter, flags: Object, args: Array): Promise { if (args.length) { @@ -56,28 +59,49 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg const linkLoc = path.join(config.linkFolder, name); if (await fs.exists(linkLoc)) { reporter.warn(reporter.lang('linkCollision', name)); + await fs.unlink(path.join(config.cwd, linkLoc)); + } + + if (flags.deep) { + await linkDeep(config, linkLoc); } else { await fs.mkdirp(path.dirname(linkLoc)); await fs.symlink(config.cwd, linkLoc); + } - // If there is a `bin` defined in the package.json, - // link each bin to the global bin - if (manifest.bin) { - const globalBinFolder = await getGlobalBinFolder(config, flags); - for (const binName in manifest.bin) { - const binSrc = manifest.bin[binName]; - const binSrcLoc = path.join(linkLoc, binSrc); - const binDestLoc = path.join(globalBinFolder, binName); - if (await fs.exists(binDestLoc)) { - reporter.warn(reporter.lang('binLinkCollision', binName)); - } else { - await fs.symlink(binSrcLoc, binDestLoc); - } + // If there is a `bin` defined in the package.json, + // link each bin to the global bin + if (manifest.bin) { + const globalBinFolder = await getGlobalBinFolder(config, flags); + for (const binName in manifest.bin) { + const binSrc = manifest.bin[binName]; + const binSrcLoc = path.join(linkLoc, binSrc); + const binDestLoc = path.join(globalBinFolder, binName); + if (await fs.exists(binDestLoc)) { + reporter.warn(reporter.lang('binLinkCollision', binName)); + } else { + await fs.symlink(binSrcLoc, binDestLoc); } } + } - reporter.success(reporter.lang('linkRegistered', name)); - reporter.info(reporter.lang('linkRegisteredMessage', name)); + reporter.success(reporter.lang('linkRegistered', name)); + reporter.info(reporter.lang('linkRegisteredMessage', name)); + } +} + +async function linkDeep(config, linkLoc) { + await fs.mkdirp(linkLoc); + + const {keepFiles} = await getPackageFilters(config); + for (let name of await fs.readdir(config.cwd)) { + const relative = path.relative(config.cwd, name); + if (!keepFiles.has(relative)) { + continue; } + + const src = path.join(config.cwd, name); + const dest = path.join(linkLoc, name); + await fs.symlink(src, dest); } } diff --git a/src/cli/commands/pack.js b/src/cli/commands/pack.js index ce0f6812ae..af6784150d 100644 --- a/src/cli/commands/pack.js +++ b/src/cli/commands/pack.js @@ -48,10 +48,7 @@ const NEVER_IGNORE = ignoreLinesToRegex([ '!/+(changes|changelog|history)*', ]); -export async function packTarball( - config: Config, - {mapHeader}: {mapHeader?: Object => Object} = {}, -): Promise { +export async function getPackageFilters(config) { const pkg = await config.readRootManifest(); const {bundledDependencies, main, files: onlyFiles} = pkg; @@ -108,7 +105,14 @@ export async function packTarball( const possibleKeepFiles: Set = new Set(); // apply filters - sortFilter(files, filters, keepFiles, possibleKeepFiles, ignoredFiles); + return sortFilter(files, filters, keepFiles, possibleKeepFiles, ignoredFiles); +} + +export async function packTarball( + config: Config, + {mapHeader}: {mapHeader?: Object => Object} = {}, +): Promise { + const {keepFiles} = await getPackageFilters(config); const packer = tar.pack(config.cwd, { ignore: name => { diff --git a/src/package-request.js b/src/package-request.js index ee9ac6412e..fb4c1efffa 100644 --- a/src/package-request.js +++ b/src/package-request.js @@ -145,7 +145,14 @@ export default class PackageRequest { } async normalize(pattern: string): any { - const {name, range, hasVersion} = PackageRequest.normalizePattern(pattern); + let {name, range, hasVersion} = PackageRequest.normalizePattern(pattern); + + // Check if package is linked globally if specified + const linkPath = path.join(this.config.linkFolder, name); + if (this.resolver.flags.link && await fs.exists(linkPath)) { + range = 'link:' + linkPath; + } + const newRange = await this.normalizeRange(range); return {name, range: newRange, hasVersion}; } diff --git a/src/package-resolver.js b/src/package-resolver.js index f59321ba32..a3818f2c0a 100644 --- a/src/package-resolver.js +++ b/src/package-resolver.js @@ -23,7 +23,7 @@ export type ResolverOptions = {| |}; export default class PackageResolver { - constructor(config: Config, lockfile: Lockfile) { + constructor(config: Config, flags: Object, lockfile: Lockfile) { this.patternsByPackage = map(); this.fetchingPatterns = map(); this.fetchingQueue = new BlockingQueue('resolver fetching'); @@ -34,6 +34,7 @@ export default class PackageResolver { this.reporter = config.reporter; this.lockfile = lockfile; this.config = config; + this.flags = flags; this.delayedResolveQueue = []; }