diff --git a/docs/docs/cmd/spo/site/site-list.mdx b/docs/docs/cmd/spo/site/site-list.mdx index eebe060ee7d..9c19e21bb24 100644 --- a/docs/docs/cmd/spo/site/site-list.mdx +++ b/docs/docs/cmd/spo/site/site-list.mdx @@ -16,7 +16,7 @@ m365 spo site list [options] ```md definition-list `-t, --type [type]` -: convenience option for type of sites to list. Allowed values are `TeamSite,CommunicationSite`. +: convenience option for type of sites to list. Allowed values are `TeamSite,CommunicationSite,fullyArchived,recentlyArchived,archived`. `--webTemplate [webTemplate]` : type of sites to list. To be used with values like `GROUP#0` and `SITEPAGEPUBLISHING#0`. Specify either `type` or `webTemplate`, but not both. @@ -31,6 +31,11 @@ m365 spo site list [options] ## Remarks +When providing a value for option `type`, consider the following: + +- `fullyArchived`: return sites that are fully archived (longer than 7 days) +- `recentlyArchived`: return sites that are recently archived (within 7 days) +- `archived`: return both recently archived and fully archived sites Using the `--filter` option you can specify which sites you want to retrieve. For example, to get sites with _project_ in their URL, use `Url -like 'project'` as the filter. diff --git a/src/m365/spo/commands/site/site-list.spec.ts b/src/m365/spo/commands/site/site-list.spec.ts index 221fa46478f..f06f1c7fd89 100644 --- a/src/m365/spo/commands/site/site-list.spec.ts +++ b/src/m365/spo/commands/site/site-list.spec.ts @@ -1,4 +1,5 @@ import assert from 'assert'; +import { z } from 'zod'; import sinon from 'sinon'; import auth from '../../../../Auth.js'; import { cli } from '../../../../cli/cli.js'; @@ -20,6 +21,7 @@ describe(commands.SITE_LIST, () => { let logger: Logger; let loggerLogSpy: sinon.SinonSpy; let commandInfo: CommandInfo; + let commandOptionsSchema: z.ZodTypeAny; before(() => { sinon.stub(auth, 'restoreAuth').resolves(); @@ -30,6 +32,7 @@ describe(commands.SITE_LIST, () => { auth.connection.active = true; auth.connection.spoUrl = 'https://contoso.sharepoint.com'; commandInfo = cli.getCommandInfo(command); + commandOptionsSchema = commandInfo.command.getSchemaToParse()!; }); beforeEach(() => { @@ -72,39 +75,34 @@ describe(commands.SITE_LIST, () => { assert.deepStrictEqual(command.defaultProperties(), ['Title', 'Url']); }); - it('passes validation if type TeamSite specified', async () => { - const actual = await command.validate({ options: { type: 'TeamSite' } }, commandInfo); - assert.strictEqual(actual, true); - }); - - it('passes validation if type CommunicationSite specified', async () => { - const actual = await command.validate({ options: { type: 'CommunicationSite' } }, commandInfo); - assert.strictEqual(actual, true); + it('fails when type is invalid', () => { + const parsed = commandOptionsSchema.safeParse({ type: 'invalid' }); + assert.strictEqual(parsed.success, false); }); - it('fails validation if non existing type specified', async () => { - const actual = await command.validate({ options: { type: 'invalid' } }, commandInfo); - assert.notStrictEqual(actual, true); + it('fails when both type and webTemplate are specified', () => { + const parsed = commandOptionsSchema.safeParse({ type: 'TeamSite', webTemplate: 'STS#3' }); + assert.strictEqual(parsed.success, false); }); - it('fails validation if type and webTemplate are both specified', async () => { - const actual = await command.validate({ options: { type: 'TeamSite', webTemplate: 'STS#3' } }, commandInfo); - assert.notStrictEqual(actual, true); + it('fails when withOneDriveSites is used with type', () => { + const parsed = commandOptionsSchema.safeParse({ type: 'TeamSite', withOneDriveSites: true }); + assert.strictEqual(parsed.success, false); }); - it('fails validation if withOneDriveSites is specified together with the type option', async () => { - const actual = await command.validate({ options: { type: 'TeamSite', withOneDriveSites: '1' } }, commandInfo); - assert.notStrictEqual(actual, true); + it('fails when withOneDriveSites is used with webTemplate', () => { + const parsed = commandOptionsSchema.safeParse({ webTemplate: 'STS#3', withOneDriveSites: true }); + assert.strictEqual(parsed.success, false); }); - it('fails validation if withOneDriveSites is specified together with the webTemplate option', async () => { - const actual = await command.validate({ options: { webTemplate: 'STS#3', withOneDriveSites: '1' } }, commandInfo); - assert.notStrictEqual(actual, true); + it('passes when only withOneDriveSites is set', () => { + const parsed = commandOptionsSchema.safeParse({ withOneDriveSites: true }); + assert.strictEqual(parsed.success, true); }); it('retrieves list of sites when no type and no filter specified', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) { + if (opts.url === `https://contoso-admin.sharepoint.com/_vti_bin/client.svc/ProcessQuery`) { if (opts.headers && opts.headers['X-RequestDigest'] && opts.headers['X-RequestDigest'] === 'abc' && @@ -144,7 +142,7 @@ describe(commands.SITE_LIST, () => { it('retrieves list of sites when no type and no filter specified (debug)', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) { + if (opts.url === `https://contoso-admin.sharepoint.com/_vti_bin/client.svc/ProcessQuery`) { if (opts.headers && opts.headers['X-RequestDigest'] && opts.headers['X-RequestDigest'] === 'abc' && @@ -184,7 +182,7 @@ describe(commands.SITE_LIST, () => { it('retrieves list of sites when type TeamSite is specified', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) { + if (opts.url === `https://contoso-admin.sharepoint.com/_vti_bin/client.svc/ProcessQuery`) { if (opts.headers && opts.headers['X-RequestDigest'] && opts.headers['X-RequestDigest'] === 'abc' && @@ -224,7 +222,7 @@ describe(commands.SITE_LIST, () => { it('retrieves list of sites when type CommunicationSite is specified', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) { + if (opts.url === `https://contoso-admin.sharepoint.com/_vti_bin/client.svc/ProcessQuery`) { if (opts.headers && opts.headers['X-RequestDigest'] && opts.headers['X-RequestDigest'] === 'abc' && @@ -264,7 +262,7 @@ describe(commands.SITE_LIST, () => { it('retrieves list of all sites when results returned in multiple pages', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) { + if (opts.url === `https://contoso-admin.sharepoint.com/_vti_bin/client.svc/ProcessQuery`) { if (opts.headers && opts.headers['X-RequestDigest'] && opts.headers['X-RequestDigest'] === 'abc' && @@ -332,7 +330,7 @@ describe(commands.SITE_LIST, () => { it('includes all properties for json output', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) { + if (opts.url === `https://contoso-admin.sharepoint.com/_vti_bin/client.svc/ProcessQuery`) { if (opts.headers && opts.headers['X-RequestDigest'] && opts.headers['X-RequestDigest'] === 'abc' && @@ -372,7 +370,7 @@ describe(commands.SITE_LIST, () => { it('retrieves list of sites when STS#0 type and no filter specified', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) { + if (opts.url === `https://contoso-admin.sharepoint.com/_vti_bin/client.svc/ProcessQuery`) { if (opts.headers && opts.headers['X-RequestDigest'] && opts.headers['X-RequestDigest'] === 'abc' && @@ -412,7 +410,7 @@ describe(commands.SITE_LIST, () => { it('retrieves list of sites when no type and filter specified', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) { + if (opts.url === `https://contoso-admin.sharepoint.com/_vti_bin/client.svc/ProcessQuery`) { if (opts.headers && opts.headers['X-RequestDigest'] && opts.headers['X-RequestDigest'] === 'abc' && @@ -452,7 +450,7 @@ describe(commands.SITE_LIST, () => { it('retrieves list of sites when no filter and withOneDriveSites is specified', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) { + if (opts.url === `https://contoso-admin.sharepoint.com/_vti_bin/client.svc/ProcessQuery`) { if (opts.headers && opts.headers['X-RequestDigest'] && opts.headers['X-RequestDigest'] === 'abc' && @@ -492,7 +490,7 @@ describe(commands.SITE_LIST, () => { it('retrieves list of sites when STS#0 webTemplate and filter specified', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) { + if (opts.url === `https://contoso-admin.sharepoint.com/_vti_bin/client.svc/ProcessQuery`) { if (opts.headers && opts.headers['X-RequestDigest'] && opts.headers['X-RequestDigest'] === 'abc' && @@ -532,7 +530,7 @@ describe(commands.SITE_LIST, () => { it('escapes XML in the filter', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) { + if (opts.url === `https://contoso-admin.sharepoint.com/_vti_bin/client.svc/ProcessQuery`) { if (opts.headers && opts.headers['X-RequestDigest'] && opts.headers['X-RequestDigest'] === 'abc' && @@ -570,6 +568,145 @@ describe(commands.SITE_LIST, () => { ])); }); + it('passes validation if type fullyArchived specified', async () => { + const actual = await command.validate({ options: { type: 'fullyArchived' } }, commandInfo); + assert.strictEqual(actual, true); + }); + + it('passes validation if type recentlyArchived specified', async () => { + const actual = await command.validate({ options: { type: 'recentlyArchived' } }, commandInfo); + assert.strictEqual(actual, true); + }); + + it('passes validation if type archived specified', async () => { + const actual = await command.validate({ options: { type: 'archived' } }, commandInfo); + assert.strictEqual(actual, true); + }); + + it('retrieves list of sites when type fullyArchived is specified', async () => { + sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === `https://contoso-admin.sharepoint.com/_vti_bin/client.svc/ProcessQuery`) { + if (opts.headers && + opts.headers['X-RequestDigest'] && + opts.headers['X-RequestDigest'] === 'abc' && + opts.data === `ArchiveStatus -eq 'FullyArchived'false00`) { + return JSON.stringify([ + { + "SchemaVersion": "15.0.0.0", "LibraryVersion": "16.0.7206.1204", "ErrorInfo": null, "TraceCorrelationId": "487c379e-80f8-4000-80be-1d37a4995717" + }, 2, { + "IsNull": false + }, 4, { + "IsNull": false + }, 5, { + "_ObjectType_": "Microsoft.Online.SharePoint.TenantAdministration.SPOSitePropertiesEnumerable", "NextStartIndex": -1, "NextStartIndexFromSharePoint": null, "_Child_Items_": [ + { + "_ObjectType_": "Microsoft.Online.SharePoint.TenantAdministration.SiteProperties", "_ObjectIdentity_": "487c379e-80f8-4000-80be-1d37a4995717|908bed80-a04a-4433-b4a0-883d9847d110:67753f63-bc14-4012-869e-f808a43fe023\nSiteProperties\nhttps%3a%2f%2fcontoso.sharepoint.com%2fsites%2farchived_1", "AllowDownloadingNonWebViewableFiles": false, "AllowEditing": false, "AllowSelfServiceUpgrade": true, "AverageResourceUsage": 0, "CommentsOnSitePagesDisabled": false, "CompatibilityLevel": 15, "ConditionalAccessPolicy": 0, "CurrentResourceUsage": 0, "DenyAddAndCustomizePages": 2, "DisableAppViews": 0, "DisableCompanyWideSharingLinks": 0, "DisableFlows": 0, "HasHolds": false, "Lcid": 1033, "Status": "Active", "Title": "Archived site 1", "Url": "https:\u002f\u002fcontoso.sharepoint.com\u002fsites\u002farchived_1" + } + ] + } + ]); + } + } + + throw 'Invalid request'; + }); + + await command.action(logger, { options: { type: 'fullyArchived' } }); + assert(loggerLogSpy.calledOnce); + }); + + it('retrieves list of sites when type recentlyArchived is specified', async () => { + sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === `https://contoso-admin.sharepoint.com/_vti_bin/client.svc/ProcessQuery`) { + if (opts.headers && + opts.headers['X-RequestDigest'] && + opts.headers['X-RequestDigest'] === 'abc' && + opts.data === `ArchiveStatus -eq 'RecentlyArchived'false00`) { + return JSON.stringify([ + { + "SchemaVersion": "15.0.0.0", "LibraryVersion": "16.0.7206.1204", "ErrorInfo": null, "TraceCorrelationId": "487c379e-80f8-4000-80be-1d37a4995717" + }, 2, { + "IsNull": false + }, 4, { + "IsNull": false + }, 5, { + "_ObjectType_": "Microsoft.Online.SharePoint.TenantAdministration.SPOSitePropertiesEnumerable", "NextStartIndex": -1, "NextStartIndexFromSharePoint": null, "_Child_Items_": [ + { + "_ObjectType_": "Microsoft.Online.SharePoint.TenantAdministration.SiteProperties", "_ObjectIdentity_": "487c379e-80f8-4000-80be-1d37a4995717|908bed80-a04a-4433-b4a0-883d9847d110:67753f63-bc14-4012-869e-f808a43fe023\nSiteProperties\nhttps%3a%2f%2fcontoso.sharepoint.com%2fsites%2frecent_1", "AllowDownloadingNonWebViewableFiles": false, "AllowEditing": false, "AllowSelfServiceUpgrade": true, "AverageResourceUsage": 0, "CommentsOnSitePagesDisabled": false, "CompatibilityLevel": 15, "ConditionalAccessPolicy": 0, "CurrentResourceUsage": 0, "DenyAddAndCustomizePages": 2, "DisableAppViews": 0, "DisableCompanyWideSharingLinks": 0, "DisableFlows": 0, "HasHolds": false, "Lcid": 1033, "Status": "Active", "Title": "Recently archived 1", "Url": "https:\u002f\u002fcontoso.sharepoint.com\u002fsites\u002frecent_1" + } + ] + } + ]); + } + } + + throw 'Invalid request'; + }); + + await command.action(logger, { options: { type: 'recentlyArchived' } }); + assert(loggerLogSpy.calledOnce); + }); + + it('retrieves list of sites when type archived is specified', async () => { + sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === `https://contoso-admin.sharepoint.com/_vti_bin/client.svc/ProcessQuery`) { + if (opts.headers && + opts.headers['X-RequestDigest'] && + opts.headers['X-RequestDigest'] === 'abc' && + opts.data === `ArchiveStatus -ne 'NotArchived'false00`) { + return JSON.stringify([ + { + "SchemaVersion": "15.0.0.0", "LibraryVersion": "16.0.7206.1204", "ErrorInfo": null, "TraceCorrelationId": "487c379e-80f8-4000-80be-1d37a4995717" + }, 2, { + "IsNull": false + }, 4, { + "IsNull": false + }, 5, { + "_ObjectType_": "Microsoft.Online.SharePoint.TenantAdministration.SPOSitePropertiesEnumerable", "NextStartIndex": -1, "NextStartIndexFromSharePoint": null, "_Child_Items_": [ + { + "_ObjectType_": "Microsoft.Online.SharePoint.TenantAdministration.SiteProperties", "_ObjectIdentity_": "487c379e-80f8-4000-80be-1d37a4995717|908bed80-a04a-4433-b4a0-883d9847d110:67753f63-bc14-4012-869e-f808a43fe023\nSiteProperties\nhttps%3a%2f%2fcontoso.sharepoint.com%2fsites%2farchived_any_1", "AllowDownloadingNonWebViewableFiles": false, "AllowEditing": false, "AllowSelfServiceUpgrade": true, "AverageResourceUsage": 0, "CommentsOnSitePagesDisabled": false, "CompatibilityLevel": 15, "ConditionalAccessPolicy": 0, "CurrentResourceUsage": 0, "DenyAddAndCustomizePages": 2, "DisableAppViews": 0, "DisableCompanyWideSharingLinks": 0, "DisableFlows": 0, "HasHolds": false, "Lcid": 1033, "Status": "Active", "Title": "Archived any 1", "Url": "https:\u002f\u002fcontoso.sharepoint.com\u002fsites\u002farchived_any_1" + } + ] + } + ]); + } + } + + throw 'Invalid request'; + }); + + await command.action(logger, { options: { type: 'archived' } }); + assert(loggerLogSpy.calledOnce); + }); + + it('combines provided filter with archived filter when type archived is specified', async () => { + sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === `https://contoso-admin.sharepoint.com/_vti_bin/client.svc/ProcessQuery`) { + if (opts.headers && + opts.headers['X-RequestDigest'] && + opts.headers['X-RequestDigest'] === 'abc' && + opts.data === `Url -like 'ctest' and ArchiveStatus -ne 'NotArchived'false00`) { + return JSON.stringify([ + { + "SchemaVersion": "15.0.0.0", "LibraryVersion": "16.0.7206.1204", "ErrorInfo": null, "TraceCorrelationId": "487c379e-80f8-4000-80be-1d37a4995717" + }, 2, { + "IsNull": false + }, 4, { + "IsNull": false + }, 5, { + "_ObjectType_": "Microsoft.Online.SharePoint.TenantAdministration.SPOSitePropertiesEnumerable", "NextStartIndex": -1, "NextStartIndexFromSharePoint": null, "_Child_Items_": [] + } + ]); + } + } + + throw 'Invalid request'; + }); + + await command.action(logger, { options: { type: 'archived', filter: "Url -like 'ctest'" } }); + assert(loggerLogSpy.calledOnce); + }); + it('correctly handles error when retrieving sites', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) { diff --git a/src/m365/spo/commands/site/site-list.ts b/src/m365/spo/commands/site/site-list.ts index c743d142456..8ad78266c1b 100644 --- a/src/m365/spo/commands/site/site-list.ts +++ b/src/m365/spo/commands/site/site-list.ts @@ -1,23 +1,36 @@ import { Logger } from '../../../../cli/Logger.js'; +import { z } from 'zod'; +import { globalOptionsZod } from '../../../../Command.js'; import config from '../../../../config.js'; -import GlobalOptions from '../../../../GlobalOptions.js'; import request, { CliRequestOptions } from '../../../../request.js'; import { formatting } from '../../../../utils/formatting.js'; import { ClientSvcResponse, ClientSvcResponseContents, FormDigestInfo, spo } from '../../../../utils/spo.js'; +import { zod } from '../../../../utils/zod.js'; import SpoCommand from '../../../base/SpoCommand.js'; import commands from '../../commands.js'; import { SiteProperties } from './SiteProperties.js'; import { SPOSitePropertiesEnumerable } from './SPOSitePropertiesEnumerable.js'; -interface CommandArgs { - options: Options; +enum SiteListType { + TeamSite = 'TeamSite', + CommunicationSite = 'CommunicationSite', + fullyArchived = 'fullyArchived', + recentlyArchived = 'recentlyArchived', + archived = 'archived' } -interface Options extends GlobalOptions { - type?: string; - webTemplate?: string; - filter?: string; - withOneDriveSites?: boolean; +const options = globalOptionsZod + .extend({ + type: zod.alias('t', zod.coercedEnum(SiteListType)).optional(), + webTemplate: z.string().optional(), + filter: z.string().optional(), + withOneDriveSites: z.boolean().optional() + }) + .strict(); +declare type Options = z.infer; + +interface CommandArgs { + options: Options; } class SpoSiteListCommand extends SpoCommand { @@ -35,64 +48,18 @@ class SpoSiteListCommand extends SpoCommand { return ['Title', 'Url']; } - constructor() { - super(); - - this.#initTelemetry(); - this.#initOptions(); - this.#initValidators(); + public get schema(): z.ZodTypeAny | undefined { + return options; } - #initTelemetry(): void { - this.telemetry.push((args: CommandArgs) => { - Object.assign(this.telemetryProperties, { - webTemplate: args.options.webTemplate, - type: args.options.type, - filter: (!(!args.options.filter)).toString(), - withOneDriveSites: typeof args.options.withOneDriveSites !== 'undefined' + public getRefinedSchema(schema: typeof options): z.ZodEffects | undefined { + return schema + .refine(o => !(o.type && o.webTemplate), { + message: 'Specify either type or webTemplate, but not both' + }) + .refine(o => !(o.withOneDriveSites && (o.type !== undefined || o.webTemplate !== undefined)), { + message: 'When using withOneDriveSites, don\'t specify the type or webTemplate options' }); - }); - } - - #initOptions(): void { - this.options.unshift( - { - option: '-t, --type [type]', - autocomplete: ['TeamSite', 'CommunicationSite'] - }, - { - option: '--webTemplate [webTemplate]' - }, - { - option: '--filter [filter]' - }, - { - option: '--withOneDriveSites' - } - ); - } - - #initValidators(): void { - this.validators.push( - async (args: CommandArgs) => { - if (args.options.type && args.options.webTemplate) { - return 'Specify either type or webTemplate, but not both'; - } - - const typeValues = ['TeamSite', 'CommunicationSite']; - if (args.options.type && - typeValues.indexOf(args.options.type) < 0) { - return `${args.options.type} is not a valid value for the type option. Allowed values are ${typeValues.join('|')}`; - } - - if (args.options.withOneDriveSites - && (args.options.type || args.options.webTemplate)) { - return 'When using withOneDriveSites, don\'t specify the type or webTemplate options'; - } - - return true; - } - ); } public alias(): string[] | undefined { @@ -103,6 +70,7 @@ class SpoSiteListCommand extends SpoCommand { const webTemplate: string = this.getWebTemplateId(args.options); const includeOneDriveSites: boolean = args.options.withOneDriveSites || false; const personalSite: string = includeOneDriveSites === false ? '0' : '1'; + const effectiveFilter: string = this.buildFilter(args.options); try { const spoAdminUrl: string = await spo.getSpoAdminUrl(logger, this.debug); @@ -113,7 +81,7 @@ class SpoSiteListCommand extends SpoCommand { this.allSites = []; - await this.getAllSites(spoAdminUrl, formatting.escapeXml(args.options.filter || ''), '0', personalSite, webTemplate, undefined, logger); + await this.getAllSites(spoAdminUrl, formatting.escapeXml(effectiveFilter), '0', personalSite, webTemplate, undefined, logger); await logger.log(this.allSites); } catch (err: any) { @@ -170,6 +138,29 @@ class SpoSiteListCommand extends SpoCommand { return ''; } } + + private buildFilter(options: Options): string { + const providedFilter: string = options.filter || ''; + + let archivedFilter: string = ''; + switch (options.type) { + case 'fullyArchived': + archivedFilter = "ArchiveStatus -eq 'FullyArchived'"; + break; + case 'recentlyArchived': + archivedFilter = "ArchiveStatus -eq 'RecentlyArchived'"; + break; + case 'archived': + archivedFilter = "ArchiveStatus -ne 'NotArchived'"; + break; + } + + if (archivedFilter && providedFilter) { + return `${providedFilter} and ${archivedFilter}`; + } + + return archivedFilter || providedFilter; + } } export default new SpoSiteListCommand(); \ No newline at end of file