From 7efbf08d79b6ff3dede665b87aabb83ea90624aa Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Thu, 29 Jun 2017 00:59:41 -0700 Subject: [PATCH 1/2] Add link --deep option Creates a symlink for each file/directory inside a package instead of the directory itself. This has the effect of excluding node_modules (and other files excluded by filters). --- src/cli/commands/link.js | 56 ++++++++++++++++++++++++++++------------ src/cli/commands/pack.js | 14 ++++++---- 2 files changed, 49 insertions(+), 21 deletions(-) diff --git a/src/cli/commands/link.js b/src/cli/commands/link.js index 1eb2537108..760b55a604 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 = 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 = 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 => { From 2b3353b3f92e87b7df3f7adc6049454639dd4356 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Thu, 29 Jun 2017 01:03:20 -0700 Subject: [PATCH 2/2] Add install --link option This will automatically link dependencies to the globally linked copy when installing. Avoids having to manually setup links when setting up a project locally. --- src/cli/commands/add.js | 1 + src/cli/commands/install.js | 3 ++- src/package-request.js | 9 ++++++++- src/package-resolver.js | 3 ++- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/cli/commands/add.js b/src/cli/commands/add.js index 2cba3eb50f..0d9a93933a 100644 --- a/src/cli/commands/add.js +++ b/src/cli/commands/add.js @@ -185,6 +185,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 b48bdc629e..359e0740f6 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); @@ -830,6 +830,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/package-request.js b/src/package-request.js index b1980076f2..ba2bbc32b5 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 81b7afcac5..028ac759f5 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 = []; }