diff --git a/src/cli/cli.spec.ts b/src/cli/cli.spec.ts index fa5891a3709..08062ce1711 100644 --- a/src/cli/cli.spec.ts +++ b/src/cli/cli.spec.ts @@ -280,6 +280,25 @@ class MockCommandWithSchemaAndRequiredOptions extends AnonymousCommand { } } +class MockCommandWithSchemaAndBoolRequiredOption extends AnonymousCommand { + public get name(): string { + return 'cli mock schema bool required'; + } + public get description(): string { + return 'Mock command with schema and required options'; + } + public get schema(): z.ZodTypeAny { + return globalOptionsZod + .extend({ + url: z.string(), + bool: z.boolean() + }) + .strict(); + } + public async commandAction(): Promise { + } +} + describe('cli', () => { let rootFolder: string; let cliLogStub: sinon.SinonStub; @@ -295,6 +314,7 @@ describe('cli', () => { let mockCommandWithValidation: Command; let mockCommandWithSchema: Command; let mockCommandWithSchemaAndRequiredOptions: Command; + let mockCommandWithSchemaAndBoolRequiredOption: Command; let log: string[] = []; let mockCommandWithBooleanRewrite: Command; @@ -318,6 +338,7 @@ describe('cli', () => { mockCommandWithValidation = new MockCommandWithValidation(); mockCommandWithSchema = new MockCommandWithSchema(); mockCommandWithSchemaAndRequiredOptions = new MockCommandWithSchemaAndRequiredOptions(); + mockCommandWithSchemaAndBoolRequiredOption = new MockCommandWithSchemaAndBoolRequiredOption(); mockCommandWithOptionSets = new MockCommandWithOptionSets(); mockCommandActionSpy = sinon.spy(mockCommand, 'action'); @@ -339,6 +360,7 @@ describe('cli', () => { cli.getCommandInfo(mockCommandWithValidation, 'cli-validation-mock.js', 'help.mdx'), cli.getCommandInfo(mockCommandWithSchema, 'cli-schema-mock.js', 'help.mdx'), cli.getCommandInfo(mockCommandWithSchemaAndRequiredOptions, 'cli-schema-mock.js', 'help.mdx'), + cli.getCommandInfo(mockCommandWithSchemaAndBoolRequiredOption, 'cli-schema-mock.js', 'help.mdx'), cli.getCommandInfo(cliCompletionUpdateCommand, 'cli/commands/completion/completion-clink-update.js', 'cli/completion/completion-clink-update.mdx'), cli.getCommandInfo(mockCommandWithBooleanRewrite, 'cli-boolean-rewrite-mock.js', 'help.mdx') ]; @@ -1093,6 +1115,29 @@ describe('cli', () => { }); }); + it(`throws error if coercing value fails when validating schema`, (done) => { + cli.commandToExecute = cli.commands.find(c => c.name === 'cli mock schema bool required'); + sinon.stub(cli, 'getSettingWithDefaultValue').callsFake((settingName, defaultValue) => { + if (settingName === settingsNames.prompt) { + return true; + } + return defaultValue; + }); + sinon.stub(cli, 'promptForValue').resolves('nonbool'); + const executeCommandSpy = sinon.spy(cli, 'executeCommand'); + cli + .execute(['cli', 'mock', 'schema', 'bool', 'required', '--url', 'https://contoso.sharepoint.com']) + .then(_ => done('Promise fulfilled while error expected'), _ => { + try { + assert(executeCommandSpy.notCalled); + done(); + } + catch (e) { + done(e); + } + }); + }); + it(`throws an error when command's schema-based validation failed`, (done) => { cli.commandToExecute = cli.commands.find(c => c.name === 'cli mock schema'); const mockCommandWithSchemaActionSpy: sinon.SinonSpy = sinon.spy(mockCommandWithSchema, 'action'); @@ -1689,7 +1734,7 @@ describe('cli', () => { await cli.loadCommandFromArgs(['spo', 'site', 'list']); cli.printAvailableCommands(); - assert(cliLogStub.calledWith(' cli * 10 commands')); + assert(cliLogStub.calledWith(' cli * 11 commands')); }); it(`prints commands from the specified group`, async () => { @@ -1702,7 +1747,7 @@ describe('cli', () => { }; cli.printAvailableCommands(); - assert(cliLogStub.calledWith(' cli mock * 7 commands')); + assert(cliLogStub.calledWith(' cli mock * 8 commands')); }); it(`prints commands from the root group when the specified string doesn't match any group`, async () => { @@ -1715,7 +1760,7 @@ describe('cli', () => { }; cli.printAvailableCommands(); - assert(cliLogStub.calledWith(' cli * 10 commands')); + assert(cliLogStub.calledWith(' cli * 11 commands')); }); it(`runs properly when context file not found`, async () => { diff --git a/src/cli/cli.ts b/src/cli/cli.ts index be5a5e60394..774d63ba719 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -194,9 +194,17 @@ async function execute(rawArgs: string[]): Promise { await cli.error('🌶️ Provide values for the following parameters:'); for (const error of result.error.errors) { - const optionInfo = cli.commandToExecute!.options.find(o => o.name === error.path.join('.')); + const optionName = error.path.join('.'); + const optionInfo = cli.commandToExecute.options.find(o => o.name === optionName); const answer = await cli.promptForValue(optionInfo!); - cli.optionsFromArgs!.options[error.path.join('.')] = answer; + // coerce the answer to the correct type + try { + const parsed = getCommandOptionsFromArgs([`--${optionName}`, answer], cli.commandToExecute); + cli.optionsFromArgs.options[optionName] = parsed[optionName]; + } + catch (e: any) { + return cli.closeWithError(e.message, cli.optionsFromArgs, true); + } } } else {