@pnp/cli-microsoft365 7.6.0 → 7.7.0-beta.7b57cf9

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 (81) hide show
  1. package/.eslintrc.cjs +1 -0
  2. package/allCommands.json +1 -1
  3. package/allCommandsFull.json +1 -1
  4. package/dist/Command.js +1 -1
  5. package/dist/m365/commands/setup.js +1 -7
  6. package/dist/m365/entra/commands/app/app-add.js +7 -1
  7. package/dist/m365/entra/commands/app/app-set.js +32 -3
  8. package/dist/m365/entra/commands/pim/pim-role-assignment-add.js +233 -0
  9. package/dist/m365/entra/commands/pim/pim-role-assignment-list.js +122 -0
  10. package/dist/m365/entra/commands.js +2 -0
  11. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.0.0.js +3 -1
  12. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.0.1.js +3 -1
  13. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.0.2.js +3 -1
  14. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.1.0.js +3 -1
  15. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.1.1.js +3 -1
  16. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.1.3.js +3 -1
  17. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.10.0.js +3 -1
  18. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.11.0.js +3 -1
  19. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.12.0.js +3 -1
  20. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.12.1.js +3 -1
  21. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.13.0.js +3 -1
  22. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.13.1.js +3 -1
  23. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.14.0.js +3 -1
  24. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.15.0.js +3 -1
  25. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.15.2.js +3 -1
  26. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.16.0.js +3 -1
  27. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.16.1.js +3 -1
  28. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.17.0.js +3 -1
  29. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.17.1.js +3 -1
  30. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.17.2.js +3 -1
  31. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.17.3.js +3 -1
  32. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.17.4.js +3 -1
  33. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.18.0.js +3 -1
  34. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.18.1.js +3 -1
  35. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.18.2.js +3 -1
  36. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.19.0-beta.0.js +25 -0
  37. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.2.0.js +3 -1
  38. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.3.0.js +3 -1
  39. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.3.1.js +3 -1
  40. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.3.2.js +3 -1
  41. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.3.4.js +3 -1
  42. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.4.0.js +3 -1
  43. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.4.1.js +3 -1
  44. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.5.0.js +3 -1
  45. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.5.1.js +3 -1
  46. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.6.0.js +3 -1
  47. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.7.0.js +3 -1
  48. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.7.1.js +3 -1
  49. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.8.0.js +3 -1
  50. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.8.1.js +3 -1
  51. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.8.2.js +3 -1
  52. package/dist/m365/spfx/commands/project/project-doctor/doctor-1.9.1.js +3 -1
  53. package/dist/m365/spfx/commands/project/project-doctor/generic-rules.js +0 -2
  54. package/dist/m365/spfx/commands/project/project-doctor/rules/FN021001_PKG_spfx_deps_versions_match_project_version.js +3 -2
  55. package/dist/m365/spfx/commands/project/project-doctor/rules/FN021013_PKG_spfx_devdeps_match_version.js +58 -0
  56. package/dist/m365/spfx/commands/project/project-doctor/spfx-deps.js +2 -0
  57. package/dist/m365/spfx/commands/project/project-doctor.js +2 -1
  58. package/dist/m365/spfx/commands/project/project-upgrade/upgrade-1.19.0-beta.0.js +53 -0
  59. package/dist/m365/spfx/commands/project/project-upgrade.js +16 -13
  60. package/dist/m365/spfx/commands/spfx-doctor.js +15 -0
  61. package/dist/m365/spo/commands/contenttype/contenttype-sync.js +139 -0
  62. package/dist/m365/spo/commands/listitem/listitem-set.js +9 -156
  63. package/dist/m365/spo/commands/page/page-add.js +6 -24
  64. package/dist/m365/spo/commands/page/page-set.js +19 -43
  65. package/dist/m365/spo/commands/site/site-apppermission-remove.js +8 -9
  66. package/dist/m365/spo/commands/site/site-hubsite-connect.js +3 -3
  67. package/dist/m365/spo/commands/site/site-hubsite-disconnect.js +2 -5
  68. package/dist/m365/spo/commands.js +1 -0
  69. package/dist/utils/prompt.js +2 -0
  70. package/dist/utils/spo.js +188 -0
  71. package/dist/utils/validation.js +4 -0
  72. package/docs/docs/cmd/entra/app/app-add.mdx +9 -0
  73. package/docs/docs/cmd/entra/app/app-set.mdx +9 -0
  74. package/docs/docs/cmd/entra/pim/pim-role-assignment-add.mdx +230 -0
  75. package/docs/docs/cmd/entra/pim/pim-role-assignment-list.mdx +224 -0
  76. package/docs/docs/cmd/spfx/project/project-upgrade.mdx +1 -1
  77. package/docs/docs/cmd/spo/contenttype/contenttype-sync.mdx +144 -0
  78. package/docs/docs/cmd/spo/page/page-set.mdx +1 -1
  79. package/docs/docs/cmd/spo/site/site-apppermission-add.mdx +1 -1
  80. package/npm-shrinkwrap.json +140 -216
  81. package/package.json +15 -15
@@ -5,7 +5,6 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
5
5
  };
6
6
  var _SpoPageSetCommand_instances, _SpoPageSetCommand_initTelemetry, _SpoPageSetCommand_initOptions, _SpoPageSetCommand_initTypes, _SpoPageSetCommand_initValidators;
7
7
  import { Auth } from '../../../../Auth.js';
8
- import { cli } from '../../../../cli/cli.js';
9
8
  import request from '../../../../request.js';
10
9
  import { formatting } from '../../../../utils/formatting.js';
11
10
  import { spo } from '../../../../utils/spo.js';
@@ -13,8 +12,6 @@ import { urlUtil } from '../../../../utils/urlUtil.js';
13
12
  import { validation } from '../../../../utils/validation.js';
14
13
  import SpoCommand from '../../../base/SpoCommand.js';
15
14
  import commands from '../../commands.js';
16
- import spoFileGetCommand from '../file/file-get.js';
17
- import spoListItemSetCommand from '../listitem/listitem-set.js';
18
15
  import { Page, supportedPageLayouts, supportedPromoteAs } from './Page.js';
19
16
  class SpoPageSetCommand extends SpoCommand {
20
17
  get name() {
@@ -49,6 +46,8 @@ class SpoPageSetCommand extends SpoCommand {
49
46
  }
50
47
  const listServerRelativeUrl = `${urlUtil.getServerRelativeSiteUrl(args.options.webUrl)}/sitepages`;
51
48
  const serverRelativeFileUrl = `${listServerRelativeUrl}/${pageName}`;
49
+ const listUrl = urlUtil.getServerRelativePath(args.options.webUrl, listServerRelativeUrl);
50
+ const requestUrl = `${args.options.webUrl}/_api/web/GetList('${formatting.encodeQueryParameter(listUrl)}')`;
52
51
  const needsToSavePage = !!args.options.title || !!args.options.description;
53
52
  try {
54
53
  const requestDigestResult = await spo.getRequestDigest(args.options.webUrl);
@@ -65,21 +64,16 @@ class SpoPageSetCommand extends SpoCommand {
65
64
  authorByline = page.AuthorByline;
66
65
  }
67
66
  if (args.options.layoutType) {
68
- const itemId = await this.getFileListItemId(args.options.webUrl, serverRelativeFileUrl);
67
+ const file = await spo.getFileAsListItemByUrl(args.options.webUrl, serverRelativeFileUrl, logger, this.verbose);
68
+ const itemId = file.Id;
69
69
  const listItemSetOptions = {
70
- webUrl: args.options.webUrl,
71
- listUrl: listServerRelativeUrl,
72
- id: itemId,
73
- systemUpdate: true,
74
- PageLayoutType: args.options.layoutType,
75
- verbose: this.verbose,
76
- debug: this.debug
70
+ PageLayoutType: args.options.layoutType
77
71
  };
78
72
  if (args.options.layoutType === 'Article') {
79
73
  listItemSetOptions.PromotedState = 0;
80
74
  listItemSetOptions.BannerImageUrl = `${resource}/_layouts/15/images/sitepagethumbnail.png, /_layouts/15/images/sitepagethumbnail.png`;
81
75
  }
82
- await cli.executeCommand(spoListItemSetCommand, { options: { ...listItemSetOptions, _: [] } });
76
+ await spo.systemUpdateListItem(requestUrl, itemId, logger, this.verbose, listItemSetOptions);
83
77
  }
84
78
  if (args.options.promoteAs) {
85
79
  const requestOptions = {
@@ -101,21 +95,17 @@ class SpoPageSetCommand extends SpoCommand {
101
95
  await request.post(requestOptions);
102
96
  break;
103
97
  case 'NewsPage':
104
- const newsPageItemId = await this.getFileListItemId(args.options.webUrl, serverRelativeFileUrl);
98
+ const newsPageItem = await spo.getFileAsListItemByUrl(args.options.webUrl, serverRelativeFileUrl, logger, this.verbose);
99
+ const newsPageItemId = newsPageItem.Id;
105
100
  const listItemSetOptions = {
106
- webUrl: args.options.webUrl,
107
- listUrl: listServerRelativeUrl,
108
- id: newsPageItemId,
109
- systemUpdate: true,
110
101
  PromotedState: 2,
111
- FirstPublishedDate: new Date().toISOString(),
112
- verbose: this.verbose,
113
- debug: this.debug
102
+ FirstPublishedDate: new Date().toISOString()
114
103
  };
115
- await cli.executeCommand(spoListItemSetCommand, { options: { ...listItemSetOptions, _: [] } });
104
+ await spo.systemUpdateListItem(requestUrl, newsPageItemId, logger, this.verbose, listItemSetOptions);
116
105
  break;
117
106
  case 'Template':
118
- const templateItemId = await this.getFileListItemId(args.options.webUrl, serverRelativeFileUrl);
107
+ const templateItem = await spo.getFileAsListItemByUrl(args.options.webUrl, serverRelativeFileUrl, logger, this.verbose);
108
+ const templateItemId = templateItem.Id;
119
109
  requestOptions.headers = {
120
110
  'X-RequestDigest': requestDigest,
121
111
  'content-type': 'application/json;odata=nometadata',
@@ -210,17 +200,12 @@ class SpoPageSetCommand extends SpoCommand {
210
200
  await request.post(requestOptions);
211
201
  }
212
202
  if (args.options.demoteFrom === 'NewsPage') {
213
- const fileId = await this.getFileListItemId(args.options.webUrl, serverRelativeFileUrl);
203
+ const file = await spo.getFileAsListItemByUrl(args.options.webUrl, serverRelativeFileUrl, logger, this.verbose);
204
+ const fileId = file.Id;
214
205
  const listItemSetOptions = {
215
- webUrl: args.options.webUrl,
216
- listUrl: listServerRelativeUrl,
217
- id: fileId,
218
- systemUpdate: true,
219
- PromotedState: 0,
220
- verbose: this.verbose,
221
- debug: this.debug
206
+ PromotedState: 0
222
207
  };
223
- await cli.executeCommandWithOutput(spoListItemSetCommand, { options: { ...listItemSetOptions, _: [] } });
208
+ await spo.systemUpdateListItem(requestUrl, fileId, logger, this.verbose, listItemSetOptions);
224
209
  }
225
210
  let requestOptions;
226
211
  if (!args.options.publish) {
@@ -254,18 +239,6 @@ class SpoPageSetCommand extends SpoCommand {
254
239
  this.handleRejectedODataJsonPromise(err);
255
240
  }
256
241
  }
257
- async getFileListItemId(webUrl, serverRelativeFileUrl) {
258
- const fileGetOptions = {
259
- webUrl: webUrl,
260
- url: serverRelativeFileUrl,
261
- asListItem: true,
262
- verbose: this.verbose,
263
- debug: this.debug
264
- };
265
- const fileGetOutput = await cli.executeCommandWithOutput(spoFileGetCommand, { options: { ...fileGetOptions, _: [] } });
266
- const fileGetOutputJson = JSON.parse(fileGetOutput.stdout);
267
- return fileGetOutputJson.Id;
268
- }
269
242
  }
270
243
  _SpoPageSetCommand_instances = new WeakSet(), _SpoPageSetCommand_initTelemetry = function _SpoPageSetCommand_initTelemetry() {
271
244
  this.telemetry.push((args) => {
@@ -317,6 +290,9 @@ _SpoPageSetCommand_instances = new WeakSet(), _SpoPageSetCommand_initTelemetry =
317
290
  if (isValidSharePointUrl !== true) {
318
291
  return isValidSharePointUrl;
319
292
  }
293
+ if (!args.options.layoutType && !args.options.promoteAs && !args.options.demoteFrom && args.options.commentsEnabled === undefined && !args.options.publish && !args.options.description && !args.options.title && !args.options.content) {
294
+ return 'Specify at least one option to update.';
295
+ }
320
296
  if (args.options.layoutType &&
321
297
  supportedPageLayouts.indexOf(args.options.layoutType) < 0) {
322
298
  return `${args.options.layoutType} is not a valid option for layoutType. Allowed values ${supportedPageLayouts.join(', ')}`;
@@ -15,7 +15,7 @@ class SpoSiteAppPermissionRemoveCommand extends GraphCommand {
15
15
  return commands.SITE_APPPERMISSION_REMOVE;
16
16
  }
17
17
  get description() {
18
- return 'Removes an application permission from the site';
18
+ return 'Removes a specific application permission from a site';
19
19
  }
20
20
  constructor() {
21
21
  super();
@@ -48,7 +48,7 @@ class SpoSiteAppPermissionRemoveCommand extends GraphCommand {
48
48
  }
49
49
  async getPermissionIds(siteId, options) {
50
50
  if (options.id) {
51
- return Promise.resolve([options.id]);
51
+ return [options.id];
52
52
  }
53
53
  const permissionsObject = await this.getPermissions(siteId);
54
54
  let permissions = permissionsObject.value;
@@ -61,7 +61,7 @@ class SpoSiteAppPermissionRemoveCommand extends GraphCommand {
61
61
  const spRequestOptions = {
62
62
  url: `${this.resource}/v1.0/sites/${siteId}/permissions/${permissionId}`,
63
63
  headers: {
64
- 'accept': 'application/json;odata.metadata=none'
64
+ accept: 'application/json;odata.metadata=none'
65
65
  },
66
66
  responseType: 'json'
67
67
  };
@@ -69,16 +69,16 @@ class SpoSiteAppPermissionRemoveCommand extends GraphCommand {
69
69
  }
70
70
  async commandAction(logger, args) {
71
71
  if (args.options.force) {
72
- await this.removeSiteAppPermission(logger, args.options);
72
+ await this.removeSiteAppPermission(args.options);
73
73
  }
74
74
  else {
75
75
  const result = await cli.promptForConfirmation({ message: `Are you sure you want to remove the specified application permission from site ${args.options.siteUrl}?` });
76
76
  if (result) {
77
- await this.removeSiteAppPermission(logger, args.options);
77
+ await this.removeSiteAppPermission(args.options);
78
78
  }
79
79
  }
80
80
  }
81
- async removeSiteAppPermission(logger, options) {
81
+ async removeSiteAppPermission(options) {
82
82
  try {
83
83
  const siteId = await spo.getSpoGraphSiteId(options.siteUrl);
84
84
  const permissionIdsToRemove = await this.getPermissionIds(siteId, options);
@@ -86,8 +86,7 @@ class SpoSiteAppPermissionRemoveCommand extends GraphCommand {
86
86
  for (const permissionId of permissionIdsToRemove) {
87
87
  tasks.push(this.removePermissions(siteId, permissionId));
88
88
  }
89
- const response = await Promise.all(tasks);
90
- await logger.log(response);
89
+ await Promise.all(tasks);
91
90
  }
92
91
  catch (err) {
93
92
  this.handleRejectedODataJsonPromise(err);
@@ -100,7 +99,7 @@ _SpoSiteAppPermissionRemoveCommand_instances = new WeakSet(), _SpoSiteAppPermiss
100
99
  appId: typeof args.options.appId !== 'undefined',
101
100
  appDisplayName: typeof args.options.appDisplayName !== 'undefined',
102
101
  id: typeof args.options.id !== 'undefined',
103
- force: (!!args.options.force).toString()
102
+ force: !!args.options.force
104
103
  });
105
104
  });
106
105
  }, _SpoSiteAppPermissionRemoveCommand_initOptions = function _SpoSiteAppPermissionRemoveCommand_initOptions() {
@@ -6,7 +6,6 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
6
6
  var _SpoSiteHubSiteConnectCommand_instances, _SpoSiteHubSiteConnectCommand_initOptions, _SpoSiteHubSiteConnectCommand_initValidators;
7
7
  import request from '../../../../request.js';
8
8
  import { formatting } from '../../../../utils/formatting.js';
9
- import { spo } from '../../../../utils/spo.js';
10
9
  import { validation } from '../../../../utils/validation.js';
11
10
  import SpoCommand from '../../../base/SpoCommand.js';
12
11
  import commands from '../../commands.js';
@@ -25,11 +24,12 @@ class SpoSiteHubSiteConnectCommand extends SpoCommand {
25
24
  }
26
25
  async commandAction(logger, args) {
27
26
  try {
28
- const res = await spo.getRequestDigest(args.options.siteUrl);
27
+ if (this.verbose) {
28
+ await logger.logToStderr(`Connecting site collection ${args.options.siteUrl} to hub site ${args.options.id}...`);
29
+ }
29
30
  const requestOptions = {
30
31
  url: `${args.options.siteUrl}/_api/site/JoinHubSite('${formatting.encodeQueryParameter(args.options.id)}')`,
31
32
  headers: {
32
- 'X-RequestDigest': res.FormDigestValue,
33
33
  accept: 'application/json;odata=nometadata'
34
34
  },
35
35
  responseType: 'json'
@@ -6,7 +6,6 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
6
6
  var _SpoSiteHubSiteDisconnectCommand_instances, _SpoSiteHubSiteDisconnectCommand_initTelemetry, _SpoSiteHubSiteDisconnectCommand_initOptions, _SpoSiteHubSiteDisconnectCommand_initValidators;
7
7
  import { cli } from '../../../../cli/cli.js';
8
8
  import request from '../../../../request.js';
9
- import { spo } from '../../../../utils/spo.js';
10
9
  import { validation } from '../../../../utils/validation.js';
11
10
  import SpoCommand from '../../../base/SpoCommand.js';
12
11
  import commands from '../../commands.js';
@@ -15,7 +14,7 @@ class SpoSiteHubSiteDisconnectCommand extends SpoCommand {
15
14
  return commands.SITE_HUBSITE_DISCONNECT;
16
15
  }
17
16
  get description() {
18
- return 'Disconnects the specifies site collection from its hub site';
17
+ return 'Disconnects the specified site collection from its hub site';
19
18
  }
20
19
  constructor() {
21
20
  super();
@@ -37,14 +36,12 @@ class SpoSiteHubSiteDisconnectCommand extends SpoCommand {
37
36
  }
38
37
  async disconnectHubSite(logger, args) {
39
38
  try {
40
- const res = await spo.getRequestDigest(args.options.siteUrl);
41
39
  if (this.verbose) {
42
- await logger.logToStderr(`Disconnecting site collection ${args.options.siteUrl} from its hubsite...`);
40
+ await logger.logToStderr(`Disconnecting site collection ${args.options.siteUrl} from its hub site...`);
43
41
  }
44
42
  const requestOptions = {
45
43
  url: `${args.options.siteUrl}/_api/site/JoinHubSite('00000000-0000-0000-0000-000000000000')`,
46
44
  headers: {
47
- 'X-RequestDigest': res.FormDigestValue,
48
45
  accept: 'application/json;odata=nometadata'
49
46
  },
50
47
  responseType: 'json'
@@ -37,6 +37,7 @@ export default {
37
37
  CONTENTTYPE_LIST: `${prefix} contenttype list`,
38
38
  CONTENTTYPE_REMOVE: `${prefix} contenttype remove`,
39
39
  CONTENTTYPE_SET: `${prefix} contenttype set`,
40
+ CONTENTTYPE_SYNC: `${prefix} contenttype sync`,
40
41
  CONTENTTYPEHUB_GET: `${prefix} contenttypehub get`,
41
42
  CUSTOMACTION_ADD: `${prefix} customaction add`,
42
43
  CUSTOMACTION_CLEAR: `${prefix} customaction clear`,
@@ -3,6 +3,8 @@ import { cli } from '../cli/cli.js';
3
3
  let inquirerInput;
4
4
  let inquirerConfirm;
5
5
  let inquirerSelect;
6
+ ;
7
+ ;
6
8
  export const prompt = {
7
9
  /* c8 ignore next 9 */
8
10
  async forInput(config) {
package/dist/utils/spo.js CHANGED
@@ -1,3 +1,4 @@
1
+ import os from 'os';
1
2
  import url from 'url';
2
3
  import { urlUtil } from "./urlUtil.js";
3
4
  import { validation } from "./validation.js";
@@ -1231,6 +1232,113 @@ export const spo = {
1231
1232
  };
1232
1233
  await request.post(requestOptions);
1233
1234
  },
1235
+ /**
1236
+ * Gets a file as list item by url
1237
+ * @param absoluteListUrl The absolute url to the list
1238
+ * @param url The url of the file
1239
+ * @param logger The logger object
1240
+ * @param verbose If in verbose mode
1241
+ * @returns The list item object
1242
+ */
1243
+ async getFileAsListItemByUrl(absoluteListUrl, url, logger, verbose) {
1244
+ if (verbose && logger) {
1245
+ logger.logToStderr(`Getting the file properties with url ${url}`);
1246
+ }
1247
+ const serverRelativePath = urlUtil.getServerRelativePath(absoluteListUrl, url);
1248
+ const requestUrl = `${absoluteListUrl}/_api/web/GetFileByServerRelativePath(DecodedUrl=@f)?$expand=ListItemAllFields&@f='${formatting.encodeQueryParameter(serverRelativePath)}'`;
1249
+ const requestOptions = {
1250
+ url: requestUrl,
1251
+ headers: {
1252
+ 'accept': 'application/json;odata=nometadata'
1253
+ },
1254
+ responseType: 'json'
1255
+ };
1256
+ const file = await request.get(requestOptions);
1257
+ return file.ListItemAllFields;
1258
+ },
1259
+ /**
1260
+ * Updates a list item with system update
1261
+ * @param absoluteListUrl The absolute base URL without query parameters, pointing to the specific list where the item resides. This URL should represent the list.
1262
+ * @param itemId The id of the list item
1263
+ * @param properties An object of the properties that should be updated
1264
+ * @param contentTypeName The name of the content type to update
1265
+ * @param logger The logger object
1266
+ * @param verbose If in verbose mode
1267
+ * @returns The updated list item object
1268
+ */
1269
+ async systemUpdateListItem(absoluteListUrl, itemId, logger, verbose, properties, contentTypeName) {
1270
+ if (!properties && !contentTypeName) {
1271
+ // Neither properties nor contentTypeName provided, no need to proceed
1272
+ throw 'Either properties or contentTypeName must be provided for systemUpdateListItem.';
1273
+ }
1274
+ const parsedUrl = new URL(absoluteListUrl);
1275
+ const serverRelativeSiteMatch = absoluteListUrl.match(new RegExp('/sites/[^/]+'));
1276
+ const webUrl = `${parsedUrl.protocol}//${parsedUrl.host}${serverRelativeSiteMatch ?? ''}`;
1277
+ if (verbose && logger) {
1278
+ logger.logToStderr(`Getting list id...`);
1279
+ }
1280
+ const listRequestOptions = {
1281
+ url: `${absoluteListUrl}?$select=Id`,
1282
+ headers: {
1283
+ 'accept': 'application/json;odata=nometadata'
1284
+ },
1285
+ responseType: 'json'
1286
+ };
1287
+ const list = await request.get(listRequestOptions);
1288
+ const listId = list.Id;
1289
+ if (verbose && logger) {
1290
+ logger.logToStderr(`Getting request digest for systemUpdate request`);
1291
+ }
1292
+ const res = await spo.getRequestDigest(webUrl);
1293
+ const formDigestValue = res.FormDigestValue;
1294
+ const objectIdentity = await spo.requestObjectIdentity(webUrl, logger, verbose);
1295
+ let index = 0;
1296
+ const requestBodyOptions = properties ? Object.keys(properties).map(key => `
1297
+ <Method Name="ParseAndSetFieldValue" Id="${++index}" ObjectPathId="147">
1298
+ <Parameters>
1299
+ <Parameter Type="String">${key}</Parameter>
1300
+ <Parameter Type="String">${properties[key].toString()}</Parameter>
1301
+ </Parameters>
1302
+ </Method>`) : [];
1303
+ const additionalContentType = contentTypeName ? `
1304
+ <Method Name="ParseAndSetFieldValue" Id="${++index}" ObjectPathId="147">
1305
+ <Parameters>
1306
+ <Parameter Type="String">ContentType</Parameter>
1307
+ <Parameter Type="String">${contentTypeName}</Parameter>
1308
+ </Parameters>
1309
+ </Method>` : '';
1310
+ const requestBody = `<Request AddExpandoFieldTypeSuffix="true" SchemaVersion="15.0.0.0" LibraryVersion="16.0.0.0" ApplicationName="${config.applicationName}" xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009">
1311
+ <Actions>
1312
+ ${requestBodyOptions.join('')}${additionalContentType}
1313
+ <Method Name="SystemUpdate" Id="${++index}" ObjectPathId="147" />
1314
+ </Actions>
1315
+ <ObjectPaths>
1316
+ <Identity Id="147" Name="${objectIdentity}:list:${listId}:item:${itemId},1" />
1317
+ </ObjectPaths>
1318
+ </Request>`;
1319
+ const requestOptions = {
1320
+ url: `${webUrl}/_vti_bin/client.svc/ProcessQuery`,
1321
+ headers: {
1322
+ 'Content-Type': 'text/xml',
1323
+ 'X-RequestDigest': formDigestValue
1324
+ },
1325
+ data: requestBody
1326
+ };
1327
+ const response = await request.post(requestOptions);
1328
+ if (response.indexOf("ErrorMessage") > -1) {
1329
+ throw `Error occurred in systemUpdate operation - ${response}`;
1330
+ }
1331
+ const id = Number(itemId);
1332
+ const requestOptionsItems = {
1333
+ url: `${absoluteListUrl}/items(${id})`,
1334
+ headers: {
1335
+ 'accept': 'application/json;odata=nometadata'
1336
+ },
1337
+ responseType: 'json'
1338
+ };
1339
+ const itemsResponse = await request.get(requestOptionsItems);
1340
+ return (itemsResponse);
1341
+ },
1234
1342
  /**
1235
1343
  * Removes the retention label from the items in the given list.
1236
1344
  * @param webUrl The url of the web
@@ -1334,6 +1442,86 @@ export const spo = {
1334
1442
  };
1335
1443
  const site = await request.get(requestOptions);
1336
1444
  return site.id;
1445
+ },
1446
+ /**
1447
+ * Retrieves the ObjectIdentity from a SharePoint site
1448
+ * @param webUrl web url
1449
+ * @param logger The logger object
1450
+ * @param verbose If in verbose mode
1451
+ * @return The ObjectIdentity as string
1452
+ */
1453
+ async requestObjectIdentity(webUrl, logger, verbose) {
1454
+ const res = await spo.getRequestDigest(webUrl);
1455
+ const formDigestValue = res.FormDigestValue;
1456
+ const requestOptions = {
1457
+ url: `${webUrl}/_vti_bin/client.svc/ProcessQuery`,
1458
+ headers: {
1459
+ 'X-RequestDigest': formDigestValue
1460
+ },
1461
+ 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><Query Id="1" ObjectPathId="5"><Query SelectAllProperties="false"><Properties><Property Name="ServerRelativeUrl" ScalarProperty="true" /></Properties></Query></Query></Actions><ObjectPaths><Property Id="5" ParentId="3" Name="Web" /><StaticProperty Id="3" TypeId="{3747adcd-a3c3-41b9-bfab-4a64dd2f1e0a}" Name="Current" /></ObjectPaths></Request>`
1462
+ };
1463
+ const response = await request.post(requestOptions);
1464
+ if (verbose) {
1465
+ logger.logToStderr('Attempt to get _ObjectIdentity_ key values');
1466
+ }
1467
+ const json = JSON.parse(response);
1468
+ const contents = json.find(x => { return x.ErrorInfo; });
1469
+ if (contents && contents.ErrorInfo) {
1470
+ throw contents.ErrorInfo.ErrorMessage || 'ClientSvc unknown error';
1471
+ }
1472
+ const identityObject = json.find(x => { return x._ObjectIdentity_; });
1473
+ if (identityObject) {
1474
+ return identityObject._ObjectIdentity_;
1475
+ }
1476
+ throw 'Cannot proceed. _ObjectIdentity_ not found'; // this is not supposed to happen
1477
+ },
1478
+ /**
1479
+ * Updates a list item without system update
1480
+ * @param absoluteListUrl The absolute base URL without query parameters, pointing to the specific list where the item resides. This URL should represent the list.
1481
+ * @param itemId The id of the list item
1482
+ * @param properties An object of the properties that should be updated
1483
+ * @param contentTypeName The name of the content type to update
1484
+ * @returns The updated listitem object
1485
+ */
1486
+ async updateListItem(absoluteListUrl, itemId, properties, contentTypeName) {
1487
+ const requestBodyOptions = [
1488
+ ...(properties
1489
+ ? Object.keys(properties).map((key) => ({
1490
+ FieldName: key,
1491
+ FieldValue: properties[key].toString()
1492
+ }))
1493
+ : [])
1494
+ ];
1495
+ const requestBody = {
1496
+ formValues: requestBodyOptions
1497
+ };
1498
+ contentTypeName && requestBody.formValues.push({
1499
+ FieldName: 'ContentType',
1500
+ FieldValue: contentTypeName
1501
+ });
1502
+ const requestOptions = {
1503
+ url: `${absoluteListUrl}/items(${itemId})/ValidateUpdateListItem()`,
1504
+ headers: {
1505
+ 'accept': 'application/json;odata=nometadata'
1506
+ },
1507
+ data: requestBody,
1508
+ responseType: 'json'
1509
+ };
1510
+ const response = await request.post(requestOptions);
1511
+ // Response is from /ValidateUpdateListItem POST call, perform get on updated item to get all field values
1512
+ const fieldValues = response.value;
1513
+ if (fieldValues.some(f => f.HasException)) {
1514
+ throw `Updating the items has failed with the following errors: ${os.EOL}${fieldValues.filter(f => f.HasException).map(f => { return `- ${f.FieldName} - ${f.ErrorMessage}`; }).join(os.EOL)}`;
1515
+ }
1516
+ const requestOptionsItems = {
1517
+ url: `${absoluteListUrl}/items(${itemId})`,
1518
+ headers: {
1519
+ 'accept': 'application/json;odata=nometadata'
1520
+ },
1521
+ responseType: 'json'
1522
+ };
1523
+ const itemsResponse = await request.get(requestOptionsItems);
1524
+ return (itemsResponse);
1337
1525
  }
1338
1526
  };
1339
1527
  //# sourceMappingURL=spo.js.map
@@ -325,6 +325,10 @@ export const validation = {
325
325
  isValidMailNickname(mailNickname) {
326
326
  const mailNicknameRegEx = new RegExp(/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]*$/i);
327
327
  return mailNicknameRegEx.test(mailNickname);
328
+ },
329
+ isValidISODuration(duration) {
330
+ const durationRegEx = new RegExp(/^P(?!$)((\d+Y)|(\d+\.\d+Y$))?((\d+M)|(\d+\.\d+M$))?((\d+W)|(\d+\.\d+W$))?((\d+D)|(\d+\.\d+D$))?(T(?=\d)((\d+H)|(\d+\.\d+H$))?((\d+M)|(\d+\.\d+M$))?(\d+(\.\d+)?S)?)??$/);
331
+ return durationRegEx.test(duration);
328
332
  }
329
333
  };
330
334
  //# sourceMappingURL=validation.js.map
@@ -78,6 +78,9 @@ m365 entra appregistration add [options]
78
78
 
79
79
  `--save`
80
80
  : Use to store the information about the created app in a local file.
81
+
82
+ `--allowPublicClientFlows`
83
+ : Enable the allow public client flows feature on the app registration.
81
84
  ```
82
85
 
83
86
  <Global />
@@ -192,6 +195,12 @@ Create new Entra app registration with a certificate
192
195
  m365 entra app add --name 'My Entra app' --certificateDisplayName "Some certificate name" --certificateFile "c:\temp\some-certificate.cer"
193
196
  ```
194
197
 
198
+ Create a new Entra app registration with the allow public client flows feature enabled.
199
+
200
+ ```sh
201
+ m365 entra app add --name 'My Entra app' --allowPublicClientFlows
202
+ ```
203
+
195
204
  ## Response
196
205
 
197
206
  ### Standard response
@@ -49,6 +49,9 @@ m365 entra appregistration set [options]
49
49
 
50
50
  `--certificateDisplayName [certificateDisplayName]`
51
51
  : Display name for the certificate. If not given, the displayName will be set to the certificate subject. When specified, also specify either `certificateFile` or `certificateBase64Encoded`.
52
+
53
+ `--allowPublicClientFlows [allowPublicClientFlows]`
54
+ : Set to `true` or `false` to toggle the allow public client flows feature on the app registration.
52
55
  ```
53
56
 
54
57
  <Global />
@@ -99,6 +102,12 @@ Add a certificate to the app
99
102
  m365 entra app set --appId e75be2e1-0204-4f95-857d-51a37cf40be8 --certificateDisplayName "Some certificate name" --certificateFile "c:\temp\some-certificate.cer"
100
103
  ```
101
104
 
105
+ Enable the allow public client flows feature on the app registration
106
+
107
+ ```sh
108
+ m365 entra app set --appId e75be2e1-0204-4f95-857d-51a37cf40be8 --allowPublicClientFlows true
109
+ ```
110
+
102
111
  ## Response
103
112
 
104
113
  The command won't return a response on success.