feat(core): add a variety of usages for nx show (#17073)

This commit is contained in:
Craigory Coppola 2023-05-23 18:07:37 -04:00 committed by GitHub
parent f95f8c4b69
commit 5ef9ea68cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 359 additions and 35 deletions

View File

@ -23,6 +23,18 @@ Show all projects in the workspace:
nx show projects
```
Show all projects with names starting with "api-". The pattern option is useful to see which projects would be selected by run-many.:
```shell
nx show projects --pattern=api-*
```
Show all projects with a serve target:
```shell
nx show projects --with-target serve
```
Show affected projects in the workspace:
```shell
@ -35,6 +47,18 @@ Show affected projects in the workspace, excluding end-to-end projects:
nx show projects --affected --exclude *-e2e
```
Show detailed information about "my-app" in a json format.:
```shell
nx show project my-app
```
Show information about "my-app" in a human readable format.:
```shell
nx show project my-app --json false
```
## Options
### help
@ -43,6 +67,12 @@ Type: `boolean`
Show help
### json
Type: `boolean`
Output JSON
### version
Type: `boolean`
@ -97,6 +127,12 @@ Type: `boolean`
Show help
##### projects
Type: `string`
Show only projects that match a given pattern.
##### uncommitted
Type: `boolean`
@ -114,3 +150,37 @@ Untracked changes
Type: `boolean`
Show version number
##### withTarget
Type: `string`
Show only projects that have a specific target
### project
Show a list of targets in the workspace.
```shell
nx show project <projectName>
```
#### Options
##### help
Type: `boolean`
Show help
##### projectName
Type: `string`
Show targets for the given project
##### version
Type: `boolean`
Show version number

View File

@ -23,6 +23,18 @@ Show all projects in the workspace:
nx show projects
```
Show all projects with names starting with "api-". The pattern option is useful to see which projects would be selected by run-many.:
```shell
nx show projects --pattern=api-*
```
Show all projects with a serve target:
```shell
nx show projects --with-target serve
```
Show affected projects in the workspace:
```shell
@ -35,6 +47,18 @@ Show affected projects in the workspace, excluding end-to-end projects:
nx show projects --affected --exclude *-e2e
```
Show detailed information about "my-app" in a json format.:
```shell
nx show project my-app
```
Show information about "my-app" in a human readable format.:
```shell
nx show project my-app --json false
```
## Options
### help
@ -43,6 +67,12 @@ Type: `boolean`
Show help
### json
Type: `boolean`
Output JSON
### version
Type: `boolean`
@ -97,6 +127,12 @@ Type: `boolean`
Show help
##### projects
Type: `string`
Show only projects that match a given pattern.
##### uncommitted
Type: `boolean`
@ -114,3 +150,37 @@ Untracked changes
Type: `boolean`
Show version number
##### withTarget
Type: `string`
Show only projects that have a specific target
### project
Show a list of targets in the workspace.
```shell
nx show project <projectName>
```
#### Options
##### help
Type: `boolean`
Show help
##### projectName
Type: `string`
Show targets for the given project
##### version
Type: `boolean`
Show version number

View File

@ -1,4 +1,4 @@
import type { NxJsonConfiguration } from '@nx/devkit';
import type { NxJsonConfiguration, ProjectConfiguration } from '@nx/devkit';
import {
cleanupProject,
createNonNxProjectDirectory,
@ -37,7 +37,7 @@ describe('Nx Commands', () => {
runCLI('show projects').replace(/.*nx show projects( --verbose)?\n/, '')
).toEqual('');
runCLI(`generate @nx/web:app ${app1}`);
runCLI(`generate @nx/web:app ${app1} --tags e2etag`);
runCLI(`generate @nx/web:app ${app2}`);
const s = runCLI('show projects').split('\n');
@ -47,6 +47,24 @@ describe('Nx Commands', () => {
expect(s).toContain(app2);
expect(s).toContain(`${app1}-e2e`);
expect(s).toContain(`${app2}-e2e`);
const withTag = JSON.parse(runCLI('show projects -p tag:e2etag --json'));
expect(withTag).toEqual([app1]);
const withTargets = JSON.parse(
runCLI('show projects --with-target e2e --json')
);
expect(withTargets).toEqual([`${app1}-e2e`, `${app2}-e2e`]);
});
it('should show detailed project info', () => {
const app = uniq('myapp');
runCLI(`generate @nx/web:app ${app}`);
const project: ProjectConfiguration = JSON.parse(
runCLI(`show project ${app}`)
);
expect(project.targets.build).toBeDefined();
expect(project.targets.lint).toBeDefined();
});
});

View File

@ -193,6 +193,5 @@
"build-storybook": {
"inputs": ["default", "^production", "{workspaceRoot}/.storybook/**/*"]
}
},
"plugins": ["@monodon/rust"]
}
}

View File

@ -345,6 +345,17 @@ export const examples: Record<string, Example[]> = {
description: 'Show all projects in the workspace',
},
{
command: 'show projects --pattern=api-*',
description:
'Show all projects with names starting with "api-". The pattern option is useful to see which projects would be selected by run-many.',
},
{
command: 'show projects --with-target serve',
description: 'Show all projects with a serve target',
},
{
command: 'show projects --affected',
description: 'Show affected projects in the workspace',
@ -355,6 +366,17 @@ export const examples: Record<string, Example[]> = {
description:
'Show affected projects in the workspace, excluding end-to-end projects',
},
{
command: 'show project my-app',
description: 'Show detailed information about "my-app" in a json format.',
},
{
command: 'show project my-app --json false',
description:
'Show information about "my-app" in a human readable format.',
},
],
watch: [
{

View File

@ -1,35 +1,56 @@
import { CommandModule } from 'yargs';
import { withAffectedOptions } from '../yargs-utils/shared-options';
import { ShowProjectOptions } from './show';
import { CommandModule, showHelp } from 'yargs';
import { parseCSV, withAffectedOptions } from '../yargs-utils/shared-options';
export const yargsShowCommand: CommandModule = {
export interface NxShowArgs {
json?: boolean;
}
export type ShowProjectsOptions = NxShowArgs & {
exclude: string;
files: string;
uncommitted: any;
untracked: any;
base: string;
head: string;
affected: boolean;
projects: string[];
withTarget: string;
};
export type ShowProjectOptions = NxShowArgs & {
projectName: string;
};
export const yargsShowCommand: CommandModule<
Record<string, unknown>,
NxShowArgs
> = {
command: 'show',
describe: 'Show information about the workspace (e.g., list of projects)',
builder: (yargs) =>
yargs
.command(showProjectsCommand)
.command(showProjectCommand)
.demandCommand()
.option('json', {
type: 'boolean',
description: 'Output JSON',
})
.example(
'$0 show projects',
'Show a list of all projects in the workspace'
)
.example(
'$0 show projects --affected',
'Show affected projects in the workspace'
)
.example(
'$0 show projects --affected --exclude *-e2e',
'Show affected projects in the workspace, excluding end-to-end projects'
'$0 show targets',
'Show a list of all targets in the workspace'
),
handler: async (args) => {
// Noop, yargs will error if not in a subcommand.
showHelp();
process.exit(1);
},
};
const showProjectsCommand: CommandModule<
Record<string, unknown>,
ShowProjectOptions
> = {
const showProjectsCommand: CommandModule<NxShowArgs, ShowProjectsOptions> = {
command: 'projects',
describe: 'Show a list of projects in the workspace',
builder: (yargs) =>
@ -38,10 +59,55 @@ const showProjectsCommand: CommandModule<
type: 'boolean',
description: 'Show only affected projects',
})
.option('projects', {
type: 'string',
alias: ['p'],
description: 'Show only projects that match a given pattern.',
coerce: parseCSV,
})
.option('withTarget', {
type: 'string',
alias: ['t'],
description: 'Show only projects that have a specific target',
})
.implies('untracked', 'affected')
.implies('uncommitted', 'affected')
.implies('files', 'affected')
.implies('base', 'affected')
.implies('head', 'affected'),
.implies('head', 'affected')
.example(
'$0 show projects --patterns "apps/*"',
'Show all projects in the apps directory'
)
.example(
'$0 show projects --patterns "shared-*"',
'Show all projects that start with "shared-"'
)
.example(
'$0 show projects --affected',
'Show affected projects in the workspace'
)
.example(
'$0 show projects --affected --exclude *-e2e',
'Show affected projects in the workspace, excluding end-to-end projects'
) as any,
handler: (args) => import('./show').then((m) => m.showProjectsHandler(args)),
};
const showProjectCommand: CommandModule<NxShowArgs, ShowProjectOptions> = {
command: 'project <projectName>',
describe: 'Show a list of targets in the workspace.',
builder: (yargs) =>
yargs
.positional('projectName', {
type: 'string',
alias: 'p',
description: 'Show targets for the given project',
})
.default('json', true)
.example(
'$0 show project my-app',
'View project information for my-app in JSON format'
),
handler: (args) => import('./show').then((m) => m.showProjectHandler(args)),
};

View File

@ -10,22 +10,20 @@ import {
} from '../../utils/command-line-utils';
import { createProjectGraphAsync } from '../../project-graph/project-graph';
import { NxJsonConfiguration } from '../../config/nx-json';
import { ProjectGraph } from '../../config/project-graph';
import {
ProjectGraph,
ProjectGraphProjectNode,
} from '../../config/project-graph';
import { findMatchingProjects } from '../../utils/find-matching-projects';
import { fileHasher } from '../../hasher/impl';
export type ShowProjectOptions = {
exclude: string;
files: string;
uncommitted: any;
untracked: any;
base: string;
head: string;
affected: boolean;
};
import {
NxShowArgs,
ShowProjectsOptions,
ShowProjectOptions,
} from './command-object';
export async function showProjectsHandler(
args: ShowProjectOptions
args: ShowProjectsOptions
): Promise<void> {
let graph = await createProjectGraphAsync();
const nxJson = readNxJson();
@ -42,6 +40,19 @@ export async function showProjectsHandler(
graph = await getAffectedGraph(nxArgs, nxJson, graph);
}
if (args.projects) {
graph.nodes = getGraphNodesMatchingPatterns(graph, args.projects);
}
if (args.withTarget) {
graph.nodes = Object.entries(graph.nodes).reduce((acc, [name, node]) => {
if (node.data.targets?.[args.withTarget]) {
acc[name] = node;
}
return acc;
}, {} as ProjectGraph['nodes']);
}
const selectedProjects = new Set(Object.keys(graph.nodes));
if (args.exclude) {
@ -51,13 +62,81 @@ export async function showProjectsHandler(
}
}
const projects = Array.from(selectedProjects).join('\n');
if (projects.length) {
console.log(projects);
if (args.json) {
console.log(JSON.stringify(Array.from(selectedProjects), null, 2));
} else {
for (const project of selectedProjects) {
console.log(project);
}
}
process.exit(0);
}
export async function showProjectHandler(
args: ShowProjectOptions
): Promise<void> {
const graph = await createProjectGraphAsync();
const node = graph.nodes[args.projectName];
if (!node) {
console.log(`Could not find project ${args.projectName}`);
process.exit(1);
}
if (args.json) {
console.log(JSON.stringify(node.data, null, 2));
} else {
const chalk = require('chalk') as typeof import('chalk');
const logIfExists = (label, key: keyof typeof node['data']) => {
if (node.data[key]) {
console.log(`${chalk.bold(label)}: ${node.data[key]}`);
}
};
logIfExists('Name', 'name');
logIfExists('Root', 'root');
logIfExists('Source Root', 'sourceRoot');
logIfExists('Tags', 'tags');
logIfExists('Implicit Dependencies', 'implicitDependencies');
const targets = Object.entries(node.data.targets ?? {});
const maxTargetNameLength = Math.max(...targets.map(([t]) => t.length));
const maxExecutorNameLength = Math.max(
...targets.map(([, t]) => t?.executor?.length ?? 0)
);
if (targets.length > 0) {
console.log(`${chalk.bold('Targets')}: `);
for (const [target, targetConfig] of targets) {
console.log(
`- ${chalk.bold((target + ':').padEnd(maxTargetNameLength + 2))} ${(
targetConfig?.executor ?? ''
).padEnd(maxExecutorNameLength + 2)} ${(() => {
const configurations = Object.keys(
targetConfig.configurations ?? {}
);
if (configurations.length) {
return chalk.dim(configurations.join(', '));
}
return '';
})()}`
);
}
}
}
process.exit(0);
}
function getGraphNodesMatchingPatterns(
graph: ProjectGraph,
patterns: string[]
): ProjectGraph['nodes'] {
const nodes: Record<string, ProjectGraphProjectNode> = {};
const matches = findMatchingProjects(patterns, graph.nodes);
for (const match of matches) {
nodes[match] = graph.nodes[match];
}
return nodes;
}
function getAffectedGraph(
nxArgs: NxArgs,
nxJson: NxJsonConfiguration<'*' | string[]>,