From 3350400b2bcd250d06ce8d900f2adde05193e26f Mon Sep 17 00:00:00 2001 From: Willie Ruemmele Date: Tue, 9 May 2023 13:45:29 -0600 Subject: [PATCH 01/17] fix: deletes singular CL when from CLs when specified --- package.json | 7 ++-- src/commands/project/delete/source.ts | 31 +++++++++++++++-- test/nuts/delete/source.nut.ts | 48 +++++++++++++++++++++++++++ yarn.lock | 39 +++++++++++++--------- 4 files changed, 103 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index e9d9fb90..cbd2a590 100644 --- a/package.json +++ b/package.json @@ -13,13 +13,14 @@ "@salesforce/source-deploy-retrieve": "^8.0.2", "@salesforce/source-tracking": "^3.1.0", "chalk": "^4.1.2", + "fast-xml-parser": "^4.2.2", "fs-extra": "^10.0.1", "shelljs": "^0.8.5", "tslib": "^2" }, "devDependencies": { "@oclif/plugin-command-snapshot": "^3.3.10", - "@salesforce/cli-plugins-testkit": "^3.3.2", + "@salesforce/cli-plugins-testkit": "^3.3.6", "@salesforce/dev-config": "^3.1.0", "@salesforce/dev-scripts": "^4.3.1", "@salesforce/plugin-command-reference": "^2.4.1", @@ -29,7 +30,7 @@ "@salesforce/plugin-templates": "^55.4.10", "@salesforce/plugin-user": "^2.3.6", "@salesforce/prettier-config": "^0.0.2", - "@salesforce/source-testkit": "^2.0.43", + "@salesforce/source-testkit": "^2.0.59", "@salesforce/ts-sinon": "1.4.6", "@salesforce/ts-types": "^1.5.20", "@swc/core": "1.3.39", @@ -269,4 +270,4 @@ "output": [] } } -} \ No newline at end of file +} diff --git a/src/commands/project/delete/source.ts b/src/commands/project/delete/source.ts index 777e411f..4901a3d0 100644 --- a/src/commands/project/delete/source.ts +++ b/src/commands/project/delete/source.ts @@ -32,6 +32,7 @@ import { SfCommand, } from '@salesforce/sf-plugins-core'; import * as chalk from 'chalk'; +import { XMLParser, XMLBuilder } from 'fast-xml-parser'; import { DeleteSourceJson, TestLevel, isSourceComponent } from '../../../utils/types'; import { getPackageDirs, getSourceApiVersion } from '../../../utils/project'; import { resolveApi } from '../../../utils/deploy'; @@ -346,15 +347,39 @@ export class Source extends SfCommand { this.components?.filter(isSourceComponent).map((component: SourceComponent) => { // mixed delete/deploy operations have already been deleted and stashed if (!this.mixedDeployDelete.delete.length) { - if (component.content) { + if (component.type.id === 'customlabel' && component.xml) { + const parser = new XMLParser({ + ignoreDeclaration: false, + ignoreAttributes: false, + attributeNamePrefix: '@_', + }); + const customLabels = parser.parse(fs.readFileSync(component.xml, 'utf8')) as { + CustomLabels: { labels: Array<{ fullName: string }> }; + }; + customLabels.CustomLabels.labels = customLabels.CustomLabels.labels.filter( + (label) => label.fullName !== component.fullName + ); + + // delete customLabels.CustomLabels.labels; + const builder = new XMLBuilder({ + attributeNamePrefix: '@_', + ignoreAttributes: false, + format: true, + indentBy: ' ', + }); + const xml = builder.build(customLabels) as string; + fs.writeFileSync(component.xml, xml); + // parse xml to json + // delete json key + // write json back to xml + } else if (component.content) { const stats = fs.statSync(component.content); if (stats.isDirectory()) { promises.push(fsPromises.rm(component.content, { recursive: true })); } else { promises.push(fsPromises.unlink(component.content)); } - } - if (component.xml) { + } else if (component.xml) { promises.push(fsPromises.unlink(component.xml)); } } diff --git a/test/nuts/delete/source.nut.ts b/test/nuts/delete/source.nut.ts index ba571b6c..b5be1ad5 100644 --- a/test/nuts/delete/source.nut.ts +++ b/test/nuts/delete/source.nut.ts @@ -48,6 +48,54 @@ describe('project delete source NUTs', () => { return { apexName, output, pathToClass }; }; + describe('CustomLabels', () => { + before(async () => { + testkit = await SourceTestkit.create({ + nut: __filename, + repository: 'https://github.com/mdapi-issues/sfdx-delete-customlabel.git', + }); + execCmd('force:source:deploy --sourcepath force-app', { ensureExitCode: 0 }); + }); + + after(async () => { + await testkit?.clean(); + }); + it('will not delete the entire .xml file', () => { + const clPath = path.join( + testkit.projectDir, + 'force-app', + 'main', + 'default', + 'labels', + 'CustomLabels.labels-meta.xml' + ); + const result = execCmd('project:delete:source --no-prompt --metadata CustomLabel:DeleteMe', { + ensureExitCode: 0, + }).jsonOutput?.result; + expect(result?.deletedSource).to.have.length(2); + expect(fs.existsSync(clPath)).to.be.true; + expect(fs.readFileSync(clPath, 'utf8')).to.not.contain('DeleteMe'); + expect(fs.readFileSync(clPath, 'utf8')).to.contain('KeepMe1'); + expect(fs.readFileSync(clPath, 'utf8')).to.contain('KeepMe2'); + }); + + it('will delete the entire .xml file', () => { + const clPath = path.join( + testkit.projectDir, + 'force-app', + 'main', + 'default', + 'labels', + 'CustomLabels.labels-meta.xml' + ); + const result = execCmd('project:delete:source --no-prompt --metadata CustomLabels', { + ensureExitCode: 0, + }).jsonOutput?.result; + expect(result?.deletedSource).to.have.length(4); + expect(fs.existsSync(clPath)).to.be.false; + }); + }); + before(async () => { testkit = await SourceTestkit.create({ nut: __filename, diff --git a/yarn.lock b/yarn.lock index e8587829..7911daaa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1043,15 +1043,15 @@ mv "~2" safe-json-stringify "~1" -"@salesforce/cli-plugins-testkit@^3.3.2": - version "3.3.2" - resolved "https://registry.yarnpkg.com/@salesforce/cli-plugins-testkit/-/cli-plugins-testkit-3.3.2.tgz#5a5bd6f1c505962b97682463c0a204fb6b67b5bf" - integrity sha512-lXxm9tYNRD34GdcHtYVcFIccG9k5v8Ai/O1ytHE4zOoDowmeyB5l47O7Qo8blmeYbggRJUQIFfao2EPrJlTKCQ== +"@salesforce/cli-plugins-testkit@^3.3.6": + version "3.3.6" + resolved "https://registry.yarnpkg.com/@salesforce/cli-plugins-testkit/-/cli-plugins-testkit-3.3.6.tgz#89c17e87a58e1d0bd6006b65568a6883a17e7e5b" + integrity sha512-40o363ISrdX1FNyt0YLBouQMMMySXS0YLhozTqSjtZDHZn/9gfcVH1LOz9XsOUd1C4SwnVnVjHH2bdVfHzxRWA== dependencies: - "@salesforce/core" "^3.34.6" + "@salesforce/core" "^3.34.8" "@salesforce/kit" "^1.9.2" "@salesforce/ts-types" "^1.7.3" - "@types/shelljs" "^0.8.11" + "@types/shelljs" "^0.8.12" archiver "^5.2.0" debug "^4.3.1" shelljs "^0.8.4" @@ -1082,7 +1082,7 @@ semver "^7.3.5" ts-retry-promise "^0.6.0" -"@salesforce/core@^3.24.0", "@salesforce/core@^3.32.12", "@salesforce/core@^3.33.5", "@salesforce/core@^3.33.6", "@salesforce/core@^3.34.4", "@salesforce/core@^3.34.6", "@salesforce/core@^3.36.0": +"@salesforce/core@^3.24.0", "@salesforce/core@^3.32.12", "@salesforce/core@^3.33.5", "@salesforce/core@^3.33.6", "@salesforce/core@^3.34.4", "@salesforce/core@^3.34.6", "@salesforce/core@^3.34.8", "@salesforce/core@^3.36.0": version "3.36.0" resolved "https://registry.yarnpkg.com/@salesforce/core/-/core-3.36.0.tgz#cbff147d888eee0b921e368c1fdc1a1a3c2eacab" integrity sha512-LOeSJgozf5B1S8C98/K3afewRsathfEm+HrXxaFXJjITFfIhxqcIDB5xC1cw0VikfRUlq7dQocmVsvezcK0Ghw== @@ -1283,7 +1283,7 @@ chalk "^4" inquirer "^8.2.5" -"@salesforce/source-deploy-retrieve@^7.15.0": +"@salesforce/source-deploy-retrieve@^7.15.1": version "7.15.1" resolved "https://registry.yarnpkg.com/@salesforce/source-deploy-retrieve/-/source-deploy-retrieve-7.15.1.tgz#059d088f5fb5726323d9822599d730474ce6dc70" integrity sha512-37jjQWb24FBEwP0aAW1UnSkVk+TMr8KEtCN5tNa43LXK21m06suMDx92Y+lS8xJ9esgj/GEEuXgG8OCf+L5qcg== @@ -1321,15 +1321,15 @@ proxy-from-env "^1.1.0" unzipper "0.10.11" -"@salesforce/source-testkit@^2.0.43": - version "2.0.51" - resolved "https://registry.yarnpkg.com/@salesforce/source-testkit/-/source-testkit-2.0.51.tgz#69fd318295d77200b5957db6d0d3eb59b07c11bc" - integrity sha512-MBkUbT9aIAwxYJBw0aIeiZ2JYasQETWLdc8ZQdCbiEgrFsjGlAqa/eQMrRgLrlcokKQFa655cvHxQELbONVgbw== +"@salesforce/source-testkit@^2.0.59": + version "2.0.59" + resolved "https://registry.yarnpkg.com/@salesforce/source-testkit/-/source-testkit-2.0.59.tgz#0e11da6defa203458ebc09459451dd1ae57acc5a" + integrity sha512-1bez0fivdYhoQZphRX/rvNxBvbZ+c8kVRD+uYf8TApCYDra/eQFHJgY2g16bFL1LOrydMEBmlwBN78Ydcyhlag== dependencies: - "@salesforce/cli-plugins-testkit" "^3.3.2" - "@salesforce/core" "^3.34.6" + "@salesforce/cli-plugins-testkit" "^3.3.6" + "@salesforce/core" "^3.36.0" "@salesforce/kit" "^1.9.2" - "@salesforce/source-deploy-retrieve" "^7.15.0" + "@salesforce/source-deploy-retrieve" "^7.15.1" "@salesforce/ts-types" "^1.7.3" archiver "^5.2.0" chai-each "^0.0.1" @@ -1744,7 +1744,7 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw== -"@types/shelljs@^0.8.11", "@types/shelljs@^0.8.12": +"@types/shelljs@^0.8.12": version "0.8.12" resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.8.12.tgz#79dc9632af7d5ca1b5afb65a6bfc1422d79b5fa0" integrity sha512-ZA8U81/gldY+rR5zl/7HSHrG2KDfEb3lzG6uCUDhW1DTQE9yC/VBQ45fXnXq8f3CgInfhZmjtdu/WOUlrXRQUg== @@ -4020,6 +4020,13 @@ fast-xml-parser@^4.1.4: dependencies: strnum "^1.0.5" +fast-xml-parser@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.2.2.tgz#cb7310d1e9cf42d22c687b0fae41f3c926629368" + integrity sha512-DLzIPtQqmvmdq3VUKR7T6omPK/VCRNqgFlGtbESfyhcH2R4I8EzK1/K6E8PkRCK2EabWrUHK32NjYRbEFnnz0Q== + dependencies: + strnum "^1.0.5" + fastest-levenshtein@^1.0.7: version "1.0.16" resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" From 5eaea8aec33587cbda22fdbf99f45886f6b82d7d Mon Sep 17 00:00:00 2001 From: Willie Ruemmele Date: Tue, 9 May 2023 13:53:44 -0600 Subject: [PATCH 02/17] test: fix logic caught by UT --- src/commands/project/delete/source.ts | 59 ++++++++++++++------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/src/commands/project/delete/source.ts b/src/commands/project/delete/source.ts index 4901a3d0..83d830fb 100644 --- a/src/commands/project/delete/source.ts +++ b/src/commands/project/delete/source.ts @@ -347,40 +347,43 @@ export class Source extends SfCommand { this.components?.filter(isSourceComponent).map((component: SourceComponent) => { // mixed delete/deploy operations have already been deleted and stashed if (!this.mixedDeployDelete.delete.length) { - if (component.type.id === 'customlabel' && component.xml) { - const parser = new XMLParser({ - ignoreDeclaration: false, - ignoreAttributes: false, - attributeNamePrefix: '@_', - }); - const customLabels = parser.parse(fs.readFileSync(component.xml, 'utf8')) as { - CustomLabels: { labels: Array<{ fullName: string }> }; - }; - customLabels.CustomLabels.labels = customLabels.CustomLabels.labels.filter( - (label) => label.fullName !== component.fullName - ); - - // delete customLabels.CustomLabels.labels; - const builder = new XMLBuilder({ - attributeNamePrefix: '@_', - ignoreAttributes: false, - format: true, - indentBy: ' ', - }); - const xml = builder.build(customLabels) as string; - fs.writeFileSync(component.xml, xml); - // parse xml to json - // delete json key - // write json back to xml - } else if (component.content) { + if (component.content) { const stats = fs.statSync(component.content); if (stats.isDirectory()) { promises.push(fsPromises.rm(component.content, { recursive: true })); } else { promises.push(fsPromises.unlink(component.content)); } - } else if (component.xml) { - promises.push(fsPromises.unlink(component.xml)); + } + if (component.xml) { + if (component.type.id === 'customlabel') { + // for custom labels, we need to remove the individual label from the xml file + // so we'll parse the xml + const parser = new XMLParser({ + ignoreDeclaration: false, + ignoreAttributes: false, + attributeNamePrefix: '@_', + }); + const customLabels = parser.parse(fs.readFileSync(component.xml, 'utf8')) as { + CustomLabels: { labels: Array<{ fullName: string }> }; + }; + // delete the label from the json based on it's fullName + customLabels.CustomLabels.labels = customLabels.CustomLabels.labels.filter( + (label) => label.fullName !== component.fullName + ); + + const builder = new XMLBuilder({ + attributeNamePrefix: '@_', + ignoreAttributes: false, + format: true, + indentBy: ' ', + }); + // and then write that json back to xml and back to the fs + const xml = builder.build(customLabels) as string; + fs.writeFileSync(component.xml, xml); + } else { + promises.push(fsPromises.unlink(component.xml)); + } } } }); From 2b366dd6c0f5718e9b9d9ea6a632f53e289b8942 Mon Sep 17 00:00:00 2001 From: Willie Ruemmele Date: Tue, 9 May 2023 14:12:27 -0600 Subject: [PATCH 03/17] test: move NUT to it's own describe --- test/nuts/delete/source.nut.ts | 98 +++++++++++++++++----------------- 1 file changed, 50 insertions(+), 48 deletions(-) diff --git a/test/nuts/delete/source.nut.ts b/test/nuts/delete/source.nut.ts index b5be1ad5..734f1dc7 100644 --- a/test/nuts/delete/source.nut.ts +++ b/test/nuts/delete/source.nut.ts @@ -28,6 +28,56 @@ const isNameObsolete = async (username: string, memberType: string, memberName: return res.IsNameObsolete; }; +describe('CustomLabels', () => { + let testkit: SourceTestkit; + + before(async () => { + testkit = await SourceTestkit.create({ + nut: __filename, + repository: 'https://github.com/mdapi-issues/sfdx-delete-customlabel.git', + }); + execCmd('force:source:deploy --sourcepath force-app', { ensureExitCode: 0 }); + }); + + after(async () => { + await testkit?.clean(); + }); + it('will not delete the entire .xml file', () => { + const clPath = path.join( + testkit.projectDir, + 'force-app', + 'main', + 'default', + 'labels', + 'CustomLabels.labels-meta.xml' + ); + const result = execCmd('project:delete:source --no-prompt --metadata CustomLabel:DeleteMe', { + ensureExitCode: 0, + }).jsonOutput?.result; + expect(result?.deletedSource).to.have.length(2); + expect(fs.existsSync(clPath)).to.be.true; + expect(fs.readFileSync(clPath, 'utf8')).to.not.contain('DeleteMe'); + expect(fs.readFileSync(clPath, 'utf8')).to.contain('KeepMe1'); + expect(fs.readFileSync(clPath, 'utf8')).to.contain('KeepMe2'); + }); + + it('will delete the entire .xml file', () => { + const clPath = path.join( + testkit.projectDir, + 'force-app', + 'main', + 'default', + 'labels', + 'CustomLabels.labels-meta.xml' + ); + const result = execCmd('project:delete:source --no-prompt --metadata CustomLabels', { + ensureExitCode: 0, + }).jsonOutput?.result; + expect(result?.deletedSource).to.have.length(4); + expect(fs.existsSync(clPath)).to.be.false; + }); +}); + describe('project delete source NUTs', () => { let testkit: SourceTestkit; @@ -48,54 +98,6 @@ describe('project delete source NUTs', () => { return { apexName, output, pathToClass }; }; - describe('CustomLabels', () => { - before(async () => { - testkit = await SourceTestkit.create({ - nut: __filename, - repository: 'https://github.com/mdapi-issues/sfdx-delete-customlabel.git', - }); - execCmd('force:source:deploy --sourcepath force-app', { ensureExitCode: 0 }); - }); - - after(async () => { - await testkit?.clean(); - }); - it('will not delete the entire .xml file', () => { - const clPath = path.join( - testkit.projectDir, - 'force-app', - 'main', - 'default', - 'labels', - 'CustomLabels.labels-meta.xml' - ); - const result = execCmd('project:delete:source --no-prompt --metadata CustomLabel:DeleteMe', { - ensureExitCode: 0, - }).jsonOutput?.result; - expect(result?.deletedSource).to.have.length(2); - expect(fs.existsSync(clPath)).to.be.true; - expect(fs.readFileSync(clPath, 'utf8')).to.not.contain('DeleteMe'); - expect(fs.readFileSync(clPath, 'utf8')).to.contain('KeepMe1'); - expect(fs.readFileSync(clPath, 'utf8')).to.contain('KeepMe2'); - }); - - it('will delete the entire .xml file', () => { - const clPath = path.join( - testkit.projectDir, - 'force-app', - 'main', - 'default', - 'labels', - 'CustomLabels.labels-meta.xml' - ); - const result = execCmd('project:delete:source --no-prompt --metadata CustomLabels', { - ensureExitCode: 0, - }).jsonOutput?.result; - expect(result?.deletedSource).to.have.length(4); - expect(fs.existsSync(clPath)).to.be.false; - }); - }); - before(async () => { testkit = await SourceTestkit.create({ nut: __filename, From 6cc016f77889db44979d9660fe0d30ff6882fa49 Mon Sep 17 00:00:00 2001 From: Willie Ruemmele Date: Tue, 9 May 2023 14:30:24 -0600 Subject: [PATCH 04/17] test: use TestSession --- test/nuts/delete/source.nut.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/test/nuts/delete/source.nut.ts b/test/nuts/delete/source.nut.ts index 734f1dc7..8bc3e896 100644 --- a/test/nuts/delete/source.nut.ts +++ b/test/nuts/delete/source.nut.ts @@ -8,7 +8,7 @@ import * as fs from 'fs'; import * as path from 'path'; import { expect } from 'chai'; -import { execCmd } from '@salesforce/cli-plugins-testkit'; +import { execCmd, TestSession } from '@salesforce/cli-plugins-testkit'; import { SourceTestkit } from '@salesforce/source-testkit'; import { exec } from 'shelljs'; import { FileResponse } from '@salesforce/source-deploy-retrieve'; @@ -29,12 +29,14 @@ const isNameObsolete = async (username: string, memberType: string, memberName: }; describe('CustomLabels', () => { - let testkit: SourceTestkit; + let testkit: TestSession; before(async () => { - testkit = await SourceTestkit.create({ - nut: __filename, - repository: 'https://github.com/mdapi-issues/sfdx-delete-customlabel.git', + testkit = await TestSession.create({ + project: { + gitClone: 'https://github.com/mdapi-issues/sfdx-delete-customlabel.git', + }, + devhubAuthStrategy: 'NONE', }); execCmd('force:source:deploy --sourcepath force-app', { ensureExitCode: 0 }); }); @@ -44,7 +46,7 @@ describe('CustomLabels', () => { }); it('will not delete the entire .xml file', () => { const clPath = path.join( - testkit.projectDir, + testkit.project.dir, 'force-app', 'main', 'default', @@ -63,7 +65,7 @@ describe('CustomLabels', () => { it('will delete the entire .xml file', () => { const clPath = path.join( - testkit.projectDir, + testkit.project.dir, 'force-app', 'main', 'default', From d37e0903e26bb498ac1295c775c8d51ee546b3a9 Mon Sep 17 00:00:00 2001 From: Willie Ruemmele Date: Tue, 9 May 2023 14:40:29 -0600 Subject: [PATCH 05/17] test: create and set default SO --- test/nuts/delete/source.nut.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/nuts/delete/source.nut.ts b/test/nuts/delete/source.nut.ts index 8bc3e896..e352f182 100644 --- a/test/nuts/delete/source.nut.ts +++ b/test/nuts/delete/source.nut.ts @@ -36,6 +36,7 @@ describe('CustomLabels', () => { project: { gitClone: 'https://github.com/mdapi-issues/sfdx-delete-customlabel.git', }, + scratchOrgs: [{ setDefault: true, config: path.join('config', 'project-scratch-def.json') }], devhubAuthStrategy: 'NONE', }); execCmd('force:source:deploy --sourcepath force-app', { ensureExitCode: 0 }); From df11b53287339a2ade83de983e5d8e912613331c Mon Sep 17 00:00:00 2001 From: Willie Ruemmele Date: Tue, 9 May 2023 14:45:47 -0600 Subject: [PATCH 06/17] test: devhub auth - auto --- test/nuts/delete/source.nut.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/nuts/delete/source.nut.ts b/test/nuts/delete/source.nut.ts index e352f182..328c3734 100644 --- a/test/nuts/delete/source.nut.ts +++ b/test/nuts/delete/source.nut.ts @@ -37,7 +37,7 @@ describe('CustomLabels', () => { gitClone: 'https://github.com/mdapi-issues/sfdx-delete-customlabel.git', }, scratchOrgs: [{ setDefault: true, config: path.join('config', 'project-scratch-def.json') }], - devhubAuthStrategy: 'NONE', + devhubAuthStrategy: 'AUTO', }); execCmd('force:source:deploy --sourcepath force-app', { ensureExitCode: 0 }); }); From 6a4a2951be69101ab750f72adcbf511d2b828902 Mon Sep 17 00:00:00 2001 From: Willie Ruemmele Date: Tue, 9 May 2023 15:22:30 -0600 Subject: [PATCH 07/17] test: if you're asserting on json, use the --json flag --- test/nuts/delete/source.nut.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/test/nuts/delete/source.nut.ts b/test/nuts/delete/source.nut.ts index 328c3734..ea53fc06 100644 --- a/test/nuts/delete/source.nut.ts +++ b/test/nuts/delete/source.nut.ts @@ -54,14 +54,17 @@ describe('CustomLabels', () => { 'labels', 'CustomLabels.labels-meta.xml' ); - const result = execCmd('project:delete:source --no-prompt --metadata CustomLabel:DeleteMe', { - ensureExitCode: 0, - }).jsonOutput?.result; - expect(result?.deletedSource).to.have.length(2); + const result = execCmd( + 'project:delete:source --json --no-prompt --metadata CustomLabel:DeleteMe', + { + ensureExitCode: 0, + } + ).jsonOutput?.result; expect(fs.existsSync(clPath)).to.be.true; expect(fs.readFileSync(clPath, 'utf8')).to.not.contain('DeleteMe'); expect(fs.readFileSync(clPath, 'utf8')).to.contain('KeepMe1'); expect(fs.readFileSync(clPath, 'utf8')).to.contain('KeepMe2'); + expect(result?.deletedSource).to.have.length(1); }); it('will delete the entire .xml file', () => { @@ -73,10 +76,10 @@ describe('CustomLabels', () => { 'labels', 'CustomLabels.labels-meta.xml' ); - const result = execCmd('project:delete:source --no-prompt --metadata CustomLabels', { + const result = execCmd('project:delete:source --json --no-prompt --metadata CustomLabels', { ensureExitCode: 0, }).jsonOutput?.result; - expect(result?.deletedSource).to.have.length(4); + expect(result?.deletedSource).to.have.length(3); expect(fs.existsSync(clPath)).to.be.false; }); }); From a9de587df82b82859223fe782cfe8d6f6b19737a Mon Sep 17 00:00:00 2001 From: Willie Ruemmele Date: Wed, 10 May 2023 09:24:50 -0600 Subject: [PATCH 08/17] test: use my fork for consistency --- test/nuts/delete/source.nut.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/nuts/delete/source.nut.ts b/test/nuts/delete/source.nut.ts index ea53fc06..4358540d 100644 --- a/test/nuts/delete/source.nut.ts +++ b/test/nuts/delete/source.nut.ts @@ -34,7 +34,7 @@ describe('CustomLabels', () => { before(async () => { testkit = await TestSession.create({ project: { - gitClone: 'https://github.com/mdapi-issues/sfdx-delete-customlabel.git', + gitClone: 'https://github.com/WillieRuemmele/sfdx-delete-customlabel', }, scratchOrgs: [{ setDefault: true, config: path.join('config', 'project-scratch-def.json') }], devhubAuthStrategy: 'AUTO', From 59d1e3812ede0ecba54ceb79c501e4d2ec5ca075 Mon Sep 17 00:00:00 2001 From: Willie Ruemmele Date: Wed, 10 May 2023 15:52:11 -0600 Subject: [PATCH 09/17] chore: nicely handle last CL delete, allow retrieve:start to only delete, print CL/line --- src/commands/project/delete/source.ts | 35 ++++++---- src/commands/project/retrieve/start.ts | 91 +++++++++++++----------- test/nuts/retrieve/customLabels.nut.ts | 96 ++++++++++++++++++++++++++ 3 files changed, 165 insertions(+), 57 deletions(-) create mode 100644 test/nuts/retrieve/customLabels.nut.ts diff --git a/src/commands/project/delete/source.ts b/src/commands/project/delete/source.ts index 83d830fb..0bc625f3 100644 --- a/src/commands/project/delete/source.ts +++ b/src/commands/project/delete/source.ts @@ -365,22 +365,27 @@ export class Source extends SfCommand { attributeNamePrefix: '@_', }); const customLabels = parser.parse(fs.readFileSync(component.xml, 'utf8')) as { - CustomLabels: { labels: Array<{ fullName: string }> }; + CustomLabels: { labels: Array<{ fullName: string }> | { fullName: string } }; }; - // delete the label from the json based on it's fullName - customLabels.CustomLabels.labels = customLabels.CustomLabels.labels.filter( - (label) => label.fullName !== component.fullName - ); - - const builder = new XMLBuilder({ - attributeNamePrefix: '@_', - ignoreAttributes: false, - format: true, - indentBy: ' ', - }); - // and then write that json back to xml and back to the fs - const xml = builder.build(customLabels) as string; - fs.writeFileSync(component.xml, xml); + if ('fullName' in customLabels.CustomLabels.labels) { + // a single custom label remains, delete the entire file + return fs.promises.unlink(component.xml); + } else { + // delete the label from the json based on it's fullName + customLabels.CustomLabels.labels = customLabels.CustomLabels.labels.filter( + (label) => label.fullName !== component.fullName + ); + + const builder = new XMLBuilder({ + attributeNamePrefix: '@_', + ignoreAttributes: false, + format: true, + indentBy: ' ', + }); + // and then write that json back to xml and back to the fs + const xml = builder.build(customLabels) as string; + fs.writeFileSync(component.xml, xml); + } } else { promises.push(fsPromises.unlink(component.xml)); } diff --git a/src/commands/project/retrieve/start.ts b/src/commands/project/retrieve/start.ts index f94d7de2..7d28bc2b 100644 --- a/src/commands/project/retrieve/start.ts +++ b/src/commands/project/retrieve/start.ts @@ -15,6 +15,7 @@ import { SfCommand, toHelpSection, Flags } from '@salesforce/sf-plugins-core'; import { getString } from '@salesforce/ts-types'; import { SourceTracking, SourceConflictError } from '@salesforce/source-tracking'; import { Duration } from '@salesforce/kit'; +import { MetadataApiRetrieveStatus } from '@salesforce/source-deploy-retrieve/lib/src/client/types'; import { DEFAULT_ZIP_FILE_NAME, ensuredDirFlag, zipFileFlag } from '../../../utils/flags'; import { RetrieveResultFormatter } from '../../../formatters/retrieveResultFormatter'; import { MetadataRetrieveResultFormatter } from '../../../formatters/metadataRetrieveResultFormatter'; @@ -161,69 +162,75 @@ export default class RetrieveMetadata extends SfCommand { }), fileResponsesFromDelete: [], }; - // stl sets version based on config/files--if the command overrides it, we need to update - if (isChanges && flags['api-version']) { - componentSetFromNonDeletes.apiVersion = flags['api-version']; - } - this.spinner.status = messages.getMessage('spinner.sending', [ - componentSetFromNonDeletes.sourceApiVersion ?? componentSetFromNonDeletes.apiVersion, - ]); + this.retrieveResult = new RetrieveResult({} as MetadataApiRetrieveStatus, componentSetFromNonDeletes); const zipFileName = flags['zip-file-name'] ?? DEFAULT_ZIP_FILE_NAME; - const retrieveOpts: RetrieveSetOptions = { - usernameOrConnection: - flags['target-org'].getUsername() ?? flags['target-org'].getConnection(flags['api-version']), - merge: true, - output: this.project.getDefaultPackage().fullPath, - packageOptions: flags['package-name'], - format, - ...(format === 'metadata' - ? { - singlePackage: flags['single-package'], - unzip: flags.unzip, - zipFileName, - output: flags['target-metadata-dir'], - } - : {}), - }; - const retrieve = await componentSetFromNonDeletes.retrieve(retrieveOpts); + if (componentSetFromNonDeletes.size !== 0) { + // we have changes to retrieve + // stl sets version based on config/files--if the command overrides it, we need to update + if (isChanges && flags['api-version']) { + componentSetFromNonDeletes.apiVersion = flags['api-version']; + } + this.spinner.status = messages.getMessage('spinner.sending', [ + componentSetFromNonDeletes.sourceApiVersion ?? componentSetFromNonDeletes.apiVersion, + ]); + const retrieveOpts: RetrieveSetOptions = { + usernameOrConnection: + flags['target-org'].getUsername() ?? flags['target-org'].getConnection(flags['api-version']), + merge: true, + output: this.project.getDefaultPackage().fullPath, + packageOptions: flags['package-name'], + format, + ...(format === 'metadata' + ? { + singlePackage: flags['single-package'], + unzip: flags.unzip, + zipFileName, + output: flags['target-metadata-dir'], + } + : {}), + }; - this.spinner.status = messages.getMessage('spinner.polling'); + const retrieve = await componentSetFromNonDeletes.retrieve(retrieveOpts); - retrieve.onUpdate((data) => { - this.spinner.status = mdTransferMessages.getMessage(data.status); - }); + this.spinner.status = messages.getMessage('spinner.polling'); - // any thing else should stop the progress bar - retrieve.onFinish((data) => this.spinner.stop(mdTransferMessages.getMessage(data.response.status))); + retrieve.onUpdate((data) => { + this.spinner.status = mdTransferMessages.getMessage(data.status); + }); - retrieve.onCancel((data) => this.spinner.stop(mdTransferMessages.getMessage(data?.status ?? 'Canceled'))); + // any thing else should stop the progress bar + retrieve.onFinish((data) => this.spinner.stop(mdTransferMessages.getMessage(data.response.status))); - retrieve.onError((error: Error) => { - this.spinner.stop(error.name); - throw error; - }); + retrieve.onCancel((data) => this.spinner.stop(mdTransferMessages.getMessage(data?.status ?? 'Canceled'))); + + retrieve.onError((error: Error) => { + this.spinner.stop(error.name); + throw error; + }); - await retrieve.start(); - const result = await retrieve.pollStatus(500, flags.wait.seconds); + await retrieve.start(); + this.retrieveResult = await retrieve.pollStatus(500, flags.wait.seconds); + } this.spinner.stop(); // reference the flag instead of `format` so we get correct type const formatter = flags['target-metadata-dir'] - ? new MetadataRetrieveResultFormatter(result, { + ? new MetadataRetrieveResultFormatter(this.retrieveResult, { 'target-metadata-dir': flags['target-metadata-dir'], 'zip-file-name': zipFileName, unzip: flags.unzip, }) - : new RetrieveResultFormatter(result, flags['package-name'], fileResponsesFromDelete); + : new RetrieveResultFormatter(this.retrieveResult, flags['package-name'], fileResponsesFromDelete); if (!this.jsonEnabled()) { - if (result.response.status === 'Succeeded') { + // in the case where we didn't retrieve anything, check if we have any deletes + if (this.retrieveResult.response.status === 'Succeeded' || fileResponsesFromDelete.length !== 0) { await formatter.display(); } else { throw new SfError( - getString(result.response, 'errorMessage', result.response.status), - getString(result.response, 'errorStatusCode', 'unknown') + getString(this.retrieveResult.response, 'errorMessage', this.retrieveResult.response.status), + getString(this.retrieveResult.response, 'errorStatusCode', 'unknown') ); } } diff --git a/test/nuts/retrieve/customLabels.nut.ts b/test/nuts/retrieve/customLabels.nut.ts new file mode 100644 index 00000000..d1de7d25 --- /dev/null +++ b/test/nuts/retrieve/customLabels.nut.ts @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2020, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import * as path from 'path'; +import * as fs from 'fs'; +import { expect } from 'chai'; + +import { execCmd, TestSession } from '@salesforce/cli-plugins-testkit'; +import { AuthInfo, Connection } from '@salesforce/core'; +import { DeployResultJson, RetrieveResultJson } from '../../../src/utils/types'; + +let session: TestSession; + +describe('CustomLabel source tracking', () => { + before(async () => { + session = await TestSession.create({ + project: { + gitClone: 'https://github.com/WillieRuemmele/sfdx-delete-customlabel', + }, + devhubAuthStrategy: 'AUTO', + scratchOrgs: [ + { + duration: 1, + setDefault: true, + wait: 10, + config: path.join('config', 'project-scratch-def.json'), + }, + ], + }); + }); + + after(async () => { + await session?.zip(undefined, 'artifacts'); + await session?.clean(); + }); + + it('pushes to initiate the remote', () => { + execCmd('project:deploy:start --json', { ensureExitCode: 0 }); + }); + + it("deletes the 'DeleteMe' CustomLabel", async () => { + const clFile = path.join( + session.project.dir, + 'force-app', + 'main', + 'default', + 'labels', + 'CustomLabels.labels-meta.xml' + ); + const conn = await Connection.create({ + authInfo: await AuthInfo.create({ + username: session.orgs.get('default')?.username, + }), + }); + const id = ( + await conn.singleRecordQuery<{ Id: string }>("SELECT Id FROM CustomLabel WHERE name = 'DeleteMe'", { + tooling: true, + }) + ).Id; + await conn.tooling.sobject('CustomLabel').delete(id); + + const result = execCmd('project:retrieve:start --json', { ensureExitCode: 0 }).jsonOutput + ?.result; + expect(result?.fileProperties).length.to.equal(1); + expect(fs.existsSync(clFile)).to.be.true; + expect(fs.readFileSync(clFile, { encoding: 'utf-8' })).to.not.include('DeleteMe'); + expect(fs.readFileSync(clFile, { encoding: 'utf-8' })).to.include('KeepMe1'); + }); + + it('deletes the remaining CustomLabel', async () => { + const clFile = path.join( + session.project.dir, + 'force-app', + 'main', + 'default', + 'labels', + 'CustomLabels.labels-meta.xml' + ); + const conn = await Connection.create({ + authInfo: await AuthInfo.create({ + username: session.orgs.get('default')?.username, + }), + }); + const ids = (await conn.tooling.query<{ Id: string }>('SELECT Id FROM CustomLabel')).records.map((r) => r.Id); + await conn.tooling.sobject('CustomLabel').delete(ids); + + const result = execCmd('force:source:pull -f', { ensureExitCode: 0 }).shellOutput.stdout; + expect(fs.existsSync(clFile)).to.be.false; + expect(result).to.contain('KeepMe1'); + expect(result).to.contain('KeepMe2'); + }); +}); From a4f65daa9c4f6b9906110dcdde6c70667a6142a3 Mon Sep 17 00:00:00 2001 From: Willie Ruemmele Date: Thu, 11 May 2023 10:46:26 -0600 Subject: [PATCH 10/17] test: remove deprecated command from NUTs --- package.json | 2 +- test/nuts/tracking/forceIgnore.nut.ts | 2 +- test/nuts/tracking/remoteChanges.nut.ts | 6 ++++-- yarn.lock | 22 ++++++++-------------- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index cbd2a590..6130933d 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "@salesforce/core": "^3.34.6", "@salesforce/kit": "^1.9.2", "@salesforce/sf-plugins-core": "^2.4.2", - "@salesforce/source-deploy-retrieve": "^8.0.2", + "@salesforce/source-deploy-retrieve": "^8.4.0", "@salesforce/source-tracking": "^3.1.0", "chalk": "^4.1.2", "fast-xml-parser": "^4.2.2", diff --git a/test/nuts/tracking/forceIgnore.nut.ts b/test/nuts/tracking/forceIgnore.nut.ts index 0b13cd66..a373381f 100644 --- a/test/nuts/tracking/forceIgnore.nut.ts +++ b/test/nuts/tracking/forceIgnore.nut.ts @@ -174,7 +174,7 @@ describe('forceignore changes', () => { it('sf will not retrieve a remote file added to the ignore AFTER it is being tracked', () => { // pull doesn't retrieve that change - const pullOutput = execCmd('retrieve:metadata --json', { + const pullOutput = execCmd('project:retrieve:start --json', { ensureExitCode: 0, }).jsonOutput?.result; expect( diff --git a/test/nuts/tracking/remoteChanges.nut.ts b/test/nuts/tracking/remoteChanges.nut.ts index b8541944..0b037bdb 100644 --- a/test/nuts/tracking/remoteChanges.nut.ts +++ b/test/nuts/tracking/remoteChanges.nut.ts @@ -135,7 +135,8 @@ describe('remote changes', () => { expect(result?.toDeploy).to.deep.equal([]); }); it('can pull the delete', () => { - const result = execCmd('retrieve:metadata --json', { ensureExitCode: 0 }).jsonOutput?.result; + const result = execCmd('project:retrieve:start --json', { ensureExitCode: 0 }).jsonOutput + ?.result; assert(result); assert(Array.isArray(result.files)); // the 2 files for the apexClass, and possibly one for the Profile (depending on whether it got created in time) @@ -212,7 +213,8 @@ describe('remote changes', () => { ).to.equal(true); }); it('can pull the add', () => { - const result = execCmd('retrieve:metadata --json', { ensureExitCode: 0 }).jsonOutput?.result; + const result = execCmd('project:retrieve:start --json', { ensureExitCode: 0 }).jsonOutput + ?.result; // SDR marks all retrieves as 'Changed' even if it creates new local files. This is different from toolbelt, which marked those as 'Created' result?.files .filter((r) => r.fullName === className) diff --git a/yarn.lock b/yarn.lock index 7911daaa..ce2a7530 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1302,16 +1302,17 @@ proxy-from-env "^1.1.0" unzipper "0.10.11" -"@salesforce/source-deploy-retrieve@^8.0.1", "@salesforce/source-deploy-retrieve@^8.0.2": - version "8.0.2" - resolved "https://registry.yarnpkg.com/@salesforce/source-deploy-retrieve/-/source-deploy-retrieve-8.0.2.tgz#b07b768c8fbf0214a3f843ad0da65fec48d5b7f2" - integrity sha512-w73QgWRSd41Kx/6z9/bUpShm36hIoTj/YUswzjfyNzQEuRovaSsBWKXWqWvoserOkz3PBPTYD9t8rHsm98D0SA== +"@salesforce/source-deploy-retrieve@^8.0.1", "@salesforce/source-deploy-retrieve@^8.4.0": + version "8.4.0" + resolved "https://registry.yarnpkg.com/@salesforce/source-deploy-retrieve/-/source-deploy-retrieve-8.4.0.tgz#10cadb7a02dc4657c5b941161b30a5017dabd788" + integrity sha512-eAOuhmbNAMoJdvhdzF1cBtqNIG/WhLR/GRCA89wevvPLidCdzMNzNGPUQWO0N+PSVWskRbbNeZG3n9ws4SV1vA== dependencies: - "@salesforce/core" "^3.34.6" + "@salesforce/core" "^3.36.0" "@salesforce/kit" "^1.9.2" "@salesforce/ts-types" "^1.7.2" archiver "^5.3.1" - fast-xml-parser "^4.1.4" + fast-levenshtein "^3.0.0" + fast-xml-parser "^4.2.2" got "^11.8.6" graceful-fs "^4.2.11" ignore "^5.2.4" @@ -4013,14 +4014,7 @@ fast-levenshtein@^3.0.0: dependencies: fastest-levenshtein "^1.0.7" -fast-xml-parser@^4.1.4: - version "4.2.0" - resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.2.0.tgz#6db2ba33b95b8b4af93f94fe024d4b4d02a50855" - integrity sha512-+zVQv4aVTO+o8oRUyRL7PjgeVo1J6oP8Cw2+a8UTZQcj5V0yUK5T63gTN0ldgiHDPghUjKc4OpT6SwMTwnOQug== - dependencies: - strnum "^1.0.5" - -fast-xml-parser@^4.2.2: +fast-xml-parser@^4.1.4, fast-xml-parser@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.2.2.tgz#cb7310d1e9cf42d22c687b0fae41f3c926629368" integrity sha512-DLzIPtQqmvmdq3VUKR7T6omPK/VCRNqgFlGtbESfyhcH2R4I8EzK1/K6E8PkRCK2EabWrUHK32NjYRbEFnnz0Q== From 7b5d263bfee262edbfbe95807149d4e157d084cc Mon Sep 17 00:00:00 2001 From: Willie Ruemmele Date: Thu, 11 May 2023 11:52:47 -0600 Subject: [PATCH 11/17] chore: use method in STLgp --- package.json | 3 +-- src/commands/project/delete/source.ts | 39 +++++++-------------------- yarn.lock | 30 +++++++++++++++++++-- 3 files changed, 38 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index 6130933d..3fa516f1 100644 --- a/package.json +++ b/package.json @@ -7,13 +7,12 @@ "dependencies": { "@oclif/core": "^2.8.2", "@salesforce/apex-node": "^1.6.0", - "@salesforce/core": "^3.34.6", + "@salesforce/core": "^3.36.0", "@salesforce/kit": "^1.9.2", "@salesforce/sf-plugins-core": "^2.4.2", "@salesforce/source-deploy-retrieve": "^8.4.0", "@salesforce/source-tracking": "^3.1.0", "chalk": "^4.1.2", - "fast-xml-parser": "^4.2.2", "fs-extra": "^10.0.1", "shelljs": "^0.8.5", "tslib": "^2" diff --git a/src/commands/project/delete/source.ts b/src/commands/project/delete/source.ts index 0bc625f3..33465da7 100644 --- a/src/commands/project/delete/source.ts +++ b/src/commands/project/delete/source.ts @@ -32,7 +32,6 @@ import { SfCommand, } from '@salesforce/sf-plugins-core'; import * as chalk from 'chalk'; -import { XMLParser, XMLBuilder } from 'fast-xml-parser'; import { DeleteSourceJson, TestLevel, isSourceComponent } from '../../../utils/types'; import { getPackageDirs, getSourceApiVersion } from '../../../utils/project'; import { resolveApi } from '../../../utils/deploy'; @@ -357,35 +356,15 @@ export class Source extends SfCommand { } if (component.xml) { if (component.type.id === 'customlabel') { - // for custom labels, we need to remove the individual label from the xml file - // so we'll parse the xml - const parser = new XMLParser({ - ignoreDeclaration: false, - ignoreAttributes: false, - attributeNamePrefix: '@_', - }); - const customLabels = parser.parse(fs.readFileSync(component.xml, 'utf8')) as { - CustomLabels: { labels: Array<{ fullName: string }> | { fullName: string } }; - }; - if ('fullName' in customLabels.CustomLabels.labels) { - // a single custom label remains, delete the entire file - return fs.promises.unlink(component.xml); - } else { - // delete the label from the json based on it's fullName - customLabels.CustomLabels.labels = customLabels.CustomLabels.labels.filter( - (label) => label.fullName !== component.fullName - ); - - const builder = new XMLBuilder({ - attributeNamePrefix: '@_', - ignoreAttributes: false, - format: true, - indentBy: ' ', - }); - // and then write that json back to xml and back to the fs - const xml = builder.build(customLabels) as string; - fs.writeFileSync(component.xml, xml); - } + promises.push( + SourceTracking.deleteCustomLabels( + component.xml, + this.componentSet + .getSourceComponents() + .toArray() + .filter((comp) => comp.type.id === 'customlabel') + ) + ); } else { promises.push(fsPromises.unlink(component.xml)); } diff --git a/yarn.lock b/yarn.lock index ce2a7530..d0bd69a1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1302,7 +1302,26 @@ proxy-from-env "^1.1.0" unzipper "0.10.11" -"@salesforce/source-deploy-retrieve@^8.0.1", "@salesforce/source-deploy-retrieve@^8.4.0": +"@salesforce/source-deploy-retrieve@^8.0.1": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@salesforce/source-deploy-retrieve/-/source-deploy-retrieve-8.0.2.tgz#b07b768c8fbf0214a3f843ad0da65fec48d5b7f2" + integrity sha512-w73QgWRSd41Kx/6z9/bUpShm36hIoTj/YUswzjfyNzQEuRovaSsBWKXWqWvoserOkz3PBPTYD9t8rHsm98D0SA== + dependencies: + "@salesforce/core" "^3.34.6" + "@salesforce/kit" "^1.9.2" + "@salesforce/ts-types" "^1.7.2" + archiver "^5.3.1" + fast-xml-parser "^4.1.4" + got "^11.8.6" + graceful-fs "^4.2.11" + ignore "^5.2.4" + mime "2.6.0" + minimatch "^5.1.6" + proxy-agent "^5.0.0" + proxy-from-env "^1.1.0" + unzipper "0.10.11" + +"@salesforce/source-deploy-retrieve@^8.4.0": version "8.4.0" resolved "https://registry.yarnpkg.com/@salesforce/source-deploy-retrieve/-/source-deploy-retrieve-8.4.0.tgz#10cadb7a02dc4657c5b941161b30a5017dabd788" integrity sha512-eAOuhmbNAMoJdvhdzF1cBtqNIG/WhLR/GRCA89wevvPLidCdzMNzNGPUQWO0N+PSVWskRbbNeZG3n9ws4SV1vA== @@ -4014,7 +4033,14 @@ fast-levenshtein@^3.0.0: dependencies: fastest-levenshtein "^1.0.7" -fast-xml-parser@^4.1.4, fast-xml-parser@^4.2.2: +fast-xml-parser@^4.1.4: + version "4.2.0" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.2.0.tgz#6db2ba33b95b8b4af93f94fe024d4b4d02a50855" + integrity sha512-+zVQv4aVTO+o8oRUyRL7PjgeVo1J6oP8Cw2+a8UTZQcj5V0yUK5T63gTN0ldgiHDPghUjKc4OpT6SwMTwnOQug== + dependencies: + strnum "^1.0.5" + +fast-xml-parser@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.2.2.tgz#cb7310d1e9cf42d22c687b0fae41f3c926629368" integrity sha512-DLzIPtQqmvmdq3VUKR7T6omPK/VCRNqgFlGtbESfyhcH2R4I8EzK1/K6E8PkRCK2EabWrUHK32NjYRbEFnnz0Q== From 5dcec482b80cb30a8827f41f81c9c403d40eb7e8 Mon Sep 17 00:00:00 2001 From: Willie Ruemmele Date: Thu, 11 May 2023 15:28:11 -0600 Subject: [PATCH 12/17] chore: consume new STL changes --- src/commands/project/delete/source.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/commands/project/delete/source.ts b/src/commands/project/delete/source.ts index 33465da7..7c43b652 100644 --- a/src/commands/project/delete/source.ts +++ b/src/commands/project/delete/source.ts @@ -22,7 +22,7 @@ import { SourceComponent, } from '@salesforce/source-deploy-retrieve'; import { Duration } from '@salesforce/kit'; -import { ChangeResult, ConflictResponse, SourceTracking } from '@salesforce/source-tracking'; +import { ChangeResult, ConflictResponse, deleteCustomLabels, SourceTracking } from '@salesforce/source-tracking'; import { arrayWithDeprecation, Flags, @@ -343,6 +343,10 @@ export class Source extends SfCommand { private async deleteFilesLocally(): Promise { if (!this.flags['check-only'] && this.deployResult?.response?.status === RequestStatus.Succeeded) { const promises: Array> = []; + const customLabels = this.componentSet + .getSourceComponents() + .toArray() + .filter((comp) => comp.type.id === 'customlabel'); this.components?.filter(isSourceComponent).map((component: SourceComponent) => { // mixed delete/deploy operations have already been deleted and stashed if (!this.mixedDeployDelete.delete.length) { @@ -356,15 +360,7 @@ export class Source extends SfCommand { } if (component.xml) { if (component.type.id === 'customlabel') { - promises.push( - SourceTracking.deleteCustomLabels( - component.xml, - this.componentSet - .getSourceComponents() - .toArray() - .filter((comp) => comp.type.id === 'customlabel') - ) - ); + promises.push(deleteCustomLabels(component.xml, customLabels)); } else { promises.push(fsPromises.unlink(component.xml)); } From 7b7ecdadf5c8fbcdb43d41a1642def258f5b1733 Mon Sep 17 00:00:00 2001 From: Willie Ruemmele Date: Thu, 11 May 2023 15:36:20 -0600 Subject: [PATCH 13/17] chore: optimize a bit more --- src/commands/project/delete/source.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/commands/project/delete/source.ts b/src/commands/project/delete/source.ts index 7c43b652..f5c178b3 100644 --- a/src/commands/project/delete/source.ts +++ b/src/commands/project/delete/source.ts @@ -347,6 +347,9 @@ export class Source extends SfCommand { .getSourceComponents() .toArray() .filter((comp) => comp.type.id === 'customlabel'); + if (customLabels.length && customLabels[0].xml) { + promises.push(deleteCustomLabels(customLabels[0].xml, customLabels)); + } this.components?.filter(isSourceComponent).map((component: SourceComponent) => { // mixed delete/deploy operations have already been deleted and stashed if (!this.mixedDeployDelete.delete.length) { @@ -359,9 +362,8 @@ export class Source extends SfCommand { } } if (component.xml) { - if (component.type.id === 'customlabel') { - promises.push(deleteCustomLabels(component.xml, customLabels)); - } else { + if (component.type.id !== 'customlabel') { + // CustomLabels handled as a special case above promises.push(fsPromises.unlink(component.xml)); } } From 8327fec0a29eac48a072ab1d974c51e8ef2c1411 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Fri, 12 May 2023 14:07:19 -0500 Subject: [PATCH 14/17] chore: qa package for STL --- package.json | 4 ++-- yarn.lock | 68 +++++++++++++++++++++++++++------------------------- 2 files changed, 38 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index 3fa516f1..a448e16f 100644 --- a/package.json +++ b/package.json @@ -7,11 +7,11 @@ "dependencies": { "@oclif/core": "^2.8.2", "@salesforce/apex-node": "^1.6.0", - "@salesforce/core": "^3.36.0", + "@salesforce/core": "^3.36.1", "@salesforce/kit": "^1.9.2", "@salesforce/sf-plugins-core": "^2.4.2", "@salesforce/source-deploy-retrieve": "^8.4.0", - "@salesforce/source-tracking": "^3.1.0", + "@salesforce/source-tracking": "^3.2.0-beta.1", "chalk": "^4.1.2", "fs-extra": "^10.0.1", "shelljs": "^0.8.5", diff --git a/yarn.lock b/yarn.lock index d0bd69a1..5d4218ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1082,10 +1082,10 @@ semver "^7.3.5" ts-retry-promise "^0.6.0" -"@salesforce/core@^3.24.0", "@salesforce/core@^3.32.12", "@salesforce/core@^3.33.5", "@salesforce/core@^3.33.6", "@salesforce/core@^3.34.4", "@salesforce/core@^3.34.6", "@salesforce/core@^3.34.8", "@salesforce/core@^3.36.0": - version "3.36.0" - resolved "https://registry.yarnpkg.com/@salesforce/core/-/core-3.36.0.tgz#cbff147d888eee0b921e368c1fdc1a1a3c2eacab" - integrity sha512-LOeSJgozf5B1S8C98/K3afewRsathfEm+HrXxaFXJjITFfIhxqcIDB5xC1cw0VikfRUlq7dQocmVsvezcK0Ghw== +"@salesforce/core@^3.24.0", "@salesforce/core@^3.32.12", "@salesforce/core@^3.33.5", "@salesforce/core@^3.33.6", "@salesforce/core@^3.34.4", "@salesforce/core@^3.34.6", "@salesforce/core@^3.34.8", "@salesforce/core@^3.36.0", "@salesforce/core@^3.36.1": + version "3.36.1" + resolved "https://registry.yarnpkg.com/@salesforce/core/-/core-3.36.1.tgz#053a5e1079b9749b62e461e6ac3e630b5689694a" + integrity sha512-kcjyr9bj35nnL8Bqv8U39xeho3CrZYXJiS/X5X1eEHVNZLd9zckrmKrh1V7z8ElCFpsJrewT989SJsdvi9kE8w== dependencies: "@salesforce/bunyan" "^2.0.0" "@salesforce/kit" "^1.9.2" @@ -1302,26 +1302,7 @@ proxy-from-env "^1.1.0" unzipper "0.10.11" -"@salesforce/source-deploy-retrieve@^8.0.1": - version "8.0.2" - resolved "https://registry.yarnpkg.com/@salesforce/source-deploy-retrieve/-/source-deploy-retrieve-8.0.2.tgz#b07b768c8fbf0214a3f843ad0da65fec48d5b7f2" - integrity sha512-w73QgWRSd41Kx/6z9/bUpShm36hIoTj/YUswzjfyNzQEuRovaSsBWKXWqWvoserOkz3PBPTYD9t8rHsm98D0SA== - dependencies: - "@salesforce/core" "^3.34.6" - "@salesforce/kit" "^1.9.2" - "@salesforce/ts-types" "^1.7.2" - archiver "^5.3.1" - fast-xml-parser "^4.1.4" - got "^11.8.6" - graceful-fs "^4.2.11" - ignore "^5.2.4" - mime "2.6.0" - minimatch "^5.1.6" - proxy-agent "^5.0.0" - proxy-from-env "^1.1.0" - unzipper "0.10.11" - -"@salesforce/source-deploy-retrieve@^8.4.0": +"@salesforce/source-deploy-retrieve@^8.0.1", "@salesforce/source-deploy-retrieve@^8.4.0": version "8.4.0" resolved "https://registry.yarnpkg.com/@salesforce/source-deploy-retrieve/-/source-deploy-retrieve-8.4.0.tgz#10cadb7a02dc4657c5b941161b30a5017dabd788" integrity sha512-eAOuhmbNAMoJdvhdzF1cBtqNIG/WhLR/GRCA89wevvPLidCdzMNzNGPUQWO0N+PSVWskRbbNeZG3n9ws4SV1vA== @@ -1369,6 +1350,19 @@ isomorphic-git "1.17.0" ts-retry-promise "^0.7.0" +"@salesforce/source-tracking@^3.2.0-beta.1": + version "3.2.0-beta.1" + resolved "https://registry.yarnpkg.com/@salesforce/source-tracking/-/source-tracking-3.2.0-beta.1.tgz#3a43cbee93dd50833e4e0286dd72a86ca690ff46" + integrity sha512-WSKsAW5/JgOoTwL+C8xJ2fStzffj+Wqg08UPze2XTo58EFxGLis4IHGYWJGifDpUkiF3RkRyL7aGhYadM7Emhw== + dependencies: + "@salesforce/core" "^3.36.0" + "@salesforce/kit" "^1.9.2" + "@salesforce/source-deploy-retrieve" "^8.4.0" + fast-xml-parser "^4.2.2" + graceful-fs "^4.2.11" + isomorphic-git "1.23.0" + ts-retry-promise "^0.7.0" + "@salesforce/templates@^57.1.0", "@salesforce/templates@^57.1.1": version "57.1.1" resolved "https://registry.yarnpkg.com/@salesforce/templates/-/templates-57.1.1.tgz#851af880d135af00f9941706aae81bc28014fdb1" @@ -4033,14 +4027,7 @@ fast-levenshtein@^3.0.0: dependencies: fastest-levenshtein "^1.0.7" -fast-xml-parser@^4.1.4: - version "4.2.0" - resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.2.0.tgz#6db2ba33b95b8b4af93f94fe024d4b4d02a50855" - integrity sha512-+zVQv4aVTO+o8oRUyRL7PjgeVo1J6oP8Cw2+a8UTZQcj5V0yUK5T63gTN0ldgiHDPghUjKc4OpT6SwMTwnOQug== - dependencies: - strnum "^1.0.5" - -fast-xml-parser@^4.2.2: +fast-xml-parser@^4.1.4, fast-xml-parser@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.2.2.tgz#cb7310d1e9cf42d22c687b0fae41f3c926629368" integrity sha512-DLzIPtQqmvmdq3VUKR7T6omPK/VCRNqgFlGtbESfyhcH2R4I8EzK1/K6E8PkRCK2EabWrUHK32NjYRbEFnnz0Q== @@ -5313,6 +5300,23 @@ isomorphic-git@1.17.0: sha.js "^2.4.9" simple-get "^4.0.1" +isomorphic-git@1.23.0: + version "1.23.0" + resolved "https://registry.yarnpkg.com/isomorphic-git/-/isomorphic-git-1.23.0.tgz#3afaeb2831e57a2eb95d6ef503cf8251424f03f2" + integrity sha512-7mQlnZivFwrU6B3CswvmoNtVN8jqF9BcLf80uk7yh4fNA8PhFjAfQigi2Hu/Io0cmIvpOc7vn0/Rq3KtL5Ph8g== + dependencies: + async-lock "^1.1.0" + clean-git-ref "^2.0.1" + crc-32 "^1.2.0" + diff3 "0.0.3" + ignore "^5.1.4" + minimisted "^2.0.0" + pako "^1.0.10" + pify "^4.0.1" + readable-stream "^3.4.0" + sha.js "^2.4.9" + simple-get "^4.0.1" + isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" From 4dd229bc813e187edadd3b0856e71ff2729b4338 Mon Sep 17 00:00:00 2001 From: Willie Ruemmele Date: Tue, 16 May 2023 10:43:46 -0600 Subject: [PATCH 15/17] chore: fix delete source output, errors --- src/commands/project/delete/source.ts | 7 ++++++- src/formatters/deleteResultFormatter.ts | 26 ++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/commands/project/delete/source.ts b/src/commands/project/delete/source.ts index f5c178b3..1dd42e41 100644 --- a/src/commands/project/delete/source.ts +++ b/src/commands/project/delete/source.ts @@ -419,7 +419,12 @@ export class Source extends SfCommand { this.components?.flatMap((component) => { if (component instanceof SourceComponent) { - local.push(component.xml as string, ...component.walkContent()); + if (component.type.name === 'CustomLabel') { + // for custom labels, print each custom label to be deleted, not the whole file + local.push(`${component.type.name}:${component.fullName}`); + } else { + local.push(component.xml as string, ...component.walkContent()); + } } else { // remote only metadata remote.push(`${component.type.name}:${component.fullName}`); diff --git a/src/formatters/deleteResultFormatter.ts b/src/formatters/deleteResultFormatter.ts index d1f2ec13..41ac2883 100644 --- a/src/formatters/deleteResultFormatter.ts +++ b/src/formatters/deleteResultFormatter.ts @@ -6,8 +6,10 @@ */ import { ux } from '@oclif/core'; import * as chalk from 'chalk'; -import { DeployResult, FileResponse } from '@salesforce/source-deploy-retrieve'; +import { DeployResult, FileResponse, RequestStatus } from '@salesforce/source-deploy-retrieve'; import { ensureArray } from '@salesforce/kit'; +import { bold } from 'chalk'; +import { StandardColors } from '@salesforce/sf-plugins-core'; import { DeleteSourceJson, Formatter } from '../utils/types'; import { sortFileResponses, asRelativePaths } from '../utils/output'; @@ -74,6 +76,28 @@ export class DeleteResultFormatter implements Formatter { filePath: { header: 'PROJECT PATH' }, } ); + } else { + this.displayFailures(); } } + + private displayFailures(): void { + if (this.result.response.status === RequestStatus.Succeeded) return; + + const failures = ensureArray(this.result.response.details.componentFailures); + if (!failures.length) return; + + const columns = { + problemType: { header: 'Type' }, + fullName: { header: 'Name' }, + error: { header: 'Problem' }, + }; + const options = { title: StandardColors.error(bold(`Component Failures [${failures.length}]`)) }; + ux.log(); + ux.table( + failures.map((f) => ({ problemType: f.problemType, fullName: f.fullName, error: f.problem })), + columns, + options + ); + } } From 678d7bea8e30025e7ad14e390c6798bbb5c19ddb Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 17 May 2023 16:38:50 -0500 Subject: [PATCH 16/17] chore: bump stl, consume label delete return type --- package.json | 2 +- src/commands/project/delete/source.ts | 2 +- yarn.lock | 20 ++++---------------- 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 9cd159d8..5e9c18e4 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "@salesforce/kit": "^1.9.2", "@salesforce/sf-plugins-core": "^2.4.2", "@salesforce/source-deploy-retrieve": "^8.4.0", - "@salesforce/source-tracking": "^3.2.0-beta.1", + "@salesforce/source-tracking": "^3.1.4", "chalk": "^4.1.2", "fs-extra": "^10.0.1", "shelljs": "^0.8.5", diff --git a/src/commands/project/delete/source.ts b/src/commands/project/delete/source.ts index 8e048ae7..8676f92f 100644 --- a/src/commands/project/delete/source.ts +++ b/src/commands/project/delete/source.ts @@ -341,7 +341,7 @@ export class Source extends SfCommand { private async deleteFilesLocally(): Promise { if (!this.flags['check-only'] && this.deployResult?.response?.status === RequestStatus.Succeeded) { - const promises: Array> = []; + const promises: Array | ReturnType> = []; const customLabels = this.componentSet .getSourceComponents() .toArray() diff --git a/yarn.lock b/yarn.lock index e821517d..e2ddc3cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1338,26 +1338,14 @@ shelljs "^0.8.4" sinon "^10.0.0" -"@salesforce/source-tracking@^3.1.2": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@salesforce/source-tracking/-/source-tracking-3.1.2.tgz#a922820eb6e144b60feb30e669000e41ee6ea6ea" - integrity sha512-8TV1El0gzhMbyoqd1beFhJtyBeOspI4J2tHP0lFMs5DfAO2W8PiQtm4uCwo/Ldi2u1s3JGfCN8FFaW5GyI93FQ== +"@salesforce/source-tracking@^3.1.2", "@salesforce/source-tracking@^3.1.4": + version "3.1.4" + resolved "https://registry.yarnpkg.com/@salesforce/source-tracking/-/source-tracking-3.1.4.tgz#81a0cc89dc6d5791bdaef5d3fada00a9a36acbf9" + integrity sha512-3BTB4E9K+wixs2WRO90jy9KIk/0+8DJNr+uPHOz1Fija7TT53weqanPmc/N0STyC8GKcKOBhIcoGTFhifByDvg== dependencies: "@salesforce/core" "^3.36.1" "@salesforce/kit" "^1.9.2" "@salesforce/source-deploy-retrieve" "^8.4.0" - graceful-fs "^4.2.11" - isomorphic-git "1.23.0" - ts-retry-promise "^0.7.0" - -"@salesforce/source-tracking@^3.2.0-beta.1": - version "3.2.0-beta.1" - resolved "https://registry.yarnpkg.com/@salesforce/source-tracking/-/source-tracking-3.2.0-beta.1.tgz#3a43cbee93dd50833e4e0286dd72a86ca690ff46" - integrity sha512-WSKsAW5/JgOoTwL+C8xJ2fStzffj+Wqg08UPze2XTo58EFxGLis4IHGYWJGifDpUkiF3RkRyL7aGhYadM7Emhw== - dependencies: - "@salesforce/core" "^3.36.0" - "@salesforce/kit" "^1.9.2" - "@salesforce/source-deploy-retrieve" "^8.4.0" fast-xml-parser "^4.2.2" graceful-fs "^4.2.11" isomorphic-git "1.23.0" From a96ab036fd1b0c6c361e37b184e6954c6ee6e3d4 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Thu, 18 May 2023 16:32:12 -0500 Subject: [PATCH 17/17] chore: bump stl --- package.json | 2 +- yarn.lock | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 7b27acdf..54e6e857 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "@salesforce/kit": "^1.9.2", "@salesforce/sf-plugins-core": "^2.4.2", "@salesforce/source-deploy-retrieve": "^8.4.0", - "@salesforce/source-tracking": "^3.1.4", + "@salesforce/source-tracking": "^3.1.5", "chalk": "^4.1.2", "fs-extra": "^10.0.1", "shelljs": "^0.8.5", diff --git a/yarn.lock b/yarn.lock index 1359e8a2..b8c4efe8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1302,12 +1302,12 @@ proxy-from-env "^1.1.0" unzipper "0.10.11" -"@salesforce/source-deploy-retrieve@^8.0.1", "@salesforce/source-deploy-retrieve@^8.4.0": - version "8.4.0" - resolved "https://registry.yarnpkg.com/@salesforce/source-deploy-retrieve/-/source-deploy-retrieve-8.4.0.tgz#10cadb7a02dc4657c5b941161b30a5017dabd788" - integrity sha512-eAOuhmbNAMoJdvhdzF1cBtqNIG/WhLR/GRCA89wevvPLidCdzMNzNGPUQWO0N+PSVWskRbbNeZG3n9ws4SV1vA== +"@salesforce/source-deploy-retrieve@^8.0.1", "@salesforce/source-deploy-retrieve@^8.4.0", "@salesforce/source-deploy-retrieve@^8.5.1": + version "8.5.1" + resolved "https://registry.yarnpkg.com/@salesforce/source-deploy-retrieve/-/source-deploy-retrieve-8.5.1.tgz#f66f590348857aa51e11fdb69dbd9fcf1d2fba93" + integrity sha512-bdbyLlpILlGiNzsqrZjOEu3rtp3pqQyPzixL2CA9UebKGytG6zT3J6+/0Br1jAVefbl62KW6dgwurAVL7bp6+w== dependencies: - "@salesforce/core" "^3.36.0" + "@salesforce/core" "^3.36.1" "@salesforce/kit" "^1.9.2" "@salesforce/ts-types" "^1.7.2" archiver "^5.3.1" @@ -1338,14 +1338,14 @@ shelljs "^0.8.4" sinon "^10.0.0" -"@salesforce/source-tracking@^3.1.2", "@salesforce/source-tracking@^3.1.4": - version "3.1.4" - resolved "https://registry.yarnpkg.com/@salesforce/source-tracking/-/source-tracking-3.1.4.tgz#81a0cc89dc6d5791bdaef5d3fada00a9a36acbf9" - integrity sha512-3BTB4E9K+wixs2WRO90jy9KIk/0+8DJNr+uPHOz1Fija7TT53weqanPmc/N0STyC8GKcKOBhIcoGTFhifByDvg== +"@salesforce/source-tracking@^3.1.2", "@salesforce/source-tracking@^3.1.5": + version "3.1.5" + resolved "https://registry.yarnpkg.com/@salesforce/source-tracking/-/source-tracking-3.1.5.tgz#2f8843867fe3a7887da52f153e6cbec6d2af23b8" + integrity sha512-ww7WxRqkWxFock3QqLDvlkW1P7EMZyL6qXIkcx2nAzHs3IhBOJ9wgJlSe/FudWluJ+rLMkvFBvs5D8iN/DaYnQ== dependencies: "@salesforce/core" "^3.36.1" "@salesforce/kit" "^1.9.2" - "@salesforce/source-deploy-retrieve" "^8.4.0" + "@salesforce/source-deploy-retrieve" "^8.5.1" fast-xml-parser "^4.2.2" graceful-fs "^4.2.11" isomorphic-git "1.23.0"