Skip to content

Commit

Permalink
feat(link): Use publishConfig.directory as symlink source if it exi…
Browse files Browse the repository at this point in the history
…sts to allow linking sub-directories (#2274)
  • Loading branch information
kamranayub authored and evocateur committed Oct 10, 2019
1 parent f057409 commit d04ce8e
Show file tree
Hide file tree
Showing 23 changed files with 269 additions and 2 deletions.
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 () => {
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"
}
}
@@ -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

0 comments on commit d04ce8e

Please sign in to comment.