diff --git a/commands/bootstrap/README.md b/commands/bootstrap/README.md index 9f9f294c26..b71832cc78 100644 --- a/commands/bootstrap/README.md +++ b/commands/bootstrap/README.md @@ -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. diff --git a/commands/bootstrap/__tests__/__snapshots__/bootstrap-command.test.js.snap b/commands/bootstrap/__tests__/__snapshots__/bootstrap-command.test.js.snap index 51649aef7c..5acb8f1bb6 100644 --- a/commands/bootstrap/__tests__/__snapshots__/bootstrap-command.test.js.snap +++ b/commands/bootstrap/__tests__/__snapshots__/bootstrap-command.test.js.snap @@ -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 [ diff --git a/commands/bootstrap/__tests__/bootstrap-command.test.js b/commands/bootstrap/__tests__/bootstrap-command.test.js index d61ab75754..6a9b7f015b 100644 --- a/commands/bootstrap/__tests__/bootstrap-command.test.js +++ b/commands/bootstrap/__tests__/bootstrap-command.test.js @@ -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"); diff --git a/commands/bootstrap/command.js b/commands/bootstrap/command.js index 61728ca2fb..f4e633ce4c 100644 --- a/commands/bootstrap/command.js +++ b/commands/bootstrap/command.js @@ -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); diff --git a/commands/bootstrap/index.js b/commands/bootstrap/index.js index dc5034d2a4..f0601be169 100644 --- a/commands/bootstrap/index.js +++ b/commands/bootstrap/index.js @@ -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"); diff --git a/commands/link/README.md b/commands/link/README.md index 9bfa675e63..3d34c8b6fc 100644 --- a/commands/link/README.md +++ b/commands/link/README.md @@ -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`). \ No newline at end of file diff --git a/commands/link/__tests__/__fixtures__/with-contents/lerna.json b/commands/link/__tests__/__fixtures__/with-contents/lerna.json new file mode 100644 index 0000000000..1587a66968 --- /dev/null +++ b/commands/link/__tests__/__fixtures__/with-contents/lerna.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} diff --git a/commands/link/__tests__/__fixtures__/with-contents/package.json b/commands/link/__tests__/__fixtures__/with-contents/package.json new file mode 100644 index 0000000000..46358b1693 --- /dev/null +++ b/commands/link/__tests__/__fixtures__/with-contents/package.json @@ -0,0 +1,3 @@ +{ + "name": "independent" +} diff --git a/commands/link/__tests__/__fixtures__/with-contents/packages/package-1/package.json b/commands/link/__tests__/__fixtures__/with-contents/packages/package-1/package.json new file mode 100644 index 0000000000..76f973d40d --- /dev/null +++ b/commands/link/__tests__/__fixtures__/with-contents/packages/package-1/package.json @@ -0,0 +1,7 @@ +{ + "name": "@test/package-1", + "version": "1.0.0", + "publishConfig": { + "directory": "dist" + } +} diff --git a/commands/link/__tests__/__fixtures__/with-contents/packages/package-2/dist/cli.js b/commands/link/__tests__/__fixtures__/with-contents/packages/package-2/dist/cli.js new file mode 100755 index 0000000000..e4a10e1b55 --- /dev/null +++ b/commands/link/__tests__/__fixtures__/with-contents/packages/package-2/dist/cli.js @@ -0,0 +1,3 @@ +#!/usr/bin/env node +/* eslint-disable node/shebang */ +console.log("Hello, world!"); diff --git a/commands/link/__tests__/__fixtures__/with-contents/packages/package-2/package.json b/commands/link/__tests__/__fixtures__/with-contents/packages/package-2/package.json new file mode 100644 index 0000000000..de663e1f6a --- /dev/null +++ b/commands/link/__tests__/__fixtures__/with-contents/packages/package-2/package.json @@ -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" + } +} diff --git a/commands/link/__tests__/__fixtures__/with-contents/packages/package-2/src/cli.ts b/commands/link/__tests__/__fixtures__/with-contents/packages/package-2/src/cli.ts new file mode 100644 index 0000000000..e4a10e1b55 --- /dev/null +++ b/commands/link/__tests__/__fixtures__/with-contents/packages/package-2/src/cli.ts @@ -0,0 +1,3 @@ +#!/usr/bin/env node +/* eslint-disable node/shebang */ +console.log("Hello, world!"); diff --git a/commands/link/__tests__/__fixtures__/with-contents/packages/package-3/dist/cli1.js b/commands/link/__tests__/__fixtures__/with-contents/packages/package-3/dist/cli1.js new file mode 100755 index 0000000000..e4a10e1b55 --- /dev/null +++ b/commands/link/__tests__/__fixtures__/with-contents/packages/package-3/dist/cli1.js @@ -0,0 +1,3 @@ +#!/usr/bin/env node +/* eslint-disable node/shebang */ +console.log("Hello, world!"); diff --git a/commands/link/__tests__/__fixtures__/with-contents/packages/package-3/dist/cli2.js b/commands/link/__tests__/__fixtures__/with-contents/packages/package-3/dist/cli2.js new file mode 100755 index 0000000000..e4a10e1b55 --- /dev/null +++ b/commands/link/__tests__/__fixtures__/with-contents/packages/package-3/dist/cli2.js @@ -0,0 +1,3 @@ +#!/usr/bin/env node +/* eslint-disable node/shebang */ +console.log("Hello, world!"); diff --git a/commands/link/__tests__/__fixtures__/with-contents/packages/package-3/package.json b/commands/link/__tests__/__fixtures__/with-contents/packages/package-3/package.json new file mode 100644 index 0000000000..21c24c72ff --- /dev/null +++ b/commands/link/__tests__/__fixtures__/with-contents/packages/package-3/package.json @@ -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" + } +} diff --git a/commands/link/__tests__/__fixtures__/with-contents/packages/package-3/src/cli1.ts b/commands/link/__tests__/__fixtures__/with-contents/packages/package-3/src/cli1.ts new file mode 100644 index 0000000000..e4a10e1b55 --- /dev/null +++ b/commands/link/__tests__/__fixtures__/with-contents/packages/package-3/src/cli1.ts @@ -0,0 +1,3 @@ +#!/usr/bin/env node +/* eslint-disable node/shebang */ +console.log("Hello, world!"); diff --git a/commands/link/__tests__/__fixtures__/with-contents/packages/package-3/src/cli2.ts b/commands/link/__tests__/__fixtures__/with-contents/packages/package-3/src/cli2.ts new file mode 100644 index 0000000000..e4a10e1b55 --- /dev/null +++ b/commands/link/__tests__/__fixtures__/with-contents/packages/package-3/src/cli2.ts @@ -0,0 +1,3 @@ +#!/usr/bin/env node +/* eslint-disable node/shebang */ +console.log("Hello, world!"); diff --git a/commands/link/__tests__/__fixtures__/with-contents/packages/package-4/package.json b/commands/link/__tests__/__fixtures__/with-contents/packages/package-4/package.json new file mode 100644 index 0000000000..0cfacf8051 --- /dev/null +++ b/commands/link/__tests__/__fixtures__/with-contents/packages/package-4/package.json @@ -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" + } +} diff --git a/commands/link/__tests__/link-command.test.js b/commands/link/__tests__/link-command.test.js index 61d12f8b7b..871f9fc497 100644 --- a/commands/link/__tests__/link-command.test.js +++ b/commands/link/__tests__/link-command.test.js @@ -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"); @@ -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", + }, +] `); }); }); diff --git a/commands/link/command.js b/commands/link/command.js index 04362fe24f..d6c05b5150 100644 --- a/commands/link/command.js +++ b/commands/link/command.js @@ -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( diff --git a/commands/link/index.js b/commands/link/index.js index 7cd15a57ca..2cfe70e7ce 100644 --- a/commands/link/index.js +++ b/commands/link/index.js @@ -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; diff --git a/utils/symlink-binary/symlink-binary.js b/utils/symlink-binary/symlink-binary.js index d6b12bd6e2..4ba222c1b8 100644 --- a/utils/symlink-binary/symlink-binary.js +++ b/utils/symlink-binary/symlink-binary.js @@ -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 diff --git a/utils/symlink-dependencies/symlink-dependencies.js b/utils/symlink-dependencies/symlink-dependencies.js index ce01df6b15..4610ecdb5d 100644 --- a/utils/symlink-dependencies/symlink-dependencies.js +++ b/utils/symlink-dependencies/symlink-dependencies.js @@ -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));