@pnp/cli-microsoft365 10.0.0-beta.7dfc31a → 10.0.0-beta.a868b81

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 (48) hide show
  1. package/allCommands.json +1 -1
  2. package/allCommandsFull.json +1 -1
  3. package/dist/Auth.js +11 -12
  4. package/dist/cli/cli.js +14 -0
  5. package/dist/config.js +60 -5
  6. package/dist/m365/base/PowerAutomateCommand.js +1 -1
  7. package/dist/m365/base/SpoCommand.js +1 -1
  8. package/dist/m365/cli/commands/cli-consent.js +2 -2
  9. package/dist/m365/cli/commands/cli-doctor.js +2 -2
  10. package/dist/m365/cli/commands/cli-reconsent.js +2 -3
  11. package/dist/m365/cli/commands/config/config-set.js +12 -4
  12. package/dist/m365/commands/login.js +28 -9
  13. package/dist/m365/commands/setup.js +256 -33
  14. package/dist/m365/commands/setupPresets.js +2 -4
  15. package/dist/m365/connection/commands/connection-list.js +4 -4
  16. package/dist/m365/entra/commands/app/app-add.js +52 -288
  17. package/dist/m365/flow/commands/environment/environment-get.js +1 -1
  18. package/dist/m365/flow/commands/environment/environment-list.js +1 -1
  19. package/dist/m365/flow/commands/flow-disable.js +1 -1
  20. package/dist/m365/flow/commands/flow-enable.js +1 -1
  21. package/dist/m365/flow/commands/flow-export.js +17 -16
  22. package/dist/m365/flow/commands/flow-get.js +1 -1
  23. package/dist/m365/flow/commands/flow-list.js +1 -1
  24. package/dist/m365/flow/commands/flow-remove.js +1 -1
  25. package/dist/m365/flow/commands/owner/owner-ensure.js +1 -1
  26. package/dist/m365/flow/commands/owner/owner-list.js +1 -1
  27. package/dist/m365/flow/commands/owner/owner-remove.js +1 -1
  28. package/dist/m365/flow/commands/run/run-cancel.js +1 -1
  29. package/dist/m365/flow/commands/run/run-get.js +1 -1
  30. package/dist/m365/flow/commands/run/run-list.js +1 -1
  31. package/dist/m365/flow/commands/run/run-resubmit.js +2 -2
  32. package/dist/m365/spo/commands/contenttype/contenttype-field-remove.js +8 -8
  33. package/dist/m365/spo/commands/contenttype/contenttype-field-set.js +2 -2
  34. package/dist/m365/spo/commands/file/file-roleassignment-add.js +17 -54
  35. package/dist/m365/spo/commands/file/file-roleassignment-remove.js +13 -40
  36. package/dist/m365/spo/commands/file/file-roleinheritance-break.js +5 -13
  37. package/dist/m365/spo/commands/file/file-roleinheritance-reset.js +5 -13
  38. package/dist/m365/spo/commands/list/list-get.js +17 -4
  39. package/dist/m365/spo/commands/page/page-section-add.js +185 -34
  40. package/dist/settingsNames.js +6 -1
  41. package/dist/utils/entraApp.js +283 -0
  42. package/dist/utils/spo.js +30 -7
  43. package/docs/docs/_clisettings.mdx +6 -1
  44. package/docs/docs/cmd/setup.mdx +17 -6
  45. package/docs/docs/cmd/spo/contenttype/contenttype-field-remove.mdx +7 -7
  46. package/docs/docs/cmd/spo/contenttype/contenttype-field-set.mdx +2 -2
  47. package/docs/docs/cmd/spo/page/page-section-add.mdx +57 -2
  48. package/package.json +1 -1
@@ -6,12 +6,44 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
6
6
  var _SetupCommand_instances, _SetupCommand_initTelemetry, _SetupCommand_initOptions, _SetupCommand_initValidators;
7
7
  import chalk from 'chalk';
8
8
  import os from 'os';
9
+ import auth, { AuthType } from '../../Auth.js';
9
10
  import { cli } from '../../cli/cli.js';
11
+ import config from '../../config.js';
12
+ import { settingsNames } from '../../settingsNames.js';
13
+ import { accessToken } from '../../utils/accessToken.js';
14
+ import { entraApp } from '../../utils/entraApp.js';
10
15
  import { CheckStatus, formatting } from '../../utils/formatting.js';
11
16
  import { pid } from '../../utils/pid.js';
17
+ import { validation } from '../../utils/validation.js';
12
18
  import AnonymousCommand from '../base/AnonymousCommand.js';
13
19
  import commands from './commands.js';
14
20
  import { interactivePreset, powerShellPreset, scriptingPreset } from './setupPresets.js';
21
+ export var CliUsageMode;
22
+ (function (CliUsageMode) {
23
+ CliUsageMode["Interactively"] = "interactively";
24
+ CliUsageMode["Scripting"] = "scripting";
25
+ })(CliUsageMode || (CliUsageMode = {}));
26
+ export var CliExperience;
27
+ (function (CliExperience) {
28
+ CliExperience["Beginner"] = "beginner";
29
+ CliExperience["Proficient"] = "proficient";
30
+ })(CliExperience || (CliExperience = {}));
31
+ export var EntraAppConfig;
32
+ (function (EntraAppConfig) {
33
+ EntraAppConfig["Create"] = "create";
34
+ EntraAppConfig["UseExisting"] = "useExisting";
35
+ EntraAppConfig["Skip"] = "skip";
36
+ })(EntraAppConfig || (EntraAppConfig = {}));
37
+ export var NewEntraAppScopes;
38
+ (function (NewEntraAppScopes) {
39
+ NewEntraAppScopes["Minimal"] = "minimal";
40
+ NewEntraAppScopes["All"] = "all";
41
+ })(NewEntraAppScopes || (NewEntraAppScopes = {}));
42
+ export var HelpMode;
43
+ (function (HelpMode) {
44
+ HelpMode["Full"] = "full";
45
+ HelpMode["Options"] = "options";
46
+ })(HelpMode || (HelpMode = {}));
15
47
  class SetupCommand extends AnonymousCommand {
16
48
  get name() {
17
49
  return commands.SETUP;
@@ -35,11 +67,11 @@ class SetupCommand extends AnonymousCommand {
35
67
  }
36
68
  else if (args.options.scripting) {
37
69
  Object.assign(settings, scriptingPreset);
38
- if (pid.isPowerShell()) {
39
- Object.assign(settings, powerShellPreset);
40
- }
41
70
  }
42
- await this.configureSettings(settings, true, logger);
71
+ if (pid.isPowerShell()) {
72
+ Object.assign(settings, powerShellPreset);
73
+ }
74
+ await this.configureSettings({ preferences: {}, settings, silent: true, logger });
43
75
  return;
44
76
  }
45
77
  await logger.logToStderr(`Welcome to the CLI for Microsoft 365 setup!`);
@@ -47,15 +79,45 @@ class SetupCommand extends AnonymousCommand {
47
79
  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.`);
48
80
  await logger.logToStderr('');
49
81
  const preferences = {};
82
+ if (!args.options.skipApp) {
83
+ const entraAppConfig = {
84
+ message: 'CLI for Microsoft 365 requires a Microsoft Entra app. Do you want to create a new app registration or use an existing one?',
85
+ choices: [
86
+ { name: 'Create a new app registration', value: EntraAppConfig.Create },
87
+ { name: 'Use an existing app registration', value: EntraAppConfig.UseExisting },
88
+ { name: 'Skip configuring app registration', value: EntraAppConfig.Skip }
89
+ ]
90
+ };
91
+ preferences.entraApp = await cli.promptForSelection(entraAppConfig);
92
+ switch (preferences.entraApp) {
93
+ case EntraAppConfig.Create:
94
+ const newEntraAppScopesConfig = {
95
+ message: 'What scopes should the new app registration have?',
96
+ choices: [
97
+ { name: 'User.Read (you will need to add the necessary permissions yourself)', value: NewEntraAppScopes.Minimal },
98
+ { name: 'All (easy way to use all CLI commands)', value: NewEntraAppScopes.All }
99
+ ]
100
+ };
101
+ preferences.newEntraAppScopes = await cli.promptForSelection(newEntraAppScopesConfig);
102
+ break;
103
+ case EntraAppConfig.UseExisting:
104
+ const existingApp = await this.configureExistingEntraApp(logger);
105
+ Object.assign(preferences, existingApp);
106
+ break;
107
+ }
108
+ }
109
+ else {
110
+ preferences.entraApp = EntraAppConfig.Skip;
111
+ }
50
112
  const usageModeConfig = {
51
113
  message: 'How do you plan to use the CLI?',
52
114
  choices: [
53
- { name: 'Interactively', value: 'Interactively' },
54
- { name: 'Scripting', value: 'Scripting' }
115
+ { name: 'Interactively', value: CliUsageMode.Interactively },
116
+ { name: 'Scripting', value: CliUsageMode.Scripting }
55
117
  ]
56
118
  };
57
119
  preferences.usageMode = await cli.promptForSelection(usageModeConfig);
58
- if (preferences.usageMode === 'Scripting') {
120
+ if (preferences.usageMode === CliUsageMode.Scripting) {
59
121
  const usedInPowerShellConfig = {
60
122
  message: 'Are you going to use the CLI in PowerShell?',
61
123
  default: pid.isPowerShell()
@@ -65,33 +127,149 @@ class SetupCommand extends AnonymousCommand {
65
127
  const experienceConfig = {
66
128
  message: 'How experienced are you in using the CLI?',
67
129
  choices: [
68
- { name: 'Beginner', value: 'Beginner' },
69
- { name: 'Proficient', value: 'Proficient' }
130
+ { name: 'Beginner', value: CliExperience.Beginner },
131
+ { name: 'Proficient', value: CliExperience.Proficient }
70
132
  ]
71
133
  };
72
134
  preferences.experience = await cli.promptForSelection(experienceConfig);
73
135
  const summaryConfig = {
74
- message: this.getSummaryMessage(this.getSettings(preferences))
136
+ message: this.getSummaryMessage(preferences)
75
137
  };
76
138
  preferences.summary = await cli.promptForConfirmation(summaryConfig);
77
- if (preferences.summary) {
78
- // used only for testing. Normally, we'd get the settings from the answers
79
- /* c8 ignore next 3 */
80
- if (!settings) {
81
- settings = this.getSettings(preferences);
82
- }
83
- await logger.logToStderr('');
84
- await logger.logToStderr('Configuring settings...');
139
+ if (!preferences.summary) {
140
+ return;
141
+ }
142
+ // used only for testing. Normally, we'd get the settings from the answers
143
+ /* c8 ignore next 3 */
144
+ if (!settings) {
145
+ settings = this.getSettings(preferences);
146
+ }
147
+ await logger.logToStderr('');
148
+ await logger.logToStderr('Configuring settings...');
149
+ await logger.logToStderr('');
150
+ await this.configureSettings({ preferences, settings, silent: false, logger });
151
+ if (!this.verbose) {
85
152
  await logger.logToStderr('');
86
- await this.configureSettings(settings, false, logger);
87
- if (!this.verbose) {
88
- await logger.logToStderr('');
89
- await logger.logToStderr(chalk.green('DONE'));
153
+ await logger.logToStderr(chalk.green('DONE'));
154
+ }
155
+ }
156
+ async configureExistingEntraApp(logger) {
157
+ await logger.logToStderr('Please provide the details of the existing app registration.');
158
+ let clientCertificateFile;
159
+ let clientCertificateBase64Encoded;
160
+ let clientCertificatePassword;
161
+ const clientId = await cli.promptForInput({
162
+ message: 'Client ID:',
163
+ /* c8 ignore next */
164
+ validate: value => validation.isValidGuid(value) ? true : 'The specified value is not a valid GUID.'
165
+ });
166
+ const tenantId = await cli.promptForInput({
167
+ message: 'Tenant ID (leave common if the app is multitenant):',
168
+ default: 'common',
169
+ /* c8 ignore next */
170
+ validate: value => value === 'common' || validation.isValidGuid(value) ? true : `Tenant ID must be a valid GUID or 'common'.`
171
+ });
172
+ const clientSecret = await cli.promptForInput({
173
+ message: 'Client secret (leave empty if you use a certificate or a public client):'
174
+ });
175
+ if (!clientSecret) {
176
+ clientCertificateFile = await cli.promptForInput({
177
+ message: `Path to the client certificate file (leave empty if you want to specify a base64-encoded certificate string):`
178
+ });
179
+ if (!clientCertificateFile) {
180
+ clientCertificateBase64Encoded = await cli.promptForInput({
181
+ message: `Base64-encoded certificate string (leave empty if you don't connect using a certificate):`
182
+ });
183
+ }
184
+ if (clientCertificateFile || clientCertificateBase64Encoded) {
185
+ clientCertificatePassword = await cli.promptForInput({
186
+ message: 'Password for the client certificate (leave empty if the certificate is not password-protected):'
187
+ });
90
188
  }
91
189
  }
190
+ return {
191
+ clientId,
192
+ tenantId,
193
+ clientSecret,
194
+ clientCertificateFile,
195
+ clientCertificateBase64Encoded,
196
+ clientCertificatePassword
197
+ };
92
198
  }
93
- getSummaryMessage(settings) {
199
+ async createNewEntraApp(preferences, logger) {
200
+ if (!await cli.promptForConfirmation({
201
+ message: 'CLI for Microsoft 365 will now sign in to your Microsoft 365 tenant as Microsoft Azure CLI to create a new app registration. Continue?',
202
+ default: false
203
+ })) {
204
+ throw 'Cancelled';
205
+ }
206
+ // setup auth
207
+ auth.connection.authType = AuthType.Browser;
208
+ // Microsoft Azure CLI app ID
209
+ auth.connection.appId = '04b07795-8ddb-461a-bbee-02f9e1bf7b46';
210
+ auth.connection.tenant = 'common';
211
+ await auth.ensureAccessToken(auth.defaultResource, logger, this.debug);
212
+ auth.connection.active = true;
213
+ const options = {
214
+ allowPublicClientFlows: true,
215
+ apisDelegated: (preferences.newEntraAppScopes === NewEntraAppScopes.All ? config.allScopes : config.minimalScopes).join(','),
216
+ implicitFlow: false,
217
+ multitenant: false,
218
+ name: 'CLI for Microsoft 365',
219
+ platform: 'publicClient',
220
+ redirectUris: 'http://localhost,https://localhost,https://login.microsoftonline.com/common/oauth2/nativeclient'
221
+ };
222
+ const apis = await entraApp.resolveApis({
223
+ options,
224
+ logger,
225
+ verbose: this.verbose,
226
+ debug: this.debug
227
+ });
228
+ const appInfo = await entraApp.createAppRegistration({
229
+ options,
230
+ apis,
231
+ logger,
232
+ verbose: this.verbose,
233
+ debug: this.debug
234
+ });
235
+ appInfo.tenantId = accessToken.getTenantIdFromAccessToken(auth.connection.accessTokens[auth.defaultResource].accessToken);
236
+ await entraApp.grantAdminConsent({
237
+ appInfo,
238
+ appPermissions: entraApp.appPermissions,
239
+ adminConsent: true,
240
+ logger,
241
+ debug: this.debug
242
+ });
243
+ return appInfo;
244
+ }
245
+ getSummaryMessage(preferences) {
94
246
  const messageLines = [`Based on your preferences, we'll configure the following settings:`];
247
+ switch (preferences.entraApp) {
248
+ case EntraAppConfig.Create:
249
+ messageLines.push(`- Entra app: Create a new app registration with ${preferences.newEntraAppScopes} scopes`);
250
+ break;
251
+ case EntraAppConfig.UseExisting:
252
+ messageLines.push(`- Entra app: use existing`);
253
+ messageLines.push(` - Client ID: ${preferences.clientId}`);
254
+ messageLines.push(` - Tenant ID: ${preferences.tenantId}`);
255
+ if (preferences.clientSecret) {
256
+ messageLines.push(` - Client secret: ${preferences.clientSecret}`);
257
+ }
258
+ if (preferences.clientCertificateFile) {
259
+ messageLines.push(` - Client certificate file: ${preferences.clientCertificateFile}`);
260
+ }
261
+ if (preferences.clientCertificateBase64Encoded) {
262
+ messageLines.push(` - Client certificate base64-encoded: ${preferences.clientCertificateBase64Encoded}`);
263
+ }
264
+ if (preferences.clientCertificatePassword) {
265
+ messageLines.push(` - Client certificate password: ${preferences.clientCertificatePassword}`);
266
+ }
267
+ break;
268
+ case EntraAppConfig.Skip:
269
+ messageLines.push(`- Entra app: skip`);
270
+ break;
271
+ }
272
+ const settings = this.getSettings(preferences);
95
273
  for (const [key, value] of Object.entries(settings)) {
96
274
  messageLines.push(`- ${key}: ${value}`);
97
275
  }
@@ -101,10 +279,10 @@ class SetupCommand extends AnonymousCommand {
101
279
  getSettings(answers) {
102
280
  const settings = {};
103
281
  switch (answers.usageMode) {
104
- case 'Interactively':
282
+ case CliUsageMode.Interactively:
105
283
  Object.assign(settings, interactivePreset);
106
284
  break;
107
- case 'Scripting':
285
+ case CliUsageMode.Scripting:
108
286
  Object.assign(settings, scriptingPreset);
109
287
  break;
110
288
  }
@@ -112,16 +290,60 @@ class SetupCommand extends AnonymousCommand {
112
290
  Object.assign(settings, powerShellPreset);
113
291
  }
114
292
  switch (answers.experience) {
115
- case 'Beginner':
116
- settings.helpMode = 'full';
293
+ case CliExperience.Beginner:
294
+ settings.helpMode = HelpMode.Full;
117
295
  break;
118
- case 'Proficient':
119
- settings.helpMode = 'options';
296
+ case CliExperience.Proficient:
297
+ settings.helpMode = HelpMode.Options;
298
+ break;
299
+ }
300
+ switch (answers.entraApp) {
301
+ case EntraAppConfig.Create:
302
+ settings.authType = 'browser';
303
+ break;
304
+ case EntraAppConfig.UseExisting:
305
+ if (answers.clientSecret) {
306
+ settings.authType = 'secret';
307
+ break;
308
+ }
309
+ if (answers.clientCertificateFile || answers.clientCertificateBase64Encoded) {
310
+ settings.authType = 'certificate';
311
+ break;
312
+ }
313
+ settings.authType = 'browser';
120
314
  break;
121
315
  }
122
316
  return settings;
123
317
  }
124
- async configureSettings(settings, silent, logger) {
318
+ async configureSettings({ preferences, settings, silent, logger }) {
319
+ switch (preferences.entraApp) {
320
+ case EntraAppConfig.Create:
321
+ if (this.verbose) {
322
+ await logger.logToStderr('Creating a new Entra app...');
323
+ }
324
+ const appSettings = await this.createNewEntraApp(preferences, logger);
325
+ Object.assign(settings, {
326
+ clientId: appSettings.appId,
327
+ tenantId: appSettings.tenantId
328
+ });
329
+ cli.getConfig().delete(settingsNames.clientSecret);
330
+ cli.getConfig().delete(settingsNames.clientCertificateFile);
331
+ cli.getConfig().delete(settingsNames.clientCertificateBase64Encoded);
332
+ cli.getConfig().delete(settingsNames.clientCertificatePassword);
333
+ break;
334
+ case EntraAppConfig.UseExisting:
335
+ Object.assign(settings, {
336
+ clientId: preferences.clientId,
337
+ tenantId: preferences.tenantId,
338
+ clientSecret: preferences.clientSecret,
339
+ clientCertificateFile: preferences.clientCertificateFile,
340
+ clientCertificateBase64Encoded: preferences.clientCertificateBase64Encoded,
341
+ clientCertificatePassword: preferences.clientCertificatePassword
342
+ });
343
+ break;
344
+ case EntraAppConfig.Skip:
345
+ break;
346
+ }
125
347
  if (this.debug) {
126
348
  await logger.logToStderr('Configuring settings...');
127
349
  await logger.logToStderr(JSON.stringify(settings, null, 2));
@@ -137,13 +359,14 @@ class SetupCommand extends AnonymousCommand {
137
359
  _SetupCommand_instances = new WeakSet(), _SetupCommand_initTelemetry = function _SetupCommand_initTelemetry() {
138
360
  this.telemetry.push((args) => {
139
361
  const properties = {
140
- interactive: args.options.interactive,
141
- scripting: args.options.scripting
362
+ interactive: !!args.options.interactive,
363
+ scripting: !!args.options.scripting,
364
+ skipApp: !!args.options.skipApp
142
365
  };
143
366
  Object.assign(this.telemetryProperties, properties);
144
367
  });
145
368
  }, _SetupCommand_initOptions = function _SetupCommand_initOptions() {
146
- this.options.unshift({ option: '--interactive' }, { option: '--scripting' });
369
+ this.options.unshift({ option: '--interactive' }, { option: '--scripting' }, { option: '--skipApp' });
147
370
  }, _SetupCommand_initValidators = function _SetupCommand_initValidators() {
148
371
  this.validators.push(async (args) => {
149
372
  if (args.options.interactive && args.options.scripting) {
@@ -4,8 +4,7 @@ export const interactivePreset = {
4
4
  output: 'text',
5
5
  printErrorsAsPlainText: true,
6
6
  prompt: true,
7
- showHelpOnFailure: true,
8
- showSpinner: true
7
+ showHelpOnFailure: true
9
8
  };
10
9
  export const scriptingPreset = {
11
10
  autoOpenLinksInBrowser: false,
@@ -13,8 +12,7 @@ export const scriptingPreset = {
13
12
  output: 'json',
14
13
  printErrorsAsPlainText: false,
15
14
  prompt: false,
16
- showHelpOnFailure: false,
17
- showSpinner: false
15
+ showHelpOnFailure: false
18
16
  };
19
17
  export const powerShellPreset = {
20
18
  errorOutput: 'stdout'
@@ -1,7 +1,7 @@
1
- import auth, { AuthType } from '../../../Auth.js';
2
- import commands from '../commands.js';
3
- import Command, { CommandError } from '../../../Command.js';
4
1
  import assert from 'assert';
2
+ import auth from '../../../Auth.js';
3
+ import Command, { CommandError } from '../../../Command.js';
4
+ import commands from '../commands.js';
5
5
  class ConnectionListCommand extends Command {
6
6
  get name() {
7
7
  return commands.LIST;
@@ -19,7 +19,7 @@ class ConnectionListCommand extends Command {
19
19
  return {
20
20
  name: connection.name,
21
21
  connectedAs: connection.identityName,
22
- authType: AuthType[connection.authType],
22
+ authType: connection.authType,
23
23
  active: isCurrentConnection
24
24
  };
25
25
  }).sort((a, b) => {