fix(nx): validate target and configuration in @nrwl/tao/run and provide better errors

This commit is contained in:
Jo Hanna Pearce 2019-10-11 12:07:21 +01:00 committed by Victor Savkin
parent fa675f1194
commit 58353a6be8
3 changed files with 80 additions and 35 deletions

View File

@ -1,16 +1,16 @@
import { packagesWeCareAbout } from '@nrwl/workspace/src/command-line/report'; import { packagesWeCareAbout } from '@nrwl/workspace/src/command-line/report';
import { ensureProject, forEachCli, runCLI } from './utils'; import { ensureProject, forEachCli, runCommand } from './utils';
const testTimeout = 120000; const testTimeout = 120000;
forEachCli('nx', () => { forEachCli(() => {
describe('report', () => { describe('report', () => {
it( it(
`should report package versions`, `should report package versions`,
async () => { async () => {
ensureProject(); ensureProject();
const reportOutput = runCLI('report'); const reportOutput = runCommand('npm run nx report');
packagesWeCareAbout.forEach(p => { packagesWeCareAbout.forEach(p => {
expect(reportOutput).toContain(p); expect(reportOutput).toContain(p);
@ -20,15 +20,3 @@ forEachCli('nx', () => {
); );
}); });
}); });
forEachCli('angular', () => {
describe('report', () => {
it(
`shouldn't do anything at all`,
async () => {
// report is an Nx only command
},
testTimeout
);
});
});

View File

@ -49,12 +49,18 @@ export async function invokeCommand(
case 'help': case 'help':
case '--help': case '--help':
return (await import('./src/commands/help')).help(); return (await import('./src/commands/help')).help();
default: default:
const projectName = commandArgs[0] ? commandArgs[0] : ''; const projectNameIncluded =
commandArgs[0] && !commandArgs[0].startsWith('-');
const projectName = projectNameIncluded ? commandArgs[0] : '';
// this is to make `tao test mylib` same as `tao run mylib:test` // this is to make `tao test mylib` same as `tao run mylib:test`
return (await import('./src/commands/run')).run( return (await import('./src/commands/run')).run(
root, root,
[`${projectName}:${command}`, ...commandArgs.slice(1)], [
`${projectName}:${command}`,
...(projectNameIncluded ? commandArgs.slice(1) : commandArgs)
],
isVerbose isVerbose
); );
} }

View File

@ -5,7 +5,8 @@ import {
json, json,
logging, logging,
normalize, normalize,
schema schema,
terminal
} from '@angular-devkit/core'; } from '@angular-devkit/core';
import { NodeJsSyncHost } from '@angular-devkit/core/node'; import { NodeJsSyncHost } from '@angular-devkit/core/node';
import { getLogger } from '../shared/logger'; import { getLogger } from '../shared/logger';
@ -34,7 +35,8 @@ function throwInvalidInvocation() {
function parseRunOpts( function parseRunOpts(
args: string[], args: string[],
defaultProjectName: string | null defaultProjectName: string | null,
logger: logging.Logger
): RunOptions { ): RunOptions {
const runOptions = convertToCamelCase( const runOptions = convertToCamelCase(
minimist(args, { minimist(args, {
@ -47,7 +49,14 @@ function parseRunOpts(
throwInvalidInvocation(); throwInvalidInvocation();
} }
let [project, target, configuration] = runOptions._[0].split(':'); let [project, target, configuration] = runOptions._[0].split(':');
if (!project && defaultProjectName) project = defaultProjectName; if (!project && defaultProjectName) {
logger.debug(
`No project name specified. Using default project : ${terminal.bold(
defaultProjectName
)}`
);
project = defaultProjectName;
}
if (!project || !target) { if (!project || !target) {
throwInvalidInvocation(); throwInvalidInvocation();
} }
@ -82,6 +91,45 @@ function printRunHelp(
); );
} }
export function validateTargetAndConfiguration(
workspace: experimental.workspace.Workspace,
opts: RunOptions
) {
const targets = workspace.getProjectTargets(opts.project);
const target = targets[opts.target];
if (!target) {
throw new Error(
`Could not find target "${opts.target}" in the ${
opts.project
} project. Valid targets are: ${terminal.bold(
Object.keys(targets).join(', ')
)}`
);
}
// Not all targets have configurations
// and an undefined configuration is valid
if (opts.configuration) {
if (target.configurations) {
const configuration = target.configurations[opts.configuration];
if (!configuration) {
throw new Error(
`Could not find configuration "${opts.configuration}" in ${
opts.project
}:${opts.target}. Valid configurations are: ${Object.keys(
target.configurations
).join(', ')}`
);
}
} else {
throw new Error(
`No configurations are defined for ${opts.project}:${opts.target}, so "${opts.configuration}" is invalid.`
);
}
}
}
export async function run(root: string, args: string[], isVerbose: boolean) { export async function run(root: string, args: string[], isVerbose: boolean) {
const logger = getLogger(isVerbose); const logger = getLogger(isVerbose);
@ -93,7 +141,9 @@ export async function run(root: string, args: string[], isVerbose: boolean) {
) )
.loadWorkspaceFromHost('workspace.json' as any) .loadWorkspaceFromHost('workspace.json' as any)
.toPromise(); .toPromise();
const opts = parseRunOpts(args, workspace.getDefaultProjectName());
const opts = parseRunOpts(args, workspace.getDefaultProjectName(), logger);
validateTargetAndConfiguration(workspace, opts);
const registry = new json.schema.CoreSchemaRegistry(); const registry = new json.schema.CoreSchemaRegistry();
registry.addPostTransform(schema.transforms.addUndefinedDefaults); registry.addPostTransform(schema.transforms.addUndefinedDefaults);
@ -111,23 +161,24 @@ export async function run(root: string, args: string[], isVerbose: boolean) {
const flattenedSchema = await registry const flattenedSchema = await registry
.flatten(builderDesc.optionSchema! as json.JsonObject) .flatten(builderDesc.optionSchema! as json.JsonObject)
.toPromise(); .toPromise();
if (opts.help) { if (opts.help) {
printRunHelp(opts, flattenedSchema as any, logger); printRunHelp(opts, flattenedSchema as any, logger);
return 0; return 0;
} else {
const runOptions = coerceTypes(opts.runOptions, flattenedSchema as any);
const run = await architect.scheduleTarget(
{
project: opts.project,
target: opts.target,
configuration: opts.configuration
},
runOptions,
{ logger }
);
const result = await run.output.toPromise();
await run.stop();
return result.success ? 0 : 1;
} }
const runOptions = coerceTypes(opts.runOptions, flattenedSchema as any);
const run = await architect.scheduleTarget(
{
project: opts.project,
target: opts.target,
configuration: opts.configuration
},
runOptions,
{ logger }
);
const result = await run.output.toPromise();
await run.stop();
return result.success ? 0 : 1;
}); });
} }