mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-22 06:41:45 +08:00
265 lines
7.3 KiB
TypeScript
265 lines
7.3 KiB
TypeScript
import {
|
|
Option,
|
|
CommandContext,
|
|
Command,
|
|
CommandConstructor,
|
|
CommandScope,
|
|
ArgumentStrategy
|
|
} from '../models/command';
|
|
import { logging, normalize, tags } from '@angular-devkit/core';
|
|
import { camelize } from '@angular-devkit/core/src/utils/strings';
|
|
import { findUp } from '../utilities/find-up';
|
|
|
|
import * as yargsParser from 'yargs-parser';
|
|
import * as fs from 'fs';
|
|
import { join } from 'path';
|
|
|
|
const SilentError = require('silent-error');
|
|
|
|
export interface CommandMap {
|
|
[key: string]: CommandConstructor;
|
|
}
|
|
|
|
/**
|
|
* Run a command.
|
|
* @param commandMap Map of available commands.
|
|
* @param args Raw unparsed arguments.
|
|
* @param logger The logger to use.
|
|
* @param context Execution context.
|
|
*/
|
|
export async function runCommand(commandMap: CommandMap,
|
|
args: string[],
|
|
logger: logging.Logger,
|
|
context: CommandContext): Promise<any> {
|
|
|
|
// if not args supplied, just run the help command.
|
|
if (!args || args.length === 0) {
|
|
args = ['help'];
|
|
}
|
|
const rawOptions = yargsParser(args, { alias: { help: ['h'] }, boolean: [ 'help' ] });
|
|
let commandName = rawOptions._[0];
|
|
|
|
// remove the command name
|
|
rawOptions._ = rawOptions._.slice(1);
|
|
const executionScope = context.project.isEmberCLIProject()
|
|
? CommandScope.inProject
|
|
: CommandScope.outsideProject;
|
|
|
|
let Cmd: CommandConstructor;
|
|
Cmd = findCommand(commandMap, commandName);
|
|
|
|
if (!Cmd && !commandName && (rawOptions.v || rawOptions.version)) {
|
|
commandName = 'version';
|
|
Cmd = findCommand(commandMap, commandName);
|
|
}
|
|
|
|
if (!Cmd && rawOptions.help) {
|
|
commandName = 'help';
|
|
Cmd = findCommand(commandMap, commandName);
|
|
}
|
|
|
|
if (!Cmd) {
|
|
logger.error(tags.oneLine`The specified command (${commandName}) is invalid.
|
|
For a list of available options, run \`ng help\`.`);
|
|
throw '';
|
|
}
|
|
|
|
const command = new Cmd(context, logger);
|
|
|
|
args = await command.initializeRaw(args);
|
|
let options = parseOptions(args, command.options, command.arguments, command.argStrategy);
|
|
await command.initialize(options);
|
|
options = parseOptions(args, command.options, command.arguments, command.argStrategy);
|
|
if (commandName === 'help') {
|
|
options.commandMap = commandMap;
|
|
}
|
|
|
|
if (options.help) {
|
|
return await runHelp(command, options);
|
|
} else {
|
|
verifyCommandInScope(command, executionScope);
|
|
if (!command.allowMissingWorkspace) {
|
|
verifyWorkspace(command, executionScope, context.project.root);
|
|
}
|
|
delete options.h;
|
|
delete options.help;
|
|
return await validateAndRunCommand(command, options);
|
|
}
|
|
}
|
|
|
|
export function parseOptions<T = any>(
|
|
args: string[],
|
|
cmdOpts: Option[],
|
|
commandArguments: string[],
|
|
argStrategy: ArgumentStrategy,
|
|
): T {
|
|
const parser = yargsParser;
|
|
|
|
const aliases = cmdOpts.concat()
|
|
.filter(o => o.aliases && o.aliases.length > 0)
|
|
.reduce((aliases: any, opt: Option) => {
|
|
aliases[opt.name] = opt.aliases
|
|
.filter(a => a.length === 1);
|
|
return aliases;
|
|
}, {});
|
|
|
|
const booleans = cmdOpts
|
|
.filter(o => o.type && o.type === Boolean)
|
|
.map(o => o.name);
|
|
|
|
const defaults = cmdOpts
|
|
.filter(o => o.default !== undefined || booleans.indexOf(o.name) !== -1)
|
|
.reduce((defaults: any, opt: Option) => {
|
|
defaults[opt.name] = opt.default;
|
|
return defaults;
|
|
}, {});
|
|
|
|
aliases.help = ['h'];
|
|
booleans.push('help');
|
|
|
|
const yargsOptions = {
|
|
alias: aliases,
|
|
boolean: booleans,
|
|
default: defaults
|
|
};
|
|
|
|
const parsedOptions = parser(args, yargsOptions);
|
|
|
|
// Remove aliases.
|
|
cmdOpts
|
|
.filter(o => o.aliases && o.aliases.length > 0)
|
|
.map(o => o.aliases)
|
|
.reduce((allAliases: any, aliases: string[]) => {
|
|
return allAliases.concat([...aliases]);
|
|
}, [])
|
|
.forEach((alias: string) => {
|
|
delete parsedOptions[alias];
|
|
});
|
|
|
|
// Remove undefined booleans
|
|
booleans
|
|
.filter(b => parsedOptions[b] === undefined)
|
|
.map(b => camelize(b))
|
|
.forEach(b => delete parsedOptions[b]);
|
|
|
|
// remove options with dashes.
|
|
Object.keys(parsedOptions)
|
|
.filter(key => key.indexOf('-') !== -1)
|
|
.forEach(key => delete parsedOptions[key]);
|
|
|
|
// remove the command name
|
|
parsedOptions._ = parsedOptions._.slice(1);
|
|
|
|
switch (argStrategy) {
|
|
case ArgumentStrategy.MapToOptions:
|
|
parsedOptions._.forEach((value: string, index: number) => {
|
|
const arg = commandArguments[index];
|
|
if (arg) {
|
|
parsedOptions[arg] = value;
|
|
}
|
|
});
|
|
|
|
delete parsedOptions._;
|
|
break;
|
|
}
|
|
|
|
return parsedOptions;
|
|
}
|
|
|
|
// Find a command.
|
|
function findCommand(
|
|
map: CommandMap, name: string): CommandConstructor | null {
|
|
let Cmd: CommandConstructor = map[name];
|
|
|
|
if (!Cmd) {
|
|
// find command via aliases
|
|
Cmd = Object.keys(map)
|
|
.filter(key => {
|
|
if (!map[key].aliases) {
|
|
return false;
|
|
}
|
|
const foundAlias = map[key].aliases
|
|
.filter((alias: string) => alias === name);
|
|
|
|
return foundAlias.length > 0;
|
|
})
|
|
.map((key) => {
|
|
return map[key];
|
|
})[0];
|
|
}
|
|
|
|
if (!Cmd) {
|
|
return null;
|
|
}
|
|
return Cmd;
|
|
}
|
|
|
|
function verifyCommandInScope(command: Command, scope = CommandScope.everywhere): void {
|
|
if (!command) {
|
|
return;
|
|
}
|
|
if (command.scope !== CommandScope.everywhere) {
|
|
if (command.scope !== scope) {
|
|
let errorMessage: string;
|
|
if (command.scope === CommandScope.inProject) {
|
|
errorMessage = `This command can only be run inside of a CLI project.`;
|
|
} else {
|
|
errorMessage = `This command can not be run inside of a CLI project.`;
|
|
}
|
|
throw new SilentError(errorMessage);
|
|
}
|
|
}
|
|
}
|
|
|
|
function verifyWorkspace(command: Command, executionScope: CommandScope, root: string): void {
|
|
if (command.scope === CommandScope.everywhere) {
|
|
return;
|
|
}
|
|
if (executionScope === CommandScope.inProject) {
|
|
if (fs.existsSync(join(root, 'angular.json'))) {
|
|
return;
|
|
}
|
|
if (fs.existsSync(join(root, '.angular.json'))) {
|
|
return;
|
|
}
|
|
|
|
// Check if there's an old config file meaning that the project has not been updated
|
|
const oldConfigFileNames = [
|
|
normalize('.angular-cli.json'),
|
|
normalize('angular-cli.json'),
|
|
];
|
|
const oldConfigFilePath = (root && findUp(oldConfigFileNames, root, true))
|
|
|| findUp(oldConfigFileNames, process.cwd(), true)
|
|
|| findUp(oldConfigFileNames, __dirname, true);
|
|
|
|
// If an old configuration file is found, throw an exception.
|
|
if (oldConfigFilePath) {
|
|
throw new SilentError(tags.stripIndent`
|
|
An old project has been detected, which needs to be updated to Angular CLI 6.
|
|
|
|
Please run the following commands to update this project.
|
|
|
|
ng update @angular/cli --migrate-only --from=1.7.1
|
|
npm i
|
|
`);
|
|
}
|
|
|
|
// If no configuration file is found (old or new), throw an exception.
|
|
throw new SilentError('Invalid project: missing workspace file.');
|
|
}
|
|
}
|
|
|
|
// Execute a command's `printHelp`.
|
|
async function runHelp(command: Command, options: any): Promise<void> {
|
|
return await command.printHelp(options);
|
|
}
|
|
|
|
// Validate and run a command.
|
|
async function validateAndRunCommand(command: Command, options: any): Promise<any> {
|
|
const isValid = await command.validate(options);
|
|
if (isValid !== undefined && !isValid) {
|
|
throw new SilentError(`Validation error. Invalid command`);
|
|
}
|
|
return await command.run(options);
|
|
}
|