From 99a13a9bae2020f2773d21c4109148a59b1ec2d6 Mon Sep 17 00:00:00 2001 From: Austin Fahsl Date: Fri, 9 Sep 2022 10:34:23 -0400 Subject: [PATCH] fix(run): exclude dependencies with --scope when nx.json is not present (#3316) --- commands/run/command.js | 5 + commands/run/index.js | 28 ++- core/command/__tests__/command.test.js | 99 +++++++++++ core/command/index.js | 4 + core/lerna/schemas/lerna-schema.json | 10 ++ .../lerna-run-nx-include-dependencies.spec.ts | 163 ++++++++++++++++++ 6 files changed, 305 insertions(+), 4 deletions(-) create mode 100644 e2e/tests/lerna-run/lerna-run-nx-include-dependencies.spec.ts diff --git a/commands/run/command.js b/commands/run/command.js index 1d5bde2024..98c25e68cf 100644 --- a/commands/run/command.js +++ b/commands/run/command.js @@ -73,6 +73,11 @@ exports.builder = (yargs) => { hidden: true, type: "boolean", }, + verbose: { + group: "Command Options:", + describe: "When useNx is true, show verbose output from dependent tasks.", + type: "boolean", + }, }); return filterOptions(yargs); diff --git a/commands/run/index.js b/commands/run/index.js index 7d80763978..d3c623f792 100644 --- a/commands/run/index.js +++ b/commands/run/index.js @@ -2,6 +2,8 @@ "use strict"; const pMap = require("p-map"); +const path = require("path"); +const { existsSync } = require("fs-extra"); const { Command } = require("@lerna/command"); const { npmRunScript, npmRunScriptStreaming } = require("@lerna/npm-run-script"); @@ -184,7 +186,8 @@ class RunCommand extends Command { } performance.mark("init-local"); this.configureNxOutput(); - const { targetDependencies, options } = this.prepNxOptions(); + const { targetDependencies, options, extraOptions } = this.prepNxOptions(); + if (this.packagesWithScript.length === 1) { const { runOne } = require("nx/src/command-line/run-one"); const fullQualifiedTarget = @@ -197,7 +200,8 @@ class RunCommand extends Command { "project:target:configuration": fullQualifiedTarget, ...options, }, - targetDependencies + targetDependencies, + extraOptions ); } else { const { runMany } = require("nx/src/command-line/run-many"); @@ -208,7 +212,8 @@ class RunCommand extends Command { target: this.script, ...options, }, - targetDependencies + targetDependencies, + extraOptions ); } } @@ -246,10 +251,25 @@ class RunCommand extends Command { nxBail: this.bail, nxIgnoreCycles: !this.options.rejectCycles, skipNxCache: this.options.skipNxCache, + verbose: this.options.verbose, __overrides__: this.args.map((t) => t.toString()), }; - return { targetDependencies, options }; + const excludeTaskDependencies = !existsSync(path.join(this.project.rootPath, "nx.json")); + if (excludeTaskDependencies) { + this.logger.verbose( + this.name, + "nx.json was not found. Task dependencies will not be automatically included." + ); + } else { + this.logger.verbose(this.name, "nx.json was found. Task dependencies will be automatically included."); + } + + const extraOptions = { + excludeTaskDependencies, + }; + + return { targetDependencies, options, extraOptions }; } runScriptInPackagesParallel() { diff --git a/core/command/__tests__/command.test.js b/core/command/__tests__/command.test.js index 91abb62446..063ca2ba9c 100644 --- a/core/command/__tests__/command.test.js +++ b/core/command/__tests__/command.test.js @@ -407,4 +407,103 @@ describe("core-command", () => { ); }); }); + + describe("loglevel with verbose option true", () => { + it("should be set to verbose if loglevel is error", async () => { + const command = testFactory({ + loglevel: "error", + verbose: true, + }); + await command; + + expect(command.options.loglevel).toEqual("verbose"); + }); + + it("should be set to verbose if loglevel is warn", async () => { + const command = testFactory({ + loglevel: "warn", + verbose: true, + }); + await command; + + expect(command.options.loglevel).toEqual("verbose"); + }); + + it("should be set to verbose if loglevel is info", async () => { + const command = testFactory({ + loglevel: "info", + verbose: true, + }); + await command; + + expect(command.options.loglevel).toEqual("verbose"); + }); + + it("should remain set to verbose if loglevel is verbose", async () => { + const command = testFactory({ + loglevel: "verbose", + verbose: true, + }); + await command; + + expect(command.options.loglevel).toEqual("verbose"); + }); + + it("should not be set to verbose if loglevel is silly", async () => { + const command = testFactory({ + loglevel: "silly", + verbose: true, + }); + await command; + + expect(command.options.loglevel).toEqual("silly"); + }); + }); + + describe("loglevel without verbose option", () => { + it("should remain set to error if loglevel is error", async () => { + const command = testFactory({ + loglevel: "error", + }); + await command; + + expect(command.options.loglevel).toEqual("error"); + }); + + it("should remain set to warn if loglevel is warn", async () => { + const command = testFactory({ + loglevel: "warn", + }); + await command; + + expect(command.options.loglevel).toEqual("warn"); + }); + + it("should remain set to info if loglevel is info", async () => { + const command = testFactory({ + loglevel: "info", + }); + await command; + + expect(command.options.loglevel).toEqual("info"); + }); + + it("should remain set to verbose if loglevel is verbose", async () => { + const command = testFactory({ + loglevel: "verbose", + }); + await command; + + expect(command.options.loglevel).toEqual("verbose"); + }); + + it("should remain set to silly if loglevel is silly", async () => { + const command = testFactory({ + loglevel: "silly", + }); + await command; + + expect(command.options.loglevel).toEqual("silly"); + }); + }); }); diff --git a/core/command/index.js b/core/command/index.js index 1e5cca3ece..3838bd79cb 100644 --- a/core/command/index.js +++ b/core/command/index.js @@ -171,6 +171,10 @@ class Command { // Environmental defaults prepared in previous step this.envDefaults ); + + if (this.options.verbose && this.options.loglevel !== "silly") { + this.options.loglevel = "verbose"; + } } configureProperties() { diff --git a/core/lerna/schemas/lerna-schema.json b/core/lerna/schemas/lerna-schema.json index ab604b0beb..797011ee62 100644 --- a/core/lerna/schemas/lerna-schema.json +++ b/core/lerna/schemas/lerna-schema.json @@ -905,6 +905,9 @@ "profileLocation": { "$ref": "#/$defs/commandOptions/shared/profileLocation" }, + "verbose": { + "$ref": "#/$defs/commandOptions/shared/verbose" + }, "skipNxCache": { "$ref": "#/$defs/commandOptions/run/skipNxCache" }, @@ -1395,6 +1398,9 @@ }, "ignorePrepublish": { "$ref": "#/$defs/commandOptions/shared/ignorePrepublish" + }, + "verbose": { + "$ref": "#/$defs/commandOptions/shared/verbose" } }, "$defs": { @@ -1828,6 +1834,10 @@ "ignorePrepublish": { "type": "boolean", "description": "During `lerna publish` and `lerna bootstrap`, when true, disable deprecated 'prepublish' lifecycle script." + }, + "verbose": { + "type": "boolean", + "description": "When true, escalates loglevel to 'verbose' and shows nx verbose output if useNx is true." } } } diff --git a/e2e/tests/lerna-run/lerna-run-nx-include-dependencies.spec.ts b/e2e/tests/lerna-run/lerna-run-nx-include-dependencies.spec.ts new file mode 100644 index 0000000000..fc29bcb16e --- /dev/null +++ b/e2e/tests/lerna-run/lerna-run-nx-include-dependencies.spec.ts @@ -0,0 +1,163 @@ +import { remove } from "fs-extra"; +import { Fixture } from "../../utils/fixture"; +import { normalizeCommandOutput, normalizeEnvironment } from "../../utils/snapshot-serializer-utils"; + +expect.addSnapshotSerializer({ + serialize(str: string) { + return normalizeCommandOutput(normalizeEnvironment(str)); + }, + test(val: string) { + return val != null && typeof val === "string"; + }, +}); + +describe("lerna-run-nx-include-dependencies", () => { + let fixture: Fixture; + + beforeEach(async () => { + fixture = await Fixture.create({ + name: "lerna-run-nx-include-dependencies", + packageManager: "npm", + initializeGit: true, + runLernaInit: true, + installDependencies: true, + /** + * Because lerna run involves spawning further child processes, the tests would be too flaky + * if we didn't force deterministic terminal output by appending stderr to stdout instead + * of interleaving them. + */ + forceDeterministicTerminalOutput: true, + }); + + await fixture.lerna("create package-1 -y"); + await fixture.addScriptsToPackage({ + packagePath: "packages/package-1", + scripts: { + "print-name": "echo test-package-1", + }, + }); + await fixture.lerna("create package-2 -y"); + await fixture.addScriptsToPackage({ + packagePath: "packages/package-2", + scripts: { + "print-name": "echo test-package-2", + }, + }); + await fixture.lerna("create package-3 -y --dependencies package-1 package-2"); + await fixture.addScriptsToPackage({ + packagePath: "packages/package-3", + scripts: { + "print-name": "echo test-package-3", + }, + }); + + await fixture.updateJson("lerna.json", (json) => ({ + ...json, + loglevel: "verbose", + })); + }); + afterEach(() => fixture.destroy()); + + describe("without nx enabled", () => { + it("should exclude dependencies by default", async () => { + const output = await fixture.lerna("run print-name --scope package-3 -- --silent"); + + expect(output.combinedOutput).toMatchInlineSnapshot(` + test-package-X + lerna notice cli v999.9.9-e2e.0 + lerna verb rootPath /tmp/lerna-e2e/lerna-run-nx-include-dependencies/lerna-workspace + lerna notice filter including "package-X" + lerna info filter [ 'package-X' ] + lerna info Executing command in 1 package: "npm run print-name --silent" + lerna info run Ran npm script 'print-name' in 'package-X' in X.Xs: + lerna success run Ran npm script 'print-name' in 1 package in X.Xs: + lerna success - package-X + + `); + }); + }); + + describe("with nx enabled, but no nx.json", () => { + it("should exclude dependencies by default", async () => { + await fixture.addNxToWorkspace(); + + await remove(fixture.getWorkspacePath("nx.json")); + + const output = await fixture.lerna("run print-name --scope package-3 -- --silent"); + + expect(output.combinedOutput).toMatchInlineSnapshot(` + + > package-X:print-name --silent + + + > package-X@0.0.0 print-name + > echo test-package-X "--silent" + + test-package-X --silent + + + + > Lerna (powered by Nx) Successfully ran target print-name for project package-X + + + lerna notice cli v999.9.9-e2e.0 + lerna verb rootPath /tmp/lerna-e2e/lerna-run-nx-include-dependencies/lerna-workspace + lerna notice filter including "package-X" + lerna info filter [ 'package-X' ] + lerna verb run nx.json was not found. Task dependencies will not be automatically included. + + `); + }); + }); + + describe("with nx enabled and with nx.json", () => { + it("should include dependencies by default", async () => { + await fixture.addNxToWorkspace(); + + const output = await fixture.lerna("run print-name --scope package-3 -- --silent"); + + expect(output.combinedOutput).toMatchInlineSnapshot(` + + > Lerna (powered by Nx) Running target print-name for project package-X and 2 task(s) it depends on + + + +> package-X:print-name + + +> package-X@0.0.0 print-name +> echo test-package-X + +test-package-X + +> package-X:print-name + + +> package-X@0.0.0 print-name +> echo test-package-X + +test-package-X + +> package-X:print-name --silent + + +> package-X@0.0.0 print-name +> echo test-package-X "--silent" + +test-package-X --silent + + + + > Lerna (powered by Nx) Successfully ran target print-name for project package-X + + +lerna notice cli v999.9.9-e2e.0 +lerna verb rootPath /tmp/lerna-e2e/lerna-run-nx-include-dependencies/lerna-workspace +lerna notice filter including "package-X" +lerna info filter [ 'package-X' ] +lerna verb run nx.json was found. Task dependencies will be automatically included. + +`); + }); + }); +});