Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(core): nx build --graph file.json should work in subdirectories #19858

Merged
merged 1 commit into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
38 changes: 1 addition & 37 deletions packages/nx/bin/init-local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,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);
Expand Down Expand Up @@ -81,41 +80,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;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/nx/src/command-line/init/init-v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,5 +129,5 @@ function setupDotNxInstallation(version: string) {
}
generateDotNxSetup(version);
// invokes the wrapper, thus invoking the initial installation process
runNxSync('');
runNxSync('--version');
}
14 changes: 12 additions & 2 deletions packages/nx/src/command-line/nx-commands.spec.ts
Original file line number Diff line number Diff line change
@@ -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(
Expand Down
3 changes: 2 additions & 1 deletion packages/nx/src/command-line/nx-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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
Expand Down
112 changes: 112 additions & 0 deletions packages/nx/src/command-line/run/command-object.spec.ts
Original file line number Diff line number Diff line change
@@ -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;
}
11 changes: 11 additions & 0 deletions packages/nx/src/command-line/run/command-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <target> [project] [_..]',
describe: 'Run a target for a project',
handler: async (args) =>
(await import('./run-one')).runOne(process.cwd(), withOverrides(args, 0)),
};
5 changes: 3 additions & 2 deletions packages/nx/src/command-line/run/run-one.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export async function runOne(
{ printWarnings: args.graph !== 'stdout' },
nxJson
);

if (nxArgs.verbose) {
process.env.NX_VERBOSE_LOGGING = 'true';
}
Expand Down Expand Up @@ -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'],
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,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) =>
Expand Down
9 changes: 7 additions & 2 deletions scripts/documentation/generators/generate-cli-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
const importFresh = require('import-fresh');

const sharedCommands = ['generate', 'run', 'exec'];
const hiddenCommands = ['$0'];

export async function generateCliDocumentation(
commandsOutputDirectory: string
Expand Down Expand Up @@ -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) =>
Expand Down