From 7323861f08f6c897babaac002b28b211e8fffda6 Mon Sep 17 00:00:00 2001 From: John Gozde Date: Mon, 9 Oct 2017 12:04:43 -0600 Subject: [PATCH] Fix: workspace support in several commands (#4654) * Use lockfileFolder for CLI check * Make "upgrade" work inside workspace packages Executes "fetchRequestFromCwd" in actual cwd, which ensures "outdated" and "upgrade" commands in workspace packages operate on the correct dependencies and preserve unrelated lockfile entries. * Support workspaces in outdated and upgrade-interactive --- __tests__/commands/outdated.js | 27 +++++++ __tests__/commands/upgrade.js | 43 +++++++++++ .../outdated/workspaces/child-a/package.json | 7 ++ .../outdated/workspaces/child-b/package.json | 8 ++ .../fixtures/outdated/workspaces/package.json | 11 +++ .../fixtures/outdated/workspaces/yarn.lock | 19 +++++ .../upgrade/workspaces/child-a/package.json | 7 ++ .../upgrade/workspaces/child-b/package.json | 7 ++ .../fixtures/upgrade/workspaces/package.json | 11 +++ .../fixtures/upgrade/workspaces/yarn.lock | 15 ++++ src/cli/commands/install.js | 76 ++++++++++++++++--- src/cli/commands/outdated.js | 16 +++- src/cli/commands/remove.js | 3 +- src/cli/commands/upgrade-interactive.js | 56 ++++++++++---- src/cli/commands/upgrade.js | 9 ++- src/cli/index.js | 17 +++-- src/package-request.js | 15 +++- src/types.js | 4 + 18 files changed, 312 insertions(+), 39 deletions(-) create mode 100644 __tests__/fixtures/outdated/workspaces/child-a/package.json create mode 100644 __tests__/fixtures/outdated/workspaces/child-b/package.json create mode 100644 __tests__/fixtures/outdated/workspaces/package.json create mode 100644 __tests__/fixtures/outdated/workspaces/yarn.lock create mode 100644 __tests__/fixtures/upgrade/workspaces/child-a/package.json create mode 100644 __tests__/fixtures/upgrade/workspaces/child-b/package.json create mode 100644 __tests__/fixtures/upgrade/workspaces/package.json create mode 100644 __tests__/fixtures/upgrade/workspaces/yarn.lock diff --git a/__tests__/commands/outdated.js b/__tests__/commands/outdated.js index 1bfe4493ce..31529115b9 100644 --- a/__tests__/commands/outdated.js +++ b/__tests__/commands/outdated.js @@ -130,3 +130,30 @@ test.concurrent('displays correct dependency types', (): Promise => { expect(body[2][4]).toBe('devDependencies'); }); }); + +test.concurrent('shows dependencies from entire workspace', async (): Promise => { + await runOutdated([], {}, 'workspaces', (config, reporter, out): ?Promise => { + const json: Object = JSON.parse(out); + + expect(json.data.body).toHaveLength(4); + expect(json.data.body[0][0]).toBe('left-pad'); + expect(json.data.body[0][1]).toBe('1.0.0'); + expect(json.data.body[1][0]).toBe('left-pad'); + expect(json.data.body[1][1]).toBe('1.0.1'); + expect(json.data.body[2][0]).toBe('max-safe-integer'); + expect(json.data.body[3][0]).toBe('right-pad'); + }); + + const childFixture = {source: 'workspaces', cwd: 'child-a'}; + return runOutdated([], {}, childFixture, (config, reporter, out): ?Promise => { + const json: Object = JSON.parse(out); + + expect(json.data.body).toHaveLength(4); + expect(json.data.body[0][0]).toBe('left-pad'); + expect(json.data.body[0][1]).toBe('1.0.0'); + expect(json.data.body[1][0]).toBe('left-pad'); + expect(json.data.body[1][1]).toBe('1.0.1'); + expect(json.data.body[2][0]).toBe('max-safe-integer'); + expect(json.data.body[3][0]).toBe('right-pad'); + }); +}); diff --git a/__tests__/commands/upgrade.js b/__tests__/commands/upgrade.js index a7b6182276..0cda4523c6 100644 --- a/__tests__/commands/upgrade.js +++ b/__tests__/commands/upgrade.js @@ -330,3 +330,46 @@ test.concurrent('--latest works if there is an install script on a hoisted depen 'latest-with-install-script', ); }); + +test.concurrent('upgrade to workspace root preserves child dependencies', (): Promise => { + return runUpgrade(['max-safe-integer@1.0.1'], {latest: true}, 'workspaces', async (config): ?Promise => { + const lockfile = explodeLockfile(await fs.readFile(path.join(config.cwd, 'yarn.lock'))); + + // child workspace deps + expect(lockfile.indexOf('left-pad@1.0.0:')).toBeGreaterThanOrEqual(0); + expect(lockfile.indexOf('right-pad@1.0.0:')).toBeGreaterThanOrEqual(0); + // root dep + expect(lockfile.indexOf('max-safe-integer@1.0.0:')).toBe(-1); + expect(lockfile.indexOf('max-safe-integer@1.0.1:')).toBeGreaterThanOrEqual(0); + + const rootPkg = await fs.readJson(path.join(config.cwd, 'package.json')); + expect(rootPkg.devDependencies['max-safe-integer']).toEqual('1.0.1'); + + const childAPkg = await fs.readJson(path.join(config.cwd, 'child-a/package.json')); + const childBPkg = await fs.readJson(path.join(config.cwd, 'child-b/package.json')); + expect(childAPkg.dependencies['left-pad']).toEqual('1.0.0'); + expect(childBPkg.dependencies['right-pad']).toEqual('1.0.0'); + }); +}); + +test.concurrent('upgrade to workspace child preserves root dependencies', (): Promise => { + const fixture = {source: 'workspaces', cwd: 'child-a'}; + return runUpgrade(['left-pad@1.1.0'], {latest: true}, fixture, async (config): ?Promise => { + const lockfile = explodeLockfile(await fs.readFile(path.join(config.lockfileFolder, 'yarn.lock'))); + + // untouched deps + expect(lockfile.indexOf('right-pad@1.0.0:')).toBeGreaterThanOrEqual(0); + expect(lockfile.indexOf('max-safe-integer@1.0.0:')).toBeGreaterThanOrEqual(0); + // upgraded child workspace + expect(lockfile.indexOf('left-pad@1.0.0:')).toBe(-1); + expect(lockfile.indexOf('left-pad@1.1.0:')).toBeGreaterThanOrEqual(0); + + const childAPkg = await fs.readJson(path.join(config.cwd, 'package.json')); + expect(childAPkg.dependencies['left-pad']).toEqual('1.1.0'); + + const rootPkg = await fs.readJson(path.join(config.lockfileFolder, 'package.json')); + const childBPkg = await fs.readJson(path.join(config.lockfileFolder, 'child-b/package.json')); + expect(rootPkg.devDependencies['max-safe-integer']).toEqual('1.0.0'); + expect(childBPkg.dependencies['right-pad']).toEqual('1.0.0'); + }); +}); diff --git a/__tests__/fixtures/outdated/workspaces/child-a/package.json b/__tests__/fixtures/outdated/workspaces/child-a/package.json new file mode 100644 index 0000000000..664cc2b5b1 --- /dev/null +++ b/__tests__/fixtures/outdated/workspaces/child-a/package.json @@ -0,0 +1,7 @@ +{ + "name": "child-a", + "version": "1.0.0", + "dependencies": { + "max-safe-integer": "1.0.0" + } +} diff --git a/__tests__/fixtures/outdated/workspaces/child-b/package.json b/__tests__/fixtures/outdated/workspaces/child-b/package.json new file mode 100644 index 0000000000..b56f2f9218 --- /dev/null +++ b/__tests__/fixtures/outdated/workspaces/child-b/package.json @@ -0,0 +1,8 @@ +{ + "name": "child-b", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.0.1", + "right-pad": "1.0.0" + } +} diff --git a/__tests__/fixtures/outdated/workspaces/package.json b/__tests__/fixtures/outdated/workspaces/package.json new file mode 100644 index 0000000000..4416f9cf40 --- /dev/null +++ b/__tests__/fixtures/outdated/workspaces/package.json @@ -0,0 +1,11 @@ +{ + "name": "my-project", + "private": true, + "devDependencies": { + "left-pad": "1.0.0" + }, + "workspaces": [ + "child-a", + "child-b" + ] +} diff --git a/__tests__/fixtures/outdated/workspaces/yarn.lock b/__tests__/fixtures/outdated/workspaces/yarn.lock new file mode 100644 index 0000000000..05ca873ea9 --- /dev/null +++ b/__tests__/fixtures/outdated/workspaces/yarn.lock @@ -0,0 +1,19 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +left-pad@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.0.0.tgz#c84e2417581bbb8eaf2b9e3d7a122e572ab1af37" + +left-pad@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.0.1.tgz#d11b8e17e70e6ecb3b6bf2858fa99c40f819d13a" + +max-safe-integer@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/max-safe-integer/-/max-safe-integer-1.0.0.tgz#4662073a02c7e02d38153e25795489b20be6f01a" + +right-pad@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/right-pad/-/right-pad-1.0.0.tgz#5ba6e56c0d7ec162d3626315c27a61f8aff42f15" diff --git a/__tests__/fixtures/upgrade/workspaces/child-a/package.json b/__tests__/fixtures/upgrade/workspaces/child-a/package.json new file mode 100644 index 0000000000..5520b53586 --- /dev/null +++ b/__tests__/fixtures/upgrade/workspaces/child-a/package.json @@ -0,0 +1,7 @@ +{ + "name": "child-a", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.0.0" + } +} diff --git a/__tests__/fixtures/upgrade/workspaces/child-b/package.json b/__tests__/fixtures/upgrade/workspaces/child-b/package.json new file mode 100644 index 0000000000..7e22906c15 --- /dev/null +++ b/__tests__/fixtures/upgrade/workspaces/child-b/package.json @@ -0,0 +1,7 @@ +{ + "name": "child-b", + "version": "1.0.0", + "dependencies": { + "right-pad": "1.0.0" + } +} diff --git a/__tests__/fixtures/upgrade/workspaces/package.json b/__tests__/fixtures/upgrade/workspaces/package.json new file mode 100644 index 0000000000..998fddf24c --- /dev/null +++ b/__tests__/fixtures/upgrade/workspaces/package.json @@ -0,0 +1,11 @@ +{ + "name": "my-project", + "private": true, + "devDependencies": { + "max-safe-integer": "1.0.0" + }, + "workspaces": [ + "child-a", + "child-b" + ] +} diff --git a/__tests__/fixtures/upgrade/workspaces/yarn.lock b/__tests__/fixtures/upgrade/workspaces/yarn.lock new file mode 100644 index 0000000000..48676640ef --- /dev/null +++ b/__tests__/fixtures/upgrade/workspaces/yarn.lock @@ -0,0 +1,15 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +left-pad@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.0.0.tgz#c84e2417581bbb8eaf2b9e3d7a122e572ab1af37" + +max-safe-integer@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/max-safe-integer/-/max-safe-integer-1.0.0.tgz#4662073a02c7e02d38153e25795489b20be6f01a" + +right-pad@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/right-pad/-/right-pad-1.0.0.tgz#5ba6e56c0d7ec162d3626315c27a61f8aff42f15" diff --git a/src/cli/commands/install.js b/src/cli/commands/install.js index 5d17d1c819..3488b80341 100644 --- a/src/cli/commands/install.js +++ b/src/cli/commands/install.js @@ -68,6 +68,12 @@ type Flags = { exact: boolean, tilde: boolean, ignoreWorkspaceRootCheck: boolean, + + // outdated, update-interactive + includeWorkspaceDeps: boolean, + + // remove, upgrade + workspaceRootIsCwd: boolean, }; /** @@ -139,6 +145,12 @@ function normalizeFlags(config: Config, rawFlags: Object): Flags { exact: !!rawFlags.exact, tilde: !!rawFlags.tilde, ignoreWorkspaceRootCheck: !!rawFlags.ignoreWorkspaceRootCheck, + + // outdated, update-interactive + includeWorkspaceDeps: !!rawFlags.includeWorkspaceDeps, + + // remove, update + workspaceRootIsCwd: rawFlags.workspaceRootIsCwd !== false, }; if (config.getOption('ignore-scripts')) { @@ -211,6 +223,13 @@ export class Install { const usedPatterns = []; let workspaceLayout; + // some commands should always run in the context of the entire workspace + const cwd = + this.flags.includeWorkspaceDeps || this.flags.workspaceRootIsCwd ? this.config.lockfileFolder : this.config.cwd; + + // non-workspaces are always root, otherwise check for workspace root + const cwdIsRoot = !this.config.workspaceRootFolder || this.config.lockfileFolder === cwd; + // exclude package names that are in install args const excludeNames = []; for (const pattern of excludePatterns) { @@ -224,9 +243,23 @@ export class Install { excludeNames.push(parts.name); } + const stripExcluded = (manifest: Manifest) => { + for (const exclude of excludeNames) { + if (manifest.dependencies && manifest.dependencies[exclude]) { + delete manifest.dependencies[exclude]; + } + if (manifest.devDependencies && manifest.devDependencies[exclude]) { + delete manifest.devDependencies[exclude]; + } + if (manifest.optionalDependencies && manifest.optionalDependencies[exclude]) { + delete manifest.optionalDependencies[exclude]; + } + } + }; + for (const registry of Object.keys(registries)) { const {filename} = registries[registry]; - const loc = path.join(this.config.lockfileFolder, filename); + const loc = path.join(cwd, filename); if (!await fs.exists(loc)) { continue; } @@ -234,7 +267,7 @@ export class Install { this.rootManifestRegistries.push(registry); const projectManifestJson = await this.config.readJson(loc); - await normalizeManifest(projectManifestJson, this.config.lockfileFolder, this.config, true); + await normalizeManifest(projectManifestJson, cwd, this.config, cwdIsRoot); Object.assign(this.resolutions, projectManifestJson.resolutions); Object.assign(manifest, projectManifestJson); @@ -278,7 +311,7 @@ export class Install { this.rootPatternsToOrigin[pattern] = depType; patterns.push(pattern); - deps.push({pattern, registry, hint, optional}); + deps.push({pattern, registry, hint, optional, workspaceName: manifest.name, workspaceLoc: manifest._loc}); } }; @@ -287,27 +320,50 @@ export class Install { pushDeps('optionalDependencies', projectManifestJson, {hint: 'optional', optional: true}, true); if (this.config.workspaceRootFolder) { - const workspacesRoot = path.dirname(loc); - const workspaces = await this.config.resolveWorkspaces(workspacesRoot, projectManifestJson); + const workspaceLoc = cwdIsRoot ? loc : path.join(this.config.lockfileFolder, filename); + const workspacesRoot = path.dirname(workspaceLoc); + + let workspaceManifestJson = projectManifestJson; + if (!cwdIsRoot) { + // the manifest we read before was a child workspace, so get the root + workspaceManifestJson = await this.config.readJson(workspaceLoc); + await normalizeManifest(workspaceManifestJson, workspacesRoot, this.config, true); + } + + const workspaces = await this.config.resolveWorkspaces(workspacesRoot, workspaceManifestJson); workspaceLayout = new WorkspaceLayout(workspaces, this.config); + // add virtual manifest that depends on all workspaces, this way package hoisters and resolvers will work fine + const workspaceDependencies = {...workspaceManifestJson.dependencies}; + for (const workspaceName of Object.keys(workspaces)) { + const workspaceManifest = workspaces[workspaceName].manifest; + workspaceDependencies[workspaceName] = workspaceManifest.version; + + // include dependencies from all workspaces + if (this.flags.includeWorkspaceDeps) { + pushDeps('dependencies', workspaceManifest, {hint: null, optional: false}, true); + pushDeps('devDependencies', workspaceManifest, {hint: 'dev', optional: false}, !this.config.production); + pushDeps('optionalDependencies', workspaceManifest, {hint: 'optional', optional: true}, true); + } + } const virtualDependencyManifest: Manifest = { _uid: '', name: `workspace-aggregator-${uuid.v4()}`, version: '1.0.0', _registry: 'npm', _loc: workspacesRoot, - dependencies: {}, + dependencies: workspaceDependencies, + devDependencies: {...workspaceManifestJson.devDependencies}, + optionalDependencies: {...workspaceManifestJson.optionalDependencies}, }; workspaceLayout.virtualManifestName = virtualDependencyManifest.name; - virtualDependencyManifest.dependencies = {}; - for (const workspaceName of Object.keys(workspaces)) { - virtualDependencyManifest.dependencies[workspaceName] = workspaces[workspaceName].manifest.version; - } const virtualDep = {}; virtualDep[virtualDependencyManifest.name] = virtualDependencyManifest.version; workspaces[virtualDependencyManifest.name] = {loc: workspacesRoot, manifest: virtualDependencyManifest}; + // ensure dependencies that should be excluded are stripped from the correct manifest + stripExcluded(cwdIsRoot ? virtualDependencyManifest : workspaces[projectManifestJson.name].manifest); + pushDeps('workspaces', {workspaces: virtualDep}, {hint: 'workspaces', optional: false}, true); } diff --git a/src/cli/commands/outdated.js b/src/cli/commands/outdated.js index 52178e7908..b1641cba25 100644 --- a/src/cli/commands/outdated.js +++ b/src/cli/commands/outdated.js @@ -20,7 +20,7 @@ export function hasWrapper(commander: Object, args: Array): boolean { export async function run(config: Config, reporter: Reporter, flags: Object, args: Array): Promise { const lockfile = await Lockfile.fromDirectory(config.lockfileFolder); - const install = new Install(flags, config, reporter, lockfile); + const install = new Install({...flags, includeWorkspaceDeps: true}, config, reporter, lockfile); let deps = await PackageRequest.getOutdatedPackages(lockfile, install, config, reporter); if (args.length) { @@ -33,18 +33,28 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg const colorizeName = ({current, wanted, name}) => reporter.format[colorForVersions(current, wanted)](name); if (deps.length) { + const usesWorkspaces = !!config.workspaceRootFolder; const body = deps.map((info): Array => { - return [ + const row = [ colorizeName(info), info.current, colorizeDiff(info.current, info.wanted, reporter), reporter.format.magenta(info.latest), + info.workspaceName || '', getNameFromHint(info.hint), reporter.format.cyan(info.url), ]; + if (!usesWorkspaces) { + row.splice(4, 1); + } + return row; }); - reporter.table(['Package', 'Current', 'Wanted', 'Latest', 'Package Type', 'URL'], body); + const header = ['Package', 'Current', 'Wanted', 'Latest', 'Workspace', 'Package Type', 'URL']; + if (!usesWorkspaces) { + header.splice(4, 1); + } + reporter.table(header, body); return 1; } return 0; diff --git a/src/cli/commands/remove.js b/src/cli/commands/remove.js index dd6b83cc9e..53eea0b239 100644 --- a/src/cli/commands/remove.js +++ b/src/cli/commands/remove.js @@ -76,7 +76,8 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg // reinstall so we can get the updated lockfile reporter.step(++step, totalSteps, reporter.lang('uninstallRegenerate')); - const reinstall = new Install({force: true, ...flags}, config, new NoopReporter(), lockfile); + const installFlags = {force: true, workspaceRootIsCwd: true, ...flags}; + const reinstall = new Install(installFlags, config, new NoopReporter(), lockfile); await reinstall.init(); // diff --git a/src/cli/commands/upgrade-interactive.js b/src/cli/commands/upgrade-interactive.js index cae9d18860..3d94befc3e 100644 --- a/src/cli/commands/upgrade-interactive.js +++ b/src/cli/commands/upgrade-interactive.js @@ -10,6 +10,8 @@ import {getOutdated} from './upgrade.js'; import colorForVersions from '../../util/color-for-versions'; import colorizeDiff from '../../util/colorize-diff.js'; +const path = require('path'); + export const requireLockfile = true; export function setFlags(commander: Object) { @@ -35,18 +37,21 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg const outdatedFieldName = flags.latest ? 'latest' : 'wanted'; const lockfile = await Lockfile.fromDirectory(config.lockfileFolder); - const deps = await getOutdated(config, reporter, flags, lockfile, args); + const deps = await getOutdated(config, reporter, {...flags, includeWorkspaceDeps: true}, lockfile, args); if (deps.length === 0) { reporter.success(reporter.lang('allDependenciesUpToDate')); return; } + const usesWorkspaces = !!config.workspaceRootFolder; + const maxLengthArr = { name: 'name'.length, current: 'from'.length, range: 'latest'.length, [outdatedFieldName]: 'to'.length, + workspaceName: 'workspace'.length, }; const keysWithDynamicLength = ['name', 'current', outdatedFieldName]; @@ -56,6 +61,10 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg keysWithDynamicLength.push('range'); } + if (usesWorkspaces) { + keysWithDynamicLength.push('workspaceName'); + } + deps.forEach(dep => keysWithDynamicLength.forEach(key => { maxLengthArr[key] = Math.max(maxLengthArr[key], dep[key].length); @@ -78,7 +87,12 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg const latest = colorizeDiff(dep.current, padding(outdatedFieldName), reporter); const url = reporter.format.cyan(dep.url); const range = reporter.format.blue(flags.latest ? 'latest' : padding('range')); - return `${name} ${range} ${current} ❯ ${latest} ${url}`; + if (usesWorkspaces) { + const workspace = padding('workspaceName'); + return `${name} ${range} ${current} ❯ ${latest} ${workspace} ${url}`; + } else { + return `${name} ${range} ${current} ❯ ${latest} ${url}`; + } }; const makeHeaderRow = () => { @@ -87,7 +101,12 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg const from = headerPadding('from', 'current'); const to = headerPadding('to', outdatedFieldName); const url = reporter.format.bold.underline('url'); - return ` ${name} ${range} ${from} ${to} ${url}`; + if (usesWorkspaces) { + const workspace = headerPadding('workspace', 'workspaceName'); + return ` ${name} ${range} ${from} ${to} ${workspace} ${url}`; + } else { + return ` ${name} ${range} ${from} ${to} ${url}`; + } }; const groupedDeps = deps.reduce((acc, dep) => { @@ -130,25 +149,34 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg const getPattern = ({upgradeTo}) => upgradeTo; const isHint = x => ({hint}) => hint === x; - await [null, 'dev', 'optional', 'peer'].reduce(async (promise, hint) => { - // Wait for previous promise to resolve - await promise; + for (const hint of [null, 'dev', 'optional', 'peer']) { // Reset dependency flags flags.dev = hint === 'dev'; flags.peer = hint === 'peer'; flags.optional = hint === 'optional'; flags.ignoreWorkspaceRootCheck = true; - const deps = answers.filter(isHint(hint)).map(getPattern); + flags.includeWorkspaceDeps = false; + flags.workspaceRootIsCwd = false; + const deps = answers.filter(isHint(hint)); if (deps.length) { - for (const pattern of deps) { - lockfile.removePattern(pattern); + const depsByWorkspace = deps.reduce((acc, dep) => { + const {workspaceLoc} = dep; + const xs = acc[workspaceLoc] || []; + acc[workspaceLoc] = xs.concat(dep); + return acc; + }, {}); + for (const loc of Object.keys(depsByWorkspace)) { + const patterns = depsByWorkspace[loc].map(getPattern); + for (const pattern of patterns) { + lockfile.removePattern(pattern); + } + reporter.info(reporter.lang('updateInstalling', getNameFromHint(hint))); + config.cwd = path.resolve(path.dirname(loc)); + const add = new Add(patterns, flags, config, reporter, lockfile); + await add.init(); } - reporter.info(reporter.lang('updateInstalling', getNameFromHint(hint))); - const add = new Add(deps, flags, config, reporter, lockfile); - return add.init(); } - return Promise.resolve(); - }, Promise.resolve()); + } } catch (e) { Promise.reject(e); } diff --git a/src/cli/commands/upgrade.js b/src/cli/commands/upgrade.js index c5d49f5dfc..009bbb0c99 100644 --- a/src/cli/commands/upgrade.js +++ b/src/cli/commands/upgrade.js @@ -41,6 +41,8 @@ function setUserRequestedPackageVersions(deps: Array, args: Array => { invariant(command, 'missing command'); @@ -425,7 +418,10 @@ export function main({ } // lockfile - const lockLoc = path.join(config.cwd, constants.LOCKFILE_FILENAME); + const lockLoc = path.join( + config.lockfileFolder || config.cwd, // lockfileFolder might not be set at this point + constants.LOCKFILE_FILENAME, + ); const lockfile = fs.existsSync(lockLoc) ? fs.readFileSync(lockLoc, 'utf8') : 'No lockfile'; log.push(`Lockfile: ${indent(lockfile)}`); @@ -485,6 +481,11 @@ export function main({ scriptsPrependNodePath: commander.scriptsPrependNodePath, }) .then(() => { + // lockfile check must happen after config.init sets lockfileFolder + if (command.requireLockfile && !fs.existsSync(path.join(config.lockfileFolder, constants.LOCKFILE_FILENAME))) { + throw new MessageError(reporter.lang('noRequiredLockfile')); + } + // option "no-progress" stored in yarn config const noProgressConfig = config.registries.yarn.getOption('no-progress'); diff --git a/src/package-request.js b/src/package-request.js index a6e8bfddeb..e7fc1bc8a6 100644 --- a/src/package-request.js +++ b/src/package-request.js @@ -362,7 +362,7 @@ export default class PackageRequest { } const deps = await Promise.all( - depReqPatterns.map(async ({pattern, hint}): Promise => { + depReqPatterns.map(async ({pattern, hint, workspaceName, workspaceLoc}): Promise => { const locked = lockfile.getLocked(pattern); if (!locked) { throw new MessageError(reporter.lang('lockfileOutdated')); @@ -384,7 +384,18 @@ export default class PackageRequest { ({latest, wanted, url} = await registry.checkOutdated(config, name, normalized.range)); } - return {name, current, wanted, latest, url, hint, range: normalized.range, upgradeTo: ''}; + return { + name, + current, + wanted, + latest, + url, + hint, + range: normalized.range, + upgradeTo: '', + workspaceName: workspaceName || '', + workspaceLoc: workspaceLoc || '', + }; }), ); diff --git a/src/types.js b/src/types.js index b259d9adfb..63b3a459e2 100644 --- a/src/types.js +++ b/src/types.js @@ -21,6 +21,8 @@ export type DependencyRequestPattern = { hint?: ?string, parentNames?: Array, parentRequest?: ?PackageRequest, + workspaceName?: string, + workspaceLoc?: string, }; export type DependencyRequestPatterns = Array; @@ -156,6 +158,8 @@ export type Dependency = { hint: ?string, range: string, upgradeTo: string, + workspaceName: string, + workspaceLoc: string, }; export type WorkspacesManifestMap = {