From a81f464828f510d3a6747770c6edf59cebec102e Mon Sep 17 00:00:00 2001 From: Saurabh Date: Fri, 29 Nov 2024 14:38:50 +0100 Subject: [PATCH] New command: spo page default get. Closes #6493 --- docs/docs/cmd/spo/page/page-default-get.mdx | 179 ++++++++++++++++++ docs/src/config/sidebars.ts | 5 + src/m365/spo/commands.ts | 1 + .../commands/page/page-default-get.spec.ts | 122 ++++++++++++ .../spo/commands/page/page-default-get.ts | 78 ++++++++ 5 files changed, 385 insertions(+) create mode 100644 docs/docs/cmd/spo/page/page-default-get.mdx create mode 100644 src/m365/spo/commands/page/page-default-get.spec.ts create mode 100644 src/m365/spo/commands/page/page-default-get.ts diff --git a/docs/docs/cmd/spo/page/page-default-get.mdx b/docs/docs/cmd/spo/page/page-default-get.mdx new file mode 100644 index 00000000000..b6fa60fa58b --- /dev/null +++ b/docs/docs/cmd/spo/page/page-default-get.mdx @@ -0,0 +1,179 @@ +import Global from '/docs/cmd/_global.mdx'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# spo page default get + +Gets the home page for a specific site + +## Usage + +```sh +m365 spo page default get [options] +``` + +## Options + +```md definition-list +`-u, --siteUrl ` +: URL of the SharePoint site. +``` + + + +## Examples + +Get the home page of a specific site + +```sh +m365 spo page default get --siteUrl https://contoso.sharepoint.com/sites/Marketing +``` + +## Response + + + + + ```json + { + "ListItemAllFields": { + "CommentsDisabled": true, + "FileSystemObjectType": 0, + "Id": 1, + "ServerRedirectedEmbedUri": null, + "ServerRedirectedEmbedUrl": "", + "ContentTypeId": "0x0101009D1CB255DA76424F860D91F20E6C411800DE13AFD7BD1A9847A5DEB2C981024CB6", + "OData__ColorTag": null, + "ComplianceAssetId": null, + "WikiField": null, + "Title": "Home", + "ClientSideApplicationId": "b6917cb1-93a0-4b97-a84d-7cf49975d4ec", + "PageLayoutType": "Home", + "CanvasContent1": null, + "BannerImageUrl": null, + "Description": null, + "PromotedState": null, + "FirstPublishedDate": null, + "LayoutWebpartsContent": null, + "OData__AuthorBylineId": null, + "_AuthorBylineStringId": null, + "OData__TopicHeader": null, + "OData__SPSitePageFlags": null, + "OData__SPCallToAction": null, + "OData__OriginalSourceUrl": null, + "OData__OriginalSourceSiteId": null, + "OData__OriginalSourceWebId": null, + "OData__OriginalSourceListId": null, + "OData__OriginalSourceItemId": null, + "ID": 1, + "Created": "2023-05-20T17:37:33", + "AuthorId": 1073741823, + "Modified": "2023-05-20T17:37:33", + "EditorId": 1073741823, + "OData__CopySource": null, + "CheckoutUserId": null, + "OData__UIVersionString": "1.0", + "GUID": "25af1202-addb-4f3a-ab69-f14d5630796a" + }, + "CheckInComment": "", + "CheckOutType": 2, + "ContentTag": "{2F6CF80C-E4CC-4FCC-A424-8D36770AC481},4,1", + "CustomizedPageStatus": 1, + "ETag": "\"{2F6CF80C-E4CC-4FCC-A424-8D36770AC481},4\"", + "Exists": true, + "ExistsAllowThrowForPolicyFailures": true, + "ExistsWithException": true, + "IrmEnabled": false, + "Length": "805", + "Level": 1, + "LinkingUri": null, + "LinkingUrl": "", + "MajorVersion": 1, + "MinorVersion": 0, + "Name": "Home.aspx", + "ServerRelativeUrl": "/teams/Marketing/SitePages/Home.aspx", + "TimeCreated": "2023-05-21T00:37:33Z", + "TimeLastModified": "2023-05-21T00:37:33Z", + "Title": "Home", + "UIVersion": 512, + "UIVersionLabel": "1.0", + "UniqueId": "2f6cf80c-e4cc-4fcc-a424-8d36770ac481" + } + ``` + + + + + ```text + CheckInComment : + CheckOutType : 2 + ContentTag : {2F6CF80C-E4CC-4FCC-A424-8D36770AC481},4,1 + CustomizedPageStatus : 1 + ETag : "{2F6CF80C-E4CC-4FCC-A424-8D36770AC481},4" + Exists : true + ExistsAllowThrowForPolicyFailures: true + ExistsWithException : true + IrmEnabled : false + Length : 805 + Level : 1 + LinkingUri : null + LinkingUrl : + ListItemAllFields : {"CommentsDisabled":true,"FileSystemObjectType":0,"Id":1,"ServerRedirectedEmbedUri":null,"ServerRedirectedEmbedUrl":"","ContentTypeId":"0x0101009D1CB255DA76424F860D91F20E6C411800DE13AFD7BD1A9847A5DEB2C981024CB6","OData__ColorTag":null,"ComplianceAssetId":null,"WikiField":null,"Title":"Home","ClientSideApplicationId":"b6917cb1-93a0-4b97-a84d-7cf49975d4ec","PageLayoutType":"Home","CanvasContent1":null,"BannerImageUrl":null,"Description":null,"PromotedState":null,"FirstPublishedDate":null,"LayoutWebpartsContent":null,"OData__AuthorBylineId":null,"_AuthorBylineStringId":null,"OData__TopicHeader":null,"OData__SPSitePageFlags":null,"OData__SPCallToAction":null,"OData__OriginalSourceUrl":null,"OData__OriginalSourceSiteId":null,"OData__OriginalSourceWebId":null,"OData__OriginalSourceListId":null,"OData__OriginalSourceItemId":null,"ID":1,"Created":"2023-05-20T17:37:33","AuthorId":1073741823,"Modified":"2023-05-20T17:37:33","EditorId":1073741823,"OData__CopySource":null,"CheckoutUserId":null,"OData__UIVersionString":"1.0","GUID":"25af1202-addb-4f3a-ab69-f14d5630796a"} + MajorVersion : 1 + MinorVersion : 0 + Name : Home.aspx + ServerRelativeUrl : /teams/Marketing/SitePages/Home.aspx + TimeCreated : 2023-05-21T00:37:33Z + TimeLastModified : 2023-05-21T00:37:33Z + Title : Home + UIVersion : 512 + UIVersionLabel : 1.0 + UniqueId : 2f6cf80c-e4cc-4fcc-a424-8d36770ac481 + ``` + + + + + ```csv + CheckInComment,CheckOutType,ContentTag,CustomizedPageStatus,ETag,Exists,ExistsAllowThrowForPolicyFailures,ExistsWithException,IrmEnabled,Length,Level,LinkingUri,LinkingUrl,MajorVersion,MinorVersion,Name,ServerRelativeUrl,TimeCreated,TimeLastModified,Title,UIVersion,UIVersionLabel,UniqueId + ,2,"{2F6CF80C-E4CC-4FCC-A424-8D36770AC481},4,1",1,"""{2F6CF80C-E4CC-4FCC-A424-8D36770AC481},4""",1,1,1,0,805,1,,,1,0,Home.aspx,/teams/Marketing/SitePages/Home.aspx,2023-05-21T00:37:33Z,2023-05-21T00:37:33Z,Home,512,1.0,2f6cf80c-e4cc-4fcc-a424-8d36770ac481 + ``` + + + + + ```md + # spo page default get --debug "false" --verbose "false" --siteUrl "https://contoso.sharepoint.com/teams/Marketing" + + Date: 28/11/2024 + + ## Home (2f6cf80c-e4cc-4fcc-a424-8d36770ac481) + + Property | Value + ---------|------- + CheckInComment | + CheckOutType | 2 + ContentTag | {2F6CF80C-E4CC-4FCC-A424-8D36770AC481},4,1 + CustomizedPageStatus | 1 + ETag | "{2F6CF80C-E4CC-4FCC-A424-8D36770AC481},4" + Exists | true + ExistsAllowThrowForPolicyFailures | true + ExistsWithException | true + IrmEnabled | false + Length | 805 + Level | 1 + LinkingUrl | + MajorVersion | 1 + MinorVersion | 0 + Name | Home.aspx + ServerRelativeUrl | /teams/Marketing/SitePages/Home.aspx + TimeCreated | 2023-05-21T00:37:33Z + TimeLastModified | 2023-05-21T00:37:33Z + Title | Home + UIVersion | 512 + UIVersionLabel | 1.0 + UniqueId | 2f6cf80c-e4cc-4fcc-a424-8d36770ac481 + ``` + + + diff --git a/docs/src/config/sidebars.ts b/docs/src/config/sidebars.ts index 06ed8558b56..314c0f8e73b 100644 --- a/docs/src/config/sidebars.ts +++ b/docs/src/config/sidebars.ts @@ -3221,6 +3221,11 @@ const sidebars: SidebarsConfig = { label: 'page control set', id: 'cmd/spo/page/page-control-set' }, + { + type: 'doc', + label: 'page default get', + id: 'cmd/spo/page/page-default-get' + }, { type: 'doc', label: 'page header set', diff --git a/src/m365/spo/commands.ts b/src/m365/spo/commands.ts index 9ad1de6b0f2..ee60888e05b 100644 --- a/src/m365/spo/commands.ts +++ b/src/m365/spo/commands.ts @@ -213,6 +213,7 @@ export default { PAGE_CONTROL_GET: `${prefix} page control get`, PAGE_CONTROL_LIST: `${prefix} page control list`, PAGE_CONTROL_SET: `${prefix} page control set`, + PAGE_DEFAULT_GET: `${prefix} page default get`, PAGE_HEADER_SET: `${prefix} page header set`, PAGE_SECTION_ADD: `${prefix} page section add`, PAGE_SECTION_GET: `${prefix} page section get`, diff --git a/src/m365/spo/commands/page/page-default-get.spec.ts b/src/m365/spo/commands/page/page-default-get.spec.ts new file mode 100644 index 00000000000..b668ea91e0a --- /dev/null +++ b/src/m365/spo/commands/page/page-default-get.spec.ts @@ -0,0 +1,122 @@ +import assert from 'assert'; +import sinon from 'sinon'; +import { z } from 'zod'; +import auth from '../../../../Auth.js'; +import { cli } from '../../../../cli/cli.js'; +import { CommandInfo } from '../../../../cli/CommandInfo.js'; +import { Logger } from '../../../../cli/Logger.js'; +import { CommandError } from '../../../../Command.js'; +import request from '../../../../request.js'; +import { telemetry } from '../../../../telemetry.js'; +import { pid } from '../../../../utils/pid.js'; +import { session } from '../../../../utils/session.js'; +import { sinonUtil } from '../../../../utils/sinonUtil.js'; +import commands from '../../commands.js'; +import command from './page-default-get.js'; +import { urlUtil } from '../../../../utils/urlUtil.js'; +import { formatting } from '../../../../utils/formatting.js'; +import { pageListItemMock } from './page-get.mock.js'; + +describe(commands.PAGE_DEFAULT_GET, () => { + let log: string[]; + let logger: Logger; + let loggerLogSpy: sinon.SinonSpy; + let commandInfo: CommandInfo; + let commandOptionsSchema: z.ZodTypeAny; + + const siteUrl = 'https://contoso.sharepoint.com/sites/Marketing'; + const serverRelativeUrl = urlUtil.getServerRelativeSiteUrl(siteUrl); + const page = { + WelcomePage: '/SitePages/Home.aspx' + }; + + before(() => { + sinon.stub(auth, 'restoreAuth').resolves(); + sinon.stub(telemetry, 'trackEvent').returns(); + sinon.stub(pid, 'getProcessName').returns(''); + sinon.stub(session, 'getId').returns(''); + auth.connection.active = true; + commandInfo = cli.getCommandInfo(command); + commandOptionsSchema = commandInfo.command.getSchemaToParse()!; + }); + + beforeEach(() => { + log = []; + logger = { + log: async (msg: string) => { + log.push(msg); + }, + logRaw: async (msg: string) => { + log.push(msg); + }, + logToStderr: async (msg: string) => { + log.push(msg); + } + }; + loggerLogSpy = sinon.spy(logger, 'log'); + }); + + afterEach(() => { + sinonUtil.restore([ + request.get + ]); + }); + + after(() => { + sinon.restore(); + auth.connection.active = false; + }); + + it('has correct name', () => { + assert.strictEqual(command.name, commands.PAGE_DEFAULT_GET); + }); + + it('has a description', () => { + assert.notStrictEqual(command.description, null); + }); + + it('fails validation if siteUrl is not a valid SharePoint URL', async () => { + const actual = commandOptionsSchema.safeParse({ siteUrl: 'foo' }); + assert.strictEqual(actual.success, false); + }); + + it('passes validation when the siteUrl is a valid SharePoint URL and name is specified', async () => { + const actual = commandOptionsSchema.safeParse({ siteUrl: siteUrl }); + assert.strictEqual(actual.success, true); + }); + + it('gets the home page details for a specific site', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `${siteUrl}/_api/Web/RootFolder?$select=WelcomePage`) { + return page; + } + + if (opts.url === `${siteUrl}/_api/web/GetFileByServerRelativePath(DecodedUrl='${serverRelativeUrl}/${formatting.encodeQueryParameter(page.WelcomePage)}')?$expand=ListItemAllFields/ClientSideApplicationId,ListItemAllFields/PageLayoutType,ListItemAllFields/CommentsDisabled`) { + return pageListItemMock; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { options: { siteUrl: siteUrl, verbose: true } }); + assert(loggerLogSpy.calledWith({ ...pageListItemMock })); + }); + + it('correctly handles API error', async () => { + const errorMessage = 'The file /sites/Marketing/SitePages/Welcome.aspx does not exist.'; + + sinon.stub(request, 'get').rejects({ + error: { + 'odata.error': { + message: { + lang: 'en-US', + value: errorMessage + } + } + } + }); + + await assert.rejects(command.action(logger, { options: { siteUrl: siteUrl } }), + new CommandError(errorMessage)); + }); +}); \ No newline at end of file diff --git a/src/m365/spo/commands/page/page-default-get.ts b/src/m365/spo/commands/page/page-default-get.ts new file mode 100644 index 00000000000..c256a7583c7 --- /dev/null +++ b/src/m365/spo/commands/page/page-default-get.ts @@ -0,0 +1,78 @@ +import { z } from 'zod'; +import { zod } from '../../../../utils/zod.js'; +import { Logger } from '../../../../cli/Logger.js'; +import { globalOptionsZod } from '../../../../Command.js'; +import request, { CliRequestOptions } from '../../../../request.js'; +import { formatting } from '../../../../utils/formatting.js'; +import { urlUtil } from '../../../../utils/urlUtil.js'; +import { validation } from '../../../../utils/validation.js'; +import SpoCommand from '../../../base/SpoCommand.js'; +import commands from '../../commands.js'; + +const options = globalOptionsZod + .extend({ + siteUrl: zod.alias('u', z.string() + .refine(url => validation.isValidSharePointUrl(url) === true, url => ({ + message: `'${url}' is not a valid SharePoint Online site URL.` + })) + ) + }) + .strict(); +declare type Options = z.infer; + +interface CommandArgs { + options: Options; +} + +class SpoPageDefaultGetCommand extends SpoCommand { + public get name(): string { + return commands.PAGE_DEFAULT_GET; + } + + public get description(): string { + return 'Gets the home page for a specific site'; + } + + public get schema(): z.ZodTypeAny { + return options; + } + + public async commandAction(logger: Logger, args: CommandArgs): Promise { + try { + if (this.verbose) { + await logger.logToStderr(`Retrieving home page information for site: '${args.options.siteUrl}'...`); + } + + let requestOptions: CliRequestOptions = { + url: `${args.options.siteUrl}/_api/Web/RootFolder?$select=WelcomePage`, + headers: { + accept: 'application/json;odata=nometadata' + }, + responseType: 'json' + }; + + const { WelcomePage } = await request.get<{ WelcomePage: string }>(requestOptions); + + if (this.verbose) { + await logger.logToStderr(`Home page URL retrieved: '${WelcomePage}'. Fetching the details...`); + } + + requestOptions = { + url: `${args.options.siteUrl}/_api/web/GetFileByServerRelativePath(DecodedUrl='${urlUtil.getServerRelativeSiteUrl(args.options.siteUrl)}/${formatting.encodeQueryParameter(WelcomePage)}')?$expand=ListItemAllFields/ClientSideApplicationId,ListItemAllFields/PageLayoutType,ListItemAllFields/CommentsDisabled`, + headers: { + accept: 'application/json;odata=nometadata' + }, + responseType: 'json' + }; + + const page = await request.get(requestOptions); + + await logger.log(page); + } + catch (err: any) { + this.handleRejectedODataJsonPromise(err); + } + } +} + +export default new SpoPageDefaultGetCommand(); \ No newline at end of file