From 2b2d97551f0a679d8cc61ec8904637902ba32925 Mon Sep 17 00:00:00 2001 From: AgentEnder Date: Wed, 17 May 2023 18:29:03 -0400 Subject: [PATCH] feat(core): add a variety of usages for nx show --- docs/generated/cli/show.md | 70 +++++++++++ docs/generated/packages/nx/documents/show.md | 70 +++++++++++ e2e/nx-misc/src/misc.test.ts | 22 +++- nx.json | 3 +- packages/nx/src/command-line/examples.ts | 22 ++++ .../src/command-line/show/command-object.ts | 98 +++++++++++++--- packages/nx/src/command-line/show/show.ts | 109 +++++++++++++++--- 7 files changed, 359 insertions(+), 35 deletions(-) diff --git a/docs/generated/cli/show.md b/docs/generated/cli/show.md index 1d2ef98a27caf..2e2581cb8f61b 100644 --- a/docs/generated/cli/show.md +++ b/docs/generated/cli/show.md @@ -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 +``` + +#### Options + +##### help + +Type: `boolean` + +Show help + +##### projectName + +Type: `string` + +Show targets for the given project + +##### version + +Type: `boolean` + +Show version number diff --git a/docs/generated/packages/nx/documents/show.md b/docs/generated/packages/nx/documents/show.md index 1d2ef98a27caf..2e2581cb8f61b 100644 --- a/docs/generated/packages/nx/documents/show.md +++ b/docs/generated/packages/nx/documents/show.md @@ -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 +``` + +#### Options + +##### help + +Type: `boolean` + +Show help + +##### projectName + +Type: `string` + +Show targets for the given project + +##### version + +Type: `boolean` + +Show version number diff --git a/e2e/nx-misc/src/misc.test.ts b/e2e/nx-misc/src/misc.test.ts index 46e8a843f5513..a2736b1e17a38 100644 --- a/e2e/nx-misc/src/misc.test.ts +++ b/e2e/nx-misc/src/misc.test.ts @@ -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(); }); }); diff --git a/nx.json b/nx.json index 945e6e0145c10..2c7257a96e249 100644 --- a/nx.json +++ b/nx.json @@ -193,6 +193,5 @@ "build-storybook": { "inputs": ["default", "^production", "{workspaceRoot}/.storybook/**/*"] } - }, - "plugins": ["@monodon/rust"] + } } diff --git a/packages/nx/src/command-line/examples.ts b/packages/nx/src/command-line/examples.ts index e70ddbb82f2b7..e63616ab0da75 100644 --- a/packages/nx/src/command-line/examples.ts +++ b/packages/nx/src/command-line/examples.ts @@ -345,6 +345,17 @@ export const examples: Record = { 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 = { 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: [ { diff --git a/packages/nx/src/command-line/show/command-object.ts b/packages/nx/src/command-line/show/command-object.ts index 5bae3fe0df443..4a2a394c6f99b 100644 --- a/packages/nx/src/command-line/show/command-object.ts +++ b/packages/nx/src/command-line/show/command-object.ts @@ -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, + 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, - ShowProjectOptions -> = { +const showProjectsCommand: CommandModule = { 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 = { + command: 'project ', + 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)), +}; diff --git a/packages/nx/src/command-line/show/show.ts b/packages/nx/src/command-line/show/show.ts index e082c8672d116..d6789ef667c3c 100644 --- a/packages/nx/src/command-line/show/show.ts +++ b/packages/nx/src/command-line/show/show.ts @@ -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 { 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 { + 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 = {}; + 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[]>,