11import fs from 'fs' ;
22import { v4 } from 'uuid' ;
3+ import { z } from 'zod' ;
34import auth from '../../../../Auth.js' ;
4- import GlobalOptions from '../../../../GlobalOptions.js' ;
55import { Logger } from '../../../../cli/Logger.js' ;
6+ import { globalOptionsZod } from '../../../../Command.js' ;
67import request , { CliRequestOptions } from '../../../../request.js' ;
78import { accessToken } from '../../../../utils/accessToken.js' ;
89import { AppCreationOptions , AppInfo , entraApp } from '../../../../utils/entraApp.js' ;
10+ import { optionsUtils } from '../../../../utils/optionsUtils.js' ;
11+ import { zod } from '../../../../utils/zod.js' ;
912import GraphCommand from '../../../base/GraphCommand.js' ;
1013import { M365RcJson } from '../../../base/M365RcJson.js' ;
1114import commands from '../../commands.js' ;
12- import { optionsUtils } from '../../../../utils/optionsUtils.js' ;
15+
16+ const entraApplicationPlatform = [ 'spa' , 'web' , 'publicClient' ] as const ;
17+ const entraAppScopeConsentBy = [ 'admins' , 'adminsAndUsers' ] as const ;
18+
19+ const options = globalOptionsZod
20+ . extend ( {
21+ name : zod . alias ( 'n' , z . string ( ) . optional ( ) ) ,
22+ multitenant : z . boolean ( ) . optional ( ) ,
23+ redirectUris : zod . alias ( 'r' , z . string ( ) . optional ( ) ) ,
24+ platform : zod . alias ( 'p' , z . enum ( entraApplicationPlatform ) . optional ( ) ) ,
25+ implicitFlow : z . boolean ( ) . optional ( ) ,
26+ withSecret : zod . alias ( 's' , z . boolean ( ) . optional ( ) ) ,
27+ apisDelegated : z . string ( ) . optional ( ) ,
28+ apisApplication : z . string ( ) . optional ( ) ,
29+ uri : zod . alias ( 'u' , z . string ( ) . optional ( ) ) ,
30+ scopeName : z . string ( ) . optional ( ) ,
31+ scopeConsentBy : z . enum ( entraAppScopeConsentBy ) . optional ( ) ,
32+ scopeAdminConsentDisplayName : z . string ( ) . optional ( ) ,
33+ scopeAdminConsentDescription : z . string ( ) . optional ( ) ,
34+ certificateFile : z . string ( ) . optional ( ) ,
35+ certificateBase64Encoded : z . string ( ) . optional ( ) ,
36+ certificateDisplayName : z . string ( ) . optional ( ) ,
37+ manifest : z . string ( ) . optional ( ) ,
38+ save : z . boolean ( ) . optional ( ) ,
39+ grantAdminConsent : z . boolean ( ) . optional ( ) ,
40+ allowPublicClientFlows : z . boolean ( ) . optional ( )
41+ } )
42+ . and ( z . any ( ) ) ;
43+
44+ declare type Options = z . infer < typeof options > & AppCreationOptions ;
1345
1446interface CommandArgs {
1547 options : Options ;
1648}
1749
18- interface Options extends GlobalOptions , AppCreationOptions {
19- grantAdminConsent ?: boolean ;
20- manifest ?: string ;
21- save ?: boolean ;
22- scopeAdminConsentDescription ?: string ;
23- scopeAdminConsentDisplayName ?: string ;
24- scopeConsentBy ?: string ;
25- scopeName ?: string ;
26- uri ?: string ;
27- withSecret : boolean ;
28- }
29-
3050class EntraAppAddCommand extends GraphCommand {
31- private static entraApplicationPlatform : string [ ] = [ 'spa' , 'web' , 'publicClient' ] ;
32- private static entraAppScopeConsentBy : string [ ] = [ 'admins' , 'adminsAndUsers' ] ;
3351 private manifest : any ;
3452 private appName : string = '' ;
3553
@@ -45,173 +63,91 @@ class EntraAppAddCommand extends GraphCommand {
4563 return true ;
4664 }
4765
48- constructor ( ) {
49- super ( ) ;
50-
51- this . #initTelemetry( ) ;
52- this . #initOptions( ) ;
53- this . #initValidators( ) ;
54- this . #initOptionSets( ) ;
55- }
56-
57- #initTelemetry( ) : void {
58- this . telemetry . push ( ( args : CommandArgs ) => {
59- Object . assign ( this . telemetryProperties , {
60- apis : typeof args . options . apisDelegated !== 'undefined' ,
61- implicitFlow : args . options . implicitFlow ,
62- multitenant : args . options . multitenant ,
63- platform : args . options . platform ,
64- redirectUris : typeof args . options . redirectUris !== 'undefined' ,
65- scopeAdminConsentDescription : typeof args . options . scopeAdminConsentDescription !== 'undefined' ,
66- scopeAdminConsentDisplayName : typeof args . options . scopeAdminConsentDisplayName !== 'undefined' ,
67- scopeConsentBy : args . options . scopeConsentBy ,
68- scopeName : typeof args . options . scopeName !== 'undefined' ,
69- uri : typeof args . options . uri !== 'undefined' ,
70- withSecret : args . options . withSecret ,
71- certificateFile : typeof args . options . certificateFile !== 'undefined' ,
72- certificateBase64Encoded : typeof args . options . certificateBase64Encoded !== 'undefined' ,
73- certificateDisplayName : typeof args . options . certificateDisplayName !== 'undefined' ,
74- grantAdminConsent : typeof args . options . grantAdminConsent !== 'undefined' ,
75- allowPublicClientFlows : typeof args . options . allowPublicClientFlows !== 'undefined'
76- } ) ;
77- } ) ;
66+ public get schema ( ) : z . ZodTypeAny | undefined {
67+ return options ;
7868 }
7969
80- #initOptions( ) : void {
81- this . options . unshift (
82- {
83- option : '-n, --name [name]'
84- } ,
85- {
86- option : '--multitenant'
87- } ,
88- {
89- option : '-r, --redirectUris [redirectUris]'
90- } ,
91- {
92- option : '-p, --platform [platform]' ,
93- autocomplete : EntraAppAddCommand . entraApplicationPlatform
94- } ,
95- {
96- option : '--implicitFlow'
97- } ,
98- {
99- option : '-s, --withSecret'
100- } ,
101- {
102- option : '--apisDelegated [apisDelegated]'
103- } ,
104- {
105- option : '--apisApplication [apisApplication]'
106- } ,
107- {
108- option : '-u, --uri [uri]'
109- } ,
110- {
111- option : '--scopeName [scopeName]'
112- } ,
113- {
114- option : '--scopeConsentBy [scopeConsentBy]' ,
115- autocomplete : EntraAppAddCommand . entraAppScopeConsentBy
116- } ,
117- {
118- option : '--scopeAdminConsentDisplayName [scopeAdminConsentDisplayName]'
119- } ,
120- {
121- option : '--scopeAdminConsentDescription [scopeAdminConsentDescription]'
122- } ,
123- {
124- option : '--certificateFile [certificateFile]'
125- } ,
126- {
127- option : '--certificateBase64Encoded [certificateBase64Encoded]'
128- } ,
129- {
130- option : '--certificateDisplayName [certificateDisplayName]'
131- } ,
132- {
133- option : '--manifest [manifest]'
134- } ,
135- {
136- option : '--save'
137- } ,
138- {
139- option : '--grantAdminConsent'
140- } ,
141- {
142- option : '--allowPublicClientFlows'
143- }
144- ) ;
145- }
146-
147- #initValidators( ) : void {
148- this . validators . push (
149- async ( args : CommandArgs ) => {
150- if ( args . options . platform &&
151- EntraAppAddCommand . entraApplicationPlatform . indexOf ( args . options . platform ) < 0 ) {
152- return `${ args . options . platform } is not a valid value for platform. Allowed values are ${ EntraAppAddCommand . entraApplicationPlatform . join ( ', ' ) } ` ;
70+ public getRefinedSchema ( schema : typeof options ) : z . ZodEffects < any > | undefined {
71+ return schema
72+ . refine ( options => {
73+ if ( options . redirectUris && ! options . platform ) {
74+ return false ;
15375 }
154-
155- if ( args . options . redirectUris && ! args . options . platform ) {
156- return ` When you specify redirectUris you also need to specify platform` ;
157- }
158-
159- if ( args . options . platform && [ 'spa' , 'web' , 'publicClient' ] . indexOf ( args . options . platform ) > - 1 && ! args . options . redirectUris ) {
160- return `When you use platform spa, web or publicClient, you'll need to specify redirectUris` ;
76+ return true ;
77+ } , {
78+ message : ' When you specify redirectUris you also need to specify platform'
79+ } )
80+ . refine ( options => {
81+ if ( options . platform && [ 'spa' , 'web' , 'publicClient' ] . includes ( options . platform ) && ! options . redirectUris ) {
82+ return false ;
16183 }
162-
163- if ( args . options . certificateFile && args . options . certificateBase64Encoded ) {
164- return 'Specify either certificateFile or certificateBase64Encoded but not both' ;
84+ return true ;
85+ } , {
86+ message : 'When you use platform spa, web or publicClient, you\'ll need to specify redirectUris'
87+ } )
88+ . refine ( options => {
89+ if ( options . certificateFile && options . certificateBase64Encoded ) {
90+ return false ;
16591 }
166-
167- if ( args . options . certificateDisplayName && ! args . options . certificateFile && ! args . options . certificateBase64Encoded ) {
168- return 'When you specify certificateDisplayName you also need to specify certificateFile or certificateBase64Encoded' ;
92+ return true ;
93+ } , {
94+ message : 'Specify either certificateFile or certificateBase64Encoded but not both'
95+ } )
96+ . refine ( options => {
97+ if ( options . certificateDisplayName && ! options . certificateFile && ! options . certificateBase64Encoded ) {
98+ return false ;
16999 }
170-
171- if ( args . options . certificateFile && ! fs . existsSync ( args . options . certificateFile as string ) ) {
172- return 'Certificate file not found' ;
100+ return true ;
101+ } , {
102+ message : 'When you specify certificateDisplayName you also need to specify certificateFile or certificateBase64Encoded'
103+ } )
104+ . refine ( options => {
105+ if ( options . certificateFile && ! fs . existsSync ( options . certificateFile ) ) {
106+ return false ;
173107 }
174-
175- if ( args . options . scopeName ) {
176- if ( ! args . options . uri ) {
177- return `When you specify scopeName you also need to specify uri` ;
108+ return true ;
109+ } , {
110+ message : 'Certificate file not found'
111+ } )
112+ . refine ( options => {
113+ if ( options . scopeName ) {
114+ if ( ! options . uri ) {
115+ return false ;
178116 }
179-
180- if ( ! args . options . scopeAdminConsentDescription ) {
181- return `When you specify scopeName you also need to specify scopeAdminConsentDescription` ;
117+ if ( ! options . scopeAdminConsentDescription ) {
118+ return false ;
182119 }
183-
184- if ( ! args . options . scopeAdminConsentDisplayName ) {
185- return `When you specify scopeName you also need to specify scopeAdminConsentDisplayName` ;
120+ if ( ! options . scopeAdminConsentDisplayName ) {
121+ return false ;
186122 }
187123 }
188-
189- if ( args . options . scopeConsentBy &&
190- EntraAppAddCommand . entraAppScopeConsentBy . indexOf ( args . options . scopeConsentBy ) < 0 ) {
191- return `${ args . options . scopeConsentBy } is not a valid value for scopeConsentBy. Allowed values are ${ EntraAppAddCommand . entraAppScopeConsentBy . join ( ', ' ) } ` ;
192- }
193-
194- if ( args . options . manifest ) {
124+ return true ;
125+ } , {
126+ message : 'When you specify scopeName you also need to specify uri, scopeAdminConsentDescription, and scopeAdminConsentDisplayName'
127+ } )
128+ . refine ( options => {
129+ if ( options . manifest ) {
195130 try {
196- this . manifest = JSON . parse ( args . options . manifest ) ;
197- if ( ! args . options . name && ! this . manifest . name ) {
198- return `Specify the name of the app to create either through the 'name' option or the 'name' property in the manifest` ;
131+ const manifest = JSON . parse ( options . manifest ) ;
132+ if ( ! options . name && ! manifest . name ) {
133+ return false ;
199134 }
135+ this . manifest = manifest ;
136+ return true ;
200137 }
201138 catch ( e ) {
202- return `Error while parsing the specified manifest: ${ e } ` ;
139+ return false ;
203140 }
204141 }
205-
206142 return true ;
207- } ,
208- ) ;
209- }
210-
211- #initOptionSets ( ) : void {
212- this . optionSets . push (
213- { options : [ ' name' , ' manifest'] }
214- ) ;
143+ } , {
144+ message : 'Specify the name of the app to create either through the \'name\' option or the \'name\' property in the manifest'
145+ } )
146+ . refine ( options => {
147+ return options . name || options . manifest ;
148+ } , {
149+ message : 'Specify either name or manifest'
150+ } ) ;
215151 }
216152
217153 public async commandAction ( logger : Logger , args : CommandArgs ) : Promise < void > {
@@ -230,7 +166,7 @@ class EntraAppAddCommand extends GraphCommand {
230166 } ) ;
231167 let appInfo : any = await entraApp . createAppRegistration ( {
232168 options : args . options ,
233- unknownOptions : optionsUtils . getUnknownOptions ( args . options , this . options ) ,
169+ unknownOptions : optionsUtils . getUnknownOptions ( args . options , zod . schemaToOptions ( this . schema ! ) ) ,
234170 apis,
235171 logger,
236172 verbose : this . verbose ,
@@ -343,7 +279,7 @@ class EntraAppAddCommand extends GraphCommand {
343279 if ( args . options . redirectUris ) {
344280 // take submitted redirectUris/platform as options
345281 // otherwise, they will be removed from the app
346- v2Manifest . replyUrlsWithType = args . options . redirectUris . split ( ',' ) . map ( u => {
282+ v2Manifest . replyUrlsWithType = ( args . options . redirectUris as string ) . split ( ',' ) . map ( u => {
347283 return {
348284 url : u . trim ( ) ,
349285 type : this . translatePlatformToType ( args . options . platform ! )
0 commit comments