From acb5190d93638d045be62ed44f10d6dc2a7d7bc3 Mon Sep 17 00:00:00 2001 From: Milan Holemans <11723921+milanholemans@users.noreply.github.com> Date: Mon, 23 Dec 2024 01:39:56 +0100 Subject: [PATCH] Adds command 'spo list defaultvalue clear'. Closes #6505 --- .../cmd/spo/list/list-defaultvalue-clear.mdx | 62 ++++ docs/src/config/sidebars.ts | 5 + src/m365/spo/commands.ts | 1 + .../list/list-defaultvalue-clear.spec.ts | 322 ++++++++++++++++++ .../commands/list/list-defaultvalue-clear.ts | 212 ++++++++++++ 5 files changed, 602 insertions(+) create mode 100644 docs/docs/cmd/spo/list/list-defaultvalue-clear.mdx create mode 100644 src/m365/spo/commands/list/list-defaultvalue-clear.spec.ts create mode 100644 src/m365/spo/commands/list/list-defaultvalue-clear.ts diff --git a/docs/docs/cmd/spo/list/list-defaultvalue-clear.mdx b/docs/docs/cmd/spo/list/list-defaultvalue-clear.mdx new file mode 100644 index 00000000000..19b6424fc2c --- /dev/null +++ b/docs/docs/cmd/spo/list/list-defaultvalue-clear.mdx @@ -0,0 +1,62 @@ +import Global from '/docs/cmd/_global.mdx'; + +# spo list defaultvalue clear + +Clears default column values for a specific document library + +## Usage + +```sh +m365 spo list defaultvalue clear [options] +``` + +## Options + +```md definition-list +`-u, --webUrl ` +: URL of the site where the list is located. + +`-i, --listId [listId]` +: ID of the list. Specify either `listTitle`, `listId`, or `listUrl`. + +`-t, --listTitle [listTitle]` +: Title of the list. Specify either `listTitle`, `listId`, or `listUrl`. + +`--listUrl [listUrl]` +: Server- or site-relative URL of the list. Specify either `listTitle`, `listId`, or `listUrl`. + +`--fieldName [fieldName]` +: Internal name of the field to clear over the entire list. Specify either `fieldName` or `folderUrl`, but not both. + +`--folderUrl [folderUrl]` +: Clear all fields of a specific folder. Specify either `fieldName` or `folderUrl`, but not both. + +`-f, --force` +: Don't prompt for confirmation. +``` + + + +## Examples + +Remove all default column values from a library + +```sh +m365 spo list defaultvalue clear --webUrl https://contoso.sharepoint.com/sites/Marketing --listTitle Logos +``` + +Clear a field from all folders of a library + +```sh +m365 spo list defaultvalue clear --webUrl https://contoso.sharepoint.com/sites/Marketing --listTitle Logos --field Company +``` + +Clear all fields from a specific folder in a library + +```sh +m365 spo list defaultvalue clear --webUrl https://contoso.sharepoint.com/sites/Marketing --listTitle Logos --folderUrl "/sites/Marketing/Logos/Contoso" +``` + +## Response + +The command won't return a response on success. diff --git a/docs/src/config/sidebars.ts b/docs/src/config/sidebars.ts index 68fa5ec4249..cd9d4db30be 100644 --- a/docs/src/config/sidebars.ts +++ b/docs/src/config/sidebars.ts @@ -2871,6 +2871,11 @@ const sidebars: SidebarsConfig = { label: 'list contenttype default set', id: 'cmd/spo/list/list-contenttype-default-set' }, + { + type: 'doc', + label: 'list defaultvalue clear', + id: 'cmd/spo/list/list-defaultvalue-clear' + }, { type: 'doc', label: 'list retentionlabel ensure', diff --git a/src/m365/spo/commands.ts b/src/m365/spo/commands.ts index f17b2edca20..0ad4f6e2f82 100644 --- a/src/m365/spo/commands.ts +++ b/src/m365/spo/commands.ts @@ -138,6 +138,7 @@ export default { LIST_CONTENTTYPE_LIST: `${prefix} list contenttype list`, LIST_CONTENTTYPE_REMOVE: `${prefix} list contenttype remove`, LIST_CONTENTTYPE_DEFAULT_SET: `${prefix} list contenttype default set`, + LIST_DEFAULTVALUE_CLEAR: `${prefix} list defaultvalue clear`, LIST_GET: `${prefix} list get`, LIST_LIST: `${prefix} list list`, LIST_REMOVE: `${prefix} list remove`, diff --git a/src/m365/spo/commands/list/list-defaultvalue-clear.spec.ts b/src/m365/spo/commands/list/list-defaultvalue-clear.spec.ts new file mode 100644 index 00000000000..f76429915a1 --- /dev/null +++ b/src/m365/spo/commands/list/list-defaultvalue-clear.spec.ts @@ -0,0 +1,322 @@ +import assert from 'assert'; +import sinon from 'sinon'; +import auth from '../../../../Auth.js'; +import { Logger } from '../../../../cli/Logger.js'; +import request from '../../../../request.js'; +import { telemetry } from '../../../../telemetry.js'; +import { pid } from '../../../../utils/pid.js'; +import { CommandInfo } from '../../../../cli/CommandInfo.js'; +import { session } from '../../../../utils/session.js'; +import { sinonUtil } from '../../../../utils/sinonUtil.js'; +import commands from '../../commands.js'; +import command from './list-defaultvalue-clear.js'; +import { z } from 'zod'; +import { cli } from '../../../../cli/cli.js'; +import { formatting } from '../../../../utils/formatting.js'; +import { CommandError } from '../../../../Command.js'; + +describe(commands.LIST_DEFAULTVALUE_CLEAR, () => { + let log: string[]; + let logger: Logger; + let loggerLogSpy: sinon.SinonSpy; + let commandInfo: CommandInfo; + let commandOptionsSchema: z.ZodTypeAny; + let confirmationPromptStub: sinon.SinonStub; + let putStub: sinon.SinonStub; + + const siteUrl = 'https://contoso.sharepoint.com/sites/marketing'; + const listId = 'c090e594-3b8e-4f4d-9b9f-3e8e1f0b9f1a'; + const listTitle = 'Documents'; + const listUrl = '/sites/marketing/Shared Documents'; + const siteRelListUrl = '/Shared Documents'; + const folderUrl = '/sites/marketing/Shared Documents/Logos'; + const fieldName = 'DocumentType'; + + const defaultColumnXml = `19;#Belgium|442affc2-7fab-4f33-9590-330403a579c2;#18;#Croatia|59f1ab85-235b-4cf8-b669-4373cc9393c6General20;#Canada|e3d25461-68ef-4070-8523-5ba439f6d4d5LogoTemplate`; + + before(() => { + sinon.stub(auth, 'restoreAuth').resolves(); + sinon.stub(telemetry, 'trackEvent').returns(); + sinon.stub(pid, 'getProcessName').returns(''); + sinon.stub(session, 'getId').returns(''); + + commandInfo = cli.getCommandInfo(command); + commandOptionsSchema = commandInfo.command.getSchemaToParse()!; + auth.connection.active = true; + }); + + beforeEach(() => { + log = []; + logger = { + log: async (msg: string) => { + log.push(msg); + }, + logRaw: async (msg: string) => { + log.push(msg); + }, + logToStderr: async (msg: string) => { + log.push(msg); + } + }; + loggerLogSpy = sinon.spy(logger, 'log'); + confirmationPromptStub = sinon.stub(cli, 'promptForConfirmation').resolves(false); + + putStub = sinon.stub(request, 'put').callsFake(async (opts) => { + if (opts.url === `${siteUrl}/_api/Web/GetFileByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter(listUrl + '/Forms/client_LocationBasedDefaults.html')}')/$value`) { + return; + } + + throw `Invalid request: ${opts.url}`; + }); + }); + + afterEach(() => { + sinonUtil.restore([ + request.get, + request.put, + cli.promptForConfirmation + ]); + }); + + after(() => { + sinon.restore(); + auth.connection.active = false; + }); + + it('has correct name', () => { + assert.strictEqual(command.name, commands.LIST_DEFAULTVALUE_CLEAR); + }); + + it('has a description', () => { + assert.notStrictEqual(command.description, null); + }); + + it('fails validation if webUrl is not a valid URL', async () => { + const actual = commandOptionsSchema.safeParse({ webUrl: 'invalid', listId: listId }); + assert.strictEqual(actual.success, false); + }); + + it('fails validation if listId is not a valid GUID', async () => { + const actual = commandOptionsSchema.safeParse({ webUrl: siteUrl, listId: 'invalid' }); + assert.strictEqual(actual.success, false); + }); + + it('fails validation if listId, listTitle and listUrl are not specified', async () => { + const actual = commandOptionsSchema.safeParse({ webUrl: siteUrl, fieldName: fieldName }); + assert.strictEqual(actual.success, false); + }); + + it('fails validation if listId and listTitle are specified', async () => { + const actual = commandOptionsSchema.safeParse({ webUrl: siteUrl, listId: listId, listTitle: listTitle, fieldName: fieldName }); + assert.strictEqual(actual.success, false); + }); + + it('fails validation if listId and listUrl are specified', async () => { + const actual = commandOptionsSchema.safeParse({ webUrl: siteUrl, listId: listId, listUrl: listUrl, fieldName: fieldName }); + assert.strictEqual(actual.success, false); + }); + + it('fails validation if folderUrl and fieldName are specified', async () => { + const actual = commandOptionsSchema.safeParse({ webUrl: siteUrl, listId: listId, folderUrl: folderUrl, fieldName: fieldName }); + assert.strictEqual(actual.success, false); + }); + + it('succeeds validation if folderUrl and fieldName are not specified', async () => { + const actual = commandOptionsSchema.safeParse({ webUrl: siteUrl, listId: listId }); + assert.strictEqual(actual.success, true); + }); + + it('succeeds validation if folderUrl is specified', async () => { + const actual = commandOptionsSchema.safeParse({ webUrl: siteUrl, listId: listId, folderUrl: folderUrl }); + assert.strictEqual(actual.success, true); + }); + + it('succeeds validation if fieldName is specified', async () => { + const actual = commandOptionsSchema.safeParse({ webUrl: siteUrl, listId: listId, fieldName: fieldName }); + assert.strictEqual(actual.success, true); + }); + + it('prompts before removing default values', async () => { + await command.action(logger, { options: { webUrl: siteUrl, listId: listId } }); + assert(confirmationPromptStub.calledOnce); + }); + + it('prompts before removing default values with fieldName', async () => { + await command.action(logger, { options: { webUrl: siteUrl, listId: listId, fieldName: fieldName } }); + assert(confirmationPromptStub.calledOnce); + }); + + it('prompts before removing default values with folderUrl', async () => { + await command.action(logger, { options: { webUrl: siteUrl, listId: listId, folderUrl: folderUrl } }); + assert(confirmationPromptStub.calledOnce); + }); + + it('aborts removing default values when prompt not confirmed', async () => { + await command.action(logger, { options: { webUrl: siteUrl, listId: listId } }); + assert(putStub.notCalled); + }); + + it('clears default values for a list without giving output', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `${siteUrl}/_api/Web/GetList('${formatting.encodeQueryParameter(listUrl)}')?$expand=RootFolder&$select=RootFolder/ServerRelativeUrl,BaseTemplate`) { + return { + BaseTemplate: 101, + RootFolder: { + ServerRelativeUrl: listUrl + } + }; + } + if (opts.url === `${siteUrl}/_api/Web/GetFileByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter(listUrl + '/Forms/client_LocationBasedDefaults.html')}')/$value`) { + return defaultColumnXml; + } + + throw `Invalid request: ${opts.url}`; + }); + + await command.action(logger, { options: { webUrl: siteUrl, listUrl: listUrl, force: true } }); + assert(loggerLogSpy.notCalled); + }); + + it('clears default values for an entire list', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `${siteUrl}/_api/Web/GetList('${formatting.encodeQueryParameter(listUrl)}')?$expand=RootFolder&$select=RootFolder/ServerRelativeUrl,BaseTemplate`) { + return { + BaseTemplate: 101, + RootFolder: { + ServerRelativeUrl: listUrl + } + }; + } + if (opts.url === `${siteUrl}/_api/Web/GetFileByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter(listUrl + '/Forms/client_LocationBasedDefaults.html')}')/$value`) { + return defaultColumnXml; + } + + throw `Invalid request: ${opts.url}`; + }); + + await command.action(logger, { options: { webUrl: siteUrl, listUrl: siteRelListUrl, verbose: true, force: true } }); + assert.deepStrictEqual(putStub.firstCall.args[0].data, ''); + }); + + it('clears default values for a specific folder', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `${siteUrl}/_api/Web/Lists('${listId}')?$expand=RootFolder&$select=RootFolder/ServerRelativeUrl,BaseTemplate`) { + return { + BaseTemplate: 101, + RootFolder: { + ServerRelativeUrl: listUrl + } + }; + } + if (opts.url === `${siteUrl}/_api/Web/GetFileByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter(listUrl + '/Forms/client_LocationBasedDefaults.html')}')/$value`) { + return defaultColumnXml; + } + + throw `Invalid request: ${opts.url}`; + }); + + await command.action(logger, { options: { webUrl: siteUrl, listId: listId, folderUrl: folderUrl, verbose: true, force: true } }); + const expectedXml = `19;#Belgium|442affc2-7fab-4f33-9590-330403a579c2;#18;#Croatia|59f1ab85-235b-4cf8-b669-4373cc9393c6GeneralTemplate`; + + assert.deepStrictEqual(putStub.firstCall.args[0].data, expectedXml); + }); + + it('clears default values for a specific field', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `${siteUrl}/_api/Web/Lists/GetByTitle('${listTitle}')?$expand=RootFolder&$select=RootFolder/ServerRelativeUrl,BaseTemplate`) { + return { + BaseTemplate: 101, + RootFolder: { + ServerRelativeUrl: listUrl + } + }; + } + if (opts.url === `${siteUrl}/_api/Web/GetFileByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter(listUrl + '/Forms/client_LocationBasedDefaults.html')}')/$value`) { + return defaultColumnXml; + } + + throw `Invalid request: ${opts.url}`; + }); + + await command.action(logger, { options: { webUrl: siteUrl, listTitle: listTitle, fieldName: fieldName, verbose: true, force: true } }); + const expectedXml = `19;#Belgium|442affc2-7fab-4f33-9590-330403a579c2;#18;#Croatia|59f1ab85-235b-4cf8-b669-4373cc9393c620;#Canada|e3d25461-68ef-4070-8523-5ba439f6d4d5`; + + assert.deepStrictEqual(putStub.firstCall.args[0].data, expectedXml); + }); + + it('clears default values for a list without default values', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `${siteUrl}/_api/Web/Lists/GetByTitle('${listTitle}')?$expand=RootFolder&$select=RootFolder/ServerRelativeUrl,BaseTemplate`) { + return { + BaseTemplate: 101, + RootFolder: { + ServerRelativeUrl: listUrl + } + }; + } + if (opts.url === `${siteUrl}/_api/Web/GetFileByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter(listUrl + '/Forms/client_LocationBasedDefaults.html')}')/$value`) { + throw { + status: 404, + error: { 'odata.error': { message: { value: 'File not found' } } } + }; + } + + throw `Invalid request: ${opts.url}`; + }); + + await command.action(logger, { options: { webUrl: siteUrl, listTitle: listTitle, fieldName: fieldName, verbose: true, force: true } }); + assert(putStub.notCalled); + }); + + it('throws error when running the command on a non-document library', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `${siteUrl}/_api/Web/Lists('${listId}')?$expand=RootFolder&$select=RootFolder/ServerRelativeUrl,BaseTemplate`) { + return { + BaseTemplate: 100, + RootFolder: { + ServerRelativeUrl: listUrl + } + }; + } + + throw `Invalid request: ${opts.url}`; + }); + + await assert.rejects(command.action(logger, { options: { webUrl: siteUrl, listId: listId, force: true } }), + new CommandError('The specified list is not a document library.')); + }); + + it('throws error when list does not exist', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `${siteUrl}/_api/Web/GetList('${formatting.encodeQueryParameter(listUrl)}')?$expand=RootFolder&$select=RootFolder/ServerRelativeUrl,BaseTemplate`) { + throw { status: 404, error: { 'odata.error': { message: { value: 'The file does not exist.' } } } }; + } + + throw `Invalid request: ${opts.url}`; + }); + + await assert.rejects(command.action(logger, { options: { webUrl: siteUrl, listUrl: listUrl, force: true } }), + new CommandError(`List '${listUrl}' was not found.`)); + }); + + it('throws error when retrieving default values fails', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `${siteUrl}/_api/Web/GetList('${formatting.encodeQueryParameter(listUrl)}')?$expand=RootFolder&$select=RootFolder/ServerRelativeUrl,BaseTemplate`) { + return { + BaseTemplate: 101, + RootFolder: { + ServerRelativeUrl: listUrl + } + }; + } + + throw { + status: 500, + error: { 'odata.error': { message: { value: 'An error has occurred.' } } } + }; + }); + + await assert.rejects(command.action(logger, { options: { webUrl: siteUrl, listUrl: listUrl, force: true } }), + new CommandError(`An error has occurred.`)); + }); +}); \ No newline at end of file diff --git a/src/m365/spo/commands/list/list-defaultvalue-clear.ts b/src/m365/spo/commands/list/list-defaultvalue-clear.ts new file mode 100644 index 00000000000..490b17bdd3e --- /dev/null +++ b/src/m365/spo/commands/list/list-defaultvalue-clear.ts @@ -0,0 +1,212 @@ +import SpoCommand from '../../../base/SpoCommand.js'; +import { globalOptionsZod } from '../../../../Command.js'; +import { z } from 'zod'; +import { zod } from '../../../../utils/zod.js'; +import { Logger } from '../../../../cli/Logger.js'; +import commands from '../../commands.js'; +import { DOMParser } from '@xmldom/xmldom'; +import { validation } from '../../../../utils/validation.js'; +import { urlUtil } from '../../../../utils/urlUtil.js'; +import request, { CliRequestOptions } from '../../../../request.js'; +import { formatting } from '../../../../utils/formatting.js'; +import { cli } from '../../../../cli/cli.js'; + +const options = globalOptionsZod + .extend({ + webUrl: zod.alias('u', z.string() + .refine(url => validation.isValidSharePointUrl(url) === true, url => ({ + message: `'${url}' is not a valid SharePoint Online site URL.` + })) + ), + listId: zod.alias('i', z.string().optional() + .refine(id => id === undefined || validation.isValidGuid(id), id => ({ + message: `'${id}' is not a valid GUID.` + })) + ), + listTitle: zod.alias('t', z.string().optional()), + listUrl: z.string().optional(), + fieldName: z.string().optional(), + folderUrl: z.string().optional(), + force: zod.alias('f', z.boolean().optional()) + }) + .strict(); +declare type Options = z.infer; + +interface CommandArgs { + options: Options; +} + +class SpoListDefaultValueClearCommand extends SpoCommand { + public get name(): string { + return commands.LIST_DEFAULTVALUE_CLEAR; + } + + public get description(): string { + return 'Clears default column values for a specific document library'; + } + + public get schema(): z.ZodTypeAny { + return options; + } + + public getRefinedSchema(schema: z.ZodTypeAny): z.ZodEffects | undefined { + return schema + .refine(options => [options.listId, options.listTitle, options.listUrl].filter(o => o !== undefined).length === 1, { + message: 'Use one of the following options: listId, listTitle, listUrl.' + }) + .refine(options => (options.fieldName !== undefined) !== (options.folderUrl !== undefined) || (options.fieldName === undefined && options.folderUrl === undefined), { + message: `Specify 'fieldName' or 'folderUrl', but not both.` + }); + } + + public async commandAction(logger: Logger, args: CommandArgs): Promise { + if (!args.options.force) { + const result = await cli.promptForConfirmation({ message: `Are you sure you want to clear all default values${args.options.fieldName ? ` for field '${args.options.fieldName}'` : args.options.folderUrl ? ` for folder ${args.options.folderUrl}` : ''}?` }); + if (!result) { + return; + } + } + + try { + if (this.verbose) { + await logger.logToStderr(`Clearing all default column values${args.options.fieldName ? ` for field ${args.options.fieldName}` : args.options.folderUrl ? `for folder '${args.options.folderUrl}'` : ''}...`); + await logger.logToStderr(`Getting server-relative URL of the list...`); + } + + const listServerRelUrl = await this.getServerRelativeListUrl(args.options); + if (this.verbose) { + await logger.logToStderr(`List server-relative URL: ${listServerRelUrl}`); + await logger.logToStderr(`Getting default column values...`); + } + + try { + const defaultValuesXml = await this.getDefaultColumnValuesXml(args.options.webUrl, listServerRelUrl); + const trimmedXml = this.removeFieldsFromXml(defaultValuesXml, args.options); + await this.uploadDefaultColumnValuesXml(args.options.webUrl, listServerRelUrl, trimmedXml); + } + catch (err: any) { + if (err.status !== 404) { + throw err; + } + // For lists that have never had default column values set, the client_LocationBasedDefaults.html file does not exist. + // In this case, we don't need to do anything. + return; + } + } + catch (err: any) { + this.handleRejectedODataJsonPromise(err); + } + } + + private async getServerRelativeListUrl(options: Options): Promise { + const requestOptions: CliRequestOptions = { + url: `${options.webUrl}/_api/Web`, + headers: { + accept: 'application/json;odata=nometadata' + }, + responseType: 'json' + }; + + if (options.listUrl) { + const serverRelativeUrl = urlUtil.getServerRelativePath(options.webUrl, options.listUrl); + requestOptions.url += `/GetList('${formatting.encodeQueryParameter(serverRelativeUrl)}')`; + } + else if (options.listId) { + requestOptions.url += `/Lists('${options.listId}')`; + } + else if (options.listTitle) { + requestOptions.url += `/Lists/GetByTitle('${formatting.encodeQueryParameter(options.listTitle)}')`; + } + + requestOptions.url += '?$expand=RootFolder&$select=RootFolder/ServerRelativeUrl,BaseTemplate'; + + try { + const response = await request.get<{ BaseTemplate: number; RootFolder: { ServerRelativeUrl: string } }>(requestOptions); + if (response.BaseTemplate !== 101) { + throw `The specified list is not a document library.`; + } + + return response.RootFolder.ServerRelativeUrl; + } + catch (error: any) { + if (error.status === 404) { + throw `List '${options.listId || options.listTitle || options.listUrl}' was not found.`; + } + + throw error; + } + } + + private async getDefaultColumnValuesXml(webUrl: string, listServerRelUrl: string): Promise { + const requestOptions: CliRequestOptions = { + url: `${webUrl}/_api/Web/GetFileByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter(listServerRelUrl + '/Forms/client_LocationBasedDefaults.html')}')/$value`, + headers: { + accept: 'application/json;odata=nometadata' + }, + responseType: 'json' + }; + const defaultValuesXml = await request.get(requestOptions); + return defaultValuesXml; + } + + private removeFieldsFromXml(xml: string, options: Options): string { + if (!options.fieldName && !options.folderUrl) { + return ''; + } + + let folderUrlToRemove = null; + if (options.folderUrl) { + folderUrlToRemove = urlUtil.removeTrailingSlashes(urlUtil.getServerRelativePath(options.webUrl, options.folderUrl)); + } + + const parser = new DOMParser(); + const doc = parser.parseFromString(xml, 'application/xml'); + + const folderLinks = doc.getElementsByTagName('a'); + for (let i = 0; i < folderLinks.length; i++) { + const folderNode = folderLinks[i]; + const folderUrl = folderNode.getAttribute('href')!; + + if (folderUrlToRemove && folderUrlToRemove.toLowerCase() === decodeURIComponent(folderUrl).toLowerCase()) { + folderNode.parentNode!.removeChild(folderNode); + break; + } + else if (options.fieldName) { + const defaultValues = folderNode.getElementsByTagName('DefaultValue'); + for (let j = 0; j < defaultValues.length; j++) { + const defaultValueNode = defaultValues[j]; + const fieldName = defaultValueNode.getAttribute('FieldName')!; + + if (fieldName.toLowerCase() === options.fieldName!.toLowerCase()) { + // Remove the entire folder node if it becomes empty + if (folderNode.childNodes.length === 1) { + folderNode.parentNode!.removeChild(defaultValueNode.parentNode!); + } + else { + folderNode.removeChild(defaultValueNode); + } + break; + } + } + } + } + + return doc.toString(); + } + + private async uploadDefaultColumnValuesXml(webUrl: string, listServerRelUrl: string, xml: string): Promise { + const requestOptions: CliRequestOptions = { + url: `${webUrl}/_api/Web/GetFileByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter(listServerRelUrl + '/Forms/client_LocationBasedDefaults.html')}')/$value`, + headers: { + accept: 'application/json;odata=nometadata', + 'If-Match': '*' + }, + responseType: 'json', + data: xml + }; + + await request.put(requestOptions); + } +} + +export default new SpoListDefaultValueClearCommand(); \ No newline at end of file