From f8a3c92b9aa8621b0344fc77e05dd9276301b915 Mon Sep 17 00:00:00 2001 From: Fodil Date: Wed, 2 Feb 2022 21:18:50 +0100 Subject: [PATCH 1/7] feat: preview a visualforce page with open command --- README.md | 2 +- messages/open.json | 2 +- src/commands/force/source/open.ts | 36 ++++++++++++++++++++----------- test/commands/source/open.test.ts | 26 ++++++++++++++++++++++ 4 files changed, 52 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index bc4c66683..26f5ed7e7 100644 --- a/README.md +++ b/README.md @@ -1186,7 +1186,7 @@ OPTIONS DESCRIPTION Opens the specified Lightning Page in Lightning App Builder. Lightning Page files have the suffix .flexipage-meta.xml, - and are stored in the flexipages directory. If you specify a different type of file, this command opens your org’s + and are stored in the flexipages directory. If you specify a visualforce page (with a .page suffix) this command let you preview your visualforce page. If you specify a different type of file, this command opens your org’s home page. The file opens in your default browser. diff --git a/messages/open.json b/messages/open.json index 478d967b7..10f4d9eea 100644 --- a/messages/open.json +++ b/messages/open.json @@ -1,5 +1,5 @@ { - "description": "edit a Lightning Page with Lightning App Builder\nOpens the specified Lightning Page in Lightning App Builder. Lightning Page files have the suffix .flexipage-meta.xml, and are stored in the flexipages directory. If you specify a different type of file, this command opens your org’s home page.\n\nThe file opens in your default browser.\nIf no browser-based editor is available for the selected file, this command opens your org's home page.\nTo generate a URL for the browser-based editor but not open the editor, use --urlonly.", + "description": "edit a Lightning Page with Lightning App Builder\nOpens the specified Lightning Page in Lightning App Builder. Lightning Page files have the suffix .flexipage-meta.xml, and are stored in the flexipages directory. \nIf you specify a visualforce page (with a .page suffix) this command let you preview your visualforce page. If you specify a different type of file, this command opens your org’s home page.\n\nThe file opens in your default browser.\nIf no browser-based editor is available for the selected file, this command opens your org's home page.\nTo generate a URL for the browser-based editor but not open the editor, use --urlonly.", "examples": [ "$ sfdx force:source:open -f path/to/source", "$ sfdx force:source:open -r -f path/to/source", diff --git a/src/commands/force/source/open.ts b/src/commands/force/source/open.ts index 2fec7a475..54304ced3 100644 --- a/src/commands/force/source/open.ts +++ b/src/commands/force/source/open.ts @@ -61,7 +61,13 @@ export class Open extends SourceCommand { private async doOpen(): Promise { const typeName = this.getTypeNameDefinitionByFileName(path.resolve(this.flags.sourcefile as string)); - const openPath = typeName === 'FlexiPage' ? await this.setUpOpenPath() : await this.buildFrontdoorUrl(); + let openPath; + if (typeName === 'FlexiPage' || typeName === 'ApexPage') { + // if it is a lightning page + openPath = await this.setUpOpenPath(typeName); + } else { + openPath = await this.buildFrontdoorUrl(); + } this.openResult = await this.open(openPath); } @@ -98,18 +104,24 @@ export class Open extends SourceCommand { return this.flags.urlonly ? result : this.openBrowser(url, result); } - private async setUpOpenPath(): Promise { + private async setUpOpenPath(pageType: string): Promise { try { - const flexipage = await this.org - .getConnection() - .singleRecordQuery<{ Id: string }>( - `SELECT id FROM flexipage WHERE DeveloperName='${path.basename( - this.flags.sourcefile as string, - '.flexipage-meta.xml' - )}'`, - { tooling: true } - ); - return `/visualEditor/appBuilder.app?pageId=${flexipage.Id}`; + if (pageType === 'FlexiPage') { + const flexipage = await this.org + .getConnection() + .singleRecordQuery<{ Id: string }>( + `SELECT id FROM flexipage WHERE DeveloperName='${path.basename( + this.flags.sourcefile as string, + '.flexipage-meta.xml' + )}'`, + { tooling: true } + ); + return `/visualEditor/appBuilder.app?pageId=${flexipage.Id}`; + } else if (pageType === 'ApexPage') { + return `/apex/${path.basename(this.flags.sourcefile as string, '.page')}`; + } else { + return '_ui/flexipage/ui/FlexiPageFilterListPage'; + } } catch (error) { return '_ui/flexipage/ui/FlexiPageFilterListPage'; } diff --git a/test/commands/source/open.test.ts b/test/commands/source/open.test.ts index 35806f103..8b2ead600 100644 --- a/test/commands/source/open.test.ts +++ b/test/commands/source/open.test.ts @@ -45,6 +45,19 @@ describe('force:source:open', () => { }, }, ] as SourceComponent[]; + const apexPageComponents = [ + { + name: 'MyPage', + type: { + id: 'page', + name: 'ApexPage', + suffix: 'page', + directoryName: 'pages', + inFolder: false, + strictDirectoryName: false, + }, + }, + ] as SourceComponent[]; const testIp = '1.1.1.1'; const orgId = '00DJ0000003htplMAA'; const defaultDir = join('my', 'default', 'package'); @@ -108,6 +121,9 @@ describe('force:source:open', () => { if (fsPath.includes('layout')) { return layoutComponents; } + if (fsPath.includes('page')) { + return apexPageComponents; + } } ); stubMethod(sandbox, MyDomainResolver.prototype, 'resolve').resolves(testIp); @@ -140,4 +156,14 @@ describe('force:source:open', () => { url: `${frontDoorUrl}&retURL=${encodeURIComponent(decodeURIComponent(frontDoorUrl))}`, }); }); + + it('given a visualforce page url return /apex/pagename', async () => { + const sourcefile = 'force-app/main/default/pages/MyPage.page'; + const result = await runOpenCmd(['--sourcefile', sourcefile, '--json', '--urlonly']); + expect(result).to.deep.equal({ + orgId, + username, + url: `${frontDoorUrl}&retURL=%2Fapex%2FMyPage`, + }); + }); }); From 5db4d3d7a16577e658faf78de8b7762b570b8856 Mon Sep 17 00:00:00 2001 From: Shane McLaughlin Date: Wed, 2 Feb 2022 15:16:00 -0800 Subject: [PATCH 2/7] Update messages/open.json Co-authored-by: Juliet Shackell <63259011+jshackell-sfdc@users.noreply.github.com> --- messages/open.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/open.json b/messages/open.json index 10f4d9eea..a515d9c15 100644 --- a/messages/open.json +++ b/messages/open.json @@ -1,5 +1,5 @@ { - "description": "edit a Lightning Page with Lightning App Builder\nOpens the specified Lightning Page in Lightning App Builder. Lightning Page files have the suffix .flexipage-meta.xml, and are stored in the flexipages directory. \nIf you specify a visualforce page (with a .page suffix) this command let you preview your visualforce page. If you specify a different type of file, this command opens your org’s home page.\n\nThe file opens in your default browser.\nIf no browser-based editor is available for the selected file, this command opens your org's home page.\nTo generate a URL for the browser-based editor but not open the editor, use --urlonly.", + "description": "edit a Lightning Page with Lightning App Builder\nOpens the specified Lightning Page in Lightning App Builder. Lightning Page files have the suffix .flexipage-meta.xml, and are stored in the flexipages directory. \nIf you specify a Visualforce page, which has a .page suffix, the page opens in your browser so you can preview it. If you specify a different type of file, this command opens your org’s home page.\n\nThe file opens in your default browser.\nIf no browser-based editor is available for the selected file, this command opens your org's home page.\nTo generate a URL for the browser-based editor but not open the editor, use --urlonly.", "examples": [ "$ sfdx force:source:open -f path/to/source", "$ sfdx force:source:open -r -f path/to/source", From 8f4ff2028badd3327fef0d74ae00f6624eb37b4f Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 2 Feb 2022 18:38:12 -0600 Subject: [PATCH 3/7] refactor: use const, ternary --- src/commands/force/source/open.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/commands/force/source/open.ts b/src/commands/force/source/open.ts index 54304ced3..0ffbc4432 100644 --- a/src/commands/force/source/open.ts +++ b/src/commands/force/source/open.ts @@ -61,13 +61,9 @@ export class Open extends SourceCommand { private async doOpen(): Promise { const typeName = this.getTypeNameDefinitionByFileName(path.resolve(this.flags.sourcefile as string)); - let openPath; - if (typeName === 'FlexiPage' || typeName === 'ApexPage') { - // if it is a lightning page - openPath = await this.setUpOpenPath(typeName); - } else { - openPath = await this.buildFrontdoorUrl(); - } + const openPath = ['FlexiPage', 'ApexPage'].includes(typeName) + ? await this.setUpOpenPath(typeName) + : await this.buildFrontdoorUrl(); this.openResult = await this.open(openPath); } From c776af52bedbee4b0ed49f6946d0e9e1e35fa4ae Mon Sep 17 00:00:00 2001 From: mshanemc Date: Thu, 3 Feb 2022 08:14:56 -0600 Subject: [PATCH 4/7] feat: open vf from meta files, error when file doesn't exist --- src/commands/force/source/open.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/commands/force/source/open.ts b/src/commands/force/source/open.ts index 0ffbc4432..3c6752f6d 100644 --- a/src/commands/force/source/open.ts +++ b/src/commands/force/source/open.ts @@ -74,6 +74,7 @@ export class Open extends SourceCommand { const components: SourceComponent[] = metadataResolver.getComponentsFromPath(fsPath); return components[0].type.name; } + throw new SfdxError(`File not found: ${fsPath}`, 'FileNotFound'); } private async buildFrontdoorUrl(): Promise { @@ -114,7 +115,10 @@ export class Open extends SourceCommand { ); return `/visualEditor/appBuilder.app?pageId=${flexipage.Id}`; } else if (pageType === 'ApexPage') { - return `/apex/${path.basename(this.flags.sourcefile as string, '.page')}`; + return `/apex/${path + .basename(this.flags.sourcefile as string) + .replace('.page-meta.xml', '') + .replace('.page', '')}`; } else { return '_ui/flexipage/ui/FlexiPageFilterListPage'; } From 7a0a1a4bd183ed1f2b89bf143cef8bdeebaf96db Mon Sep 17 00:00:00 2001 From: mshanemc Date: Thu, 3 Feb 2022 10:01:48 -0600 Subject: [PATCH 5/7] test: nuts check file existence --- test/nuts/open.nut.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/test/nuts/open.nut.ts b/test/nuts/open.nut.ts index e9161da3e..6a3c4d92e 100644 --- a/test/nuts/open.nut.ts +++ b/test/nuts/open.nut.ts @@ -6,12 +6,16 @@ */ import * as querystring from 'querystring'; +import * as path from 'path'; +import * as fs from 'fs'; import { expect } from '@salesforce/command/lib/test'; import { TestSession } from '@salesforce/cli-plugins-testkit'; import { execCmd } from '@salesforce/cli-plugins-testkit'; import { AnyJson, Dictionary, getString, isArray } from '@salesforce/ts-types'; const flexiPagePath = 'force-app/main/default/flexipages/Property_Explorer.flexipage-meta.xml'; +const layoutDir = 'force-app/main/default/layouts'; +const layoutFilePath = path.join(layoutDir, 'MyLayout.layout-meta.xml'); describe('force:source:open', () => { let session: TestSession; @@ -33,6 +37,10 @@ describe('force:source:open', () => { defaultUsername = getString(session.setup[0], 'result.username'); defaultUserOrgId = getString(session.setup[0], 'result.orgId'); } + await fs.promises.writeFile( + path.join(session.project.dir, layoutFilePath), + '\n' + ); }); after(async () => { @@ -49,13 +57,18 @@ describe('force:source:open', () => { expect(result).to.property('url').to.include(querystring.escape('visualEditor/appBuilder.app')); }); it("should produce the org's frontdoor url when edition of file is not supported", () => { - const unsupportedFilePath = 'force-app/main/default/layouts/MyLayout.layout-meta.xml'; - const result = execCmd(`force:source:open -f ${unsupportedFilePath} --urlonly --json`, { + const result = execCmd(`force:source:open -f ${layoutFilePath} --urlonly --json`, { ensureExitCode: 0, }).jsonOutput.result; expect(result).to.be.ok; expect(result).to.include({ orgId: defaultUserOrgId, username: defaultUsername }); expect(result).to.property('url').to.include(querystring.escape('secur/frontdoor.jsp')); }); + it('should throw for non-existent files', () => { + const result = execCmd('force:source:open -f NotThere --urlonly', { + ensureExitCode: 1, + }); + expect(result.shellOutput.stderr).to.include('NotThere'); + }); }); }); From ebcbcd4fe5db7bb6f0b5e3e4333a563642cc3286 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 2 Feb 2022 09:22:48 -0600 Subject: [PATCH 6/7] refactor: move shared code out of tests --- test/nuts/delete.nut.ts | 2 +- test/nuts/deployDestructive.nut.ts | 15 +-------------- test/nuts/shared/isNameObsolete.ts | 20 ++++++++++++++++++++ 3 files changed, 22 insertions(+), 15 deletions(-) create mode 100644 test/nuts/shared/isNameObsolete.ts diff --git a/test/nuts/delete.nut.ts b/test/nuts/delete.nut.ts index 3ceb23363..e2255074c 100644 --- a/test/nuts/delete.nut.ts +++ b/test/nuts/delete.nut.ts @@ -13,7 +13,7 @@ import { execCmd } from '@salesforce/cli-plugins-testkit'; import { SourceTestkit } from '@salesforce/source-testkit'; import { exec } from 'shelljs'; import { FileResponse } from '@salesforce/source-deploy-retrieve'; -import { isNameObsolete } from './deployDestructive.nut'; +import { isNameObsolete } from './shared/isNameObsolete'; describe('source:delete NUTs', () => { const executable = path.join(process.cwd(), 'bin', 'run'); diff --git a/test/nuts/deployDestructive.nut.ts b/test/nuts/deployDestructive.nut.ts index d17a78648..1fc0f53bc 100644 --- a/test/nuts/deployDestructive.nut.ts +++ b/test/nuts/deployDestructive.nut.ts @@ -10,20 +10,7 @@ import * as os from 'os'; import { expect } from 'chai'; import { execCmd } from '@salesforce/cli-plugins-testkit'; import { SourceTestkit } from '@salesforce/source-testkit'; -import { AuthInfo, Connection } from '@salesforce/core'; - -export const isNameObsolete = async (username: string, memberType: string, memberName: string): Promise => { - const connection = await Connection.create({ - authInfo: await AuthInfo.create({ username }), - }); - - const res = await connection.singleRecordQuery<{ IsNameObsolete: boolean }>( - `SELECT IsNameObsolete FROM SourceMember WHERE MemberType='${memberType}' AND MemberName='${memberName}'`, - { tooling: true } - ); - - return res.IsNameObsolete; -}; +import { isNameObsolete } from './shared/isNameObsolete'; describe('source:deploy --destructive NUTs', () => { const executable = path.join(process.cwd(), 'bin', 'run'); diff --git a/test/nuts/shared/isNameObsolete.ts b/test/nuts/shared/isNameObsolete.ts new file mode 100644 index 000000000..27d95df3c --- /dev/null +++ b/test/nuts/shared/isNameObsolete.ts @@ -0,0 +1,20 @@ +/* + * 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 { AuthInfo, Connection } from '@salesforce/core'; + +export const isNameObsolete = async (username: string, memberType: string, memberName: string): Promise => { + const connection = await Connection.create({ + authInfo: await AuthInfo.create({ username }), + }); + + const res = await connection.singleRecordQuery<{ IsNameObsolete: boolean }>( + `SELECT IsNameObsolete FROM SourceMember WHERE MemberType='${memberType}' AND MemberName='${memberName}'`, + { tooling: true } + ); + + return res.IsNameObsolete; +}; From 98d8b5434b6ecef5be194c327481d04353f99cfe Mon Sep 17 00:00:00 2001 From: Steve Hetzel Date: Thu, 3 Feb 2022 13:31:29 -0700 Subject: [PATCH 7/7] fix: temporarily disable async deploy NUTs --- test/nuts/seeds/deploy.async.seed.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/nuts/seeds/deploy.async.seed.ts b/test/nuts/seeds/deploy.async.seed.ts index 5201f8ce5..39e013a1d 100644 --- a/test/nuts/seeds/deploy.async.seed.ts +++ b/test/nuts/seeds/deploy.async.seed.ts @@ -40,7 +40,7 @@ context('Async Deploy NUTs [name: %REPO_NAME%] [exec: %EXECUTABLE%]', () => { } }); - describe('async deploy', () => { + describe.skip('async deploy', () => { it('should return an id immediately when --wait is set to 0 and deploy:report should report results', async () => { // delete the lwc test stubs which will cause errors with the source tracking/globbing await testkit.deleteGlobs(['force-app/test/**/*']);