From aada9b45bef2c79199c43d6b2207071eed7b54c3 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Mon, 22 Apr 2024 10:10:08 -0500 Subject: [PATCH 1/5] fix: missing walkContent on delete prompt --- src/commands/project/delete/source.ts | 2 +- src/utils/previewOutput.ts | 2 +- src/utils/types.ts | 8 ++++++-- test/utils/types.test.ts | 6 ------ 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/commands/project/delete/source.ts b/src/commands/project/delete/source.ts index cce7691b..9dcb0d1c 100644 --- a/src/commands/project/delete/source.ts +++ b/src/commands/project/delete/source.ts @@ -402,7 +402,7 @@ export class Source extends SfCommand { // for custom labels, print each custom label to be deleted, not the whole file isNonDecomposedCustomLabelsOrCustomLabel(c) ? [`${c.type.name}:${c.fullName}`] - : [c.xml as string, ...c.walkContent()] ?? [] + : [c.xml, ...c.walkContent()] ?? [] ) .concat(this.mixedDeployDelete.delete.map((fr) => `${fr.fullName} (${fr.filePath})`)); diff --git a/src/utils/previewOutput.ts b/src/utils/previewOutput.ts index 07f2375e..3b4ba526 100644 --- a/src/utils/previewOutput.ts +++ b/src/utils/previewOutput.ts @@ -66,7 +66,7 @@ const resolvePaths = ( return resolver.getComponentsFromPath(filename); } catch (e) { // resolver will do logging before throw we don't do it here - return undefined; + return []; } }) .filter(isSourceComponentWithXml) diff --git a/src/utils/types.ts b/src/utils/types.ts index 1f7ce6c5..6f37978f 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -14,6 +14,7 @@ import { SourceComponent, FileResponseFailure, FileResponseSuccess, + ComponentLike, } from '@salesforce/source-deploy-retrieve'; import { isObject } from '@salesforce/ts-types'; import { DefaultReportOptions } from '@salesforce/apex-node'; @@ -89,7 +90,7 @@ export type Formatter = { }; /** validates source component with fullname, type, and xml props */ -export const isSourceComponent = (sc: unknown): sc is SourceComponent => +export const isSourceComponent = (sc: ComponentLike): sc is SourceComponent => isObject(sc) && 'type' in sc && typeof sc.type === 'object' && @@ -97,10 +98,11 @@ export const isSourceComponent = (sc: unknown): sc is SourceComponent => 'name' in sc.type && typeof sc.type.name === 'string' && 'fullName' in sc && + 'walkContent' in sc && // (typeof sc.fullName === 'string' || typeof sc.fullName === 'function'); typeof sc.fullName === 'string'; -export const isSourceComponentWithXml = (sc: unknown): sc is SourceComponent & { xml: string } => +export const isSourceComponentWithXml = (sc: ComponentLike): sc is SourceComponent & { xml: string } => isSourceComponent(sc) && 'xml' in sc && typeof sc.xml === 'string'; export const isSdrFailure = (fileResponse: FileResponse): fileResponse is FileResponseFailure => @@ -111,3 +113,5 @@ export const isSdrSuccess = (fileResponse: FileResponse): fileResponse is FileRe export const isFileResponseDeleted = (fileResponse: FileResponseSuccess): boolean => fileResponse.state === ComponentStatus.Deleted; + +export const isDefined = (value?: T): value is T => value !== undefined; diff --git a/test/utils/types.test.ts b/test/utils/types.test.ts index 94e62c26..40e17f6e 100644 --- a/test/utils/types.test.ts +++ b/test/utils/types.test.ts @@ -16,9 +16,6 @@ const type = reg.getTypeByName('ApexClass'); describe('isSourceComponent (type guard)', () => { describe('good', () => { - it('full, correct definition', () => { - expect({ fullName: 'foo', type, xml: 'fooXml', content: 'fooContent' }).to.satisfy(isSourceComponent); - }); it('SC constructed with xml', () => { expect(new SourceComponent({ name: 'foo', type, xml: 'classes/foo.cls' })).to.satisfy(isSourceComponent); }); @@ -45,9 +42,6 @@ describe('isSourceComponent (type guard)', () => { describe('isSourceComponentWithXml (type guard)', () => { describe('good', () => { - it('full, correct definition', () => { - expect({ fullName: 'foo', type, xml: 'fooXml', content: 'fooContent' }).to.satisfy(isSourceComponentWithXml); - }); it('SC constructed with xml', () => { expect(new SourceComponent({ name: 'foo', type, xml: 'classes/foo.cls' })).to.satisfy(isSourceComponentWithXml); }); From e179481e2367e0f19c379dd28c75be5e23f0c424 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Thu, 25 Apr 2024 10:33:56 -0500 Subject: [PATCH 2/5] test: nut for remote-only source delete interactive --- package.json | 2 +- test/nuts/delete/source.nut.ts | 23 +++++++++++++++++++++-- yarn.lock | 16 ++++++++++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index d5d9d59e..7e9e46b9 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ }, "devDependencies": { "@oclif/plugin-command-snapshot": "^5.1.6", - "@salesforce/cli-plugins-testkit": "^5.2.0", + "@salesforce/cli-plugins-testkit": "^5.2.4-qa.1", "@salesforce/dev-scripts": "^9.0.0", "@salesforce/plugin-command-reference": "^3.0.78", "@salesforce/source-testkit": "^2.2.1", diff --git a/test/nuts/delete/source.nut.ts b/test/nuts/delete/source.nut.ts index 779d32b0..53165e79 100644 --- a/test/nuts/delete/source.nut.ts +++ b/test/nuts/delete/source.nut.ts @@ -8,14 +8,16 @@ import * as fs from 'node:fs'; import { fileURLToPath } from 'node:url'; import * as path from 'node:path'; -import { expect, assert } from 'chai'; -import { execCmd } from '@salesforce/cli-plugins-testkit'; +import { expect, assert, config } from 'chai'; +import { Interaction, execCmd, execInteractiveCmd } from '@salesforce/cli-plugins-testkit'; import { SourceTestkit } from '@salesforce/source-testkit'; import { FileResponse } from '@salesforce/source-deploy-retrieve'; import { AuthInfo, Connection } from '@salesforce/core'; import { ensureArray } from '@salesforce/ts-types'; import { DeleteSourceJson } from '../../../src/utils/types.js'; +config.truncateThreshold = 0; + const isNameObsolete = async (username: string, memberType: string, memberName: string): Promise => { const connection = await Connection.create({ authInfo: await AuthInfo.create({ username }), @@ -265,4 +267,21 @@ describe('project delete source NUTs', () => { expect(await isNameObsolete(testkit.username, 'LightningComponentBundle', 'brokerCard')).to.be.false; expect(fs.existsSync(brokerPath)).to.be.true; }); + + it('deletes a remote-only layout using interactive prompt', async () => { + const layoutName = 'Account-Account %28Marketing%29 Layout'; + const response = ( + await execInteractiveCmd( + ['project:delete:source', '--metadata', `Layout:${layoutName}`], + { + 'Are you sure': Interaction.Yes, + }, + { + ensureExitCode: 0, + } + ) + ).stdout; + expect(response).to.include('Deleted Source'); + expect(response).to.include(layoutName); + }); }); diff --git a/yarn.lock b/yarn.lock index c5a31d7c..8163408d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1674,6 +1674,22 @@ strip-ansi "6.0.1" ts-retry-promise "^0.8.0" +"@salesforce/cli-plugins-testkit@^5.2.4-qa.1": + version "5.2.4-qa.1" + resolved "https://registry.yarnpkg.com/@salesforce/cli-plugins-testkit/-/cli-plugins-testkit-5.2.4-qa.1.tgz#837d17ffbe8d286d8c3db98736339c6704fc37dd" + integrity sha512-dfgfGYISR+7f1wXCms5o+XovHfhabBvxioITB8sNu5bPpK1NGfy6rpJjSrfCZoMkJV7SoQNx0f8MxbnGIgnLPQ== + dependencies: + "@salesforce/core" "^7.3.0" + "@salesforce/kit" "^3.1.1" + "@salesforce/ts-types" "^2.0.9" + "@types/shelljs" "^0.8.15" + debug "^4.3.1" + jszip "^3.10.1" + shelljs "^0.8.4" + sinon "^17.0.1" + strip-ansi "6.0.1" + ts-retry-promise "^0.8.0" + "@salesforce/core@^7.0.0", "@salesforce/core@^7.0.1", "@salesforce/core@^7.2.0", "@salesforce/core@^7.3.0": version "7.3.0" resolved "https://registry.yarnpkg.com/@salesforce/core/-/core-7.3.0.tgz#2ad3dfccb1ef0eb2e65b49b655e39748002bbc13" From 1cd3a9d2930ca3d6da3b75e4bdee1bbfbcf31943 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Thu, 25 Apr 2024 14:43:41 -0500 Subject: [PATCH 3/5] chore: bump testkit --- package.json | 2 +- yarn.lock | 26 +++++--------------------- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 7e9e46b9..f196091a 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ }, "devDependencies": { "@oclif/plugin-command-snapshot": "^5.1.6", - "@salesforce/cli-plugins-testkit": "^5.2.4-qa.1", + "@salesforce/cli-plugins-testkit": "^5.3.0", "@salesforce/dev-scripts": "^9.0.0", "@salesforce/plugin-command-reference": "^3.0.78", "@salesforce/source-testkit": "^2.2.1", diff --git a/yarn.lock b/yarn.lock index 8163408d..3b78d634 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1658,26 +1658,10 @@ istanbul-lib-report "^3.0.1" istanbul-reports "^3.1.6" -"@salesforce/cli-plugins-testkit@^5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@salesforce/cli-plugins-testkit/-/cli-plugins-testkit-5.2.0.tgz#dd664a9a51eac42f9959e0cfa1d3349256446291" - integrity sha512-b0lHGBycDBmeGcDx0j9SfxlNyCg3WhflNeX1PBitMTxgZ5O9UMiSFvLbqgGeTB8Eoq4JgNgou6RRrNoZHFnqcw== - dependencies: - "@salesforce/core" "^7.0.0" - "@salesforce/kit" "^3.1.0" - "@salesforce/ts-types" "^2.0.9" - "@types/shelljs" "^0.8.15" - debug "^4.3.1" - jszip "^3.10.1" - shelljs "^0.8.4" - sinon "^17.0.1" - strip-ansi "6.0.1" - ts-retry-promise "^0.8.0" - -"@salesforce/cli-plugins-testkit@^5.2.4-qa.1": - version "5.2.4-qa.1" - resolved "https://registry.yarnpkg.com/@salesforce/cli-plugins-testkit/-/cli-plugins-testkit-5.2.4-qa.1.tgz#837d17ffbe8d286d8c3db98736339c6704fc37dd" - integrity sha512-dfgfGYISR+7f1wXCms5o+XovHfhabBvxioITB8sNu5bPpK1NGfy6rpJjSrfCZoMkJV7SoQNx0f8MxbnGIgnLPQ== +"@salesforce/cli-plugins-testkit@^5.2.0", "@salesforce/cli-plugins-testkit@^5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@salesforce/cli-plugins-testkit/-/cli-plugins-testkit-5.3.0.tgz#ec7c8da49c36e4c07f6ddc80cb57776e687e4925" + integrity sha512-g+kmsucxS8QBxlV9gjltl4lVW8GjvtlQNUijhtZEqm9Vwb0s5MG/K0wfzg2MwVUZ+ZwNWgcKnRruD5K8LzMZkA== dependencies: "@salesforce/core" "^7.3.0" "@salesforce/kit" "^3.1.1" @@ -1690,7 +1674,7 @@ strip-ansi "6.0.1" ts-retry-promise "^0.8.0" -"@salesforce/core@^7.0.0", "@salesforce/core@^7.0.1", "@salesforce/core@^7.2.0", "@salesforce/core@^7.3.0": +"@salesforce/core@^7.0.1", "@salesforce/core@^7.2.0", "@salesforce/core@^7.3.0": version "7.3.0" resolved "https://registry.yarnpkg.com/@salesforce/core/-/core-7.3.0.tgz#2ad3dfccb1ef0eb2e65b49b655e39748002bbc13" integrity sha512-c/gZLvKFHvgAv/Gyd4LjGGQykvGLn67QtCmdT7Hnm57bTDZoyr7XJXcaI+ILN0NO47guG1tEWP5eBvAi+u2DNA== From 1713dfa789a736c80120c48ce8dea9b22f9fa65a Mon Sep 17 00:00:00 2001 From: mshanemc Date: Thu, 25 Apr 2024 15:07:51 -0500 Subject: [PATCH 4/5] test: no interactive tests on win --- test/nuts/delete/source.nut.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/nuts/delete/source.nut.ts b/test/nuts/delete/source.nut.ts index 53165e79..f4d41c44 100644 --- a/test/nuts/delete/source.nut.ts +++ b/test/nuts/delete/source.nut.ts @@ -7,6 +7,7 @@ import * as fs from 'node:fs'; import { fileURLToPath } from 'node:url'; +import { platform } from 'node:os'; import * as path from 'node:path'; import { expect, assert, config } from 'chai'; import { Interaction, execCmd, execInteractiveCmd } from '@salesforce/cli-plugins-testkit'; @@ -268,7 +269,7 @@ describe('project delete source NUTs', () => { expect(fs.existsSync(brokerPath)).to.be.true; }); - it('deletes a remote-only layout using interactive prompt', async () => { + (platform() === 'win32' ? it : it.skip)('deletes a remote-only layout using interactive prompt', async () => { const layoutName = 'Account-Account %28Marketing%29 Layout'; const response = ( await execInteractiveCmd( From 07c821f358d1851ca2023d8529132122100420b6 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Fri, 26 Apr 2024 17:38:39 -0500 Subject: [PATCH 5/5] test: don't run on windows --- 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 f4d41c44..e2f3b809 100644 --- a/test/nuts/delete/source.nut.ts +++ b/test/nuts/delete/source.nut.ts @@ -269,7 +269,7 @@ describe('project delete source NUTs', () => { expect(fs.existsSync(brokerPath)).to.be.true; }); - (platform() === 'win32' ? it : it.skip)('deletes a remote-only layout using interactive prompt', async () => { + (platform() === 'win32' ? it.skip : it)('deletes a remote-only layout using interactive prompt', async () => { const layoutName = 'Account-Account %28Marketing%29 Layout'; const response = ( await execInteractiveCmd(