diff --git a/commands/bootstrap/__tests__/__snapshots__/bootstrap-command.test.js.snap b/commands/bootstrap/__tests__/__snapshots__/bootstrap-command.test.js.snap index ebfd0fb864..2424dbf80d 100644 --- a/commands/bootstrap/__tests__/__snapshots__/bootstrap-command.test.js.snap +++ b/commands/bootstrap/__tests__/__snapshots__/bootstrap-command.test.js.snap @@ -252,15 +252,39 @@ Object { "bar@^2.0.0", "foo@^1.0.0", ], + "packages/package-3": Array [ + "foo@0.1.12", + "@test/package-2@^1.0.0", + ], +} +`; + +exports[`BootstrapCommand with local package dependencies should not bootstrap ignored packages 2`] = ` +Array [ + Object { + "_src": "packages/package-1", + "dest": "packages/package-3/node_modules/@test/package-1", + "type": "junction", + }, +] +`; + +exports[`BootstrapCommand with local package dependencies should not update package.json when filtering 1`] = ` +Object { "packages/package-2": Array [ + "@test/package-1@^1.0.0", "foo@^1.0.0", ], } `; +exports[`BootstrapCommand with local package dependencies should not update package.json when filtering 2`] = `Array []`; + exports[`BootstrapCommand with local package dependencies should only bootstrap scoped packages 1`] = ` Object { "packages/package-3": Array [ + "@test/package-1@^1.0.0", + "@test/package-2@^1.0.0", "foo@0.1.12", ], "packages/package-4": Array [ @@ -271,21 +295,6 @@ Object { exports[`BootstrapCommand with local package dependencies should only bootstrap scoped packages 2`] = ` Array [ - Object { - "_src": "packages/package-1", - "dest": "packages/package-3/node_modules/@test/package-1", - "type": "junction", - }, - Object { - "_src": "packages/package-2", - "dest": "packages/package-3/node_modules/@test/package-2", - "type": "junction", - }, - Object { - "_src": "packages/package-2/cli.js", - "dest": "packages/package-3/node_modules/.bin/package-2", - "type": "exec", - }, Object { "_src": "packages/package-3", "dest": "packages/package-4/node_modules/package-3", @@ -304,6 +313,28 @@ Array [ ] `; +exports[`BootstrapCommand with local package dependencies should respect --force-local 1`] = ` +Object { + "packages/package-1": Array [ + "bar@^2.0.0", + "foo@^1.0.0", + ], + "packages/package-4": Array [ + "package-3@^1.0.0", + ], +} +`; + +exports[`BootstrapCommand with local package dependencies should respect --force-local 2`] = ` +Array [ + Object { + "_src": "packages/package-1", + "dest": "packages/package-4/node_modules/@test/package-1", + "type": "junction", + }, +] +`; + exports[`BootstrapCommand with multiple package locations bootstraps dependencies excluded by --ignore with --include-filtered-dependencies 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 84b06b6136..2d55bdef27 100644 --- a/commands/bootstrap/__tests__/bootstrap-command.test.js +++ b/commands/bootstrap/__tests__/bootstrap-command.test.js @@ -228,7 +228,7 @@ describe("BootstrapCommand", () => { it("should not pass subCommand to npmInstall if on npm version earlier than 5.7.0", async () => { const testDir = await initFixture("ci"); - hasNpmVersion.mockReturnValue(false); + hasNpmVersion.mockReturnValueOnce(false); await lernaBootstrap(testDir)("--ci"); @@ -277,9 +277,10 @@ describe("BootstrapCommand", () => { it("should not bootstrap ignored packages", async () => { const testDir = await initFixture("basic"); - await lernaBootstrap(testDir)("--ignore", "package-@(3|4)"); + await lernaBootstrap(testDir)("--ignore", "{@test/package-2,package-4}"); expect(installedPackagesInDirectories(testDir)).toMatchSnapshot(); + expect(symlinkedDirectories(testDir)).toMatchSnapshot(); }); it("should only bootstrap scoped packages", async () => { @@ -291,6 +292,40 @@ describe("BootstrapCommand", () => { expect(symlinkedDirectories(testDir)).toMatchSnapshot(); }); + it("should respect --force-local", async () => { + const testDir = await initFixture("basic"); + + await lernaBootstrap(testDir)("--scope", "@test/package-1", "--scope", "package-4", "--force-local"); + + expect(installedPackagesInDirectories(testDir)).toMatchSnapshot(); + expect(symlinkedDirectories(testDir)).toMatchSnapshot(); + }); + + it("should not update package.json when filtering", async () => { + const testDir = await initFixture("basic"); + + await lernaBootstrap(testDir)("--scope", "@test/package-2", "--ci"); + + expect(installedPackagesInDirectories(testDir)).toMatchSnapshot(); + expect(symlinkedDirectories(testDir)).toMatchSnapshot(); + expect(npmInstall.dependencies.mock.calls[0][2]).toMatchObject({ + subCommand: "install", // not "ci" + npmClient: "npm", + npmClientArgs: ["--no-save"], + }); + }); + + it("should not update yarn.lock when filtering", async () => { + const testDir = await initFixture("basic"); + + await lernaBootstrap(testDir)("--scope", "@test/package-2", "--npm-client", "yarn", "--ci"); + + expect(npmInstall.dependencies.mock.calls[0][2]).toMatchObject({ + npmClient: "yarn", + npmClientArgs: ["--pure-lockfile"], + }); + }); + it("never installs with global style", async () => { const testDir = await initFixture("basic"); diff --git a/commands/bootstrap/index.js b/commands/bootstrap/index.js index 29e2d3aa2d..a3762d909c 100644 --- a/commands/bootstrap/index.js +++ b/commands/bootstrap/index.js @@ -103,9 +103,47 @@ class BootstrapCommand extends Command { let chain = Promise.resolve(); - chain = chain.then(() => getFilteredPackages(this.targetGraph, this.execOpts, this.options)); + chain = chain.then(() => { + if (this.options.scope) { + this.logger.notice("filter", "including %j", this.options.scope); + } + + if (this.options.ignore) { + this.logger.notice("filter", "excluding %j", this.options.ignore); + } + + if (this.options.since) { + this.logger.notice("filter", "changed since %j", this.options.since); + } + + if (this.options.includeFilteredDependents) { + this.logger.notice("filter", "including filtered dependents"); + } + + if (this.options.includeFilteredDependencies) { + this.logger.notice("filter", "including filtered dependencies"); + } + + return getFilteredPackages(this.targetGraph, this.execOpts, this.options); + }); + chain = chain.then(filteredPackages => { this.filteredPackages = filteredPackages; + + if (filteredPackages.length !== this.targetGraph.size) { + this.logger.warn("bootstrap", "Installing local packages that do not match filters from registry"); + + // an explicit --scope, --ignore, or --since should only symlink the targeted packages, no others + this.targetGraph = new PackageGraph(filteredPackages, "allDependencies", this.options.forceLocal); + + // never automatically --save or modify lockfiles + this.npmConfig.npmClientArgs.unshift(npmClient === "yarn" ? "--pure-lockfile" : "--no-save"); + + // never attempt `npm ci`, it would always fail + if (this.npmConfig.subCommand === "ci") { + this.npmConfig.subCommand = "install"; + } + } }); chain = chain.then(() => {