Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions __tests__/commands/add.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,24 @@ test.concurrent("add with --dev shouldn't fail on the workspace root", async ()
});
});

test.concurrent('adding to the workspace root should preserve workspace packages in lockfile', async () => {
await runInstall({}, 'workspaces-install-basic', async (config, reporter): Promise<void> => {
await add(config, reporter, {dev: true}, ['[email protected]']);

expect(await fs.exists(`${config.cwd}/yarn.lock`)).toEqual(true);

const pkg = await fs.readJson(path.join(config.cwd, 'package.json'));
expect(pkg.devDependencies).toEqual({'max-safe-integer': '1.0.0'});
expect(pkg.dependencies).toEqual({'left-pad': '1.1.3'});

const lockfile = explodeLockfile(await fs.readFile(path.join(config.cwd, 'yarn.lock')));
expect(lockfile).toHaveLength(15);
expect(lockfile.indexOf('[email protected]:')).toEqual(0);
expect(lockfile.indexOf('[email protected]:')).toEqual(3);
expect(lockfile.indexOf('[email protected]:')).toEqual(6);
});
});

test.concurrent('adds any new package to the current workspace, but install from the workspace', async () => {
await runInstall({}, 'simple-worktree', async (config): Promise<void> => {
const inOut = new stream.PassThrough();
Expand Down
19 changes: 19 additions & 0 deletions __tests__/commands/outdated.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,22 @@ test.concurrent('displays correct dependency types', (): Promise<void> => {
expect(body[2][4]).toBe('devDependencies');
});
});

test.concurrent('works with workspace packages', async (): Promise<void> => {
// when invoked at root, only shows root outdated packages
await runOutdated([], {}, 'workspaces', (config, reporter, out): ?Promise<void> => {
const json: Object = JSON.parse(out);

expect(json.data.body).toHaveLength(1);
expect(json.data.body[0][0]).toBe('left-pad');
});

// when invoked in child, only shows child outdated packages
const childFixture = {source: 'workspaces', cwd: 'workspace-child'};
return runOutdated([], {}, childFixture, (config, reporter, out): ?Promise<void> => {
const json: Object = JSON.parse(out);

expect(json.data.body).toHaveLength(1);
expect(json.data.body[0][0]).toBe('max-safe-integer');
});
});
27 changes: 26 additions & 1 deletion __tests__/commands/remove.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* @flow */

import {ConsoleReporter} from '../../src/reporters/index.js';
import {run as buildRun, explodeLockfile} from './_helpers.js';
import {explodeLockfile, makeConfigFromDirectory, run as buildRun, runInstall} from './_helpers.js';
import {run as check} from '../../src/cli/commands/check.js';
import {run as remove} from '../../src/cli/commands/remove.js';
import * as fs from '../../src/util/fs.js';
Expand Down Expand Up @@ -133,3 +133,28 @@ test.concurrent('can prune the offline mirror', (): Promise<void> => {
expect(await fs.exists(path.join(config.cwd, `${mirrorPath}/dep-c-1.0.0.tgz`))).toEqual(true);
});
});

test.concurrent('removes from workspace packages', async () => {
await runInstall({}, 'workspaces-install-basic', async (config): Promise<void> => {
const reporter = new ConsoleReporter({});

expect(await fs.exists(`${config.cwd}/node_modules/isarray`)).toEqual(true);
expect(await fs.exists(`${config.cwd}/workspace-child/node_modules/isarray`)).toEqual(false);

const childConfig = await makeConfigFromDirectory(`${config.cwd}/workspace-child`, reporter);
await remove(childConfig, reporter, {}, ['isarray']);
await check(childConfig, reporter, {verifyTree: true}, []);

expect(JSON.parse(await fs.readFile(path.join(config.cwd, 'workspace-child/package.json'))).dependencies).toEqual(
{},
);

expect(await fs.exists(`${config.cwd}/node_modules/isarray`)).toEqual(false);
expect(await fs.exists(`${config.cwd}/workspace-child/node_modules/isarray`)).toEqual(false);

const lockFileContent = await fs.readFile(path.join(config.cwd, 'yarn.lock'));
const lockFileLines = explodeLockfile(lockFileContent);
expect(lockFileLines).toHaveLength(9);
expect(lockFileLines[0]).toEqual('[email protected]:');
});
});
43 changes: 43 additions & 0 deletions __tests__/commands/upgrade.js
Original file line number Diff line number Diff line change
Expand Up @@ -315,3 +315,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<void> => {
return runUpgrade(['[email protected]'], {latest: true}, 'workspaces', async (config): ?Promise<void> => {
const lockfile = explodeLockfile(await fs.readFile(path.join(config.cwd, 'yarn.lock')));

// child workspace deps
expect(lockfile.indexOf('[email protected]:')).toBeGreaterThanOrEqual(0);
expect(lockfile.indexOf('[email protected]:')).toBeGreaterThanOrEqual(0);
// root dep
expect(lockfile.indexOf('[email protected]:')).toBe(-1);
expect(lockfile.indexOf('[email protected]:')).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<void> => {
const fixture = {source: 'workspaces', cwd: 'child-a'};
return runUpgrade(['[email protected]'], {latest: true}, fixture, async (config): ?Promise<void> => {
const lockfile = explodeLockfile(await fs.readFile(path.join(config.lockfileFolder, 'yarn.lock')));

// untouched deps
expect(lockfile.indexOf('[email protected]:')).toBeGreaterThanOrEqual(0);
expect(lockfile.indexOf('[email protected]:')).toBeGreaterThanOrEqual(0);
// upgraded child workspace
expect(lockfile.indexOf('[email protected]:')).toBe(-1);
expect(lockfile.indexOf('[email protected]:')).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');
});
});
10 changes: 10 additions & 0 deletions __tests__/fixtures/outdated/workspaces/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "my-project",
"private": true,
"devDependencies": {
"left-pad": "1.0.0"
},
"workspaces": [
"workspace-child"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "workspace-1",
"version": "1.0.0",
"dependencies": {
"max-safe-integer": "1.0.0"
}
}
11 changes: 11 additions & 0 deletions __tests__/fixtures/outdated/workspaces/yarn.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


[email protected]:
version "1.0.0"
resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.0.0.tgz#c84e2417581bbb8eaf2b9e3d7a122e572ab1af37"

[email protected]:
version "1.0.0"
resolved "https://registry.yarnpkg.com/max-safe-integer/-/max-safe-integer-1.0.0.tgz#4662073a02c7e02d38153e25795489b20be6f01a"
Binary file not shown.
7 changes: 7 additions & 0 deletions __tests__/fixtures/upgrade/workspaces/child-a/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "child-a",
"version": "1.0.0",
"dependencies": {
"left-pad": "1.0.0"
}
}
7 changes: 7 additions & 0 deletions __tests__/fixtures/upgrade/workspaces/child-b/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "child-b",
"version": "1.0.0",
"dependencies": {
"right-pad": "1.0.0"
}
}
11 changes: 11 additions & 0 deletions __tests__/fixtures/upgrade/workspaces/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "my-project",
"private": true,
"devDependencies": {
"max-safe-integer": "1.0.0"
},
"workspaces": [
"child-a",
"child-b"
]
}
15 changes: 15 additions & 0 deletions __tests__/fixtures/upgrade/workspaces/yarn.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


[email protected]:
version "1.0.0"
resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.0.0.tgz#c84e2417581bbb8eaf2b9e3d7a122e572ab1af37"

[email protected]:
version "1.0.0"
resolved "https://registry.yarnpkg.com/max-safe-integer/-/max-safe-integer-1.0.0.tgz#4662073a02c7e02d38153e25795489b20be6f01a"

[email protected]:
version "1.0.0"
resolved "https://registry.yarnpkg.com/right-pad/-/right-pad-1.0.0.tgz#5ba6e56c0d7ec162d3626315c27a61f8aff42f15"
5 changes: 3 additions & 2 deletions src/cli/commands/add.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class Add extends Install {
.shift();

if (this.config.workspaceRootFolder && this.config.cwd === this.config.workspaceRootFolder) {
this.setIgnoreWorkspaces(true);
this.inWorkspaceRoot = true;
// flagsToOrgin defaults to being a hard `dependency` when no flags are passed (see above),
// so it incorrectly throws a warning when upgrading existing devDependencies in workspace root
// To allow for a successful upgrade, override flagsToOrigin when `existing` flag is passed by `upgrade` command
Expand All @@ -46,6 +46,7 @@ export class Add extends Install {

args: Array<string>;
flagToOrigin: string;
inWorkspaceRoot: boolean;
addedPatterns: Array<string>;

/**
Expand Down Expand Up @@ -132,7 +133,7 @@ export class Add extends Install {
*/

async init(): Promise<Array<string>> {
if (this.ignoreWorkspaces) {
if (this.inWorkspaceRoot) {
if (this.flagToOrigin === 'dependencies') {
throw new MessageError(this.reporter.lang('workspacesPreferDevDependencies'));
}
Expand Down
61 changes: 42 additions & 19 deletions src/cli/commands/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,6 @@ export class Install {
this.integrityChecker = new InstallationIntegrityChecker(config);
this.linker = new PackageLinker(config, this.resolver);
this.scripts = new PackageInstallScripts(config, this.resolver, this.flags.force);
this.ignoreWorkspaces = false;
}

flags: Flags;
Expand All @@ -192,7 +191,6 @@ export class Install {
rootPatternsToOrigin: {[pattern: string]: string};
integrityChecker: InstallationIntegrityChecker;
resolutionMap: ResolutionMap;
ignoreWorkspaces: boolean;

/**
* Create a list of dependency requests from the current directories manifests.
Expand All @@ -211,6 +209,9 @@ export class Install {
const usedPatterns = [];
let workspaceLayout;

// non-workspaces are always root, otherwise check for workspace root
const cwdIsRoot = !this.config.workspaceRootFolder || this.config.lockfileFolder === this.config.cwd;

// exclude package names that are in install args
const excludeNames = [];
for (const pattern of excludePatterns) {
Expand All @@ -224,17 +225,31 @@ 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(this.config.cwd, filename);
if (!await fs.exists(loc)) {
continue;
}

this.rootManifestRegistries.push(registry);

const projectManifestJson = await this.config.readJson(loc);
await normalizeManifest(projectManifestJson, this.config.lockfileFolder, this.config, true);
await normalizeManifest(projectManifestJson, this.config.cwd, this.config, cwdIsRoot);

Object.assign(this.resolutions, projectManifestJson.resolutions);
Object.assign(manifest, projectManifestJson);
Expand Down Expand Up @@ -286,28 +301,43 @@ export class Install {
pushDeps('devDependencies', projectManifestJson, {hint: 'dev', optional: false}, !this.config.production);
pushDeps('optionalDependencies', projectManifestJson, {hint: 'optional', optional: true}, true);

if (this.config.workspaceRootFolder && !this.ignoreWorkspaces) {
const workspacesRoot = path.dirname(loc);
const workspaces = await this.config.resolveWorkspaces(workspacesRoot, projectManifestJson);
if (this.config.workspaceRootFolder) {
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)) {
workspaceDependencies[workspaceName] = workspaces[workspaceName].manifest.version;
}
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);
}

Expand All @@ -329,13 +359,6 @@ export class Install {
};
}

/**
* Sets the value of `ignoreWorkspaces` for install commands that should skip workspaces
*/
setIgnoreWorkspaces(ignoreWorkspaces: boolean) {
this.ignoreWorkspaces = ignoreWorkspaces;
}

/**
* TODO description
*/
Expand Down
2 changes: 1 addition & 1 deletion src/cli/commands/outdated.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function hasWrapper(commander: Object, args: Array<string>): boolean {
}

export async function run(config: Config, reporter: Reporter, flags: Object, args: Array<string>): Promise<number> {
const lockfile = await Lockfile.fromDirectory(config.cwd);
const lockfile = await Lockfile.fromDirectory(config.lockfileFolder);
const install = new Install(flags, config, reporter, lockfile);
let deps = await PackageRequest.getOutdatedPackages(lockfile, install, config, reporter);

Expand Down
2 changes: 1 addition & 1 deletion src/cli/commands/remove.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
let step = 0;

// load manifests
const lockfile = await Lockfile.fromDirectory(config.cwd);
const lockfile = await Lockfile.fromDirectory(config.lockfileFolder);
const rootManifests = await config.getRootManifests();
const manifests = [];

Expand Down
Loading