Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(link): Use publishConfig.directory as symlink source if it exists to allow linking sub-directories #2274

Merged
merged 16 commits into from Oct 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions commands/bootstrap/README.md
Expand Up @@ -198,6 +198,18 @@ $ lerna bootstrap --force-local

When passed, this flag causes the `bootstrap` command to always symlink local dependencies regardless of matching version range.

### `publishConfig.directory`

This _non-standard_ field allows you to customize the symlinked subdirectory that will be the _source_ directory of the symlink, just like how the published package would be consumed.

```json
"publishConfig": {
"directory": "dist"
}
```

In this example, when this package is bootstrapped and linked, the `dist` directory will be the source directory (e.g. `package-1/dist => node_modules/package-1`).

## How It Works

Let's use `babel` as an example.
Expand Down
Expand Up @@ -313,6 +313,46 @@ Array [
]
`;

exports[`BootstrapCommand with local package dependencies should respect --contents argument during linking step 1`] = `
Array [
Object {
"_src": "packages/package-1/dist",
"dest": "packages/package-2/node_modules/@test/package-1",
"type": "junction",
},
Object {
"_src": "packages/package-1/dist",
"dest": "packages/package-3/node_modules/@test/package-1",
"type": "junction",
},
Object {
"_src": "packages/package-2/dist",
"dest": "packages/package-3/node_modules/@test/package-2",
"type": "junction",
},
Object {
"_src": "packages/package-2/dist/cli.js",
"dest": "packages/package-3/node_modules/.bin/package-2",
"type": "exec",
},
Object {
"_src": "packages/package-3/dist",
"dest": "packages/package-4/node_modules/package-3",
"type": "junction",
},
Object {
"_src": "packages/package-3/dist/cli1.js",
"dest": "packages/package-4/node_modules/.bin/package3cli1",
"type": "exec",
},
Object {
"_src": "packages/package-3/dist/cli2.js",
"dest": "packages/package-4/node_modules/.bin/package3cli2",
"type": "exec",
},
]
`;

exports[`BootstrapCommand with local package dependencies should respect --force-local 1`] = `
Object {
"packages/package-1": Array [
Expand Down
8 changes: 8 additions & 0 deletions commands/bootstrap/__tests__/bootstrap-command.test.js
Expand Up @@ -345,6 +345,14 @@ describe("BootstrapCommand", () => {
});
});

it("should respect --contents argument during linking step", async () => {
evocateur marked this conversation as resolved.
Show resolved Hide resolved
const testDir = await initFixture("basic");

await lernaBootstrap(testDir)("--contents", "dist");

expect(symlinkedDirectories(testDir)).toMatchSnapshot();
});

it("should not update package.json when filtering", async () => {
const testDir = await initFixture("basic");

Expand Down
6 changes: 6 additions & 0 deletions commands/bootstrap/command.js
Expand Up @@ -58,6 +58,12 @@ exports.builder = yargs => {
describe: "Don't allow warnings when hoisting as it causes longer bootstrap times and other issues.",
type: "boolean",
},
contents: {
group: "Command Options:",
describe: "Subdirectory to use as the source of any links. Must apply to ALL packages.",
type: "string",
defaultDescription: ".",
},
});

return filterable(yargs);
Expand Down
7 changes: 7 additions & 0 deletions commands/bootstrap/index.js
Expand Up @@ -160,6 +160,13 @@ class BootstrapCommand extends Command {
chain = chain.then(filteredPackages => {
this.filteredPackages = filteredPackages;

if (this.options.contents) {
// globally override directory to link
for (const pkg of filteredPackages) {
pkg.contents = this.options.contents;
}
}

if (filteredPackages.length !== this.targetGraph.size && !this.options.forceLocal) {
this.logger.warn("bootstrap", "Installing local packages that do not match filters from registry");

Expand Down
12 changes: 12 additions & 0 deletions commands/link/README.md
Expand Up @@ -21,3 +21,15 @@ $ lerna link --force-local
```

When passed, this flag causes the `link` command to always symlink local dependencies regardless of matching version range.

### `publishConfig.directory`

This _non-standard_ field allows you to customize the symlinked subdirectory that will be the _source_ directory of the symlink, just like how the published package would be consumed.

```json
"publishConfig": {
"directory": "dist"
}
```

In this example, when this package is linked, the `dist` directory will be the source directory (e.g. `package-1/dist => node_modules/package-1`).
3 changes: 3 additions & 0 deletions commands/link/__tests__/__fixtures__/with-contents/lerna.json
@@ -0,0 +1,3 @@
{
"version": "1.0.0"
}
@@ -0,0 +1,3 @@
{
"name": "independent"
}
@@ -0,0 +1,7 @@
{
"name": "@test/package-1",
"version": "1.0.0",
"publishConfig": {
"directory": "dist"
evocateur marked this conversation as resolved.
Show resolved Hide resolved
}
}
@@ -0,0 +1,3 @@
#!/usr/bin/env node
/* eslint-disable node/shebang */
console.log("Hello, world!");
@@ -0,0 +1,11 @@
{
"name": "@test/package-2",
"version": "1.0.0",
"publishConfig": {
"directory": "dist"
},
"bin": "cli.js",
"dependencies": {
"@test/package-1": "^1.0.0"
}
}
@@ -0,0 +1,3 @@
#!/usr/bin/env node
/* eslint-disable node/shebang */
console.log("Hello, world!");
@@ -0,0 +1,3 @@
#!/usr/bin/env node
/* eslint-disable node/shebang */
console.log("Hello, world!");
@@ -0,0 +1,3 @@
#!/usr/bin/env node
/* eslint-disable node/shebang */
console.log("Hello, world!");
@@ -0,0 +1,15 @@
{
"name": "package-3",
"version": "1.0.0",
"publishConfig": {
"directory": "dist"
},
"bin": {
"package3cli1": "cli1.js",
"package3cli2": "cli2.js"
},
"devDependencies": {
"@test/package-1": "^1.0.0",
"@test/package-2": "^1.0.0"
}
}
@@ -0,0 +1,3 @@
#!/usr/bin/env node
/* eslint-disable node/shebang */
console.log("Hello, world!");
@@ -0,0 +1,3 @@
#!/usr/bin/env node
/* eslint-disable node/shebang */
console.log("Hello, world!");
@@ -0,0 +1,11 @@
{
"name": "package-4",
"version": "1.0.0",
"publishConfig": {
"directory": "dist"
},
"dependencies": {
"@test/package-1": "^0.0.0",
"package-3": "^1.0.0"
}
}
94 changes: 94 additions & 0 deletions commands/link/__tests__/link-command.test.js
Expand Up @@ -87,6 +87,53 @@ Array [
});
});

describe("with publishConfig.directory", () => {
it("should symlink sub-directory of package folders and bin directories", async () => {
const testDir = await initFixture("with-contents");
await lernaLink(testDir)();

expect(symlinkedDirectories(testDir)).toMatchInlineSnapshot(`
Array [
Object {
"_src": "packages/package-1/dist",
"dest": "packages/package-2/node_modules/@test/package-1",
"type": "junction",
},
Object {
"_src": "packages/package-1/dist",
"dest": "packages/package-3/node_modules/@test/package-1",
"type": "junction",
},
Object {
"_src": "packages/package-2/dist",
"dest": "packages/package-3/node_modules/@test/package-2",
"type": "junction",
},
Object {
"_src": "packages/package-2/dist/cli.js",
"dest": "packages/package-3/node_modules/.bin/package-2",
"type": "exec",
},
Object {
"_src": "packages/package-3/dist",
"dest": "packages/package-4/node_modules/package-3",
"type": "junction",
},
Object {
"_src": "packages/package-3/dist/cli1.js",
"dest": "packages/package-4/node_modules/.bin/package3cli1",
"type": "exec",
},
Object {
"_src": "packages/package-3/dist/cli2.js",
"dest": "packages/package-4/node_modules/.bin/package3cli2",
"type": "exec",
},
]
`);
});
});

describe("with --force-local", () => {
it("should force symlink of all packages", async () => {
const testDir = await initFixture("force-local");
Expand Down Expand Up @@ -135,6 +182,53 @@ Array [
"type": "exec",
},
]
`);
});
});

describe("with --contents", () => {
it("should symlink sub-directory of package folders and bin directories", async () => {
const testDir = await initFixture("with-contents");
await lernaLink(testDir)("--contents", "build");

expect(symlinkedDirectories(testDir)).toMatchInlineSnapshot(`
Array [
Object {
"_src": "packages/package-1/build",
"dest": "packages/package-2/node_modules/@test/package-1",
"type": "junction",
},
Object {
"_src": "packages/package-1/build",
"dest": "packages/package-3/node_modules/@test/package-1",
"type": "junction",
},
Object {
"_src": "packages/package-2/build",
"dest": "packages/package-3/node_modules/@test/package-2",
"type": "junction",
},
Object {
"_src": "packages/package-2/build/cli.js",
"dest": "packages/package-3/node_modules/.bin/package-2",
"type": "exec",
},
Object {
"_src": "packages/package-3/build",
"dest": "packages/package-4/node_modules/package-3",
"type": "junction",
},
Object {
"_src": "packages/package-3/build/cli1.js",
"dest": "packages/package-4/node_modules/.bin/package3cli1",
"type": "exec",
},
Object {
"_src": "packages/package-3/build/cli2.js",
"dest": "packages/package-4/node_modules/.bin/package3cli2",
"type": "exec",
},
]
`);
});
});
Expand Down
6 changes: 6 additions & 0 deletions commands/link/command.js
Expand Up @@ -14,6 +14,12 @@ exports.builder = yargs => {
describe: "Force local sibling links regardless of version range match",
type: "boolean",
},
contents: {
group: "Command Options:",
describe: "Subdirectory to use as the source of the symlink. Must apply to ALL packages.",
type: "string",
defaultDescription: ".",
},
});

return yargs.command(
Expand Down
8 changes: 8 additions & 0 deletions commands/link/index.js
Expand Up @@ -20,6 +20,14 @@ class LinkCommand extends Command {

initialize() {
this.allPackages = this.packageGraph.rawPackageList;

if (this.options.contents) {
// globally override directory to link
for (const pkg of this.allPackages) {
pkg.contents = this.options.contents;
}
}

this.targetGraph = this.options.forceLocal
? new PackageGraph(this.allPackages, "allDependencies", "forceLocal")
: this.packageGraph;
Expand Down
5 changes: 4 additions & 1 deletion utils/symlink-binary/symlink-binary.js
Expand Up @@ -19,7 +19,10 @@ function symlinkBinary(srcPackageRef, destPackageRef) {
return Promise.all([Package.lazy(srcPackageRef), Package.lazy(destPackageRef)]).then(
([srcPackage, destPackage]) => {
const actions = Object.keys(srcPackage.bin).map(name => {
const src = path.join(srcPackage.location, srcPackage.bin[name]);
const srcLocation = srcPackage.contents
? path.resolve(srcPackage.location, srcPackage.contents)
: srcPackage.location;
const src = path.join(srcLocation, srcPackage.bin[name]);
const dst = path.join(destPackage.binLocation, name);

// Symlink all declared binaries, even if they don't exist (yet). We will
Expand Down
5 changes: 4 additions & 1 deletion utils/symlink-dependencies/symlink-dependencies.js
Expand Up @@ -77,7 +77,10 @@ function symlinkDependencies(packages, packageGraph, tracker) {
});

// create package symlink
chain = chain.then(() => createSymlink(dependencyNode.location, targetDirectory, "junction"));
const dependencyLocation = dependencyNode.pkg.contents
? path.resolve(dependencyNode.location, dependencyNode.pkg.contents)
: dependencyNode.location;
chain = chain.then(() => createSymlink(dependencyLocation, targetDirectory, "junction"));

// TODO: pass PackageGraphNodes directly instead of Packages
chain = chain.then(() => symlinkBinary(dependencyNode.pkg, currentNode.pkg));
Expand Down