nx
Version:
587 lines • 23.6 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.convertSmartDefaultsIntoNamedParams = exports.warnDeprecations = exports.combineOptionsForGenerator = exports.combineOptionsForExecutor = exports.applyVerbosity = exports.setDefaults = exports.validateObject = exports.validateOptsAgainstSchema = exports.SchemaError = exports.convertAliases = exports.coerceTypesInOptions = exports.convertToCamelCase = exports.handleErrors = void 0;
const tslib_1 = require("tslib");
const logger_1 = require("./logger");
const output_1 = require("./output");
function handleErrors(isVerbose, fn) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
try {
return yield fn();
}
catch (err) {
err !== null && err !== void 0 ? err : (err = new Error('Unknown error caught'));
if (err.constructor.name === 'UnsuccessfulWorkflowExecution') {
logger_1.logger.error('The generator workflow failed. See above.');
}
else {
const lines = (err.message ? err.message : err.toString()).split('\n');
const bodyLines = lines.slice(1);
if (err.stack && !isVerbose) {
bodyLines.push('Pass --verbose to see the stacktrace.');
}
output_1.output.error({
title: lines[0],
bodyLines,
});
if (err.stack && isVerbose) {
logger_1.logger.info(err.stack);
}
}
return 1;
}
});
}
exports.handleErrors = handleErrors;
function camelCase(input) {
if (input.indexOf('-') > 1) {
return input
.toLowerCase()
.replace(/-(.)/g, (match, group1) => group1.toUpperCase());
}
else {
return input;
}
}
function convertToCamelCase(parsed, schema) {
return Object.keys(parsed).reduce((m, c) => {
if (schema.properties[camelCase(c)]) {
return Object.assign(Object.assign({}, m), { [camelCase(c)]: parsed[c] });
}
else {
return Object.assign(Object.assign({}, m), { [c]: parsed[c] });
}
}, {});
}
exports.convertToCamelCase = convertToCamelCase;
/**
* 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
*
*/
function coerceTypesInOptions(opts, schema) {
Object.keys(opts).forEach((k) => {
const prop = findSchemaForProperty(k, schema);
opts[k] = coerceType(prop === null || prop === void 0 ? void 0 : prop.description, opts[k]);
});
return opts;
}
exports.coerceTypesInOptions = coerceTypesInOptions;
function coerceType(prop, value) {
if (!prop)
return value;
if (typeof value !== 'string' && value !== undefined)
return value;
if (prop.oneOf) {
for (let i = 0; i < prop.oneOf.length; ++i) {
const coerced = coerceType(prop.oneOf[i], value);
if (coerced !== value) {
return coerced;
}
}
return value;
}
else if (Array.isArray(prop.type)) {
for (let i = 0; i < prop.type.length; ++i) {
const coerced = coerceType({ type: prop.type[i] }, value);
if (coerced !== value) {
return coerced;
}
}
return value;
}
else if (normalizedPrimitiveType(prop.type) == 'boolean' &&
isConvertibleToBoolean(value)) {
return value === true || value == 'true';
}
else if (normalizedPrimitiveType(prop.type) == 'number' &&
isConvertibleToNumber(value)) {
return Number(value);
}
else if (prop.type == 'array') {
return value.split(',').map((v) => coerceType(prop.items, v));
}
else {
return value;
}
}
/**
* 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
*/
function convertAliases(opts, schema, excludeUnmatched) {
return Object.keys(opts).reduce((acc, k) => {
const prop = findSchemaForProperty(k, schema);
if (prop) {
acc[prop.name] = opts[k];
}
else if (excludeUnmatched) {
if (!acc['--']) {
acc['--'] = [];
}
acc['--'].push({
name: k,
possible: [],
});
}
else {
acc[k] = opts[k];
}
return acc;
}, {});
}
exports.convertAliases = convertAliases;
class SchemaError {
constructor(message) {
this.message = message;
}
}
exports.SchemaError = SchemaError;
function validateOptsAgainstSchema(opts, schema) {
validateObject(opts, schema.properties || {}, schema.required || [], schema.additionalProperties, schema.definitions || {});
}
exports.validateOptsAgainstSchema = validateOptsAgainstSchema;
function validateObject(opts, properties, required, additionalProperties, definitions) {
required.forEach((p) => {
if (opts[p] === undefined) {
throw new SchemaError(`Required property '${p}' is missing`);
}
});
if (additionalProperties === false) {
Object.keys(opts).find((p) => {
if (Object.keys(properties).indexOf(p) === -1) {
if (p === '_') {
throw new SchemaError(`Schema does not support positional arguments. Argument '${opts[p]}' found`);
}
else {
throw new SchemaError(`'${p}' is not found in schema`);
}
}
});
}
Object.keys(opts).forEach((p) => {
validateProperty(p, opts[p], properties[p], definitions);
});
}
exports.validateObject = validateObject;
function validateProperty(propName, value, schema, definitions) {
if (!schema)
return;
if (schema.$ref) {
schema = resolveDefinition(schema.$ref, definitions);
}
if (schema.oneOf) {
if (!Array.isArray(schema.oneOf))
throw new Error(`Invalid schema file. oneOf must be an array.`);
const passes = schema.oneOf.filter((r) => {
try {
const rule = Object.assign({ type: schema.type }, r);
validateProperty(propName, value, rule, definitions);
return true;
}
catch (e) {
return false;
}
}).length === 1;
if (!passes)
throwInvalidSchema(propName, schema);
return;
}
if (schema.anyOf) {
if (!Array.isArray(schema.anyOf))
throw new Error(`Invalid schema file. anyOf must be an array.`);
let passes = false;
schema.anyOf.forEach((r) => {
try {
const rule = Object.assign({ type: schema.type }, r);
validateProperty(propName, value, rule, definitions);
passes = true;
}
catch (e) { }
});
if (!passes)
throwInvalidSchema(propName, schema);
return;
}
if (schema.allOf) {
if (!Array.isArray(schema.allOf))
throw new Error(`Invalid schema file. anyOf must be an array.`);
if (!schema.allOf.every((r) => {
try {
const rule = Object.assign({ type: schema.type }, r);
validateProperty(propName, value, rule, definitions);
return true;
}
catch (e) {
return false;
}
})) {
throwInvalidSchema(propName, schema);
}
return;
}
const isPrimitive = typeof value !== 'object';
if (isPrimitive) {
if (Array.isArray(schema.type)) {
const passes = schema.type.some((t) => {
try {
const rule = { type: t };
validateProperty(propName, value, rule, definitions);
return true;
}
catch (e) {
return false;
}
});
if (!passes) {
throw new SchemaError(`Property '${propName}' does not match the schema. '${value}' should be a '${schema.type}'.`);
}
}
else if (schema.type &&
typeof value !== normalizedPrimitiveType(schema.type)) {
throw new SchemaError(`Property '${propName}' does not match the schema. '${value}' should be a '${schema.type}'.`);
}
if (schema.enum && !schema.enum.includes(value)) {
throw new SchemaError(`Property '${propName}' does not match the schema. '${value}' should be one of ${schema.enum.join(',')}.`);
}
if (schema.type === 'number') {
if (typeof schema.multipleOf === 'number' &&
value % schema.multipleOf !== 0) {
throw new SchemaError(`Property '${propName}' does not match the schema. ${value} should be a multiple of ${schema.multipleOf}.`);
}
if (typeof schema.minimum === 'number' && value < schema.minimum) {
throw new SchemaError(`Property '${propName}' does not match the schema. ${value} should be at least ${schema.minimum}`);
}
if (typeof schema.exclusiveMinimum === 'number' &&
value <= schema.exclusiveMinimum) {
throw new SchemaError(`Property '${propName}' does not match the schema. ${value} should be greater than ${schema.exclusiveMinimum}`);
}
if (typeof schema.maximum === 'number' && value > schema.maximum) {
throw new SchemaError(`Property '${propName}' does not match the schema. ${value} should be at most ${schema.maximum}`);
}
if (typeof schema.exclusiveMaximum === 'number' &&
value >= schema.exclusiveMaximum) {
throw new SchemaError(`Property '${propName}' does not match the schema. ${value} should be less than ${schema.exclusiveMaximum}`);
}
}
if (schema.type === 'string') {
if (schema.pattern && !new RegExp(schema.pattern).test(value)) {
throw new SchemaError(`Property '${propName}' does not match the schema. '${value}' should match the pattern '${schema.pattern}'.`);
}
if (typeof schema.minLength === 'number' &&
value.length < schema.minLength) {
throw new SchemaError(`Property '${propName}' does not match the schema. '${value}' (${value.length} character(s)) should have at least ${schema.minLength} character(s).`);
}
if (typeof schema.maxLength === 'number' &&
value.length > schema.maxLength) {
throw new SchemaError(`Property '${propName}' does not match the schema. '${value}' (${value.length} character(s)) should have at most ${schema.maxLength} character(s).`);
}
}
}
else if (Array.isArray(value)) {
if (schema.type !== 'array')
throwInvalidSchema(propName, schema);
value.forEach((valueInArray) => validateProperty(propName, valueInArray, schema.items || {}, definitions));
}
else {
if (schema.type !== 'object')
throwInvalidSchema(propName, schema);
validateObject(value, schema.properties || {}, schema.required || [], schema.additionalProperties, definitions);
}
}
/**
* Unfortunately, due to use supporting Angular Devkit, we have to do the following
* conversions.
*/
function normalizedPrimitiveType(type) {
if (type === 'integer')
return 'number';
return type;
}
function throwInvalidSchema(propName, schema) {
throw new SchemaError(`Property '${propName}' does not match the schema.\n${JSON.stringify(schema, null, 2)}'`);
}
function setDefaults(opts, schema) {
setDefaultsInObject(opts, schema.properties || {}, schema.definitions || {});
return opts;
}
exports.setDefaults = setDefaults;
function setDefaultsInObject(opts, properties, definitions) {
Object.keys(properties).forEach((p) => {
setPropertyDefault(opts, p, properties[p], definitions);
});
}
function setPropertyDefault(opts, propName, schema, definitions) {
if (schema.$ref) {
schema = resolveDefinition(schema.$ref, definitions);
}
if (schema.type !== 'object' && schema.type !== 'array') {
if (opts[propName] === undefined && schema.default !== undefined) {
opts[propName] = schema.default;
}
}
else if (schema.type === 'array') {
const items = schema.items || {};
if (opts[propName] &&
Array.isArray(opts[propName]) &&
items.type === 'object') {
opts[propName].forEach((valueInArray) => setDefaultsInObject(valueInArray, items.properties || {}, definitions));
}
else if (!opts[propName] && schema.default) {
opts[propName] = schema.default;
}
}
else {
const wasUndefined = opts[propName] === undefined;
if (wasUndefined) {
// We need an object to set values onto
opts[propName] = {};
}
setDefaultsInObject(opts[propName], schema.properties || {}, definitions);
// If the property was initially undefined but no properties were added, we remove it again instead of having an {}
if (wasUndefined && Object.keys(opts[propName]).length === 0) {
delete opts[propName];
}
}
}
function resolveDefinition(ref, definitions) {
if (!ref.startsWith('#/definitions/')) {
throw new Error(`$ref should start with "#/definitions/"`);
}
const definition = ref.split('#/definitions/')[1];
if (!definitions[definition]) {
throw new Error(`Cannot resolve ${ref}`);
}
return definitions[definition];
}
function applyVerbosity(options, schema, isVerbose) {
if ((schema.additionalProperties || 'verbose' in schema.properties) &&
isVerbose) {
options['verbose'] = true;
}
}
exports.applyVerbosity = applyVerbosity;
function combineOptionsForExecutor(commandLineOpts, config, target, schema, defaultProjectName, relativeCwd, isVerbose = false) {
const r = convertAliases(coerceTypesInOptions(convertToCamelCase(commandLineOpts, schema), schema), schema, false);
let combined = target.options || {};
if (config && target.configurations && target.configurations[config]) {
Object.assign(combined, target.configurations[config]);
}
combined = convertAliases(combined, schema, false);
Object.assign(combined, r);
convertSmartDefaultsIntoNamedParams(combined, schema, defaultProjectName, relativeCwd);
warnDeprecations(combined, schema);
setDefaults(combined, schema);
validateOptsAgainstSchema(combined, schema);
applyVerbosity(combined, schema, isVerbose);
return combined;
}
exports.combineOptionsForExecutor = combineOptionsForExecutor;
function combineOptionsForGenerator(commandLineOpts, collectionName, generatorName, wc, schema, isInteractive, defaultProjectName, relativeCwd, isVerbose = false) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
const generatorDefaults = wc
? getGeneratorDefaults(defaultProjectName, wc, collectionName, generatorName)
: {};
let combined = convertAliases(coerceTypesInOptions(Object.assign(Object.assign({}, generatorDefaults), commandLineOpts), schema), schema, false);
convertSmartDefaultsIntoNamedParams(combined, schema, defaultProjectName, relativeCwd);
if (isInteractive && isTTY()) {
combined = yield promptForValues(combined, schema, wc);
}
warnDeprecations(combined, schema);
setDefaults(combined, schema);
validateOptsAgainstSchema(combined, schema);
applyVerbosity(combined, schema, isVerbose);
return combined;
});
}
exports.combineOptionsForGenerator = combineOptionsForGenerator;
function warnDeprecations(opts, schema) {
Object.keys(opts).forEach((option) => {
var _a;
const deprecated = (_a = schema.properties[option]) === null || _a === void 0 ? void 0 : _a['x-deprecated'];
if (deprecated) {
logger_1.logger.warn(`Option "${option}" is deprecated${typeof deprecated == 'string' ? ': ' + deprecated : '.'}`);
}
});
}
exports.warnDeprecations = warnDeprecations;
function convertSmartDefaultsIntoNamedParams(opts, schema, defaultProjectName, relativeCwd) {
const argv = opts['_'] || [];
const usedPositionalArgs = {};
Object.entries(schema.properties).forEach(([k, v]) => {
if (opts[k] === undefined &&
v.$default !== undefined &&
v.$default.$source === 'argv' &&
argv[v.$default.index]) {
usedPositionalArgs[v.$default.index] = true;
opts[k] = coerceType(v, argv[v.$default.index]);
}
else if (v.$default !== undefined && v.$default.$source === 'unparsed') {
opts[k] = opts['__overrides_unparsed__'] || [];
}
else if (opts[k] === undefined &&
v.$default !== undefined &&
v.$default.$source === 'projectName' &&
defaultProjectName) {
opts[k] = defaultProjectName;
}
else if (opts[k] === undefined &&
v.format === 'path' &&
v.visible === false &&
relativeCwd) {
opts[k] = relativeCwd.replace(/\\/g, '/');
}
});
const leftOverPositionalArgs = [];
for (let i = 0; i < argv.length; ++i) {
if (!usedPositionalArgs[i]) {
leftOverPositionalArgs.push(argv[i]);
}
}
if (leftOverPositionalArgs.length === 0) {
delete opts['_'];
}
else {
opts['_'] = leftOverPositionalArgs;
}
delete opts['__overrides_unparsed__'];
}
exports.convertSmartDefaultsIntoNamedParams = convertSmartDefaultsIntoNamedParams;
function getGeneratorDefaults(projectName, wc, collectionName, generatorName) {
var _a, _b;
let defaults = {};
if (wc === null || wc === void 0 ? void 0 : wc.generators) {
if ((_a = wc.generators[collectionName]) === null || _a === void 0 ? void 0 : _a[generatorName]) {
defaults = Object.assign(Object.assign({}, defaults), wc.generators[collectionName][generatorName]);
}
if (wc.generators[`${collectionName}:${generatorName}`]) {
defaults = Object.assign(Object.assign({}, defaults), wc.generators[`${collectionName}:${generatorName}`]);
}
}
if (projectName && ((_b = wc === null || wc === void 0 ? void 0 : wc.projects[projectName]) === null || _b === void 0 ? void 0 : _b.generators)) {
const g = wc.projects[projectName].generators;
if (g[collectionName] && g[collectionName][generatorName]) {
defaults = Object.assign(Object.assign({}, defaults), g[collectionName][generatorName]);
}
if (g[`${collectionName}:${generatorName}`]) {
defaults = Object.assign(Object.assign({}, defaults), g[`${collectionName}:${generatorName}`]);
}
}
return defaults;
}
function promptForValues(opts, schema, wc) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
const prompts = [];
Object.entries(schema.properties).forEach(([k, v]) => {
var _a;
if (v['x-prompt'] && opts[k] === undefined) {
const question = {
name: k,
};
if (v.default) {
question.initial = v.default;
}
if (typeof v['x-prompt'] === 'string') {
question.message = v['x-prompt'];
if (v.type === 'string' && v.enum && Array.isArray(v.enum)) {
question.type = 'autocomplete';
question.choices = [...v.enum];
}
else if (v.type === 'string' &&
((_a = v.$default) === null || _a === void 0 ? void 0 : _a.$source) === 'projectName' &&
wc) {
question.type = 'autocomplete';
question.choices = Object.keys(wc.projects);
}
else {
question.type = v.type === 'boolean' ? 'confirm' : 'input';
}
}
else if (v['x-prompt'].type == 'number') {
question.message = v['x-prompt'].message;
question.type = 'numeral';
}
else if (v['x-prompt'].type == 'confirmation' ||
v['x-prompt'].type == 'confirm') {
question.message = v['x-prompt'].message;
question.type = 'confirm';
}
else {
question.message = v['x-prompt'].message;
question.type = v['x-prompt'].multiselect
? 'multiselect'
: 'autocomplete';
question.choices =
v['x-prompt'].items &&
v['x-prompt'].items.map((item) => {
if (typeof item == 'string') {
return item;
}
else {
return {
message: item.label,
name: item.value,
};
}
});
}
prompts.push(question);
}
});
return yield (yield Promise.resolve().then(() => require('enquirer')))
.prompt(prompts)
.then((values) => (Object.assign(Object.assign({}, opts), values)))
.catch((e) => {
console.error(e);
process.exit(0);
});
});
}
function findSchemaForProperty(propName, schema) {
if (propName in schema.properties) {
return {
name: propName,
description: schema.properties[propName],
};
}
const found = Object.entries(schema.properties).find(([_, d]) => d.alias === propName ||
(Array.isArray(d.aliases) && d.aliases.includes(propName)));
if (found) {
const [name, description] = found;
return { name, description };
}
return null;
}
function isTTY() {
return !!process.stdout.isTTY && process.env['CI'] !== 'true';
}
/**
* Verifies whether the given value can be converted to a boolean
* @param value
*/
function isConvertibleToBoolean(value) {
if ('boolean' === typeof value) {
return true;
}
if ('string' === typeof value && /true|false/.test(value)) {
return true;
}
return false;
}
/**
* Verifies whether the given value can be converted to a number
* @param value
*/
function isConvertibleToNumber(value) {
// exclude booleans explicitly
if ('boolean' === typeof value) {
return false;
}
return !isNaN(+value);
}
//# sourceMappingURL=params.js.map
;