diff --git a/packages/nx/bin/init-local.ts b/packages/nx/bin/init-local.ts index 0d015ba4d829d..b2af6de8ef460 100644 --- a/packages/nx/bin/init-local.ts +++ b/packages/nx/bin/init-local.ts @@ -63,8 +63,7 @@ export function initLocal(workspace: WorkspaceTypeAndRoot) { commandsObject.parse(newArgs); } } else { - const newArgs = rewritePositionalArguments(process.argv); - commandsObject.parse(newArgs); + commandsObject.parse(process.argv.slice(2)); } } catch (e) { console.error(e.message); @@ -103,41 +102,6 @@ export function rewriteTargetsAndProjects(args: string[]) { return newArgs; } -function rewritePositionalArguments(args: string[]) { - const relevantPositionalArgs = []; - const rest = []; - for (let i = 2; i < args.length; i++) { - if (args[i] === '--') { - rest.push(...args.slice(i + 1)); - break; - } else if (!args[i].startsWith('-')) { - relevantPositionalArgs.push(args[i]); - if (relevantPositionalArgs.length === 2) { - rest.push(...args.slice(i + 1)); - break; - } - } else { - rest.push(args[i]); - } - } - - if (relevantPositionalArgs.length === 1) { - return [ - 'run', - `${wrapIntoQuotesIfNeeded(relevantPositionalArgs[0])}`, - ...rest, - ]; - } - - return [ - 'run', - `${relevantPositionalArgs[1]}:${wrapIntoQuotesIfNeeded( - relevantPositionalArgs[0] - )}`, - ...rest, - ]; -} - function wrapIntoQuotesIfNeeded(arg: string) { return arg.indexOf(':') > -1 ? `"${arg}"` : arg; } diff --git a/packages/nx/src/command-line/init/init-v1.ts b/packages/nx/src/command-line/init/init-v1.ts index b14f2eed0125c..4ffac5738d9d4 100644 --- a/packages/nx/src/command-line/init/init-v1.ts +++ b/packages/nx/src/command-line/init/init-v1.ts @@ -129,5 +129,5 @@ function setupDotNxInstallation(version: string) { } generateDotNxSetup(version); // invokes the wrapper, thus invoking the initial installation process - runNxSync(''); + runNxSync('--version'); } diff --git a/packages/nx/src/command-line/nx-commands.spec.ts b/packages/nx/src/command-line/nx-commands.spec.ts index ed3fa65d6179f..1caefbee0777d 100644 --- a/packages/nx/src/command-line/nx-commands.spec.ts +++ b/packages/nx/src/command-line/nx-commands.spec.ts @@ -1,9 +1,19 @@ import { commandsObject } from './nx-commands'; +import * as yargsParser from 'yargs-parser'; + describe('nx-commands', () => { it('should parse dot notion cli args', () => { - const actual = commandsObject.parse( - 'nx e2e project-e2e --env.NX_API_URL=http://localhost:4200 --abc.123.xyx=false --a.b=3' + const actual = yargsParser( + [ + 'nx', + 'e2e', + 'project-e2e', + '--env.NX_API_URL=http://localhost:4200', + '--abc.123.xyx=false', + '--a.b=3', + ], + commandsObject.parserConfiguration ); expect(actual).toEqual( diff --git a/packages/nx/src/command-line/nx-commands.ts b/packages/nx/src/command-line/nx-commands.ts index df89fea20263c..2fec55f99b9ed 100644 --- a/packages/nx/src/command-line/nx-commands.ts +++ b/packages/nx/src/command-line/nx-commands.ts @@ -31,7 +31,7 @@ import { import { yargsNewCommand } from './new/command-object'; import { yargsRepairCommand } from './repair/command-object'; import { yargsReportCommand } from './report/command-object'; -import { yargsRunCommand } from './run/command-object'; +import { yargsNxInfixCommand, yargsRunCommand } from './run/command-object'; import { yargsRunManyCommand } from './run-many/command-object'; import { yargsShowCommand } from './show/command-object'; import { yargsWatchCommand } from './watch/command-object'; @@ -86,6 +86,7 @@ export const commandsObject = yargs .command(yargsShowCommand) .command(yargsViewLogsCommand) .command(yargsWatchCommand) + .command(yargsNxInfixCommand) .scriptName('nx') .help() // NOTE: we handle --version in nx.ts, this just tells yargs that the option exists diff --git a/packages/nx/src/command-line/run/command-object.spec.ts b/packages/nx/src/command-line/run/command-object.spec.ts new file mode 100644 index 0000000000000..e6b31a59d0833 --- /dev/null +++ b/packages/nx/src/command-line/run/command-object.spec.ts @@ -0,0 +1,112 @@ +import yargs = require('yargs'); +import { withOverrides } from '../yargs-utils/shared-options'; +import { yargsNxInfixCommand, yargsRunCommand } from './command-object'; + +describe('run-one command setup', () => { + it('should parse simple infix and `run` notation equivalently', () => { + const infixOptions = getParsedInfixArgs(['serve', 'myapp']); + const runOptions = getParsedRunArgs(['run', 'myapp:serve']); + + compareArgs(infixOptions, runOptions); + }); + + it.each(['--array=1,2,3', '--array=1 2 3', '--array=1 --array=2 --array=3'])( + 'should read arrays (%s)', + (args) => { + const infixArgs = getParsedInfixArgs([ + 'serve', + 'myapp', + ...args.split(' '), + ]); + const runArgs = getParsedRunArgs([ + 'run', + 'myapp:serve', + ...args.split(' '), + ]); + + compareArgs(infixArgs, runArgs); + } + ); + + describe('infix notation', () => { + it('should handle flags passed after project', () => { + const parsed = getParsedInfixArgs([ + 'serve', + 'myapp', + '--prod', + '--configuration=production', + ]); + + expect(parsed.target).toEqual('serve'); + expect(parsed.project).toEqual('myapp'); + expect(parsed.configuration).toEqual('production'); + expect(parsed.prod).toEqual(true); + }); + + it('should handle flags passed before project', () => { + const parsed = getParsedInfixArgs([ + 'serve', + '--prod', + '--configuration=production', + 'myapp', + ]); + + expect(parsed.target).toEqual('serve'); + expect(parsed.project).toEqual('myapp'); + expect(parsed.configuration).toEqual('production'); + expect(parsed.prod).toEqual(true); + }); + + it('should parse with missing project', () => { + const parsed = getParsedArgs(['serve', '--prod'], yargsNxInfixCommand); + + expect(parsed.target).toEqual('serve'); + expect(parsed.project).toEqual(undefined); + expect(parsed.configuration).toEqual(undefined); + expect(parsed.prod).toEqual(true); + }); + }); +}); + +function compareArgs(a: any, b: any) { + delete a['_']; + delete b['_']; + // delete a['__overrides_unparsed__']; + // delete b['__overrides_unparsed__']; + if (a['target'] && a['project']) { + a['project:target:configuration'] = `${a['project']}:${a['target']}`; + delete a['project']; + delete a['target']; + } + if (b['target'] && b['project']) { + b['project:target:configuration'] = `${b['project']}:${b['target']}`; + delete b['project']; + delete b['target']; + } + expect(a).toEqual(b); +} + +function getParsedInfixArgs(args: string[]) { + return getParsedArgs(args, yargsNxInfixCommand, 0); +} + +function getParsedRunArgs(args: string[]) { + return getParsedArgs(args, yargsRunCommand); +} + +function getParsedArgs( + args: string[], + command: yargs.CommandModule, + withOverridesLevel = 1 +) { + let parsedArgs: any; + yargs(args) + .command({ + ...command, + handler: (args) => { + parsedArgs = withOverrides({ ...args }, withOverridesLevel); + }, + }) + .parse(); + return parsedArgs; +} diff --git a/packages/nx/src/command-line/run/command-object.ts b/packages/nx/src/command-line/run/command-object.ts index 4461ec5c3d0d0..4425b517c8a27 100644 --- a/packages/nx/src/command-line/run/command-object.ts +++ b/packages/nx/src/command-line/run/command-object.ts @@ -18,3 +18,14 @@ export const yargsRunCommand: CommandModule = { handler: async (args) => (await import('./run-one')).runOne(process.cwd(), withOverrides(args)), }; + +/** + * Handles the infix notation for running a target. + */ +export const yargsNxInfixCommand: CommandModule = { + ...yargsRunCommand, + command: '$0 [project] [_..]', + describe: 'Run a target for a project', + handler: async (args) => + (await import('./run-one')).runOne(process.cwd(), withOverrides(args, 0)), +}; diff --git a/packages/nx/src/command-line/run/run-one.ts b/packages/nx/src/command-line/run/run-one.ts index c010baadc3192..b7fb30a4317dd 100644 --- a/packages/nx/src/command-line/run/run-one.ts +++ b/packages/nx/src/command-line/run/run-one.ts @@ -51,6 +51,7 @@ export async function runOne( { printWarnings: args.graph !== 'stdout' }, nxJson ); + if (nxArgs.verbose) { process.env.NX_VERBOSE_LOGGING = 'true'; } @@ -133,7 +134,7 @@ function parseRunOneOptions( let target; let configuration; - if (parsedArgs['project:target:configuration'].indexOf(':') > -1) { + if (parsedArgs['project:target:configuration']?.indexOf(':') > -1) { // run case [project, target, configuration] = splitTarget( parsedArgs['project:target:configuration'], @@ -145,7 +146,7 @@ function parseRunOneOptions( project = defaultProjectName; } } else { - target = parsedArgs['project:target:configuration']; + target = parsedArgs.target ?? parsedArgs['project:target:configuration']; } if (parsedArgs.project) { project = parsedArgs.project; 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 f15ab2d209454..22f2982371403 100644 --- a/packages/nx/src/command-line/yargs-utils/shared-options.ts +++ b/packages/nx/src/command-line/yargs-utils/shared-options.ts @@ -346,7 +346,10 @@ export function parseCSV(args: string[] | string): string[] { return []; } if (Array.isArray(args)) { - return args; + // If parseCSV is used on `type: 'array'`, the first option may be something like ['a,b,c']. + return args.length === 1 && args[0].includes(',') + ? parseCSV(args[0]) + : args; } const items = args.split(','); return items.map((i) => diff --git a/scripts/documentation/generators/generate-cli-data.ts b/scripts/documentation/generators/generate-cli-data.ts index 52841e608185a..7f498abe47814 100644 --- a/scripts/documentation/generators/generate-cli-data.ts +++ b/scripts/documentation/generators/generate-cli-data.ts @@ -18,6 +18,7 @@ import { const importFresh = require('import-fresh'); const sharedCommands = ['generate', 'run', 'exec']; +const hiddenCommands = ['$0']; export async function generateCliDocumentation( commandsOutputDirectory: string @@ -97,8 +98,12 @@ description: "${command.description}" const nxCommands = getCommands(commandsObject); await Promise.all( Object.keys(nxCommands) - .filter((name) => !sharedCommands.includes(name)) - .filter((name) => nxCommands[name].description) + .filter( + (name) => + !sharedCommands.includes(name) && + !hiddenCommands.includes(name) && + nxCommands[name].description + ) .map((name) => parseCommand(name, nxCommands[name])) .map(async (command) => generateMarkdown(await command)) .map(async (templateObject) =>