mirror of
https://github.com/zhigang1992/yarn.git
synced 2026-04-29 09:45:02 +08:00
Workspace phase 3 & 4 (#3516)
* Workspaces phase 3 & 4 * fixed check command * addressed comments from @arcanis * returned if worksapce condititon
This commit is contained in:
@@ -42,7 +42,7 @@ const runAdd = buildRun.bind(
|
||||
},
|
||||
);
|
||||
|
||||
test.concurrent('adds any new package to the current workspace, but install from the worktree', async () => {
|
||||
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();
|
||||
const reporter = new reporters.JSONReporter({stdout: inOut});
|
||||
|
||||
@@ -117,5 +117,3 @@ test('Only top level (after hoisting) bin links should be linked', (): Promise<v
|
||||
expect(await linkAt(config, 'node_modules', '.bin', 'eslint')).toEqual('../eslint/bin/eslint.js');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -1,34 +1,74 @@
|
||||
/* @flow */
|
||||
|
||||
import {run as check} from '../../../src/cli/commands/check.js';
|
||||
import {Install} from '../../../src/cli/commands/install.js';
|
||||
import * as reporters from '../../../src/reporters/index.js';
|
||||
import * as fs from '../../../src/util/fs.js';
|
||||
import {runInstall} from '../_helpers.js';
|
||||
import {runInstall, run as buildRun} from '../_helpers.js';
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 150000;
|
||||
|
||||
const path = require('path');
|
||||
|
||||
test.concurrent("workspaces don't work without a configuration in .yarnrc", async (): Promise<void> => {
|
||||
let thrown = false;
|
||||
let error = '';
|
||||
const reporter = new reporters.ConsoleReporter({});
|
||||
try {
|
||||
await runInstall({}, 'workspaces-install-enabled');
|
||||
} catch (e) {
|
||||
thrown = true;
|
||||
error = e.message;
|
||||
}
|
||||
expect(thrown).toBe(true);
|
||||
expect(error).toContain(reporter.lang('workspaceExperimentalDisabled'));
|
||||
});
|
||||
|
||||
test.concurrent("workspaces don't work on non private projects", async (): Promise<void> => {
|
||||
let thrown = false;
|
||||
let error = '';
|
||||
const reporter = new reporters.ConsoleReporter({});
|
||||
try {
|
||||
await runInstall({}, 'workspaces-install-private');
|
||||
} catch (e) {
|
||||
thrown = true;
|
||||
error = e.message;
|
||||
}
|
||||
expect(thrown).toBe(true);
|
||||
expect(error).toContain(reporter.lang('workspacesRequirePrivateProjects'));
|
||||
});
|
||||
|
||||
test.concurrent('installs workspaces into root folder', (): Promise<void> => {
|
||||
test.concurrent("workspaces don't work with duplicate names", async (): Promise<void> => {
|
||||
let error = '';
|
||||
const reporter = new reporters.ConsoleReporter({});
|
||||
try {
|
||||
await runInstall({}, 'workspaces-install-duplicate');
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
}
|
||||
expect(error).toContain(reporter.lang('workspaceNameDuplicate', 'workspace-1'));
|
||||
});
|
||||
|
||||
test.concurrent("workspaces warn and get ignored if they don't have a name and a version", (): Promise<void> => {
|
||||
return buildRun(
|
||||
reporters.BufferReporter,
|
||||
path.join(__dirname, '..', '..', 'fixtures', 'install'),
|
||||
async (args, flags, config, reporter, lockfile): Promise<void> => {
|
||||
const install = new Install(flags, config, reporter, lockfile);
|
||||
await install.init();
|
||||
const warnings = reporter.getBuffer();
|
||||
expect(
|
||||
warnings.some(warning => {
|
||||
return warning.data.toString().toLowerCase().includes('missing version in workspace');
|
||||
}),
|
||||
).toEqual(true);
|
||||
expect(
|
||||
warnings.some(warning => {
|
||||
return warning.data.toString().toLowerCase().includes('missing name in workspace');
|
||||
}),
|
||||
).toEqual(true);
|
||||
},
|
||||
[],
|
||||
{},
|
||||
'workspaces-install-mandatory-fields',
|
||||
);
|
||||
});
|
||||
|
||||
test.concurrent('installs workspaces dependencies into root folder', (): Promise<void> => {
|
||||
return runInstall({}, 'workspaces-install-basic', async (config): Promise<void> => {
|
||||
const lockfile = await fs.readFile(path.join(config.cwd, 'yarn.lock'));
|
||||
expect(lockfile.indexOf('isarray')).toBeGreaterThanOrEqual(0);
|
||||
@@ -48,6 +88,109 @@ test.concurrent('installs workspaces into root folder', (): Promise<void> => {
|
||||
});
|
||||
});
|
||||
|
||||
test.concurrent('install should install unhoistable dependencies in workspace node_modules', (): Promise<void> => {
|
||||
return runInstall({}, 'workspaces-install-conflict', async (config): Promise<void> => {
|
||||
// node_modules/left-pad@1.1.3
|
||||
let packageFile = await fs.readFile(path.join(config.cwd, 'node_modules', 'left-pad', 'package.json'));
|
||||
expect(JSON.parse(packageFile).version).toEqual('1.1.3');
|
||||
|
||||
// node_modules/workspace-child/left-pad@1.1.1
|
||||
packageFile = await fs.readFile(
|
||||
path.join(config.cwd, 'workspace-child', 'node_modules', 'left-pad', 'package.json'),
|
||||
);
|
||||
expect(JSON.parse(packageFile).version).toEqual('1.1.1');
|
||||
});
|
||||
});
|
||||
|
||||
test.concurrent('install should link workspaces that refer each other', (): Promise<void> => {
|
||||
return runInstall({}, 'workspaces-install-link', async (config): Promise<void> => {
|
||||
// packages/workspace-1/node_modules/left-pad - missing because it is hoisted to the root
|
||||
expect(await fs.exists(path.join(config.cwd, 'packages', 'workspace-1', 'node_modules'))).toBe(false);
|
||||
|
||||
// node_modules/left-pad - link
|
||||
const packageFile = await fs.readFile(path.join(config.cwd, 'node_modules', 'left-pad', 'package.json'));
|
||||
expect(JSON.parse(packageFile).version).toEqual('1.1.2');
|
||||
const readme = await fs.readFile(path.join(config.cwd, 'node_modules', 'left-pad', 'README.md'));
|
||||
expect(readme.split('\n')[0]).toEqual('WORKSPACES ROCK!');
|
||||
});
|
||||
});
|
||||
|
||||
test.concurrent(
|
||||
'install should not link workspaces that refer not compatible version of another workspace',
|
||||
(): Promise<void> => {
|
||||
return runInstall({}, 'workspaces-install-link', async (config): Promise<void> => {
|
||||
// packages/workspace-2/node_modules/left-pad - from npm
|
||||
const packageFile = await fs.readFile(
|
||||
path.join(config.cwd, 'packages', 'workspace-2', 'node_modules', 'left-pad', 'package.json'),
|
||||
);
|
||||
const readme = await fs.readFile(
|
||||
path.join(config.cwd, 'packages', 'workspace-2', 'node_modules', 'left-pad', 'README.md'),
|
||||
);
|
||||
expect(JSON.parse(packageFile).version).not.toBe('1.1.2');
|
||||
expect(readme.split('\n')[0]).not.toEqual('WORKSPACES ROCK!');
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.concurrent('install should prioritize non workspace dependency at root over the workspace symlink', (): Promise<
|
||||
void,
|
||||
> => {
|
||||
return runInstall({}, 'workspaces-install-link-root', async (config): Promise<void> => {
|
||||
// node_modules/left-pad - from npm
|
||||
let packageFile = await fs.readFile(path.join(config.cwd, 'node_modules', 'left-pad', 'package.json'));
|
||||
expect(JSON.parse(packageFile).version).toEqual('1.1.3');
|
||||
let readme = await fs.readFile(path.join(config.cwd, 'node_modules', 'left-pad', 'README.md'));
|
||||
expect(readme.split('\n')[0]).not.toEqual('WORKSPACES ROCK!');
|
||||
|
||||
// node_modules/workspace-1/left-pad - link
|
||||
packageFile = await fs.readFile(
|
||||
path.join(config.cwd, 'packages', 'workspace-1', 'node_modules', 'left-pad', 'package.json'),
|
||||
);
|
||||
expect(JSON.parse(packageFile).version).toEqual('1.1.2');
|
||||
readme = await fs.readFile(
|
||||
path.join(config.cwd, 'packages', 'workspace-1', 'node_modules', 'left-pad', 'README.md'),
|
||||
);
|
||||
expect(readme.split('\n')[0]).toEqual('WORKSPACES ROCK!');
|
||||
|
||||
// packages/workspace-2/node_modules/left-pad - missing because it is hoisted to the root
|
||||
expect(await fs.exists(path.join(config.cwd, 'packages', 'workspace-2', 'node_modules'))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
test.concurrent('install should install subedependencies of workspaces', (): Promise<void> => {
|
||||
// the tricky part is that isarray is a subdependency of left-pad that is not referenced in the root
|
||||
// but another workspace
|
||||
return runInstall({}, 'workspaces-install-subdeps', async (config): Promise<void> => {
|
||||
expect(await fs.exists(path.join(config.cwd, 'node_modules', 'isarray'))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test.concurrent(
|
||||
'install should install subedependencies of workspaces that are not referenced in other workspaces',
|
||||
(): Promise<void> => {
|
||||
// the tricky part is that left-pad is not a dependency of root
|
||||
return runInstall({}, 'workspaces-install-subdeps-2', async (config): Promise<void> => {
|
||||
expect(await fs.exists(path.join(config.cwd, 'node_modules', 'isarray'))).toBe(true);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.concurrent('install should install dev dependencies of workspaces', (): Promise<void> => {
|
||||
// the tricky part is that left-pad is not a dependency of root
|
||||
return runInstall({}, 'workspaces-install-subdeps-dev', async (config): Promise<void> => {
|
||||
expect(await fs.exists(path.join(config.cwd, 'node_modules', 'left-pad'))).toBe(true);
|
||||
expect(await fs.exists(path.join(config.cwd, 'packages', 'workspace-1', 'node_modules', 'left-pad'))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test.concurrent('install should not install dev dependencies of workspaces in production mode', (): Promise<void> => {
|
||||
// the tricky part is that left-pad is not a dependency of root
|
||||
return runInstall({production: true}, 'workspaces-install-subdeps-dev', async (config): Promise<void> => {
|
||||
expect(await fs.exists(path.join(config.cwd, 'node_modules', 'left-pad'))).toBe(true);
|
||||
expect(await fs.exists(path.join(config.cwd, 'packages', 'workspace-1', 'node_modules', 'left-pad'))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
test.concurrent('check command should work', (): Promise<void> => {
|
||||
return runInstall({checkFiles: true}, 'workspaces-install-basic', async (config, reporter): Promise<void> => {
|
||||
// check command + integrity check
|
||||
@@ -62,14 +205,4 @@ test.concurrent('check command should work', (): Promise<void> => {
|
||||
});
|
||||
});
|
||||
|
||||
test.concurrent('install should fail if a workspace has a conflicting version of a dependency', async (): Promise<
|
||||
void,
|
||||
> => {
|
||||
let thrown = false;
|
||||
try {
|
||||
await runInstall({}, 'workspaces-install-conflict');
|
||||
} catch (e) {
|
||||
thrown = true;
|
||||
}
|
||||
expect(thrown).toBe(true);
|
||||
});
|
||||
// TODO need more thorough tests for all kinds of checks: integrity, verify-tree
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"name": "package-a",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"name": "package-b",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "workspace-2",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"right-pad": "1.0.1"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "workspace-3",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"repeat-string": "1.6.1"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "workspace-1",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"isarray": "2.0.1"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "workspace-1",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"left-pad": "1.1.1"
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
workspaces-experimental true
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "my-project",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "workspace-1",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"right-pad": "1.0.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "workspace-1",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"right-pad": "1.0.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
workspaces-experimental true
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "my-project",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"left-pad": "^1.1.3"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
WORKSPACES ROCK!
|
||||
@@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
module.exports = leftPad;
|
||||
|
||||
var cache = ['', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '];
|
||||
|
||||
function leftPad(str, len, ch) {
|
||||
// convert `str` to `string`
|
||||
str = str + '';
|
||||
// `len` is the `pad`'s length now
|
||||
len = len - str.length;
|
||||
// doesn't need to pad
|
||||
if (len <= 0) return str;
|
||||
// `ch` defaults to `' '`
|
||||
if (!ch && ch !== 0) ch = ' ';
|
||||
// convert `ch` to `string`
|
||||
ch = ch + '';
|
||||
// cache common use cases
|
||||
if (ch === ' ' && len < 10) return cache[len] + str;
|
||||
// `pad` starts with an empty string
|
||||
var pad = '';
|
||||
// loop
|
||||
while (true) {
|
||||
// add `ch` to `pad` if `len` is odd
|
||||
if (len & 1) pad += ch;
|
||||
// devide `len` by 2, ditch the fraction
|
||||
len >>= 1;
|
||||
// "double" the `ch` so this operation count grows logarithmically on `len`
|
||||
// each time `ch` is "doubled", the `len` would need to be "doubled" too
|
||||
// similar to finding a value in binary search tree, hence O(log(n))
|
||||
if (len) ch += ch;
|
||||
else
|
||||
// `len` is 0, exit the loop
|
||||
break;
|
||||
}
|
||||
// pad `str`!
|
||||
return pad + str;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "left-pad",
|
||||
"version": "1.1.2",
|
||||
"description": "String left pad",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "node test",
|
||||
"bench": "node perf/perf.js"
|
||||
},
|
||||
"keywords": [
|
||||
"leftpad",
|
||||
"left",
|
||||
"pad",
|
||||
"padding",
|
||||
"string",
|
||||
"repeat"
|
||||
],
|
||||
"repository": {
|
||||
"url": "git@github.com:stevemao/left-pad.git",
|
||||
"type": "git"
|
||||
},
|
||||
"author": "azer",
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Cameron Westland",
|
||||
"email": "camwest@gmail.com"
|
||||
}
|
||||
],
|
||||
"license": "WTFPL"
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
var leftPad = require('./');
|
||||
var test = require('tape');
|
||||
|
||||
test('left pad', function(assert) {
|
||||
assert.plan(7);
|
||||
assert.strictEqual(leftPad('foo', 5), ' foo');
|
||||
assert.strictEqual(leftPad('foobar', 6), 'foobar');
|
||||
assert.strictEqual(leftPad(1, 2, 0), '01');
|
||||
assert.strictEqual(leftPad(1, 2, '-'), '-1');
|
||||
assert.strictEqual(leftPad('foo', 2, ' '), 'foo');
|
||||
assert.strictEqual(leftPad('foo', -1, ' '), 'foo');
|
||||
assert.strictEqual(leftPad('foo', 7, 1), '1111foo');
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "workspace-1",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"left-pad": "1.1.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "workspace-2",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"left-pad": "^1.1.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
workspaces-experimental true
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "my-project",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"left-pad": "^1.0.0"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
WORKSPACES ROCK!
|
||||
@@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
module.exports = leftPad;
|
||||
|
||||
var cache = ['', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '];
|
||||
|
||||
function leftPad(str, len, ch) {
|
||||
// convert `str` to `string`
|
||||
str = str + '';
|
||||
// `len` is the `pad`'s length now
|
||||
len = len - str.length;
|
||||
// doesn't need to pad
|
||||
if (len <= 0) return str;
|
||||
// `ch` defaults to `' '`
|
||||
if (!ch && ch !== 0) ch = ' ';
|
||||
// convert `ch` to `string`
|
||||
ch = ch + '';
|
||||
// cache common use cases
|
||||
if (ch === ' ' && len < 10) return cache[len] + str;
|
||||
// `pad` starts with an empty string
|
||||
var pad = '';
|
||||
// loop
|
||||
while (true) {
|
||||
// add `ch` to `pad` if `len` is odd
|
||||
if (len & 1) pad += ch;
|
||||
// devide `len` by 2, ditch the fraction
|
||||
len >>= 1;
|
||||
// "double" the `ch` so this operation count grows logarithmically on `len`
|
||||
// each time `ch` is "doubled", the `len` would need to be "doubled" too
|
||||
// similar to finding a value in binary search tree, hence O(log(n))
|
||||
if (len) ch += ch;
|
||||
else
|
||||
// `len` is 0, exit the loop
|
||||
break;
|
||||
}
|
||||
// pad `str`!
|
||||
return pad + str;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "left-pad",
|
||||
"version": "1.1.2",
|
||||
"description": "String left pad",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "node test",
|
||||
"bench": "node perf/perf.js"
|
||||
},
|
||||
"keywords": [
|
||||
"leftpad",
|
||||
"left",
|
||||
"pad",
|
||||
"padding",
|
||||
"string",
|
||||
"repeat"
|
||||
],
|
||||
"repository": {
|
||||
"url": "git@github.com:stevemao/left-pad.git",
|
||||
"type": "git"
|
||||
},
|
||||
"author": "azer",
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Cameron Westland",
|
||||
"email": "camwest@gmail.com"
|
||||
}
|
||||
],
|
||||
"license": "WTFPL"
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
var leftPad = require('./');
|
||||
var test = require('tape');
|
||||
|
||||
test('left pad', function(assert) {
|
||||
assert.plan(7);
|
||||
assert.strictEqual(leftPad('foo', 5), ' foo');
|
||||
assert.strictEqual(leftPad('foobar', 6), 'foobar');
|
||||
assert.strictEqual(leftPad(1, 2, 0), '01');
|
||||
assert.strictEqual(leftPad(1, 2, '-'), '-1');
|
||||
assert.strictEqual(leftPad('foo', 2, ' '), 'foo');
|
||||
assert.strictEqual(leftPad('foo', -1, ' '), 'foo');
|
||||
assert.strictEqual(leftPad('foo', 7, 1), '1111foo');
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "workspace-1",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"left-pad": "1.1.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "workspace-2",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"left-pad": "^1.1.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
workspaces-experimental true
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "my-project",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "workspace-1",
|
||||
"dependencies": {
|
||||
"right-pad": "1.0.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"right-pad": "1.0.1"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "workspace-1",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"isarray": "2.0.1"
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
workspaces-experimental true
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "my-project",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"left-pad": "^1.1.3"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
WORKSPACES ROCK!
|
||||
@@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
module.exports = leftPad;
|
||||
|
||||
var cache = ['', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '];
|
||||
|
||||
function leftPad(str, len, ch) {
|
||||
// convert `str` to `string`
|
||||
str = str + '';
|
||||
// `len` is the `pad`'s length now
|
||||
len = len - str.length;
|
||||
// doesn't need to pad
|
||||
if (len <= 0) return str;
|
||||
// `ch` defaults to `' '`
|
||||
if (!ch && ch !== 0) ch = ' ';
|
||||
// convert `ch` to `string`
|
||||
ch = ch + '';
|
||||
// cache common use cases
|
||||
if (ch === ' ' && len < 10) return cache[len] + str;
|
||||
// `pad` starts with an empty string
|
||||
var pad = '';
|
||||
// loop
|
||||
while (true) {
|
||||
// add `ch` to `pad` if `len` is odd
|
||||
if (len & 1) pad += ch;
|
||||
// devide `len` by 2, ditch the fraction
|
||||
len >>= 1;
|
||||
// "double" the `ch` so this operation count grows logarithmically on `len`
|
||||
// each time `ch` is "doubled", the `len` would need to be "doubled" too
|
||||
// similar to finding a value in binary search tree, hence O(log(n))
|
||||
if (len) ch += ch;
|
||||
else
|
||||
// `len` is 0, exit the loop
|
||||
break;
|
||||
}
|
||||
// pad `str`!
|
||||
return pad + str;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "left-pad",
|
||||
"version": "1.1.2",
|
||||
"description": "String left pad",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "node test",
|
||||
"bench": "node perf/perf.js"
|
||||
},
|
||||
"keywords": [
|
||||
"leftpad",
|
||||
"left",
|
||||
"pad",
|
||||
"padding",
|
||||
"string",
|
||||
"repeat"
|
||||
],
|
||||
"dependencies": {
|
||||
"isarray": "^1.0.0"
|
||||
},
|
||||
"repository": {
|
||||
"url": "git@github.com:stevemao/left-pad.git",
|
||||
"type": "git"
|
||||
},
|
||||
"author": "azer",
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Cameron Westland",
|
||||
"email": "camwest@gmail.com"
|
||||
}
|
||||
],
|
||||
"license": "WTFPL"
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
var leftPad = require('./');
|
||||
var test = require('tape');
|
||||
|
||||
test('left pad', function(assert) {
|
||||
assert.plan(7);
|
||||
assert.strictEqual(leftPad('foo', 5), ' foo');
|
||||
assert.strictEqual(leftPad('foobar', 6), 'foobar');
|
||||
assert.strictEqual(leftPad(1, 2, 0), '01');
|
||||
assert.strictEqual(leftPad(1, 2, '-'), '-1');
|
||||
assert.strictEqual(leftPad('foo', 2, ' '), 'foo');
|
||||
assert.strictEqual(leftPad('foo', -1, ' '), 'foo');
|
||||
assert.strictEqual(leftPad('foo', 7, 1), '1111foo');
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
workspaces-experimental true
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "my-project",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"left-pad": "^1.1.3"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "workspace-1",
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"left-pad": "1.1.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
workspaces-experimental true
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "my-project",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"left-pad": "^1.1.3"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
WORKSPACES ROCK!
|
||||
@@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
module.exports = leftPad;
|
||||
|
||||
var cache = ['', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '];
|
||||
|
||||
function leftPad(str, len, ch) {
|
||||
// convert `str` to `string`
|
||||
str = str + '';
|
||||
// `len` is the `pad`'s length now
|
||||
len = len - str.length;
|
||||
// doesn't need to pad
|
||||
if (len <= 0) return str;
|
||||
// `ch` defaults to `' '`
|
||||
if (!ch && ch !== 0) ch = ' ';
|
||||
// convert `ch` to `string`
|
||||
ch = ch + '';
|
||||
// cache common use cases
|
||||
if (ch === ' ' && len < 10) return cache[len] + str;
|
||||
// `pad` starts with an empty string
|
||||
var pad = '';
|
||||
// loop
|
||||
while (true) {
|
||||
// add `ch` to `pad` if `len` is odd
|
||||
if (len & 1) pad += ch;
|
||||
// devide `len` by 2, ditch the fraction
|
||||
len >>= 1;
|
||||
// "double" the `ch` so this operation count grows logarithmically on `len`
|
||||
// each time `ch` is "doubled", the `len` would need to be "doubled" too
|
||||
// similar to finding a value in binary search tree, hence O(log(n))
|
||||
if (len) ch += ch;
|
||||
else
|
||||
// `len` is 0, exit the loop
|
||||
break;
|
||||
}
|
||||
// pad `str`!
|
||||
return pad + str;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "left-pad",
|
||||
"version": "1.1.2",
|
||||
"description": "String left pad",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "node test",
|
||||
"bench": "node perf/perf.js"
|
||||
},
|
||||
"keywords": [
|
||||
"leftpad",
|
||||
"left",
|
||||
"pad",
|
||||
"padding",
|
||||
"string",
|
||||
"repeat"
|
||||
],
|
||||
"dependencies": {
|
||||
"isarray": "^1.0.0"
|
||||
},
|
||||
"repository": {
|
||||
"url": "git@github.com:stevemao/left-pad.git",
|
||||
"type": "git"
|
||||
},
|
||||
"author": "azer",
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Cameron Westland",
|
||||
"email": "camwest@gmail.com"
|
||||
}
|
||||
],
|
||||
"license": "WTFPL"
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
var leftPad = require('./');
|
||||
var test = require('tape');
|
||||
|
||||
test('left pad', function(assert) {
|
||||
assert.plan(7);
|
||||
assert.strictEqual(leftPad('foo', 5), ' foo');
|
||||
assert.strictEqual(leftPad('foobar', 6), 'foobar');
|
||||
assert.strictEqual(leftPad(1, 2, 0), '01');
|
||||
assert.strictEqual(leftPad(1, 2, '-'), '-1');
|
||||
assert.strictEqual(leftPad('foo', 2, ' '), 'foo');
|
||||
assert.strictEqual(leftPad('foo', -1, ' '), 'foo');
|
||||
assert.strictEqual(leftPad('foo', 7, 1), '1111foo');
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "workspace-1",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"left-pad": "1.1.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "workspace-2",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"left-pad": "^1.1.3"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -18,7 +18,10 @@ const prettier = isWindows ? 'prettier.cmd' : 'prettier';
|
||||
const prettierCmd = path.resolve(__dirname, '../node_modules/.bin/' + prettier);
|
||||
|
||||
const config = {
|
||||
ignore: ['**/node_modules/**'],
|
||||
ignore: [
|
||||
'**/node_modules/**',
|
||||
'__tests__/fixtures/**'
|
||||
],
|
||||
options: {
|
||||
'bracket-spacing': 'false',
|
||||
'print-width': 120,
|
||||
|
||||
@@ -165,9 +165,9 @@ async function integrityHashCheck(
|
||||
const install = new Install(flags, config, reporter, lockfile);
|
||||
|
||||
// get patterns that are installed when running `yarn install`
|
||||
const {patterns} = await install.fetchRequestFromCwd();
|
||||
const {patterns, workspaceLayout} = await install.fetchRequestFromCwd();
|
||||
|
||||
const match = await integrityChecker.check(patterns, lockfile.cache, flags);
|
||||
const match = await integrityChecker.check(patterns, lockfile.cache, flags, workspaceLayout);
|
||||
for (const pattern of match.missingPatterns) {
|
||||
reportError('lockfileNotContainPattern', pattern);
|
||||
}
|
||||
@@ -220,12 +220,12 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
|
||||
}
|
||||
|
||||
// get patterns that are installed when running `yarn install`
|
||||
const {patterns: rawPatterns} = await install.hydrate(true);
|
||||
const {patterns: rawPatterns, workspaceLayout} = await install.hydrate();
|
||||
const patterns = await install.flatten(rawPatterns);
|
||||
|
||||
// check if patterns exist in lockfile
|
||||
for (const pattern of patterns) {
|
||||
if (!lockfile.getLocked(pattern)) {
|
||||
if (!lockfile.getLocked(pattern) && (!workspaceLayout || !workspaceLayout.getManifestByPattern(pattern))) {
|
||||
reportError('lockfileNotContainPattern', pattern);
|
||||
}
|
||||
}
|
||||
@@ -266,7 +266,8 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
|
||||
|
||||
// skip unnecessary checks for linked dependencies
|
||||
const remoteType = pkg._reference.remote.type;
|
||||
const isLinkedDepencency = remoteType === 'link' || (remoteType === 'file' && config.linkFileDependencies);
|
||||
const isLinkedDepencency =
|
||||
remoteType === 'link' || remoteType === 'workspace' || (remoteType === 'file' && config.linkFileDependencies);
|
||||
if (isLinkedDepencency) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -254,9 +254,8 @@ class ImportPackageResolver extends PackageResolver {
|
||||
}
|
||||
}
|
||||
|
||||
async init(deps: DependencyRequestPatterns, isFlat: boolean, rootName?: string): Promise<void> {
|
||||
async init(deps: DependencyRequestPatterns, isFlat: boolean): Promise<void> {
|
||||
this.flat = isFlat;
|
||||
this.rootName = rootName || this.rootName;
|
||||
const activity = (this.activity = this.reporter.activity());
|
||||
await this.findAll(deps);
|
||||
this.resetOptional();
|
||||
@@ -278,7 +277,10 @@ export class Import extends Install {
|
||||
}
|
||||
await verifyTreeCheck(this.config, this.reporter, {}, []);
|
||||
const {requests, patterns, manifest} = await this.fetchRequestFromCwd();
|
||||
await this.resolver.init(requests, this.flags.flat, manifest.name);
|
||||
if (manifest.name && this.resolver instanceof ImportPackageResolver) {
|
||||
this.resolver.rootName = manifest.name;
|
||||
}
|
||||
await this.resolver.init(requests, this.flags.flat);
|
||||
const manifests: Array<Manifest> = await fetcher.fetch(this.resolver.getManifests(), this.config);
|
||||
this.resolver.updateManifests(manifests);
|
||||
await compatibility.check(this.resolver.getManifests(), this.config, this.flags.ignoreEngines);
|
||||
|
||||
@@ -23,12 +23,14 @@ import * as constants from '../../constants.js';
|
||||
import * as fs from '../../util/fs.js';
|
||||
import map from '../../util/map.js';
|
||||
import {version as YARN_VERSION, getInstallationMethod} from '../../util/yarn-version.js';
|
||||
import WorkspaceLayout from '../../workspace-layout.js';
|
||||
|
||||
const emoji = require('node-emoji');
|
||||
const invariant = require('invariant');
|
||||
const isCI = require('is-ci');
|
||||
const path = require('path');
|
||||
const semver = require('semver');
|
||||
const uuid = require('uuid');
|
||||
|
||||
const ONE_DAY = 1000 * 60 * 60 * 24;
|
||||
|
||||
@@ -38,6 +40,7 @@ export type InstallCwdRequest = {
|
||||
ignorePatterns: Array<string>,
|
||||
usedPatterns: Array<string>,
|
||||
manifest: Object,
|
||||
workspaceLayout?: WorkspaceLayout,
|
||||
};
|
||||
|
||||
type Flags = {
|
||||
@@ -194,11 +197,12 @@ export class Install {
|
||||
ignoreUnusedPatterns?: boolean = false,
|
||||
): Promise<InstallCwdRequest> {
|
||||
const patterns = [];
|
||||
const deps = [];
|
||||
const deps: DependencyRequestPatterns = [];
|
||||
const manifest = {};
|
||||
|
||||
const ignorePatterns = [];
|
||||
const usedPatterns = [];
|
||||
let workspaceLayout;
|
||||
|
||||
// exclude package names that are in install args
|
||||
const excludeNames = [];
|
||||
@@ -224,42 +228,10 @@ export class Install {
|
||||
const projectManifestJson = await this.config.readJson(loc);
|
||||
await normalizeManifest(projectManifestJson, this.config.cwd, this.config, true);
|
||||
|
||||
const rootCwd = this.config.cwd;
|
||||
// if project has workspaces we aggreagate all dependences from workspaces into root
|
||||
if (projectManifestJson.workspaces) {
|
||||
if (!projectManifestJson.private) {
|
||||
throw new MessageError(this.reporter.lang('workspacesRequirePrivateProjects'));
|
||||
}
|
||||
const workspaces = await this.config.resolveWorkspaces(path.dirname(loc), projectManifestJson.workspaces);
|
||||
const workspaceEntries = Object.keys(workspaces).map(name => workspaces[name]);
|
||||
for (const {loc: workspaceLoc, manifest: workspaceManifest} of workspaceEntries) {
|
||||
for (const type of ['dependencies', 'devDependencies', 'optionalDependencies']) {
|
||||
if (workspaceManifest[type]) {
|
||||
for (const key of Object.keys(workspaceManifest[type])) {
|
||||
if (
|
||||
projectManifestJson[type] &&
|
||||
projectManifestJson[type][key] &&
|
||||
projectManifestJson[type][key] !== workspaceManifest[type][key]
|
||||
) {
|
||||
// TODO conflicts should still be installed inside workspaces' folders
|
||||
throw new MessageError(
|
||||
this.reporter.lang('workspacesIncompatibleDependencies', key, workspaceLoc, rootCwd),
|
||||
);
|
||||
}
|
||||
if (!projectManifestJson[type]) {
|
||||
projectManifestJson[type] = {};
|
||||
}
|
||||
projectManifestJson[type][key] = workspaceManifest[type][key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(this.resolutions, projectManifestJson.resolutions);
|
||||
Object.assign(manifest, projectManifestJson);
|
||||
|
||||
const pushDeps = (depType, {hint, optional}, isUsed) => {
|
||||
const pushDeps = (depType, manifest: Object, {hint, optional}, isUsed) => {
|
||||
if (ignoreUnusedPatterns && !isUsed) {
|
||||
return;
|
||||
}
|
||||
@@ -269,7 +241,7 @@ export class Install {
|
||||
if (this.flags.flat && !isUsed) {
|
||||
return;
|
||||
}
|
||||
const depMap = projectManifestJson[depType];
|
||||
const depMap = manifest[depType];
|
||||
for (const name in depMap) {
|
||||
if (excludeNames.indexOf(name) >= 0) {
|
||||
continue;
|
||||
@@ -295,9 +267,38 @@ export class Install {
|
||||
}
|
||||
};
|
||||
|
||||
pushDeps('dependencies', {hint: null, optional: false}, true);
|
||||
pushDeps('devDependencies', {hint: 'dev', optional: false}, !this.config.production);
|
||||
pushDeps('optionalDependencies', {hint: 'optional', optional: true}, !this.flags.ignoreOptional);
|
||||
pushDeps('dependencies', projectManifestJson, {hint: null, optional: false}, true);
|
||||
pushDeps('devDependencies', projectManifestJson, {hint: 'dev', optional: false}, !this.config.production);
|
||||
pushDeps(
|
||||
'optionalDependencies',
|
||||
projectManifestJson,
|
||||
{hint: 'optional', optional: true},
|
||||
!this.flags.ignoreOptional,
|
||||
);
|
||||
|
||||
if (this.config.workspacesEnabled) {
|
||||
const workspaces = await this.config.resolveWorkspaces(path.dirname(loc), projectManifestJson);
|
||||
workspaceLayout = new WorkspaceLayout(workspaces, this.config);
|
||||
// add virtual manifest that depends on all workspaces, this way package hoisters and resolvers will work fine
|
||||
const virtualDependencyManifest: Manifest = {
|
||||
_uid: '',
|
||||
name: `workspace-aggregator-${uuid.v4()}`,
|
||||
version: '1.0.0',
|
||||
_registry: 'npm',
|
||||
_loc: '.',
|
||||
dependencies: {},
|
||||
};
|
||||
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: '', manifest: virtualDependencyManifest};
|
||||
|
||||
pushDeps('workspaces', {workspaces: virtualDep}, {hint: 'workspaces', optional: false}, true);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -313,6 +314,7 @@ export class Install {
|
||||
manifest,
|
||||
usedPatterns,
|
||||
ignorePatterns,
|
||||
workspaceLayout,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -328,7 +330,7 @@ export class Install {
|
||||
return patterns;
|
||||
}
|
||||
|
||||
async bailout(patterns: Array<string>): Promise<boolean> {
|
||||
async bailout(patterns: Array<string>, workspaceLayout: ?WorkspaceLayout): Promise<boolean> {
|
||||
if (this.flags.skipIntegrityCheck || this.flags.force) {
|
||||
return false;
|
||||
}
|
||||
@@ -336,7 +338,7 @@ export class Install {
|
||||
if (!lockfileCache) {
|
||||
return false;
|
||||
}
|
||||
const match = await this.integrityChecker.check(patterns, lockfileCache, this.flags);
|
||||
const match = await this.integrityChecker.check(patterns, lockfileCache, this.flags, workspaceLayout);
|
||||
if (this.flags.frozenLockfile && match.missingPatterns.length > 0) {
|
||||
throw new MessageError(this.reporter.lang('frozenLockfileError'));
|
||||
}
|
||||
@@ -404,7 +406,12 @@ export class Install {
|
||||
|
||||
let flattenedTopLevelPatterns: Array<string> = [];
|
||||
const steps: Array<(curr: number, total: number) => Promise<{bailout: boolean} | void>> = [];
|
||||
const {requests: depRequests, patterns: rawPatterns, ignorePatterns} = await this.fetchRequestFromCwd();
|
||||
const {
|
||||
requests: depRequests,
|
||||
patterns: rawPatterns,
|
||||
ignorePatterns,
|
||||
workspaceLayout,
|
||||
} = await this.fetchRequestFromCwd();
|
||||
let topLevelPatterns: Array<string> = [];
|
||||
|
||||
const artifacts = await this.integrityChecker.getArtifacts();
|
||||
@@ -415,10 +422,10 @@ export class Install {
|
||||
|
||||
steps.push(async (curr: number, total: number) => {
|
||||
this.reporter.step(curr, total, this.reporter.lang('resolvingPackages'), emoji.get('mag'));
|
||||
await this.resolver.init(this.prepareRequests(depRequests), this.flags.flat);
|
||||
await this.resolver.init(this.prepareRequests(depRequests), this.flags.flat, workspaceLayout);
|
||||
topLevelPatterns = this.preparePatterns(rawPatterns);
|
||||
flattenedTopLevelPatterns = await this.flatten(topLevelPatterns);
|
||||
return {bailout: await this.bailout(topLevelPatterns)};
|
||||
return {bailout: await this.bailout(topLevelPatterns, workspaceLayout)};
|
||||
});
|
||||
|
||||
steps.push(async (curr: number, total: number) => {
|
||||
@@ -433,7 +440,7 @@ export class Install {
|
||||
// remove integrity hash to make this operation atomic
|
||||
await this.integrityChecker.removeIntegrityFile();
|
||||
this.reporter.step(curr, total, this.reporter.lang('linkingDependencies'), emoji.get('link'));
|
||||
await this.linker.init(flattenedTopLevelPatterns, this.flags.linkDuplicates);
|
||||
await this.linker.init(flattenedTopLevelPatterns, this.flags.linkDuplicates, workspaceLayout);
|
||||
});
|
||||
|
||||
steps.push(async (curr: number, total: number) => {
|
||||
@@ -482,7 +489,7 @@ export class Install {
|
||||
}
|
||||
|
||||
// fin!
|
||||
await this.saveLockfileAndIntegrity(topLevelPatterns);
|
||||
await this.saveLockfileAndIntegrity(topLevelPatterns, workspaceLayout);
|
||||
this.maybeOutputUpdate();
|
||||
this.config.requestManager.clearCache();
|
||||
return flattenedTopLevelPatterns;
|
||||
@@ -624,13 +631,23 @@ export class Install {
|
||||
* Save updated integrity and lockfiles.
|
||||
*/
|
||||
|
||||
async saveLockfileAndIntegrity(patterns: Array<string>): Promise<void> {
|
||||
async saveLockfileAndIntegrity(patterns: Array<string>, workspaceLayout: ?WorkspaceLayout): Promise<void> {
|
||||
// --no-lockfile or --pure-lockfile flag
|
||||
if (this.flags.lockfile === false || this.flags.pureLockfile) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lockfileBasedOnResolver = this.lockfile.getLockfile(this.resolver.patterns);
|
||||
const resolvedPatterns: {[packagePattern: string]: Manifest} = {};
|
||||
Object.keys(this.resolver.patterns).forEach(pattern => {
|
||||
if (!workspaceLayout || !workspaceLayout.getManifestByPattern(pattern)) {
|
||||
resolvedPatterns[pattern] = this.resolver.patterns[pattern];
|
||||
}
|
||||
});
|
||||
|
||||
// TODO this code is duplicated in a few places, need a common way to filter out workspace patterns from lockfile
|
||||
patterns = patterns.filter(p => !workspaceLayout || !workspaceLayout.getManifestByPattern(p));
|
||||
|
||||
const lockfileBasedOnResolver = this.lockfile.getLockfile(resolvedPatterns);
|
||||
|
||||
if (this.config.pruneOfflineMirror) {
|
||||
await this.pruneOfflineMirror(lockfileBasedOnResolver);
|
||||
@@ -674,33 +691,38 @@ export class Install {
|
||||
/**
|
||||
* Load the dependency graph of the current install. Only does package resolving and wont write to the cwd.
|
||||
*/
|
||||
async hydrate(fetch?: boolean, ignoreUnusedPatterns?: boolean): Promise<InstallCwdRequest> {
|
||||
async hydrate(ignoreUnusedPatterns?: boolean): Promise<InstallCwdRequest> {
|
||||
const request = await this.fetchRequestFromCwd([], ignoreUnusedPatterns);
|
||||
const {requests: depRequests, patterns: rawPatterns, ignorePatterns} = request;
|
||||
const {requests: depRequests, patterns: rawPatterns, ignorePatterns, workspaceLayout} = request;
|
||||
|
||||
await this.resolver.init(depRequests, this.flags.flat);
|
||||
await this.resolver.init(depRequests, this.flags.flat, workspaceLayout);
|
||||
await this.flatten(rawPatterns);
|
||||
this.markIgnored(ignorePatterns);
|
||||
|
||||
if (fetch) {
|
||||
// fetch packages, should hit cache most of the time
|
||||
const manifests: Array<Manifest> = await fetcher.fetch(this.resolver.getManifests(), this.config);
|
||||
this.resolver.updateManifests(manifests);
|
||||
await compatibility.check(this.resolver.getManifests(), this.config, this.flags.ignoreEngines);
|
||||
// fetch packages, should hit cache most of the time
|
||||
const manifests: Array<Manifest> = await fetcher.fetch(this.resolver.getManifests(), this.config);
|
||||
this.resolver.updateManifests(manifests);
|
||||
await compatibility.check(this.resolver.getManifests(), this.config, this.flags.ignoreEngines);
|
||||
|
||||
// expand minimal manifests
|
||||
for (const manifest of this.resolver.getManifests()) {
|
||||
const ref = manifest._reference;
|
||||
invariant(ref, 'expected reference');
|
||||
const {type} = ref.remote;
|
||||
// link specifier won't ever hit cache
|
||||
if (type === 'link') {
|
||||
// expand minimal manifests
|
||||
for (const manifest of this.resolver.getManifests()) {
|
||||
const ref = manifest._reference;
|
||||
invariant(ref, 'expected reference');
|
||||
const {type} = ref.remote;
|
||||
// link specifier won't ever hit cache
|
||||
let loc = '';
|
||||
if (type === 'link') {
|
||||
continue;
|
||||
} else if (type === 'workspace') {
|
||||
if (!ref.remote.reference) {
|
||||
continue;
|
||||
}
|
||||
const loc = this.config.generateHardModulePath(ref);
|
||||
const newPkg = await this.config.readManifest(loc);
|
||||
await this.resolver.updateManifest(ref, newPkg);
|
||||
loc = ref.remote.reference;
|
||||
} else {
|
||||
loc = this.config.generateHardModulePath(ref);
|
||||
}
|
||||
const newPkg = await this.config.readManifest(loc);
|
||||
await this.resolver.updateManifest(ref, newPkg);
|
||||
}
|
||||
|
||||
return request;
|
||||
|
||||
@@ -17,7 +17,7 @@ export function hasWrapper(flags: Object, args: Array<string>): boolean {
|
||||
async function getManifests(config: Config, flags: Object): Promise<Array<Manifest>> {
|
||||
const lockfile = await Lockfile.fromDirectory(config.cwd);
|
||||
const install = new Install({skipIntegrityCheck: true, ...flags}, config, new NoopReporter(), lockfile);
|
||||
await install.hydrate(true, true);
|
||||
await install.hydrate(true);
|
||||
|
||||
let manifests = install.resolver.getManifests();
|
||||
|
||||
|
||||
@@ -14,29 +14,29 @@ export function hasWrapper(): boolean {
|
||||
}
|
||||
|
||||
export async function run(config: Config, reporter: Reporter, flags: Object, args: Array<string>): Promise<void> {
|
||||
const {worktreeFolder} = config;
|
||||
const {workspaceRootFolder} = config;
|
||||
|
||||
if (!worktreeFolder) {
|
||||
throw new MessageError(reporter.lang('worktreeRootNotFound', config.cwd));
|
||||
if (!workspaceRootFolder) {
|
||||
throw new MessageError(reporter.lang('workspaceRootNotFound', config.cwd));
|
||||
}
|
||||
|
||||
if (args.length < 1) {
|
||||
throw new MessageError(reporter.lang('worktreeMissingWorkspace'));
|
||||
throw new MessageError(reporter.lang('workspaceMissingWorkspace'));
|
||||
}
|
||||
|
||||
if (args.length < 2) {
|
||||
throw new MessageError(reporter.lang('worktreeMissingCommand'));
|
||||
throw new MessageError(reporter.lang('workspaceMissingCommand'));
|
||||
}
|
||||
|
||||
const manifest = await config.findManifest(worktreeFolder, false);
|
||||
const manifest = await config.findManifest(workspaceRootFolder, false);
|
||||
invariant(manifest && manifest.workspaces, 'We must find a manifest with a "workspaces" property');
|
||||
|
||||
const workspaces = await config.resolveWorkspaces(worktreeFolder, manifest.workspaces);
|
||||
const workspaces = await config.resolveWorkspaces(workspaceRootFolder, manifest);
|
||||
|
||||
const [workspaceName, ...rest] = args;
|
||||
|
||||
if (!Object.prototype.hasOwnProperty.call(workspaces, workspaceName)) {
|
||||
throw new MessageError(reporter.lang('worktreeUnknownWorkspace', workspaceName));
|
||||
throw new MessageError(reporter.lang('workspaceUnknownWorkspace', workspaceName));
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import type {RegistryNames, ConfigRegistries} from './registries/index.js';
|
||||
import type {Reporter} from './reporters/index.js';
|
||||
import type {Manifest, PackageRemote} from './types.js';
|
||||
import type {Manifest, PackageRemote, WorkspacesManifestMap} from './types.js';
|
||||
import type PackageReference from './package-reference.js';
|
||||
import {execFromManifest} from './util/execute-lifecycle-script.js';
|
||||
import {expandPath} from './util/path.js';
|
||||
@@ -145,11 +145,11 @@ export default class Config {
|
||||
|
||||
nonInteractive: boolean;
|
||||
|
||||
workspacesExperimental: boolean;
|
||||
workspacesEnabled: boolean;
|
||||
|
||||
//
|
||||
cwd: string;
|
||||
worktreeFolder: ?string;
|
||||
workspaceRootFolder: ?string;
|
||||
lockfileFolder: string;
|
||||
|
||||
//
|
||||
@@ -188,7 +188,7 @@ export default class Config {
|
||||
getOption(key: string, expand: boolean = true): mixed {
|
||||
const value = this.registries.yarn.getOption(key);
|
||||
|
||||
if (expand && (typeof value === 'string')) {
|
||||
if (expand && typeof value === 'string') {
|
||||
return expandPath(value);
|
||||
}
|
||||
|
||||
@@ -210,8 +210,8 @@ export default class Config {
|
||||
async init(opts: ConfigOptions = {}): Promise<void> {
|
||||
this._init(opts);
|
||||
|
||||
this.worktreeFolder = await this.findWorktree(this.cwd);
|
||||
this.lockfileFolder = this.worktreeFolder || this.cwd;
|
||||
this.workspaceRootFolder = await this.findWorkspaceRoot(this.cwd);
|
||||
this.lockfileFolder = this.workspaceRootFolder || this.cwd;
|
||||
|
||||
await fs.mkdirp(this.globalFolder);
|
||||
await fs.mkdirp(this.linkFolder);
|
||||
@@ -273,7 +273,7 @@ export default class Config {
|
||||
this._cacheRootFolder = String(
|
||||
opts.cacheFolder || this.getOption('cache-folder') || constants.MODULE_CACHE_DIRECTORY,
|
||||
);
|
||||
this.workspacesExperimental = Boolean(this.getOption('workspaces-experimental'));
|
||||
this.workspacesEnabled = Boolean(this.getOption('workspaces-experimental'));
|
||||
|
||||
this.pruneOfflineMirror = Boolean(this.getOption('yarn-offline-mirror-pruning'));
|
||||
this.enableMetaFolder = Boolean(this.getOption('enable-meta-folder'));
|
||||
@@ -299,8 +299,8 @@ export default class Config {
|
||||
this.production = !!opts.production;
|
||||
}
|
||||
|
||||
if (this.worktreeFolder && !this.workspacesExperimental) {
|
||||
throw new MessageError(this.reporter.lang('worktreeExperimentalDisabled'));
|
||||
if (this.workspaceRootFolder && !this.workspacesEnabled) {
|
||||
throw new MessageError(this.reporter.lang('workspaceExperimentalDisabled'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,7 +552,7 @@ export default class Config {
|
||||
return null;
|
||||
}
|
||||
|
||||
async findWorktree(initial: string): Promise<?string> {
|
||||
async findWorkspaceRoot(initial: string): Promise<?string> {
|
||||
let previous = null;
|
||||
let current = path.normalize(initial);
|
||||
|
||||
@@ -570,11 +570,15 @@ export default class Config {
|
||||
return null;
|
||||
}
|
||||
|
||||
async resolveWorkspaces(
|
||||
root: string,
|
||||
patterns: Array<string>,
|
||||
): Promise<{[string]: {loc: string, manifest: Manifest}}> {
|
||||
async resolveWorkspaces(root: string, rootManifest: Manifest): Promise<WorkspacesManifestMap> {
|
||||
const workspaces = {};
|
||||
const patterns = rootManifest.workspaces || [];
|
||||
if (!this.workspacesEnabled) {
|
||||
return workspaces;
|
||||
}
|
||||
if (!rootManifest.private && patterns.length > 0) {
|
||||
throw new MessageError(this.reporter.lang('workspacesRequirePrivateProjects'));
|
||||
}
|
||||
|
||||
const registryFilenames = registryNames.map(registryName => this.registries[registryName].constructor.filename);
|
||||
const trailingPattern = `/+(${registryFilenames.join(`|`)})`;
|
||||
@@ -594,13 +598,16 @@ export default class Config {
|
||||
}
|
||||
|
||||
if (!manifest.name) {
|
||||
// TODO raise a warning?
|
||||
this.reporter.warn(this.reporter.lang('workspaceNameMandatory', loc));
|
||||
continue;
|
||||
}
|
||||
if (!manifest.version) {
|
||||
this.reporter.warn(this.reporter.lang('workspaceVersionMandatory', loc));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(workspaces, manifest.name)) {
|
||||
// TODO raise a warning?
|
||||
continue;
|
||||
throw new MessageError(this.reporter.lang('workspaceNameDuplicate', manifest.name));
|
||||
}
|
||||
|
||||
workspaces[manifest.name] = {loc, manifest};
|
||||
|
||||
@@ -12,4 +12,4 @@ export {TarballFetcher as tarball};
|
||||
|
||||
export type Fetchers = BaseFetcher | CopyFetcher | GitFetcher | TarballFetcher;
|
||||
|
||||
export type FetcherNames = 'base' | 'copy' | 'git' | 'link' | 'tarball';
|
||||
export type FetcherNames = 'base' | 'copy' | 'git' | 'link' | 'tarball' | 'workspace';
|
||||
|
||||
@@ -8,6 +8,7 @@ import {registryNames} from './registries/index.js';
|
||||
import * as fs from './util/fs.js';
|
||||
import {sortAlpha, compareSortedArrays} from './util/misc.js';
|
||||
import type {InstallArtifacts} from './package-install-scripts.js';
|
||||
import WorkspaceLayout from './workspace-layout.js';
|
||||
|
||||
const invariant = require('invariant');
|
||||
const path = require('path');
|
||||
@@ -243,9 +244,12 @@ export default class InstallationIntegrityChecker {
|
||||
patterns: Array<string>,
|
||||
lockfile: {[key: string]: LockManifest},
|
||||
flags: IntegrityFlags,
|
||||
workspaceLayout: ?WorkspaceLayout,
|
||||
): Promise<IntegrityCheckResult> {
|
||||
// check if patterns exist in lockfile
|
||||
const missingPatterns = patterns.filter(p => !lockfile[p]);
|
||||
const missingPatterns = patterns.filter(
|
||||
p => !lockfile[p] && (!workspaceLayout || !workspaceLayout.getManifestByPattern(p)),
|
||||
);
|
||||
|
||||
const loc = await this._getIntegrityHashLocation();
|
||||
if (missingPatterns.length || !loc.exists) {
|
||||
|
||||
@@ -25,8 +25,8 @@ async function fetchOne(ref: PackageReference, config: Config): Promise<FetchedM
|
||||
|
||||
const remote = ref.remote;
|
||||
|
||||
// Mock metedata for linked dependencies
|
||||
if (remote.type === 'link') {
|
||||
// Mock metedata for symlinked dependencies
|
||||
if (remote.type === 'link' || remote.type === 'workspace') {
|
||||
const mockPkg: Manifest = {_uid: '', name: '', version: '0.0.0'};
|
||||
return Promise.resolve({resolved: null, hash: '', dest, package: mockPkg, cached: false});
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import {entries} from './util/misc.js';
|
||||
import * as fs from './util/fs.js';
|
||||
import lockMutex from './util/mutex.js';
|
||||
import {satisfiesWithPreleases} from './util/semver.js';
|
||||
import WorkspaceLayout from './workspace-layout.js';
|
||||
|
||||
const invariant = require('invariant');
|
||||
const cmdShim = promise.promisify(require('cmd-shim'));
|
||||
@@ -124,7 +125,11 @@ export default class PackageLinker {
|
||||
return Promise.resolve(hoister.init());
|
||||
}
|
||||
|
||||
async copyModules(patterns: Array<string>, linkDuplicates: boolean): Promise<void> {
|
||||
async copyModules(
|
||||
patterns: Array<string>,
|
||||
linkDuplicates: boolean,
|
||||
workspaceLayout?: WorkspaceLayout,
|
||||
): Promise<void> {
|
||||
let flatTree = await this.getFlatHoistedTree(patterns);
|
||||
|
||||
// sorted tree makes file creation and copying not to interfere with each other
|
||||
@@ -140,22 +145,47 @@ export default class PackageLinker {
|
||||
const hardlinksEnabled = linkDuplicates && (await fs.hardlinksWork(this.config.cwd));
|
||||
|
||||
const copiedSrcs: Map<string, string> = new Map();
|
||||
for (const [dest, {pkg, loc}] of flatTree) {
|
||||
const symlinkPaths: Map<string, string> = new Map();
|
||||
for (const [folder, {pkg, loc}] of flatTree) {
|
||||
const remote = pkg._remote || {type: ''};
|
||||
const ref = pkg._reference;
|
||||
const src = remote.type === 'link' ? remote.reference : loc;
|
||||
let dest = folder;
|
||||
invariant(ref, 'expected package reference');
|
||||
ref.setLocation(dest);
|
||||
|
||||
// backwards compatibility: get build artifacts from metadata
|
||||
// does not apply to linked dependencies
|
||||
if (remote.type !== 'link') {
|
||||
let src = loc;
|
||||
let type = '';
|
||||
if (remote.type === 'link') {
|
||||
// replace package source from incorrect cache location (workspaces and link: are not cached)
|
||||
// with a symlink source
|
||||
src = remote.reference;
|
||||
type = 'symlink';
|
||||
} else if (workspaceLayout && remote.type === 'workspace') {
|
||||
src = remote.reference;
|
||||
type = 'symlink';
|
||||
if (dest.indexOf(workspaceLayout.virtualManifestName) !== -1) {
|
||||
// we don't need to install virtual manifest
|
||||
continue;
|
||||
}
|
||||
// to get real path for non hoisted dependencies
|
||||
symlinkPaths.set(dest, src);
|
||||
} else {
|
||||
// backwards compatibility: get build artifacts from metadata
|
||||
// does not apply to symlinked dependencies
|
||||
const metadata = await this.config.readPackageMetadata(src);
|
||||
for (const file of metadata.artifacts) {
|
||||
artifactFiles.push(path.join(dest, file));
|
||||
}
|
||||
}
|
||||
|
||||
// fs copy can't copy through a symlink, so we replace those with real paths to workpsaces
|
||||
for (const [symlink, realpath] of symlinkPaths.entries()) {
|
||||
if (dest !== symlink && dest.indexOf(symlink) === 0) {
|
||||
dest = dest.replace(symlink, realpath);
|
||||
}
|
||||
}
|
||||
|
||||
ref.setLocation(dest);
|
||||
|
||||
const integrityArtifacts = this.artifacts[`${pkg.name}@${pkg.version}`];
|
||||
if (integrityArtifacts) {
|
||||
for (const file of integrityArtifacts) {
|
||||
@@ -171,7 +201,7 @@ export default class PackageLinker {
|
||||
copyQueue.set(dest, {
|
||||
src,
|
||||
dest,
|
||||
type: remote.type,
|
||||
type,
|
||||
onFresh() {
|
||||
if (ref) {
|
||||
ref.setFresh(true);
|
||||
@@ -355,8 +385,8 @@ export default class PackageLinker {
|
||||
return range === '*' || satisfiesWithPreleases(version, range, this.config.looseSemver);
|
||||
}
|
||||
|
||||
async init(patterns: Array<string>, linkDuplicates: boolean): Promise<void> {
|
||||
async init(patterns: Array<string>, linkDuplicates: boolean, workspaceLayout?: WorkspaceLayout): Promise<void> {
|
||||
this.resolvePeerModules();
|
||||
await this.copyModules(patterns, linkDuplicates);
|
||||
await this.copyModules(patterns, linkDuplicates, workspaceLayout);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {MessageError} from './errors.js';
|
||||
import {entries} from './util/misc.js';
|
||||
import * as constants from './constants.js';
|
||||
import * as versionUtil from './util/version.js';
|
||||
import WorkspaceResolver from './resolvers/contextual/workspace-resolver.js';
|
||||
import * as resolvers from './resolvers/index.js';
|
||||
import * as fs from './util/fs.js';
|
||||
|
||||
@@ -221,6 +222,10 @@ export default class PackageRequest {
|
||||
const exoticResolver = PackageRequest.getExoticResolver(this.pattern);
|
||||
if (exoticResolver) {
|
||||
return this.findExoticVersionInfo(exoticResolver, this.pattern);
|
||||
} else if (WorkspaceResolver.isWorkspace(this.pattern, this.resolver.workspaceLayout)) {
|
||||
invariant(this.resolver.workspaceLayout, 'expected workspaceLayout');
|
||||
const resolver = new WorkspaceResolver(this, this.pattern, this.resolver.workspaceLayout);
|
||||
return resolver.resolve();
|
||||
} else {
|
||||
return this.findVersionOnRegistry(this.pattern);
|
||||
}
|
||||
@@ -321,6 +326,21 @@ export default class PackageRequest {
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (remote.type === 'workspace' && !this.config.production) {
|
||||
// workspaces support dev dependencies
|
||||
for (const depName in info.devDependencies) {
|
||||
const depPattern = depName + '@' + info.devDependencies[depName];
|
||||
deps.push(depPattern);
|
||||
promises.push(
|
||||
this.resolver.find({
|
||||
pattern: depPattern,
|
||||
registry: remote.registry,
|
||||
optional: false,
|
||||
parentRequest: this,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
ref.addDependencies(deps);
|
||||
|
||||
@@ -10,6 +10,7 @@ import RequestManager from './util/request-manager.js';
|
||||
import BlockingQueue from './util/blocking-queue.js';
|
||||
import Lockfile from './lockfile/wrapper.js';
|
||||
import map from './util/map.js';
|
||||
import WorkspaceLayout from './workspace-layout.js';
|
||||
|
||||
const invariant = require('invariant');
|
||||
const semver = require('semver');
|
||||
@@ -32,6 +33,8 @@ export default class PackageResolver {
|
||||
// whether the dependency graph will be flattened
|
||||
flat: boolean;
|
||||
|
||||
workspaceLayout: ?WorkspaceLayout;
|
||||
|
||||
// list of registries that have been used in this resolution
|
||||
usedRegistries: Set<RegistryNames>;
|
||||
|
||||
@@ -452,8 +455,9 @@ export default class PackageResolver {
|
||||
* TODO description
|
||||
*/
|
||||
|
||||
async init(deps: DependencyRequestPatterns, isFlat: boolean): Promise<void> {
|
||||
async init(deps: DependencyRequestPatterns, isFlat: boolean, workspaceLayout?: WorkspaceLayout): Promise<void> {
|
||||
this.flat = isFlat;
|
||||
this.workspaceLayout = workspaceLayout;
|
||||
const activity = (this.activity = this.reporter.activity());
|
||||
await Promise.all(deps.map((req): Promise<void> => this.find(req)));
|
||||
|
||||
|
||||
@@ -97,8 +97,6 @@ const messages = {
|
||||
fileWriteError: 'Could not write file $0: $1',
|
||||
multiplePackagesCantUnpackInSameDestination: 'Pattern $0 is trying to unpack in the same destination $1 as pattern $2. This could result in a non deterministic behavior, skipping.',
|
||||
incorrectLockfileEntry: 'Lockfile has incorrect entry for $0. Ignoring it.',
|
||||
workspacesIncompatibleDependencies: 'Dependency $0 has different versions in $1 and $2',
|
||||
workspacesRequirePrivateProjects: 'Workspaces can only be enabled for private projects',
|
||||
|
||||
yarnOutdated: "Your current version of Yarn is out of date. The latest version is $0 while you're on $1.",
|
||||
yarnOutdatedInstaller: 'To upgrade, download the latest installer at $0.',
|
||||
@@ -142,11 +140,15 @@ const messages = {
|
||||
createInvalidBin: 'Invalid bin entry found in package $0.',
|
||||
createMissingPackage: 'Package not found - this is probably an internal error, and should be reported at https://github.com/yarnpkg/yarn/issues.',
|
||||
|
||||
worktreeExperimentalDisabled: 'The worktree feature is currently experimental and needs to be manually enabled - please add "workspaces-experimental true" to your .yarnrc file.',
|
||||
worktreeRootNotFound: "Cannot find the root of your worktree - are you sure you're currently in a workspace?",
|
||||
worktreeMissingWorkspace: 'Missing workspace name.',
|
||||
worktreeMissingCommand: 'Missing command name.',
|
||||
worktreeUnknownWorkspace: 'Unknown workspace $0.',
|
||||
workspacesRequirePrivateProjects: 'Workspaces can only be enabled in private projects',
|
||||
workspaceExperimentalDisabled: 'The workspace feature is currently experimental and needs to be manually enabled - please add "workspaces-experimental true" to your .yarnrc file.',
|
||||
workspaceRootNotFound: "Cannot find the root of your workspace - are you sure you're currently in a workspace?",
|
||||
workspaceMissingWorkspace: 'Missing workspace name.',
|
||||
workspaceMissingCommand: 'Missing command name.',
|
||||
workspaceUnknownWorkspace: 'Unknown workspace $0.',
|
||||
workspaceVersionMandatory: 'Missing version in workspace at $0, ignoring.',
|
||||
workspaceNameMandatory: 'Missing name in workspace at $0, ignoring.',
|
||||
workspaceNameDuplicate: 'There are more than one workspace with name $0',
|
||||
|
||||
execMissingCommand: 'Missing command name.',
|
||||
|
||||
|
||||
40
src/resolvers/contextual/workspace-resolver.js
Normal file
40
src/resolvers/contextual/workspace-resolver.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/* @flow */
|
||||
|
||||
import type {Manifest} from '../../types.js';
|
||||
import PackageRequest from '../../package-request.js';
|
||||
import BaseResolver from '../base-resolver.js';
|
||||
import WorkspaceLayout from '../../workspace-layout.js';
|
||||
|
||||
const invariant = require('invariant');
|
||||
|
||||
export default class WorkspaceResolver extends BaseResolver {
|
||||
static isWorkspace(pattern: string, workspaceLayout: ?WorkspaceLayout): boolean {
|
||||
return !!workspaceLayout && !!workspaceLayout.getManifestByPattern(pattern);
|
||||
}
|
||||
|
||||
constructor(request: PackageRequest, fragment: string, workspaceLayout: WorkspaceLayout) {
|
||||
super(request, fragment);
|
||||
this.workspaceLayout = workspaceLayout;
|
||||
}
|
||||
|
||||
workspaceLayout: WorkspaceLayout;
|
||||
|
||||
resolve(): Promise<Manifest> {
|
||||
const workspace = this.workspaceLayout.getManifestByPattern(this.request.pattern);
|
||||
invariant(workspace, 'expected workspace');
|
||||
const {manifest, loc} = workspace;
|
||||
const registry = manifest._registry;
|
||||
invariant(registry, 'expected reference');
|
||||
|
||||
manifest._remote = {
|
||||
type: 'workspace',
|
||||
registry,
|
||||
hash: '',
|
||||
reference: loc,
|
||||
};
|
||||
|
||||
manifest._uid = manifest.version;
|
||||
|
||||
return Promise.resolve(manifest);
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,8 @@ export type Manifest = {
|
||||
name: string,
|
||||
version: string,
|
||||
|
||||
private?: boolean,
|
||||
|
||||
author?: {
|
||||
name?: string,
|
||||
email?: string,
|
||||
@@ -152,3 +154,7 @@ export type Dependency = {
|
||||
url: string,
|
||||
hint: ?string,
|
||||
};
|
||||
|
||||
export type WorkspacesManifestMap = {
|
||||
[string]: {loc: string, manifest: Manifest},
|
||||
};
|
||||
|
||||
@@ -165,7 +165,7 @@ async function buildActionsForCopy(
|
||||
const onDone = data.onDone || noop;
|
||||
files.add(dest);
|
||||
|
||||
if (type === 'link') {
|
||||
if (type === 'symlink') {
|
||||
await mkdirp(path.dirname(dest));
|
||||
onFresh();
|
||||
actions.push({
|
||||
|
||||
31
src/workspace-layout.js
Normal file
31
src/workspace-layout.js
Normal file
@@ -0,0 +1,31 @@
|
||||
/* @flow */
|
||||
|
||||
import type Config from './config.js';
|
||||
import PackageRequest from './package-request.js';
|
||||
import type {WorkspacesManifestMap, Manifest} from './types.js';
|
||||
|
||||
const semver = require('semver');
|
||||
|
||||
export default class WorkspaceLayout {
|
||||
constructor(workspaces: WorkspacesManifestMap, config: Config) {
|
||||
this.workspaces = workspaces;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
workspaces: WorkspacesManifestMap;
|
||||
config: Config;
|
||||
virtualManifestName: string;
|
||||
|
||||
getWorkspaceManifest(key: string): {loc: string, manifest: Manifest} {
|
||||
return this.workspaces[key];
|
||||
}
|
||||
|
||||
getManifestByPattern(pattern: string): ?{loc: string, manifest: Manifest} {
|
||||
const {name, range} = PackageRequest.normalizePattern(pattern);
|
||||
const workspace = this.getWorkspaceManifest(name);
|
||||
if (!workspace || !semver.satisfies(workspace.manifest.version, range, this.config.looseSemver)) {
|
||||
return null;
|
||||
}
|
||||
return workspace;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user