Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New command: spo page default get. #6509

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading