Skip to content

Adding a command

Adam Wójcik edited this page Jul 1, 2023 · 9 revisions

The following article describes how to add a new command to the CLI for Microsoft 365.

Command files

Each command consists of three files:

  • command implementation, located under ./src/m365/[service]/commands, eg. ./src/m365/spo/commands/login.ts
  • command unit tests, located under ./src/m365/[service]/commands, eg. ./src/m365/spo/commands/login.spec.ts
  • command documentation page, located under ./docs/docs/cmd/[service], eg. ./docs/docs/cmd/spo/login.mdx

Additionally, each command is listed in:

  • list of all commands for the given service, located in ./src/m365/[service]/commands.ts, eg. ./src/m365/spo/commands.ts
  • the documentation table of contents, located in ./docs/src/config/sidebars.js

Add new files

Commands are organized by the Microsoft 365 service, such as SharePoint Online (spo), that they apply to. Before building your command, find the right folder corresponding to your command in the project structure.

Create new files

In the ./src/m365/[service]/commands folder, create two files for your command: my-command.ts for the command implementation, and my-command.spec.ts for the unit tests.

Define command name constant

In the ./src/m365/[service]/commands.ts file, define a constant with your command's name including the service prefix. You will use this constant to refer to the command in its implementation, unit tests, help, etc.

Add the command manual page

In the ./docs/docs/cmd/[service] folder, create a new file for your command's help page: my-command.mdx. Next, open the ./docs/src/config/sidebars.js file and add the reference to the my-command.mdx file in the table of contents.

The table of contents is organized alphabetically so that users can quickly find the command they are looking for.

Implement command

Each command in the CLI for Microsoft 365 is defined as a class extending the Command base class. At minimum, a command must define name, description, and commandAction:

import config from '../../../config';
import commands from '../commands';
import Command from '../../../Command';

class MyCommand extends Command {
  public get name(): string {
    return commands.MYCOMMAND;
  }

  public get description(): string {
    return 'My command';
  }

  public async commandAction(cmd: CommandInstance, args: CommandArgs): Promise<void> {
    // command implementation goes here

  }
}

module.exports = new MyCommand();

Depending on your command and the service for which you're building the command, there might be a base class that you can use to simplify the implementation. For example for SPO, you can inherit from the SpoCommand base class. This class contains a number of helper methods simplifying your implementation.

Tracking command usage

The CLI for Microsoft 365 tracks the usage of the different commands using Azure Application Insights. By default, for each command the CLI tracks its name and whether it's been executed in debug/verbose mode or not. If your command has additional properties that should be included in the telemetry, you can define them by implementing the #initTelemetry method and adding your properties to the this.telemetryProperties object:

class SpoMyCommand extends Command {
  constructor() {
    super();
  
    this.#initTelemetry();
    // ...
  }
  // ...

  #initTelemetry(): void {
    this.telemetry.push((args: CommandArgs) => {
      Object.assign(this.telemetryProperties, {
        id: typeof args.options.id !== 'undefined',
        name: typeof args.options.name !== 'undefined',
        listTitle: typeof args.options.listTitle !== 'undefined',
        listId: typeof args.options.listId !== 'undefined',
        listUrl: typeof args.options.listUrl !== 'undefined'
      });
    });
  }

  // ...
}

Important: if your command requires URLs or other user-defined strings, you should not include them in the telemetry as these strings might include personal or confidential information that we shouldn't have access to.

Error handling

If an error occurred while executing the command, throw the error message that should be displayed to the user:

class SpoMyCommand extends Command {
  // ...

  public async commandAction(cmd: CommandInstance, args: CommandArgs): Promise<void> {
    // command implementation goes here

    throw 'An error has occurred'; // notify that an error has occurred
  }

  // ...
}

Export command class instance

Finish the implementation of your command, by exporting the instance of the command class:

module.exports = new SpoMyCommand();

On runtime, CLI for Microsoft 365 iterates through all JavaScript files in the m365 folder and registers all exported command classes as commands in the CLI.

Additional command capabilities

When building CLI for Microsoft 365 commands, you can use additional features such as optional and required arguments, autocomplete, or validation.

Implement unit tests

Each command must be accompanied by a set of unit tests. We aim for 100% code and branch coverage in every command file.

To see the current code coverage, run npm test. Once testing completes, open the ./coverage/lcov-report/index.html file in the web browser and browser through the different project files.

See the existing test files to get a better understanding of how they are structured and how different elements such as objects or web requests are mocked. To run just your tests, either add .only to your test suite (eg. describe.only(commands.MY_COMMAND)) or update the glob in the .mocharc.json file, spec property to match the path to your test files, like dist/m365/aad/commands/approleassignment/**/*.spec.js.

Once you're done with your unit tests, run npm test to verify that you're covering all code and branches of your command with your unit tests.

For more information about working with tests in CLI for Microsoft 365, check out this awesome article by Martin Lingstuyl.

Write help page

Each command has a corresponding manual page. The contents of this page are almost identical to the help implemented in the command itself. This way, users working with the CLI can get the help directly inside the CLI, while users interested in the capabilities of the CLI, can browse through the help pages published on the Internet.

In the CLI for Microsoft 365 we extend this basic information with additional remarks and examples to help users work with the CLI. The CLI will look in the ./docs/docs/cmd/[service] folder for your documentation file and output to users when requesting help for your command:

m365 spo my-command --help

That's it

If the project is still building, your command is working as expected, all unit tests are passing, you have 100% code coverage on your command file and the documentation is in place, you're ready to submit a PR.