Skip to content

Refactor command configuration #3409

@waldekmastykarz

Description

@waldekmastykarz

Refactor command configuration so that we can have a central place for configuring validation, options, telemetry, etc.

Update commands:

  • aad app add
  • aad app get
  • aad app remove
  • aad app role add
  • aad app role list
  • aad app role remove
  • aad app set
  • aad approleassignment add
  • aad approleassignment list
  • aad approleassignment remove
  • aad group list
  • aad groupsetting add
  • aad groupsetting get
  • aad groupsetting list
  • aad groupsetting remove
  • aad groupsetting set
  • aad groupsettingtemplate get
  • aad groupsettingtemplate list
  • aad o365group add
  • aad o365group conversation list
  • aad o365group conversation post list
  • aad o365group get
  • aad o365group list
  • aad o365group recyclebinitem clear
  • aad o365group recyclebinitem list
  • aad o365group recyclebinitem restore
  • aad o365group remove
  • aad o365group renew
  • aad o365group report activitycounts
  • aad o365group report activitydetail
  • aad o365group report activityfilecounts
  • aad o365group report activitygroupcounts
  • aad o365group report activitystorage
  • aad o365group set
  • aad o365group teamify
  • aad o365group user add
  • aad o365group user list
  • aad o365group user remove
  • aad o365group user set
  • aad oauth2grant add
  • aad oauth2grant list
  • aad oauth2grant remove
  • aad oauth2grant set
  • aad policy list
  • aad siteclassification disable
  • aad siteclassification enable
  • aad siteclassification get
  • aad siteclassification set
  • aad sp add
  • aad sp get
  • aad user get
  • aad user hibp
  • aad user list
  • aad user password validate
  • aad user set
  • aad user signin list
  • adaptivecard send
  • app get
  • app open
  • app permission list
  • cli consent
  • cli doctor
  • cli issue
  • cli reconsent
  • cli completion clink update
  • cli completion pwsh setup
  • cli completion pwsh update
  • cli completion sh setup
  • cli completion sh update
  • cli config get
  • cli config reset
  • cli config set
  • login
  • logout
  • status
  • version
  • file convert pdf
  • file add
  • file list
  • flow environment get
  • flow environment list
  • flow disable
  • flow enable
  • flow export
  • flow get
  • flow list
  • flow remove
  • flow run cancel
  • flow run get
  • flow run list
  • flow run resubmit
  • graph schemaextension add
  • graph schemaextension get
  • graph schemaextension list
  • graph schemaextension remove
  • graph schemaextension set
  • graph subscription add
  • onedrive list
  • onedrive report activityfilecounts
  • onedrive report activityusercounts
  • onedrive report activityuserdetail
  • onedrive report usageaccountcounts
  • onedrive report usageaccountdetail
  • onedrive report usagefilecounts
  • onedrive report usagestorage
  • outlook mail send
  • outlook message list
  • outlook message move
  • outlook report mailactivitycounts
  • outlook report mailactivityusercounts
  • outlook report mailactivityuserdetail
  • outlook report mailappusageappsusercounts
  • outlook report mailappusageusercounts
  • outlook report mailappusageuserdetail
  • outlook report mailappusageversionsusercounts
  • outlook report mailboxusagedetail
  • outlook report mailboxusagemailboxcount
  • outlook report mailboxusagequotastatusmailboxcounts
  • outlook report mailboxusagestorage
  • outlook room list
  • outlook roomlist list
  • pa app get
  • pa app list
  • pa app remove
  • pa connector export
  • pa connector list
  • pa environment get
  • pa environment list
  • pa pcf init
  • pa solution init
  • pa solution reference add
  • planner bucket add
  • planner bucket get
  • planner bucket list
  • planner bucket remove
  • planner bucket set
  • planner plan add
  • planner plan details get
  • planner plan get
  • planner plan list
  • planner task add
  • planner task checklistitem add
  • planner task details get
  • planner task get
  • planner task list
  • planner task reference add
  • planner task reference list
  • planner task remove
  • planner task set
  • planner tenant settings list
  • pp environment list
  • pp gateway list
  • pp managementapp add
  • pp managementapp list
  • search externalconnection add
  • search externalconnection list
  • skype report activitycounts
  • skype report activityusercounts
  • skype report activityuserdetail
  • spfx package generate
  • spfx project doctor
  • spfx project externalize
  • spfx project rename
  • spfx project upgrade
  • spfx doctor
  • spo app add
  • spo app deploy
  • spo app get
  • spo app install
  • spo app instance list
  • spo app list
  • spo app remove
  • spo app retract
  • spo app teamspackage download
  • spo app uninstall
  • spo app upgrade
  • spo apppage add
  • spo apppage set
  • spo cdn get
  • spo cdn origin add
  • spo cdn origin list
  • spo cdn origin remove
  • spo cdn policy list
  • spo cdn policy set
  • spo cdn set
  • spo contenttype add
  • spo contenttype field remove
  • spo contenttype field set
  • spo contenttype get
  • spo contenttype list
  • spo contenttype remove
  • spo contenttypehub get
  • spo customaction add
  • spo customaction clear
  • spo customaction get
  • spo customaction list
  • spo customaction remove
  • spo customaction set
  • spo eventreceiver get
  • spo eventreceiver list
  • spo externaluser list
  • spo feature disable
  • spo feature enable
  • spo feature list
  • spo field add
  • spo field get
  • spo field list
  • spo field remove
  • spo field set
  • spo file add
  • spo file checkin
  • spo file checkout
  • spo file copy
  • spo file get
  • spo file list
  • spo file move
  • spo file remove
  • spo file sharinginfo get
  • spo folder add
  • spo folder copy
  • spo folder get
  • spo folder list
  • spo folder move
  • spo folder remove
  • spo folder rename
  • spo group get
  • spo group list
  • spo group remove
  • spo group user add
  • spo group user list
  • spo group user remove
  • spo hidedefaultthemes get
  • spo hidedefaultthemes set
  • spo homesite get
  • spo homesite remove
  • spo homesite set
  • spo hubsite connect
  • spo hubsite data get
  • spo hubsite disconnect
  • spo hubsite get
  • spo hubsite list
  • spo hubsite register
  • spo hubsite rights grant
  • spo hubsite rights revoke
  • spo hubsite set
  • spo hubsite theme sync
  • spo hubsite unregister
  • spo knowledgehub get
  • spo knowledgehub remove
  • spo knowledgehub set
  • spo list add
  • spo list contenttype add
  • spo list contenttype default set
  • spo list contenttype list
  • spo list contenttype remove
  • spo list get
  • spo list label get
  • spo list label set
  • spo list list
  • spo list remove
  • spo list roleinheritance break
  • spo list roleinheritance reset
  • spo list set
  • spo list sitescript get
  • spo list view add
  • spo list view field add
  • spo list view field remove
  • spo list view field set
  • spo list view get
  • spo list view list
  • spo list view remove
  • spo list view set
  • spo list webhook add
  • spo list webhook get
  • spo list webhook list
  • spo list webhook remove
  • spo list webhook set
  • spo listitem add
  • spo listitem attachment list
  • spo listitem get
  • spo listitem isrecord
  • spo listitem list
  • spo listitem record declare
  • spo listitem record undeclare
  • spo listitem remove
  • spo listitem roleinheritance break
  • spo listitem roleinheritance reset
  • spo listitem set
  • spo mail send
  • spo navigation node add
  • spo navigation node list
  • spo navigation node remove
  • spo orgassetslibrary add
  • spo orgassetslibrary list
  • spo orgassetslibrary remove
  • spo orgnewssite list
  • spo orgnewssite remove
  • spo orgnewssite set
  • spo page add
  • spo page clientsidewebpart add
  • spo page column get
  • spo page column list
  • spo page control get
  • spo page control list
  • spo page control set
  • spo page copy
  • spo page get
  • spo page header set
  • spo page list
  • spo page remove
  • spo page section add
  • spo page section get
  • spo page section list
  • spo page set
  • spo page template list
  • spo page text add
  • spo propertybag get
  • spo propertybag list
  • spo propertybag remove
  • spo propertybag set
  • spo report activityfilecounts
  • spo report activitypages
  • spo report activityusercounts
  • spo report activityuserdetail
  • spo report siteusagedetail
  • spo report siteusagefilecounts
  • spo report siteusagepages
  • spo report siteusagesitecounts
  • spo report siteusagestorage
  • spo roledefinition list
  • spo roledefinition remove
  • spo serviceprincipal grant add
  • spo serviceprincipal grant list
  • spo serviceprincipal grant revoke
  • spo serviceprincipal permissionrequest approve
  • spo serviceprincipal permissionrequest deny
  • spo serviceprincipal permissionrequest list
  • spo serviceprincipal set
  • spo site add
  • spo site appcatalog add
  • spo site appcatalog remove
  • spo site apppermission add
  • spo site apppermission get
  • spo site apppermission list
  • spo site apppermission remove
  • spo site apppermission set
  • spo site chrome set
  • spo site classic add
  • spo site classic list
  • spo site classic set
  • spo site commsite enable
  • spo site ensure
  • spo site get
  • spo site groupify
  • spo site inplacerecordsmanagement set
  • spo site list
  • spo site recyclebinitem list
  • spo site recyclebinitem restore
  • spo site remove
  • spo site rename
  • spo site set
  • spo sitedesign add
  • spo sitedesign apply
  • spo sitedesign get
  • spo sitedesign list
  • spo sitedesign remove
  • spo sitedesign rights grant
  • spo sitedesign rights list
  • spo sitedesign rights revoke
  • spo sitedesign run list
  • spo sitedesign run status get
  • spo sitedesign set
  • spo sitedesign task get
  • spo sitedesign task list
  • spo sitedesign task remove
  • spo sitescript add
  • spo sitescript get
  • spo sitescript list
  • spo sitescript remove
  • spo sitescript set
  • spo get
  • spo search
  • spo set
  • spo storageentity get
  • spo storageentity list
  • spo storageentity remove
  • spo storageentity set
  • spo tenant appcatalog add
  • spo tenant appcatalogurl get
  • spo tenant recyclebinitem list
  • spo tenant recyclebinitem remove
  • spo tenant recyclebinitem restore
  • spo tenant settings list
  • spo tenant settings set
  • spo term add
  • spo term get
  • spo term group add
  • spo term group get
  • spo term group list
  • spo term list
  • spo term set add
  • spo term set get
  • spo term set list
  • spo theme apply
  • spo theme get
  • spo theme list
  • spo theme remove
  • spo theme set
  • spo user get
  • spo user list
  • spo user remove
  • spo userprofile get
  • spo userprofile set
  • spo web add
  • spo web clientsidewebpart list
  • spo web get
  • spo web installedlanguage list
  • spo web list
  • spo web reindex
  • spo web remove
  • spo web set
  • teams app install
  • teams app list
  • teams app publish
  • teams app remove
  • teams app uninstall
  • teams app update
  • teams channel add
  • teams channel get
  • teams channel list
  • teams channel member add
  • teams channel member list
  • teams channel member remove
  • teams channel member set
  • teams channel remove
  • teams channel set
  • teams chat get
  • teams chat list
  • teams chat member list
  • teams chat message list
  • teams chat message send
  • teams funsettings list
  • teams funsettings set
  • teams guestsettings list
  • teams guestsettings set
  • teams membersettings list
  • teams membersettings set
  • teams message get
  • teams message list
  • teams message reply list
  • teams messagingsettings list
  • teams messagingsettings set
  • teams report deviceusagedistributionusercounts
  • teams report deviceusageusercounts
  • teams report deviceusageuserdetail
  • teams report directroutingcalls
  • teams report pstncalls
  • teams report useractivitycounts
  • teams report useractivityusercounts
  • teams report useractivityuserdetail
  • teams tab add
  • teams tab get
  • teams tab list
  • teams tab remove
  • teams team add
  • teams team archive
  • teams team clone
  • teams team get
  • teams team list
  • teams team remove
  • teams team set
  • teams team unarchive
  • teams user app add
  • teams user app list
  • teams user app remove
  • teams user list
  • tenant id get
  • tenant report activeusercounts
  • tenant report activeuserdetail
  • tenant report office365activationcounts
  • tenant report office365activationsusercounts
  • tenant report office365activationsuserdetail
  • tenant report servicesusercounts
  • tenant security alerts list
  • tenant serviceannouncement health get
  • tenant serviceannouncement health list
  • tenant serviceannouncement healthissue get
  • tenant serviceannouncement healthissue list
  • tenant serviceannouncement message get
  • tenant serviceannouncement message list
  • todo list add
  • todo list list
  • todo list remove
  • todo list set
  • todo task add
  • todo task list
  • todo task remove
  • todo task set
  • util accesstoken get
  • viva connections app create
  • yammer group list
  • yammer group user add
  • yammer group user remove
  • yammer message add
  • yammer message get
  • yammer message like set
  • yammer message list
  • yammer message remove
  • yammer network list
  • yammer report activitycounts
  • yammer report activityusercounts
  • yammer report activityuserdetail
  • yammer report deviceusagedistributionusercounts
  • yammer report deviceusageusercounts
  • yammer report deviceusageuserdetail
  • yammer report groupsactivitycounts
  • yammer report groupsactivitydetail
  • yammer report groupsactivitygroupcounts
  • yammer user get
  • yammer user list
  • yammer search
  • fix compilation errors
  • fix broken tests
  • fix coverage

OK, to make things more tangible, what if we had something like this:

// base Command class that contains logic shared by all commands
abstract class Command {
  // arrays that hold delegates for configuring the command
  public options: CommandOption[] = [];
  public validations: ((args: CommandArgs) => boolean | string)[] = [];
  public telemetry: ((args: CommandArgs) => void)[] = [];

  protected telemetryProperties: any = {};

  // we configure the command directly in the constructor so that we don't
  // need to do any extra calls anywhere else
  constructor() {
    // we break down configuration into separate functions for readability
    this.#initOptions();
    this.#initValidations();
    this.#initTelemetry();

    // these functions must be defined with # so that they're truly private
    // otherwise you'll get a ts2415 error (Types have separate declarations of a private property 'x'.)
    // another way to avoid it is to init everything directly in the constructor
    // without breaking it down into separate functions
    // `private` in TS is a design-time flag and private members end-up being
    // regular class properties that would collide on runtime, which is why we need the extra `#`
  }

  #initTelemetry(): void {
    // rather than configuring the telemetryProperties object directly,
    // we return a function that we'll run later on, because when the command
    // is instantiated, args haven't been parsed and validated yet
    this.telemetry.push(
      (args) => {
        this.telemetryProperties.query = args.options.query;
        this.telemetryProperties.output = args.options.output || 'json';
      }
    );
  }

  #initOptions(): void {
    this.options.push(
      { option: '--query [query]' },
      {
        option: '-o, --output [output]',
        autocomplete: ['csv', 'json', 'text']
      },
      { option: '--verbose' },
      { option: '--debug' }
    );
  }

  #initValidations(): void {
    this.validations.push(
      // common validation logic that we move from the CLI runtime to the Command
      (args) => this.validateRequiredOptions(args),
      (args) => this.validateOptionSets(args),
      // command-specific validation function; it's up to us to decide if
      // each validation is a separate function or if we want to move them
      // as-is from the current `validate` method in each command
      (args) => {
        if (args.options.output) {
          const outputOption = this.options.find(o => o.option.indexOf('--output') > -1);
          if (outputOption!.autocomplete!.indexOf(args.options.output) < 0) {
            return `${args.options.output} is not a valid output. Allowed values are ${outputOption!.autocomplete!.join(', ')}`;
          }
        }

        return true;
      }
    );
  }

  // validation logic from the CLI runtime that we move to the Command
  private validateRequiredOptions(args: CommandArgs): boolean | string {
  }
  private validateOptionSets(args: CommandArgs): boolean | string {
  }

  // called by the CLI runtime to validate command's args
  public validate(args: CommandArgs): boolean | string {
    for (const validation of this.validations) {
      const result = validation(args);
      if (typeof result === 'string') {
        return result;
      }
    }

    return true;
  }
}

// sample command
class SpoSiteGetCommand extends Command {
  constructor() {
    // we call super to include config from the base command class
    super();

    this.#initOptions();
    this.#initValidations();
    this.#initTelemetry();
  }

  #initTelemetry(): void {
    this.telemetry.push(
      (args) => {
        this.telemetryProperties.url = typeof args.options.url !== 'undefined';
        this.telemetryProperties.id = typeof args.options.id !== 'undefined';
      }
    );
  }

  #initOptions(): void {
    this.options.push(
      { option: '--url [url]' },
      { option: '--id [id]' }
    );
  }

  #initValidations(): void {
    this.validations.push(
      (args) => {
        if (args.options.url) {
          return validation.isValidSharePointUrl(args.options.url);
        }

        return true;
      }
    );
  }
}

I haven't included everything in the example above (types, option sets, aliases, default properties, processing options), but I hope the example gives a clearer idea of the possible direction we could take. Features missing from this example would be basically 'more of the same' (array with functions that the runtime would execute).

Originally posted by @waldekmastykarz in #3218 (comment)

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions