Skip to content

Commit

Permalink
feat(run): add experimental support to run tasks via Nx
Browse files Browse the repository at this point in the history
- follows same implementation as Lerna's [PR #3139](lerna/lerna#3139), follow the link to get full description
  • Loading branch information
ghiscoding committed May 26, 2022
1 parent b907126 commit 743d434
Show file tree
Hide file tree
Showing 14 changed files with 1,367 additions and 27 deletions.
1,222 changes: 1,207 additions & 15 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -83,6 +83,7 @@
"normalize-newline": "^3.0.0",
"normalize-path": "^3.0.0",
"npm-run-all": "^4.1.5",
"nx": "14.1.8",
"rimraf": "^3.0.2",
"tacks": "^1.3.0",
"tempy": "^1.0.1",
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/models/command-options.ts
Expand Up @@ -282,4 +282,7 @@ export interface RunCommandOption {

/** npm script to run by the command */
script: string;

/** Enables integration with [Nx](https://nx.dev) */
useNx?: boolean;
}
23 changes: 23 additions & 0 deletions packages/run/README.md
Expand Up @@ -151,3 +151,26 @@ You can provide a custom location for the performance profile output. The path p
```sh
$ lerna run build --profile --profile-location=logs/profile/
```

### `useNx` (experimental)

Enables integration with [Nx](https://nx.dev). Setting `"useNx": true` in `lerna.json` will tell Lerna to delegate
running tasks to Nx instead of using `p-map` and `p-queue`. This only works if Nx is installed and `nx.json` is present.
vsavkin marked this conversation as resolved.
Show resolved

Example of `nx.json`:

```json
{
"extends": "nx/presets/npm.json",
"tasksRunnerOptions": {
"default": {
"runner": "nx/tasks-runners/default",
"options": {
"cacheableOperations": ["build"]
}
}
}
}
```
3 changes: 2 additions & 1 deletion packages/run/package.json
Expand Up @@ -42,6 +42,7 @@
},
"devDependencies": {
"@types/execa": "^2.0.0",
"@types/p-map": "^2.0.0"
"@types/p-map": "^2.0.0",
"perf_hooks": "^0.0.1"
}
}
4 changes: 4 additions & 0 deletions packages/run/src/__fixtures__/powered-by-nx/lerna.json
@@ -0,0 +1,4 @@
{
"version": "1.0.0",
"useNx": true
}
10 changes: 10 additions & 0 deletions packages/run/src/__fixtures__/powered-by-nx/nx.json
@@ -0,0 +1,10 @@
{
"tasksRunnerOptions": {
"default": {
"runner": "nx/tasks-runners/default",
"options": {
"useDaemonProcess": false
}
}
}
}
3 changes: 3 additions & 0 deletions packages/run/src/__fixtures__/powered-by-nx/package.json
@@ -0,0 +1,3 @@
{
"name": "powered-by-nx"
}
@@ -0,0 +1,8 @@
{
"name": "package-1",
"version": "1.0.0",
"scripts": {
"fail": "exit 1",
"my-script": "echo package-1"
}
}
@@ -0,0 +1,10 @@
{
"name": "package-2",
"version": "1.0.0",
"scripts": {
"fail": "exit 1"
},
"dependencies": {
"package-1": "^1.0.0"
}
}
@@ -0,0 +1,10 @@
{
"name": "package-3",
"version": "1.0.0",
"scripts": {
"my-script": "echo package-3"
},
"devDependencies": {
"package-2": "^1.0.0"
}
}
@@ -0,0 +1,7 @@
{
"name": "package-4",
"version": "1.0.0",
"dependencies": {
"package-1": "^0.0.0"
}
}
47 changes: 46 additions & 1 deletion packages/run/src/__tests__/run-command.spec.ts
Expand Up @@ -228,7 +228,7 @@ describe('RunCommand', () => {
await new RunCommand(createArgv(cwd, 'my-script', '--profile', '--profile-location', 'foo/bar'));

const [profileLocation] = await globby('foo/bar/Lerna-Profile-*.json', { cwd, absolute: true });
const exists = await fs.exists(profileLocation, null);
const exists = await fs.exists(profileLocation, null as any);

expect(exists).toBe(true);
});
Expand Down Expand Up @@ -349,4 +349,49 @@ describe('RunCommand', () => {
await expect(command).rejects.toThrow('Dependency cycles detected, you should fix these!');
});
});

// this is a temporary set of tests, which will be replaced by verdacio-driven tests
// once the required setup is fully set up
describe("in a repo powered by Nx", () => {
let testDir;
let collectedOutput = "";

beforeAll(async () => {
testDir = await initFixture("powered-by-nx");
process.env.NX_WORKSPACE_ROOT_PATH = testDir;
// eslint-disable-next-line global-require
const nxOutput = require("nx/src/utils/output");
nxOutput.output.writeToStdOut = (v) => {
collectedOutput = `${collectedOutput}\n${v}`;
};
// @ts-ignore
jest.spyOn(process, "exit").mockImplementation((code: any) => {
if (code !== 0) {
throw new Error();
}
});
});

it("runs a script in packages", async () => {
collectedOutput = "";
await lernaRun(testDir)("my-script");
expect(collectedOutput).toContain("package-1");
expect(collectedOutput).toContain("package-3");
expect(collectedOutput).toContain("Successfully ran target");
});

it("runs a script only in scoped packages", async () => {
collectedOutput = "";
await lernaRun(testDir)("my-script", "--scope", "package-1");
expect(collectedOutput).toContain("package-1");
expect(collectedOutput).not.toContain("package-3");
});

it("does not run a script in ignored packages", async () => {
collectedOutput = "";
await lernaRun(testDir)("my-script", "--ignore", "package-@(2|3|4)");
expect(collectedOutput).toContain("package-1");
expect(collectedOutput).not.toContain("package-3");
});
});
});
43 changes: 33 additions & 10 deletions packages/run/src/run-command.ts
Expand Up @@ -9,6 +9,7 @@ import {
} from '@lerna-lite/core';
import { FilterOptions, getFilteredPackages, Profiler } from '@lerna-lite/exec-run-common';
import pMap from 'p-map';
import { performance } from 'perf_hooks';

import { npmRunScript, npmRunScriptStreaming, timer } from './lib';
import { ScriptStreamingOption } from './models';
Expand Down Expand Up @@ -82,18 +83,22 @@ export class RunCommand extends Command<RunCommandOption & FilterOptions> {
}

execute() {
this.logger.info(
'',
'Executing command in %d %s: %j',
this.count,
this.packagePlural,
this.joinedCommand
);
if (!this.options.useNx) {
this.logger.info(
'',
'Executing command in %d %s: %j',
this.count,
this.packagePlural,
this.joinedCommand
);
}

let chain: Promise<any> = Promise.resolve();
const getElapsed = timer();

if (this.options.parallel) {
if (this.options.useNx) {
chain = chain.then(() => this.runScriptsUsingNx());
} else if (this.options.parallel) {
this.logger.verbose('Parallel', this.joinedCommand!);
chain = chain.then(() => this.runScriptInPackagesParallel());
} else if (this.toposort) {
Expand All @@ -116,7 +121,7 @@ export class RunCommand extends Command<RunCommandOption & FilterOptions> {
// detect error (if any) from collected results
chain = chain.then((results: Array<{ exitCode: number; failed?: boolean; pkg?: Package; stderr: any; }>) => {
/* istanbul ignore else */
if (results.some((result?: { failed?: boolean; }) => result?.failed)) {
if (results?.some((result?: { failed?: boolean; }) => result?.failed)) {
// propagate 'highest' error code, it's probably the most useful
const codes = results.filter((result) => result?.failed).map((result) => result.exitCode);
const exitCode = Math.max(...codes, 1);
Expand All @@ -136,7 +141,7 @@ export class RunCommand extends Command<RunCommandOption & FilterOptions> {
}

return chain.then((results: Array<{ exitCode: number; failed?: boolean; pkg?: Package; stderr: any; }>) => {
const someFailed = results.some((result) => result?.failed);
const someFailed = results?.some((result) => result?.failed);
const logType = someFailed ? 'error' : 'success';

this.logger[logType](
Expand Down Expand Up @@ -208,6 +213,24 @@ export class RunCommand extends Command<RunCommandOption & FilterOptions> {
return chain;
}

async runScriptsUsingNx() {
performance.mark('init-local');
const nxOutput = await import('nx/src/utils/output');
nxOutput.output.cliName = 'Lerna (powered by Nx)';
nxOutput.output.formatCommand = (message) => message.replace(':', ' ');
if (this.options.ci) {
process.env.CI = 'true';
}
const { runMany } = await import('nx/src/command-line/run-many');
return runMany({
projects: this.packagesWithScript.map((p) => p.name).join(','),
target: this.script,
outputStyle: this.options.stream ? 'stream' : 'static',
parallel: this.options.concurrency as number,
_: this.args,
} as any);
}

runScriptInPackagesParallel() {
return pMap(this.packagesWithScript, (pkg: Package) => this.runScriptInPackageStreaming(pkg));
}
Expand Down

0 comments on commit 743d434

Please sign in to comment.