@pnp/cli-microsoft365 7.1.0-beta.d53f0d9 → 7.2.0-beta.194fd07

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/.eslintrc.cjs +2 -0
  2. package/dist/Auth.js +1 -1
  3. package/dist/Command.js +18 -28
  4. package/dist/cli/Cli.js +6 -4
  5. package/dist/m365/aad/commands/administrativeunit/administrativeunit-add.js +61 -0
  6. package/dist/m365/aad/commands/administrativeunit/administrativeunit-get.js +93 -0
  7. package/dist/m365/aad/commands/administrativeunit/administrativeunit-list.js +25 -0
  8. package/dist/m365/aad/commands/administrativeunit/administrativeunit-remove.js +108 -0
  9. package/dist/m365/aad/commands/group/group-user-list.js +146 -0
  10. package/dist/m365/aad/commands/m365group/m365group-remove.js +109 -15
  11. package/dist/m365/aad/commands.js +5 -0
  12. package/dist/m365/base/AppCommand.js +3 -12
  13. package/dist/m365/cli/commands/cli-doctor.js +11 -6
  14. package/dist/m365/commands/setup.js +6 -1
  15. package/dist/m365/spo/commands/sitedesign/sitedesign-get.js +15 -14
  16. package/dist/m365/spo/commands/sitedesign/sitedesign-remove.js +22 -22
  17. package/dist/m365/teams/commands/app/app-update.js +9 -6
  18. package/dist/m365/todo/commands/task/task-list.js +8 -10
  19. package/dist/m365/todo/commands/task/task-remove.js +36 -36
  20. package/dist/m365/todo/commands/task/task-set.js +11 -13
  21. package/dist/m365/yammer/commands/group/group-user-remove.js +22 -23
  22. package/dist/utils/aadGroup.js +3 -1
  23. package/dist/utils/odata.js +20 -19
  24. package/dist/utils/prompt.js +16 -0
  25. package/docs/docs/cmd/aad/administrativeunit/administrativeunit-add.mdx +115 -0
  26. package/docs/docs/cmd/aad/administrativeunit/administrativeunit-get.mdx +98 -0
  27. package/docs/docs/cmd/aad/administrativeunit/administrativeunit-list.mdx +83 -0
  28. package/docs/docs/cmd/aad/administrativeunit/administrativeunit-remove.mdx +52 -0
  29. package/docs/docs/cmd/aad/group/group-user-list.mdx +135 -0
  30. package/docs/docs/cmd/aad/m365group/m365group-remove.mdx +11 -1
  31. package/docs/docs/cmd/spo/field/field-get.mdx +1 -1
  32. package/npm-shrinkwrap.json +2 -2
  33. package/package.json +33 -13
@@ -10,16 +10,21 @@ import { aadGroup } from '../../../../utils/aadGroup.js';
10
10
  import { validation } from '../../../../utils/validation.js';
11
11
  import GraphCommand from '../../../base/GraphCommand.js';
12
12
  import commands from '../../commands.js';
13
+ import config from '../../../../config.js';
14
+ import { formatting } from '../../../../utils/formatting.js';
15
+ import { spo } from '../../../../utils/spo.js';
16
+ import { setTimeout } from 'timers/promises';
13
17
  class AadM365GroupRemoveCommand extends GraphCommand {
14
18
  get name() {
15
19
  return commands.M365GROUP_REMOVE;
16
20
  }
17
21
  get description() {
18
- return 'Removes an Microsoft 365 Group';
22
+ return 'Removes a Microsoft 365 Group';
19
23
  }
20
24
  constructor() {
21
25
  super();
22
26
  _AadM365GroupRemoveCommand_instances.add(this);
27
+ this.intervalInMs = 5000;
23
28
  __classPrivateFieldGet(this, _AadM365GroupRemoveCommand_instances, "m", _AadM365GroupRemoveCommand_initTelemetry).call(this);
24
29
  __classPrivateFieldGet(this, _AadM365GroupRemoveCommand_instances, "m", _AadM365GroupRemoveCommand_initOptions).call(this);
25
30
  __classPrivateFieldGet(this, _AadM365GroupRemoveCommand_instances, "m", _AadM365GroupRemoveCommand_initValidators).call(this);
@@ -34,21 +39,13 @@ class AadM365GroupRemoveCommand extends GraphCommand {
34
39
  if (!isUnifiedGroup) {
35
40
  throw Error(`Specified group with id '${args.options.id}' is not a Microsoft 365 group.`);
36
41
  }
37
- const requestOptions = {
38
- url: `${this.resource}/v1.0/groups/${args.options.id}`,
39
- headers: {
40
- 'accept': 'application/json;odata.metadata=none'
41
- }
42
- };
43
- await request.delete(requestOptions);
42
+ const siteUrl = await this.getM365GroupSiteUrl(logger, args.options.id);
43
+ const spoAdminUrl = await spo.getSpoAdminUrl(logger, this.debug);
44
+ // Delete the Microsoft 365 group site. This operation will also delete the group.
45
+ await this.deleteM365GroupSite(logger, siteUrl, spoAdminUrl);
44
46
  if (args.options.skipRecycleBin) {
45
- const requestOptions2 = {
46
- url: `${this.resource}/v1.0/directory/deletedItems/${args.options.id}`,
47
- headers: {
48
- 'accept': 'application/json;odata.metadata=none'
49
- }
50
- };
51
- await request.delete(requestOptions2);
47
+ await this.deleteM365GroupFromRecycleBin(logger, args.options.id);
48
+ await this.deleteSiteFromRecycleBin(logger, siteUrl, spoAdminUrl);
52
49
  }
53
50
  }
54
51
  catch (err) {
@@ -70,6 +67,102 @@ class AadM365GroupRemoveCommand extends GraphCommand {
70
67
  }
71
68
  }
72
69
  }
70
+ async getM365GroupSiteUrl(logger, id) {
71
+ if (this.verbose) {
72
+ await logger.logToStderr(`Getting the site URL of Microsoft 365 Group: ${id}...`);
73
+ }
74
+ const requestOptions = {
75
+ url: `${this.resource}/v1.0/groups/${id}/drive?$select=webUrl`,
76
+ headers: {
77
+ accept: 'application/json;odata.metadata=none'
78
+ },
79
+ responseType: 'json'
80
+ };
81
+ const res = await request.get(requestOptions);
82
+ // Extract the base URL by removing everything after the last '/' character in the URL.
83
+ const baseUrl = res.webUrl.substring(0, res.webUrl.lastIndexOf('/'));
84
+ return baseUrl;
85
+ }
86
+ async deleteM365GroupSite(logger, url, spoAdminUrl) {
87
+ if (this.verbose) {
88
+ await logger.logToStderr(`Deleting the group site: '${url}'...`);
89
+ }
90
+ const requestOptions = {
91
+ url: `${spoAdminUrl}/_api/GroupSiteManager/Delete?siteUrl='${url}'`,
92
+ headers: {
93
+ accept: 'application/json;odata=nometadata'
94
+ },
95
+ responseType: 'json'
96
+ };
97
+ await request.post(requestOptions);
98
+ }
99
+ async deleteM365GroupFromRecycleBin(logger, id) {
100
+ for (let retries = 0; retries < AadM365GroupRemoveCommand.maxRetries; retries++) {
101
+ if (await this.isM365GroupInDeletedItemsList(id)) {
102
+ await this.removeM365GroupPermanently(logger, id);
103
+ return;
104
+ }
105
+ else {
106
+ if (this.verbose) {
107
+ await logger.logToStderr(`Group has not been deleted yet. Waiting for ${this.intervalInMs / 1000} seconds before the next attempt. ${AadM365GroupRemoveCommand.maxRetries - retries} attempts remaining...`);
108
+ }
109
+ await setTimeout(this.intervalInMs);
110
+ }
111
+ }
112
+ await logger.logToStderr(`Group has been successfully deleted, but it couldn't be permanently removed from the recycle bin after all retries. It will still appear in the deleted groups list.`);
113
+ }
114
+ async removeM365GroupPermanently(logger, id) {
115
+ if (this.verbose) {
116
+ await logger.logToStderr(`Group has been deleted and is now available in the deleted groups list. Removing permanently...`);
117
+ }
118
+ const requestOptions = {
119
+ url: `${this.resource}/v1.0/directory/deletedItems/${id}`,
120
+ headers: {
121
+ accept: 'application/json;odata.metadata=none'
122
+ }
123
+ };
124
+ await request.delete(requestOptions);
125
+ }
126
+ async isM365GroupInDeletedItemsList(id) {
127
+ const requestOptions = {
128
+ url: `${this.resource}/v1.0/directory/deletedItems/${id}`,
129
+ headers: {
130
+ accept: 'application/json;odata.metadata=none'
131
+ },
132
+ responseType: 'json'
133
+ };
134
+ try {
135
+ const response = await request.get(requestOptions);
136
+ return Boolean(response && response.id);
137
+ }
138
+ catch (error) {
139
+ if (error.response && error.response.status === 404) {
140
+ return false;
141
+ }
142
+ else {
143
+ throw error;
144
+ }
145
+ }
146
+ }
147
+ async deleteSiteFromRecycleBin(logger, url, spoAdminUrl) {
148
+ if (this.verbose) {
149
+ await logger.logToStderr(`Deleting the M365 group site '${url}' from the recycle bin...`);
150
+ }
151
+ const res = await spo.ensureFormDigest(spoAdminUrl, logger, undefined, this.debug);
152
+ const requestOptions = {
153
+ url: `${spoAdminUrl}/_vti_bin/client.svc/ProcessQuery`,
154
+ headers: {
155
+ 'X-RequestDigest': res.FormDigestValue
156
+ },
157
+ data: `<Request AddExpandoFieldTypeSuffix="true" SchemaVersion="15.0.0.0" LibraryVersion="16.0.0.0" ApplicationName="${config.applicationName}" xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009"><Actions><ObjectPath Id="185" ObjectPathId="184" /><Query Id="186" ObjectPathId="184"><Query SelectAllProperties="false"><Properties><Property Name="IsComplete" ScalarProperty="true" /><Property Name="PollingInterval" ScalarProperty="true" /></Properties></Query></Query></Actions><ObjectPaths><Method Id="184" ParentId="175" Name="RemoveDeletedSite"><Parameters><Parameter Type="String">${formatting.escapeXml(url)}</Parameter></Parameters></Method><Constructor Id="175" TypeId="{268004ae-ef6b-4e9b-8425-127220d84719}" /></ObjectPaths></Request>`
158
+ };
159
+ const processQuery = await request.post(requestOptions);
160
+ const json = JSON.parse(processQuery);
161
+ const response = json[0];
162
+ if (response.ErrorInfo) {
163
+ throw response.ErrorInfo.ErrorMessage;
164
+ }
165
+ }
73
166
  }
74
167
  _AadM365GroupRemoveCommand_instances = new WeakSet(), _AadM365GroupRemoveCommand_initTelemetry = function _AadM365GroupRemoveCommand_initTelemetry() {
75
168
  this.telemetry.push((args) => {
@@ -94,5 +187,6 @@ _AadM365GroupRemoveCommand_instances = new WeakSet(), _AadM365GroupRemoveCommand
94
187
  return true;
95
188
  });
96
189
  };
190
+ AadM365GroupRemoveCommand.maxRetries = 10;
97
191
  export default new AadM365GroupRemoveCommand();
98
192
  //# sourceMappingURL=m365group-remove.js.map
@@ -1,5 +1,9 @@
1
1
  const prefix = 'aad';
2
2
  export default {
3
+ ADMINISTRATIVEUNIT_ADD: `${prefix} administrativeunit add`,
4
+ ADMINISTRATIVEUNIT_GET: `${prefix} administrativeunit get`,
5
+ ADMINISTRATIVEUNIT_LIST: `${prefix} administrativeunit list`,
6
+ ADMINISTRATIVEUNIT_REMOVE: `${prefix} administrativeunit remove`,
3
7
  APP_ADD: `${prefix} app add`,
4
8
  APP_GET: `${prefix} app get`,
5
9
  APP_LIST: `${prefix} app list`,
@@ -14,6 +18,7 @@ export default {
14
18
  GROUP_GET: `${prefix} group get`,
15
19
  GROUP_LIST: `${prefix} group list`,
16
20
  GROUP_REMOVE: `${prefix} group remove`,
21
+ GROUP_USER_LIST: `${prefix} group user list`,
17
22
  GROUPSETTING_ADD: `${prefix} groupsetting add`,
18
23
  GROUPSETTING_GET: `${prefix} groupsetting get`,
19
24
  GROUPSETTING_LIST: `${prefix} groupsetting list`,
@@ -8,6 +8,7 @@ import fs from 'fs';
8
8
  import { Cli } from '../../cli/Cli.js';
9
9
  import Command, { CommandError } from '../../Command.js';
10
10
  import { validation } from '../../utils/validation.js';
11
+ import { formatting } from '../../utils/formatting.js';
11
12
  class AppCommand extends Command {
12
13
  get resource() {
13
14
  return 'https://graph.microsoft.com';
@@ -52,18 +53,8 @@ class AppCommand extends Command {
52
53
  return super.action(logger, args);
53
54
  }
54
55
  if (this.m365rcJson.apps.length > 1) {
55
- const result = await Cli.prompt({
56
- message: `Multiple Azure AD apps found in ${m365rcJsonPath}. Which app would you like to use?`,
57
- type: 'list',
58
- choices: this.m365rcJson.apps.map((app, i) => {
59
- return {
60
- name: `${app.name} (${app.appId})`,
61
- value: i
62
- };
63
- }),
64
- default: 0,
65
- name: 'appIdIndex'
66
- });
56
+ const resultAsKeyValuePair = formatting.convertArrayToHashTable('appIdIndex', this.m365rcJson.apps);
57
+ const result = await Cli.handleMultipleResultsFound(`Multiple Azure AD apps found in ${m365rcJsonPath}.`, resultAsKeyValuePair);
67
58
  this.appId = this.m365rcJson.apps[result.appIdIndex].appId;
68
59
  await super.action(logger, args);
69
60
  }
@@ -14,11 +14,14 @@ class CliDoctorCommand extends Command {
14
14
  }
15
15
  async commandAction(logger) {
16
16
  const roles = [];
17
- const scopes = [];
17
+ const scopes = new Map();
18
18
  Object.keys(auth.service.accessTokens).forEach(resource => {
19
19
  const accessToken = auth.service.accessTokens[resource].accessToken;
20
20
  this.getRolesFromAccessToken(accessToken).forEach(role => roles.push(role));
21
- this.getScopesFromAccessToken(accessToken).forEach(scope => scopes.push(scope));
21
+ const [res, scp] = this.getScopesFromAccessToken(accessToken);
22
+ if (res !== "") {
23
+ scopes.set(res, scp);
24
+ }
22
25
  });
23
26
  const diagnosticInfo = {
24
27
  os: {
@@ -34,7 +37,7 @@ class CliDoctorCommand extends Command {
34
37
  cliEnvironment: process.env.CLIMICROSOFT365_ENV ? process.env.CLIMICROSOFT365_ENV : '',
35
38
  cliConfig: Cli.getInstance().config.all,
36
39
  roles: roles,
37
- scopes: scopes
40
+ scopes: Object.fromEntries(scopes)
38
41
  };
39
42
  await logger.log(diagnosticInfo);
40
43
  }
@@ -55,20 +58,22 @@ class CliDoctorCommand extends Command {
55
58
  return roles;
56
59
  }
57
60
  getScopesFromAccessToken(accessToken) {
61
+ let resource = "";
58
62
  let scopes = [];
59
63
  if (!accessToken || accessToken.length === 0) {
60
- return scopes;
64
+ return [resource, scopes];
61
65
  }
62
66
  const chunks = accessToken.split('.');
63
67
  if (chunks.length !== 3) {
64
- return scopes;
68
+ return [resource, scopes];
65
69
  }
66
70
  const tokenString = Buffer.from(chunks[1], 'base64').toString();
67
71
  const token = JSON.parse(tokenString);
68
72
  if (token.scp?.length > 0) {
73
+ resource = token.aud.replace(/(-my|-admin).sharepoint.com/, '.sharepoint.com');
69
74
  scopes = token.scp.split(' ');
70
75
  }
71
- return scopes;
76
+ return [resource, scopes];
72
77
  }
73
78
  }
74
79
  export default new CliDoctorCommand();
@@ -12,6 +12,7 @@ import { pid } from '../../utils/pid.js';
12
12
  import AnonymousCommand from '../base/AnonymousCommand.js';
13
13
  import commands from './commands.js';
14
14
  import { interactivePreset, powerShellPreset, scriptingPreset } from './setupPresets.js';
15
+ import { prompt } from '../../utils/prompt.js';
15
16
  class SetupCommand extends AnonymousCommand {
16
17
  get name() {
17
18
  return commands.SETUP;
@@ -44,11 +45,13 @@ class SetupCommand extends AnonymousCommand {
44
45
  await this.configureSettings(settings, true, logger);
45
46
  return;
46
47
  }
48
+ // stop the spinner. Fixes #5598
49
+ Cli.getInstance().spinner.stop();
47
50
  await logger.logToStderr(`Welcome to the CLI for Microsoft 365 setup!`);
48
51
  await logger.logToStderr(`This command will guide you through the process of configuring the CLI for your needs.`);
49
52
  await logger.logToStderr(`Please, answer the following questions and we'll define a set of settings to best match how you intend to use the CLI.`);
50
53
  await logger.logToStderr('');
51
- const preferences = await Cli.prompt([
54
+ const preferences = await prompt.forInput([
52
55
  {
53
56
  type: 'list',
54
57
  name: 'usageMode',
@@ -94,6 +97,8 @@ class SetupCommand extends AnonymousCommand {
94
97
  await logger.logToStderr('');
95
98
  await logger.logToStderr('Configuring settings...');
96
99
  await logger.logToStderr('');
100
+ // start the spinner. Fixes #5598
101
+ Cli.getInstance().spinner.start();
97
102
  await this.configureSettings(settings, false, logger);
98
103
  if (!this.verbose) {
99
104
  await logger.logToStderr('');
@@ -4,7 +4,9 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
4
4
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
5
  };
6
6
  var _SpoSiteDesignGetCommand_instances, _SpoSiteDesignGetCommand_initTelemetry, _SpoSiteDesignGetCommand_initOptions, _SpoSiteDesignGetCommand_initValidators, _SpoSiteDesignGetCommand_initOptionSets;
7
+ import { Cli } from '../../../../cli/Cli.js';
7
8
  import request from '../../../../request.js';
9
+ import { formatting } from '../../../../utils/formatting.js';
8
10
  import { spo } from '../../../../utils/spo.js';
9
11
  import { validation } from '../../../../utils/validation.js';
10
12
  import SpoCommand from '../../../base/SpoCommand.js';
@@ -24,9 +26,9 @@ class SpoSiteDesignGetCommand extends SpoCommand {
24
26
  __classPrivateFieldGet(this, _SpoSiteDesignGetCommand_instances, "m", _SpoSiteDesignGetCommand_initValidators).call(this);
25
27
  __classPrivateFieldGet(this, _SpoSiteDesignGetCommand_instances, "m", _SpoSiteDesignGetCommand_initOptionSets).call(this);
26
28
  }
27
- getSiteDesignId(args, spoUrl) {
29
+ async getSiteDesignId(args, spoUrl) {
28
30
  if (args.options.id) {
29
- return Promise.resolve(args.options.id);
31
+ return args.options.id;
30
32
  }
31
33
  const requestOptions = {
32
34
  url: `${spoUrl}/_api/Microsoft.Sharepoint.Utilities.WebTemplateExtensions.SiteScriptUtility.GetSiteDesigns`,
@@ -35,18 +37,17 @@ class SpoSiteDesignGetCommand extends SpoCommand {
35
37
  },
36
38
  responseType: 'json'
37
39
  };
38
- return request
39
- .post(requestOptions)
40
- .then(response => {
41
- const matchingSiteDesigns = response.value.filter(x => x.Title === args.options.title);
42
- if (matchingSiteDesigns.length === 0) {
43
- return Promise.reject(`The specified site design does not exist`);
44
- }
45
- if (matchingSiteDesigns.length > 1) {
46
- return Promise.reject(`Multiple site designs with title ${args.options.title} found: ${matchingSiteDesigns.map(x => x.Id).join(', ')}`);
47
- }
48
- return Promise.resolve(matchingSiteDesigns[0].Id);
49
- });
40
+ const response = await request.post(requestOptions);
41
+ const matchingSiteDesigns = response.value.filter(x => x.Title === args.options.title);
42
+ if (matchingSiteDesigns.length === 0) {
43
+ throw `The specified site design does not exist`;
44
+ }
45
+ if (matchingSiteDesigns.length > 1) {
46
+ const resultAsKeyValuePair = formatting.convertArrayToHashTable('Id', matchingSiteDesigns);
47
+ const result = await Cli.handleMultipleResultsFound(`Multiple site designs with title '${args.options.title}' found.`, resultAsKeyValuePair);
48
+ return result.Id;
49
+ }
50
+ return matchingSiteDesigns[0].Id;
50
51
  }
51
52
  async commandAction(logger, args) {
52
53
  try {
@@ -25,28 +25,8 @@ class SpoSiteDesignRemoveCommand extends SpoCommand {
25
25
  __classPrivateFieldGet(this, _SpoSiteDesignRemoveCommand_instances, "m", _SpoSiteDesignRemoveCommand_initValidators).call(this);
26
26
  }
27
27
  async commandAction(logger, args) {
28
- const removeSiteDesign = async () => {
29
- try {
30
- const spoUrl = await spo.getSpoUrl(logger, this.debug);
31
- const requestDigest = await spo.getRequestDigest(spoUrl);
32
- const requestOptions = {
33
- url: `${spoUrl}/_api/Microsoft.Sharepoint.Utilities.WebTemplateExtensions.SiteScriptUtility.DeleteSiteDesign`,
34
- headers: {
35
- 'X-RequestDigest': requestDigest.FormDigestValue,
36
- 'content-type': 'application/json;charset=utf-8',
37
- accept: 'application/json;odata=nometadata'
38
- },
39
- data: { id: args.options.id },
40
- responseType: 'json'
41
- };
42
- await request.post(requestOptions);
43
- }
44
- catch (err) {
45
- this.handleRejectedODataJsonPromise(err);
46
- }
47
- };
48
28
  if (args.options.force) {
49
- await removeSiteDesign();
29
+ await this.removeSiteDesign(logger, args.options.id);
50
30
  }
51
31
  else {
52
32
  const result = await Cli.prompt({
@@ -56,10 +36,30 @@ class SpoSiteDesignRemoveCommand extends SpoCommand {
56
36
  message: `Are you sure you want to remove the site design ${args.options.id}?`
57
37
  });
58
38
  if (result.continue) {
59
- await removeSiteDesign();
39
+ await this.removeSiteDesign(logger, args.options.id);
60
40
  }
61
41
  }
62
42
  }
43
+ async removeSiteDesign(logger, id) {
44
+ try {
45
+ const spoUrl = await spo.getSpoUrl(logger, this.debug);
46
+ const requestDigest = await spo.getRequestDigest(spoUrl);
47
+ const requestOptions = {
48
+ url: `${spoUrl}/_api/Microsoft.Sharepoint.Utilities.WebTemplateExtensions.SiteScriptUtility.DeleteSiteDesign`,
49
+ headers: {
50
+ 'X-RequestDigest': requestDigest.FormDigestValue,
51
+ 'content-type': 'application/json;charset=utf-8',
52
+ accept: 'application/json;odata=nometadata'
53
+ },
54
+ data: { id: id },
55
+ responseType: 'json'
56
+ };
57
+ await request.post(requestOptions);
58
+ }
59
+ catch (err) {
60
+ this.handleRejectedODataJsonPromise(err);
61
+ }
62
+ }
63
63
  }
64
64
  _SpoSiteDesignRemoveCommand_instances = new WeakSet(), _SpoSiteDesignRemoveCommand_initTelemetry = function _SpoSiteDesignRemoveCommand_initTelemetry() {
65
65
  this.telemetry.push((args) => {
@@ -11,6 +11,7 @@ import { formatting } from '../../../../utils/formatting.js';
11
11
  import { validation } from '../../../../utils/validation.js';
12
12
  import GraphCommand from '../../../base/GraphCommand.js';
13
13
  import commands from '../../commands.js';
14
+ import { Cli } from '../../../../cli/Cli.js';
14
15
  class TeamsAppUpdateCommand extends GraphCommand {
15
16
  get name() {
16
17
  return commands.APP_UPDATE;
@@ -29,7 +30,7 @@ class TeamsAppUpdateCommand extends GraphCommand {
29
30
  async commandAction(logger, args) {
30
31
  const { filePath } = args.options;
31
32
  try {
32
- const appId = await this.getAppId(args);
33
+ const appId = await this.getAppId(args.options);
33
34
  const fullPath = path.resolve(filePath);
34
35
  if (this.verbose) {
35
36
  await logger.logToStderr(`Updating app with id '${appId}' and file '${fullPath}' in the app catalog...`);
@@ -47,12 +48,12 @@ class TeamsAppUpdateCommand extends GraphCommand {
47
48
  this.handleRejectedODataJsonPromise(err);
48
49
  }
49
50
  }
50
- async getAppId(args) {
51
- if (args.options.id) {
52
- return args.options.id;
51
+ async getAppId(options) {
52
+ if (options.id) {
53
+ return options.id;
53
54
  }
54
55
  const requestOptions = {
55
- url: `${this.resource}/v1.0/appCatalogs/teamsApps?$filter=displayName eq '${formatting.encodeQueryParameter(args.options.name)}'`,
56
+ url: `${this.resource}/v1.0/appCatalogs/teamsApps?$filter=displayName eq '${formatting.encodeQueryParameter(options.name)}'`,
56
57
  headers: {
57
58
  accept: 'application/json;odata.metadata=none'
58
59
  },
@@ -64,7 +65,9 @@ class TeamsAppUpdateCommand extends GraphCommand {
64
65
  throw `The specified Teams app does not exist`;
65
66
  }
66
67
  if (response.value.length > 1) {
67
- throw `Multiple Teams apps with name ${args.options.name} found. Please choose one of these ids: ${response.value.map(x => x.id).join(', ')}`;
68
+ const resultAsKeyValuePair = formatting.convertArrayToHashTable('id', response.value);
69
+ const result = await Cli.handleMultipleResultsFound(`Multiple Teams apps with name ${options.name} found.`, resultAsKeyValuePair);
70
+ return result.id;
68
71
  }
69
72
  return app.id;
70
73
  }
@@ -26,9 +26,9 @@ class TodoTaskListCommand extends GraphCommand {
26
26
  __classPrivateFieldGet(this, _TodoTaskListCommand_instances, "m", _TodoTaskListCommand_initOptions).call(this);
27
27
  __classPrivateFieldGet(this, _TodoTaskListCommand_instances, "m", _TodoTaskListCommand_initOptionSets).call(this);
28
28
  }
29
- getTodoListId(args) {
29
+ async getTodoListId(args) {
30
30
  if (args.options.listId) {
31
- return Promise.resolve(args.options.listId);
31
+ return args.options.listId;
32
32
  }
33
33
  const requestOptions = {
34
34
  url: `${this.resource}/v1.0/me/todo/lists?$filter=displayName eq '${escape(args.options.listName)}'`,
@@ -37,14 +37,12 @@ class TodoTaskListCommand extends GraphCommand {
37
37
  },
38
38
  responseType: 'json'
39
39
  };
40
- return request.get(requestOptions)
41
- .then(response => {
42
- const taskList = response.value[0];
43
- if (!taskList) {
44
- return Promise.reject(`The specified task list does not exist`);
45
- }
46
- return Promise.resolve(taskList.id);
47
- });
40
+ const response = await request.get(requestOptions);
41
+ const taskList = response.value[0];
42
+ if (!taskList) {
43
+ throw `The specified task list does not exist`;
44
+ }
45
+ return taskList.id;
48
46
  }
49
47
  async commandAction(logger, args) {
50
48
  try {
@@ -23,42 +23,8 @@ class TodoTaskRemoveCommand extends GraphCommand {
23
23
  __classPrivateFieldGet(this, _TodoTaskRemoveCommand_instances, "m", _TodoTaskRemoveCommand_initOptionSets).call(this);
24
24
  }
25
25
  async commandAction(logger, args) {
26
- const getToDoListId = async () => {
27
- if (args.options.listName) {
28
- // Search list by its name
29
- const requestOptions = {
30
- url: `${this.resource}/v1.0/me/todo/lists?$filter=displayName eq '${escape(args.options.listName)}'`,
31
- headers: {
32
- accept: "application/json;odata.metadata=none"
33
- },
34
- responseType: 'json'
35
- };
36
- const response = await request.get(requestOptions);
37
- return response.value && response.value.length === 1 ? response.value[0].id : undefined;
38
- }
39
- return Promise.resolve(args.options.listId);
40
- };
41
- const removeToDoTask = async () => {
42
- try {
43
- const toDoListId = await getToDoListId();
44
- if (!toDoListId) {
45
- throw `The list ${args.options.listName} cannot be found`;
46
- }
47
- const requestOptions = {
48
- url: `${this.resource}/v1.0/me/todo/lists/${toDoListId}/tasks/${args.options.id}`,
49
- headers: {
50
- accept: "application/json;odata.metadata=none"
51
- },
52
- responseType: 'json'
53
- };
54
- await request.delete(requestOptions);
55
- }
56
- catch (err) {
57
- this.handleRejectedODataJsonPromise(err);
58
- }
59
- };
60
26
  if (args.options.force) {
61
- await removeToDoTask();
27
+ await this.removeToDoTask(args.options);
62
28
  }
63
29
  else {
64
30
  const result = await Cli.prompt({
@@ -68,10 +34,44 @@ class TodoTaskRemoveCommand extends GraphCommand {
68
34
  message: `Are you sure you want to remove the task ${args.options.id} from list ${args.options.listId || args.options.listName}?`
69
35
  });
70
36
  if (result.continue) {
71
- await removeToDoTask();
37
+ await this.removeToDoTask(args.options);
72
38
  }
73
39
  }
74
40
  }
41
+ async getToDoListId(options) {
42
+ if (options.listName) {
43
+ // Search list by its name
44
+ const requestOptions = {
45
+ url: `${this.resource}/v1.0/me/todo/lists?$filter=displayName eq '${escape(options.listName)}'`,
46
+ headers: {
47
+ accept: "application/json;odata.metadata=none"
48
+ },
49
+ responseType: 'json'
50
+ };
51
+ const response = await request.get(requestOptions);
52
+ return response.value && response.value.length === 1 ? response.value[0].id : undefined;
53
+ }
54
+ return options.listId;
55
+ }
56
+ async removeToDoTask(options) {
57
+ try {
58
+ const toDoListId = await this.getToDoListId(options);
59
+ if (!toDoListId) {
60
+ throw `The list ${options.listName} cannot be found`;
61
+ }
62
+ const requestOptions = {
63
+ url: `${this.resource}/v1.0/me/todo/lists/${toDoListId}/tasks/${options.id}`,
64
+ headers: {
65
+ accept: "application/json;odata.metadata=none"
66
+ },
67
+ responseType: 'json'
68
+ };
69
+ await request.delete(requestOptions);
70
+ }
71
+ catch (err) {
72
+ this.handleRejectedODataJsonPromise(err);
73
+ }
74
+ }
75
75
  }
76
76
  _TodoTaskRemoveCommand_instances = new WeakSet(), _TodoTaskRemoveCommand_initTelemetry = function _TodoTaskRemoveCommand_initTelemetry() {
77
77
  this.telemetry.push((args) => {
@@ -28,7 +28,7 @@ class TodoTaskSetCommand extends GraphCommand {
28
28
  const endpoint = `${this.resource}/v1.0`;
29
29
  const data = this.mapRequestBody(args.options);
30
30
  try {
31
- const listId = await this.getTodoListId(args);
31
+ const listId = await this.getTodoListId(args.options);
32
32
  const requestOptions = {
33
33
  url: `${endpoint}/me/todo/lists/${listId}/tasks/${formatting.encodeQueryParameter(args.options.id)}`,
34
34
  headers: {
@@ -45,25 +45,23 @@ class TodoTaskSetCommand extends GraphCommand {
45
45
  this.handleRejectedODataJsonPromise(err);
46
46
  }
47
47
  }
48
- getTodoListId(args) {
49
- if (args.options.listId) {
50
- return Promise.resolve(args.options.listId);
48
+ async getTodoListId(options) {
49
+ if (options.listId) {
50
+ return options.listId;
51
51
  }
52
52
  const requestOptions = {
53
- url: `${this.resource}/v1.0/me/todo/lists?$filter=displayName eq '${escape(args.options.listName)}'`,
53
+ url: `${this.resource}/v1.0/me/todo/lists?$filter=displayName eq '${escape(options.listName)}'`,
54
54
  headers: {
55
55
  accept: 'application/json;odata.metadata=none'
56
56
  },
57
57
  responseType: 'json'
58
58
  };
59
- return request.get(requestOptions)
60
- .then(response => {
61
- const taskList = response.value[0];
62
- if (!taskList) {
63
- return Promise.reject(`The specified task list does not exist`);
64
- }
65
- return Promise.resolve(taskList.id);
66
- });
59
+ const response = await request.get(requestOptions);
60
+ const taskList = response.value[0];
61
+ if (!taskList) {
62
+ throw `The specified task list does not exist`;
63
+ }
64
+ return taskList.id;
67
65
  }
68
66
  getDateTimeTimeZone(dateTime) {
69
67
  return {