Skip to content

Commit 57dcbc4

Browse files
Saurabh7019martinlingstuyl
authored andcommitted
Enhances 'spo page get' to get default page. Closes #6493
1 parent 3149a76 commit 57dcbc4

File tree

3 files changed

+85
-52
lines changed

3 files changed

+85
-52
lines changed

docs/docs/cmd/spo/page/page-get.mdx

+11-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@ m365 spo page get [options]
1515
## Options
1616

1717
```md definition-list
18-
`-n, --name <name>`
19-
: Name of the page to retrieve.
18+
`-n, --name [name]`
19+
: Name of the page to retrieve. Specify either `name` or `default`, but not both.
20+
21+
`--default`
22+
: Get the homepage of a specific web. Specify either `name` or `default`, but not both.
2023

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

45+
Get information about the home page.
46+
47+
```sh
48+
m365 spo page get --webUrl https://contoso.sharepoint.com/sites/team-a --default
49+
```
50+
4251
Get all the metadata from the modern page, without the section and control count information.
4352

4453
```sh

src/m365/spo/commands/page/page-get.spec.ts

+28-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import assert from 'assert';
22
import sinon from 'sinon';
3+
import { z } from 'zod';
34
import auth from '../../../../Auth.js';
45
import { cli } from '../../../../cli/cli.js';
56
import { CommandInfo } from '../../../../cli/CommandInfo.js';
@@ -19,6 +20,7 @@ describe(commands.PAGE_GET, () => {
1920
let logger: Logger;
2021
let loggerLogSpy: sinon.SinonSpy;
2122
let commandInfo: CommandInfo;
23+
let commandOptionsSchema: z.ZodTypeAny;
2224

2325
before(() => {
2426
sinon.stub(auth, 'restoreAuth').resolves();
@@ -27,6 +29,7 @@ describe(commands.PAGE_GET, () => {
2729
sinon.stub(session, 'getId').returns('');
2830
auth.connection.active = true;
2931
commandInfo = cli.getCommandInfo(command);
32+
commandOptionsSchema = commandInfo.command.getSchemaToParse()!;
3033
});
3134

3235
beforeEach(() => {
@@ -149,6 +152,25 @@ describe(commands.PAGE_GET, () => {
149152
}));
150153
});
151154

155+
it('gets information about home page when default option is specified', async () => {
156+
sinon.stub(request, 'get').callsFake(async (opts) => {
157+
if (opts.url === `https://contoso.sharepoint.com/_api/Web/RootFolder?$select=WelcomePage`) {
158+
return {
159+
WelcomePage: '/SitePages/home.aspx'
160+
};
161+
}
162+
163+
if (opts.url === `https://contoso.sharepoint.com/_api/web/GetFileByServerRelativePath(DecodedUrl='/SitePages/home.aspx')?$expand=ListItemAllFields/ClientSideApplicationId,ListItemAllFields/PageLayoutType,ListItemAllFields/CommentsDisabled`) {
164+
return pageListItemMock;
165+
}
166+
167+
throw 'Invalid request';
168+
});
169+
170+
await command.action(logger, { options: { webUrl: 'https://contoso.sharepoint.com', default: true, metadataOnly: true, output: 'json' } });
171+
assert(loggerLogSpy.calledWith(pageListItemMock));
172+
});
173+
152174
it('check if section and control HTML parsing gets skipped for metadata only mode', async () => {
153175
sinon.stub(request, 'get').callsFake(async (opts) => {
154176
if ((opts.url as string).indexOf(`/_api/web/GetFileByServerRelativePath(DecodedUrl='/sites/team-a/SitePages/home.aspx')`) > -1) {
@@ -204,23 +226,17 @@ describe(commands.PAGE_GET, () => {
204226
});
205227

206228
it('supports specifying metadataOnly flag', () => {
207-
const options = command.options;
208-
let containsOption = false;
209-
options.forEach(o => {
210-
if (o.option === '--metadataOnly') {
211-
containsOption = true;
212-
}
213-
});
214-
assert(containsOption);
229+
const actual = commandOptionsSchema.safeParse({ webUrl: 'https://contoso.sharepoint.com', metadataOnly: true, default: true });
230+
assert.strictEqual(actual.success, true);
215231
});
216232

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

222238
it('passes validation when the webUrl is a valid SharePoint URL and name is specified', async () => {
223-
const actual = await command.validate({ options: { webUrl: 'https://contoso.sharepoint.com', name: 'home.aspx' } }, commandInfo);
224-
assert.strictEqual(actual, true);
239+
const actual = commandOptionsSchema.safeParse({ webUrl: 'https://contoso.sharepoint.com', name: 'home.aspx' });
240+
assert.strictEqual(actual.success, true);
225241
});
226242
});

src/m365/spo/commands/page/page-get.ts

+46-38
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,32 @@
1+
import { z } from 'zod';
2+
import { zod } from '../../../../utils/zod.js';
13
import { Logger } from '../../../../cli/Logger.js';
2-
import GlobalOptions from '../../../../GlobalOptions.js';
3-
import request from '../../../../request.js';
4+
import { globalOptionsZod } from '../../../../Command.js';
5+
import request, { CliRequestOptions } from '../../../../request.js';
46
import { formatting } from '../../../../utils/formatting.js';
57
import { urlUtil } from '../../../../utils/urlUtil.js';
68
import { validation } from '../../../../utils/validation.js';
79
import SpoCommand from '../../../base/SpoCommand.js';
810
import commands from '../../commands.js';
911

12+
const options = globalOptionsZod
13+
.extend({
14+
webUrl: zod.alias('u', z.string()
15+
.refine(url => validation.isValidSharePointUrl(url) === true, url => ({
16+
message: `'${url}' is not a valid SharePoint Online site URL.`
17+
}))
18+
),
19+
name: zod.alias('n', z.string()).optional(),
20+
default: z.boolean().optional(),
21+
metadataOnly: z.boolean().optional()
22+
})
23+
.strict();
24+
declare type Options = z.infer<typeof options>;
25+
1026
interface CommandArgs {
1127
options: Options;
1228
}
1329

14-
interface Options extends GlobalOptions {
15-
name: string;
16-
webUrl: string;
17-
metadataOnly?: boolean;
18-
}
19-
2030
class SpoPageGetCommand extends SpoCommand {
2131
public get name(): string {
2232
return commands.PAGE_GET;
@@ -30,45 +40,43 @@ class SpoPageGetCommand extends SpoCommand {
3040
return ['commentsDisabled', 'numSections', 'numControls', 'title', 'layoutType'];
3141
}
3242

33-
constructor() {
34-
super();
35-
36-
this.#initOptions();
37-
this.#initValidators();
43+
public get schema(): z.ZodTypeAny {
44+
return options;
3845
}
3946

40-
#initOptions(): void {
41-
this.options.unshift(
42-
{
43-
option: '-n, --name <name>'
44-
},
45-
{
46-
option: '-u, --webUrl <webUrl>'
47-
},
48-
{
49-
option: '--metadataOnly'
50-
}
51-
);
52-
}
53-
54-
#initValidators(): void {
55-
this.validators.push(
56-
async (args: CommandArgs) => validation.isValidSharePointUrl(args.options.webUrl)
57-
);
47+
public getRefinedSchema(schema: typeof options): z.ZodEffects<any> | undefined {
48+
return schema
49+
.refine(options => [options.name, options.default].filter(x => x !== undefined).length === 1, {
50+
message: `Specify either name or default, but not both.`
51+
});
5852
}
5953

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

65-
let pageName: string = args.options.name;
66-
if (args.options.name.indexOf('.aspx') < 0) {
67-
pageName += '.aspx';
68-
}
69-
59+
let pageName: string = '';
7060
try {
71-
let requestOptions: any = {
61+
if (args.options.name) {
62+
pageName = args.options.name.endsWith('.aspx')
63+
? args.options.name
64+
: `${args.options.name}.aspx`;
65+
}
66+
else if (args.options.default) {
67+
const requestOptions: CliRequestOptions = {
68+
url: `${args.options.webUrl}/_api/Web/RootFolder?$select=WelcomePage`,
69+
headers: {
70+
accept: 'application/json;odata=nometadata'
71+
},
72+
responseType: 'json'
73+
};
74+
75+
const { WelcomePage } = await request.get<{ WelcomePage: string }>(requestOptions);
76+
pageName = WelcomePage.split('/').pop()!;
77+
}
78+
79+
let requestOptions: CliRequestOptions = {
7280
url: `${args.options.webUrl}/_api/web/GetFileByServerRelativePath(DecodedUrl='${urlUtil.getServerRelativeSiteUrl(args.options.webUrl)}/SitePages/${formatting.encodeQueryParameter(pageName)}')?$expand=ListItemAllFields/ClientSideApplicationId,ListItemAllFields/PageLayoutType,ListItemAllFields/CommentsDisabled`,
7381
headers: {
7482
'content-type': 'application/json;charset=utf-8',
@@ -80,7 +88,7 @@ class SpoPageGetCommand extends SpoCommand {
8088
const page = await request.get<any>(requestOptions);
8189

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

8694
let pageItemData: any = {};

0 commit comments

Comments
 (0)