diff --git a/src/m365/spo/commands/cdn/cdn-get.spec.ts b/src/m365/spo/commands/cdn/cdn-get.spec.ts index 57d1acf8188..fc45bbb363f 100644 --- a/src/m365/spo/commands/cdn/cdn-get.spec.ts +++ b/src/m365/spo/commands/cdn/cdn-get.spec.ts @@ -12,12 +12,13 @@ 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 './cdn-get.js'; +import command, { options } from './cdn-get.js'; describe(commands.CDN_GET, () => { let log: any[]; let logger: Logger; let commandInfo: CommandInfo; + let commandOptionsSchema: typeof options; let loggerLogSpy: sinon.SinonSpy; let loggerLogToStderrSpy: sinon.SinonSpy; const spoAdminUrl = 'https://contoso-admin.sharepoint.com'; @@ -31,6 +32,7 @@ describe(commands.CDN_GET, () => { auth.connection.spoUrl = 'https://contoso.sharepoint.com'; auth.connection.spoTenantId = 'abc'; commandInfo = cli.getCommandInfo(command); + commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options; }); beforeEach(() => { @@ -93,7 +95,7 @@ describe(commands.CDN_GET, () => { throw 'Invalid request ' + opts.url; }); - await command.action(logger, { options: { verbose: true, type: 'Public' } }); + await command.action(logger, { options: commandOptionsSchema.parse({ verbose: true, type: 'Public' }) }); assert(loggerLogToStderrSpy.calledWithExactly(`Public CDN at ${spoAdminUrl} is enabled`)); }); @@ -119,7 +121,7 @@ describe(commands.CDN_GET, () => { throw 'Invalid request'; }); - await command.action(logger, { options: { type: 'Private' } }); + await command.action(logger, { options: commandOptionsSchema.parse({ type: 'Private' }) }); assert(loggerLogSpy.calledOnceWithExactly(false)); }); @@ -145,7 +147,7 @@ describe(commands.CDN_GET, () => { throw 'Invalid request'; }); - await command.action(logger, { options: { verbose: true } }); + await command.action(logger, { options: commandOptionsSchema.parse({ verbose: true }) }); assert(loggerLogSpy.calledOnceWithExactly(false)); }); @@ -171,7 +173,7 @@ describe(commands.CDN_GET, () => { throw 'Invalid request'; }); - await command.action(logger, { options: { verbose: true, type: 'Private' } }); + await command.action(logger, { options: commandOptionsSchema.parse({ verbose: true, type: 'Private' }) }); assert(loggerLogToStderrSpy.calledWithExactly(`Private CDN at ${spoAdminUrl} is disabled`)); }); @@ -197,7 +199,7 @@ describe(commands.CDN_GET, () => { throw 'Invalid request'; }); - await command.action(logger, { options: { verbose: true } }); + await command.action(logger, { options: commandOptionsSchema.parse({ verbose: true }) }); assert(loggerLogToStderrSpy.calledWithExactly(`Public CDN at ${spoAdminUrl} is enabled`)); }); @@ -231,33 +233,38 @@ describe(commands.CDN_GET, () => { throw 'Invalid request'; }); - await assert.rejects(command.action(logger, { options: { debug: true } } as any), new CommandError('An error has occurred')); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ debug: true }) }), new CommandError('An error has occurred')); }); it('correctly handles random API error', async () => { sinonUtil.restore(request.post); sinon.stub(request, 'post').rejects(new Error('An error has occurred')); - await assert.rejects(command.action(logger, { options: {} } as any), new CommandError('An error has occurred')); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({}) }), new CommandError('An error has occurred')); }); - it('accepts Public SharePoint Online CDN type', async () => { - const actual = await command.validate({ options: { type: 'Public' } }, commandInfo); - assert.strictEqual(actual, true); + it('passes validation with no options', () => { + const actual = commandOptionsSchema.safeParse({}); + assert.strictEqual(actual.success, true); }); - it('accepts Private SharePoint Online CDN type', async () => { - const actual = await command.validate({ options: { type: 'Private' } }, commandInfo); - assert.strictEqual(actual, true); + it('accepts Public SharePoint Online CDN type', () => { + const actual = commandOptionsSchema.safeParse({ type: 'Public' }); + assert.strictEqual(actual.success, true); }); - it('rejects invalid SharePoint Online CDN type', async () => { - const actual = await command.validate({ options: { type: 'foo' } }, commandInfo); - assert.notStrictEqual(actual, true); + it('accepts Private SharePoint Online CDN type', () => { + const actual = commandOptionsSchema.safeParse({ type: 'Private' }); + assert.strictEqual(actual.success, true); }); - it('doesn\'t fail validation if the optional type option not specified', async () => { - const actual = await command.validate({ options: {} }, commandInfo); - assert.strictEqual(actual, true); + it('rejects invalid SharePoint Online CDN type', () => { + const actual = commandOptionsSchema.safeParse({ type: 'foo' }); + assert.strictEqual(actual.success, false); + }); + + it('fails validation with unknown options', () => { + const actual = commandOptionsSchema.safeParse({ unknownOption: 'value' }); + assert.strictEqual(actual.success, false); }); }); diff --git a/src/m365/spo/commands/cdn/cdn-get.ts b/src/m365/spo/commands/cdn/cdn-get.ts index 52017b9c2f0..00c08be92a4 100644 --- a/src/m365/spo/commands/cdn/cdn-get.ts +++ b/src/m365/spo/commands/cdn/cdn-get.ts @@ -1,22 +1,24 @@ +import { z } from 'zod'; +import { globalOptionsZod } from '../../../../Command.js'; import { Logger } from '../../../../cli/Logger.js'; import config from '../../../../config.js'; -import GlobalOptions from '../../../../GlobalOptions.js'; import request, { CliRequestOptions } from '../../../../request.js'; import { ClientSvcResponse, ClientSvcResponseContents, spo } from '../../../../utils/spo.js'; import SpoCommand from '../../../base/SpoCommand.js'; import commands from '../../commands.js'; +export const options = z.strictObject({ + ...globalOptionsZod.shape, + type: z.enum(['Public', 'Private']).optional().alias('t') +}); + +declare type Options = z.infer; + interface CommandArgs { options: Options; } -interface Options extends GlobalOptions { - type?: string; -} - class SpoCdnGetCommand extends SpoCommand { - private readonly validTypes: string[] = ['Public', 'Private']; - public get name(): string { return commands.CDN_GET; } @@ -25,46 +27,8 @@ class SpoCdnGetCommand extends SpoCommand { return 'View current status of the specified Microsoft 365 CDN'; } - constructor() { - super(); - - this.#initTelemetry(); - this.#initOptions(); - this.#initValidators(); - this.#initTypes(); - } - - #initTelemetry(): void { - this.telemetry.push((args: CommandArgs) => { - Object.assign(this.telemetryProperties, { - cdnType: args.options.type || 'Public' - }); - }); - } - - #initOptions(): void { - this.options.unshift( - { - option: '-t, --type [type]', - autocomplete: this.validTypes - } - ); - } - - #initValidators(): void { - this.validators.push( - async (args: CommandArgs) => { - if (args.options.type && !this.validTypes.includes(args.options.type)) { - return `'${args.options.type}' is not a valid CDN type. Allowed values are: ${this.validTypes.join(', ')}.`; - } - - return true; - } - ); - } - - #initTypes(): void { - this.types.string.push('type'); + public get schema(): z.ZodType | undefined { + return options; } public async commandAction(logger: Logger, args: CommandArgs): Promise { diff --git a/src/m365/spo/commands/cdn/cdn-origin-add.spec.ts b/src/m365/spo/commands/cdn/cdn-origin-add.spec.ts index 10bd30bd456..2201d978e0e 100644 --- a/src/m365/spo/commands/cdn/cdn-origin-add.spec.ts +++ b/src/m365/spo/commands/cdn/cdn-origin-add.spec.ts @@ -13,12 +13,13 @@ import { session } from '../../../../utils/session.js'; import { sinonUtil } from '../../../../utils/sinonUtil.js'; import { spo } from '../../../../utils/spo.js'; import commands from '../../commands.js'; -import command from './cdn-origin-add.js'; +import command, { options } from './cdn-origin-add.js'; describe(commands.CDN_ORIGIN_ADD, () => { let log: string[]; let logger: Logger; let commandInfo: CommandInfo; + let commandOptionsSchema: typeof options; let requests: any[]; before(() => { @@ -59,6 +60,7 @@ describe(commands.CDN_ORIGIN_ADD, () => { throw 'Invalid request'; }); commandInfo = cli.getCommandInfo(command); + commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options; }); beforeEach(() => { @@ -93,7 +95,7 @@ describe(commands.CDN_ORIGIN_ADD, () => { }); it('sets CDN origin on the public CDN when Public type specified', async () => { - await command.action(logger, { options: { debug: true, origin: '*/cdn', type: 'Public' } }); + await command.action(logger, { options: commandOptionsSchema.parse({ debug: true, origin: '*/cdn', type: 'Public' }) }); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -107,7 +109,7 @@ describe(commands.CDN_ORIGIN_ADD, () => { }); it('sets CDN origin on the private CDN when Private type specified', async () => { - await assert.rejects(command.action(logger, { options: { debug: true, origin: '*/cdn', type: 'Private' } })); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ debug: true, origin: '*/cdn', type: 'Private' }) })); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -121,7 +123,7 @@ describe(commands.CDN_ORIGIN_ADD, () => { }); it('sets CDN origin on the public CDN when no type specified', async () => { - await command.action(logger, { options: { origin: '*/cdn' } }); + await command.action(logger, { options: commandOptionsSchema.parse({ origin: '*/cdn' }) }); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -165,14 +167,14 @@ describe(commands.CDN_ORIGIN_ADD, () => { throw 'Invalid request'; }); - await assert.rejects(command.action(logger, { options: { debug: true, origin: '*/cdn', type: 'Public' } } as any), + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ debug: true, origin: '*/cdn', type: 'Public' }) }), new CommandError('The library is already registered as a CDN origin.')); }); it('correctly handles random API error', async () => { sinonUtil.restore(request.post); sinon.stub(request, 'post').rejects(new Error('An error has occurred')); - await assert.rejects(command.action(logger, { options: { debug: true, origin: '*/cdn', type: 'Public' } } as any), + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ debug: true, origin: '*/cdn', type: 'Public' }) }), new CommandError('An error has occurred')); }); @@ -208,7 +210,7 @@ describe(commands.CDN_ORIGIN_ADD, () => { throw 'Invalid request'; }); - await command.action(logger, { options: { debug: true, origin: '<*/CDN>' } }); + await command.action(logger, { options: commandOptionsSchema.parse({ debug: true, origin: '<*/CDN>' }) }); let isDone = false; log.forEach(l => { if (l && typeof l === 'string' && l.indexOf('DONE')) { @@ -219,35 +221,28 @@ describe(commands.CDN_ORIGIN_ADD, () => { assert(isDone); }); - it('requires CDN origin name', () => { - const options = command.options; - let requiresCdnOriginName = false; - options.forEach(o => { - if (o.option.indexOf('') > -1) { - requiresCdnOriginName = true; - } - }); - assert(requiresCdnOriginName); + it('accepts Public SharePoint Online CDN type', () => { + const actual = commandOptionsSchema.safeParse({ type: 'Public', origin: '*/CDN' }); + assert.strictEqual(actual.success, true); }); - it('accepts Public SharePoint Online CDN type', async () => { - const actual = await command.validate({ options: { type: 'Public', origin: '*/CDN' } }, commandInfo); - assert.strictEqual(actual, true); + it('accepts Private SharePoint Online CDN type', () => { + const actual = commandOptionsSchema.safeParse({ type: 'Private', origin: '*/CDN' }); + assert.strictEqual(actual.success, true); }); - it('accepts Private SharePoint Online CDN type', async () => { - const actual = await command.validate({ options: { type: 'Private', origin: '*/CDN' } }, commandInfo); - assert.strictEqual(actual, true); + it('rejects invalid SharePoint Online CDN type', () => { + const actual = commandOptionsSchema.safeParse({ type: 'foo', origin: '*/CDN' }); + assert.strictEqual(actual.success, false); }); - it('rejects invalid SharePoint Online CDN type', async () => { - const type = 'foo'; - const actual = await command.validate({ options: { type: type, origin: '*/CDN' } }, commandInfo); - assert.strictEqual(actual, `${type} is not a valid CDN type. Allowed values are Public|Private`); + it('doesn\'t fail validation if the optional type option not specified', () => { + const actual = commandOptionsSchema.safeParse({ origin: '*/CDN' }); + assert.strictEqual(actual.success, true); }); - it('doesn\'t fail validation if the optional type option not specified', async () => { - const actual = await command.validate({ options: { origin: '*/CDN' } }, commandInfo); - assert.strictEqual(actual, true); + it('fails validation with unknown options', () => { + const actual = commandOptionsSchema.safeParse({ origin: '*/CDN', unknownOption: 'value' }); + assert.strictEqual(actual.success, false); }); }); diff --git a/src/m365/spo/commands/cdn/cdn-origin-add.ts b/src/m365/spo/commands/cdn/cdn-origin-add.ts index 5e6e7bcde94..29abd52bf75 100644 --- a/src/m365/spo/commands/cdn/cdn-origin-add.ts +++ b/src/m365/spo/commands/cdn/cdn-origin-add.ts @@ -1,21 +1,25 @@ +import { z } from 'zod'; +import { globalOptionsZod } from '../../../../Command.js'; import { Logger } from '../../../../cli/Logger.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, spo } from '../../../../utils/spo.js'; import SpoCommand from '../../../base/SpoCommand.js'; import commands from '../../commands.js'; +export const options = z.strictObject({ + ...globalOptionsZod.shape, + type: z.enum(['Public', 'Private']).optional().alias('t'), + origin: z.string().alias('r') +}); + +declare type Options = z.infer; + interface CommandArgs { options: Options; } -interface Options extends GlobalOptions { - type: string; - origin: string; -} - class SpoCdnOriginAddCommand extends SpoCommand { public get name(): string { return commands.CDN_ORIGIN_ADD; @@ -25,47 +29,8 @@ class SpoCdnOriginAddCommand extends SpoCommand { return 'Adds CDN origin to the current SharePoint Online tenant'; } - constructor() { - super(); - - this.#initTelemetry(); - this.#initOptions(); - this.#initValidators(); - } - - #initTelemetry(): void { - this.telemetry.push((args: CommandArgs) => { - Object.assign(this.telemetryProperties, { - cdnType: args.options.type || 'Public' - }); - }); - } - - #initOptions(): void { - this.options.unshift( - { - option: '-t, --type [type]', - autocomplete: ['Public', 'Private'] - }, - { - option: '-r, --origin ' - } - ); - } - - #initValidators(): void { - this.validators.push( - async (args: CommandArgs) => { - if (args.options.type) { - if (args.options.type !== 'Public' && - args.options.type !== 'Private') { - return `${args.options.type} is not a valid CDN type. Allowed values are Public|Private`; - } - } - - return true; - } - ); + public get schema(): z.ZodType | undefined { + return options; } public async commandAction(logger: Logger, args: CommandArgs): Promise { diff --git a/src/m365/spo/commands/cdn/cdn-origin-list.spec.ts b/src/m365/spo/commands/cdn/cdn-origin-list.spec.ts index 9960eb155bb..4d7236da813 100644 --- a/src/m365/spo/commands/cdn/cdn-origin-list.spec.ts +++ b/src/m365/spo/commands/cdn/cdn-origin-list.spec.ts @@ -12,13 +12,14 @@ 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 './cdn-origin-list.js'; +import command, { options } from './cdn-origin-list.js'; describe(commands.CDN_ORIGIN_LIST, () => { let log: string[]; let logger: Logger; let loggerLogSpy: sinon.SinonSpy; let commandInfo: CommandInfo; + let commandOptionsSchema: typeof options; before(() => { sinon.stub(auth, 'restoreAuth').resolves(); @@ -29,6 +30,7 @@ describe(commands.CDN_ORIGIN_LIST, () => { auth.connection.spoUrl = 'https://contoso.sharepoint.com'; auth.connection.spoTenantId = 'abc'; commandInfo = cli.getCommandInfo(command); + commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options; }); beforeEach(() => { @@ -91,7 +93,7 @@ describe(commands.CDN_ORIGIN_LIST, () => { throw 'Invalid request'; }); - await command.action(logger, { options: { debug: true, type: 'Public' } }); + await command.action(logger, { options: commandOptionsSchema.parse({ debug: true, type: 'Public' }) }); assert(loggerLogSpy.calledWith(['/master', '*/cdn'])); }); @@ -116,7 +118,7 @@ describe(commands.CDN_ORIGIN_LIST, () => { throw 'Invalid request'; }); - await command.action(logger, { options: { type: 'Private' } }); + await command.action(logger, { options: commandOptionsSchema.parse({ type: 'Private' }) }); assert(loggerLogSpy.calledWith(['/master'])); }); @@ -141,7 +143,7 @@ describe(commands.CDN_ORIGIN_LIST, () => { throw 'Invalid request'; }); - await command.action(logger, { options: { debug: true, type: 'Private' } }); + await command.action(logger, { options: commandOptionsSchema.parse({ debug: true, type: 'Private' }) }); assert(loggerLogSpy.calledWith(['/master'])); }); @@ -166,7 +168,7 @@ describe(commands.CDN_ORIGIN_LIST, () => { throw 'Invalid request'; }); - await command.action(logger, { options: { debug: true } }); + await command.action(logger, { options: commandOptionsSchema.parse({ debug: true }) }); assert(loggerLogSpy.calledWith(['/master', '*/cdn'])); }); @@ -200,7 +202,7 @@ describe(commands.CDN_ORIGIN_LIST, () => { throw 'Invalid request'; }); - await assert.rejects(command.action(logger, { options: { debug: true } } as any), new CommandError('An error has occurred')); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ debug: true }) }), new CommandError('An error has occurred')); }); it('correctly handles random API error', async () => { @@ -209,38 +211,31 @@ describe(commands.CDN_ORIGIN_LIST, () => { return Promise.reject('An error has occurred'); }); - await assert.rejects(command.action(logger, { options: {} } as any), new CommandError('An error has occurred')); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({}) }), new CommandError('An error has occurred')); }); - it('supports specifying CDN type', () => { - const options = command.options; - let containsTypeOption = false; - options.forEach(o => { - if (o.option.indexOf('[type]') > -1) { - containsTypeOption = true; - } - }); - assert(containsTypeOption); + it('accepts Public SharePoint Online CDN type', () => { + const actual = commandOptionsSchema.safeParse({ type: 'Public' }); + assert.strictEqual(actual.success, true); }); - it('accepts Public SharePoint Online CDN type', async () => { - const actual = await command.validate({ options: { type: 'Public' } }, commandInfo); - assert.strictEqual(actual, true); + it('accepts Private SharePoint Online CDN type', () => { + const actual = commandOptionsSchema.safeParse({ type: 'Private' }); + assert.strictEqual(actual.success, true); }); - it('accepts Private SharePoint Online CDN type', async () => { - const actual = await command.validate({ options: { type: 'Private' } }, commandInfo); - assert.strictEqual(actual, true); + it('rejects invalid SharePoint Online CDN type', () => { + const actual = commandOptionsSchema.safeParse({ type: 'foo' }); + assert.strictEqual(actual.success, false); }); - it('rejects invalid SharePoint Online CDN type', async () => { - const type = 'foo'; - const actual = await command.validate({ options: { type: type } }, commandInfo); - assert.strictEqual(actual, `${type} is not a valid CDN type. Allowed values are Public|Private`); + it('doesn\'t fail validation if the optional type option not specified', () => { + const actual = commandOptionsSchema.safeParse({}); + assert.strictEqual(actual.success, true); }); - it('doesn\'t fail validation if the optional type option not specified', async () => { - const actual = await command.validate({ options: {} }, commandInfo); - assert.strictEqual(actual, true); + it('fails validation with unknown options', () => { + const actual = commandOptionsSchema.safeParse({ unknownOption: 'value' }); + assert.strictEqual(actual.success, false); }); }); diff --git a/src/m365/spo/commands/cdn/cdn-origin-list.ts b/src/m365/spo/commands/cdn/cdn-origin-list.ts index 23d1b702900..1297eb0f325 100644 --- a/src/m365/spo/commands/cdn/cdn-origin-list.ts +++ b/src/m365/spo/commands/cdn/cdn-origin-list.ts @@ -1,19 +1,23 @@ +import { z } from 'zod'; +import { globalOptionsZod } from '../../../../Command.js'; import { Logger } from '../../../../cli/Logger.js'; import config from '../../../../config.js'; -import GlobalOptions from '../../../../GlobalOptions.js'; import request, { CliRequestOptions } from '../../../../request.js'; import { ClientSvcResponse, ClientSvcResponseContents, spo } from '../../../../utils/spo.js'; import SpoCommand from '../../../base/SpoCommand.js'; import commands from '../../commands.js'; +export const options = z.strictObject({ + ...globalOptionsZod.shape, + type: z.enum(['Public', 'Private']).optional().alias('t') +}); + +declare type Options = z.infer; + interface CommandArgs { options: Options; } -interface Options extends GlobalOptions { - type: string; -} - class SpoCdnOriginListCommand extends SpoCommand { public get name(): string { return commands.CDN_ORIGIN_LIST; @@ -23,44 +27,8 @@ class SpoCdnOriginListCommand extends SpoCommand { return 'List CDN origins settings for the current SharePoint Online tenant'; } - constructor() { - super(); - - this.#initTelemetry(); - this.#initOptions(); - this.#initValidators(); - } - - #initTelemetry(): void { - this.telemetry.push((args: CommandArgs) => { - Object.assign(this.telemetryProperties, { - cdnType: args.options.type || 'Public' - }); - }); - } - - #initOptions(): void { - this.options.unshift( - { - option: '-t, --type [type]', - autocomplete: ['Public', 'Private'] - } - ); - } - - #initValidators(): void { - this.validators.push( - async (args: CommandArgs) => { - if (args.options.type) { - if (args.options.type !== 'Public' && - args.options.type !== 'Private') { - return `${args.options.type} is not a valid CDN type. Allowed values are Public|Private`; - } - } - - return true; - } - ); + public get schema(): z.ZodType | undefined { + return options; } public async commandAction(logger: Logger, args: CommandArgs): Promise { diff --git a/src/m365/spo/commands/cdn/cdn-origin-remove.spec.ts b/src/m365/spo/commands/cdn/cdn-origin-remove.spec.ts index f0074bd0072..5933f92e2b0 100644 --- a/src/m365/spo/commands/cdn/cdn-origin-remove.spec.ts +++ b/src/m365/spo/commands/cdn/cdn-origin-remove.spec.ts @@ -13,12 +13,13 @@ import { session } from '../../../../utils/session.js'; import { sinonUtil } from '../../../../utils/sinonUtil.js'; import { spo } from '../../../../utils/spo.js'; import commands from '../../commands.js'; -import command from './cdn-origin-remove.js'; +import command, { options } from './cdn-origin-remove.js'; describe(commands.CDN_ORIGIN_REMOVE, () => { let log: string[]; let logger: Logger; let commandInfo: CommandInfo; + let commandOptionsSchema: typeof options; let requests: any[]; let promptIssued: boolean = false; @@ -52,6 +53,7 @@ describe(commands.CDN_ORIGIN_REMOVE, () => { throw 'Invalid request'; }); commandInfo = cli.getCommandInfo(command); + commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options; }); beforeEach(() => { @@ -96,7 +98,7 @@ describe(commands.CDN_ORIGIN_REMOVE, () => { }); it('removes existing CDN origin from the public CDN when Public type specified without prompting with confirmation argument', async () => { - await command.action(logger, { options: { origin: '*/cdn', force: true, type: 'Public' } }); + await command.action(logger, { options: commandOptionsSchema.parse({ origin: '*/cdn', force: true, type: 'Public' }) }); let deleteRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -110,7 +112,7 @@ describe(commands.CDN_ORIGIN_REMOVE, () => { }); it('removes existing CDN origin from the private CDN when Private type specified without prompting with confirmation argument', async () => { - await assert.rejects(command.action(logger, { options: { origin: '*/cdn', force: true, type: 'Private' } })); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ origin: '*/cdn', force: true, type: 'Private' }) })); let deleteRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -124,7 +126,7 @@ describe(commands.CDN_ORIGIN_REMOVE, () => { }); it('removes existing CDN origin from the private CDN when Private type specified without prompting with confirmation argument (debug)', async () => { - await assert.rejects(command.action(logger, { options: { debug: true, origin: '*/cdn', force: true, type: 'Private' } })); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ debug: true, origin: '*/cdn', force: true, type: 'Private' }) })); let deleteRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -138,7 +140,7 @@ describe(commands.CDN_ORIGIN_REMOVE, () => { }); it('removes existing CDN origin from the public CDN when no type specified without prompting with confirmation argument', async () => { - await command.action(logger, { options: { origin: '*/cdn', force: true } }); + await command.action(logger, { options: commandOptionsSchema.parse({ origin: '*/cdn', force: true }) }); let deleteRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -152,7 +154,7 @@ describe(commands.CDN_ORIGIN_REMOVE, () => { }); it('prompts before removing CDN origin when confirmation argument not passed', async () => { - await command.action(logger, { options: { debug: true, origin: '*/cdn' } }); + await command.action(logger, { options: commandOptionsSchema.parse({ debug: true, origin: '*/cdn' }) }); assert(promptIssued); }); @@ -161,7 +163,7 @@ describe(commands.CDN_ORIGIN_REMOVE, () => { sinonUtil.restore(cli.promptForConfirmation); sinon.stub(cli, 'promptForConfirmation').resolves(false); - await command.action(logger, { options: { debug: true, origin: '*/cdn' } }); + await command.action(logger, { options: commandOptionsSchema.parse({ debug: true, origin: '*/cdn' }) }); assert(requests.length === 0); }); @@ -169,7 +171,7 @@ describe(commands.CDN_ORIGIN_REMOVE, () => { sinonUtil.restore(cli.promptForConfirmation); sinon.stub(cli, 'promptForConfirmation').resolves(true); - await command.action(logger, { options: { debug: true, origin: '*/cdn' } }); + await command.action(logger, { options: commandOptionsSchema.parse({ debug: true, origin: '*/cdn' }) }); }); it('correctly handles an error when removing CDN origin', async () => { @@ -202,57 +204,39 @@ describe(commands.CDN_ORIGIN_REMOVE, () => { throw 'Invalid request'; }); - await assert.rejects(command.action(logger, { options: { debug: true, origin: '*/cdn', force: true } } as any), new CommandError('An error has occurred')); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ debug: true, origin: '*/cdn', force: true }) }), new CommandError('An error has occurred')); }); it('correctly handles a random API error', async () => { sinonUtil.restore(request.post); sinon.stub(request, 'post').rejects(new Error('An error has occurred')); - await assert.rejects(command.action(logger, { options: { origin: '*/cdn', force: true } } as any), + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ origin: '*/cdn', force: true }) }), new CommandError('An error has occurred')); }); - it('supports suppressing confirmation prompt', () => { - const options = command.options; - let containsConfirmOption = false; - options.forEach(o => { - if (o.option.indexOf('--force') > -1) { - containsConfirmOption = true; - } - }); - assert(containsConfirmOption); - }); - - it('requires CDN origin name', () => { - const options = command.options; - let requiresCdnOriginName = false; - options.forEach(o => { - if (o.option.indexOf('') > -1) { - requiresCdnOriginName = true; - } - }); - assert(requiresCdnOriginName); + it('accepts Public SharePoint Online CDN type', () => { + const actual = commandOptionsSchema.safeParse({ type: 'Public', origin: '*/CDN' }); + assert.strictEqual(actual.success, true); }); - it('accepts Public SharePoint Online CDN type', async () => { - const actual = await command.validate({ options: { type: 'Public', origin: '*/CDN' } }, commandInfo); - assert.strictEqual(actual, true); + it('accepts Private SharePoint Online CDN type', () => { + const actual = commandOptionsSchema.safeParse({ type: 'Private', origin: '*/CDN' }); + assert.strictEqual(actual.success, true); }); - it('accepts Private SharePoint Online CDN type', async () => { - const actual = await command.validate({ options: { type: 'Private', origin: '*/CDN' } }, commandInfo); - assert.strictEqual(actual, true); + it('rejects invalid SharePoint Online CDN type', () => { + const actual = commandOptionsSchema.safeParse({ type: 'foo', origin: '*/CDN' }); + assert.strictEqual(actual.success, false); }); - it('rejects invalid SharePoint Online CDN type', async () => { - const type = 'foo'; - const actual = await command.validate({ options: { type: type, origin: '*/CDN' } }, commandInfo); - assert.strictEqual(actual, `${type} is not a valid CDN type. Allowed values are Public|Private`); + it('doesn\'t fail validation if the optional type option not specified', () => { + const actual = commandOptionsSchema.safeParse({ origin: '*/CDN' }); + assert.strictEqual(actual.success, true); }); - it('doesn\'t fail validation if the optional type option not specified', async () => { - const actual = await command.validate({ options: { origin: '*/CDN' } }, commandInfo); - assert.strictEqual(actual, true); + it('fails validation with unknown options', () => { + const actual = commandOptionsSchema.safeParse({ origin: '*/CDN', unknownOption: 'value' }); + assert.strictEqual(actual.success, false); }); }); diff --git a/src/m365/spo/commands/cdn/cdn-origin-remove.ts b/src/m365/spo/commands/cdn/cdn-origin-remove.ts index 4fb55b7def3..2cd162944e5 100644 --- a/src/m365/spo/commands/cdn/cdn-origin-remove.ts +++ b/src/m365/spo/commands/cdn/cdn-origin-remove.ts @@ -1,23 +1,27 @@ +import { z } from 'zod'; +import { globalOptionsZod } from '../../../../Command.js'; import { cli } from '../../../../cli/cli.js'; import { Logger } from '../../../../cli/Logger.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, spo } from '../../../../utils/spo.js'; import SpoCommand from '../../../base/SpoCommand.js'; import commands from '../../commands.js'; +export const options = z.strictObject({ + ...globalOptionsZod.shape, + type: z.enum(['Public', 'Private']).optional().alias('t'), + origin: z.string().alias('r'), + force: z.boolean().optional().alias('f') +}); + +declare type Options = z.infer; + interface CommandArgs { options: Options; } -interface Options extends GlobalOptions { - type: string; - origin: string; - force?: boolean; -} - class SpoCdnOriginRemoveCommand extends SpoCommand { public get name(): string { return commands.CDN_ORIGIN_REMOVE; @@ -27,51 +31,8 @@ class SpoCdnOriginRemoveCommand extends SpoCommand { return 'Removes CDN origin for the current SharePoint Online tenant'; } - constructor() { - super(); - - this.#initTelemetry(); - this.#initOptions(); - this.#initValidators(); - } - - #initTelemetry(): void { - this.telemetry.push((args: CommandArgs) => { - Object.assign(this.telemetryProperties, { - cdnType: args.options.type || 'Public', - force: (!(!args.options.force)).toString() - }); - }); - } - - #initOptions(): void { - this.options.unshift( - { - option: '-t, --type [type]', - autocomplete: ['Public', 'Private'] - }, - { - option: '-r, --origin ' - }, - { - option: '-f, --force' - } - ); - } - - #initValidators(): void { - this.validators.push( - async (args: CommandArgs) => { - if (args.options.type) { - if (args.options.type !== 'Public' && - args.options.type !== 'Private') { - return `${args.options.type} is not a valid CDN type. Allowed values are Public|Private`; - } - } - - return true; - } - ); + public get schema(): z.ZodType | undefined { + return options; } public async commandAction(logger: Logger, args: CommandArgs): Promise { diff --git a/src/m365/spo/commands/cdn/cdn-policy-list.spec.ts b/src/m365/spo/commands/cdn/cdn-policy-list.spec.ts index 821e53362ae..656b54c959f 100644 --- a/src/m365/spo/commands/cdn/cdn-policy-list.spec.ts +++ b/src/m365/spo/commands/cdn/cdn-policy-list.spec.ts @@ -12,13 +12,14 @@ 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 './cdn-policy-list.js'; +import command, { options } from './cdn-policy-list.js'; describe(commands.CDN_POLICY_LIST, () => { let log: string[]; let logger: Logger; let loggerLogSpy: sinon.SinonSpy; let commandInfo: CommandInfo; + let commandOptionsSchema: typeof options; before(() => { sinon.stub(auth, 'restoreAuth').resolves(); @@ -29,6 +30,7 @@ describe(commands.CDN_POLICY_LIST, () => { auth.connection.spoUrl = 'https://contoso.sharepoint.com'; auth.connection.spoTenantId = 'abc'; commandInfo = cli.getCommandInfo(command); + commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options; }); beforeEach(() => { @@ -89,7 +91,7 @@ describe(commands.CDN_POLICY_LIST, () => { throw 'Invalid request'; }); - await command.action(logger, { options: { debug: true, cdnType: 'Public' } }); + await command.action(logger, { options: commandOptionsSchema.parse({ debug: true, cdnType: 'Public' }) }); assert(loggerLogSpy.calledWith([{ Policy: 'IncludeFileExtensions', Value: 'CSS,EOT,GIF,ICO,JPEG,JPG,JS,MAP,PNG,SVG,TTF,WOFF,JSON' @@ -119,7 +121,7 @@ describe(commands.CDN_POLICY_LIST, () => { throw 'Invalid request'; }); - await command.action(logger, { options: { debug: true, cdnType: 'Private' } }); + await command.action(logger, { options: commandOptionsSchema.parse({ debug: true, cdnType: 'Private' }) }); assert(loggerLogSpy.calledWith([{ Policy: 'IncludeFileExtensions', Value: 'CSS,EOT,GIF,ICO,JPEG,JPG,JS,MAP,PNG,SVG,TTF,WOFF,JSON' @@ -149,7 +151,7 @@ describe(commands.CDN_POLICY_LIST, () => { throw 'Invalid request'; }); - await command.action(logger, { options: {} }); + await command.action(logger, { options: commandOptionsSchema.parse({}) }); assert(loggerLogSpy.calledWith([{ Policy: 'IncludeFileExtensions', Value: 'CSS,EOT,GIF,ICO,JPEG,JPG,JS,MAP,PNG,SVG,TTF,WOFF,JSON' @@ -188,7 +190,7 @@ describe(commands.CDN_POLICY_LIST, () => { throw 'Invalid request'; }); - await assert.rejects(command.action(logger, { options: {} } as any), + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({}) }), new CommandError('An error has occurred')); }); @@ -196,39 +198,32 @@ describe(commands.CDN_POLICY_LIST, () => { sinonUtil.restore(request.post); sinon.stub(request, 'post').rejects(new Error('An error has occurred')); - await assert.rejects(command.action(logger, { options: {} } as any), + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({}) }), new CommandError('An error has occurred')); }); - it('supports specifying CDN type', () => { - const options = command.options; - let containsTypeOption = false; - options.forEach(o => { - if (o.option.indexOf('[cdnType]') > -1) { - containsTypeOption = true; - } - }); - assert(containsTypeOption); + it('passes validation with no options', () => { + const actual = commandOptionsSchema.safeParse({}); + assert.strictEqual(actual.success, true); }); - it('accepts Public SharePoint Online CDN type', async () => { - const actual = await command.validate({ options: { cdnType: 'Public' } }, commandInfo); - assert.strictEqual(actual, true); + it('accepts Public SharePoint Online CDN type', () => { + const actual = commandOptionsSchema.safeParse({ cdnType: 'Public' }); + assert.strictEqual(actual.success, true); }); - it('accepts Private SharePoint Online CDN type', async () => { - const actual = await command.validate({ options: { cdnType: 'Private' } }, commandInfo); - assert.strictEqual(actual, true); + it('accepts Private SharePoint Online CDN type', () => { + const actual = commandOptionsSchema.safeParse({ cdnType: 'Private' }); + assert.strictEqual(actual.success, true); }); - it('rejects invalid SharePoint Online CDN type', async () => { - const type = 'foo'; - const actual = await command.validate({ options: { cdnType: type } }, commandInfo); - assert.strictEqual(actual, `${type} is not a valid CDN type. Allowed values are Public|Private`); + it('rejects invalid SharePoint Online CDN type', () => { + const actual = commandOptionsSchema.safeParse({ cdnType: 'foo' }); + assert.strictEqual(actual.success, false); }); - it('doesn\'t fail validation if the optional type option not specified', async () => { - const actual = await command.validate({ options: {} }, commandInfo); - assert.strictEqual(actual, true); + it('fails validation with unknown options', () => { + const actual = commandOptionsSchema.safeParse({ unknownOption: 'value' }); + assert.strictEqual(actual.success, false); }); }); diff --git a/src/m365/spo/commands/cdn/cdn-policy-list.ts b/src/m365/spo/commands/cdn/cdn-policy-list.ts index 60da0c572ea..99574a1bc71 100644 --- a/src/m365/spo/commands/cdn/cdn-policy-list.ts +++ b/src/m365/spo/commands/cdn/cdn-policy-list.ts @@ -1,19 +1,23 @@ +import { z } from 'zod'; +import { globalOptionsZod } from '../../../../Command.js'; import { Logger } from '../../../../cli/Logger.js'; import config from '../../../../config.js'; -import GlobalOptions from '../../../../GlobalOptions.js'; import request, { CliRequestOptions } from '../../../../request.js'; import { ClientSvcResponse, ClientSvcResponseContents, spo } from '../../../../utils/spo.js'; import SpoCommand from '../../../base/SpoCommand.js'; import commands from '../../commands.js'; +export const options = z.strictObject({ + ...globalOptionsZod.shape, + cdnType: z.enum(['Public', 'Private']).optional().alias('t') +}); + +declare type Options = z.infer; + interface CommandArgs { options: Options; } -interface Options extends GlobalOptions { - cdnType: string; -} - class SpoCdnPolicyListCommand extends SpoCommand { public get name(): string { return commands.CDN_POLICY_LIST; @@ -23,44 +27,8 @@ class SpoCdnPolicyListCommand extends SpoCommand { return 'Lists CDN policies settings for the current SharePoint Online tenant'; } - constructor() { - super(); - - this.#initTelemetry(); - this.#initOptions(); - this.#initValidators(); - } - - #initTelemetry(): void { - this.telemetry.push((args: CommandArgs) => { - Object.assign(this.telemetryProperties, { - cdnType: args.options.cdnType || 'Public' - }); - }); - } - - #initOptions(): void { - this.options.unshift( - { - option: '-t, --cdnType [cdnType]', - autocomplete: ['Public', 'Private'] - } - ); - } - - #initValidators(): void { - this.validators.push( - async (args: CommandArgs) => { - if (args.options.cdnType) { - if (args.options.cdnType !== 'Public' && - args.options.cdnType !== 'Private') { - return `${args.options.cdnType} is not a valid CDN type. Allowed values are Public|Private`; - } - } - - return true; - } - ); + public get schema(): z.ZodType | undefined { + return options; } public async commandAction(logger: Logger, args: CommandArgs): Promise { diff --git a/src/m365/spo/commands/cdn/cdn-policy-set.spec.ts b/src/m365/spo/commands/cdn/cdn-policy-set.spec.ts index e3f3c501b24..1b259f528c4 100644 --- a/src/m365/spo/commands/cdn/cdn-policy-set.spec.ts +++ b/src/m365/spo/commands/cdn/cdn-policy-set.spec.ts @@ -13,12 +13,13 @@ import { session } from '../../../../utils/session.js'; import { sinonUtil } from '../../../../utils/sinonUtil.js'; import { spo } from '../../../../utils/spo.js'; import commands from '../../commands.js'; -import command from './cdn-policy-set.js'; +import command, { options } from './cdn-policy-set.js'; describe(commands.CDN_POLICY_SET, () => { let log: string[]; let logger: Logger; let commandInfo: CommandInfo; + let commandOptionsSchema: typeof options; let requests: any[]; before(() => { @@ -51,6 +52,7 @@ describe(commands.CDN_POLICY_SET, () => { throw 'Invalid request'; }); commandInfo = cli.getCommandInfo(command); + commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options; }); beforeEach(() => { @@ -85,7 +87,7 @@ describe(commands.CDN_POLICY_SET, () => { }); it('sets IncludeFileExtensions CDN policy on the public CDN when Public type specified', async () => { - await command.action(logger, { options: { debug: true, policy: 'IncludeFileExtensions', value: 'WOFF', cdnType: 'Public' } }); + await command.action(logger, { options: commandOptionsSchema.parse({ debug: true, policy: 'IncludeFileExtensions', value: 'WOFF', cdnType: 'Public' }) }); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -98,7 +100,7 @@ describe(commands.CDN_POLICY_SET, () => { }); it('sets IncludeFileExtensions CDN policy on the private CDN when Private type specified', async () => { - await assert.rejects(command.action(logger, { options: { debug: true, policy: 'IncludeFileExtensions', value: 'WOFF', cdnType: 'Private' } })); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ debug: true, policy: 'IncludeFileExtensions', value: 'WOFF', cdnType: 'Private' }) })); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -112,7 +114,7 @@ describe(commands.CDN_POLICY_SET, () => { }); it('sets IncludeFileExtensions CDN policy on the public CDN when no type specified', async () => { - await command.action(logger, { options: { debug: true, policy: 'IncludeFileExtensions', value: 'WOFF' } }); + await command.action(logger, { options: commandOptionsSchema.parse({ debug: true, policy: 'IncludeFileExtensions', value: 'WOFF' }) }); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -126,7 +128,7 @@ describe(commands.CDN_POLICY_SET, () => { }); it('sets ExcludeRestrictedSiteClassifications CDN policy on the public CDN when no type specified', async () => { - await assert.rejects(command.action(logger, { options: { policy: 'ExcludeRestrictedSiteClassifications', value: 'foo' } })); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ policy: 'ExcludeRestrictedSiteClassifications', value: 'foo' }) })); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -165,7 +167,7 @@ describe(commands.CDN_POLICY_SET, () => { throw 'Invalid request'; }); - await command.action(logger, { options: { policy: 'IncludeFileExtensions', value: ' { throw 'Invalid request'; }); - await assert.rejects(command.action(logger, { options: { policy: 'IncludeFileExtensions', value: ' { - const options = command.options; - let requiresCdnPolicyName = false; - options.forEach(o => { - if (o.option.indexOf('') > -1) { - requiresCdnPolicyName = true; - } - }); - assert(requiresCdnPolicyName); + it('accepts Public SharePoint Online CDN type', () => { + const actual = commandOptionsSchema.safeParse({ cdnType: 'Public', policy: 'IncludeFileExtensions', value: 'CSS,EOT,GIF,ICO,JPEG,JPG,JS,MAP,PNG,SVG,TTF,WOFF,JSON' }); + assert.strictEqual(actual.success, true); }); - it('requires CDN policy value', () => { - const options = command.options; - let requiresCdnPolicyValue = false; - options.forEach(o => { - if (o.option.indexOf('') > -1) { - requiresCdnPolicyValue = true; - } - }); - assert(requiresCdnPolicyValue); + it('accepts Private SharePoint Online CDN type', () => { + const actual = commandOptionsSchema.safeParse({ cdnType: 'Private', policy: 'IncludeFileExtensions', value: 'CSS,EOT,GIF,ICO,JPEG,JPG,JS,MAP,PNG,SVG,TTF,WOFF,JSON' }); + assert.strictEqual(actual.success, true); + }); + + it('rejects invalid SharePoint Online CDN type', () => { + const actual = commandOptionsSchema.safeParse({ cdnType: 'foo', policy: 'IncludeFileExtensions', value: 'CSS,EOT,GIF,ICO,JPEG,JPG,JS,MAP,PNG,SVG,TTF,WOFF,JSON' }); + assert.strictEqual(actual.success, false); + }); + + it('doesn\'t fail validation if the optional type option not specified', () => { + const actual = commandOptionsSchema.safeParse({ policy: 'IncludeFileExtensions', value: 'CSS,EOT,GIF,ICO,JPEG,JPG,JS,MAP,PNG,SVG,TTF,WOFF,JSON' }); + assert.strictEqual(actual.success, true); }); - it('accepts Public SharePoint Online CDN type', async () => { - const actual = await command.validate({ options: { cdnType: 'Public', policy: 'IncludeFileExtensions', value: 'CSS,EOT,GIF,ICO,JPEG,JPG,JS,MAP,PNG,SVG,TTF,WOFF,JSON' } }, commandInfo); - assert.strictEqual(actual, true); + it('fails validation when required origin option not specified', () => { + const actual = commandOptionsSchema.safeParse({}); + assert.strictEqual(actual.success, false); }); - it('accepts Private SharePoint Online CDN type', async () => { - const actual = await command.validate({ options: { cdnType: 'Private', policy: 'IncludeFileExtensions', value: 'CSS,EOT,GIF,ICO,JPEG,JPG,JS,MAP,PNG,SVG,TTF,WOFF,JSON' } }, commandInfo); - assert.strictEqual(actual, true); + it('fails validation when required policy option not specified', () => { + const actual = commandOptionsSchema.safeParse({ value: 'CSS' }); + assert.strictEqual(actual.success, false); }); - it('rejects invalid SharePoint Online CDN type', async () => { - const type = 'foo'; - const actual = await command.validate({ options: { cdnType: type, policy: 'IncludeFileExtensions', value: 'CSS,EOT,GIF,ICO,JPEG,JPG,JS,MAP,PNG,SVG,TTF,WOFF,JSON' } }, commandInfo); - assert.strictEqual(actual, `${type} is not a valid CDN type. Allowed values are Public|Private`); + it('fails validation when required value option not specified', () => { + const actual = commandOptionsSchema.safeParse({ policy: 'IncludeFileExtensions' }); + assert.strictEqual(actual.success, false); }); - it('doesn\'t fail validation if the optional type option not specified', async () => { - const actual = await command.validate({ options: { policy: 'IncludeFileExtensions', value: 'CSS,EOT,GIF,ICO,JPEG,JPG,JS,MAP,PNG,SVG,TTF,WOFF,JSON' } }, commandInfo); - assert.strictEqual(actual, true); + it('accepts IncludeFileExtensions SharePoint Online CDN policy', () => { + const actual = commandOptionsSchema.safeParse({ policy: 'IncludeFileExtensions', value: 'CSS,EOT,GIF,ICO,JPEG,JPG,JS,MAP,PNG,SVG,TTF,WOFF,JSON' }); + assert.strictEqual(actual.success, true); }); - it('accepts IncludeFileExtensions SharePoint Online CDN policy', async () => { - const actual = await command.validate({ options: { policy: 'IncludeFileExtensions', value: 'CSS,EOT,GIF,ICO,JPEG,JPG,JS,MAP,PNG,SVG,TTF,WOFF,JSON' } }, commandInfo); - assert.strictEqual(actual, true); + it('accepts ExcludeRestrictedSiteClassifications SharePoint Online CDN policy', () => { + const actual = commandOptionsSchema.safeParse({ policy: 'ExcludeRestrictedSiteClassifications', value: 'Public' }); + assert.strictEqual(actual.success, true); }); - it('accepts ExcludeRestrictedSiteClassifications SharePoint Online CDN policy', async () => { - const actual = await command.validate({ options: { policy: 'ExcludeRestrictedSiteClassifications', value: 'Public' } }, commandInfo); - assert.strictEqual(actual, true); + it('rejects invalid SharePoint Online CDN policy', () => { + const actual = commandOptionsSchema.safeParse({ policy: 'foo', value: 'bar' }); + assert.strictEqual(actual.success, false); }); - it('rejects invalid SharePoint Online CDN policy', async () => { - const policy = 'foo'; - const actual = await command.validate({ options: { policy: policy, value: 'bar' } }, commandInfo); - assert.strictEqual(actual, `${policy} is not a valid CDN policy. Allowed values are IncludeFileExtensions|ExcludeRestrictedSiteClassifications`); + it('fails validation with unknown options', () => { + const actual = commandOptionsSchema.safeParse({ policy: 'IncludeFileExtensions', value: 'CSS', unknownOption: 'value' }); + assert.strictEqual(actual.success, false); }); }); diff --git a/src/m365/spo/commands/cdn/cdn-policy-set.ts b/src/m365/spo/commands/cdn/cdn-policy-set.ts index fe0fcab4daa..c82a2bc265b 100644 --- a/src/m365/spo/commands/cdn/cdn-policy-set.ts +++ b/src/m365/spo/commands/cdn/cdn-policy-set.ts @@ -1,22 +1,26 @@ +import { z } from 'zod'; +import { globalOptionsZod } from '../../../../Command.js'; import { Logger } from '../../../../cli/Logger.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, spo } from '../../../../utils/spo.js'; import SpoCommand from '../../../base/SpoCommand.js'; import commands from '../../commands.js'; +export const options = z.strictObject({ + ...globalOptionsZod.shape, + cdnType: z.enum(['Public', 'Private']).optional().alias('t'), + policy: z.enum(['IncludeFileExtensions', 'ExcludeRestrictedSiteClassifications']).alias('p'), + value: z.string().alias('v') +}); + +declare type Options = z.infer; + interface CommandArgs { options: Options; } -interface Options extends GlobalOptions { - cdnType: string; - policy: string; - value: string; -} - class SpoCdnPolicySetCommand extends SpoCommand { public get name(): string { return commands.CDN_POLICY_SET; @@ -26,58 +30,8 @@ class SpoCdnPolicySetCommand extends SpoCommand { return 'Sets CDN policy value for the current SharePoint Online tenant'; } - constructor() { - super(); - - this.#initTelemetry(); - this.#initOptions(); - this.#initValidators(); - } - - #initTelemetry(): void { - this.telemetry.push((args: CommandArgs) => { - Object.assign(this.telemetryProperties, { - cdnType: args.options.cdnType || 'Public', - policy: args.options.policy - }); - }); - } - - #initOptions(): void { - this.options.unshift( - { - option: '-t, --cdnType [cdnType]', - autocomplete: ['Public', 'Private'] - }, - { - option: '-p, --policy ', - autocomplete: ['IncludeFileExtensions', 'ExcludeRestrictedSiteClassifications'] - }, - { - option: '-v, --value ' - } - ); - } - - #initValidators(): void { - this.validators.push( - async (args: CommandArgs) => { - if (args.options.cdnType) { - if (args.options.cdnType !== 'Public' && - args.options.cdnType !== 'Private') { - return `${args.options.cdnType} is not a valid CDN type. Allowed values are Public|Private`; - } - } - - if (!args.options.policy || - (args.options.policy !== 'IncludeFileExtensions' && - args.options.policy !== 'ExcludeRestrictedSiteClassifications')) { - return `${args.options.policy} is not a valid CDN policy. Allowed values are IncludeFileExtensions|ExcludeRestrictedSiteClassifications`; - } - - return true; - } - ); + public get schema(): z.ZodType | undefined { + return options; } public async commandAction(logger: Logger, args: CommandArgs): Promise { diff --git a/src/m365/spo/commands/cdn/cdn-set.spec.ts b/src/m365/spo/commands/cdn/cdn-set.spec.ts index 957fd9de500..610ffd14720 100644 --- a/src/m365/spo/commands/cdn/cdn-set.spec.ts +++ b/src/m365/spo/commands/cdn/cdn-set.spec.ts @@ -13,12 +13,13 @@ import { session } from '../../../../utils/session.js'; import { sinonUtil } from '../../../../utils/sinonUtil.js'; import { spo } from '../../../../utils/spo.js'; import commands from '../../commands.js'; -import command from './cdn-set.js'; +import command, { options } from './cdn-set.js'; describe(commands.CDN_SET, () => { let log: string[]; let logger: Logger; let commandInfo: CommandInfo; + let commandOptionsSchema: typeof options; let requests: any[]; before(() => { @@ -50,6 +51,7 @@ describe(commands.CDN_SET, () => { throw 'Invalid request'; }); commandInfo = cli.getCommandInfo(command); + commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options; }); beforeEach(() => { @@ -83,7 +85,7 @@ describe(commands.CDN_SET, () => { }); it('enables public CDN when Public type specified and enabled set to true', async () => { - await command.action(logger, { options: { enabled: true, type: 'Public' } }); + await command.action(logger, { options: commandOptionsSchema.parse({ enabled: true, type: 'Public' }) }); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -97,7 +99,7 @@ describe(commands.CDN_SET, () => { }); it('enables public CDN when Public type specified and enabled set to true (debug)', async () => { - await command.action(logger, { options: { debug: true, enabled: true, type: 'Public' } }); + await command.action(logger, { options: commandOptionsSchema.parse({ debug: true, enabled: true, type: 'Public' }) }); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -111,7 +113,7 @@ describe(commands.CDN_SET, () => { }); it('enables public CDN when Public type specified and enabled set to true without default origins (debug)', async () => { - await assert.rejects(command.action(logger, { options: { debug: true, enabled: true, type: 'Public', noDefaultOrigins: true } })); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ debug: true, enabled: true, type: 'Public', noDefaultOrigins: true }) })); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -125,7 +127,7 @@ describe(commands.CDN_SET, () => { }); it('enables public CDN when no type specified and enabled set to true', async () => { - await assert.doesNotReject(command.action(logger, { options: { enabled: true } })); + await assert.doesNotReject(command.action(logger, { options: commandOptionsSchema.parse({ enabled: true }) })); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -139,7 +141,7 @@ describe(commands.CDN_SET, () => { }); it('enables private CDN when Private type specified and enabled set to true (debug)', async () => { - await assert.rejects(command.action(logger, { options: { debug: true, enabled: true, type: 'Private' } })); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ debug: true, enabled: true, type: 'Private' }) })); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -153,7 +155,7 @@ describe(commands.CDN_SET, () => { }); it('enables private CDN when Private type specified and enabled set to true without default origins (debug)', async () => { - await assert.rejects(command.action(logger, { options: { debug: true, enabled: true, type: 'Private', noDefaultOrigins: true } })); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ debug: true, enabled: true, type: 'Private', noDefaultOrigins: true }) })); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -167,7 +169,7 @@ describe(commands.CDN_SET, () => { }); it('enables both CDN\'s when Both type specified and enabled set to true (debug)', async () => { - await assert.rejects(command.action(logger, { options: { debug: true, enabled: true, type: 'Both' } })); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ debug: true, enabled: true, type: 'Both' }) })); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -181,7 +183,7 @@ describe(commands.CDN_SET, () => { }); it('enables both CDN\'s when Both type specified and enabled set to true, with default origins', async () => { - await assert.rejects(command.action(logger, { options: { enabled: true, type: 'Both', noDefaultOrigins: false } })); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ enabled: true, type: 'Both', noDefaultOrigins: false }) })); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -195,7 +197,7 @@ describe(commands.CDN_SET, () => { }); it('enables both CDN\'s when Both type specified and enabled set to true, with origins (debug)', async () => { - await assert.rejects(command.action(logger, { options: { debug: true, enabled: true, type: 'Both', noDefaultOrigins: false } })); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ debug: true, enabled: true, type: 'Both', noDefaultOrigins: false }) })); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -209,7 +211,7 @@ describe(commands.CDN_SET, () => { }); it('enables both CDN\'s when Both type specified and enabled set to true, without Default Origins', async () => { - await assert.rejects(command.action(logger, { options: { enabled: true, type: 'Both', noDefaultOrigins: true } })); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ enabled: true, type: 'Both', noDefaultOrigins: true }) })); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -223,7 +225,7 @@ describe(commands.CDN_SET, () => { }); it('enables both CDN\'s when Both type specified and enabled set to true, without default origins (debug)', async () => { - await assert.rejects(command.action(logger, { options: { debug: true, enabled: true, type: 'Both', noDefaultOrigins: true } })); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ debug: true, enabled: true, type: 'Both', noDefaultOrigins: true }) })); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -237,7 +239,7 @@ describe(commands.CDN_SET, () => { }); it('disable both CDN\'s when Both type specified and enabled set to false, with default (debug)', async () => { - await assert.rejects(command.action(logger, { options: { debug: true, enabled: false, type: 'Both', noDefaultOrigins: false } })); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ debug: true, enabled: false, type: 'Both', noDefaultOrigins: false }) })); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -251,7 +253,7 @@ describe(commands.CDN_SET, () => { }); it('disable both CDN\'s when Both type specified and enabled set to false, without default origins (debug)', async () => { - await assert.rejects(command.action(logger, { options: { debug: true, enabled: false, type: 'Both', noDefaultOrigins: true } })); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ debug: true, enabled: false, type: 'Both', noDefaultOrigins: true }) })); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -265,7 +267,7 @@ describe(commands.CDN_SET, () => { }); it('disables both CDN\'s when Both type specified and enabled set to false', async () => { - await assert.rejects(command.action(logger, { options: { enabled: false, type: 'Both' } })); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ enabled: false, type: 'Both' }) })); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -279,7 +281,7 @@ describe(commands.CDN_SET, () => { }); it('disables both CDN\'s when Both type specified and enabled set to false (debug)', async () => { - await assert.rejects(command.action(logger, { options: { debug: true, enabled: false, type: 'Both' } })); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ debug: true, enabled: false, type: 'Both' }) })); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -293,7 +295,7 @@ describe(commands.CDN_SET, () => { }); it('disables public CDN when Public type specified and enabled set to false (debug)', async () => { - await assert.rejects(command.action(logger, { options: { debug: true, enabled: false, type: 'Public' } })); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ debug: true, enabled: false, type: 'Public' }) })); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -307,7 +309,7 @@ describe(commands.CDN_SET, () => { }); it('disables public CDN when Public type specified and enabled set to false and noDefaultOrigins is passed (debug)', async () => { - await assert.rejects(command.action(logger, { options: { debug: true, enabled: false, type: 'Public', noDefaultOrigins: true } })); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ debug: true, enabled: false, type: 'Public', noDefaultOrigins: true }) })); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -321,7 +323,7 @@ describe(commands.CDN_SET, () => { }); it('disables public CDN when Public type specified and enabled set to false and noDefaultOrigins is passed', async () => { - await assert.rejects(command.action(logger, { options: { enabled: false, type: 'Public', noDefaultOrigins: true } })); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ enabled: false, type: 'Public', noDefaultOrigins: true }) })); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -335,7 +337,7 @@ describe(commands.CDN_SET, () => { }); it('disables public CDN when Public type specified and enabled set to false', async () => { - await assert.rejects(command.action(logger, { options: { enabled: false, type: 'Public' } })); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ enabled: false, type: 'Public' }) })); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -349,7 +351,7 @@ describe(commands.CDN_SET, () => { }); it('disables Private CDN when Private type specified and enabled set to false (debug)', async () => { - await assert.rejects(command.action(logger, { options: { debug: true, enabled: false, type: 'Private' } })); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ debug: true, enabled: false, type: 'Private' }) })); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -363,7 +365,7 @@ describe(commands.CDN_SET, () => { }); it('disables Private CDN when Private type specified and enabled set to false and noDefaultOrigins is passed (debug)', async () => { - await assert.rejects(command.action(logger, { options: { debug: true, enabled: false, type: 'Private', noDefaultOrigins: true } })); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ debug: true, enabled: false, type: 'Private', noDefaultOrigins: true }) })); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -377,7 +379,7 @@ describe(commands.CDN_SET, () => { }); it('disables Private CDN when Private type specified and enabled set to false and noDefaultOrigins is passed', async () => { - await assert.rejects(command.action(logger, { options: { enabled: false, type: 'Private', noDefaultOrigins: true } })); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ enabled: false, type: 'Private', noDefaultOrigins: true }) })); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -391,7 +393,7 @@ describe(commands.CDN_SET, () => { }); it('disables Private CDN when Private type specified and enabled set to false', async () => { - await assert.rejects(command.action(logger, { options: { enabled: false, type: 'Private' } })); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ enabled: false, type: 'Private' }) })); let setRequestIssued = false; requests.forEach(r => { if (r.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && @@ -434,53 +436,52 @@ describe(commands.CDN_SET, () => { throw 'Invalid request'; }); - await assert.rejects(command.action(logger, { options: { enabled: true } } as any), + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ enabled: true }) }), new CommandError('An error has occurred')); }); - it('requires tenant enabled state', () => { - const options = command.options; - let requiresOption = false; - options.forEach(o => { - if (o.option.indexOf('') > -1) { - requiresOption = true; - } - }); - assert(requiresOption); + it('accepts Public SharePoint Online CDN type', () => { + const actual = commandOptionsSchema.safeParse({ type: 'Public', enabled: true }); + assert.strictEqual(actual.success, true); + }); + + it('accepts Private SharePoint Online CDN type', () => { + const actual = commandOptionsSchema.safeParse({ type: 'Private', enabled: true }); + assert.strictEqual(actual.success, true); }); - it('accepts Public SharePoint Online CDN type', async () => { - const actual = await command.validate({ options: { type: 'Public', enabled: true } }, commandInfo); - assert.strictEqual(actual, true); + it('accepts Both SharePoint Online CDN type', () => { + const actual = commandOptionsSchema.safeParse({ type: 'Both', enabled: true }); + assert.strictEqual(actual.success, true); }); - it('accepts Private SharePoint Online CDN type', async () => { - const actual = await command.validate({ options: { type: 'Private', enabled: true } }, commandInfo); - assert.strictEqual(actual, true); + it('rejects invalid SharePoint Online CDN type', () => { + const actual = commandOptionsSchema.safeParse({ type: 'foo', enabled: true }); + assert.strictEqual(actual.success, false); }); - it('accepts Both SharePoint Online CDN type', async () => { - const actual = await command.validate({ options: { type: 'Both', enabled: true } }, commandInfo); - assert.strictEqual(actual, true); + it('doesn\'t fail validation if the optional type option not specified', () => { + const actual = commandOptionsSchema.safeParse({ enabled: true }); + assert.strictEqual(actual.success, true); }); - it('rejects invalid SharePoint Online CDN type', async () => { - const actual = await command.validate({ options: { type: 'foo', enabled: true } }, commandInfo); - assert.notStrictEqual(actual, true); + it('fails validation when required enabled option not specified', () => { + const actual = commandOptionsSchema.safeParse({}); + assert.strictEqual(actual.success, false); }); - it('doesn\'t fail validation if the optional type option not specified', async () => { - const actual = await command.validate({ options: { enabled: true } }, commandInfo); - assert.strictEqual(actual, true); + it('accepts true SharePoint Online CDN enabled state', () => { + const actual = commandOptionsSchema.safeParse({ enabled: true }); + assert.strictEqual(actual.success, true); }); - it('accepts true SharePoint Online CDN enabled state', async () => { - const actual = await command.validate({ options: { enabled: true } }, commandInfo); - assert.strictEqual(actual, true); + it('accepts false SharePoint Online CDN enabled state', () => { + const actual = commandOptionsSchema.safeParse({ enabled: false }); + assert.strictEqual(actual.success, true); }); - it('accepts false SharePoint Online CDN enabled state', async () => { - const actual = await command.validate({ options: { enabled: false } }, commandInfo); - assert.strictEqual(actual, true); + it('fails validation with unknown options', () => { + const actual = commandOptionsSchema.safeParse({ enabled: true, unknownOption: 'value' }); + assert.strictEqual(actual.success, false); }); }); \ No newline at end of file diff --git a/src/m365/spo/commands/cdn/cdn-set.ts b/src/m365/spo/commands/cdn/cdn-set.ts index 6d2713fdba4..93e68188c5b 100644 --- a/src/m365/spo/commands/cdn/cdn-set.ts +++ b/src/m365/spo/commands/cdn/cdn-set.ts @@ -1,24 +1,26 @@ +import { z } from 'zod'; +import { globalOptionsZod } from '../../../../Command.js'; import { Logger } from '../../../../cli/Logger.js'; import config from '../../../../config.js'; -import GlobalOptions from '../../../../GlobalOptions.js'; import request, { CliRequestOptions } from '../../../../request.js'; import { ClientSvcResponse, ClientSvcResponseContents, spo } from '../../../../utils/spo.js'; import SpoCommand from '../../../base/SpoCommand.js'; import commands from '../../commands.js'; +export const options = z.strictObject({ + ...globalOptionsZod.shape, + type: z.enum(['Public', 'Private', 'Both']).optional().alias('t'), + enabled: z.boolean().alias('e'), + noDefaultOrigins: z.boolean().optional() +}); + +declare type Options = z.infer; + interface CommandArgs { options: Options; } -interface Options extends GlobalOptions { - type: string; - enabled: boolean; - noDefaultOrigins?: boolean; -} - class SpoCdnSetCommand extends SpoCommand { - private readonly validTypes: string[] = ['Public', 'Private', 'Both']; - public get name(): string { return commands.CDN_SET; } @@ -27,59 +29,8 @@ class SpoCdnSetCommand extends SpoCommand { return 'Enable or disable the specified Microsoft 365 CDN'; } - constructor() { - super(); - - this.#initTelemetry(); - this.#initOptions(); - this.#initTypes(); - this.#initValidators(); - } - - #initTelemetry(): void { - this.telemetry.push((args: CommandArgs) => { - Object.assign(this.telemetryProperties, { - cdnType: args.options.type || 'Public', - enabled: args.options.enabled, - noDefaultOrigins: !!args.options.noDefaultOrigins - }); - }); - } - - #initOptions(): void { - this.options.unshift( - { - option: '-e, --enabled ', - autocomplete: ['true', 'false'] - }, - { - option: '-t, --type [type]', - autocomplete: this.validTypes - }, - { - option: '--noDefaultOrigins' - } - ); - } - - #initTypes(): void { - this.types.boolean.push('enabled', 'noDefaultOrigins'); - this.types.string.push('type'); - } - - #initValidators(): void { - this.validators.push( - async (args: CommandArgs) => { - if (args.options.type) { - if (args.options.type !== 'Public' && args.options.type !== 'Both' && - args.options.type !== 'Private') { - return `${args.options.type} is not a valid CDN type. Allowed values are ${this.validTypes.join(', ')}.`; - } - } - - return true; - } - ); + public get schema(): z.ZodType | undefined { + return options; } public async commandAction(logger: Logger, args: CommandArgs): Promise {