diff --git a/packages/nx/src/utils/params.spec.ts b/packages/nx/src/utils/params.spec.ts index 3187073ade..29db263d54 100644 --- a/packages/nx/src/utils/params.spec.ts +++ b/packages/nx/src/utils/params.spec.ts @@ -190,6 +190,43 @@ describe('params', () => { expect(options).toEqual({}); }); + + it('should not throw if missing required property of an optional object', () => { + const commandLineOpts = {}; + const target: TargetConfiguration = { + executor: '@nx/do:stuff', + options: {}, + }; + + const options = combineOptionsForExecutor( + commandLineOpts, + 'production', + target, + { + properties: { + foo: { + type: 'object', + properties: { + bar: { + description: 'The target server ', + type: 'string', + }, + baz: { + type: 'string', + default: 'qux', + }, + }, + required: ['bar'], + }, + }, + required: [], + }, + 'proj', + process.cwd() + ); + + expect(options).toEqual({}); + }); }); describe('coerceTypes', () => { @@ -595,7 +632,9 @@ describe('params', () => { it('should be able to set defaults for underlying properties', () => { const opts = setDefaults( - {}, + { + a: {}, + }, { properties: { a: { @@ -798,6 +837,27 @@ describe('params', () => { ).toThrow("Required property 'a' is missing"); }); + it('should not throw if missing a required property of an optional object', () => { + expect(() => + validateOptsAgainstSchema( + {}, + { + properties: { + a: { + type: 'object', + properties: { + b: { + type: 'boolean', + }, + }, + required: ['b'], + }, + }, + } + ) + ).not.toThrow(); + }); + it('should throw if none of the oneOf conditions are met', () => { expect(() => validateOptsAgainstSchema( @@ -962,13 +1022,20 @@ describe('params', () => { it('should throw if property name matching pattern is not valid', () => { expect(() => validateOptsAgainstSchema( - { a: true, b: false }, + { + a: true, + b: false, + }, { properties: { - a: { type: 'boolean' }, + a: { + type: 'boolean', + }, }, patternProperties: { - '^b$': { type: 'number' }, + '^b$': { + type: 'number', + }, }, additionalProperties: false, } @@ -978,63 +1045,6 @@ describe('params', () => { ); }); - it('should handle properties matching patternProperties schema', () => { - expect(() => - validateOptsAgainstSchema( - { a: true, b: false }, - { - properties: { - a: { type: 'boolean' }, - }, - patternProperties: { - '^b$': { type: 'boolean' }, - }, - additionalProperties: false, - } - ) - ).not.toThrow(); - }); - - it('should throw if additional property does not match schema', () => { - expect(() => - validateOptsAgainstSchema( - { a: true, b: 'b', c: 'c' }, - { - properties: { - a: { type: 'boolean' }, - }, - patternProperties: { - '^b$': { type: 'string' }, - }, - additionalProperties: { - type: 'number', - }, - } - ) - ).toThrow( - "Property 'c' does not match the schema. 'c' should be a 'number'." - ); - }); - - it('should handle additional properties when they match the additionalProperties schema', () => { - expect(() => - validateOptsAgainstSchema( - { a: true, b: 'b', c: 1, d: 2 }, - { - properties: { - a: { type: 'boolean' }, - }, - patternProperties: { - '^b$': { type: 'string' }, - }, - additionalProperties: { - type: 'number', - }, - } - ) - ).not.toThrow(); - }); - it('should throw if found unsupported positional property', () => { expect(() => validateOptsAgainstSchema( @@ -1870,16 +1880,25 @@ describe('params', () => { } ); - expect(prompts).toEqual([ - { - message: 'What kind of pets do you have?', - name: 'pets', - type: 'multiselect', - choices: ['cat', 'dog', 'fish'], - limit: expect.any(Number), - validate: expect.any(Function), - }, - ]); + // Limit is determined by terminal size + // deleting it to make the snapshot consistent + delete prompts[0].limit; + + expect(prompts).toMatchInlineSnapshot(` + [ + { + "choices": [ + "cat", + "dog", + "fish", + ], + "message": "What kind of pets do you have?", + "name": "pets", + "type": "multiselect", + "validate": [Function], + }, + ] + `); }); describe('Project prompts', () => { diff --git a/packages/nx/src/utils/params.ts b/packages/nx/src/utils/params.ts index 3ac0bfa313..5099ab18c9 100644 --- a/packages/nx/src/utils/params.ts +++ b/packages/nx/src/utils/params.ts @@ -536,15 +536,13 @@ function setPropertyDefault( schema: any, definitions: Properties ) { + let defaultValueToSet: any | undefined; + 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') { + if (schema.type === 'array') { const items = schema.items || {}; if ( opts[propName] && @@ -555,20 +553,33 @@ function setPropertyDefault( setDefaultsInObject(valueInArray, items.properties || {}, definitions) ); } else if (!opts[propName] && schema.default) { - opts[propName] = schema.default; + defaultValueToSet = schema.default; } } else { - const wasUndefined = opts[propName] === undefined; - if (wasUndefined) { - // We need an object to set values onto - opts[propName] = {}; + if (opts[propName] === undefined && schema.default !== undefined) { + defaultValueToSet = schema.default; } - setDefaultsInObject(opts[propName], schema.properties || {}, definitions); + if (schema.type === 'object') { + const wasUndefined = opts[propName] === undefined; + if (!wasUndefined) { + 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]; + if (defaultValueToSet !== undefined) { + try { + validateProperty(propName, defaultValueToSet, schema, definitions); + opts[propName] = defaultValueToSet; + } catch (e) { + // If the default value is invalid, we don't set it... + // this should honestly never be needed... but some notable + // 3rd party schema's are invalid. } } }