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
### target
### targets
Type: `string`
Type: `array`
Task to run for affected projects
Tasks to run for affected projects
### uncommitted

View File

@ -81,11 +81,11 @@ Default: `false`
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

View File

@ -99,11 +99,11 @@ Type: `string`
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

View File

@ -121,11 +121,11 @@ Default: `false`
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

View File

@ -149,11 +149,11 @@ Default: `false`
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

View File

@ -81,11 +81,11 @@ Default: `false`
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

View File

@ -99,11 +99,11 @@ Type: `string`
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

View File

@ -121,11 +121,11 @@ Default: `false`
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

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.
```

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:

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.
```

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.
```

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:

View File

@ -140,7 +140,7 @@ describe('js e2e', () => {
});
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');
updateJson(`libs/${lib}/tsconfig.json`, (json) => {
@ -226,7 +226,7 @@ describe('js e2e', () => {
});
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');
updateJson(`libs/${lib}/.lib.swcrc`, (json) => {

View File

@ -398,7 +398,9 @@ export function tslibC(): string {
const fixedStout = runCLI(`lint ${libC} --fix`, {
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`);
expect(fileContent).toContain(`import { tslibC } from './tslib-c';`);
@ -424,7 +426,9 @@ export function tslibC(): string {
const fixedStout = runCLI(`lint ${libB} --fix`, {
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`);
expect(fileContent).toContain(

View File

@ -129,7 +129,7 @@ describe('Nx Affected and Graph Tests', () => {
const build = runCLI(
`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(`- ${mypublishablelib}`);
expect(build).not.toContain('is not registered with the build command');
@ -138,7 +138,7 @@ describe('Nx Affected and Graph Tests', () => {
const buildExcluded = runCLI(
`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}`);
// test

View File

@ -290,7 +290,7 @@ describe('Nx Running Tests', () => {
const output = runCLI(`build ${myapp}`);
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(mylib1);
@ -310,7 +310,7 @@ describe('Nx Running Tests', () => {
const output = runCLI(`build ${myapp}`);
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(mylib1);
@ -347,7 +347,7 @@ describe('Nx Running Tests', () => {
const output = runCLI(`outside ${myapp}`);
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`);
@ -391,7 +391,7 @@ describe('Nx Running Tests', () => {
const buildParallel = runCLI(
`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).toContain(`- ${libB}`);
expect(buildParallel).toContain(`- ${libC}`);
@ -401,7 +401,7 @@ describe('Nx Running Tests', () => {
// testing run many --all starting
const buildAllParallel = runCLI(`run-many --target=build`);
expect(buildAllParallel).toContain(
`Running target build for 4 project(s):`
`Running target build for 4 projects:`
);
expect(buildAllParallel).toContain(`- ${appA}`);
expect(buildAllParallel).toContain(`- ${libA}`);
@ -415,7 +415,7 @@ describe('Nx Running Tests', () => {
`run-many --target=build --projects="${libA}"`
);
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(`${libC}`); // build should include libC as dependency
@ -428,7 +428,7 @@ describe('Nx Running Tests', () => {
`run-many --target=build --projects="${appA},${libA}" --prod`
);
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 ${libA}:build`);
@ -441,6 +441,19 @@ describe('Nx Running Tests', () => {
});
expect(buildWithDaemon).toContain(`Successfully ran target build`);
}, 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', () => {

View File

@ -93,7 +93,7 @@ export async function affected(
break;
case 'print-affected':
if (nxArgs.target) {
if (nxArgs.targets && nxArgs.targets.length > 0) {
await printAffected(
allProjectsWithTarget(projects, nxArgs),
projectGraph,
@ -163,7 +163,9 @@ function allProjectsWithTarget(
projects: ProjectGraphProjectNode[],
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) {

View File

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

View File

@ -22,15 +22,16 @@ export async function printAffected(
nxArgs.type ? p.type === nxArgs.type : true
);
const projectNames = projectsForType.map((p) => p.name);
const tasksJson = nxArgs.target
? await createTasks(
projectsForType,
projectGraph,
nxArgs,
nxJson,
overrides
)
: [];
const tasksJson =
nxArgs.targets && nxArgs.targets.length > 0
? await createTasks(
projectsForType,
projectGraph,
nxArgs,
nxJson,
overrides
)
: [];
const result = {
tasks: tasksJson,
projects: projectNames,
@ -53,24 +54,28 @@ async function createTasks(
const workspaces = new Workspaces(workspaceRoot);
const hasher = new Hasher(projectGraph, nxJson, {});
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(
affectedProject,
nxArgs.target,
target,
nxArgs.configuration
);
return p.createTask(
p.getId(affectedProject.name, nxArgs.target, resolvedConfiguration),
affectedProject,
nxArgs.target,
resolvedConfiguration,
overrides
);
try {
tasks.push(
p.createTask(
p.getId(affectedProject.name, target, resolvedConfiguration),
affectedProject,
target,
resolvedConfiguration,
overrides
)
);
} catch (e) {}
}
);
}
await Promise.all(
tasks.map((t) => hashTask(workspaces, hasher, projectGraph, {} as any, t))

View File

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

View File

@ -56,7 +56,7 @@ export function projectsToRun(
projectGraph: ProjectGraph
): 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 invalidProjects: string[] = [];
@ -122,12 +122,12 @@ export function projectsToRun(
function runnableForTarget(
projects: Record<string, ProjectGraphProjectNode>,
target: string
targets: string[]
): Set<string> {
const runnable = new Set<string>();
for (let projectName in projects) {
const project = projects[projectName];
if (projectHasTarget(project, target)) {
if (targets.find((target) => projectHasTarget(project, target))) {
runnable.add(projectName);
}
}

View File

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

View File

@ -31,6 +31,10 @@ export const getImplicitlyTouchedProjects: TouchedProjectLocator = (
const globalFiles = [
...extractGlobalFilesFromInputs(nxJson, projectGraphNodes),
'nx.json',
'package-lock.json',
'yarn.lock',
'pnpm-lock.yaml',
'pnpm-lock.yml',
];
globalFiles.forEach((file) => {
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', () => {
projectGraph = {
nodes: {

View File

@ -26,21 +26,24 @@ export class ProcessTasks {
) {
for (const projectName of projectNames) {
for (const target of targets) {
const resolvedConfiguration = this.resolveConfiguration(
this.projectGraph.nodes[projectName],
target,
configuration
);
const id = this.getId(projectName, target, resolvedConfiguration);
const task = this.createTask(
id,
this.projectGraph.nodes[projectName],
target,
resolvedConfiguration,
overrides
);
this.tasks[task.id] = task;
this.dependencies[task.id] = [];
const project = this.projectGraph.nodes[projectName];
if (targets.length === 1 || project.data.targets[target]) {
const resolvedConfiguration = this.resolveConfiguration(
project,
target,
configuration
);
const id = this.getId(projectName, target, resolvedConfiguration);
const task = this.createTask(
id,
project,
target,
resolvedConfiguration,
overrides
);
this.tasks[task.id] = task;
this.dependencies[task.id] = [];
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,6 +12,7 @@ export interface RawNxArgs extends NxArgs {
export interface NxArgs {
target?: string;
targets?: string[];
configuration?: string;
runner?: string;
parallel?: number;
@ -44,6 +45,13 @@ export function splitArgsIntoNxArgsAndOverrides(
nxArgs: NxArgs;
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._) {
// required for backwards compatibility
args.__overrides_unparsed__ = args._;
@ -75,10 +83,6 @@ export function splitArgsIntoNxArgsAndOverrides(
if (mode === 'run-many') {
if (!nxArgs.projects) {
nxArgs.projects = [];
} else {
nxArgs.projects = (args.projects as string)
.split(',')
.map((p: string) => p.trim());
}
}