feat(core): add the ability to specify multiple targets

This commit is contained in:
Victor Savkin 2022-12-21 14:26:22 -05:00
parent 376a4ca496
commit 8572eed44e
33 changed files with 337 additions and 218 deletions

View File

@ -149,11 +149,11 @@ Default: `false`
Rerun the tasks even when the results are available in the cache Rerun the tasks even when the results are available in the cache
### target ### targets
Type: `string` Type: `array`
Task to run for affected projects Tasks to run for affected projects
### uncommitted ### uncommitted

View File

@ -81,11 +81,11 @@ Default: `false`
Rerun the tasks even when the results are available in the cache Rerun the tasks even when the results are available in the cache
### target ### targets
Type: `string` Type: `array`
Task to run for affected projects Tasks to run for affected projects
### verbose ### verbose

View File

@ -99,11 +99,11 @@ Type: `string`
Select the subset of the returned json document (e.g., --select=projects) Select the subset of the returned json document (e.g., --select=projects)
### target ### targets
Type: `string` Type: `array`
Task to run for affected projects Tasks to run for affected projects
### type ### type

View File

@ -121,11 +121,11 @@ Default: `false`
Rerun the tasks even when the results are available in the cache Rerun the tasks even when the results are available in the cache
### target ### targets
Type: `string` Type: `array`
Task to run for affected projects Tasks to run for affected projects
### verbose ### verbose

View File

@ -149,11 +149,11 @@ Default: `false`
Rerun the tasks even when the results are available in the cache Rerun the tasks even when the results are available in the cache
### target ### targets
Type: `string` Type: `array`
Task to run for affected projects Tasks to run for affected projects
### uncommitted ### uncommitted

View File

@ -81,11 +81,11 @@ Default: `false`
Rerun the tasks even when the results are available in the cache Rerun the tasks even when the results are available in the cache
### target ### targets
Type: `string` Type: `array`
Task to run for affected projects Tasks to run for affected projects
### verbose ### verbose

View File

@ -99,11 +99,11 @@ Type: `string`
Select the subset of the returned json document (e.g., --select=projects) Select the subset of the returned json document (e.g., --select=projects)
### target ### targets
Type: `string` Type: `array`
Task to run for affected projects Tasks to run for affected projects
### type ### type

View File

@ -121,11 +121,11 @@ Default: `false`
Rerun the tasks even when the results are available in the cache Rerun the tasks even when the results are available in the cache
### target ### targets
Type: `string` Type: `array`
Task to run for affected projects Tasks to run for affected projects
### verbose ### verbose

View File

@ -34,7 +34,7 @@ Build at: 2022-11-30T16:44:43.171Z - Hash: 9850ece7cc7c6b7c - Time: 6527ms
————————————————————————————————————————————————————————————————————————————————————————————————————————————————— —————————————————————————————————————————————————————————————————————————————————————————————————————————————————
> NX Successfully ran target build for project store and 1 task(s) it depends on (9s) > NX Successfully ran target build for project store and 1 task(s) they depend on (9s)
``` ```
@ -100,7 +100,7 @@ Build at: 2022-11-30T16:44:43.171Z - Hash: 9850ece7cc7c6b7c - Time: 6527ms
———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
> NX Successfully ran target build for project store and 1 task(s) it depends on (13ms) > NX Successfully ran target build for project store and 1 task(s) they depend on (13ms)
Nx read the output from the cache instead of running the command for 2 out of 2 tasks. Nx read the output from the cache instead of running the command for 2 out of 2 tasks.
``` ```

View File

@ -198,7 +198,7 @@ Build at: 2022-11-23T22:53:29.097Z - Hash: 3cd478c78ebeead2 - Time: 5154ms
———————————————————————————————————————————————————————————————————————————————————————————————— ————————————————————————————————————————————————————————————————————————————————————————————————
> NX Successfully ran target build for project store and 1 task(s) it depends on (8s) > NX Successfully ran target build for project store and 1 task(s) they depend on (8s)
``` ```
Notice the line here: Notice the line here:

View File

@ -191,7 +191,7 @@ webpack compiled successfully (bafa37be9890ecb2)
——————————————————————————————————————————————————————————————————————————————————————————————— ———————————————————————————————————————————————————————————————————————————————————————————————
> NX Successfully ran target build for project products-cli and 1 task(s) it depends on (2s) > NX Successfully ran target build for project products-cli and 1 task(s) they depend on (2s)
Nx read the output from the cache instead of running the command for 1 out of 2 tasks. Nx read the output from the cache instead of running the command for 1 out of 2 tasks.
``` ```

View File

@ -25,7 +25,7 @@ dist/store/assets/index.f18c2b19.js.map 565.79 KiB
———————————————————————————————————————————————————————————————————————————————————————————————————————— ————————————————————————————————————————————————————————————————————————————————————————————————————————
> NX Successfully ran target build for project store and 2 task(s) it depends on (6s) > NX Successfully ran target build for project store and 2 task(s) they depend on (6s)
``` ```
@ -86,7 +86,7 @@ dist/store/assets/index.f18c2b19.js.map 565.79 KiB
———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
> NX Successfully ran target build for project store and 2 task(s) it depends on (42ms) > NX Successfully ran target build for project store and 2 task(s) they depend on (42ms)
Nx read the output from the cache instead of running the command for 3 out of 3 tasks. Nx read the output from the cache instead of running the command for 3 out of 3 tasks.
``` ```

View File

@ -181,7 +181,7 @@ webpack compiled successfully (06e95dfdacea84c7)
——————————————————————————————————————————————————————————————————————————————————————————————————— ———————————————————————————————————————————————————————————————————————————————————————————————————
> NX Successfully ran target build for project store and 1 task(s) it depends on (5s) > NX Successfully ran target build for project store and 1 task(s) they depend on (5s)
``` ```
Notice the line here: Notice the line here:

View File

@ -140,7 +140,7 @@ describe('js e2e', () => {
}); });
const output = runCLI(`build ${parentLib}`); const output = runCLI(`build ${parentLib}`);
expect(output).toContain('1 task(s) it depends on'); expect(output).toContain('1 task it depends on');
expect(output).toContain('Done compiling TypeScript files'); expect(output).toContain('Done compiling TypeScript files');
updateJson(`libs/${lib}/tsconfig.json`, (json) => { updateJson(`libs/${lib}/tsconfig.json`, (json) => {
@ -226,7 +226,7 @@ describe('js e2e', () => {
}); });
const output = runCLI(`build ${parentLib}`); const output = runCLI(`build ${parentLib}`);
expect(output).toContain('1 task(s) it depends on'); expect(output).toContain('1 task it depends on');
expect(output).toContain('Successfully compiled: 2 files with swc'); expect(output).toContain('Successfully compiled: 2 files with swc');
updateJson(`libs/${lib}/.lib.swcrc`, (json) => { updateJson(`libs/${lib}/.lib.swcrc`, (json) => {

View File

@ -398,7 +398,9 @@ export function tslibC(): string {
const fixedStout = runCLI(`lint ${libC} --fix`, { const fixedStout = runCLI(`lint ${libC} --fix`, {
silenceError: true, silenceError: true,
}); });
expect(fixedStout).toContain('Successfully ran target lint for project'); expect(fixedStout).toContain(
`Successfully ran target lint for project ${libC}`
);
const fileContent = readFile(`libs/${libC}/src/lib/tslib-c-another.ts`); const fileContent = readFile(`libs/${libC}/src/lib/tslib-c-another.ts`);
expect(fileContent).toContain(`import { tslibC } from './tslib-c';`); expect(fileContent).toContain(`import { tslibC } from './tslib-c';`);
@ -424,7 +426,9 @@ export function tslibC(): string {
const fixedStout = runCLI(`lint ${libB} --fix`, { const fixedStout = runCLI(`lint ${libB} --fix`, {
silenceError: true, silenceError: true,
}); });
expect(fixedStout).toContain('Successfully ran target lint for project'); expect(fixedStout).toContain(
`Successfully ran target lint for project ${libB}`
);
const fileContent = readFile(`libs/${libB}/src/lib/tslib-b.ts`); const fileContent = readFile(`libs/${libB}/src/lib/tslib-b.ts`);
expect(fileContent).toContain( expect(fileContent).toContain(

View File

@ -129,7 +129,7 @@ describe('Nx Affected and Graph Tests', () => {
const build = runCLI( const build = runCLI(
`affected:build --files="libs/${mylib}/src/index.ts" --parallel` `affected:build --files="libs/${mylib}/src/index.ts" --parallel`
); );
expect(build).toContain(`Running target build for 3 project(s):`); expect(build).toContain(`Running target build for 3 projects:`);
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');
@ -138,7 +138,7 @@ describe('Nx Affected and Graph Tests', () => {
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}`
); );
expect(buildExcluded).toContain(`Running target build for 2 project(s):`); expect(buildExcluded).toContain(`Running target build for 2 projects:`);
expect(buildExcluded).toContain(`- ${mypublishablelib}`); expect(buildExcluded).toContain(`- ${mypublishablelib}`);
// test // test

View File

@ -290,7 +290,7 @@ describe('Nx Running Tests', () => {
const output = runCLI(`build ${myapp}`); const output = runCLI(`build ${myapp}`);
expect(output).toContain( expect(output).toContain(
`NX Running target build for project ${myapp} and 3 task(s) it depends on` `NX Running target build for project ${myapp} and 3 tasks it depends on`
); );
expect(output).toContain(myapp); expect(output).toContain(myapp);
expect(output).toContain(mylib1); expect(output).toContain(mylib1);
@ -310,7 +310,7 @@ describe('Nx Running Tests', () => {
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) it depends on` `NX Running target build for project ${myapp} and 2 tasks it depends on`
); );
expect(output).toContain(myapp); expect(output).toContain(myapp);
expect(output).toContain(mylib1); expect(output).toContain(mylib1);
@ -347,7 +347,7 @@ describe('Nx Running Tests', () => {
const output = runCLI(`outside ${myapp}`); const output = runCLI(`outside ${myapp}`);
expect(output).toContain( expect(output).toContain(
`NX Running target outside for project ${myapp} and 1 task(s) it depends on` `NX Running target outside for project ${myapp} and 1 task it depends on`
); );
removeFile(`one.txt`); removeFile(`one.txt`);
@ -391,7 +391,7 @@ describe('Nx Running Tests', () => {
const buildParallel = runCLI( const buildParallel = runCLI(
`run-many --target=build --projects="${libC},${libB}"` `run-many --target=build --projects="${libC},${libB}"`
); );
expect(buildParallel).toContain(`Running target build for 2 project(s):`); expect(buildParallel).toContain(`Running target build for 2 projects:`);
expect(buildParallel).not.toContain(`- ${libA}`); expect(buildParallel).not.toContain(`- ${libA}`);
expect(buildParallel).toContain(`- ${libB}`); expect(buildParallel).toContain(`- ${libB}`);
expect(buildParallel).toContain(`- ${libC}`); expect(buildParallel).toContain(`- ${libC}`);
@ -401,7 +401,7 @@ describe('Nx Running Tests', () => {
// testing run many --all starting // testing run many --all starting
const buildAllParallel = runCLI(`run-many --target=build`); const buildAllParallel = runCLI(`run-many --target=build`);
expect(buildAllParallel).toContain( expect(buildAllParallel).toContain(
`Running target build for 4 project(s):` `Running target build for 4 projects:`
); );
expect(buildAllParallel).toContain(`- ${appA}`); expect(buildAllParallel).toContain(`- ${appA}`);
expect(buildAllParallel).toContain(`- ${libA}`); expect(buildAllParallel).toContain(`- ${libA}`);
@ -415,7 +415,7 @@ describe('Nx Running Tests', () => {
`run-many --target=build --projects="${libA}"` `run-many --target=build --projects="${libA}"`
); );
expect(buildWithDeps).toContain( expect(buildWithDeps).toContain(
`Running target build for 1 project(s) and 1 task(s) they depend on:` `Running target build for project ${libA} and 1 task it depends on:`
); );
expect(buildWithDeps).toContain(`- ${libA}`); expect(buildWithDeps).toContain(`- ${libA}`);
expect(buildWithDeps).toContain(`${libC}`); // build should include libC as dependency expect(buildWithDeps).toContain(`${libC}`); // build should include libC as dependency
@ -428,7 +428,7 @@ describe('Nx Running Tests', () => {
`run-many --target=build --projects="${appA},${libA}" --prod` `run-many --target=build --projects="${appA},${libA}" --prod`
); );
expect(buildConfig).toContain( expect(buildConfig).toContain(
`Running target build for 2 project(s) and 1 task(s) they depend on:` `Running target build for 2 projects and 1 task they depend on:`
); );
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`);
@ -441,6 +441,19 @@ describe('Nx Running Tests', () => {
}); });
expect(buildWithDaemon).toContain(`Successfully ran target build`); expect(buildWithDaemon).toContain(`Successfully ran target build`);
}, 1000000); }, 1000000);
it('should run multiple targets', () => {
const myapp1 = uniq('myapp');
const myapp2 = uniq('myapp');
runCLI(`generate @nrwl/web:app ${myapp1}`);
runCLI(`generate @nrwl/web:app ${myapp2}`);
let outputs = runCLI(`run-many -t build test -p ${myapp1} ${myapp2}`);
expect(outputs).toContain('Running targets build, test for 2 projects:');
outputs = runCLI(`run-many -t build test -p=${myapp1},${myapp2}`);
expect(outputs).toContain('Running targets build, test for 2 projects:');
});
}); });
describe('exec', () => { describe('exec', () => {

View File

@ -93,7 +93,7 @@ export async function affected(
break; break;
case 'print-affected': case 'print-affected':
if (nxArgs.target) { if (nxArgs.targets && nxArgs.targets.length > 0) {
await printAffected( await printAffected(
allProjectsWithTarget(projects, nxArgs), allProjectsWithTarget(projects, nxArgs),
projectGraph, projectGraph,
@ -163,7 +163,9 @@ function allProjectsWithTarget(
projects: ProjectGraphProjectNode[], projects: ProjectGraphProjectNode[],
nxArgs: NxArgs nxArgs: NxArgs
) { ) {
return projects.filter((p) => projectHasTarget(p, nxArgs.target)); return projects.filter((p) =>
nxArgs.targets.find((target) => projectHasTarget(p, target))
);
} }
function printError(e: any, verbose?: boolean) { function printError(e: any, verbose?: boolean) {

View File

@ -571,9 +571,12 @@ function withRunManyOptions(yargs: yargs.Argv): yargs.Argv {
'populate--': true, 'populate--': true,
}) })
.option('projects', { .option('projects', {
type: 'array',
string: true,
alias: 'p',
coerce: parseCSV,
describe: describe:
'Projects to run. (comma delimited project names and/or patterns)', 'Projects to run. (comma delimited project names and/or patterns)',
type: 'string',
}) })
.option('all', { .option('all', {
describe: '[deprecated] Run the target on all projects in the workspace', describe: '[deprecated] Run the target on all projects in the workspace',
@ -649,10 +652,12 @@ function withTargetAndConfigurationOption(
yargs: yargs.Argv, yargs: yargs.Argv,
demandOption = true demandOption = true
): yargs.Argv { ): yargs.Argv {
return withConfiguration(yargs).option('target', { return withConfiguration(yargs).option('targets', {
describe: 'Task to run for affected projects', describe: 'Tasks to run for affected projects',
type: 'string', type: 'array',
alias: ['target', 't'],
requiresArg: true, requiresArg: true,
coerce: parseCSV,
demandOption, demandOption,
global: false, global: false,
}); });
@ -946,6 +951,7 @@ function withWatchOptions(yargs: yargs.Argv) {
.option('projects', { .option('projects', {
type: 'array', type: 'array',
string: true, string: true,
alias: 'p',
coerce: parseCSV, coerce: parseCSV,
description: 'Projects to watch (comma delimited).', description: 'Projects to watch (comma delimited).',
}) })

View File

@ -22,7 +22,8 @@ export async function printAffected(
nxArgs.type ? p.type === nxArgs.type : true nxArgs.type ? p.type === nxArgs.type : true
); );
const projectNames = projectsForType.map((p) => p.name); const projectNames = projectsForType.map((p) => p.name);
const tasksJson = nxArgs.target const tasksJson =
nxArgs.targets && nxArgs.targets.length > 0
? await createTasks( ? await createTasks(
projectsForType, projectsForType,
projectGraph, projectGraph,
@ -53,24 +54,28 @@ async function createTasks(
const workspaces = new Workspaces(workspaceRoot); const workspaces = new Workspaces(workspaceRoot);
const hasher = new Hasher(projectGraph, nxJson, {}); const hasher = new Hasher(projectGraph, nxJson, {});
const execCommand = getPackageManagerCommand().exec; const execCommand = getPackageManagerCommand().exec;
const tasks: Task[] = affectedProjectsWithTargetAndConfig.map(
(affectedProject) => {
const p = new ProcessTasks({}, projectGraph); const p = new ProcessTasks({}, projectGraph);
const tasks = [];
for (let target of nxArgs.targets) {
for (const affectedProject of affectedProjectsWithTargetAndConfig) {
const resolvedConfiguration = p.resolveConfiguration( const resolvedConfiguration = p.resolveConfiguration(
affectedProject, affectedProject,
nxArgs.target, target,
nxArgs.configuration nxArgs.configuration
); );
return p.createTask( try {
p.getId(affectedProject.name, nxArgs.target, resolvedConfiguration), tasks.push(
p.createTask(
p.getId(affectedProject.name, target, resolvedConfiguration),
affectedProject, affectedProject,
nxArgs.target, target,
resolvedConfiguration, resolvedConfiguration,
overrides overrides
)
); );
} catch (e) {}
}
} }
);
await Promise.all( await Promise.all(
tasks.map((t) => hashTask(workspaces, hasher, projectGraph, {} as any, t)) tasks.map((t) => hashTask(workspaces, hasher, projectGraph, {} as any, t))

View File

@ -38,7 +38,7 @@ describe('run-many', () => {
const projects = projectsToRun( const projects = projectsToRun(
{ {
all: true, all: true,
target: 'test', targets: ['test'],
projects: [], projects: [],
}, },
projectGraph projectGraph
@ -50,7 +50,7 @@ describe('run-many', () => {
it('should select a project with a target', () => { it('should select a project with a target', () => {
const projects = projectsToRun( const projects = projectsToRun(
{ {
target: 'test', targets: ['test'],
projects: ['proj1'], projects: ['proj1'],
}, },
projectGraph projectGraph
@ -62,7 +62,7 @@ describe('run-many', () => {
it('should filter projects with a pattern', () => { it('should filter projects with a pattern', () => {
const projects = projectsToRun( const projects = projectsToRun(
{ {
target: 'test', targets: ['test'],
projects: ['proj*'], projects: ['proj*'],
}, },
projectGraph projectGraph
@ -75,7 +75,7 @@ describe('run-many', () => {
expect(() => { expect(() => {
projectsToRun( projectsToRun(
{ {
target: 'test', targets: ['test'],
projects: ['nomatch*'], projects: ['nomatch*'],
}, },
projectGraph projectGraph
@ -87,7 +87,7 @@ describe('run-many', () => {
const projects = projectsToRun( const projects = projectsToRun(
{ {
all: true, all: true,
target: 'test', targets: ['test'],
projects: [], projects: [],
exclude: ['proj1'], exclude: ['proj1'],
}, },
@ -101,7 +101,7 @@ describe('run-many', () => {
const projects = projectsToRun( const projects = projectsToRun(
{ {
all: true, all: true,
target: 'test', targets: ['test'],
projects: [], projects: [],
exclude: ['proj*'], exclude: ['proj*'],
}, },
@ -131,7 +131,7 @@ describe('run-many', () => {
performance.mark('start'); performance.mark('start');
projectsToRun( projectsToRun(
{ {
target: 'test', targets: ['test'],
projects: ['proj1*'], projects: ['proj1*'],
exclude: ['proj12*'], exclude: ['proj12*'],
}, },

View File

@ -56,7 +56,7 @@ export function projectsToRun(
projectGraph: ProjectGraph projectGraph: ProjectGraph
): ProjectGraphProjectNode[] { ): ProjectGraphProjectNode[] {
const selectedProjects = new Map<string, ProjectGraphProjectNode>(); const selectedProjects = new Map<string, ProjectGraphProjectNode>();
const validProjects = runnableForTarget(projectGraph.nodes, nxArgs.target); const validProjects = runnableForTarget(projectGraph.nodes, nxArgs.targets);
const validProjectNames = Array.from(validProjects.keys()); const validProjectNames = Array.from(validProjects.keys());
const invalidProjects: string[] = []; const invalidProjects: string[] = [];
@ -122,12 +122,12 @@ export function projectsToRun(
function runnableForTarget( function runnableForTarget(
projects: Record<string, ProjectGraphProjectNode>, projects: Record<string, ProjectGraphProjectNode>,
target: string targets: string[]
): Set<string> { ): Set<string> {
const runnable = new Set<string>(); const runnable = new Set<string>();
for (let projectName in projects) { for (let projectName in projects) {
const project = projects[projectName]; const project = projects[projectName];
if (projectHasTarget(project, target)) { if (targets.find((target) => projectHasTarget(project, target))) {
runnable.add(projectName); runnable.add(projectName);
} }
} }

View File

@ -44,7 +44,7 @@ export async function runOne(
{ {
...opts.parsedArgs, ...opts.parsedArgs,
configuration: opts.configuration, configuration: opts.configuration,
target: opts.target, targets: [opts.target],
}, },
'run-one', 'run-one',
{ printWarnings: true }, { printWarnings: true },

View File

@ -31,6 +31,10 @@ export const getImplicitlyTouchedProjects: TouchedProjectLocator = (
const globalFiles = [ const globalFiles = [
...extractGlobalFilesFromInputs(nxJson, projectGraphNodes), ...extractGlobalFilesFromInputs(nxJson, projectGraphNodes),
'nx.json', 'nx.json',
'package-lock.json',
'yarn.lock',
'pnpm-lock.yaml',
'pnpm-lock.yml',
]; ];
globalFiles.forEach((file) => { globalFiles.forEach((file) => {
implicits[file] = '*' as any; implicits[file] = '*' as any;

View File

@ -412,6 +412,63 @@ describe('createTaskGraph', () => {
}); });
}); });
it('should correctly set dependencies when they are all given as inputs', () => {
const taskGraph = createTaskGraph(
projectGraph,
{},
['app1', 'lib1'],
['build', 'prebuild'],
'development',
{
__overrides_unparsed__: [],
}
);
// prebuild should also be in here
expect(taskGraph).toEqual({
roots: ['app1:prebuild', 'lib1:build'],
tasks: {
'app1:build': {
id: 'app1:build',
target: {
project: 'app1',
target: 'build',
},
overrides: {
__overrides_unparsed__: [],
},
projectRoot: 'app1-root',
},
'app1:prebuild': {
id: 'app1:prebuild',
target: {
project: 'app1',
target: 'prebuild',
},
overrides: {
__overrides_unparsed__: [],
},
projectRoot: 'app1-root',
},
'lib1:build': {
id: 'lib1:build',
target: {
project: 'lib1',
target: 'build',
},
overrides: {
__overrides_unparsed__: [],
},
projectRoot: 'lib1-root',
},
},
dependencies: {
'app1:build': ['lib1:build', 'app1:prebuild'],
'app1:prebuild': [],
'lib1:build': [],
},
});
});
it('should handle diamond shape dependencies', () => { it('should handle diamond shape dependencies', () => {
projectGraph = { projectGraph = {
nodes: { nodes: {

View File

@ -26,15 +26,17 @@ export class ProcessTasks {
) { ) {
for (const projectName of projectNames) { for (const projectName of projectNames) {
for (const target of targets) { for (const target of targets) {
const project = this.projectGraph.nodes[projectName];
if (targets.length === 1 || project.data.targets[target]) {
const resolvedConfiguration = this.resolveConfiguration( const resolvedConfiguration = this.resolveConfiguration(
this.projectGraph.nodes[projectName], project,
target, target,
configuration configuration
); );
const id = this.getId(projectName, target, resolvedConfiguration); const id = this.getId(projectName, target, resolvedConfiguration);
const task = this.createTask( const task = this.createTask(
id, id,
this.projectGraph.nodes[projectName], project,
target, target,
resolvedConfiguration, resolvedConfiguration,
overrides overrides
@ -43,6 +45,7 @@ export class ProcessTasks {
this.dependencies[task.id] = []; this.dependencies[task.id] = [];
} }
} }
}
// used when excluding tasks // used when excluding tasks
const initialTasks = { ...this.tasks }; const initialTasks = { ...this.tasks };

View File

@ -7,7 +7,7 @@ import type { LifeCycle } from '../life-cycle';
import type { TaskStatus } from '../tasks-runner'; import type { TaskStatus } from '../tasks-runner';
import { Task } from '../../config/task-graph'; import { Task } from '../../config/task-graph';
import { prettyTime } from './pretty-time'; import { prettyTime } from './pretty-time';
import { formatFlags } from './formatting-utils'; import { formatFlags, formatTargetsAndProjects } from './formatting-utils';
import { viewLogsFooterRows } from './view-logs-utils'; import { viewLogsFooterRows } from './view-logs-utils';
/** /**
@ -28,7 +28,7 @@ export async function createRunManyDynamicOutputRenderer({
}: { }: {
projectNames: string[]; projectNames: string[];
tasks: Task[]; tasks: Task[];
args: { target?: string; configuration?: string; parallel?: number }; args: { targets?: string[]; configuration?: string; parallel?: number };
overrides: Record<string, unknown>; overrides: Record<string, unknown>;
}): Promise<{ lifeCycle: LifeCycle; renderIsDone: Promise<void> }> { }): Promise<{ lifeCycle: LifeCycle; renderIsDone: Promise<void> }> {
cliCursor.hide(); cliCursor.hide();
@ -41,8 +41,8 @@ export async function createRunManyDynamicOutputRenderer({
}); });
function clearRenderInterval() { function clearRenderInterval() {
if (renderProjectRowsIntervalId) { if (renderIntervalId) {
clearInterval(renderProjectRowsIntervalId); clearInterval(renderIntervalId);
} }
} }
@ -57,14 +57,11 @@ export async function createRunManyDynamicOutputRenderer({
const start = process.hrtime(); const start = process.hrtime();
const figures = await import('figures'); const figures = await import('figures');
const targets = args.targets;
const totalTasks = tasks.length; const totalTasks = tasks.length;
const totalProjects = projectNames.length; const taskRows = tasks.map((task) => {
const totalDependentTasks = totalTasks - totalProjects;
const targetName = args.target;
const configuration = args.configuration;
const projectRows = projectNames.map((projectName) => {
return { return {
projectName, task,
status: 'pending', status: 'pending',
}; };
}); });
@ -83,8 +80,8 @@ export async function createRunManyDynamicOutputRenderer({
let totalCachedTasks = 0; let totalCachedTasks = 0;
// Used to control the rendering of the spinner on each project row // Used to control the rendering of the spinner on each project row
let projectRowsCurrentFrame = 0; let currentFrame = 0;
let renderProjectRowsIntervalId: NodeJS.Timeout | undefined; let renderIntervalId: NodeJS.Timeout | undefined;
const clearPinnedFooter = () => { const clearPinnedFooter = () => {
for (let i = 0; i < pinnedFooterNumLines; i++) { for (let i = 0; i < pinnedFooterNumLines; i++) {
@ -180,16 +177,16 @@ export async function createRunManyDynamicOutputRenderer({
delete tasksToTerminalOutputs[task.id]; delete tasksToTerminalOutputs[task.id];
renderPinnedFooter([]); renderPinnedFooter([]);
renderProjectRows(); renderRows();
}; };
const renderProjectRows = () => { const renderRows = () => {
const max = dots.frames.length - 1; const max = dots.frames.length - 1;
const curr = projectRowsCurrentFrame; const curr = currentFrame;
projectRowsCurrentFrame = curr >= max ? 0 : curr + 1; currentFrame = curr >= max ? 0 : curr + 1;
const additionalFooterRows: string[] = ['']; const additionalFooterRows: string[] = [''];
const runningTasks = projectRows.filter((row) => row.status === 'running'); const runningTasks = taskRows.filter((row) => row.status === 'running');
const remainingTasks = totalTasks - totalCompletedTasks; const remainingTasks = totalTasks - totalCompletedTasks;
if (runningTasks.length > 0) { if (runningTasks.length > 0) {
@ -203,16 +200,11 @@ export async function createRunManyDynamicOutputRenderer({
) )
); );
additionalFooterRows.push(''); additionalFooterRows.push('');
for (const projectRow of runningTasks) { for (const runningTask of runningTasks) {
additionalFooterRows.push( additionalFooterRows.push(
` ${output.dim.cyan( ` ${output.dim.cyan(
dots.frames[projectRowsCurrentFrame] dots.frames[currentFrame]
)} ${output.formatCommand( )} ${output.formatCommand(runningTask.task.id)}`
projectRow.projectName +
':' +
targetName +
(configuration ? ':' + configuration : '')
)}`
); );
} }
/** /**
@ -261,15 +253,11 @@ export async function createRunManyDynamicOutputRenderer({
clearPinnedFooter(); clearPinnedFooter();
if (additionalFooterRows.length > 1) { if (additionalFooterRows.length > 1) {
let text = `Running target ${output.bold.cyan( const text = `Running target ${formatTargetsAndProjects(
targetName projectNames,
)} for ${output.bold.cyan(totalProjects)} projects`; targets,
if (totalDependentTasks > 0) { tasks
text += ` and ${output.bold( )}`;
totalDependentTasks
)} task(s) they depend on`;
}
const taskOverridesRows = []; const taskOverridesRows = [];
if (Object.keys(overrides).length > 0) { if (Object.keys(overrides).length > 0) {
const leftPadding = `${output.X_PADDING} `; const leftPadding = `${output.X_PADDING} `;
@ -302,14 +290,17 @@ export async function createRunManyDynamicOutputRenderer({
}; };
lifeCycle.startCommand = () => { lifeCycle.startCommand = () => {
if (totalProjects <= 0) { if (projectNames.length <= 0) {
let description = `with target ${output.colors.white.bold(targetName)}`;
if (args.configuration) {
description += ` that are configured for "${args.configuration}"`;
}
renderPinnedFooter([ renderPinnedFooter([
'', '',
output.applyNxPrefix('gray', `No projects ${description} were run`), output.applyNxPrefix(
'gray',
`No projects with ${formatTargetsAndProjects(
projectNames,
targets,
tasks
)} were run`
),
]); ]);
resolveRenderIsDonePromise(); resolveRenderIsDonePromise();
return; return;
@ -323,15 +314,11 @@ export async function createRunManyDynamicOutputRenderer({
clearPinnedFooter(); clearPinnedFooter();
if (totalSuccessfulTasks === totalTasks) { if (totalSuccessfulTasks === totalTasks) {
let text = `Successfully ran target ${output.bold( const text = `Successfully ran ${formatTargetsAndProjects(
targetName projectNames,
)} for ${output.bold(totalProjects)} projects`; targets,
if (totalDependentTasks > 0) { tasks
text += ` and ${output.bold( )}`;
totalDependentTasks
)} task(s) they depend on`;
}
const taskOverridesRows = []; const taskOverridesRows = [];
if (Object.keys(overrides).length > 0) { if (Object.keys(overrides).length > 0) {
const leftPadding = `${output.X_PADDING} `; const leftPadding = `${output.X_PADDING} `;
@ -362,15 +349,11 @@ export async function createRunManyDynamicOutputRenderer({
} }
renderPinnedFooter(pinnedFooterLines, 'green'); renderPinnedFooter(pinnedFooterLines, 'green');
} else { } else {
let text = `Ran target ${output.bold(targetName)} for ${output.bold( const text = `Ran ${formatTargetsAndProjects(
totalProjects projectNames,
)} projects`; targets,
if (totalDependentTasks > 0) { tasks
text += ` and ${output.bold( )}`;
totalDependentTasks
)} task(s) they depend on`;
}
const taskOverridesRows = []; const taskOverridesRows = [];
if (Object.keys(overrides).length > 0) { if (Object.keys(overrides).length > 0) {
const leftPadding = `${output.X_PADDING} `; const leftPadding = `${output.X_PADDING} `;
@ -437,17 +420,13 @@ export async function createRunManyDynamicOutputRenderer({
for (const task of tasks) { for (const task of tasks) {
tasksToProcessStartTimes[task.id] = process.hrtime(); tasksToProcessStartTimes[task.id] = process.hrtime();
} }
for (const projectRow of projectRows) { for (const taskRow of taskRows) {
const matchedTask = tasks.find( if (tasks.indexOf(taskRow.task) > -1) {
(t) => t.target.project === projectRow.projectName taskRow.status = 'running';
);
if (!matchedTask) {
continue;
} }
projectRow.status = 'running';
} }
if (!renderProjectRowsIntervalId) { if (!renderIntervalId) {
renderProjectRowsIntervalId = setInterval(renderProjectRows, 100); renderIntervalId = setInterval(renderRows, 100);
} }
}; };
@ -458,11 +437,9 @@ export async function createRunManyDynamicOutputRenderer({
lifeCycle.endTasks = (taskResults) => { lifeCycle.endTasks = (taskResults) => {
for (let t of taskResults) { for (let t of taskResults) {
totalCompletedTasks++; totalCompletedTasks++;
const matchingProjectRow = projectRows.find( const matchingTaskRow = taskRows.find((r) => r.task === t.task);
(pr) => pr.projectName === t.task.target.project if (matchingTaskRow) {
); matchingTaskRow.status = t.status;
if (matchingProjectRow) {
matchingProjectRow.status = t.status;
} }
switch (t.status) { switch (t.status) {

View File

@ -6,7 +6,7 @@ import { output } from '../../utils/output';
import type { LifeCycle } from '../life-cycle'; import type { LifeCycle } from '../life-cycle';
import { prettyTime } from './pretty-time'; import { prettyTime } from './pretty-time';
import { Task } from '../../config/task-graph'; import { Task } from '../../config/task-graph';
import { formatFlags } from './formatting-utils'; import { formatFlags, formatTargetsAndProjects } from './formatting-utils';
import { viewLogsFooterRows } from './view-logs-utils'; import { viewLogsFooterRows } from './view-logs-utils';
/** /**
@ -272,14 +272,11 @@ export async function createRunOneDynamicOutputRenderer({
if (totalSuccessfulTasks === totalTasks) { if (totalSuccessfulTasks === totalTasks) {
state = 'COMPLETED_SUCCESSFULLY'; state = 'COMPLETED_SUCCESSFULLY';
let text = `Successfully ran target ${output.bold( const text = `Successfully ran ${formatTargetsAndProjects(
targetName [initiatingProject],
)} for project ${output.bold(initiatingProject)}`; [tasks[0].target.target],
if (totalDependentTasks > 0) { tasks
text += ` and ${output.bold( )}`;
totalDependentTasks
)} task(s) it depends on`;
}
const taskOverridesLines = []; const taskOverridesLines = [];
if (Object.keys(overrides).length > 0) { if (Object.keys(overrides).length > 0) {
@ -319,7 +316,7 @@ export async function createRunOneDynamicOutputRenderer({
if (totalDependentTasks > 0) { if (totalDependentTasks > 0) {
text += ` and ${output.bold( text += ` and ${output.bold(
totalDependentTasks totalDependentTasks
)} task(s) it depends on`; )} task(s) they depend on`;
} }
const taskOverridesLines = []; const taskOverridesLines = [];

View File

@ -1,3 +1,6 @@
import { Task } from '../../config/task-graph';
import { output } from '../../utils/output';
export function formatFlags( export function formatFlags(
leftPadding: string, leftPadding: string,
flag: string, flag: string,
@ -17,3 +20,38 @@ function formatValue(value: any) {
return value; return value;
} }
} }
export function formatTargetsAndProjects(
projectNames: string[],
targets: string[],
tasks: Task[]
) {
if (tasks.length === 1)
return `target ${targets[0]} for project ${projectNames[0]}`;
let text;
const project =
projectNames.length === 1
? `project ${projectNames[0]}`
: `${projectNames.length} projects`;
if (targets.length === 1) {
text = `target ${output.bold(targets[0])} for ${project}`;
} else {
text = `targets ${targets
.map((t) => output.bold(t))
.join(', ')} for ${project}`;
}
const dependentTasks = tasks.filter(
(t) =>
projectNames.indexOf(t.target.project) === -1 ||
targets.indexOf(t.target.target) === -1
).length;
if (dependentTasks > 0) {
text += ` and ${output.bold(dependentTasks)} ${
dependentTasks === 1 ? 'task' : 'tasks'
} ${projectNames.length === 1 ? 'it depends on' : 'they depend on'}`;
}
return text;
}

View File

@ -3,7 +3,7 @@ import { TaskStatus } from '../tasks-runner';
import { getPrintableCommandArgsForTask } from '../utils'; import { getPrintableCommandArgsForTask } from '../utils';
import type { LifeCycle } from '../life-cycle'; import type { LifeCycle } from '../life-cycle';
import { Task } from '../../config/task-graph'; import { Task } from '../../config/task-graph';
import { formatFlags } from './formatting-utils'; import { formatFlags, formatTargetsAndProjects } from './formatting-utils';
/** /**
* The following life cycle's outputs are static, meaning no previous content * The following life cycle's outputs are static, meaning no previous content
@ -22,7 +22,7 @@ export class StaticRunManyTerminalOutputLifeCycle implements LifeCycle {
private readonly projectNames: string[], private readonly projectNames: string[],
private readonly tasks: Task[], private readonly tasks: Task[],
private readonly args: { private readonly args: {
target?: string; targets?: string[];
configuration?: string; configuration?: string;
}, },
private readonly taskOverrides: any private readonly taskOverrides: any
@ -30,11 +30,13 @@ export class StaticRunManyTerminalOutputLifeCycle implements LifeCycle {
startCommand(): void { startCommand(): void {
if (this.projectNames.length <= 0) { if (this.projectNames.length <= 0) {
let description = `with "${this.args.target}"`; output.logSingleLine(
if (this.args.configuration) { `No projects with ${formatTargetsAndProjects(
description += ` that are configured for "${this.args.configuration}"`; this.projectNames,
} this.args.targets,
output.logSingleLine(`No projects ${description} were run`); this.tasks
)} were run`
);
return; return;
} }
@ -49,16 +51,11 @@ export class StaticRunManyTerminalOutputLifeCycle implements LifeCycle {
.forEach((arg) => bodyLines.push(arg)); .forEach((arg) => bodyLines.push(arg));
} }
let title = `Running target ${output.bold( const title = `Running ${formatTargetsAndProjects(
this.args.target this.projectNames,
)} for ${output.bold(this.projectNames.length)} project(s)`; this.args.targets,
const dependentTasksCount = this.tasks.length - this.projectNames.length; this.tasks
if (dependentTasksCount > 0) { )}:`;
title += ` and ${output.bold(
dependentTasksCount
)} task(s) they depend on`;
}
title += ':';
output.log({ output.log({
color: 'cyan', color: 'cyan',
@ -85,9 +82,11 @@ export class StaticRunManyTerminalOutputLifeCycle implements LifeCycle {
: []; : [];
output.success({ output.success({
title: `Successfully ran target ${output.bold( title: `Successfully ran ${formatTargetsAndProjects(
this.args.target this.projectNames,
)} for ${output.bold(this.projectNames.length)} projects`, this.args.targets,
this.tasks
)}`,
bodyLines, bodyLines,
}); });
} else { } else {
@ -113,7 +112,11 @@ export class StaticRunManyTerminalOutputLifeCycle implements LifeCycle {
) )
); );
output.error({ output.error({
title: `Running target "${this.args.target}" failed`, title: `Running ${formatTargetsAndProjects(
this.projectNames,
this.args.targets,
this.tasks
)} failed`,
bodyLines, bodyLines,
}); });
} }

View File

@ -3,6 +3,7 @@ import { TaskStatus } from '../tasks-runner';
import { getPrintableCommandArgsForTask } from '../utils'; import { getPrintableCommandArgsForTask } from '../utils';
import type { LifeCycle } from '../life-cycle'; import type { LifeCycle } from '../life-cycle';
import { Task } from '../../config/task-graph'; import { Task } from '../../config/task-graph';
import { formatTargetsAndProjects } from './formatting-utils';
/** /**
* The following life cycle's outputs are static, meaning no previous content * The following life cycle's outputs are static, meaning no previous content
@ -21,22 +22,22 @@ export class StaticRunOneTerminalOutputLifeCycle implements LifeCycle {
private readonly projectNames: string[], private readonly projectNames: string[],
private readonly tasks: Task[], private readonly tasks: Task[],
private readonly args: { private readonly args: {
target?: string; targets?: string[];
configuration?: string; configuration?: string;
} }
) {} ) {}
startCommand(): void { startCommand(): void {
const numberOfDeps = this.tasks.length - 1; const numberOfDeps = this.tasks.length - 1;
const title = `Running ${formatTargetsAndProjects(
this.projectNames,
this.args.targets,
this.tasks
)}:`;
if (numberOfDeps > 0) { if (numberOfDeps > 0) {
output.log({ output.log({
color: 'cyan', color: 'cyan',
title: `Running target ${output.bold( title,
this.args.target
)} for project ${output.bold(this.initiatingProject)} and ${output.bold(
numberOfDeps
)} task(s) it depends on`,
}); });
output.addVerticalSeparatorWithoutNewLines('cyan'); output.addVerticalSeparatorWithoutNewLines('cyan');
} }
@ -58,9 +59,11 @@ export class StaticRunOneTerminalOutputLifeCycle implements LifeCycle {
: []; : [];
output.success({ output.success({
title: `Successfully ran target ${output.bold( title: `Successfully ran ${formatTargetsAndProjects(
this.args.target this.projectNames,
)} for project ${output.bold(this.initiatingProject)}`, this.args.targets,
this.tasks
)}`,
bodyLines, bodyLines,
}); });
} else { } else {
@ -76,7 +79,11 @@ export class StaticRunOneTerminalOutputLifeCycle implements LifeCycle {
)}`, )}`,
]; ];
output.error({ output.error({
title: `Running target "${this.initiatingProject}:${this.args.target}" failed`, title: `Running ${formatTargetsAndProjects(
this.projectNames,
this.args.targets,
this.tasks
)} failed`,
bodyLines, bodyLines,
}); });
} }

View File

@ -129,7 +129,7 @@ export async function runCommand(
projectGraph, projectGraph,
defaultDependencyConfigs, defaultDependencyConfigs,
projectNames, projectNames,
[nxArgs.target], nxArgs.targets,
nxArgs.configuration, nxArgs.configuration,
overrides, overrides,
extraOptions.excludeTaskDependencies extraOptions.excludeTaskDependencies
@ -199,7 +199,6 @@ export async function runCommand(
{ {
initiatingProject: initiatingProject:
nxArgs.outputStyle === 'compact' ? null : initiatingProject, nxArgs.outputStyle === 'compact' ? null : initiatingProject,
target: nxArgs.target,
projectGraph, projectGraph,
nxJson, nxJson,
nxArgs, nxArgs,

View File

@ -12,6 +12,7 @@ export interface RawNxArgs extends NxArgs {
export interface NxArgs { export interface NxArgs {
target?: string; target?: string;
targets?: string[];
configuration?: string; configuration?: string;
runner?: string; runner?: string;
parallel?: number; parallel?: number;
@ -44,6 +45,13 @@ export function splitArgsIntoNxArgsAndOverrides(
nxArgs: NxArgs; nxArgs: NxArgs;
overrides: Arguments & { __overrides_unparsed__: string[] }; overrides: Arguments & { __overrides_unparsed__: string[] };
} { } {
// this is to lerna case when this function is invoked imperatively
if (args['target'] && !args['targets']) {
args['targets'] = [args['target']];
}
delete args['target'];
delete args['t'];
if (!args.__overrides_unparsed__ && args._) { if (!args.__overrides_unparsed__ && args._) {
// required for backwards compatibility // required for backwards compatibility
args.__overrides_unparsed__ = args._; args.__overrides_unparsed__ = args._;
@ -75,10 +83,6 @@ export function splitArgsIntoNxArgsAndOverrides(
if (mode === 'run-many') { if (mode === 'run-many') {
if (!nxArgs.projects) { if (!nxArgs.projects) {
nxArgs.projects = []; nxArgs.projects = [];
} else {
nxArgs.projects = (args.projects as string)
.split(',')
.map((p: string) => p.trim());
} }
} }