mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-17 11:03:53 +08:00
refactor: make the cli testable
This commit is contained in:
parent
1753b6affb
commit
40129ccebd
@ -17,7 +17,7 @@ import {
|
|||||||
terminal,
|
terminal,
|
||||||
virtualFs,
|
virtualFs,
|
||||||
} from '@angular-devkit/core';
|
} from '@angular-devkit/core';
|
||||||
import { NodeJsSyncHost, createConsoleLogger } from '@angular-devkit/core/node';
|
import { NodeJsSyncHost, ProcessOutput, createConsoleLogger } from '@angular-devkit/core/node';
|
||||||
import {
|
import {
|
||||||
DryRunEvent,
|
DryRunEvent,
|
||||||
SchematicEngine,
|
SchematicEngine,
|
||||||
@ -31,42 +31,6 @@ import {
|
|||||||
import * as minimist from 'minimist';
|
import * as minimist from 'minimist';
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show usage of the CLI tool, and exit the process.
|
|
||||||
*/
|
|
||||||
function usage(exitCode = 0): never {
|
|
||||||
logger.info(tags.stripIndent`
|
|
||||||
schematics [CollectionName:]SchematicName [options, ...]
|
|
||||||
|
|
||||||
By default, if the collection name is not specified, use the internal collection provided
|
|
||||||
by the Schematics CLI.
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--debug Debug mode. This is true by default if the collection is a relative
|
|
||||||
path (in that case, turn off with --debug=false).
|
|
||||||
|
|
||||||
--allowPrivate Allow private schematics to be run from the command line. Default to
|
|
||||||
false.
|
|
||||||
|
|
||||||
--dry-run Do not output anything, but instead just show what actions would be
|
|
||||||
performed. Default to true if debug is also true.
|
|
||||||
|
|
||||||
--force Force overwriting files that would otherwise be an error.
|
|
||||||
|
|
||||||
--list-schematics List all schematics from the collection, by name. A collection name
|
|
||||||
should be suffixed by a colon. Example: '@schematics/schematics:'.
|
|
||||||
|
|
||||||
--verbose Show more information.
|
|
||||||
|
|
||||||
--help Show this message.
|
|
||||||
|
|
||||||
Any additional option is passed to the Schematics depending on
|
|
||||||
`);
|
|
||||||
|
|
||||||
return process.exit(exitCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the name of schematic passed in argument, and return a {collection, schematic} named
|
* Parse the name of schematic passed in argument, and return a {collection, schematic} named
|
||||||
* tuple. The user can pass in `collection-name:schematic-name`, and this function will either
|
* tuple. The user can pass in `collection-name:schematic-name`, and this function will either
|
||||||
@ -93,6 +57,18 @@ function parseSchematicName(str: string | null): { collection: string, schematic
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface MainOptions {
|
||||||
|
args: string[];
|
||||||
|
stdout?: ProcessOutput;
|
||||||
|
stderr?: ProcessOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function main({
|
||||||
|
args,
|
||||||
|
stdout = process.stdout,
|
||||||
|
stderr = process.stderr,
|
||||||
|
}: MainOptions): Promise<0 | 1> {
|
||||||
|
|
||||||
/** Parse the command line. */
|
/** Parse the command line. */
|
||||||
const booleanArgs = [
|
const booleanArgs = [
|
||||||
'allowPrivate',
|
'allowPrivate',
|
||||||
@ -103,7 +79,7 @@ const booleanArgs = [
|
|||||||
'list-schematics',
|
'list-schematics',
|
||||||
'verbose',
|
'verbose',
|
||||||
];
|
];
|
||||||
const argv = minimist(process.argv.slice(2), {
|
const argv = minimist(args, {
|
||||||
boolean: booleanArgs,
|
boolean: booleanArgs,
|
||||||
default: {
|
default: {
|
||||||
'debug': null,
|
'debug': null,
|
||||||
@ -113,10 +89,12 @@ const argv = minimist(process.argv.slice(2), {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/** Create the DevKit Logger used through the CLI. */
|
/** Create the DevKit Logger used through the CLI. */
|
||||||
const logger = createConsoleLogger(argv['verbose']);
|
const logger = createConsoleLogger(argv['verbose'], stdout, stderr);
|
||||||
|
|
||||||
if (argv.help) {
|
if (argv.help) {
|
||||||
usage();
|
logger.info(getUsage());
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get the collection an schematic name from the first argument. */
|
/** Get the collection an schematic name from the first argument. */
|
||||||
@ -137,12 +115,13 @@ if (argv['list-schematics']) {
|
|||||||
const collection = engine.createCollection(collectionName);
|
const collection = engine.createCollection(collectionName);
|
||||||
logger.info(engine.listSchematicNames(collection).join('\n'));
|
logger.info(engine.listSchematicNames(collection).join('\n'));
|
||||||
|
|
||||||
process.exit(0);
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!schematicName) {
|
if (!schematicName) {
|
||||||
usage(1);
|
logger.info(getUsage());
|
||||||
throw 0; // TypeScript doesn't know that process.exit() never returns.
|
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Gather the arguments for later use. */
|
/** Gather the arguments for later use. */
|
||||||
@ -225,10 +204,10 @@ workflow.lifeCycle.subscribe(event => {
|
|||||||
/**
|
/**
|
||||||
* Remove every options from argv that we support in schematics itself.
|
* Remove every options from argv that we support in schematics itself.
|
||||||
*/
|
*/
|
||||||
const args = Object.assign({}, argv);
|
const parsedArgs = Object.assign({}, argv);
|
||||||
delete args['--'];
|
delete parsedArgs['--'];
|
||||||
for (const key of booleanArgs) {
|
for (const key of booleanArgs) {
|
||||||
delete args[key];
|
delete parsedArgs[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -236,7 +215,7 @@ for (const key of booleanArgs) {
|
|||||||
*/
|
*/
|
||||||
const argv2 = minimist(argv['--']);
|
const argv2 = minimist(argv['--']);
|
||||||
for (const key of Object.keys(argv2)) {
|
for (const key of Object.keys(argv2)) {
|
||||||
args[key] = argv2[key];
|
parsedArgs[key] = argv2[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass the rest of the arguments as the smart default "argv". Then delete it.
|
// Pass the rest of the arguments as the smart default "argv". Then delete it.
|
||||||
@ -247,7 +226,7 @@ workflow.registry.addSmartDefaultProvider('argv', (schema: JsonObject) => {
|
|||||||
return argv._;
|
return argv._;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
delete args._;
|
delete parsedArgs._;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -258,17 +237,24 @@ delete args._;
|
|||||||
* step of the workflow failed (sink or task), with details included, and will only complete
|
* step of the workflow failed (sink or task), with details included, and will only complete
|
||||||
* when everything is done.
|
* when everything is done.
|
||||||
*/
|
*/
|
||||||
workflow.execute({
|
try {
|
||||||
|
await workflow.execute({
|
||||||
collection: collectionName,
|
collection: collectionName,
|
||||||
schematic: schematicName,
|
schematic: schematicName,
|
||||||
options: args,
|
options: parsedArgs,
|
||||||
allowPrivate: allowPrivate,
|
allowPrivate: allowPrivate,
|
||||||
debug: debug,
|
debug: debug,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
})
|
})
|
||||||
.subscribe({
|
.toPromise();
|
||||||
error(err: Error) {
|
|
||||||
// In case the workflow was not successful, show an appropriate error message.
|
if (nothingDone) {
|
||||||
|
logger.info('Nothing to be done.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
if (err instanceof UnsuccessfulWorkflowExecution) {
|
if (err instanceof UnsuccessfulWorkflowExecution) {
|
||||||
// "See above" because we already printed the error.
|
// "See above" because we already printed the error.
|
||||||
logger.fatal('The Schematic workflow failed. See above.');
|
logger.fatal('The Schematic workflow failed. See above.');
|
||||||
@ -278,11 +264,46 @@ workflow.execute({
|
|||||||
logger.fatal(err.stack || err.message);
|
logger.fatal(err.stack || err.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
process.exit(1);
|
return 1;
|
||||||
},
|
}
|
||||||
complete() {
|
}
|
||||||
if (nothingDone) {
|
|
||||||
logger.info('Nothing to be done.');
|
/**
|
||||||
|
* Get usage of the CLI tool.
|
||||||
|
*/
|
||||||
|
function getUsage(): string {
|
||||||
|
return tags.stripIndent`
|
||||||
|
schematics [CollectionName:]SchematicName [options, ...]
|
||||||
|
|
||||||
|
By default, if the collection name is not specified, use the internal collection provided
|
||||||
|
by the Schematics CLI.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--debug Debug mode. This is true by default if the collection is a relative
|
||||||
|
path (in that case, turn off with --debug=false).
|
||||||
|
|
||||||
|
--allowPrivate Allow private schematics to be run from the command line. Default to
|
||||||
|
false.
|
||||||
|
|
||||||
|
--dry-run Do not output anything, but instead just show what actions would be
|
||||||
|
performed. Default to true if debug is also true.
|
||||||
|
|
||||||
|
--force Force overwriting files that would otherwise be an error.
|
||||||
|
|
||||||
|
--list-schematics List all schematics from the collection, by name. A collection name
|
||||||
|
should be suffixed by a colon. Example: '@schematics/schematics:'.
|
||||||
|
|
||||||
|
--verbose Show more information.
|
||||||
|
|
||||||
|
--help Show this message.
|
||||||
|
|
||||||
|
Any additional option is passed to the Schematics depending on
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
main({ args })
|
||||||
|
.then(exitCode => process.exitCode = exitCode)
|
||||||
|
.catch(e => { throw (e); });
|
||||||
}
|
}
|
||||||
},
|
|
||||||
});
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user