Skip to content

Commit

Permalink
Enhances 'spo page get' to get default page. Closes #6493
Browse files Browse the repository at this point in the history
  • Loading branch information
Saurabh7019 authored and martinlingstuyl committed Jan 31, 2025
1 parent 3149a76 commit 57dcbc4
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 52 deletions.
13 changes: 11 additions & 2 deletions docs/docs/cmd/spo/page/page-get.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ m365 spo page get [options]
## Options

```md definition-list
`-n, --name <name>`
: Name of the page to retrieve.
`-n, --name [name]`
: Name of the page to retrieve. Specify either `name` or `default`, but not both.

`--default`
: Get the homepage of a specific web. Specify either `name` or `default`, but not both.

`-u, --webUrl <webUrl>`
: URL of the site where the page to retrieve is located.
Expand All @@ -39,6 +42,12 @@ Get information about the modern page.
m365 spo page get --webUrl https://contoso.sharepoint.com/sites/team-a --name home.aspx
```

Get information about the home page.

```sh
m365 spo page get --webUrl https://contoso.sharepoint.com/sites/team-a --default
```

Get all the metadata from the modern page, without the section and control count information.

```sh
Expand Down
40 changes: 28 additions & 12 deletions src/m365/spo/commands/page/page-get.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
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';
Expand All @@ -19,6 +20,7 @@ describe(commands.PAGE_GET, () => {
let logger: Logger;
let loggerLogSpy: sinon.SinonSpy;
let commandInfo: CommandInfo;
let commandOptionsSchema: z.ZodTypeAny;

before(() => {
sinon.stub(auth, 'restoreAuth').resolves();
Expand All @@ -27,6 +29,7 @@ describe(commands.PAGE_GET, () => {
sinon.stub(session, 'getId').returns('');
auth.connection.active = true;
commandInfo = cli.getCommandInfo(command);
commandOptionsSchema = commandInfo.command.getSchemaToParse()!;
});

beforeEach(() => {
Expand Down Expand Up @@ -149,6 +152,25 @@ describe(commands.PAGE_GET, () => {
}));
});

it('gets information about home page when default option is specified', async () => {
sinon.stub(request, 'get').callsFake(async (opts) => {
if (opts.url === `https://contoso.sharepoint.com/_api/Web/RootFolder?$select=WelcomePage`) {
return {
WelcomePage: '/SitePages/home.aspx'
};
}

if (opts.url === `https://contoso.sharepoint.com/_api/web/GetFileByServerRelativePath(DecodedUrl='/SitePages/home.aspx')?$expand=ListItemAllFields/ClientSideApplicationId,ListItemAllFields/PageLayoutType,ListItemAllFields/CommentsDisabled`) {
return pageListItemMock;
}

throw 'Invalid request';
});

await command.action(logger, { options: { webUrl: 'https://contoso.sharepoint.com', default: true, metadataOnly: true, output: 'json' } });
assert(loggerLogSpy.calledWith(pageListItemMock));
});

it('check if section and control HTML parsing gets skipped for metadata only mode', async () => {
sinon.stub(request, 'get').callsFake(async (opts) => {
if ((opts.url as string).indexOf(`/_api/web/GetFileByServerRelativePath(DecodedUrl='/sites/team-a/SitePages/home.aspx')`) > -1) {
Expand Down Expand Up @@ -204,23 +226,17 @@ describe(commands.PAGE_GET, () => {
});

it('supports specifying metadataOnly flag', () => {
const options = command.options;
let containsOption = false;
options.forEach(o => {
if (o.option === '--metadataOnly') {
containsOption = true;
}
});
assert(containsOption);
const actual = commandOptionsSchema.safeParse({ webUrl: 'https://contoso.sharepoint.com', metadataOnly: true, default: true });
assert.strictEqual(actual.success, true);
});

it('fails validation if the webUrl option is not a valid SharePoint site URL', async () => {
const actual = await command.validate({ options: { webUrl: 'foo', name: 'home.aspx' } }, commandInfo);
assert.notStrictEqual(actual, true);
const actual = commandOptionsSchema.safeParse({ webUrl: 'foo' });
assert.strictEqual(actual.success, false);
});

it('passes validation when the webUrl is a valid SharePoint URL and name is specified', async () => {
const actual = await command.validate({ options: { webUrl: 'https://contoso.sharepoint.com', name: 'home.aspx' } }, commandInfo);
assert.strictEqual(actual, true);
const actual = commandOptionsSchema.safeParse({ webUrl: 'https://contoso.sharepoint.com', name: 'home.aspx' });
assert.strictEqual(actual.success, true);
});
});
84 changes: 46 additions & 38 deletions src/m365/spo/commands/page/page-get.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
import { z } from 'zod';
import { zod } from '../../../../utils/zod.js';
import { Logger } from '../../../../cli/Logger.js';
import GlobalOptions from '../../../../GlobalOptions.js';
import request from '../../../../request.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({
webUrl: zod.alias('u', z.string()
.refine(url => validation.isValidSharePointUrl(url) === true, url => ({
message: `'${url}' is not a valid SharePoint Online site URL.`
}))
),
name: zod.alias('n', z.string()).optional(),
default: z.boolean().optional(),
metadataOnly: z.boolean().optional()
})
.strict();
declare type Options = z.infer<typeof options>;

interface CommandArgs {
options: Options;
}

interface Options extends GlobalOptions {
name: string;
webUrl: string;
metadataOnly?: boolean;
}

class SpoPageGetCommand extends SpoCommand {
public get name(): string {
return commands.PAGE_GET;
Expand All @@ -30,45 +40,43 @@ class SpoPageGetCommand extends SpoCommand {
return ['commentsDisabled', 'numSections', 'numControls', 'title', 'layoutType'];
}

constructor() {
super();

this.#initOptions();
this.#initValidators();
public get schema(): z.ZodTypeAny {
return options;
}

#initOptions(): void {
this.options.unshift(
{
option: '-n, --name <name>'
},
{
option: '-u, --webUrl <webUrl>'
},
{
option: '--metadataOnly'
}
);
}

#initValidators(): void {
this.validators.push(
async (args: CommandArgs) => validation.isValidSharePointUrl(args.options.webUrl)
);
public getRefinedSchema(schema: typeof options): z.ZodEffects<any> | undefined {
return schema
.refine(options => [options.name, options.default].filter(x => x !== undefined).length === 1, {
message: `Specify either name or default, but not both.`
});
}

public async commandAction(logger: Logger, args: CommandArgs): Promise<void> {
if (this.verbose) {
await logger.logToStderr(`Retrieving information about the page...`);
}

let pageName: string = args.options.name;
if (args.options.name.indexOf('.aspx') < 0) {
pageName += '.aspx';
}

let pageName: string = '';
try {
let requestOptions: any = {
if (args.options.name) {
pageName = args.options.name.endsWith('.aspx')
? args.options.name
: `${args.options.name}.aspx`;
}
else if (args.options.default) {
const requestOptions: CliRequestOptions = {
url: `${args.options.webUrl}/_api/Web/RootFolder?$select=WelcomePage`,
headers: {
accept: 'application/json;odata=nometadata'
},
responseType: 'json'
};

const { WelcomePage } = await request.get<{ WelcomePage: string }>(requestOptions);
pageName = WelcomePage.split('/').pop()!;
}

let requestOptions: CliRequestOptions = {
url: `${args.options.webUrl}/_api/web/GetFileByServerRelativePath(DecodedUrl='${urlUtil.getServerRelativeSiteUrl(args.options.webUrl)}/SitePages/${formatting.encodeQueryParameter(pageName)}')?$expand=ListItemAllFields/ClientSideApplicationId,ListItemAllFields/PageLayoutType,ListItemAllFields/CommentsDisabled`,
headers: {
'content-type': 'application/json;charset=utf-8',
Expand All @@ -80,7 +88,7 @@ class SpoPageGetCommand extends SpoCommand {
const page = await request.get<any>(requestOptions);

if (page.ListItemAllFields.ClientSideApplicationId !== 'b6917cb1-93a0-4b97-a84d-7cf49975d4ec') {
throw `Page ${args.options.name} is not a modern page.`;
throw `Page ${pageName} is not a modern page.`;
}

let pageItemData: any = {};
Expand Down

0 comments on commit 57dcbc4

Please sign in to comment.