@@ -297,6 +297,66 @@ class MockCommandWithSchemaAndBoolRequiredOption extends AnonymousCommand {
297297 }
298298}
299299
300+ const refinedSchemaOptions = z . strictObject ( {
301+ ...globalOptionsZod . shape ,
302+ authType : z . string ( ) . optional ( ) ,
303+ userName : z . string ( ) . optional ( ) ,
304+ password : z . string ( ) . optional ( ) ,
305+ certificateFile : z . string ( ) . optional ( ) ,
306+ certificateBase64Encoded : z . string ( ) . optional ( )
307+ } ) ;
308+
309+ class MockCommandWithRefinedSchema extends AnonymousCommand {
310+ public get name ( ) : string {
311+ return 'cli mock schema refined' ;
312+ }
313+ public get description ( ) : string {
314+ return 'Mock command with refined schema' ;
315+ }
316+ public get schema ( ) : z . ZodType {
317+ return refinedSchemaOptions ;
318+ }
319+ public getRefinedSchema ( schema : typeof refinedSchemaOptions ) : z . ZodObject < any > | undefined {
320+ return schema
321+ . refine ( options => options . authType !== 'password' || options . userName , {
322+ error : 'Username is required when using password authentication.' ,
323+ path : [ 'userName' ] ,
324+ params : {
325+ customCode : 'required'
326+ }
327+ } )
328+ . refine ( options => options . authType !== 'password' || options . password , {
329+ error : 'Password is required when using password authentication.' ,
330+ path : [ 'password' ] ,
331+ params : {
332+ customCode : 'required'
333+ }
334+ } )
335+ . refine ( options => options . authType !== 'certificate' || ! ( options . certificateFile && options . certificateBase64Encoded ) , {
336+ error : 'Specify either certificateFile or certificateBase64Encoded, but not both.' ,
337+ path : [ 'certificateBase64Encoded' ] ,
338+ params : {
339+ customCode : 'optionSet' ,
340+ options : [ 'certificateFile' , 'certificateBase64Encoded' ]
341+ }
342+ } )
343+ . refine ( options => options . authType !== 'certificate' || options . certificateFile || options . certificateBase64Encoded , {
344+ error : 'Specify either certificateFile or certificateBase64Encoded.' ,
345+ path : [ 'certificateFile' ] ,
346+ params : {
347+ customCode : 'optionSet' ,
348+ options : [ 'certificateFile' , 'certificateBase64Encoded' ]
349+ }
350+ } )
351+ . refine ( options => options . authType !== 'invalid' || false , {
352+ error : 'Invalid authentication type.' ,
353+ path : [ 'authType' ]
354+ } ) ;
355+ }
356+ public async commandAction ( ) : Promise < void > {
357+ }
358+ }
359+
300360describe ( 'cli' , ( ) => {
301361 let rootFolder : string ;
302362 let cliLogStub : sinon . SinonStub ;
@@ -313,6 +373,7 @@ describe('cli', () => {
313373 let mockCommandWithSchema : Command ;
314374 let mockCommandWithSchemaAndRequiredOptions : Command ;
315375 let mockCommandWithSchemaAndBoolRequiredOption : Command ;
376+ let mockCommandWithRefinedSchema : Command ;
316377 let log : string [ ] = [ ] ;
317378 let mockCommandWithBooleanRewrite : Command ;
318379
@@ -337,6 +398,7 @@ describe('cli', () => {
337398 mockCommandWithSchema = new MockCommandWithSchema ( ) ;
338399 mockCommandWithSchemaAndRequiredOptions = new MockCommandWithSchemaAndRequiredOptions ( ) ;
339400 mockCommandWithSchemaAndBoolRequiredOption = new MockCommandWithSchemaAndBoolRequiredOption ( ) ;
401+ mockCommandWithRefinedSchema = new MockCommandWithRefinedSchema ( ) ;
340402 mockCommandWithOptionSets = new MockCommandWithOptionSets ( ) ;
341403 mockCommandActionSpy = sinon . spy ( mockCommand , 'action' ) ;
342404
@@ -359,6 +421,7 @@ describe('cli', () => {
359421 cli . getCommandInfo ( mockCommandWithSchema , 'cli-schema-mock.js' , 'help.mdx' ) ,
360422 cli . getCommandInfo ( mockCommandWithSchemaAndRequiredOptions , 'cli-schema-mock.js' , 'help.mdx' ) ,
361423 cli . getCommandInfo ( mockCommandWithSchemaAndBoolRequiredOption , 'cli-schema-mock.js' , 'help.mdx' ) ,
424+ cli . getCommandInfo ( mockCommandWithRefinedSchema , 'cli-schema-refined-mock.js' , 'help.mdx' ) ,
362425 cli . getCommandInfo ( cliCompletionUpdateCommand , 'cli/commands/completion/completion-clink-update.js' , 'cli/completion/completion-clink-update.mdx' ) ,
363426 cli . getCommandInfo ( mockCommandWithBooleanRewrite , 'cli-boolean-rewrite-mock.js' , 'help.mdx' )
364427 ] ;
@@ -395,6 +458,7 @@ describe('cli', () => {
395458 cli . loadAllCommandsInfo ,
396459 cli . getConfig ( ) . get ,
397460 cli . loadCommandFromFile ,
461+ cli . promptForValue ,
398462 browserUtil . open
399463 ] ) ;
400464 } ) ;
@@ -1154,6 +1218,94 @@ describe('cli', () => {
11541218 } ) ;
11551219 } ) ;
11561220
1221+ it ( `prompts for missing required options from refined schema when prompting enabled` , async ( ) => {
1222+ cli . commandToExecute = cli . commands . find ( c => c . name === 'cli mock schema refined' ) ;
1223+ const promptInputStub : sinon . SinonStub = sinon . stub ( prompt , 'forInput' )
1224+ . onFirstCall ( ) . resolves ( 'user@contoso.com' )
1225+ . onSecondCall ( ) . resolves ( 'pass@word1' ) ;
1226+ sinon . stub ( cli , 'getSettingWithDefaultValue' ) . callsFake ( ( settingName , defaultValue ) => {
1227+ if ( settingName === settingsNames . prompt ) {
1228+ return true ;
1229+ }
1230+ return defaultValue ;
1231+ } ) ;
1232+ const executeCommandSpy = sinon . spy ( cli , 'executeCommand' ) ;
1233+
1234+ await cli . execute ( [ 'cli' , 'mock' , 'schema' , 'refined' , '--authType' , 'password' ] ) ;
1235+ assert ( cliErrorStub . calledWith ( '🌶️ Provide values for the following parameters:' ) ) ;
1236+ assert . strictEqual ( promptInputStub . callCount , 2 ) ;
1237+ assert ( executeCommandSpy . called ) ;
1238+ } ) ;
1239+
1240+ it ( `prompts for option set selection from refined schema when prompting enabled` , async ( ) => {
1241+ cli . commandToExecute = cli . commands . find ( c => c . name === 'cli mock schema refined' ) ;
1242+ const promptSelectionStub : sinon . SinonStub = sinon . stub ( prompt , 'forSelection' ) . resolves ( 'certificateFile' ) ;
1243+ const promptInputStub : sinon . SinonStub = sinon . stub ( prompt , 'forInput' ) . resolves ( '/path/to/cert.pem' ) ;
1244+ sinon . stub ( cli , 'getSettingWithDefaultValue' ) . callsFake ( ( settingName , defaultValue ) => {
1245+ if ( settingName === settingsNames . prompt ) {
1246+ return true ;
1247+ }
1248+ return defaultValue ;
1249+ } ) ;
1250+ const executeCommandSpy = sinon . spy ( cli , 'executeCommand' ) ;
1251+
1252+ await cli . execute ( [ 'cli' , 'mock' , 'schema' , 'refined' , '--authType' , 'certificate' ] ) ;
1253+ assert ( cliErrorStub . calledWith ( '🌶️ Please specify one of the following options:' ) ) ;
1254+ assert ( promptSelectionStub . calledOnce ) ;
1255+ assert . deepStrictEqual ( promptSelectionStub . firstCall . args [ 0 ] . choices , [
1256+ { name : 'certificateFile' , value : 'certificateFile' } ,
1257+ { name : 'certificateBase64Encoded' , value : 'certificateBase64Encoded' }
1258+ ] ) ;
1259+ assert ( promptInputStub . calledOnce ) ;
1260+ assert ( executeCommandSpy . called ) ;
1261+ } ) ;
1262+
1263+ it ( `exits with error for non-required/non-optionSet errors in refined schema when prompting enabled` , ( done ) => {
1264+ cli . commandToExecute = cli . commands . find ( c => c . name === 'cli mock schema refined' ) ;
1265+ sinon . stub ( cli , 'getSettingWithDefaultValue' ) . callsFake ( ( settingName , defaultValue ) => {
1266+ if ( settingName === settingsNames . prompt ) {
1267+ return true ;
1268+ }
1269+ return defaultValue ;
1270+ } ) ;
1271+ const executeCommandSpy = sinon . spy ( cli , 'executeCommand' ) ;
1272+
1273+ cli
1274+ . execute ( [ 'cli' , 'mock' , 'schema' , 'refined' , '--authType' , 'invalid' ] )
1275+ . then ( _ => done ( 'Promise fulfilled while error expected' ) , _ => {
1276+ try {
1277+ assert ( executeCommandSpy . notCalled ) ;
1278+ done ( ) ;
1279+ }
1280+ catch ( e ) {
1281+ done ( e ) ;
1282+ }
1283+ } ) ;
1284+ } ) ;
1285+
1286+ it ( `exits with proper error when prompting disabled and refined schema validation fails` , ( done ) => {
1287+ cli . commandToExecute = cli . commands . find ( c => c . name === 'cli mock schema refined' ) ;
1288+ sinon . stub ( cli , 'getSettingWithDefaultValue' ) . callsFake ( ( settingName , defaultValue ) => {
1289+ if ( settingName === settingsNames . prompt ) {
1290+ return false ;
1291+ }
1292+ return defaultValue ;
1293+ } ) ;
1294+ const executeCommandSpy = sinon . spy ( cli , 'executeCommand' ) ;
1295+
1296+ cli
1297+ . execute ( [ 'cli' , 'mock' , 'schema' , 'refined' , '--authType' , 'password' ] )
1298+ . then ( _ => done ( 'Promise fulfilled while error expected' ) , _ => {
1299+ try {
1300+ assert ( executeCommandSpy . notCalled ) ;
1301+ done ( ) ;
1302+ }
1303+ catch ( e ) {
1304+ done ( e ) ;
1305+ }
1306+ } ) ;
1307+ } ) ;
1308+
11571309 it ( `executes command when validation passed` , async ( ) => {
11581310 cli . commandToExecute = cli . commands . find ( c => c . name === 'cli mock' ) ;
11591311
@@ -1725,7 +1877,7 @@ describe('cli', () => {
17251877 await cli . loadCommandFromArgs ( [ 'spo' , 'site' , 'list' ] ) ;
17261878 cli . printAvailableCommands ( ) ;
17271879
1728- assert ( cliLogStub . calledWith ( ' cli * 11 commands' ) ) ;
1880+ assert ( cliLogStub . calledWith ( ' cli * 12 commands' ) ) ;
17291881 } ) ;
17301882
17311883 it ( `prints commands from the specified group` , async ( ) => {
@@ -1738,7 +1890,7 @@ describe('cli', () => {
17381890 } ;
17391891 cli . printAvailableCommands ( ) ;
17401892
1741- assert ( cliLogStub . calledWith ( ' cli mock * 8 commands' ) ) ;
1893+ assert ( cliLogStub . calledWith ( ' cli mock * 9 commands' ) ) ;
17421894 } ) ;
17431895
17441896 it ( `prints commands from the root group when the specified string doesn't match any group` , async ( ) => {
@@ -1751,7 +1903,7 @@ describe('cli', () => {
17511903 } ;
17521904 cli . printAvailableCommands ( ) ;
17531905
1754- assert ( cliLogStub . calledWith ( ' cli * 11 commands' ) ) ;
1906+ assert ( cliLogStub . calledWith ( ' cli * 12 commands' ) ) ;
17551907 } ) ;
17561908
17571909 it ( `runs properly when context file not found` , async ( ) => {
0 commit comments