diff --git a/__fixtures__/lifecycle-rooted-leaf/lerna.json b/__fixtures__/lifecycle-rooted-leaf/lerna.json new file mode 100644 index 0000000000..86d5f91496 --- /dev/null +++ b/__fixtures__/lifecycle-rooted-leaf/lerna.json @@ -0,0 +1,7 @@ +{ + "packages": [ + "packages/*", + "." + ], + "version": "1.0.0" +} diff --git a/__fixtures__/lifecycle-rooted-leaf/package.json b/__fixtures__/lifecycle-rooted-leaf/package.json new file mode 100644 index 0000000000..cffbc63bd8 --- /dev/null +++ b/__fixtures__/lifecycle-rooted-leaf/package.json @@ -0,0 +1,18 @@ +{ + "name": "lifecycle-rooted-leaf", + "version": "1.0.0", + "dependencies": { + "package-1": "file:packages/package-1" + }, + "scripts": { + "preversion": "echo preversion-rooted-leaf", + "version": "echo version-rooted-leaf", + "postversion": "echo postversion-rooted-leaf", + "prepublish": "echo prepublish-rooted-leaf", + "prepare": "echo prepare-rooted-leaf", + "prepublishOnly": "echo prepublishOnly-rooted-leaf", + "prepack": "echo prepack-rooted-leaf", + "postpack": "echo postpack-rooted-leaf", + "postpublish": "echo postpublish-rooted-leaf" + } +} diff --git a/commands/publish/__tests__/__fixtures__/lifecycle/packages/package-1/package.json b/__fixtures__/lifecycle-rooted-leaf/packages/package-1/package.json similarity index 100% rename from commands/publish/__tests__/__fixtures__/lifecycle/packages/package-1/package.json rename to __fixtures__/lifecycle-rooted-leaf/packages/package-1/package.json diff --git a/commands/publish/__tests__/__fixtures__/lifecycle-no-root-name/packages/package-2/package.json b/__fixtures__/lifecycle-rooted-leaf/packages/package-2/package.json similarity index 100% rename from commands/publish/__tests__/__fixtures__/lifecycle-no-root-name/packages/package-2/package.json rename to __fixtures__/lifecycle-rooted-leaf/packages/package-2/package.json diff --git a/commands/publish/__tests__/__fixtures__/lifecycle-no-root-name/lerna.json b/__fixtures__/lifecycle/lerna.json similarity index 100% rename from commands/publish/__tests__/__fixtures__/lifecycle-no-root-name/lerna.json rename to __fixtures__/lifecycle/lerna.json diff --git a/commands/publish/__tests__/__fixtures__/lifecycle/package.json b/__fixtures__/lifecycle/package.json similarity index 100% rename from commands/publish/__tests__/__fixtures__/lifecycle/package.json rename to __fixtures__/lifecycle/package.json diff --git a/__fixtures__/lifecycle/packages/package-1/package.json b/__fixtures__/lifecycle/packages/package-1/package.json new file mode 100644 index 0000000000..11bfd3b0b3 --- /dev/null +++ b/__fixtures__/lifecycle/packages/package-1/package.json @@ -0,0 +1,13 @@ +{ + "name": "package-1", + "version": "1.0.0", + "scripts": { + "preversion": "echo preversion-package-1", + "version": "echo version-package-1", + "postversion": "echo postversion-package-1", + "prepare": "echo prepare-package-1", + "prepublishOnly": "echo prepublishOnly-package-1", + "prepack": "echo prepack-package-1", + "postpublish": "echo postpublish-package-1" + } +} diff --git a/commands/publish/__tests__/__fixtures__/lifecycle/packages/package-2/package.json b/__fixtures__/lifecycle/packages/package-2/package.json similarity index 100% rename from commands/publish/__tests__/__fixtures__/lifecycle/packages/package-2/package.json rename to __fixtures__/lifecycle/packages/package-2/package.json diff --git a/commands/publish/__tests__/__fixtures__/lifecycle-no-root-name/package.json b/commands/publish/__tests__/__fixtures__/lifecycle-no-root-name/package.json deleted file mode 100644 index 01ad18e38b..0000000000 --- a/commands/publish/__tests__/__fixtures__/lifecycle-no-root-name/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "version": "0.0.0-missing-name", - "scripts": { - "preversion": "echo preversion-root", - "version": "echo version-root", - "postversion": "echo postversion-root" - } -} diff --git a/commands/publish/__tests__/__fixtures__/lifecycle-no-root-name/packages/package-1/package.json b/commands/publish/__tests__/__fixtures__/lifecycle-no-root-name/packages/package-1/package.json deleted file mode 100644 index 9b5e2ae573..0000000000 --- a/commands/publish/__tests__/__fixtures__/lifecycle-no-root-name/packages/package-1/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "package-1", - "version": "1.0.0", - "scripts": { - "preversion": "echo preversion-package-1", - "version": "echo version-package-1", - "postversion": "echo postversion-package-1" - } -} diff --git a/commands/publish/__tests__/__fixtures__/lifecycle/lerna.json b/commands/publish/__tests__/__fixtures__/lifecycle/lerna.json deleted file mode 100644 index 1587a66968..0000000000 --- a/commands/publish/__tests__/__fixtures__/lifecycle/lerna.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "version": "1.0.0" -} diff --git a/commands/publish/__tests__/publish-lifecycle-scripts.test.js b/commands/publish/__tests__/publish-lifecycle-scripts.test.js index f17c04550b..3c3327247e 100644 --- a/commands/publish/__tests__/publish-lifecycle-scripts.test.js +++ b/commands/publish/__tests__/publish-lifecycle-scripts.test.js @@ -90,4 +90,22 @@ Map { ["lifecycle", "postpack"], ]); }); + + it("does not duplicate rooted leaf scripts", async () => { + const cwd = await initFixture("lifecycle-rooted-leaf"); + + await lernaPublish(cwd)(); + + expect(runLifecycle.getOrderedCalls()).toEqual([ + // TODO: separate from VersionCommand details + ["package-1", "preversion"], + ["package-1", "version"], + ["lifecycle-rooted-leaf", "preversion"], + ["lifecycle-rooted-leaf", "version"], + ["lifecycle-rooted-leaf", "postversion"], + ["package-1", "postversion"], + // NO publish-specific root lifecycles should be duplicated + // (they are all run by pack-directory and npm-publish) + ]); + }); }); diff --git a/commands/publish/index.js b/commands/publish/index.js index f10b47f7fe..e8bbd5f8d8 100644 --- a/commands/publish/index.js +++ b/commands/publish/index.js @@ -114,6 +114,13 @@ class PublishCommand extends Command { this.conf.set("tag", distTag.trim(), "cli"); } + // a "rooted leaf" is the regrettable pattern of adding "." to the "packages" config in lerna.json + this.hasRootedLeaf = this.packageGraph.has(this.project.manifest.name); + + if (this.hasRootedLeaf) { + this.logger.info("publish", "rooted leaf detected, skipping synthetic root lifecycles"); + } + this.runPackageLifecycle = createRunner(this.options); // don't execute recursively if run from a poorly-named script @@ -572,13 +579,15 @@ class PublishCommand extends Command { chain = chain.then(() => createTempLicenses(this.project.licensePath, this.packagesToBeLicensed)); - // despite being deprecated for years... - chain = chain.then(() => this.runRootLifecycle("prepublish")); + if (!this.hasRootedLeaf) { + // despite being deprecated for years... + chain = chain.then(() => this.runRootLifecycle("prepublish")); - // these lifecycles _should_ never be employed to run `lerna publish`... - chain = chain.then(() => this.runPackageLifecycle(this.project.manifest, "prepare")); - chain = chain.then(() => this.runPackageLifecycle(this.project.manifest, "prepublishOnly")); - chain = chain.then(() => this.runPackageLifecycle(this.project.manifest, "prepack")); + // these lifecycles _should_ never be employed to run `lerna publish`... + chain = chain.then(() => this.runPackageLifecycle(this.project.manifest, "prepare")); + chain = chain.then(() => this.runPackageLifecycle(this.project.manifest, "prepublishOnly")); + chain = chain.then(() => this.runPackageLifecycle(this.project.manifest, "prepack")); + } const { contents } = this.options; const getLocation = contents ? pkg => path.resolve(pkg.location, contents) : pkg => pkg.location; @@ -611,7 +620,9 @@ class PublishCommand extends Command { // remove temporary license files if _any_ error occurs _anywhere_ in the promise chain chain = chain.catch(error => this.removeTempLicensesOnError(error)); - chain = chain.then(() => this.runPackageLifecycle(this.project.manifest, "postpack")); + if (!this.hasRootedLeaf) { + chain = chain.then(() => this.runPackageLifecycle(this.project.manifest, "postpack")); + } return pFinally(chain, () => tracker.finish()); } @@ -647,9 +658,11 @@ class PublishCommand extends Command { chain = chain.then(() => runParallelBatches(this.batchedPackages, this.concurrency, mapper)); - // cyclical "publish" lifecycles are automatically skipped - chain = chain.then(() => this.runRootLifecycle("publish")); - chain = chain.then(() => this.runRootLifecycle("postpublish")); + if (!this.hasRootedLeaf) { + // cyclical "publish" lifecycles are automatically skipped + chain = chain.then(() => this.runRootLifecycle("publish")); + chain = chain.then(() => this.runRootLifecycle("postpublish")); + } return pFinally(chain, () => tracker.finish()); } diff --git a/commands/version/__tests__/version-lifecycle-scripts.test.js b/commands/version/__tests__/version-lifecycle-scripts.test.js index 1fce67567d..4f0b5a21d5 100644 --- a/commands/version/__tests__/version-lifecycle-scripts.test.js +++ b/commands/version/__tests__/version-lifecycle-scripts.test.js @@ -6,14 +6,12 @@ jest.mock("../lib/is-anything-committed"); jest.mock("../lib/is-behind-upstream"); jest.mock("../lib/remote-branch-exists"); -const path = require("path"); - // mocked modules const runLifecycle = require("@lerna/run-lifecycle"); const loadJsonFile = require("load-json-file"); // helpers -const initFixture = require("@lerna-test/init-fixture")(path.resolve(__dirname, "../../publish/__tests__")); +const initFixture = require("@lerna-test/init-fixture")(__dirname); // test command const lernaVersion = require("@lerna-test/command-runner")(require("../command")); @@ -74,4 +72,19 @@ Map { ["package-1", "postversion"], ]); }); + + it("does not duplicate rooted leaf scripts", async () => { + const cwd = await initFixture("lifecycle-rooted-leaf"); + + await lernaVersion(cwd)(); + + expect(runLifecycle.getOrderedCalls()).toEqual([ + ["package-1", "preversion"], + ["package-1", "version"], + ["lifecycle-rooted-leaf", "preversion"], + ["lifecycle-rooted-leaf", "version"], + ["lifecycle-rooted-leaf", "postversion"], + ["package-1", "postversion"], + ]); + }); }); diff --git a/commands/version/index.js b/commands/version/index.js index 6b6ab98d61..55dee284f2 100644 --- a/commands/version/index.js +++ b/commands/version/index.js @@ -207,6 +207,13 @@ class VersionCommand extends Command { return false; } + // a "rooted leaf" is the regrettable pattern of adding "." to the "packages" config in lerna.json + this.hasRootedLeaf = this.packageGraph.has(this.project.manifest.name); + + if (this.hasRootedLeaf && !this.composed) { + this.logger.info("version", "rooted leaf detected, skipping synthetic root lifecycles"); + } + this.runPackageLifecycle = createRunner(this.options); // don't execute recursively if run from a poorly-named script @@ -450,8 +457,10 @@ class VersionCommand extends Command { // postversion: Run AFTER bumping the package version, and AFTER commit. // @see https://docs.npmjs.com/misc/scripts - // exec preversion lifecycle in root (before all updates) - chain = chain.then(() => this.runRootLifecycle("preversion")); + if (!this.hasRootedLeaf) { + // exec preversion lifecycle in root (before all updates) + chain = chain.then(() => this.runRootLifecycle("preversion")); + } const actions = [ pkg => this.runPackageLifecycle(pkg, "preversion").then(() => pkg), @@ -548,8 +557,10 @@ class VersionCommand extends Command { ); } - // exec version lifecycle in root (after all updates) - chain = chain.then(() => this.runRootLifecycle("version")); + if (!this.hasRootedLeaf) { + // exec version lifecycle in root (after all updates) + chain = chain.then(() => this.runRootLifecycle("version")); + } if (this.commitAndTag) { chain = chain.then(() => gitAdd(Array.from(changedFiles), this.execOpts)); @@ -576,8 +587,10 @@ class VersionCommand extends Command { pMap(this.packagesToVersion, pkg => this.runPackageLifecycle(pkg, "postversion")) ); - // run postversion, if set, in the root directory - chain = chain.then(() => this.runRootLifecycle("postversion")); + if (!this.hasRootedLeaf) { + // run postversion, if set, in the root directory + chain = chain.then(() => this.runRootLifecycle("postversion")); + } return chain; }