fix(core): task-orchestrator properly handles arguments (#3560)

The task-orchestrator was converting command line flags to "yargs-compatible" string using a custom logic. This commit makes sure that this happens through `yargs-unparser` to avoid issues with more complex options like arrays and objects.

ISSUES CLOSED: #3450
This commit is contained in:
Tasos Bekos 2020-08-25 11:31:27 +03:00 committed by GitHub
parent 08e4ee29fa
commit 2d1da6b4a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 101 additions and 10 deletions

View File

@ -138,6 +138,7 @@
"express": "4.17.1", "express": "4.17.1",
"file-loader": "4.2.0", "file-loader": "4.2.0",
"find-cache-dir": "3.0.0", "find-cache-dir": "3.0.0",
"flat": "^5.0.2",
"fork-ts-checker-webpack-plugin": "^3.1.1", "fork-ts-checker-webpack-plugin": "^3.1.1",
"fs-extra": "7.0.1", "fs-extra": "7.0.1",
"glob": "7.1.4", "glob": "7.1.4",

View File

@ -68,6 +68,7 @@
"yargs": "^11.0.0", "yargs": "^11.0.0",
"chalk": "2.4.2", "chalk": "2.4.2",
"@nrwl/cli": "*", "@nrwl/cli": "*",
"axios": "0.19.2" "axios": "0.19.2",
"flat": "^5.0.2"
} }
} }

View File

@ -2,7 +2,7 @@ import { Cache, TaskWithCachedResult } from './cache';
import { cliCommand } from '../core/file-utils'; import { cliCommand } from '../core/file-utils';
import { ProjectGraph } from '../core/project-graph'; import { ProjectGraph } from '../core/project-graph';
import { AffectedEventType, Task } from './tasks-runner'; import { AffectedEventType, Task } from './tasks-runner';
import { getOutputs } from './utils'; import { getOutputs, unparse } from './utils';
import { fork } from 'child_process'; import { fork } from 'child_process';
import { DefaultTasksRunnerOptions } from './default-tasks-runner'; import { DefaultTasksRunnerOptions } from './default-tasks-runner';
import { output } from '../utils/output'; import { output } from '../utils/output';
@ -318,13 +318,7 @@ export class TaskOrchestrator {
} }
private getCommandArgs(task: Task) { private getCommandArgs(task: Task) {
const args = Object.entries(task.overrides || {}).map(([prop, value]) => const args: string[] = unparse(task.overrides || {});
typeof value === 'boolean'
? value
? `--${prop}`
: `--no-${prop}`
: `--${prop}=${value}`
);
const config = task.target.configuration const config = task.target.configuration
? `:${task.target.configuration}` ? `:${task.target.configuration}`

View File

@ -1,4 +1,7 @@
import { getOutputsForTargetAndConfiguration } from '@nrwl/workspace/src/tasks-runner/utils'; import {
getOutputsForTargetAndConfiguration,
unparse,
} from '@nrwl/workspace/src/tasks-runner/utils';
describe('utils', () => { describe('utils', () => {
describe('getOutputsForTargetAndConfiguration', () => { describe('getOutputsForTargetAndConfiguration', () => {
@ -181,4 +184,59 @@ describe('utils', () => {
).toEqual(['dist/root-myapp']); ).toEqual(['dist/root-myapp']);
}); });
}); });
describe('unparse', () => {
it('should unparse options whose values are primitives', () => {
const options = {
boolean1: false,
boolean2: true,
number: 4,
string: 'foo',
'empty-string': '',
ignore: null,
};
expect(unparse(options)).toEqual([
'--no-boolean1',
'--boolean2',
'--number=4',
'--string=foo',
'--empty-string=',
]);
});
it('should unparse options whose values are arrays', () => {
const options = {
array1: [1, 2],
array2: [3, 4],
};
expect(unparse(options)).toEqual([
'--array1=1',
'--array1=2',
'--array2=3',
'--array2=4',
]);
});
it('should unparse options whose values are objects', () => {
const options = {
foo: {
x: 'x',
y: 'y',
w: [1, 2],
z: [3, 4],
},
};
expect(unparse(options)).toEqual([
'--foo.x=x',
'--foo.y=y',
'--foo.w=1',
'--foo.w=2',
'--foo.z=3',
'--foo.z=4',
]);
});
});
}); });

View File

@ -1,5 +1,6 @@
import { Task } from './tasks-runner'; import { Task } from './tasks-runner';
import { ProjectGraphNode } from '../core/project-graph'; import { ProjectGraphNode } from '../core/project-graph';
import * as flatten from 'flat';
const commonCommands = ['build', 'test', 'lint', 'e2e', 'deploy']; const commonCommands = ['build', 'test', 'lint', 'e2e', 'deploy'];
@ -75,3 +76,34 @@ export function getOutputsForTargetAndConfiguration(
return []; return [];
} }
} }
export function unparse(options: Object): string[] {
const unparsed = [];
for (const key of Object.keys(options)) {
const value = options[key];
unparseOption(key, value, unparsed);
}
return unparsed;
}
function unparseOption(key: string, value: any, unparsed: string[]) {
if (value === true) {
unparsed.push(`--${key}`);
} else if (value === false) {
unparsed.push(`--no-${key}`);
} else if (Array.isArray(value)) {
value.forEach((item) => unparseOption(key, item, unparsed));
} else if (Object.prototype.toString.call(value) === '[object Object]') {
const flattened = flatten(value, { safe: true });
for (const flattenedKey in flattened) {
unparseOption(
`${key}.${flattenedKey}`,
flattened[flattenedKey],
unparsed
);
}
} else if (typeof value === 'string' || value != null) {
unparsed.push(`--${key}=${value}`);
}
}

View File

@ -10094,6 +10094,11 @@ flat-cache@^2.0.1:
rimraf "2.6.3" rimraf "2.6.3"
write "1.0.3" write "1.0.3"
flat@^5.0.2:
version "5.0.2"
resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
flatted@^2.0.0: flatted@^2.0.0:
version "2.0.2" version "2.0.2"
resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138"