import { logging } from '@angular-devkit/core'; import { UnsuccessfulWorkflowExecution } from '@angular-devkit/schematics'; // @ts-ignore import levenshtein = require('fast-levenshtein'); export type Schema = { properties: { [p: string]: any }; required: string[]; description: string; }; export type Unmatched = { name: string; possible: string[]; }; export type Options = { '--'?: Unmatched[]; [k: string]: any; }; export async function handleErrors( logger: logging.Logger, isVerbose: boolean, fn: Function ) { try { return await fn(); } catch (err) { if (err instanceof UnsuccessfulWorkflowExecution) { logger.fatal('The Schematic workflow failed. See above.'); } else { logger.fatal(err.message); } if (isVerbose && err.stack) { logger.info(err.stack); } return 1; } } export function convertToCamelCase(parsed: Options): Options { return Object.keys(parsed).reduce( (m, c) => ({ ...m, [camelCase(c)]: parsed[c] }), {} ); } function camelCase(input: string): string { if (input.indexOf('-') > 1) { return input .toLowerCase() .replace(/-(.)/g, (match, group1) => group1.toUpperCase()); } else { return input; } } /** * Coerces (and replaces) options identified as 'boolean' or 'number' in the Schema * * @param opts The options to check * @param schema The schema definition with types to check against * */ export function coerceTypes(opts: Options, schema: Schema): Options { Object.keys(opts).forEach((k) => { if (schema.properties[k] && schema.properties[k].type == 'boolean') { opts[k] = opts[k] === true || opts[k] === 'true'; } else if (schema.properties[k] && schema.properties[k].type == 'number') { opts[k] = Number(opts[k]); } else if (schema.properties[k] && schema.properties[k].type == 'array') { opts[k] = opts[k].toString().split(','); } }); return opts; } /** * Converts any options passed in with short aliases to their full names if found * Unmatched options are added to opts['--'] * * @param opts The options passed in by the user * @param schema The schema definition to check against */ export function convertAliases( opts: Options, schema: Schema, excludeUnmatched: boolean ): Options { return Object.keys(opts).reduce((acc, k) => { if (schema.properties[k]) { acc[k] = opts[k]; } else { const found = Object.entries(schema.properties).find( ([_, d]) => d.alias === k ); if (found) { acc[found[0]] = opts[k]; } else if (excludeUnmatched) { if (!acc['--']) { acc['--'] = []; } acc['--'].push({ name: k, possible: [], }); } else { acc[k] = opts[k]; } } return acc; }, {}); } /** * Tries to find what the user meant by unmatched commands * * @param opts The options passed in by the user * @param schema The schema definition to check against * */ export function lookupUnmatched(opts: Options, schema: Schema): Options { if (opts['--']) { const props = Object.keys(schema.properties); opts['--'].forEach((unmatched) => { unmatched.possible = props.filter( (p) => levenshtein.get(p, unmatched.name) < 3 ); }); } return opts; }