chore(core): task runner dynamic output life cycle (#8590)
This commit is contained in:
parent
e69b893829
commit
faef0d8c85
@ -60,7 +60,7 @@ npx nx run-many --target=build --projects=todos,api
|
|||||||
And notice the output:
|
And notice the output:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
Nx read the output from cache instead of running the command for 1 out of 2 projects.
|
Nx read the output from the cache instead of running the command for 1 out of 2 projects.
|
||||||
```
|
```
|
||||||
|
|
||||||
Nx built `api` and retrieved `todos` from its computation cache. Read more about the cache here [here](/using-nx/caching).
|
Nx built `api` and retrieved `todos` from its computation cache. Read more about the cache here [here](/using-nx/caching).
|
||||||
|
|||||||
@ -49,7 +49,7 @@ Based on the state of the source code and the environment, Nx figured out that i
|
|||||||
**Now, run `npx nx run-many --target=build --projects=todos,api` to rebuild the two applications:**
|
**Now, run `npx nx run-many --target=build --projects=todos,api` to rebuild the two applications:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
Nx read the output from cache instead of running the command for 1 out of 2 projects.
|
Nx read the output from the cache instead of running the command for 1 out of 2 projects.
|
||||||
```
|
```
|
||||||
|
|
||||||
Nx built `api` and retrieved `todos` from its computation cache. Read more about the cache [here](/using-nx/caching).
|
Nx built `api` and retrieved `todos` from its computation cache. Read more about the cache [here](/using-nx/caching).
|
||||||
|
|||||||
@ -250,7 +250,7 @@ describe('Angular Projects', () => {
|
|||||||
expect(buildOutput).toContain(
|
expect(buildOutput).toContain(
|
||||||
`Building entry point '@${proj}/${lib}/${entryPoint}'`
|
`Building entry point '@${proj}/${lib}/${entryPoint}'`
|
||||||
);
|
);
|
||||||
expect(buildOutput).toContain('Running target "build" succeeded');
|
expect(buildOutput).toContain('Successfully ran target build');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('MFE - should serve the host and remote apps successfully', async () => {
|
it('MFE - should serve the host and remote apps successfully', async () => {
|
||||||
|
|||||||
@ -104,7 +104,7 @@ describe('list', () => {
|
|||||||
it(`should work`, async () => {
|
it(`should work`, async () => {
|
||||||
let listOutput = runCLI('list');
|
let listOutput = runCLI('list');
|
||||||
|
|
||||||
expect(listOutput).toContain('NX Installed plugins');
|
expect(listOutput).toContain('NX Installed plugins');
|
||||||
|
|
||||||
// just check for some, not all
|
// just check for some, not all
|
||||||
expect(listOutput).toContain('@nrwl/angular');
|
expect(listOutput).toContain('@nrwl/angular');
|
||||||
@ -116,7 +116,7 @@ describe('list', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
listOutput = runCLI('list');
|
listOutput = runCLI('list');
|
||||||
expect(listOutput).toContain('NX Also available');
|
expect(listOutput).toContain('NX Also available');
|
||||||
|
|
||||||
// look for specific plugin
|
// look for specific plugin
|
||||||
listOutput = runCLI('list @nrwl/workspace');
|
listOutput = runCLI('list @nrwl/workspace');
|
||||||
@ -135,14 +135,14 @@ describe('list', () => {
|
|||||||
listOutput = runCLI('list @nrwl/angular');
|
listOutput = runCLI('list @nrwl/angular');
|
||||||
|
|
||||||
expect(listOutput).toContain(
|
expect(listOutput).toContain(
|
||||||
'NX NOTE @nrwl/angular is not currently installed'
|
'NX @nrwl/angular is not currently installed'
|
||||||
);
|
);
|
||||||
|
|
||||||
// look for an unknown plugin
|
// look for an unknown plugin
|
||||||
listOutput = runCLI('list @wibble/fish');
|
listOutput = runCLI('list @wibble/fish');
|
||||||
|
|
||||||
expect(listOutput).toContain(
|
expect(listOutput).toContain(
|
||||||
'NX NOTE @wibble/fish is not currently installed'
|
'NX @wibble/fish is not currently installed'
|
||||||
);
|
);
|
||||||
|
|
||||||
// put back the @nrwl/angular module (or all the other e2e tests after this will fail)
|
// put back the @nrwl/angular module (or all the other e2e tests after this will fail)
|
||||||
|
|||||||
@ -36,14 +36,14 @@ describe('Detox', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(runCLI(`build-ios ${appName}-e2e --prod`)).toContain(
|
expect(runCLI(`build-ios ${appName}-e2e --prod`)).toContain(
|
||||||
'Running target "build-ios" succeeded'
|
'Successfully ran target build-ios'
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
runCLI(
|
runCLI(
|
||||||
`test-ios ${appName}-e2e --prod --debugSynchronization=true --loglevel=trace`
|
`test-ios ${appName}-e2e --prod --debugSynchronization=true --loglevel=trace`
|
||||||
)
|
)
|
||||||
).toContain('Running target "test-ios" succeeded');
|
).toContain('Successfully ran target test-ios');
|
||||||
|
|
||||||
await killPorts(8081); // kill the port for the serve command
|
await killPorts(8081); // kill the port for the serve command
|
||||||
}, 3000000);
|
}, 3000000);
|
||||||
|
|||||||
@ -131,7 +131,7 @@ describe('js e2e', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const output = runCLI(`build ${app}`);
|
const output = runCLI(`build ${app}`);
|
||||||
expect(output).toContain('1 task(s) that it depends on');
|
expect(output).toContain('1 task(s) it depends on');
|
||||||
expect(output).toContain('Done compiling TypeScript files');
|
expect(output).toContain('Done compiling TypeScript files');
|
||||||
|
|
||||||
expect(runCLI(`serve ${app} --no-watch`)).toContain(`Running ${lib}`);
|
expect(runCLI(`serve ${app} --no-watch`)).toContain(`Running ${lib}`);
|
||||||
@ -193,7 +193,7 @@ describe('js e2e', () => {
|
|||||||
// });
|
// });
|
||||||
//
|
//
|
||||||
// const output = runCLI(`build ${app}`);
|
// const output = runCLI(`build ${app}`);
|
||||||
// expect(output).toContain('1 task(s) that it depends on');
|
// expect(output).toContain('1 task(s) it depends on');
|
||||||
// expect(output).toContain('Successfully compiled: 2 files with swc');
|
// expect(output).toContain('Successfully compiled: 2 files with swc');
|
||||||
//
|
//
|
||||||
// expect(runCommand(`serve ${app} --watch=false`)).toContain(`Running ${lib}`)
|
// expect(runCommand(`serve ${app} --watch=false`)).toContain(`Running ${lib}`)
|
||||||
|
|||||||
@ -148,7 +148,7 @@ describe('Linter', () => {
|
|||||||
|
|
||||||
// Ensure that the unit tests for the new rule are runnable
|
// Ensure that the unit tests for the new rule are runnable
|
||||||
const unitTestsOutput = runCLI(`test eslint-rules`);
|
const unitTestsOutput = runCLI(`test eslint-rules`);
|
||||||
expect(unitTestsOutput).toContain('Running target "test" succeeded');
|
expect(unitTestsOutput).toContain('Successfully ran target test');
|
||||||
|
|
||||||
// Update the rule for the e2e test so that we can assert that it produces the expected lint failure when used
|
// Update the rule for the e2e test so that we can assert that it produces the expected lint failure when used
|
||||||
const knownLintErrorMessage = 'e2e test known error message';
|
const knownLintErrorMessage = 'e2e test known error message';
|
||||||
|
|||||||
@ -770,7 +770,7 @@ describe('with dependencies', () => {
|
|||||||
const buildWithDeps = runCLI(
|
const buildWithDeps = runCLI(
|
||||||
`build ${app} --with-deps --buildLibsFromSource=false`
|
`build ${app} --with-deps --buildLibsFromSource=false`
|
||||||
);
|
);
|
||||||
expect(buildWithDeps).toContain(`Running target "build" succeeded`);
|
expect(buildWithDeps).toContain('Successfully ran target build');
|
||||||
checkFilesDoNotExist(`apps/${app}/tsconfig/tsconfig.nx-tmp`);
|
checkFilesDoNotExist(`apps/${app}/tsconfig/tsconfig.nx-tmp`);
|
||||||
|
|
||||||
// we remove all path mappings from the root tsconfig, so when trying to build
|
// we remove all path mappings from the root tsconfig, so when trying to build
|
||||||
|
|||||||
@ -62,7 +62,7 @@ describe('Nx Plugin', () => {
|
|||||||
|
|
||||||
if (isNotWindows()) {
|
if (isNotWindows()) {
|
||||||
const e2eResults = runCLI(`e2e ${plugin}-e2e`);
|
const e2eResults = runCLI(`e2e ${plugin}-e2e`);
|
||||||
expect(e2eResults).toContain('Running target "e2e" succeeded');
|
expect(e2eResults).toContain('Successfully ran target e2e');
|
||||||
expect(await killPorts()).toBeTruthy();
|
expect(await killPorts()).toBeTruthy();
|
||||||
}
|
}
|
||||||
}, 250000);
|
}, 250000);
|
||||||
|
|||||||
@ -234,7 +234,7 @@ export async function h() { return 'c'; }
|
|||||||
const buildFromSource = runCLI(
|
const buildFromSource = runCLI(
|
||||||
`build ${app} --buildLibsFromSource=false`
|
`build ${app} --buildLibsFromSource=false`
|
||||||
);
|
);
|
||||||
expect(buildFromSource).toContain(`Running target "build" succeeded`);
|
expect(buildFromSource).toContain('Successfully ran target build');
|
||||||
checkFilesDoNotExist(`apps/${app}/tsconfig/tsconfig.nx-tmp`);
|
checkFilesDoNotExist(`apps/${app}/tsconfig/tsconfig.nx-tmp`);
|
||||||
|
|
||||||
// we remove all path mappings from the root tsconfig, so when trying to build
|
// we remove all path mappings from the root tsconfig, so when trying to build
|
||||||
|
|||||||
@ -91,7 +91,7 @@ describe('run-one', () => {
|
|||||||
env: { ...process.env, NX_DAEMON: 'true' },
|
env: { ...process.env, NX_DAEMON: 'true' },
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(buildWithDaemon).toContain(`Running target "build" succeeded`);
|
expect(buildWithDaemon).toContain('Successfully ran target build');
|
||||||
}, 10000);
|
}, 10000);
|
||||||
|
|
||||||
it('should build the project when within the project root', () => {
|
it('should build the project when within the project root', () => {
|
||||||
@ -135,7 +135,7 @@ describe('run-one', () => {
|
|||||||
it('should include deps', () => {
|
it('should include deps', () => {
|
||||||
const output = runCLI(`test ${myapp} --with-deps`);
|
const output = runCLI(`test ${myapp} --with-deps`);
|
||||||
expect(output).toContain(
|
expect(output).toContain(
|
||||||
`NX Running target test for project ${myapp} and 2 task(s) that it depends on.`
|
`NX Running target test for project ${myapp} and 2 task(s) it depends on`
|
||||||
);
|
);
|
||||||
expect(output).toContain(myapp);
|
expect(output).toContain(myapp);
|
||||||
expect(output).toContain(mylib1);
|
expect(output).toContain(mylib1);
|
||||||
@ -144,7 +144,7 @@ describe('run-one', () => {
|
|||||||
|
|
||||||
it('should include deps without the configuration if it does not exist', () => {
|
it('should include deps without the configuration if it does not exist', () => {
|
||||||
const buildWithDeps = runCLI(`build ${myapp} --with-deps --prod`);
|
const buildWithDeps = runCLI(`build ${myapp} --with-deps --prod`);
|
||||||
expect(buildWithDeps).toContain(`Running target "build" succeeded`);
|
expect(buildWithDeps).toContain('Successfully ran target build');
|
||||||
expect(buildWithDeps).toContain(`nx run ${myapp}:build:production`);
|
expect(buildWithDeps).toContain(`nx run ${myapp}:build:production`);
|
||||||
expect(buildWithDeps).toContain(`nx run ${mylib1}:build`);
|
expect(buildWithDeps).toContain(`nx run ${mylib1}:build`);
|
||||||
expect(buildWithDeps).toContain(`nx run ${mylib2}:build`);
|
expect(buildWithDeps).toContain(`nx run ${mylib2}:build`);
|
||||||
@ -188,7 +188,7 @@ describe('run-one', () => {
|
|||||||
|
|
||||||
const output = runCLI(`build ${myapp}`);
|
const output = runCLI(`build ${myapp}`);
|
||||||
expect(output).toContain(
|
expect(output).toContain(
|
||||||
`NX Running target build for project ${myapp} and 2 task(s) that it depends on.`
|
`NX Running target build for project ${myapp} and 2 task(s) it depends on`
|
||||||
);
|
);
|
||||||
expect(output).toContain(myapp);
|
expect(output).toContain(myapp);
|
||||||
expect(output).toContain(mylib1);
|
expect(output).toContain(mylib1);
|
||||||
@ -217,7 +217,7 @@ describe('run-one', () => {
|
|||||||
|
|
||||||
const output = runCLI(`build ${myapp}`);
|
const output = runCLI(`build ${myapp}`);
|
||||||
expect(output).toContain(
|
expect(output).toContain(
|
||||||
`NX Running target build for project ${myapp} and 2 task(s) that it depends on.`
|
`NX Running target build for project ${myapp} and 2 task(s) it depends on`
|
||||||
);
|
);
|
||||||
expect(output).toContain(myapp);
|
expect(output).toContain(myapp);
|
||||||
expect(output).toContain(mylib1);
|
expect(output).toContain(mylib1);
|
||||||
@ -271,7 +271,7 @@ describe('run-many', () => {
|
|||||||
expect(buildParallel).toContain(`- ${libB}`);
|
expect(buildParallel).toContain(`- ${libB}`);
|
||||||
expect(buildParallel).toContain(`- ${libC}`);
|
expect(buildParallel).toContain(`- ${libC}`);
|
||||||
expect(buildParallel).not.toContain(`- ${libD}`);
|
expect(buildParallel).not.toContain(`- ${libD}`);
|
||||||
expect(buildParallel).toContain('Running target "build" succeeded');
|
expect(buildParallel).toContain('Successfully ran target build');
|
||||||
|
|
||||||
// testing run many --all starting
|
// testing run many --all starting
|
||||||
const buildAllParallel = runCLI(`run-many --target=build --all`);
|
const buildAllParallel = runCLI(`run-many --target=build --all`);
|
||||||
@ -283,7 +283,7 @@ describe('run-many', () => {
|
|||||||
expect(buildAllParallel).toContain(`- ${libB}`);
|
expect(buildAllParallel).toContain(`- ${libB}`);
|
||||||
expect(buildAllParallel).toContain(`- ${libC}`);
|
expect(buildAllParallel).toContain(`- ${libC}`);
|
||||||
expect(buildAllParallel).not.toContain(`- ${libD}`);
|
expect(buildAllParallel).not.toContain(`- ${libD}`);
|
||||||
expect(buildAllParallel).toContain('Running target "build" succeeded');
|
expect(buildAllParallel).toContain('Successfully ran target build');
|
||||||
|
|
||||||
// testing run many --with-deps
|
// testing run many --with-deps
|
||||||
const buildWithDeps = runCLI(
|
const buildWithDeps = runCLI(
|
||||||
@ -296,7 +296,7 @@ describe('run-many', () => {
|
|||||||
expect(buildWithDeps).toContain(`${libC}`); // build should include libC as dependency
|
expect(buildWithDeps).toContain(`${libC}`); // build should include libC as dependency
|
||||||
expect(buildWithDeps).not.toContain(`- ${libB}`);
|
expect(buildWithDeps).not.toContain(`- ${libB}`);
|
||||||
expect(buildWithDeps).not.toContain(`- ${libD}`);
|
expect(buildWithDeps).not.toContain(`- ${libD}`);
|
||||||
expect(buildWithDeps).toContain('Running target "build" succeeded');
|
expect(buildWithDeps).toContain('Successfully ran target build');
|
||||||
|
|
||||||
// testing run many --configuration
|
// testing run many --configuration
|
||||||
const buildConfig = runCLI(
|
const buildConfig = runCLI(
|
||||||
@ -308,13 +308,13 @@ describe('run-many', () => {
|
|||||||
expect(buildConfig).toContain(`run ${appA}:build:production`);
|
expect(buildConfig).toContain(`run ${appA}:build:production`);
|
||||||
expect(buildConfig).toContain(`run ${libA}:build`);
|
expect(buildConfig).toContain(`run ${libA}:build`);
|
||||||
expect(buildConfig).toContain(`run ${libC}:build`);
|
expect(buildConfig).toContain(`run ${libC}:build`);
|
||||||
expect(buildConfig).toContain('Running target "build" succeeded');
|
expect(buildConfig).toContain('Successfully ran target build');
|
||||||
|
|
||||||
// testing run many with daemon enabled
|
// testing run many with daemon enabled
|
||||||
const buildWithDaemon = runCLI(`run-many --target=build --all`, {
|
const buildWithDaemon = runCLI(`run-many --target=build --all`, {
|
||||||
env: { ...process.env, NX_DAEMON: 'true' },
|
env: { ...process.env, NX_DAEMON: 'true' },
|
||||||
});
|
});
|
||||||
expect(buildWithDaemon).toContain(`Running target "build" succeeded`);
|
expect(buildWithDaemon).toContain(`Successfully ran target build`);
|
||||||
}, 1000000);
|
}, 1000000);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -422,7 +422,7 @@ describe('affected:*', () => {
|
|||||||
expect(build).toContain(`- ${myapp}`);
|
expect(build).toContain(`- ${myapp}`);
|
||||||
expect(build).toContain(`- ${mypublishablelib}`);
|
expect(build).toContain(`- ${mypublishablelib}`);
|
||||||
expect(build).not.toContain('is not registered with the build command');
|
expect(build).not.toContain('is not registered with the build command');
|
||||||
expect(build).toContain('Running target "build" succeeded');
|
expect(build).toContain('Successfully ran target build');
|
||||||
|
|
||||||
const buildExcluded = runCLI(
|
const buildExcluded = runCLI(
|
||||||
`affected:build --files="libs/${mylib}/src/index.ts" --exclude ${myapp}`
|
`affected:build --files="libs/${mylib}/src/index.ts" --exclude ${myapp}`
|
||||||
@ -722,14 +722,14 @@ describe('cache', () => {
|
|||||||
const filesApp2 = listFiles(`dist/apps/${myapp2}`);
|
const filesApp2 = listFiles(`dist/apps/${myapp2}`);
|
||||||
// now the data is in cache
|
// now the data is in cache
|
||||||
expect(outputThatPutsDataIntoCache).not.toContain(
|
expect(outputThatPutsDataIntoCache).not.toContain(
|
||||||
'read the output from cache'
|
'read the output from the cache'
|
||||||
);
|
);
|
||||||
|
|
||||||
rmDist();
|
rmDist();
|
||||||
|
|
||||||
const outputWithBothBuildTasksCached = runCLI(`affected:build ${files}`);
|
const outputWithBothBuildTasksCached = runCLI(`affected:build ${files}`);
|
||||||
expect(outputWithBothBuildTasksCached).toContain(
|
expect(outputWithBothBuildTasksCached).toContain(
|
||||||
'read the output from cache'
|
'read the output from the cache'
|
||||||
);
|
);
|
||||||
expectCached(outputWithBothBuildTasksCached, [myapp1, myapp2]);
|
expectCached(outputWithBothBuildTasksCached, [myapp1, myapp2]);
|
||||||
expect(listFiles(`dist/apps/${myapp1}`)).toEqual(filesApp1);
|
expect(listFiles(`dist/apps/${myapp1}`)).toEqual(filesApp1);
|
||||||
@ -740,7 +740,7 @@ describe('cache', () => {
|
|||||||
`affected:build ${files} --skip-nx-cache`
|
`affected:build ${files} --skip-nx-cache`
|
||||||
);
|
);
|
||||||
expect(outputWithBothBuildTasksCachedButSkipped).not.toContain(
|
expect(outputWithBothBuildTasksCachedButSkipped).not.toContain(
|
||||||
`read the output from cache`
|
`read the output from the cache`
|
||||||
);
|
);
|
||||||
|
|
||||||
// touch myapp1
|
// touch myapp1
|
||||||
@ -749,7 +749,9 @@ describe('cache', () => {
|
|||||||
return `${c}\n//some comment`;
|
return `${c}\n//some comment`;
|
||||||
});
|
});
|
||||||
const outputWithBuildApp2Cached = runCLI(`affected:build ${files}`);
|
const outputWithBuildApp2Cached = runCLI(`affected:build ${files}`);
|
||||||
expect(outputWithBuildApp2Cached).toContain('read the output from cache');
|
expect(outputWithBuildApp2Cached).toContain(
|
||||||
|
'read the output from the cache'
|
||||||
|
);
|
||||||
expectMatchedOutput(outputWithBuildApp2Cached, [myapp2]);
|
expectMatchedOutput(outputWithBuildApp2Cached, [myapp2]);
|
||||||
|
|
||||||
// touch package.json
|
// touch package.json
|
||||||
@ -760,7 +762,9 @@ describe('cache', () => {
|
|||||||
return JSON.stringify(r);
|
return JSON.stringify(r);
|
||||||
});
|
});
|
||||||
const outputWithNoBuildCached = runCLI(`affected:build ${files}`);
|
const outputWithNoBuildCached = runCLI(`affected:build ${files}`);
|
||||||
expect(outputWithNoBuildCached).not.toContain('read the output from cache');
|
expect(outputWithNoBuildCached).not.toContain(
|
||||||
|
'read the output from the cache'
|
||||||
|
);
|
||||||
|
|
||||||
// build individual project with caching
|
// build individual project with caching
|
||||||
const individualBuildWithCache = runCLI(`build ${myapp1}`);
|
const individualBuildWithCache = runCLI(`build ${myapp1}`);
|
||||||
@ -779,11 +783,13 @@ describe('cache', () => {
|
|||||||
// run lint with caching
|
// run lint with caching
|
||||||
// --------------------------------------------
|
// --------------------------------------------
|
||||||
const outputWithNoLintCached = runCLI(`affected:lint ${files}`);
|
const outputWithNoLintCached = runCLI(`affected:lint ${files}`);
|
||||||
expect(outputWithNoLintCached).not.toContain('read the output from cache');
|
expect(outputWithNoLintCached).not.toContain(
|
||||||
|
'read the output from the cache'
|
||||||
|
);
|
||||||
|
|
||||||
const outputWithBothLintTasksCached = runCLI(`affected:lint ${files}`);
|
const outputWithBothLintTasksCached = runCLI(`affected:lint ${files}`);
|
||||||
expect(outputWithBothLintTasksCached).toContain(
|
expect(outputWithBothLintTasksCached).toContain(
|
||||||
'read the output from cache'
|
'read the output from the cache'
|
||||||
);
|
);
|
||||||
expectCached(outputWithBothLintTasksCached, [
|
expectCached(outputWithBothLintTasksCached, [
|
||||||
myapp1,
|
myapp1,
|
||||||
@ -836,12 +842,12 @@ describe('cache', () => {
|
|||||||
const outputWithoutCachingEnabled1 = runCLI(`affected:build ${files}`);
|
const outputWithoutCachingEnabled1 = runCLI(`affected:build ${files}`);
|
||||||
|
|
||||||
expect(outputWithoutCachingEnabled1).not.toContain(
|
expect(outputWithoutCachingEnabled1).not.toContain(
|
||||||
'read the output from cache'
|
'read the output from the cache'
|
||||||
);
|
);
|
||||||
|
|
||||||
const outputWithoutCachingEnabled2 = runCLI(`affected:build ${files}`);
|
const outputWithoutCachingEnabled2 = runCLI(`affected:build ${files}`);
|
||||||
expect(outputWithoutCachingEnabled2).not.toContain(
|
expect(outputWithoutCachingEnabled2).not.toContain(
|
||||||
'read the output from cache'
|
'read the output from the cache'
|
||||||
);
|
);
|
||||||
}, 120000);
|
}, 120000);
|
||||||
|
|
||||||
@ -910,8 +916,12 @@ describe('cache', () => {
|
|||||||
const matchingProjects = [];
|
const matchingProjects = [];
|
||||||
const lines = actualOutput.split('\n');
|
const lines = actualOutput.split('\n');
|
||||||
lines.forEach((s) => {
|
lines.forEach((s) => {
|
||||||
if (s.startsWith(`> nx run`)) {
|
if (s.trimStart().startsWith(`> nx run`)) {
|
||||||
const projectName = s.split(`> nx run `)[1].split(':')[0].trim();
|
const projectName = s
|
||||||
|
.trimStart()
|
||||||
|
.split(`> nx run `)[1]
|
||||||
|
.split(':')[0]
|
||||||
|
.trim();
|
||||||
if (s.indexOf(cacheStatus) > -1) {
|
if (s.indexOf(cacheStatus) > -1) {
|
||||||
matchingProjects.push(projectName);
|
matchingProjects.push(projectName);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -60,7 +60,7 @@ npx nx run-many --target=build --projects=todos,api
|
|||||||
And notice the output:
|
And notice the output:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
Nx read the output from cache instead of running the command for 1 out of 2 projects.
|
Nx read the output from the cache instead of running the command for 1 out of 2 projects.
|
||||||
```
|
```
|
||||||
|
|
||||||
Nx built `api` and retrieved `todos` from its computation cache. Read more about the cache here [here](/using-nx/caching).
|
Nx built `api` and retrieved `todos` from its computation cache. Read more about the cache here [here](/using-nx/caching).
|
||||||
|
|||||||
@ -49,7 +49,7 @@ Based on the state of the source code and the environment, Nx figured out that i
|
|||||||
**Now, run `npx nx run-many --target=build --projects=todos,api` to rebuild the two applications:**
|
**Now, run `npx nx run-many --target=build --projects=todos,api` to rebuild the two applications:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
Nx read the output from cache instead of running the command for 1 out of 2 projects.
|
Nx read the output from the cache instead of running the command for 1 out of 2 projects.
|
||||||
```
|
```
|
||||||
|
|
||||||
Nx built `api` and retrieved `todos` from its computation cache. Read more about the cache [here](/using-nx/caching).
|
Nx built `api` and retrieved `todos` from its computation cache. Read more about the cache [here](/using-nx/caching).
|
||||||
|
|||||||
@ -1,8 +1,30 @@
|
|||||||
import * as chalk from 'chalk';
|
|
||||||
/*
|
/*
|
||||||
* Because we don't want to depend on @nrwl/workspace (to speed up the workspace creation)
|
* Because we don't want to depend on @nrwl/workspace (to speed up the workspace creation)
|
||||||
* we duplicate the helper functions from @nrwl/workspace in this file.
|
* we duplicate the helper functions from @nrwl/workspace in this file.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import * as chalk from 'chalk';
|
||||||
|
import { EOL } from 'os';
|
||||||
|
|
||||||
|
export function isCI() {
|
||||||
|
return (
|
||||||
|
process.env.CI === 'true' ||
|
||||||
|
process.env.TF_BUILD === 'true' ||
|
||||||
|
process.env['bamboo.buildKey'] ||
|
||||||
|
process.env.BUILDKITE === 'true' ||
|
||||||
|
process.env.CIRCLECI === 'true' ||
|
||||||
|
process.env.CIRRUS_CI === 'true' ||
|
||||||
|
process.env.CODEBUILD_BUILD_ID ||
|
||||||
|
process.env.GITHUB_ACTIONS === 'true' ||
|
||||||
|
process.env.GITLAB_CI ||
|
||||||
|
process.env.HEROKU_TEST_RUN_ID ||
|
||||||
|
process.env.BUILD_ID ||
|
||||||
|
process.env.BUILD_BUILDID ||
|
||||||
|
process.env.TEAMCITY_VERSION ||
|
||||||
|
process.env.TRAVIS === 'true'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export interface CLIErrorMessageConfig {
|
export interface CLIErrorMessageConfig {
|
||||||
title: string;
|
title: string;
|
||||||
bodyLines?: string[];
|
bodyLines?: string[];
|
||||||
@ -25,23 +47,37 @@ export interface CLISuccessMessageConfig {
|
|||||||
bodyLines?: string[];
|
bodyLines?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum TaskCacheStatus {
|
||||||
|
NoCache = '[no cache]',
|
||||||
|
MatchedExistingOutput = '[existing outputs match the cache, left as is]',
|
||||||
|
RetrievedFromCache = '[retrieved from cache]',
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Automatically disable styling applied by chalk if CI=true
|
* Automatically disable styling applied by chalk if CI=true
|
||||||
*/
|
*/
|
||||||
if (process.env.CI === 'true') {
|
if (isCI()) {
|
||||||
(chalk as any).level = 0;
|
(chalk as any).level = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
class CLIOutput {
|
class CLIOutput {
|
||||||
private readonly NX_PREFIX = `${chalk.cyan(
|
readonly X_PADDING = ' ';
|
||||||
'>'
|
|
||||||
)} ${chalk.reset.inverse.bold.cyan(' NX ')}`;
|
|
||||||
/**
|
/**
|
||||||
* Longer dash character which forms more of a continuous line when place side to side
|
* Longer dash character which forms more of a continuous line when place side to side
|
||||||
* with itself, unlike the standard dash character
|
* with itself, unlike the standard dash character
|
||||||
*/
|
*/
|
||||||
private readonly VERTICAL_SEPARATOR =
|
private get VERTICAL_SEPARATOR() {
|
||||||
'———————————————————————————————————————————————';
|
let divider = '';
|
||||||
|
for (
|
||||||
|
let i = 0;
|
||||||
|
i < process.stdout.columns - this.X_PADDING.length * 2;
|
||||||
|
i++
|
||||||
|
) {
|
||||||
|
divider += '\u2014';
|
||||||
|
}
|
||||||
|
return divider;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expose some color and other utility functions so that other parts of the codebase that need
|
* Expose some color and other utility functions so that other parts of the codebase that need
|
||||||
@ -50,28 +86,27 @@ class CLIOutput {
|
|||||||
*/
|
*/
|
||||||
colors = {
|
colors = {
|
||||||
gray: chalk.gray,
|
gray: chalk.gray,
|
||||||
|
green: chalk.green,
|
||||||
|
red: chalk.red,
|
||||||
|
cyan: chalk.cyan,
|
||||||
|
white: chalk.white,
|
||||||
};
|
};
|
||||||
bold = chalk.bold;
|
bold = chalk.bold;
|
||||||
underline = chalk.underline;
|
underline = chalk.underline;
|
||||||
|
dim = chalk.dim;
|
||||||
|
|
||||||
private writeToStdOut(str: string) {
|
private writeToStdOut(str: string) {
|
||||||
process.stdout.write(str);
|
process.stdout.write(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
private writeOutputTitle({
|
private writeOutputTitle({
|
||||||
label,
|
color,
|
||||||
title,
|
title,
|
||||||
}: {
|
}: {
|
||||||
label?: string;
|
color: string;
|
||||||
title: string;
|
title: string;
|
||||||
}): void {
|
}): void {
|
||||||
let outputTitle: string;
|
this.writeToStdOut(` ${this.applyNxPrefix(color, title)}${EOL}`);
|
||||||
if (label) {
|
|
||||||
outputTitle = `${this.NX_PREFIX} ${label} ${title}\n`;
|
|
||||||
} else {
|
|
||||||
outputTitle = `${this.NX_PREFIX} ${title}\n`;
|
|
||||||
}
|
|
||||||
this.writeToStdOut(outputTitle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private writeOptionalOutputBody(bodyLines?: string[]): void {
|
private writeOptionalOutputBody(bodyLines?: string[]): void {
|
||||||
@ -79,27 +114,45 @@ class CLIOutput {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.addNewline();
|
this.addNewline();
|
||||||
bodyLines.forEach((bodyLine) => this.writeToStdOut(' ' + bodyLine + '\n'));
|
bodyLines.forEach((bodyLine) => this.writeToStdOut(` ${bodyLine}${EOL}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
applyNxPrefix(color = 'cyan', text: string): string {
|
||||||
|
let nxPrefix = '';
|
||||||
|
if (chalk[color]) {
|
||||||
|
nxPrefix = `${chalk[color]('>')} ${chalk.reset.inverse.bold[color](
|
||||||
|
' NX '
|
||||||
|
)}`;
|
||||||
|
} else {
|
||||||
|
nxPrefix = `${chalk.keyword(color)(
|
||||||
|
'>'
|
||||||
|
)} ${chalk.reset.inverse.bold.keyword(color)(' NX ')}`;
|
||||||
|
}
|
||||||
|
return `${nxPrefix} ${text}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
addNewline() {
|
addNewline() {
|
||||||
this.writeToStdOut('\n');
|
this.writeToStdOut(EOL);
|
||||||
}
|
}
|
||||||
|
|
||||||
addVerticalSeparator() {
|
addVerticalSeparator(color = 'gray') {
|
||||||
this.writeToStdOut(`\n${chalk.gray(this.VERTICAL_SEPARATOR)}\n\n`);
|
this.addNewline();
|
||||||
|
this.addVerticalSeparatorWithoutNewLines(color);
|
||||||
|
this.addNewline();
|
||||||
}
|
}
|
||||||
|
|
||||||
addVerticalSeparatorWithoutNewLines() {
|
addVerticalSeparatorWithoutNewLines(color = 'gray') {
|
||||||
this.writeToStdOut(`${chalk.gray(this.VERTICAL_SEPARATOR)}\n`);
|
this.writeToStdOut(
|
||||||
|
`${this.X_PADDING}${chalk.dim[color](this.VERTICAL_SEPARATOR)}${EOL}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
error({ title, slug, bodyLines }: CLIErrorMessageConfig) {
|
error({ title, slug, bodyLines }: CLIErrorMessageConfig) {
|
||||||
this.addNewline();
|
this.addNewline();
|
||||||
|
|
||||||
this.writeOutputTitle({
|
this.writeOutputTitle({
|
||||||
label: chalk.reset.inverse.bold.red(' ERROR '),
|
color: 'red',
|
||||||
title: chalk.bold.red(title),
|
title: chalk.red(title),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.writeOptionalOutputBody(bodyLines);
|
this.writeOptionalOutputBody(bodyLines);
|
||||||
@ -112,7 +165,7 @@ class CLIOutput {
|
|||||||
this.writeToStdOut(
|
this.writeToStdOut(
|
||||||
`${chalk.grey(
|
`${chalk.grey(
|
||||||
' Learn more about this error: '
|
' Learn more about this error: '
|
||||||
)}https://errors.nx.dev/${slug}\n`
|
)}https://errors.nx.dev/${slug}${EOL}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,8 +176,8 @@ class CLIOutput {
|
|||||||
this.addNewline();
|
this.addNewline();
|
||||||
|
|
||||||
this.writeOutputTitle({
|
this.writeOutputTitle({
|
||||||
label: chalk.reset.inverse.bold.yellow(' WARNING '),
|
color: 'yellow',
|
||||||
title: chalk.bold.yellow(title),
|
title: chalk.yellow(title),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.writeOptionalOutputBody(bodyLines);
|
this.writeOptionalOutputBody(bodyLines);
|
||||||
@ -148,8 +201,8 @@ class CLIOutput {
|
|||||||
this.addNewline();
|
this.addNewline();
|
||||||
|
|
||||||
this.writeOutputTitle({
|
this.writeOutputTitle({
|
||||||
label: chalk.reset.inverse.bold.keyword('orange')(' NOTE '),
|
color: 'orange',
|
||||||
title: chalk.bold.keyword('orange')(title),
|
title: chalk.keyword('orange')(title),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.writeOptionalOutputBody(bodyLines);
|
this.writeOptionalOutputBody(bodyLines);
|
||||||
@ -161,8 +214,8 @@ class CLIOutput {
|
|||||||
this.addNewline();
|
this.addNewline();
|
||||||
|
|
||||||
this.writeOutputTitle({
|
this.writeOutputTitle({
|
||||||
label: chalk.reset.inverse.bold.green(' SUCCESS '),
|
color: 'green',
|
||||||
title: chalk.bold.green(title),
|
title: chalk.green(title),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.writeOptionalOutputBody(bodyLines);
|
this.writeOptionalOutputBody(bodyLines);
|
||||||
@ -174,29 +227,36 @@ class CLIOutput {
|
|||||||
this.addNewline();
|
this.addNewline();
|
||||||
|
|
||||||
this.writeOutputTitle({
|
this.writeOutputTitle({
|
||||||
|
color: 'gray',
|
||||||
title: message,
|
title: message,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addNewline();
|
this.addNewline();
|
||||||
}
|
}
|
||||||
|
|
||||||
logCommand(message: string, isCached: boolean = false) {
|
logCommand(
|
||||||
|
message: string,
|
||||||
|
cacheStatus: TaskCacheStatus = TaskCacheStatus.NoCache
|
||||||
|
) {
|
||||||
this.addNewline();
|
this.addNewline();
|
||||||
|
|
||||||
this.writeToStdOut(chalk.bold(`> ${message} `));
|
let commandOutput = ` ${chalk.dim('> nx run')} ${message}`;
|
||||||
|
if (cacheStatus !== TaskCacheStatus.NoCache) {
|
||||||
if (isCached) {
|
commandOutput += ` ${chalk.grey(cacheStatus)}`;
|
||||||
this.writeToStdOut(chalk.bold.grey(`[retrieved from cache]`));
|
|
||||||
}
|
}
|
||||||
|
this.writeToStdOut(commandOutput);
|
||||||
|
|
||||||
this.addNewline();
|
this.addNewline();
|
||||||
}
|
}
|
||||||
|
|
||||||
log({ title, bodyLines }: CLIWarnMessageConfig) {
|
log({ title, bodyLines, color }: CLIWarnMessageConfig & { color?: string }) {
|
||||||
this.addNewline();
|
this.addNewline();
|
||||||
|
|
||||||
|
color = color || 'white';
|
||||||
|
|
||||||
this.writeOutputTitle({
|
this.writeOutputTitle({
|
||||||
title: chalk.white(title),
|
color: 'cyan',
|
||||||
|
title: chalk[color](title),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.writeOptionalOutputBody(bodyLines);
|
this.writeOptionalOutputBody(bodyLines);
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { logger } from '@nrwl/devkit';
|
|
||||||
import type { Arguments } from 'yargs';
|
import type { Arguments } from 'yargs';
|
||||||
import { DAEMON_OUTPUT_LOG_FILE } from '../core/project-graph/daemon/tmp-dir';
|
import { DAEMON_OUTPUT_LOG_FILE } from '../core/project-graph/daemon/tmp-dir';
|
||||||
|
import { output } from '../utilities/output';
|
||||||
|
|
||||||
export async function daemonHandler(args: Arguments) {
|
export async function daemonHandler(args: Arguments) {
|
||||||
const { startInBackground, startInCurrentProcess } = await import(
|
const { startInBackground, startInCurrentProcess } = await import(
|
||||||
@ -9,9 +9,13 @@ export async function daemonHandler(args: Arguments) {
|
|||||||
if (!args.background) {
|
if (!args.background) {
|
||||||
return startInCurrentProcess();
|
return startInCurrentProcess();
|
||||||
}
|
}
|
||||||
logger.info(`NX Daemon Server - Starting in a background process...`);
|
|
||||||
const pid = await startInBackground();
|
const pid = await startInBackground();
|
||||||
logger.log(
|
output.log({
|
||||||
` Logs from the Daemon process (ID: ${pid}) can be found here: ${DAEMON_OUTPUT_LOG_FILE}\n`
|
title: `Daemon Server - Started in a background process...`,
|
||||||
);
|
bodyLines: [
|
||||||
|
`${output.dim('Logs from the Daemon process (')}ID: ${pid}${output.dim(
|
||||||
|
') can be found here:'
|
||||||
|
)} ${DAEMON_OUTPUT_LOG_FILE}\n`,
|
||||||
|
],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import { logger, ProjectGraph } from '@nrwl/devkit';
|
import { ProjectGraph } from '@nrwl/devkit';
|
||||||
import { ChildProcess, spawn, spawnSync } from 'child_process';
|
import { ChildProcess, spawn, spawnSync } from 'child_process';
|
||||||
import { openSync, readFileSync } from 'fs';
|
import { openSync, readFileSync } from 'fs';
|
||||||
import { ensureDirSync, ensureFileSync } from 'fs-extra';
|
import { ensureDirSync, ensureFileSync } from 'fs-extra';
|
||||||
import { connect } from 'net';
|
import { connect } from 'net';
|
||||||
import { performance } from 'perf_hooks';
|
import { performance } from 'perf_hooks';
|
||||||
|
import { output } from '../../../../utilities/output';
|
||||||
import {
|
import {
|
||||||
safelyCleanUpExistingProcess,
|
safelyCleanUpExistingProcess,
|
||||||
writeDaemonJsonProcessCache,
|
writeDaemonJsonProcessCache,
|
||||||
@ -82,7 +83,9 @@ function daemonProcessException(message: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function startInCurrentProcess(): void {
|
export function startInCurrentProcess(): void {
|
||||||
logger.info(`NX Daemon Server - Starting in the current process...`);
|
output.log({
|
||||||
|
title: `Daemon Server - Starting in the current process...`,
|
||||||
|
});
|
||||||
|
|
||||||
spawnSync(process.execPath, ['../server/start.js'], {
|
spawnSync(process.execPath, ['../server/start.js'], {
|
||||||
cwd: __dirname,
|
cwd: __dirname,
|
||||||
@ -96,7 +99,7 @@ export function stop(): void {
|
|||||||
stdio: 'inherit',
|
stdio: 'inherit',
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.info('NX Daemon Server - Stopped');
|
output.log({ title: 'Daemon Server - Stopped' });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { logger, normalizePath, stripIndents } from '@nrwl/devkit';
|
|
||||||
import { appRootPath } from '@nrwl/tao/src/utils/app-root';
|
import { appRootPath } from '@nrwl/tao/src/utils/app-root';
|
||||||
import { createServer, Server, Socket } from 'net';
|
import { createServer, Server, Socket } from 'net';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { logger } from '@nrwl/devkit';
|
import { output } from '../../../../utilities/output';
|
||||||
import { startServer } from './server';
|
import { startServer } from './server';
|
||||||
import * as process from 'process';
|
import * as process from 'process';
|
||||||
|
|
||||||
@ -6,7 +6,11 @@ import * as process from 'process';
|
|||||||
try {
|
try {
|
||||||
await startServer();
|
await startServer();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(err);
|
output.error({
|
||||||
|
title:
|
||||||
|
err?.message ||
|
||||||
|
'Something unexpected went wrong when starting the server',
|
||||||
|
});
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { logger } from '@nrwl/devkit';
|
import { output } from '../../../../utilities/output';
|
||||||
import { safelyCleanUpExistingProcess } from '../cache';
|
import { safelyCleanUpExistingProcess } from '../cache';
|
||||||
import { stopServer } from './server';
|
import { stopServer } from './server';
|
||||||
|
|
||||||
@ -7,6 +7,10 @@ import { stopServer } from './server';
|
|||||||
await stopServer();
|
await stopServer();
|
||||||
await safelyCleanUpExistingProcess();
|
await safelyCleanUpExistingProcess();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(err);
|
output.error({
|
||||||
|
title:
|
||||||
|
err?.message ||
|
||||||
|
'Something unexpected went wrong when stopping the server',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { TaskOrchestrator } from './task-orchestrator';
|
|||||||
import { performance } from 'perf_hooks';
|
import { performance } from 'perf_hooks';
|
||||||
import { TaskGraphCreator } from './task-graph-creator';
|
import { TaskGraphCreator } from './task-graph-creator';
|
||||||
import { Hasher } from '../core/hasher/hasher';
|
import { Hasher } from '../core/hasher/hasher';
|
||||||
import { LifeCycle } from './life-cycle';
|
import { LifeCycle } from './life-cycles/life-cycle';
|
||||||
|
|
||||||
export interface RemoteCache {
|
export interface RemoteCache {
|
||||||
retrieve: (hash: string, cacheDirectory: string) => Promise<boolean>;
|
retrieve: (hash: string, cacheDirectory: string) => Promise<boolean>;
|
||||||
|
|||||||
@ -39,9 +39,8 @@ export class ForkedProcessTaskRunner {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const args = getCommandArgsForTask(Object.values(taskGraph.tasks)[0]);
|
const args = getCommandArgsForTask(Object.values(taskGraph.tasks)[0]);
|
||||||
const commandLine = `nx ${args.join(' ')}`;
|
output.logCommand(`${args.filter((a) => a !== 'run').join(' ')}`);
|
||||||
|
output.addNewline();
|
||||||
output.logCommand(commandLine);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const p = fork(workerPath, {
|
const p = fork(workerPath, {
|
||||||
@ -97,10 +96,9 @@ export class ForkedProcessTaskRunner {
|
|||||||
return new Promise<{ code: number; terminalOutput: string }>((res, rej) => {
|
return new Promise<{ code: number; terminalOutput: string }>((res, rej) => {
|
||||||
try {
|
try {
|
||||||
const args = getCommandArgsForTask(task);
|
const args = getCommandArgsForTask(task);
|
||||||
const commandLine = `nx ${args.join(' ')}`;
|
|
||||||
|
|
||||||
if (forwardOutput) {
|
if (forwardOutput) {
|
||||||
output.logCommand(commandLine);
|
output.logCommand(`${args.filter((a) => a !== 'run').join(' ')}`);
|
||||||
|
output.addNewline();
|
||||||
}
|
}
|
||||||
const p = fork(this.cliPath, args, {
|
const p = fork(this.cliPath, args, {
|
||||||
stdio: ['inherit', 'pipe', 'pipe', 'ipc'],
|
stdio: ['inherit', 'pipe', 'pipe', 'ipc'],
|
||||||
@ -163,10 +161,9 @@ export class ForkedProcessTaskRunner {
|
|||||||
return new Promise<{ code: number; terminalOutput: string }>((res, rej) => {
|
return new Promise<{ code: number; terminalOutput: string }>((res, rej) => {
|
||||||
try {
|
try {
|
||||||
const args = getCommandArgsForTask(task);
|
const args = getCommandArgsForTask(task);
|
||||||
const commandLine = `nx ${args.join(' ')}`;
|
|
||||||
|
|
||||||
if (forwardOutput) {
|
if (forwardOutput) {
|
||||||
output.logCommand(commandLine);
|
output.logCommand(`${args.filter((a) => a !== 'run').join(' ')}`);
|
||||||
|
output.addNewline();
|
||||||
}
|
}
|
||||||
const p = fork(this.cliPath, args, {
|
const p = fork(this.cliPath, args, {
|
||||||
stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
|
stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
|
||||||
|
|||||||
@ -0,0 +1,471 @@
|
|||||||
|
import { dots } from 'cli-spinners';
|
||||||
|
import { EOL } from 'os';
|
||||||
|
import * as readline from 'readline';
|
||||||
|
import { output } from '../../utilities/output';
|
||||||
|
import type { Task, TaskStatus } from '../tasks-runner';
|
||||||
|
import type { LifeCycle } from './life-cycle';
|
||||||
|
import { prettyTime } from './pretty-time';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The following function is responsible for creating a life cycle with dynamic
|
||||||
|
* outputs, meaning previous outputs can be rewritten or modified as new outputs
|
||||||
|
* are added. It is therefore intended for use on a user's local machines.
|
||||||
|
*
|
||||||
|
* In CI environments the static equivalent of this life cycle should be used.
|
||||||
|
*/
|
||||||
|
export async function createDynamicOutputRenderer({
|
||||||
|
projectNames,
|
||||||
|
tasks,
|
||||||
|
args,
|
||||||
|
overrides,
|
||||||
|
}: {
|
||||||
|
projectNames: string[];
|
||||||
|
tasks: Task[];
|
||||||
|
args: { target?: string; configuration?: string; parallel?: number };
|
||||||
|
overrides: Record<string, unknown>;
|
||||||
|
}): Promise<{ lifeCycle: LifeCycle; renderIsDone: Promise<void> }> {
|
||||||
|
let resolveRenderIsDonePromise: (value: void) => void;
|
||||||
|
const renderIsDone = new Promise<void>(
|
||||||
|
(resolve) => (resolveRenderIsDonePromise = resolve)
|
||||||
|
).then(() => clearRenderInterval());
|
||||||
|
|
||||||
|
function clearRenderInterval() {
|
||||||
|
if (renderProjectRowsIntervalId) {
|
||||||
|
clearInterval(renderProjectRowsIntervalId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function teardown() {
|
||||||
|
clearRenderInterval();
|
||||||
|
if (resolveRenderIsDonePromise) {
|
||||||
|
resolveRenderIsDonePromise();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
process.on('exit', () => teardown());
|
||||||
|
process.on('SIGINT', () => teardown());
|
||||||
|
process.on('unhandledRejection', () => teardown());
|
||||||
|
process.on('uncaughtException', () => teardown());
|
||||||
|
|
||||||
|
const lifeCycle = {} as Partial<LifeCycle>;
|
||||||
|
const isVerbose = overrides.verbose === true;
|
||||||
|
|
||||||
|
const start = process.hrtime();
|
||||||
|
const figures = await import('figures');
|
||||||
|
|
||||||
|
const totalTasks = tasks.length;
|
||||||
|
const totalProjects = projectNames.length;
|
||||||
|
const totalDependentTasks = totalTasks - totalProjects;
|
||||||
|
const targetName = args.target;
|
||||||
|
const projectRows = projectNames.map((projectName) => {
|
||||||
|
return {
|
||||||
|
projectName,
|
||||||
|
status: 'pending',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const tasksToTerminalOutputs: Record<string, string> = {};
|
||||||
|
const tasksToProcessStartTimes: Record<
|
||||||
|
string,
|
||||||
|
ReturnType<NodeJS.HRTime>
|
||||||
|
> = {};
|
||||||
|
let hasTaskOutput = false;
|
||||||
|
let pinnedFooterNumLines = 0;
|
||||||
|
let totalCompletedTasks = 0;
|
||||||
|
let totalSuccessfulTasks = 0;
|
||||||
|
let totalFailedTasks = 0;
|
||||||
|
let totalCachedTasks = 0;
|
||||||
|
|
||||||
|
// Used to control the rendering of the spinner on each project row
|
||||||
|
let projectRowsCurrentFrame = 0;
|
||||||
|
let renderProjectRowsIntervalId: NodeJS.Timeout | undefined;
|
||||||
|
|
||||||
|
const clearPinnedFooter = () => {
|
||||||
|
for (let i = 0; i < pinnedFooterNumLines; i++) {
|
||||||
|
readline.moveCursor(process.stdout, 0, -1);
|
||||||
|
readline.clearLine(process.stdout, 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderPinnedFooter = (lines: string[], dividerColor = 'cyan') => {
|
||||||
|
let additionalLines = 0;
|
||||||
|
if (hasTaskOutput) {
|
||||||
|
output.addVerticalSeparator(dividerColor);
|
||||||
|
additionalLines += 3;
|
||||||
|
}
|
||||||
|
// Create vertical breathing room for cursor position under the pinned footer
|
||||||
|
lines.push('');
|
||||||
|
for (const line of lines) {
|
||||||
|
process.stdout.write(output.X_PADDING + line + EOL);
|
||||||
|
}
|
||||||
|
pinnedFooterNumLines = lines.length + additionalLines;
|
||||||
|
};
|
||||||
|
|
||||||
|
const printTaskResult = (task: Task, status: TaskStatus) => {
|
||||||
|
clearPinnedFooter();
|
||||||
|
// If this is the very first output, add some vertical breathing room
|
||||||
|
if (!hasTaskOutput) {
|
||||||
|
output.addNewline();
|
||||||
|
}
|
||||||
|
hasTaskOutput = true;
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case 'local-cache':
|
||||||
|
writeLine(
|
||||||
|
`${
|
||||||
|
output.colors.green(figures.tick) +
|
||||||
|
output.dim(' nx run ') +
|
||||||
|
task.id
|
||||||
|
} ${output.colors.gray('[local cache]')}`
|
||||||
|
);
|
||||||
|
if (isVerbose) {
|
||||||
|
writeCommandOutputBlock(tasksToTerminalOutputs[task.id]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'remote-cache':
|
||||||
|
writeLine(
|
||||||
|
`${
|
||||||
|
output.colors.green(figures.tick) +
|
||||||
|
output.dim(' nx run ') +
|
||||||
|
task.id
|
||||||
|
} ${output.colors.gray('[remote cache]')}`
|
||||||
|
);
|
||||||
|
if (isVerbose) {
|
||||||
|
writeCommandOutputBlock(tasksToTerminalOutputs[task.id]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'success': {
|
||||||
|
const timeTakenText = prettyTime(
|
||||||
|
process.hrtime(tasksToProcessStartTimes[task.id])
|
||||||
|
);
|
||||||
|
writeLine(
|
||||||
|
output.colors.green(figures.tick) +
|
||||||
|
output.dim(' nx run ') +
|
||||||
|
task.id +
|
||||||
|
output.dim.gray(` (${timeTakenText})`)
|
||||||
|
);
|
||||||
|
if (isVerbose) {
|
||||||
|
writeCommandOutputBlock(tasksToTerminalOutputs[task.id]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'failure':
|
||||||
|
output.addNewline();
|
||||||
|
writeLine(
|
||||||
|
output.colors.red(figures.cross) +
|
||||||
|
output.dim(' nx run ') +
|
||||||
|
output.colors.red(task.id)
|
||||||
|
);
|
||||||
|
writeCommandOutputBlock(tasksToTerminalOutputs[task.id]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete tasksToTerminalOutputs[task.id];
|
||||||
|
renderPinnedFooter([]);
|
||||||
|
renderProjectRows();
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderProjectRows = () => {
|
||||||
|
const max = dots.frames.length - 1;
|
||||||
|
const curr = projectRowsCurrentFrame;
|
||||||
|
projectRowsCurrentFrame = curr >= max ? 0 : curr + 1;
|
||||||
|
|
||||||
|
const additionalFooterRows: string[] = [''];
|
||||||
|
const runningTasks = projectRows.filter((row) => row.status === 'running');
|
||||||
|
const remainingTasks = totalTasks - totalCompletedTasks;
|
||||||
|
|
||||||
|
if (runningTasks.length > 0) {
|
||||||
|
additionalFooterRows.push(
|
||||||
|
output.dim(
|
||||||
|
` ${output.colors.cyan(figures.arrowRight)} Executing ${
|
||||||
|
runningTasks.length
|
||||||
|
}/${remainingTasks} remaining tasks${
|
||||||
|
runningTasks.length > 1 ? ' in parallel' : ''
|
||||||
|
}...`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
additionalFooterRows.push('');
|
||||||
|
for (const projectRow of runningTasks) {
|
||||||
|
additionalFooterRows.push(
|
||||||
|
` ${output.dim.cyan(dots.frames[projectRowsCurrentFrame])} ${
|
||||||
|
output.dim('nx run ') + projectRow.projectName + ':' + targetName
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Reduce layout thrashing by ensuring that there is a relatively consistent
|
||||||
|
* height for the area in which the task rows are rendered.
|
||||||
|
*
|
||||||
|
* We can look at the parallel flag to know how many rows are likely to be
|
||||||
|
* needed in the common case and always render that at least that many.
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
totalCompletedTasks !== totalTasks &&
|
||||||
|
Number.isInteger(args.parallel) &&
|
||||||
|
runningTasks.length < args.parallel
|
||||||
|
) {
|
||||||
|
// Don't bother with this optimization if there are fewer tasks remaining than rows required
|
||||||
|
if (remainingTasks >= args.parallel) {
|
||||||
|
for (let i = runningTasks.length; i < args.parallel; i++) {
|
||||||
|
additionalFooterRows.push('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalSuccessfulTasks > 0 || totalFailedTasks > 0) {
|
||||||
|
additionalFooterRows.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalFailedTasks > 0) {
|
||||||
|
additionalFooterRows.push(
|
||||||
|
` ${output.colors.red(
|
||||||
|
figures.cross
|
||||||
|
)} ${totalFailedTasks}${`/${totalCompletedTasks}`} failed`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalSuccessfulTasks > 0) {
|
||||||
|
additionalFooterRows.push(
|
||||||
|
` ${output.colors.green(
|
||||||
|
figures.tick
|
||||||
|
)} ${totalSuccessfulTasks}${`/${totalCompletedTasks}`} succeeded ${output.colors.gray(
|
||||||
|
`[${totalCachedTasks} read from cache]`
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearPinnedFooter();
|
||||||
|
|
||||||
|
if (additionalFooterRows.length > 1) {
|
||||||
|
let text = `Running target ${output.bold.cyan(
|
||||||
|
targetName
|
||||||
|
)} for ${output.bold.cyan(totalProjects)} projects`;
|
||||||
|
if (totalDependentTasks > 0) {
|
||||||
|
text += ` and ${output.bold(
|
||||||
|
totalDependentTasks
|
||||||
|
)} task(s) they depend on`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const taskOverridesRows = [];
|
||||||
|
if (Object.keys(overrides).length > 0) {
|
||||||
|
const leftPadding = `${output.X_PADDING} `;
|
||||||
|
taskOverridesRows.push('');
|
||||||
|
taskOverridesRows.push(
|
||||||
|
`${leftPadding}${output.dim.cyan('With additional flags:')}`
|
||||||
|
);
|
||||||
|
Object.entries(overrides)
|
||||||
|
.map(([flag, value]) =>
|
||||||
|
output.dim.cyan(`${leftPadding} --${flag}=${value}`)
|
||||||
|
)
|
||||||
|
.forEach((arg) => taskOverridesRows.push(arg));
|
||||||
|
}
|
||||||
|
|
||||||
|
const pinnedFooterLines = [
|
||||||
|
output.applyNxPrefix('cyan', output.colors.cyan(text)),
|
||||||
|
...taskOverridesRows,
|
||||||
|
...additionalFooterRows,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Vertical breathing room when there isn't yet any output or divider
|
||||||
|
if (!hasTaskOutput) {
|
||||||
|
pinnedFooterLines.unshift('');
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPinnedFooter(pinnedFooterLines);
|
||||||
|
} else {
|
||||||
|
renderPinnedFooter([]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
lifeCycle.startCommand = () => {
|
||||||
|
if (totalProjects <= 0) {
|
||||||
|
let description = `with target ${output.colors.white.bold(targetName)}`;
|
||||||
|
if (args.configuration) {
|
||||||
|
description += ` that are configured for "${args.configuration}"`;
|
||||||
|
}
|
||||||
|
renderPinnedFooter([
|
||||||
|
'',
|
||||||
|
output.applyNxPrefix('gray', `No projects ${description} were run`),
|
||||||
|
]);
|
||||||
|
resolveRenderIsDonePromise();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
renderPinnedFooter([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
lifeCycle.startTasks = (tasks: Task[]) => {
|
||||||
|
for (const task of tasks) {
|
||||||
|
tasksToProcessStartTimes[task.id] = process.hrtime();
|
||||||
|
}
|
||||||
|
for (const projectRow of projectRows) {
|
||||||
|
const matchedTask = tasks.find(
|
||||||
|
(t) => t.target.project === projectRow.projectName
|
||||||
|
);
|
||||||
|
if (!matchedTask) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
projectRow.status = 'running';
|
||||||
|
}
|
||||||
|
if (!renderProjectRowsIntervalId) {
|
||||||
|
renderProjectRowsIntervalId = setInterval(renderProjectRows, 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
lifeCycle.printTaskTerminalOutput = (task, _cacheStatus, output) => {
|
||||||
|
tasksToTerminalOutputs[task.id] = output;
|
||||||
|
};
|
||||||
|
|
||||||
|
lifeCycle.endTasks = (taskResults) => {
|
||||||
|
totalCompletedTasks++;
|
||||||
|
|
||||||
|
for (let t of taskResults) {
|
||||||
|
const matchingProjectRow = projectRows.find(
|
||||||
|
(pr) => pr.projectName === t.task.target.project
|
||||||
|
);
|
||||||
|
if (matchingProjectRow) {
|
||||||
|
matchingProjectRow.status = t.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (t.status) {
|
||||||
|
case 'remote-cache':
|
||||||
|
case 'local-cache':
|
||||||
|
totalCachedTasks++;
|
||||||
|
case 'success':
|
||||||
|
totalSuccessfulTasks++;
|
||||||
|
break;
|
||||||
|
case 'failure':
|
||||||
|
totalFailedTasks++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
printTaskResult(t.task, t.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalCompletedTasks === totalTasks) {
|
||||||
|
clearRenderInterval();
|
||||||
|
const timeTakenText = prettyTime(process.hrtime(start));
|
||||||
|
|
||||||
|
clearPinnedFooter();
|
||||||
|
|
||||||
|
if (totalSuccessfulTasks === totalTasks) {
|
||||||
|
let text = `Successfully ran target ${output.bold(
|
||||||
|
targetName
|
||||||
|
)} for ${output.bold(totalProjects)} projects`;
|
||||||
|
if (totalDependentTasks > 0) {
|
||||||
|
text += ` and ${output.bold(
|
||||||
|
totalDependentTasks
|
||||||
|
)} task(s) they depend on`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const taskOverridesRows = [];
|
||||||
|
if (Object.keys(overrides).length > 0) {
|
||||||
|
const leftPadding = `${output.X_PADDING} `;
|
||||||
|
taskOverridesRows.push('');
|
||||||
|
taskOverridesRows.push(
|
||||||
|
`${leftPadding}${output.dim.green('With additional flags:')}`
|
||||||
|
);
|
||||||
|
Object.entries(overrides)
|
||||||
|
.map(([flag, value]) =>
|
||||||
|
output.dim.green(`${leftPadding} --${flag}=${value}`)
|
||||||
|
)
|
||||||
|
.forEach((arg) => taskOverridesRows.push(arg));
|
||||||
|
}
|
||||||
|
|
||||||
|
const pinnedFooterLines = [
|
||||||
|
output.applyNxPrefix(
|
||||||
|
'green',
|
||||||
|
output.colors.green(text) + output.dim.white(` (${timeTakenText})`)
|
||||||
|
),
|
||||||
|
...taskOverridesRows,
|
||||||
|
];
|
||||||
|
if (totalCachedTasks > 0) {
|
||||||
|
pinnedFooterLines.push(
|
||||||
|
output.colors.gray(
|
||||||
|
`${EOL} Nx read the output from the cache instead of running the command for ${totalCachedTasks} out of ${totalTasks} tasks.`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
renderPinnedFooter(pinnedFooterLines, 'green');
|
||||||
|
} else {
|
||||||
|
let text = `Ran target ${output.bold(targetName)} for ${output.bold(
|
||||||
|
totalProjects
|
||||||
|
)} projects`;
|
||||||
|
if (totalDependentTasks > 0) {
|
||||||
|
text += ` and ${output.bold(
|
||||||
|
totalDependentTasks
|
||||||
|
)} task(s) they depend on`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const taskOverridesRows = [];
|
||||||
|
if (Object.keys(overrides).length > 0) {
|
||||||
|
const leftPadding = `${output.X_PADDING} `;
|
||||||
|
taskOverridesRows.push('');
|
||||||
|
taskOverridesRows.push(
|
||||||
|
`${leftPadding}${output.dim.red('With additional flags:')}`
|
||||||
|
);
|
||||||
|
Object.entries(overrides)
|
||||||
|
.map(([flag, value]) =>
|
||||||
|
output.dim.red(`${leftPadding} --${flag}=${value}`)
|
||||||
|
)
|
||||||
|
.forEach((arg) => taskOverridesRows.push(arg));
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPinnedFooter(
|
||||||
|
[
|
||||||
|
output.applyNxPrefix(
|
||||||
|
'red',
|
||||||
|
output.colors.red(text) + output.dim.white(` (${timeTakenText})`)
|
||||||
|
),
|
||||||
|
...taskOverridesRows,
|
||||||
|
'',
|
||||||
|
` ${output.colors.red(
|
||||||
|
figures.cross
|
||||||
|
)} ${totalFailedTasks}${`/${totalCompletedTasks}`} failed`,
|
||||||
|
` ${output.colors.gray(
|
||||||
|
figures.tick
|
||||||
|
)} ${totalSuccessfulTasks}${`/${totalCompletedTasks}`} succeeded ${output.colors.gray(
|
||||||
|
`[${totalCachedTasks} read from cache]`
|
||||||
|
)}`,
|
||||||
|
],
|
||||||
|
'red'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
resolveRenderIsDonePromise();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { lifeCycle, renderIsDone };
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeLine(line: string) {
|
||||||
|
const additionalXPadding = ' ';
|
||||||
|
process.stdout.write(output.X_PADDING + additionalXPadding + line + EOL);
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeCommandOutputBlock(commandOutput: string) {
|
||||||
|
commandOutput = commandOutput || '';
|
||||||
|
const additionalXPadding = ' ';
|
||||||
|
const lines = commandOutput.split(EOL);
|
||||||
|
/**
|
||||||
|
* There's not much we can do in order to "neaten up" the outputs of
|
||||||
|
* commands we do not control, but at the very least we can trim excess
|
||||||
|
* newlines so that there isn't unncecessary vertical whitespace.
|
||||||
|
*/
|
||||||
|
let totalTrailingEmptyLines = 0;
|
||||||
|
for (let i = lines.length - 1; i >= 0; i--) {
|
||||||
|
if (lines[i] !== '') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
totalTrailingEmptyLines++;
|
||||||
|
}
|
||||||
|
if (totalTrailingEmptyLines > 1) {
|
||||||
|
const linesToRemove = totalTrailingEmptyLines - 1;
|
||||||
|
lines.splice(lines.length - linesToRemove, linesToRemove);
|
||||||
|
}
|
||||||
|
// Indent the command output to make it look more "designed" in the context of the dynamic output
|
||||||
|
process.stdout.write(
|
||||||
|
lines.map((l) => `${output.X_PADDING}${additionalXPadding}${l}`).join(EOL) +
|
||||||
|
EOL
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import type { Task } from '@nrwl/devkit';
|
import type { Task } from '@nrwl/devkit';
|
||||||
import { output, TaskCacheStatus } from '../utilities/output';
|
import { output, TaskCacheStatus } from '../../utilities/output';
|
||||||
import { LifeCycle } from './life-cycle';
|
import { getCommandArgsForTask } from '../utils';
|
||||||
import { getCommandArgsForTask } from './utils';
|
import type { LifeCycle } from './life-cycle';
|
||||||
|
|
||||||
export class EmptyTerminalOutputLifeCycle implements LifeCycle {
|
export class EmptyTerminalOutputLifeCycle implements LifeCycle {
|
||||||
printTaskTerminalOutput(
|
printTaskTerminalOutput(
|
||||||
@ -11,7 +11,10 @@ export class EmptyTerminalOutputLifeCycle implements LifeCycle {
|
|||||||
) {
|
) {
|
||||||
if (cacheStatus === TaskCacheStatus.NoCache) {
|
if (cacheStatus === TaskCacheStatus.NoCache) {
|
||||||
const args = getCommandArgsForTask(task);
|
const args = getCommandArgsForTask(task);
|
||||||
output.logCommand(`nx ${args.join(' ')}`, cacheStatus);
|
output.logCommand(
|
||||||
|
`${args.filter((a) => a !== 'run').join(' ')}`,
|
||||||
|
cacheStatus
|
||||||
|
);
|
||||||
process.stdout.write(terminalOutput);
|
process.stdout.write(terminalOutput);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import type { Task } from '@nrwl/devkit';
|
import type { Task } from '@nrwl/devkit';
|
||||||
import { TaskStatus } from './tasks-runner';
|
import { TaskStatus } from '../tasks-runner';
|
||||||
import { TaskCacheStatus } from '../utilities/output';
|
import { TaskCacheStatus } from '../../utilities/output';
|
||||||
|
|
||||||
export interface TaskResult {
|
export interface TaskResult {
|
||||||
task: Task;
|
task: Task;
|
||||||
@ -1,10 +1,18 @@
|
|||||||
import type { Task } from '@nrwl/devkit';
|
import type { Task } from '@nrwl/devkit';
|
||||||
import { output, TaskCacheStatus } from '../utilities/output';
|
import { output, TaskCacheStatus } from '../../utilities/output';
|
||||||
import { LifeCycle } from './life-cycle';
|
import { TaskStatus } from '../tasks-runner';
|
||||||
import { TaskStatus } from './tasks-runner';
|
import { getCommandArgsForTask } from '../utils';
|
||||||
import { getCommandArgsForTask } from './utils';
|
import type { LifeCycle } from './life-cycle';
|
||||||
|
|
||||||
export class RunManyTerminalOutputLifeCycle implements LifeCycle {
|
/**
|
||||||
|
* The following life cycle's outputs are static, meaning no previous content
|
||||||
|
* is rewritten or modified as new outputs are added. It is therefore intended
|
||||||
|
* for use in CI environments.
|
||||||
|
*
|
||||||
|
* For the common case of a user executing a command on their local machine,
|
||||||
|
* the dynamic equivalent of this life cycle is usually preferable.
|
||||||
|
*/
|
||||||
|
export class StaticRunManyTerminalOutputLifeCycle implements LifeCycle {
|
||||||
failedTasks = [] as Task[];
|
failedTasks = [] as Task[];
|
||||||
cachedTasks = [] as Task[];
|
cachedTasks = [] as Task[];
|
||||||
skippedTasks = [] as Task[];
|
skippedTasks = [] as Task[];
|
||||||
@ -30,7 +38,7 @@ export class RunManyTerminalOutputLifeCycle implements LifeCycle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const bodyLines = this.projectNames.map(
|
const bodyLines = this.projectNames.map(
|
||||||
(affectedProject) => `${output.colors.gray('-')} ${affectedProject}`
|
(affectedProject) => ` ${output.colors.gray('-')} ${affectedProject}`
|
||||||
);
|
);
|
||||||
if (Object.keys(this.taskOverrides).length > 0) {
|
if (Object.keys(this.taskOverrides).length > 0) {
|
||||||
bodyLines.push('');
|
bodyLines.push('');
|
||||||
@ -40,44 +48,50 @@ export class RunManyTerminalOutputLifeCycle implements LifeCycle {
|
|||||||
.forEach((arg) => bodyLines.push(arg));
|
.forEach((arg) => bodyLines.push(arg));
|
||||||
}
|
}
|
||||||
|
|
||||||
let title = `${output.colors.gray('Running target')} ${
|
let title = `Running target ${output.bold(
|
||||||
this.args.target
|
this.args.target
|
||||||
} ${output.colors.gray(`for`)} ${this.projectNames.length} project(s)`;
|
)} for ${output.bold(this.projectNames.length)} project(s)`;
|
||||||
const dependentTasksCount = this.tasks.length - this.projectNames.length;
|
const dependentTasksCount = this.tasks.length - this.projectNames.length;
|
||||||
if (dependentTasksCount > 0) {
|
if (dependentTasksCount > 0) {
|
||||||
title += ` ${output.colors.gray(`and`)} ${
|
title += ` and ${output.bold(
|
||||||
this.tasks.length - this.projectNames.length
|
dependentTasksCount
|
||||||
} task(s) ${output.colors.gray(`they depend on`)}`;
|
)} task(s) they depend on`;
|
||||||
}
|
}
|
||||||
title += ':';
|
title += ':';
|
||||||
|
|
||||||
output.log({
|
output.log({
|
||||||
|
color: 'cyan',
|
||||||
title,
|
title,
|
||||||
bodyLines,
|
bodyLines,
|
||||||
});
|
});
|
||||||
|
|
||||||
output.addVerticalSeparatorWithoutNewLines();
|
output.addVerticalSeparatorWithoutNewLines('cyan');
|
||||||
}
|
}
|
||||||
|
|
||||||
endCommand(): void {
|
endCommand(): void {
|
||||||
output.addNewline();
|
output.addNewline();
|
||||||
output.addVerticalSeparatorWithoutNewLines();
|
|
||||||
|
|
||||||
if (this.failedTasks.length === 0) {
|
if (this.failedTasks.length === 0) {
|
||||||
|
output.addVerticalSeparatorWithoutNewLines('green');
|
||||||
|
|
||||||
const bodyLines =
|
const bodyLines =
|
||||||
this.cachedTasks.length > 0
|
this.cachedTasks.length > 0
|
||||||
? [
|
? [
|
||||||
output.colors.gray(
|
output.colors.gray(
|
||||||
`Nx read the output from cache instead of running the command for ${this.cachedTasks.length} out of ${this.tasks.length} tasks.`
|
`Nx read the output from the cache instead of running the command for ${this.cachedTasks.length} out of ${this.tasks.length} tasks.`
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
output.success({
|
output.success({
|
||||||
title: `Running target "${this.args.target}" succeeded`,
|
title: `Successfully ran target ${output.bold(
|
||||||
|
this.args.target
|
||||||
|
)} for ${output.bold(this.projectNames.length)} projects`,
|
||||||
bodyLines,
|
bodyLines,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
output.addVerticalSeparatorWithoutNewLines('red');
|
||||||
|
|
||||||
const bodyLines = [];
|
const bodyLines = [];
|
||||||
if (this.skippedTasks.length > 0) {
|
if (this.skippedTasks.length > 0) {
|
||||||
bodyLines.push(
|
bodyLines.push(
|
||||||
@ -127,7 +141,10 @@ export class RunManyTerminalOutputLifeCycle implements LifeCycle {
|
|||||||
terminalOutput: string
|
terminalOutput: string
|
||||||
) {
|
) {
|
||||||
const args = getCommandArgsForTask(task);
|
const args = getCommandArgsForTask(task);
|
||||||
output.logCommand(`nx ${args.join(' ')}`, cacheStatus);
|
output.logCommand(
|
||||||
|
`${args.filter((a) => a !== 'run').join(' ')}`,
|
||||||
|
cacheStatus
|
||||||
|
);
|
||||||
process.stdout.write(terminalOutput);
|
process.stdout.write(terminalOutput);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,10 +1,18 @@
|
|||||||
import type { Task } from '@nrwl/devkit';
|
import type { Task } from '@nrwl/devkit';
|
||||||
import { output, TaskCacheStatus } from '../utilities/output';
|
import { output, TaskCacheStatus } from '../../utilities/output';
|
||||||
import { LifeCycle } from './life-cycle';
|
import { TaskStatus } from '../tasks-runner';
|
||||||
import { TaskStatus } from './tasks-runner';
|
import { getCommandArgsForTask } from '../utils';
|
||||||
import { getCommandArgsForTask } from './utils';
|
import type { LifeCycle } from './life-cycle';
|
||||||
|
|
||||||
export class RunOneTerminalOutputLifeCycle implements LifeCycle {
|
/**
|
||||||
|
* The following life cycle's outputs are static, meaning no previous content
|
||||||
|
* is rewritten or modified as new outputs are added. It is therefore intended
|
||||||
|
* for use in CI environments.
|
||||||
|
*
|
||||||
|
* For the common case of a user executing a command on their local machine,
|
||||||
|
* the dynamic equivalent of this life cycle is usually preferable.
|
||||||
|
*/
|
||||||
|
export class StaticRunOneTerminalOutputLifeCycle implements LifeCycle {
|
||||||
failedTasks = [] as Task[];
|
failedTasks = [] as Task[];
|
||||||
cachedTasks = [] as Task[];
|
cachedTasks = [] as Task[];
|
||||||
skippedTasks = [] as Task[];
|
skippedTasks = [] as Task[];
|
||||||
@ -27,16 +35,14 @@ export class RunOneTerminalOutputLifeCycle implements LifeCycle {
|
|||||||
|
|
||||||
if (numberOfDeps > 0) {
|
if (numberOfDeps > 0) {
|
||||||
output.log({
|
output.log({
|
||||||
title: `${output.colors.gray('Running target')} ${
|
color: 'cyan',
|
||||||
|
title: `Running target ${output.bold(
|
||||||
this.args.target
|
this.args.target
|
||||||
} ${output.colors.gray('for project')} ${
|
)} for project ${output.bold(this.initiatingProject)} and ${output.bold(
|
||||||
this.initiatingProject
|
numberOfDeps
|
||||||
} ${output.colors.gray(
|
)} task(s) it depends on`,
|
||||||
`and`
|
|
||||||
)} ${numberOfDeps} task(s) ${output.colors.gray(`that it depends on.`)}
|
|
||||||
`,
|
|
||||||
});
|
});
|
||||||
output.addVerticalSeparatorWithoutNewLines();
|
output.addVerticalSeparatorWithoutNewLines('cyan');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,23 +52,28 @@ export class RunOneTerminalOutputLifeCycle implements LifeCycle {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
output.addNewline();
|
output.addNewline();
|
||||||
output.addVerticalSeparatorWithoutNewLines();
|
|
||||||
|
|
||||||
if (this.failedTasks.length === 0) {
|
if (this.failedTasks.length === 0) {
|
||||||
|
output.addVerticalSeparatorWithoutNewLines('green');
|
||||||
|
|
||||||
const bodyLines =
|
const bodyLines =
|
||||||
this.cachedTasks.length > 0
|
this.cachedTasks.length > 0
|
||||||
? [
|
? [
|
||||||
output.colors.gray(
|
output.colors.gray(
|
||||||
`Nx read the output from cache instead of running the command for ${this.cachedTasks.length} out of ${this.tasks.length} tasks.`
|
`Nx read the output from the cache instead of running the command for ${this.cachedTasks.length} out of ${this.tasks.length} tasks.`
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
output.success({
|
output.success({
|
||||||
title: `Running target "${this.args.target}" succeeded`,
|
title: `Successfully ran target ${output.bold(
|
||||||
|
this.args.target
|
||||||
|
)} for project ${output.bold(this.initiatingProject)}`,
|
||||||
bodyLines,
|
bodyLines,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
output.addVerticalSeparatorWithoutNewLines('red');
|
||||||
|
|
||||||
const bodyLines = [
|
const bodyLines = [
|
||||||
output.colors.gray('Failed tasks:'),
|
output.colors.gray('Failed tasks:'),
|
||||||
'',
|
'',
|
||||||
@ -107,7 +118,10 @@ export class RunOneTerminalOutputLifeCycle implements LifeCycle {
|
|||||||
task.target.project === this.initiatingProject
|
task.target.project === this.initiatingProject
|
||||||
) {
|
) {
|
||||||
const args = getCommandArgsForTask(task);
|
const args = getCommandArgsForTask(task);
|
||||||
output.logCommand(`nx ${args.join(' ')}`, cacheStatus);
|
output.logCommand(
|
||||||
|
`${args.filter((a) => a !== 'run').join(' ')}`,
|
||||||
|
cacheStatus
|
||||||
|
);
|
||||||
process.stdout.write(terminalOutput);
|
process.stdout.write(terminalOutput);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { LifeCycle, TaskMetadata } from './life-cycle';
|
import { LifeCycle, TaskMetadata } from './life-cycle';
|
||||||
import { Task, writeJsonFile } from '@nrwl/devkit';
|
import { Task, writeJsonFile } from '@nrwl/devkit';
|
||||||
import { TaskStatus } from './tasks-runner';
|
import { TaskStatus } from '../tasks-runner';
|
||||||
|
|
||||||
import { performance } from 'perf_hooks';
|
import { performance } from 'perf_hooks';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { LifeCycle } from './life-cycle';
|
import { LifeCycle } from './life-cycle';
|
||||||
import { Task } from '@nrwl/devkit';
|
import { Task } from '@nrwl/devkit';
|
||||||
import { TaskStatus } from './tasks-runner';
|
import { TaskStatus } from '../tasks-runner';
|
||||||
|
|
||||||
export class TaskTimingsLifeCycle implements LifeCycle {
|
export class TaskTimingsLifeCycle implements LifeCycle {
|
||||||
private timings: {
|
private timings: {
|
||||||
@ -1,355 +0,0 @@
|
|||||||
import * as chalk from 'chalk';
|
|
||||||
import { dots } from 'cli-spinners';
|
|
||||||
import { EOL } from 'os';
|
|
||||||
import * as readline from 'readline';
|
|
||||||
import type { Task, TaskStatus } from '../tasks-runner';
|
|
||||||
import { prettyTime } from './pretty-time';
|
|
||||||
import { LifeCycle } from '@nrwl/workspace/src/tasks-runner/life-cycle';
|
|
||||||
|
|
||||||
const X_PADDING = ' ';
|
|
||||||
|
|
||||||
function applyNxPrefix(color = 'cyan', text: string) {
|
|
||||||
return `${chalk[color]('>')} ${chalk.reset.inverse.bold[color](
|
|
||||||
' NX '
|
|
||||||
)} ${text}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function writeLine(line: string) {
|
|
||||||
const additionalXPadding = ' ';
|
|
||||||
process.stdout.write(X_PADDING + additionalXPadding + line + EOL);
|
|
||||||
}
|
|
||||||
|
|
||||||
function writeCommandOutputBlock(output: string) {
|
|
||||||
const additionalXPadding = ' ';
|
|
||||||
const lines = output.split(EOL);
|
|
||||||
/**
|
|
||||||
* There's not much we can do in order to "neaten up" the outputs of
|
|
||||||
* commands we do not control, but at the very least we can trim excess
|
|
||||||
* newlines so that there isn't unncecessary vertical whitespace.
|
|
||||||
*/
|
|
||||||
let totalTrailingEmptyLines = 0;
|
|
||||||
for (let i = lines.length - 1; i >= 0; i--) {
|
|
||||||
if (lines[i] !== '') {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
totalTrailingEmptyLines++;
|
|
||||||
}
|
|
||||||
if (totalTrailingEmptyLines > 1) {
|
|
||||||
const linesToRemove = totalTrailingEmptyLines - 1;
|
|
||||||
lines.splice(lines.length - linesToRemove, linesToRemove);
|
|
||||||
}
|
|
||||||
process.stdout.write(
|
|
||||||
lines.map((l) => `${X_PADDING}${additionalXPadding}${l}`).join(EOL) + EOL
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createOutputRenderer({
|
|
||||||
projectNames,
|
|
||||||
tasks,
|
|
||||||
args,
|
|
||||||
}: {
|
|
||||||
projectNames: string[];
|
|
||||||
tasks: Task[];
|
|
||||||
args: { target?: string; configuration?: string };
|
|
||||||
}): Promise<{ lifeCycle: LifeCycle; renderIsDone: Promise<void> }> {
|
|
||||||
const lifeCycle = {} as any;
|
|
||||||
|
|
||||||
const start = process.hrtime();
|
|
||||||
const figures = await import('figures');
|
|
||||||
|
|
||||||
let resolveIsRenderCompletePromise: (value: void) => void;
|
|
||||||
const renderIsDone = new Promise<void>(
|
|
||||||
(resolve) => (resolveIsRenderCompletePromise = resolve)
|
|
||||||
);
|
|
||||||
|
|
||||||
const totalTasks = tasks.length;
|
|
||||||
const targetName = args.target;
|
|
||||||
const totalProjects = projectNames.length;
|
|
||||||
const projectRows = projectNames.map((projectName) => {
|
|
||||||
return {
|
|
||||||
projectName,
|
|
||||||
status: 'pending',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const tasksToTerminalOutputs: Record<string, string> = {};
|
|
||||||
let hasTaskOutput = false;
|
|
||||||
let pinnedFooterNumLines = 0;
|
|
||||||
let totalCompletedTasks = 0;
|
|
||||||
let totalSuccessfulTasks = 0;
|
|
||||||
let totalFailedTasks = 0;
|
|
||||||
let totalCachedTasks = 0;
|
|
||||||
|
|
||||||
// Used to control the rendering of the spinner on each project row
|
|
||||||
let projectRowsCurrentFrame = 0;
|
|
||||||
let renderProjectRowsIntervalId: NodeJS.Timeout | undefined;
|
|
||||||
|
|
||||||
const clearPinnedFooter = () => {
|
|
||||||
for (let i = 0; i < pinnedFooterNumLines; i++) {
|
|
||||||
readline.moveCursor(process.stdout, 0, -1);
|
|
||||||
readline.clearLine(process.stdout, 0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderPinnedFooter = (lines: string[], dividerColor = 'gray') => {
|
|
||||||
let additionalLines = 0;
|
|
||||||
if (hasTaskOutput) {
|
|
||||||
let divider = '';
|
|
||||||
for (let i = 0; i < process.stdout.columns - X_PADDING.length * 2; i++) {
|
|
||||||
divider += '\u2014';
|
|
||||||
}
|
|
||||||
process.stdout.write(EOL);
|
|
||||||
process.stdout.write(
|
|
||||||
X_PADDING + chalk.dim[dividerColor](divider + EOL) + EOL
|
|
||||||
);
|
|
||||||
additionalLines += 3;
|
|
||||||
}
|
|
||||||
// Create vertical breathing room for cursor position under the pinned footer
|
|
||||||
lines.push('');
|
|
||||||
for (const line of lines) {
|
|
||||||
process.stdout.write(X_PADDING + line + EOL);
|
|
||||||
}
|
|
||||||
pinnedFooterNumLines = lines.length + additionalLines;
|
|
||||||
};
|
|
||||||
|
|
||||||
const printTaskResult = (task: Task, status: TaskStatus) => {
|
|
||||||
clearPinnedFooter();
|
|
||||||
// If this is the very first output, add some vertical breathing room
|
|
||||||
if (!hasTaskOutput) {
|
|
||||||
process.stdout.write(EOL);
|
|
||||||
}
|
|
||||||
hasTaskOutput = true;
|
|
||||||
|
|
||||||
switch (status) {
|
|
||||||
case 'local-cache':
|
|
||||||
writeLine(
|
|
||||||
chalk.green(figures.tick) +
|
|
||||||
chalk.dim.white(' nx run ') +
|
|
||||||
chalk.white(task.id) +
|
|
||||||
' ' +
|
|
||||||
chalk.gray('[from cache]')
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case 'remote-cache':
|
|
||||||
writeLine(
|
|
||||||
chalk.green(figures.tick) +
|
|
||||||
chalk.dim(' nx run ') +
|
|
||||||
task.id +
|
|
||||||
' ☁️ ' +
|
|
||||||
chalk.gray('[from cloud cache]')
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case 'success':
|
|
||||||
writeLine(chalk.green(figures.tick) + chalk.dim(' nx run ') + task.id);
|
|
||||||
break;
|
|
||||||
case 'failure':
|
|
||||||
process.stdout.write(EOL);
|
|
||||||
writeLine(
|
|
||||||
chalk.red(figures.cross) + chalk.dim(' nx run ') + chalk.red(task.id)
|
|
||||||
);
|
|
||||||
writeCommandOutputBlock(tasksToTerminalOutputs[task.id]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
delete tasksToTerminalOutputs[task.id];
|
|
||||||
renderPinnedFooter([]);
|
|
||||||
renderProjectRows();
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderProjectRows = () => {
|
|
||||||
const max = dots.frames.length - 1;
|
|
||||||
const curr = projectRowsCurrentFrame;
|
|
||||||
projectRowsCurrentFrame = curr >= max ? 0 : curr + 1;
|
|
||||||
|
|
||||||
const additionalFooterRows: string[] = [''];
|
|
||||||
const runningTasks = projectRows.filter((row) => row.status === 'running');
|
|
||||||
const pendingTasks = projectRows.filter((row) => row.status === 'pending');
|
|
||||||
const remainingTasks = pendingTasks.length + runningTasks.length;
|
|
||||||
|
|
||||||
if (runningTasks.length > 0) {
|
|
||||||
additionalFooterRows.push(
|
|
||||||
chalk.dim.cyan(
|
|
||||||
` ${figures.arrowRight} Executing ${
|
|
||||||
runningTasks.length
|
|
||||||
}/${remainingTasks} remaining tasks${
|
|
||||||
runningTasks.length > 1 ? ' in parallel' : ''
|
|
||||||
}...`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
additionalFooterRows.push('');
|
|
||||||
for (const projectRow of runningTasks) {
|
|
||||||
additionalFooterRows.push(
|
|
||||||
` ${chalk.dim.cyan(
|
|
||||||
dots.frames[projectRowsCurrentFrame]
|
|
||||||
)} ${chalk.dim.white(projectRow.projectName)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (totalSuccessfulTasks > 0 || totalFailedTasks > 0) {
|
|
||||||
additionalFooterRows.push('');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (totalFailedTasks > 0) {
|
|
||||||
additionalFooterRows.push(
|
|
||||||
` ${chalk.red(figures.cross)} ${totalFailedTasks}${chalk.dim(
|
|
||||||
`/${totalCompletedTasks}`
|
|
||||||
)} failed`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (totalSuccessfulTasks > 0) {
|
|
||||||
additionalFooterRows.push(
|
|
||||||
` ${chalk.green(figures.tick)} ${totalSuccessfulTasks}${chalk.dim(
|
|
||||||
`/${totalCompletedTasks}`
|
|
||||||
)} succeeded ${chalk.gray(`[${totalCachedTasks} read from cache]`)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearPinnedFooter();
|
|
||||||
|
|
||||||
if (additionalFooterRows.length > 1) {
|
|
||||||
const pinnedFooterLines = [
|
|
||||||
applyNxPrefix(
|
|
||||||
'cyan',
|
|
||||||
chalk.gray(
|
|
||||||
`Running target ${chalk.bold.white(
|
|
||||||
targetName
|
|
||||||
)} for ${chalk.bold.white(totalProjects)} project(s)`
|
|
||||||
)
|
|
||||||
),
|
|
||||||
...additionalFooterRows,
|
|
||||||
];
|
|
||||||
|
|
||||||
// Vertical breathing room when there isn't yet any output or divider
|
|
||||||
if (!hasTaskOutput) {
|
|
||||||
pinnedFooterLines.unshift('');
|
|
||||||
}
|
|
||||||
|
|
||||||
renderPinnedFooter(pinnedFooterLines);
|
|
||||||
} else {
|
|
||||||
renderPinnedFooter([]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
lifeCycle.startCommand = (params) => {
|
|
||||||
if (totalProjects <= 0) {
|
|
||||||
let description = `with target "${targetName}"`;
|
|
||||||
if (params.args.configuration) {
|
|
||||||
description += ` that are configured for "${params.args.configuration}"`;
|
|
||||||
}
|
|
||||||
renderPinnedFooter([
|
|
||||||
'',
|
|
||||||
applyNxPrefix('gray', `No projects ${description} were run`),
|
|
||||||
]);
|
|
||||||
resolveIsRenderCompletePromise();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
renderPinnedFooter([]);
|
|
||||||
};
|
|
||||||
|
|
||||||
lifeCycle.startTasks = (tasks) => {
|
|
||||||
for (const projectRow of projectRows) {
|
|
||||||
const matchedTask = tasks.find(
|
|
||||||
(t) => t.target.project === projectRow.projectName
|
|
||||||
);
|
|
||||||
if (!matchedTask) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
projectRow.status = 'running';
|
|
||||||
}
|
|
||||||
if (!renderProjectRowsIntervalId) {
|
|
||||||
renderProjectRowsIntervalId = setInterval(renderProjectRows, 100);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
lifeCycle.printTaskTerminalOutput = (task, _cacheStatus, output) => {
|
|
||||||
tasksToTerminalOutputs[task.id] = output;
|
|
||||||
};
|
|
||||||
|
|
||||||
lifeCycle.endTasks = (taskResults) => {
|
|
||||||
totalCompletedTasks++;
|
|
||||||
|
|
||||||
for (let t of taskResults) {
|
|
||||||
const matchingProjectRow = projectRows.find(
|
|
||||||
(pr) => pr.projectName === t.task.target.project
|
|
||||||
);
|
|
||||||
if (matchingProjectRow) {
|
|
||||||
matchingProjectRow.status = t.status;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (t.status) {
|
|
||||||
case 'remote-cache':
|
|
||||||
case 'local-cache':
|
|
||||||
totalCachedTasks++;
|
|
||||||
case 'success':
|
|
||||||
totalSuccessfulTasks++;
|
|
||||||
break;
|
|
||||||
case 'failure':
|
|
||||||
totalFailedTasks++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
printTaskResult(t.task, t.status);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (totalCompletedTasks === totalTasks) {
|
|
||||||
if (renderProjectRowsIntervalId) {
|
|
||||||
clearInterval(renderProjectRowsIntervalId);
|
|
||||||
}
|
|
||||||
const timeTakenText = prettyTime(process.hrtime(start));
|
|
||||||
|
|
||||||
clearPinnedFooter();
|
|
||||||
|
|
||||||
if (totalSuccessfulTasks === totalTasks) {
|
|
||||||
const pinnedFooterLines = [
|
|
||||||
applyNxPrefix(
|
|
||||||
'green',
|
|
||||||
chalk.green(
|
|
||||||
`Successfully ran target ${chalk.bold(
|
|
||||||
targetName
|
|
||||||
)} for ${chalk.bold(totalProjects)} projects`
|
|
||||||
) + chalk.dim.white(` (${timeTakenText})`)
|
|
||||||
),
|
|
||||||
];
|
|
||||||
if (totalCachedTasks > 0) {
|
|
||||||
pinnedFooterLines.push(
|
|
||||||
chalk.gray(
|
|
||||||
`\n Nx read the output from the cache instead of running the command for ${totalCachedTasks} out of ${totalTasks} tasks.`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
renderPinnedFooter(pinnedFooterLines, 'green');
|
|
||||||
} else {
|
|
||||||
renderPinnedFooter(
|
|
||||||
[
|
|
||||||
applyNxPrefix(
|
|
||||||
'red',
|
|
||||||
chalk.red(
|
|
||||||
`Ran target ${chalk.bold(targetName)} for ${chalk.bold(
|
|
||||||
totalProjects
|
|
||||||
)} projects`
|
|
||||||
) + chalk.dim.white(` (${timeTakenText})`)
|
|
||||||
),
|
|
||||||
'',
|
|
||||||
` ${chalk.red(figures.cross)} ${totalFailedTasks}${chalk.dim(
|
|
||||||
`/${totalCompletedTasks}`
|
|
||||||
)} failed`,
|
|
||||||
` ${chalk.gray(
|
|
||||||
figures.tick
|
|
||||||
)} ${totalSuccessfulTasks}${chalk.dim(
|
|
||||||
`/${totalCompletedTasks}`
|
|
||||||
)} succeeded ${chalk.gray(
|
|
||||||
`[${totalCachedTasks} read from cache]`
|
|
||||||
)}`,
|
|
||||||
],
|
|
||||||
'red'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveIsRenderCompletePromise();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return { lifeCycle, renderIsDone };
|
|
||||||
}
|
|
||||||
@ -19,13 +19,13 @@ import {
|
|||||||
} from '../utilities/project-graph-utils';
|
} from '../utilities/project-graph-utils';
|
||||||
import { output } from '../utilities/output';
|
import { output } from '../utilities/output';
|
||||||
import { getDependencyConfigs, shouldForwardOutput } from './utils';
|
import { getDependencyConfigs, shouldForwardOutput } from './utils';
|
||||||
import { CompositeLifeCycle, LifeCycle } from './life-cycle';
|
import { CompositeLifeCycle, LifeCycle } from './life-cycles/life-cycle';
|
||||||
import { RunManyTerminalOutputLifeCycle } from './run-many-terminal-output-life-cycle';
|
import { StaticRunManyTerminalOutputLifeCycle } from './life-cycles/static-run-many-terminal-output-life-cycle';
|
||||||
import { EmptyTerminalOutputLifeCycle } from './empty-terminal-output-life-cycle';
|
import { StaticRunOneTerminalOutputLifeCycle } from './life-cycles/static-run-one-terminal-output-life-cycle';
|
||||||
import { RunOneTerminalOutputLifeCycle } from './run-one-terminal-output-life-cycle';
|
import { EmptyTerminalOutputLifeCycle } from './life-cycles/empty-terminal-output-life-cycle';
|
||||||
import { TaskTimingsLifeCycle } from './task-timings-life-cycle';
|
import { TaskTimingsLifeCycle } from './life-cycles/task-timings-life-cycle';
|
||||||
import { createOutputRenderer } from './neo-output/render';
|
import { createDynamicOutputRenderer } from './life-cycles/dynamic-run-many-terminal-output-life-cycle';
|
||||||
import { TaskProfilingLifeCycle } from '@nrwl/workspace/src/tasks-runner/task-profiling-life-cycle';
|
import { TaskProfilingLifeCycle } from './life-cycles/task-profiling-life-cycle';
|
||||||
|
|
||||||
async function getTerminalOutputLifeCycle(
|
async function getTerminalOutputLifeCycle(
|
||||||
initiatingProject: string,
|
initiatingProject: string,
|
||||||
@ -33,12 +33,12 @@ async function getTerminalOutputLifeCycle(
|
|||||||
projectNames: string[],
|
projectNames: string[],
|
||||||
tasks: Task[],
|
tasks: Task[],
|
||||||
nxArgs: NxArgs,
|
nxArgs: NxArgs,
|
||||||
overrides: any,
|
overrides: Record<string, unknown>,
|
||||||
runnerOptions: any
|
runnerOptions: any
|
||||||
): Promise<{ lifeCycle: LifeCycle; renderIsDone: Promise<void> }> {
|
): Promise<{ lifeCycle: LifeCycle; renderIsDone: Promise<void> }> {
|
||||||
if (terminalOutputStrategy === 'run-one') {
|
if (terminalOutputStrategy === 'run-one') {
|
||||||
return {
|
return {
|
||||||
lifeCycle: new RunOneTerminalOutputLifeCycle(
|
lifeCycle: new StaticRunOneTerminalOutputLifeCycle(
|
||||||
initiatingProject,
|
initiatingProject,
|
||||||
projectNames,
|
projectNames,
|
||||||
tasks,
|
tasks,
|
||||||
@ -52,17 +52,18 @@ async function getTerminalOutputLifeCycle(
|
|||||||
renderIsDone: Promise.resolve(),
|
renderIsDone: Promise.resolve(),
|
||||||
};
|
};
|
||||||
} else if (
|
} else if (
|
||||||
shouldUseNeoLifeCycle(tasks, runnerOptions) &&
|
shouldUseDynamicLifeCycle(tasks, runnerOptions) &&
|
||||||
process.env.NX_TASKS_RUNNER_NEO_OUTPUT === 'true'
|
process.env.NX_TASKS_RUNNER_DYNAMIC_OUTPUT === 'true'
|
||||||
) {
|
) {
|
||||||
return await createOutputRenderer({
|
return await createDynamicOutputRenderer({
|
||||||
projectNames,
|
projectNames,
|
||||||
tasks,
|
tasks,
|
||||||
args: nxArgs,
|
args: nxArgs,
|
||||||
|
overrides,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
lifeCycle: new RunManyTerminalOutputLifeCycle(
|
lifeCycle: new StaticRunManyTerminalOutputLifeCycle(
|
||||||
projectNames,
|
projectNames,
|
||||||
tasks,
|
tasks,
|
||||||
nxArgs,
|
nxArgs,
|
||||||
@ -226,7 +227,7 @@ export function createTasksForProjectToRun(
|
|||||||
return Array.from(tasksMap.values());
|
return Array.from(tasksMap.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldUseNeoLifeCycle(tasks: Task[], options: any) {
|
function shouldUseDynamicLifeCycle(tasks: Task[], options: any) {
|
||||||
const isTTY = !!process.stdout.isTTY && process.env['CI'] !== 'true';
|
const isTTY = !!process.stdout.isTTY && process.env['CI'] !== 'true';
|
||||||
const noForwarding = !tasks.find((t) =>
|
const noForwarding = !tasks.find((t) =>
|
||||||
shouldForwardOutput(t, null, options)
|
shouldForwardOutput(t, null, options)
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import {
|
|||||||
shouldForwardOutput,
|
shouldForwardOutput,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import { Batch, TasksSchedule } from './tasks-schedule';
|
import { Batch, TasksSchedule } from './tasks-schedule';
|
||||||
import { TaskMetadata } from './life-cycle';
|
import { TaskMetadata } from './life-cycles/life-cycle';
|
||||||
|
|
||||||
export class TaskOrchestrator {
|
export class TaskOrchestrator {
|
||||||
private cache = new Cache(this.options);
|
private cache = new Cache(this.options);
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import * as chalk from 'chalk';
|
import * as chalk from 'chalk';
|
||||||
|
import { EOL } from 'os';
|
||||||
import { isCI } from './is_ci';
|
import { isCI } from './is_ci';
|
||||||
|
|
||||||
export interface CLIErrorMessageConfig {
|
export interface CLIErrorMessageConfig {
|
||||||
@ -37,15 +38,23 @@ if (isCI()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class CLIOutput {
|
class CLIOutput {
|
||||||
private readonly NX_PREFIX = `${chalk.cyan(
|
readonly X_PADDING = ' ';
|
||||||
'>'
|
|
||||||
)} ${chalk.reset.inverse.bold.cyan(' NX ')}`;
|
|
||||||
/**
|
/**
|
||||||
* Longer dash character which forms more of a continuous line when place side to side
|
* Longer dash character which forms more of a continuous line when place side to side
|
||||||
* with itself, unlike the standard dash character
|
* with itself, unlike the standard dash character
|
||||||
*/
|
*/
|
||||||
private readonly VERTICAL_SEPARATOR =
|
private get VERTICAL_SEPARATOR() {
|
||||||
'———————————————————————————————————————————————';
|
let divider = '';
|
||||||
|
for (
|
||||||
|
let i = 0;
|
||||||
|
i < process.stdout.columns - this.X_PADDING.length * 2;
|
||||||
|
i++
|
||||||
|
) {
|
||||||
|
divider += '\u2014';
|
||||||
|
}
|
||||||
|
return divider;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expose some color and other utility functions so that other parts of the codebase that need
|
* Expose some color and other utility functions so that other parts of the codebase that need
|
||||||
@ -54,28 +63,27 @@ class CLIOutput {
|
|||||||
*/
|
*/
|
||||||
colors = {
|
colors = {
|
||||||
gray: chalk.gray,
|
gray: chalk.gray,
|
||||||
|
green: chalk.green,
|
||||||
|
red: chalk.red,
|
||||||
|
cyan: chalk.cyan,
|
||||||
|
white: chalk.white,
|
||||||
};
|
};
|
||||||
bold = chalk.bold;
|
bold = chalk.bold;
|
||||||
underline = chalk.underline;
|
underline = chalk.underline;
|
||||||
|
dim = chalk.dim;
|
||||||
|
|
||||||
private writeToStdOut(str: string) {
|
private writeToStdOut(str: string) {
|
||||||
process.stdout.write(str);
|
process.stdout.write(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
private writeOutputTitle({
|
private writeOutputTitle({
|
||||||
label,
|
color,
|
||||||
title,
|
title,
|
||||||
}: {
|
}: {
|
||||||
label?: string;
|
color: string;
|
||||||
title: string;
|
title: string;
|
||||||
}): void {
|
}): void {
|
||||||
let outputTitle: string;
|
this.writeToStdOut(` ${this.applyNxPrefix(color, title)}${EOL}`);
|
||||||
if (label) {
|
|
||||||
outputTitle = `${this.NX_PREFIX} ${label} ${title}\n`;
|
|
||||||
} else {
|
|
||||||
outputTitle = `${this.NX_PREFIX} ${title}\n`;
|
|
||||||
}
|
|
||||||
this.writeToStdOut(outputTitle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private writeOptionalOutputBody(bodyLines?: string[]): void {
|
private writeOptionalOutputBody(bodyLines?: string[]): void {
|
||||||
@ -83,27 +91,45 @@ class CLIOutput {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.addNewline();
|
this.addNewline();
|
||||||
bodyLines.forEach((bodyLine) => this.writeToStdOut(` ${bodyLine}\n`));
|
bodyLines.forEach((bodyLine) => this.writeToStdOut(` ${bodyLine}${EOL}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
applyNxPrefix(color = 'cyan', text: string): string {
|
||||||
|
let nxPrefix = '';
|
||||||
|
if (chalk[color]) {
|
||||||
|
nxPrefix = `${chalk[color]('>')} ${chalk.reset.inverse.bold[color](
|
||||||
|
' NX '
|
||||||
|
)}`;
|
||||||
|
} else {
|
||||||
|
nxPrefix = `${chalk.keyword(color)(
|
||||||
|
'>'
|
||||||
|
)} ${chalk.reset.inverse.bold.keyword(color)(' NX ')}`;
|
||||||
|
}
|
||||||
|
return `${nxPrefix} ${text}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
addNewline() {
|
addNewline() {
|
||||||
this.writeToStdOut('\n');
|
this.writeToStdOut(EOL);
|
||||||
}
|
}
|
||||||
|
|
||||||
addVerticalSeparator() {
|
addVerticalSeparator(color = 'gray') {
|
||||||
this.writeToStdOut(`\n${chalk.gray(this.VERTICAL_SEPARATOR)}\n\n`);
|
this.addNewline();
|
||||||
|
this.addVerticalSeparatorWithoutNewLines(color);
|
||||||
|
this.addNewline();
|
||||||
}
|
}
|
||||||
|
|
||||||
addVerticalSeparatorWithoutNewLines() {
|
addVerticalSeparatorWithoutNewLines(color = 'gray') {
|
||||||
this.writeToStdOut(`${chalk.gray(this.VERTICAL_SEPARATOR)}\n`);
|
this.writeToStdOut(
|
||||||
|
`${this.X_PADDING}${chalk.dim[color](this.VERTICAL_SEPARATOR)}${EOL}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
error({ title, slug, bodyLines }: CLIErrorMessageConfig) {
|
error({ title, slug, bodyLines }: CLIErrorMessageConfig) {
|
||||||
this.addNewline();
|
this.addNewline();
|
||||||
|
|
||||||
this.writeOutputTitle({
|
this.writeOutputTitle({
|
||||||
label: chalk.reset.inverse.bold.red(' ERROR '),
|
color: 'red',
|
||||||
title: chalk.bold.red(title),
|
title: chalk.red(title),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.writeOptionalOutputBody(bodyLines);
|
this.writeOptionalOutputBody(bodyLines);
|
||||||
@ -116,7 +142,7 @@ class CLIOutput {
|
|||||||
this.writeToStdOut(
|
this.writeToStdOut(
|
||||||
`${chalk.grey(
|
`${chalk.grey(
|
||||||
' Learn more about this error: '
|
' Learn more about this error: '
|
||||||
)}https://errors.nx.dev/${slug}\n`
|
)}https://errors.nx.dev/${slug}${EOL}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,8 +153,8 @@ class CLIOutput {
|
|||||||
this.addNewline();
|
this.addNewline();
|
||||||
|
|
||||||
this.writeOutputTitle({
|
this.writeOutputTitle({
|
||||||
label: chalk.reset.inverse.bold.yellow(' WARNING '),
|
color: 'yellow',
|
||||||
title: chalk.bold.yellow(title),
|
title: chalk.yellow(title),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.writeOptionalOutputBody(bodyLines);
|
this.writeOptionalOutputBody(bodyLines);
|
||||||
@ -152,8 +178,8 @@ class CLIOutput {
|
|||||||
this.addNewline();
|
this.addNewline();
|
||||||
|
|
||||||
this.writeOutputTitle({
|
this.writeOutputTitle({
|
||||||
label: chalk.reset.inverse.bold.keyword('orange')(' NOTE '),
|
color: 'orange',
|
||||||
title: chalk.bold.keyword('orange')(title),
|
title: chalk.keyword('orange')(title),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.writeOptionalOutputBody(bodyLines);
|
this.writeOptionalOutputBody(bodyLines);
|
||||||
@ -165,8 +191,8 @@ class CLIOutput {
|
|||||||
this.addNewline();
|
this.addNewline();
|
||||||
|
|
||||||
this.writeOutputTitle({
|
this.writeOutputTitle({
|
||||||
label: chalk.reset.inverse.bold.green(' SUCCESS '),
|
color: 'green',
|
||||||
title: chalk.bold.green(title),
|
title: chalk.green(title),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.writeOptionalOutputBody(bodyLines);
|
this.writeOptionalOutputBody(bodyLines);
|
||||||
@ -178,6 +204,7 @@ class CLIOutput {
|
|||||||
this.addNewline();
|
this.addNewline();
|
||||||
|
|
||||||
this.writeOutputTitle({
|
this.writeOutputTitle({
|
||||||
|
color: 'gray',
|
||||||
title: message,
|
title: message,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -190,20 +217,23 @@ class CLIOutput {
|
|||||||
) {
|
) {
|
||||||
this.addNewline();
|
this.addNewline();
|
||||||
|
|
||||||
this.writeToStdOut(chalk.bold(`> ${message} `));
|
let commandOutput = ` ${chalk.dim('> nx run')} ${message}`;
|
||||||
|
|
||||||
if (cacheStatus !== TaskCacheStatus.NoCache) {
|
if (cacheStatus !== TaskCacheStatus.NoCache) {
|
||||||
this.writeToStdOut(chalk.bold.grey(cacheStatus));
|
commandOutput += ` ${chalk.grey(cacheStatus)}`;
|
||||||
}
|
}
|
||||||
|
this.writeToStdOut(commandOutput);
|
||||||
|
|
||||||
this.addNewline();
|
this.addNewline();
|
||||||
}
|
}
|
||||||
|
|
||||||
log({ title, bodyLines }: CLIWarnMessageConfig) {
|
log({ title, bodyLines, color }: CLIWarnMessageConfig & { color?: string }) {
|
||||||
this.addNewline();
|
this.addNewline();
|
||||||
|
|
||||||
|
color = color || 'white';
|
||||||
|
|
||||||
this.writeOutputTitle({
|
this.writeOutputTitle({
|
||||||
title: chalk.white(title),
|
color: 'cyan',
|
||||||
|
title: chalk[color](title),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.writeOptionalOutputBody(bodyLines);
|
this.writeOptionalOutputBody(bodyLines);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user