diff --git a/e2e/nx-run/src/affected-graph.test.ts b/e2e/nx-run/src/affected-graph.test.ts index e8db5114051354..260af42c12239d 100644 --- a/e2e/nx-run/src/affected-graph.test.ts +++ b/e2e/nx-run/src/affected-graph.test.ts @@ -503,6 +503,13 @@ describe('Nx Affected and Graph Tests', () => { expect(environmentJs).toContain('"affected":[]'); }); + it('graph should output valid json when stdout is specified', () => { + const result = runCLI(`graph --out stdout`); + let model; + expect(() => (model = JSON.parse(result))).not.toThrow(); + expect(model).toHaveProperty('nodes'); + }); + it('affected:graph should include affected projects in environment file', () => { runCLI(`affected:graph --file=project-graph.html`); diff --git a/packages/nx/src/command-line/affected/affected.ts b/packages/nx/src/command-line/affected/affected.ts index 4d949b5793d512..b4b0fbb2035d04 100644 --- a/packages/nx/src/command-line/affected/affected.ts +++ b/packages/nx/src/command-line/affected/affected.ts @@ -4,7 +4,10 @@ import { output } from '../../utils/output'; import { generateGraph } from '../graph/graph'; import { printAffected } from './print-affected'; import { connectToNxCloudIfExplicitlyAsked } from '../connect/connect-to-nx-cloud'; -import type { NxArgs } from '../../utils/command-line-utils'; +import { + NxArgs, + readGraphFileFromGraphArg, +} from '../../utils/command-line-utils'; import { parseFiles, splitArgsIntoNxArgsAndOverrides, @@ -83,11 +86,8 @@ export async function affected( const projectsWithTarget = allProjectsWithTarget(projects, nxArgs); if (nxArgs.graph) { const projectNames = projectsWithTarget.map((t) => t.name); - const file = - typeof nxArgs.graph === 'string' && - (nxArgs.graph.endsWith('.json') || nxArgs.graph.endsWith('html')) - ? nxArgs.graph - : undefined; + const file = readGraphFileFromGraphArg(nxArgs); + return await generateGraph( { watch: false, diff --git a/packages/nx/src/command-line/graph/graph.ts b/packages/nx/src/command-line/graph/graph.ts index 4942f53f06f3d9..d8744626dae50f 100644 --- a/packages/nx/src/command-line/graph/graph.ts +++ b/packages/nx/src/command-line/graph/graph.ts @@ -246,6 +246,12 @@ export async function generateGraph( graph = filterGraph(graph, args.focus || null, args.exclude || []); if (args.file) { + // stdout is a magical constant that doesn't actually write a file + if (args.file === 'stdout') { + console.log(JSON.stringify(graph, null, 2)); + process.exit(0); + } + const workspaceFolder = workspaceRoot; const ext = extname(args.file); const fullFilePath = isAbsolute(args.file) diff --git a/packages/nx/src/command-line/run-many/run-many.ts b/packages/nx/src/command-line/run-many/run-many.ts index 55e06f1c8aa407..968c2430434752 100644 --- a/packages/nx/src/command-line/run-many/run-many.ts +++ b/packages/nx/src/command-line/run-many/run-many.ts @@ -1,5 +1,8 @@ import { runCommand } from '../../tasks-runner/run-command'; -import type { NxArgs } from '../../utils/command-line-utils'; +import { + NxArgs, + readGraphFileFromGraphArg, +} from '../../utils/command-line-utils'; import { splitArgsIntoNxArgsAndOverrides } from '../../utils/command-line-utils'; import { projectHasTarget } from '../../utils/project-graph-utils'; import { connectToNxCloudIfExplicitlyAsked } from '../connect/connect-to-nx-cloud'; @@ -46,11 +49,7 @@ export async function runMany( const projects = projectsToRun(nxArgs, projectGraph); if (nxArgs.graph) { - const file = - typeof nxArgs.graph === 'string' && - (nxArgs.graph.endsWith('.json') || nxArgs.graph.endsWith('html')) - ? nxArgs.graph - : undefined; + const file = readGraphFileFromGraphArg(nxArgs); const projectNames = projects.map((t) => t.name); return await generateGraph( { diff --git a/packages/nx/src/command-line/run/run-one.ts b/packages/nx/src/command-line/run/run-one.ts index cf13b8b233569b..6b4a6c6eb3fbb4 100644 --- a/packages/nx/src/command-line/run/run-one.ts +++ b/packages/nx/src/command-line/run/run-one.ts @@ -1,5 +1,8 @@ import { runCommand } from '../../tasks-runner/run-command'; -import { splitArgsIntoNxArgsAndOverrides } from '../../utils/command-line-utils'; +import { + readGraphFileFromGraphArg, + splitArgsIntoNxArgsAndOverrides, +} from '../../utils/command-line-utils'; import { connectToNxCloudIfExplicitlyAsked } from '../connect/connect-to-nx-cloud'; import { performance } from 'perf_hooks'; import { @@ -66,11 +69,7 @@ export async function runOne( if (nxArgs.graph) { const projectNames = projects.map((t) => t.name); - const file = - typeof nxArgs.graph === 'string' && - (nxArgs.graph.endsWith('.json') || nxArgs.graph.endsWith('html')) - ? nxArgs.graph - : undefined; + const file = readGraphFileFromGraphArg(nxArgs); return await generateGraph( { diff --git a/packages/nx/src/command-line/yargs-utils/shared-options.ts b/packages/nx/src/command-line/yargs-utils/shared-options.ts index 45a4046ed387a9..8256b658e992cb 100644 --- a/packages/nx/src/command-line/yargs-utils/shared-options.ts +++ b/packages/nx/src/command-line/yargs-utils/shared-options.ts @@ -33,6 +33,12 @@ export function withRunOptions(yargs: Argv): Argv { describe: 'Show the task graph of the command. Pass a file path to save the graph data instead of viewing it in the browser.', default: false, + coerce: (value) => + value === 'true' || value === true + ? true + : value === 'false' || value === false + ? false + : value, }) .option('verbose', { type: 'boolean', diff --git a/packages/nx/src/utils/command-line-utils.ts b/packages/nx/src/utils/command-line-utils.ts index 878ceb62539de7..f45c9f72648c9d 100644 --- a/packages/nx/src/utils/command-line-utils.ts +++ b/packages/nx/src/utils/command-line-utils.ts @@ -81,15 +81,6 @@ export function splitArgsIntoNxArgsAndOverrides( delete (nxArgs as any).$0; delete (nxArgs as any).__overrides_unparsed__; - if (!(nxArgs.graph === null || nxArgs.graph === undefined)) { - nxArgs.graph = - nxArgs.graph === 'true' || nxArgs.graph === true - ? true - : nxArgs.graph === 'false' || nxArgs.graph === false - ? false - : nxArgs.graph; - } - if (mode === 'run-many') { const args = nxArgs as any; if (!args.projects) { @@ -297,8 +288,6 @@ function getUncommittedFiles(): string[] { return parseGitOutput(`git diff --name-only --no-renames --relative HEAD .`); } -``; - function getUntrackedFiles(): string[] { return parseGitOutput(`git ls-files --others --exclude-standard`); } @@ -347,3 +336,9 @@ export function getProjectRoots( ): string[] { return projectNames.map((name) => nodes[name].data.root); } + +export function readGraphFileFromGraphArg({ graph }: NxArgs) { + return typeof graph === 'string' && graph !== 'true' && graph !== '' + ? graph + : undefined; +}