From b2ef09297523b2a19e4269fa3f708cf54cd5df15 Mon Sep 17 00:00:00 2001 From: Matthias Rolke Date: Sat, 12 Jun 2021 14:12:20 +0200 Subject: [PATCH 1/6] style: run yarn format --- src/plugins/picklists/index.ts | 24 +++++++++++++++--------- src/plugins/picklists/pages.ts | 5 +++-- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/plugins/picklists/index.ts b/src/plugins/picklists/index.ts index e2dea064..5e346023 100644 --- a/src/plugins/picklists/index.ts +++ b/src/plugins/picklists/index.ts @@ -3,7 +3,11 @@ import { ensureArray } from '../../jsforce-utils'; import { BrowserforcePlugin } from '../../plugin'; import { removeEmptyValues } from '../utils'; import FieldDependencies from './field-dependencies'; -import { PicklistPage, DefaultPicklistAddPage, StatusPicklistAddPage } from './pages'; +import { + PicklistPage, + DefaultPicklistAddPage, + StatusPicklistAddPage +} from './pages'; import { determineStandardValueSetEditUrl } from './standard-value-set'; export default class Picklists extends BrowserforcePlugin { @@ -40,7 +44,6 @@ export default class Picklists extends BrowserforcePlugin { Boolean(newValueMatch) || action.newValue === null; result.picklistValues.push(state); } - } if (definition.fieldDependencies) { result.fieldDependencies = await new FieldDependencies( @@ -70,10 +73,7 @@ export default class Picklists extends BrowserforcePlugin { ) { return true; } - if ( - target.newValue && - !source.newValueExists - ) { + if (target.newValue && !source.newValueExists) { // New value doesn't exist in org yet return true; } @@ -115,15 +115,21 @@ export default class Picklists extends BrowserforcePlugin { action.value ); await replacePage.replaceAndDelete(action.newValue); - } else if (!action.value && action.newValue && !action.replaceAllBlankValues) { + } else if ( + !action.value && + action.newValue && + !action.replaceAllBlankValues + ) { await picklistPage.clickNewActionButton(); if (action.statusCategory) { - await new StatusPicklistAddPage(page).add(action.newValue, action.statusCategory); + await new StatusPicklistAddPage(page).add( + action.newValue, + action.statusCategory + ); } else { await new DefaultPicklistAddPage(page).add(action.newValue); } - } else { const replacePage = await picklistPage.clickReplaceActionButton(); await replacePage.replace( diff --git a/src/plugins/picklists/pages.ts b/src/plugins/picklists/pages.ts index ae150fb9..acf9ccdd 100644 --- a/src/plugins/picklists/pages.ts +++ b/src/plugins/picklists/pages.ts @@ -48,7 +48,8 @@ export class PicklistPage { ]; } public async clickNewActionButton(): Promise { - const NEW_ACTION_BUTTON_XPATH = '//tr[td[2]]//input[contains(@onclick, "/setup/ui/picklist_masteredit")][@value=" New "]'; + const NEW_ACTION_BUTTON_XPATH = + '//tr[td[2]]//input[contains(@onclick, "/setup/ui/picklist_masteredit")][@value=" New "]'; await this.page.waitForXPath(NEW_ACTION_BUTTON_XPATH); const NEW_ACTION_BUTTON = (await this.page.$x(NEW_ACTION_BUTTON_XPATH))[0]; await Promise.all([ @@ -176,7 +177,7 @@ export class StatusPicklistAddPage { async add(newValue, statusCategory) { const LABEL_INPUT = 'input#p1'; const API_NAME_INPUT = 'input#p3'; - const STATUS_CATEGORY_SELECTOR = 'select#p5' + const STATUS_CATEGORY_SELECTOR = 'select#p5'; if (newValue !== undefined && newValue !== null) { await this.page.waitForSelector(STATUS_CATEGORY_SELECTOR); await this.page.type(LABEL_INPUT, newValue); From 837fe2b6f32a109ee88046a2bf2ed972b5c2a851 Mon Sep 17 00:00:00 2001 From: Matthias Rolke Date: Sat, 12 Jun 2021 14:18:02 +0200 Subject: [PATCH 2/6] refactor: use kebab style file names --- src/{browserforceCommand.ts => browserforce-command.ts} | 0 src/commands/browserforce/apply.ts | 2 +- src/commands/browserforce/plan.ts | 2 +- ...{disable-manyWhoPref.json => disable-many-who-pref.json} | 0 .../{enable-manyWhoPref.json => enable-many-who-pref.json} | 0 src/plugins/activity-settings/index.e2e-spec.ts | 6 +++--- .../available.json | 0 .../index.test.ts | 0 .../index.ts | 0 .../sfdx-source/objects/Dummy__c/Dummy__c.object-meta.xml | 0 .../unavailable.json | 0 src/plugins/customer-portal/index.e2e-spec.ts | 4 ++-- src/plugins/customer-portal/index.ts | 2 +- .../{folderSharing => folder-sharing}/disable.json | 0 .../{folderSharing => folder-sharing}/enable.json | 0 .../{folderSharing => folder-sharing}/index.e2e-spec.ts | 0 .../{folderSharing => folder-sharing}/index.ts | 0 .../{folderSharing => folder-sharing}/schema.json | 0 src/plugins/reports-and-dashboards/index.ts | 2 +- 19 files changed, 9 insertions(+), 9 deletions(-) rename src/{browserforceCommand.ts => browserforce-command.ts} (100%) rename src/plugins/activity-settings/{disable-manyWhoPref.json => disable-many-who-pref.json} (100%) rename src/plugins/activity-settings/{enable-manyWhoPref.json => enable-many-who-pref.json} (100%) rename src/plugins/customer-portal/{availableCustomObjects => available-custom-objects}/available.json (100%) rename src/plugins/customer-portal/{availableCustomObjects => available-custom-objects}/index.test.ts (100%) rename src/plugins/customer-portal/{availableCustomObjects => available-custom-objects}/index.ts (100%) rename src/plugins/customer-portal/{availableCustomObjects => available-custom-objects}/sfdx-source/objects/Dummy__c/Dummy__c.object-meta.xml (100%) rename src/plugins/customer-portal/{availableCustomObjects => available-custom-objects}/unavailable.json (100%) rename src/plugins/reports-and-dashboards/{folderSharing => folder-sharing}/disable.json (100%) rename src/plugins/reports-and-dashboards/{folderSharing => folder-sharing}/enable.json (100%) rename src/plugins/reports-and-dashboards/{folderSharing => folder-sharing}/index.e2e-spec.ts (100%) rename src/plugins/reports-and-dashboards/{folderSharing => folder-sharing}/index.ts (100%) rename src/plugins/reports-and-dashboards/{folderSharing => folder-sharing}/schema.json (100%) diff --git a/src/browserforceCommand.ts b/src/browserforce-command.ts similarity index 100% rename from src/browserforceCommand.ts rename to src/browserforce-command.ts diff --git a/src/commands/browserforce/apply.ts b/src/commands/browserforce/apply.ts index 39179489..74fb8d6f 100644 --- a/src/commands/browserforce/apply.ts +++ b/src/commands/browserforce/apply.ts @@ -1,5 +1,5 @@ import { core } from '@salesforce/command'; -import BrowserforceCommand from '../../browserforceCommand'; +import BrowserforceCommand from '../../browserforce-command'; core.Messages.importMessagesDirectory(__dirname); const messages = core.Messages.loadMessages( diff --git a/src/commands/browserforce/plan.ts b/src/commands/browserforce/plan.ts index 535570cc..251e638c 100644 --- a/src/commands/browserforce/plan.ts +++ b/src/commands/browserforce/plan.ts @@ -1,6 +1,6 @@ import { core } from '@salesforce/command'; import * as path from 'path'; -import BrowserforceCommand from '../../browserforceCommand'; +import BrowserforceCommand from '../../browserforce-command'; core.Messages.importMessagesDirectory(__dirname); const messages = core.Messages.loadMessages( diff --git a/src/plugins/activity-settings/disable-manyWhoPref.json b/src/plugins/activity-settings/disable-many-who-pref.json similarity index 100% rename from src/plugins/activity-settings/disable-manyWhoPref.json rename to src/plugins/activity-settings/disable-many-who-pref.json diff --git a/src/plugins/activity-settings/enable-manyWhoPref.json b/src/plugins/activity-settings/enable-many-who-pref.json similarity index 100% rename from src/plugins/activity-settings/enable-manyWhoPref.json rename to src/plugins/activity-settings/enable-many-who-pref.json diff --git a/src/plugins/activity-settings/index.e2e-spec.ts b/src/plugins/activity-settings/index.e2e-spec.ts index 0ad65c2e..c8a5e528 100644 --- a/src/plugins/activity-settings/index.e2e-spec.ts +++ b/src/plugins/activity-settings/index.e2e-spec.ts @@ -10,7 +10,7 @@ describe(ActivitySettings.name, function() { const enableManyWhoPrefCmd = child.spawnSync(path.resolve('bin', 'run'), [ 'browserforce:apply', '-f', - path.resolve(path.join(__dirname, 'enable-manyWhoPref.json')) + path.resolve(path.join(__dirname, 'enable-many-who-pref.json')) ]); assert.deepStrictEqual( enableManyWhoPrefCmd.status, @@ -28,7 +28,7 @@ describe(ActivitySettings.name, function() { const enableManyWhoPrefCmd2 = child.spawnSync(path.resolve('bin', 'run'), [ 'browserforce:apply', '-f', - path.resolve(path.join(__dirname, 'enable-manyWhoPref.json')) + path.resolve(path.join(__dirname, 'enable-many-who-pref.json')) ]); assert.deepStrictEqual( enableManyWhoPrefCmd2.status, @@ -44,7 +44,7 @@ describe(ActivitySettings.name, function() { const disableManyWhoPrefCmd = child.spawnSync(path.resolve('bin', 'run'), [ 'browserforce:apply', '-f', - path.resolve(path.join(__dirname, 'disable-manyWhoPref.json')) + path.resolve(path.join(__dirname, 'disable-many-who-pref.json')) ]); assert.deepStrictEqual( disableManyWhoPrefCmd.status, diff --git a/src/plugins/customer-portal/availableCustomObjects/available.json b/src/plugins/customer-portal/available-custom-objects/available.json similarity index 100% rename from src/plugins/customer-portal/availableCustomObjects/available.json rename to src/plugins/customer-portal/available-custom-objects/available.json diff --git a/src/plugins/customer-portal/availableCustomObjects/index.test.ts b/src/plugins/customer-portal/available-custom-objects/index.test.ts similarity index 100% rename from src/plugins/customer-portal/availableCustomObjects/index.test.ts rename to src/plugins/customer-portal/available-custom-objects/index.test.ts diff --git a/src/plugins/customer-portal/availableCustomObjects/index.ts b/src/plugins/customer-portal/available-custom-objects/index.ts similarity index 100% rename from src/plugins/customer-portal/availableCustomObjects/index.ts rename to src/plugins/customer-portal/available-custom-objects/index.ts diff --git a/src/plugins/customer-portal/availableCustomObjects/sfdx-source/objects/Dummy__c/Dummy__c.object-meta.xml b/src/plugins/customer-portal/available-custom-objects/sfdx-source/objects/Dummy__c/Dummy__c.object-meta.xml similarity index 100% rename from src/plugins/customer-portal/availableCustomObjects/sfdx-source/objects/Dummy__c/Dummy__c.object-meta.xml rename to src/plugins/customer-portal/available-custom-objects/sfdx-source/objects/Dummy__c/Dummy__c.object-meta.xml diff --git a/src/plugins/customer-portal/availableCustomObjects/unavailable.json b/src/plugins/customer-portal/available-custom-objects/unavailable.json similarity index 100% rename from src/plugins/customer-portal/availableCustomObjects/unavailable.json rename to src/plugins/customer-portal/available-custom-objects/unavailable.json diff --git a/src/plugins/customer-portal/index.e2e-spec.ts b/src/plugins/customer-portal/index.e2e-spec.ts index efff1867..ed772792 100644 --- a/src/plugins/customer-portal/index.e2e-spec.ts +++ b/src/plugins/customer-portal/index.e2e-spec.ts @@ -1,7 +1,7 @@ import * as assert from 'assert'; import * as child from 'child_process'; import * as path from 'path'; -import CustomerPortalAvailableCustomObjects from './availableCustomObjects'; +import CustomerPortalAvailableCustomObjects from './available-custom-objects'; import CustomerPortalEnable from './enabled'; import CustomerPortalSetup from './portals'; @@ -168,7 +168,7 @@ describe(CustomerPortalSetup.name, function() { describe(CustomerPortalAvailableCustomObjects.name, function() { this.slow('30s'); this.timeout('2m 30s'); - const dir = path.resolve(path.join(__dirname, 'availableCustomObjects')); + const dir = path.resolve(path.join(__dirname, 'available-custom-objects')); it('should fail to make non-existent custom objects available for customer portal', () => { const setupPortalCmd = child.spawnSync(path.resolve('bin', 'run'), [ 'browserforce:apply', diff --git a/src/plugins/customer-portal/index.ts b/src/plugins/customer-portal/index.ts index 2effb3ed..1a519bc5 100644 --- a/src/plugins/customer-portal/index.ts +++ b/src/plugins/customer-portal/index.ts @@ -1,6 +1,6 @@ import { BrowserforcePlugin } from '../../plugin'; import { removeEmptyValues } from '../utils'; -import CustomerPortalAvailableCustomObjects from './availableCustomObjects'; +import CustomerPortalAvailableCustomObjects from './available-custom-objects'; import CustomerPortalEnable from './enabled'; import CustomerPortalSetup from './portals'; diff --git a/src/plugins/reports-and-dashboards/folderSharing/disable.json b/src/plugins/reports-and-dashboards/folder-sharing/disable.json similarity index 100% rename from src/plugins/reports-and-dashboards/folderSharing/disable.json rename to src/plugins/reports-and-dashboards/folder-sharing/disable.json diff --git a/src/plugins/reports-and-dashboards/folderSharing/enable.json b/src/plugins/reports-and-dashboards/folder-sharing/enable.json similarity index 100% rename from src/plugins/reports-and-dashboards/folderSharing/enable.json rename to src/plugins/reports-and-dashboards/folder-sharing/enable.json diff --git a/src/plugins/reports-and-dashboards/folderSharing/index.e2e-spec.ts b/src/plugins/reports-and-dashboards/folder-sharing/index.e2e-spec.ts similarity index 100% rename from src/plugins/reports-and-dashboards/folderSharing/index.e2e-spec.ts rename to src/plugins/reports-and-dashboards/folder-sharing/index.e2e-spec.ts diff --git a/src/plugins/reports-and-dashboards/folderSharing/index.ts b/src/plugins/reports-and-dashboards/folder-sharing/index.ts similarity index 100% rename from src/plugins/reports-and-dashboards/folderSharing/index.ts rename to src/plugins/reports-and-dashboards/folder-sharing/index.ts diff --git a/src/plugins/reports-and-dashboards/folderSharing/schema.json b/src/plugins/reports-and-dashboards/folder-sharing/schema.json similarity index 100% rename from src/plugins/reports-and-dashboards/folderSharing/schema.json rename to src/plugins/reports-and-dashboards/folder-sharing/schema.json diff --git a/src/plugins/reports-and-dashboards/index.ts b/src/plugins/reports-and-dashboards/index.ts index c05be57b..58bd1794 100644 --- a/src/plugins/reports-and-dashboards/index.ts +++ b/src/plugins/reports-and-dashboards/index.ts @@ -1,6 +1,6 @@ import { BrowserforcePlugin } from '../../plugin'; import { removeEmptyValues } from '../utils'; -import FolderSharing from './folderSharing'; +import FolderSharing from './folder-sharing'; export default class ReportsAndDashboards extends BrowserforcePlugin { public async retrieve(definition?) { From 8afd6bf86fad84a8455ea7150494ec81631164bb Mon Sep 17 00:00:00 2001 From: Matthias Rolke Date: Sat, 12 Jun 2021 14:25:30 +0200 Subject: [PATCH 3/6] refactor: replace CommonJS require with ES module import --- src/plugins/picklists/pages.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/picklists/pages.ts b/src/plugins/picklists/pages.ts index acf9ccdd..c7b55e79 100644 --- a/src/plugins/picklists/pages.ts +++ b/src/plugins/picklists/pages.ts @@ -1,4 +1,4 @@ -import pRetry = require('p-retry'); +import pRetry from 'p-retry'; import { JSHandle } from 'puppeteer'; // table columns From 00f1719b98815705325287e99aaaeac0e3fb9668 Mon Sep 17 00:00:00 2001 From: Matthias Rolke Date: Sat, 12 Jun 2021 23:56:51 +0200 Subject: [PATCH 4/6] refactor: convert default exports to named exports --- _templates/plugin/new/index.ejs.t | 2 +- _templates/plugin/new/plugins-index.ejs.t | 2 +- src/browserforce-command.ts | 6 +-- src/browserforce.ts | 2 +- src/commands/browserforce/apply.ts | 4 +- src/commands/browserforce/plan.ts | 4 +- src/config-parser.ts | 2 +- src/index.ts | 2 +- src/plugin.ts | 2 +- .../activity-settings/index.e2e-spec.ts | 2 +- src/plugins/activity-settings/index.ts | 2 +- src/plugins/communities/index.e2e-spec.ts | 2 +- src/plugins/communities/index.ts | 2 +- .../available-custom-objects/index.test.ts | 2 +- .../available-custom-objects/index.ts | 2 +- .../customer-portal/enabled/index.test.ts | 2 +- src/plugins/customer-portal/enabled/index.ts | 2 +- src/plugins/customer-portal/index.e2e-spec.ts | 6 +-- src/plugins/customer-portal/index.test.ts | 2 +- src/plugins/customer-portal/index.ts | 8 ++-- .../customer-portal/portals/index.test.ts | 2 +- src/plugins/customer-portal/portals/index.ts | 2 +- .../index.e2e-spec.ts | 2 +- .../defer-sharing-calculation/index.ts | 2 +- .../density-settings/index.e2e-spec.ts | 2 +- src/plugins/density-settings/index.ts | 2 +- .../index.e2e-spec.ts | 2 +- .../high-velocity-sales-settings/index.ts | 2 +- .../home-page-layouts/index.e2e-spec.ts | 2 +- src/plugins/home-page-layouts/index.ts | 2 +- src/plugins/index.ts | 37 +++++++++---------- .../index.e2e-spec.ts | 2 +- .../lightning-experience-settings/index.ts | 2 +- .../picklists/field-dependencies/index.ts | 2 +- src/plugins/picklists/index.e2e-spec.ts | 4 +- src/plugins/picklists/index.ts | 4 +- src/plugins/record-types/index.e2e-spec.ts | 2 +- src/plugins/record-types/index.ts | 2 +- .../folder-sharing/index.ts | 2 +- src/plugins/reports-and-dashboards/index.ts | 4 +- .../index.e2e-spec.ts | 2 +- src/plugins/salesforce-to-salesforce/index.ts | 2 +- .../certificate-and-key-management/index.ts | 2 +- .../security/identity-provider/index.ts | 2 +- src/plugins/security/index.e2e-spec.ts | 4 +- src/plugins/security/index.ts | 10 ++--- .../login-access-policies/index.e2e-spec.ts | 2 +- .../security/login-access-policies/index.ts | 2 +- .../security/sharing/index.e2e-spec.ts | 2 +- src/plugins/security/sharing/index.ts | 2 +- test/browserforce.e2e-spec.ts | 2 +- test/config-parser.test.ts | 4 +- 52 files changed, 87 insertions(+), 88 deletions(-) diff --git a/_templates/plugin/new/index.ejs.t b/_templates/plugin/new/index.ejs.t index 798ddbe9..bc1a0c65 100644 --- a/_templates/plugin/new/index.ejs.t +++ b/_templates/plugin/new/index.ejs.t @@ -13,7 +13,7 @@ const SELECTORS = { SAVE_BUTTON: 'input[id$=":save"]' }; -export default class <%= h.changeCase.pascalCase(name) %> extends BrowserforcePlugin { +export class <%= h.changeCase.pascalCase(name) %> extends BrowserforcePlugin { public async retrieve(definition?) { const page = await this.browserforce.openPage(PATHS.BASE); await page.waitForSelector(SELECTORS.ENABLED); diff --git a/_templates/plugin/new/plugins-index.ejs.t b/_templates/plugin/new/plugins-index.ejs.t index 0a012758..cf3b8469 100644 --- a/_templates/plugin/new/plugins-index.ejs.t +++ b/_templates/plugin/new/plugins-index.ejs.t @@ -3,4 +3,4 @@ to: src/plugins/index.ts inject: true before: export { --- -import * as <%= h.changeCase.camelCase(name) %> from './<%= h.changeCase.paramCase(name) %>'; +import { <%= name %> as <%= h.changeCase.camelCase(name) %> } from './<%= h.changeCase.paramCase(name) %>'; diff --git a/src/browserforce-command.ts b/src/browserforce-command.ts index 759b516c..0e7af6e7 100644 --- a/src/browserforce-command.ts +++ b/src/browserforce-command.ts @@ -1,7 +1,7 @@ import { core, flags, SfdxCommand } from '@salesforce/command'; import * as path from 'path'; -import Browserforce from './browserforce'; -import ConfigParser from './config-parser'; +import { Browserforce } from './browserforce'; +import { ConfigParser } from './config-parser'; import * as DRIVERS from './plugins'; core.Messages.importMessagesDirectory(__dirname); @@ -10,7 +10,7 @@ const messages = core.Messages.loadMessages( 'browserforce' ); -export default class BrowserforceCommand extends SfdxCommand { +export class BrowserforceCommand extends SfdxCommand { protected static requiresUsername = true; protected static flagsConfig = { diff --git a/src/browserforce.ts b/src/browserforce.ts index 4152ddfb..78baba6f 100644 --- a/src/browserforce.ts +++ b/src/browserforce.ts @@ -10,7 +10,7 @@ const ERROR_DIV_SELECTOR = '#errorTitle'; const ERROR_DIVS_SELECTOR = 'div.errorMsg'; const VF_IFRAME_SELECTOR = 'iframe[name^=vfFrameId]'; -export default class Browserforce { +export class Browserforce { public org: core.Org; public logger: core.Logger; public browser: Browser; diff --git a/src/commands/browserforce/apply.ts b/src/commands/browserforce/apply.ts index 74fb8d6f..877f44b1 100644 --- a/src/commands/browserforce/apply.ts +++ b/src/commands/browserforce/apply.ts @@ -1,5 +1,5 @@ import { core } from '@salesforce/command'; -import BrowserforceCommand from '../../browserforce-command'; +import { BrowserforceCommand } from '../../browserforce-command'; core.Messages.importMessagesDirectory(__dirname); const messages = core.Messages.loadMessages( @@ -28,7 +28,7 @@ export default class BrowserforceApply extends BrowserforceCommand { } to org ${this.org.getUsername()}` ); for (const setting of this.settings) { - const driver = setting.Driver.default; + const driver = setting.Driver; const instance = new driver(this.bf, this.org); this.ux.startSpinner(`[${driver.name}] retrieving state`); let state; diff --git a/src/commands/browserforce/plan.ts b/src/commands/browserforce/plan.ts index 251e638c..a4ba2d1f 100644 --- a/src/commands/browserforce/plan.ts +++ b/src/commands/browserforce/plan.ts @@ -1,6 +1,6 @@ import { core } from '@salesforce/command'; import * as path from 'path'; -import BrowserforceCommand from '../../browserforce-command'; +import { BrowserforceCommand } from '../../browserforce-command'; core.Messages.importMessagesDirectory(__dirname); const messages = core.Messages.loadMessages( @@ -34,7 +34,7 @@ export default class BrowserforcePlanCommand extends BrowserforceCommand { settings: {} }; for (const setting of this.settings) { - const driver = setting.Driver.default; + const driver = setting.Driver; const instance = new driver(this.bf, this.org); this.ux.startSpinner(`[${driver.name}] retrieving state`); let driverState; diff --git a/src/config-parser.ts b/src/config-parser.ts index 000d2cb7..2cc2ac68 100644 --- a/src/config-parser.ts +++ b/src/config-parser.ts @@ -1,4 +1,4 @@ -export default class ConfigParser { +export class ConfigParser { public static parse(drivers, data) { const settings = []; if (data && data.settings) { diff --git a/src/index.ts b/src/index.ts index ff8b4c56..cb0ff5c3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1 @@ -export default {}; +export {}; diff --git a/src/plugin.ts b/src/plugin.ts index 87fdf46c..f63fca9d 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,6 +1,6 @@ import { core } from '@salesforce/command'; import * as jsonMergePatch from 'json-merge-patch'; -import Browserforce from './browserforce'; +import { Browserforce } from './browserforce'; export abstract class BrowserforcePlugin { protected org: core.Org; diff --git a/src/plugins/activity-settings/index.e2e-spec.ts b/src/plugins/activity-settings/index.e2e-spec.ts index c8a5e528..60815c12 100644 --- a/src/plugins/activity-settings/index.e2e-spec.ts +++ b/src/plugins/activity-settings/index.e2e-spec.ts @@ -1,7 +1,7 @@ import * as assert from 'assert'; import * as child from 'child_process'; import * as path from 'path'; -import ActivitySettings from '.'; +import { ActivitySettings } from '.'; describe(ActivitySettings.name, function() { this.slow('30s'); diff --git a/src/plugins/activity-settings/index.ts b/src/plugins/activity-settings/index.ts index 5d2e079f..ed427b09 100644 --- a/src/plugins/activity-settings/index.ts +++ b/src/plugins/activity-settings/index.ts @@ -9,7 +9,7 @@ const SELECTORS = { SUBMIT_BUTTON: 'input[id="thePage:theForm:theBlock:buttons:submit"]' }; -export default class ActivitySettings extends BrowserforcePlugin { +export class ActivitySettings extends BrowserforcePlugin { public async retrieve() { const page = await this.browserforce.openPage(PATHS.BASE, { waitUntil: ['load', 'domcontentloaded', 'networkidle0'] diff --git a/src/plugins/communities/index.e2e-spec.ts b/src/plugins/communities/index.e2e-spec.ts index 10fc3470..55416a9d 100644 --- a/src/plugins/communities/index.e2e-spec.ts +++ b/src/plugins/communities/index.e2e-spec.ts @@ -1,7 +1,7 @@ import * as assert from 'assert'; import * as child from 'child_process'; import * as path from 'path'; -import Communities from '.'; +import { Communities } from '.'; describe.skip(Communities.name, function() { this.slow('30s'); diff --git a/src/plugins/communities/index.ts b/src/plugins/communities/index.ts index d389790a..145a5f0d 100644 --- a/src/plugins/communities/index.ts +++ b/src/plugins/communities/index.ts @@ -10,7 +10,7 @@ const SELECTORS = { SAVE_BUTTON: 'input[id$=":saveId"]' }; -export default class Communities extends BrowserforcePlugin { +export class Communities extends BrowserforcePlugin { public async retrieve() { const page = await this.browserforce.openPage(PATHS.BASE, { waitUntil: ['load', 'domcontentloaded', 'networkidle0'] diff --git a/src/plugins/customer-portal/available-custom-objects/index.test.ts b/src/plugins/customer-portal/available-custom-objects/index.test.ts index 7cee399a..62ab31ba 100644 --- a/src/plugins/customer-portal/available-custom-objects/index.test.ts +++ b/src/plugins/customer-portal/available-custom-objects/index.test.ts @@ -1,5 +1,5 @@ import * as assert from 'assert'; -import CustomerPortalAvailableCustomObjects from '.'; +import { CustomerPortalAvailableCustomObjects } from '.'; const tests = [ { diff --git a/src/plugins/customer-portal/available-custom-objects/index.ts b/src/plugins/customer-portal/available-custom-objects/index.ts index 300aa0ef..8e859dd2 100644 --- a/src/plugins/customer-portal/available-custom-objects/index.ts +++ b/src/plugins/customer-portal/available-custom-objects/index.ts @@ -13,7 +13,7 @@ interface CustomObjectRecord { NamespacePrefix: string; } -export default class CustomerPortalAvailableCustomObjects extends BrowserforcePlugin { +export class CustomerPortalAvailableCustomObjects extends BrowserforcePlugin { public async retrieve(definition?) { const response = []; if (definition) { diff --git a/src/plugins/customer-portal/enabled/index.test.ts b/src/plugins/customer-portal/enabled/index.test.ts index 5100e7dd..d081e571 100644 --- a/src/plugins/customer-portal/enabled/index.test.ts +++ b/src/plugins/customer-portal/enabled/index.test.ts @@ -1,5 +1,5 @@ import * as assert from 'assert'; -import CustomerPortalEnabled from '.'; +import { CustomerPortalEnable as CustomerPortalEnabled } from '.'; const tests = [ { diff --git a/src/plugins/customer-portal/enabled/index.ts b/src/plugins/customer-portal/enabled/index.ts index a0fda96c..987db790 100644 --- a/src/plugins/customer-portal/enabled/index.ts +++ b/src/plugins/customer-portal/enabled/index.ts @@ -8,7 +8,7 @@ const SELECTORS = { SAVE_BUTTON: 'input[name="save"]' }; -export default class CustomerPortalEnable extends BrowserforcePlugin { +export class CustomerPortalEnable extends BrowserforcePlugin { public async retrieve(definition?) { const page = await this.browserforce.openPage(PATHS.EDIT_VIEW, { waitUntil: ['load', 'domcontentloaded', 'networkidle0'] diff --git a/src/plugins/customer-portal/index.e2e-spec.ts b/src/plugins/customer-portal/index.e2e-spec.ts index ed772792..54543900 100644 --- a/src/plugins/customer-portal/index.e2e-spec.ts +++ b/src/plugins/customer-portal/index.e2e-spec.ts @@ -1,9 +1,9 @@ import * as assert from 'assert'; import * as child from 'child_process'; import * as path from 'path'; -import CustomerPortalAvailableCustomObjects from './available-custom-objects'; -import CustomerPortalEnable from './enabled'; -import CustomerPortalSetup from './portals'; +import { CustomerPortalAvailableCustomObjects } from './available-custom-objects'; +import { CustomerPortalEnable } from './enabled'; +import { CustomerPortalSetup } from './portals'; describe(CustomerPortalEnable.name, function() { this.slow('30s'); diff --git a/src/plugins/customer-portal/index.test.ts b/src/plugins/customer-portal/index.test.ts index 7bdcb145..f020967c 100644 --- a/src/plugins/customer-portal/index.test.ts +++ b/src/plugins/customer-portal/index.test.ts @@ -1,5 +1,5 @@ import * as assert from 'assert'; -import CustomerPortal from '.'; +import { CustomerPortal } from '.'; const tests = [ { diff --git a/src/plugins/customer-portal/index.ts b/src/plugins/customer-portal/index.ts index 1a519bc5..55e16563 100644 --- a/src/plugins/customer-portal/index.ts +++ b/src/plugins/customer-portal/index.ts @@ -1,10 +1,10 @@ import { BrowserforcePlugin } from '../../plugin'; import { removeEmptyValues } from '../utils'; -import CustomerPortalAvailableCustomObjects from './available-custom-objects'; -import CustomerPortalEnable from './enabled'; -import CustomerPortalSetup from './portals'; +import { CustomerPortalAvailableCustomObjects } from './available-custom-objects'; +import { CustomerPortalEnable } from './enabled'; +import { CustomerPortalSetup } from './portals'; -export default class CustomerPortal extends BrowserforcePlugin { +export class CustomerPortal extends BrowserforcePlugin { public async retrieve(definition?) { const pluginEnable = new CustomerPortalEnable(this.browserforce, this.org); const response = { diff --git a/src/plugins/customer-portal/portals/index.test.ts b/src/plugins/customer-portal/portals/index.test.ts index 08088925..4c512610 100644 --- a/src/plugins/customer-portal/portals/index.test.ts +++ b/src/plugins/customer-portal/portals/index.test.ts @@ -1,5 +1,5 @@ import * as assert from 'assert'; -import CustomerPortalSetup from '.'; +import { CustomerPortalSetup } from '.'; const tests = [ { diff --git a/src/plugins/customer-portal/portals/index.ts b/src/plugins/customer-portal/portals/index.ts index 52d8b0fb..19e5b6d6 100644 --- a/src/plugins/customer-portal/portals/index.ts +++ b/src/plugins/customer-portal/portals/index.ts @@ -22,7 +22,7 @@ const SELECTORS = { PORTAL_PROFILE_MEMBERSHIP_CHECKBOXES: 'td.dataCell input' }; -export default class CustomerPortalSetup extends BrowserforcePlugin { +export class CustomerPortalSetup extends BrowserforcePlugin { public async retrieve(definition?) { const page = await this.browserforce.openPage(PATHS.LIST_VIEW); await page.waitForXPath(SELECTORS.LIST_VIEW_PORTAL_LINKS_XPATH); diff --git a/src/plugins/defer-sharing-calculation/index.e2e-spec.ts b/src/plugins/defer-sharing-calculation/index.e2e-spec.ts index ab1fd2d1..8d697329 100644 --- a/src/plugins/defer-sharing-calculation/index.e2e-spec.ts +++ b/src/plugins/defer-sharing-calculation/index.e2e-spec.ts @@ -1,7 +1,7 @@ import * as assert from 'assert'; import * as child from 'child_process'; import * as path from 'path'; -import DeferSharingCalculation from '.'; +import { DeferSharingCalculation } from '.'; describe(DeferSharingCalculation.name, function() { this.slow('30s'); diff --git a/src/plugins/defer-sharing-calculation/index.ts b/src/plugins/defer-sharing-calculation/index.ts index 5c5ddfe1..20ce2d67 100644 --- a/src/plugins/defer-sharing-calculation/index.ts +++ b/src/plugins/defer-sharing-calculation/index.ts @@ -9,7 +9,7 @@ const SELECTORS = { RECALCULATE_BUTTON: 'input[name="rule_recalc"]' }; -export default class DeferSharingCalculation extends BrowserforcePlugin { +export class DeferSharingCalculation extends BrowserforcePlugin { public async retrieve(definition?) { const page = await this.browserforce.openPage(PATHS.BASE); await page.waitForSelector(SELECTORS.SUSPEND_BUTTON); diff --git a/src/plugins/density-settings/index.e2e-spec.ts b/src/plugins/density-settings/index.e2e-spec.ts index 5d1d7b9e..e093ca7b 100644 --- a/src/plugins/density-settings/index.e2e-spec.ts +++ b/src/plugins/density-settings/index.e2e-spec.ts @@ -1,7 +1,7 @@ import * as assert from 'assert'; import * as child from 'child_process'; import * as path from 'path'; -import DensitySettings from '.'; +import { DensitySettings } from '.'; describe(DensitySettings.name, function() { this.slow('30s'); diff --git a/src/plugins/density-settings/index.ts b/src/plugins/density-settings/index.ts index 28366452..56d2ab0f 100644 --- a/src/plugins/density-settings/index.ts +++ b/src/plugins/density-settings/index.ts @@ -9,7 +9,7 @@ const SELECTORS = { 'pierce/one-density-visual-picker one-density-visual-picker-item input' }; -export default class DensitySettings extends BrowserforcePlugin { +export class DensitySettings extends BrowserforcePlugin { public async retrieve() { const page = await this.browserforce.openPage(PATHS.BASE, { waitUntil: ['load', 'domcontentloaded', 'networkidle0'] diff --git a/src/plugins/high-velocity-sales-settings/index.e2e-spec.ts b/src/plugins/high-velocity-sales-settings/index.e2e-spec.ts index 8abe6eed..b27ca965 100644 --- a/src/plugins/high-velocity-sales-settings/index.e2e-spec.ts +++ b/src/plugins/high-velocity-sales-settings/index.e2e-spec.ts @@ -1,7 +1,7 @@ import * as assert from 'assert'; import * as child from 'child_process'; import * as path from 'path'; -import HighVelocitySalesSettings from '.'; +import { HighVelocitySalesSettings } from '.'; describe.skip(HighVelocitySalesSettings.name, function() { this.slow('30s'); diff --git a/src/plugins/high-velocity-sales-settings/index.ts b/src/plugins/high-velocity-sales-settings/index.ts index 2234deb7..377f6cfc 100644 --- a/src/plugins/high-velocity-sales-settings/index.ts +++ b/src/plugins/high-velocity-sales-settings/index.ts @@ -4,7 +4,7 @@ import { HighVelocitySalesSetupPage } from './page'; const MSG_NOT_AVAILABLE = `HighVelocitySales is not available in this organization. Please add 'HighVelocitySales' to your Scratch Org Features or purchase a license.`; -export default class HighVelocitySalesSettings extends BrowserforcePlugin { +export class HighVelocitySalesSettings extends BrowserforcePlugin { public async retrieve(definition?) { const conn = this.org.getConnection(); const result = { setUpAndEnable: false }; diff --git a/src/plugins/home-page-layouts/index.e2e-spec.ts b/src/plugins/home-page-layouts/index.e2e-spec.ts index 2ac85776..b5079763 100644 --- a/src/plugins/home-page-layouts/index.e2e-spec.ts +++ b/src/plugins/home-page-layouts/index.e2e-spec.ts @@ -1,7 +1,7 @@ import * as assert from 'assert'; import * as child from 'child_process'; import * as path from 'path'; -import HomePageLayouts from '.'; +import { HomePageLayouts } from '.'; describe(HomePageLayouts.name, function() { this.slow('30s'); diff --git a/src/plugins/home-page-layouts/index.ts b/src/plugins/home-page-layouts/index.ts index fe7152fa..06426ca5 100644 --- a/src/plugins/home-page-layouts/index.ts +++ b/src/plugins/home-page-layouts/index.ts @@ -20,7 +20,7 @@ interface HomePageLayoutRecord { Name: string; } -export default class HomePageLayouts extends BrowserforcePlugin { +export class HomePageLayouts extends BrowserforcePlugin { public async retrieve(definition?) { const page = await this.browserforce.openPage(PATHS.BASE); await page.waitForSelector(SELECTORS.BASE); diff --git a/src/plugins/index.ts b/src/plugins/index.ts index ed291a7f..188eac2f 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -1,29 +1,28 @@ -import * as activitySettings from './activity-settings'; -import * as communities from './communities'; -import * as customerPortal from './customer-portal'; -import * as deferSharingCalculation from './defer-sharing-calculation'; -import * as densitySettings from './density-settings'; -import * as homePageLayouts from './home-page-layouts'; -import * as lightningExperienceSettings from './lightning-experience-settings'; -import * as reportsAndDashboards from './reports-and-dashboards'; -import * as salesforceToSalesforce from './salesforce-to-salesforce'; -import * as security from './security'; -import * as picklists from './picklists'; -import * as recordTypes from './record-types'; - -import * as highVelocitySalesSettings from './high-velocity-sales-settings'; +import { ActivitySettings as activitySettings } from './activity-settings'; +import { Communities as communities } from './communities'; +import { CustomerPortal as customerPortal } from './customer-portal'; +import { DeferSharingCalculation as deferSharingCalculation } from './defer-sharing-calculation'; +import { DensitySettings as densitySettings } from './density-settings'; +import { HighVelocitySalesSettings as highVelocitySalesSettings } from './high-velocity-sales-settings'; +import { HomePageLayouts as homePageLayouts } from './home-page-layouts'; +import { LightningExperienceSettings as lightningExperienceSettings } from './lightning-experience-settings'; +import { Picklists as picklists } from './picklists'; +import { RecordTypes as recordTypes } from './record-types'; +import { ReportsAndDashboards as reportsAndDashboards } from './reports-and-dashboards'; +import { SalesforceToSalesforce as salesforceToSalesforce } from './salesforce-to-salesforce'; +import { Security as security } from './security'; export { - highVelocitySalesSettings, - recordTypes, - picklists, - deferSharingCalculation, - lightningExperienceSettings, activitySettings, communities, customerPortal, + deferSharingCalculation, densitySettings, + highVelocitySalesSettings, homePageLayouts, + lightningExperienceSettings, + picklists, + recordTypes, reportsAndDashboards, salesforceToSalesforce, security diff --git a/src/plugins/lightning-experience-settings/index.e2e-spec.ts b/src/plugins/lightning-experience-settings/index.e2e-spec.ts index 886043a5..0e1c3813 100644 --- a/src/plugins/lightning-experience-settings/index.e2e-spec.ts +++ b/src/plugins/lightning-experience-settings/index.e2e-spec.ts @@ -1,7 +1,7 @@ import * as assert from 'assert'; import * as child from 'child_process'; import * as path from 'path'; -import LightningExperienceSettings from '.'; +import { LightningExperienceSettings } from '.'; describe(LightningExperienceSettings.name, function() { this.slow('30s'); diff --git a/src/plugins/lightning-experience-settings/index.ts b/src/plugins/lightning-experience-settings/index.ts index 34dd29e4..028ba750 100644 --- a/src/plugins/lightning-experience-settings/index.ts +++ b/src/plugins/lightning-experience-settings/index.ts @@ -11,7 +11,7 @@ const SELECTORS = { STATES: `${THEME_ROW_SELECTOR} > td:nth-child(6) > lightning-primitive-cell-factory` }; -export default class LightningExperienceSettings extends BrowserforcePlugin { +export class LightningExperienceSettings extends BrowserforcePlugin { public async retrieve() { const page = await this.browserforce.openPage(PATHS.BASE, { waitUntil: ['load', 'domcontentloaded', 'networkidle0'] diff --git a/src/plugins/picklists/field-dependencies/index.ts b/src/plugins/picklists/field-dependencies/index.ts index 547f6b7f..036be4f8 100644 --- a/src/plugins/picklists/field-dependencies/index.ts +++ b/src/plugins/picklists/field-dependencies/index.ts @@ -1,7 +1,7 @@ import { BrowserforcePlugin } from '../../../plugin'; import { FieldDependencyPage, NewFieldDependencyPage } from './pages'; -export default class FieldDependencies extends BrowserforcePlugin { +export class FieldDependencies extends BrowserforcePlugin { public async retrieve(definition?) { const conn = this.org.getConnection(); const dependentFieldNames = definition.map( diff --git a/src/plugins/picklists/index.e2e-spec.ts b/src/plugins/picklists/index.e2e-spec.ts index c9c1742f..3c24ba33 100644 --- a/src/plugins/picklists/index.e2e-spec.ts +++ b/src/plugins/picklists/index.e2e-spec.ts @@ -1,8 +1,8 @@ import * as assert from 'assert'; import * as child from 'child_process'; import * as path from 'path'; -import Picklists from '.'; -import FieldDependencies from './field-dependencies'; +import { Picklists } from '.'; +import { FieldDependencies } from './field-dependencies'; describe(Picklists.name, function() { this.slow('30s'); diff --git a/src/plugins/picklists/index.ts b/src/plugins/picklists/index.ts index 5e346023..9cda7bb5 100644 --- a/src/plugins/picklists/index.ts +++ b/src/plugins/picklists/index.ts @@ -2,7 +2,7 @@ import { FileProperties } from 'jsforce'; import { ensureArray } from '../../jsforce-utils'; import { BrowserforcePlugin } from '../../plugin'; import { removeEmptyValues } from '../utils'; -import FieldDependencies from './field-dependencies'; +import { FieldDependencies } from './field-dependencies'; import { PicklistPage, DefaultPicklistAddPage, @@ -10,7 +10,7 @@ import { } from './pages'; import { determineStandardValueSetEditUrl } from './standard-value-set'; -export default class Picklists extends BrowserforcePlugin { +export class Picklists extends BrowserforcePlugin { public async retrieve(definition?) { const conn = this.org.getConnection(); const result = { picklistValues: [], fieldDependencies: [] }; diff --git a/src/plugins/record-types/index.e2e-spec.ts b/src/plugins/record-types/index.e2e-spec.ts index 47228634..05c3fbcf 100644 --- a/src/plugins/record-types/index.e2e-spec.ts +++ b/src/plugins/record-types/index.e2e-spec.ts @@ -1,7 +1,7 @@ import * as assert from 'assert'; import * as child from 'child_process'; import * as path from 'path'; -import RecordTypes from '.'; +import { RecordTypes } from '.'; describe(RecordTypes.name, function() { this.slow('30s'); diff --git a/src/plugins/record-types/index.ts b/src/plugins/record-types/index.ts index fedefc73..d61dd09b 100644 --- a/src/plugins/record-types/index.ts +++ b/src/plugins/record-types/index.ts @@ -2,7 +2,7 @@ import { Connection } from '@salesforce/command/node_modules/@salesforce/core'; import { BrowserforcePlugin } from '../../plugin'; import { RecordTypePage } from './pages'; -export default class RecordTypes extends BrowserforcePlugin { +export class RecordTypes extends BrowserforcePlugin { public async retrieve(definition?) { const conn = this.org.getConnection(); const response = { diff --git a/src/plugins/reports-and-dashboards/folder-sharing/index.ts b/src/plugins/reports-and-dashboards/folder-sharing/index.ts index 35a03f02..a52c1e7e 100644 --- a/src/plugins/reports-and-dashboards/folder-sharing/index.ts +++ b/src/plugins/reports-and-dashboards/folder-sharing/index.ts @@ -9,7 +9,7 @@ const SELECTORS = { SAVE_BUTTON: 'input[id="saveButton"]' }; -export default class FolderSharing extends BrowserforcePlugin { +export class FolderSharing extends BrowserforcePlugin { public async retrieve(definition?) { const response = { enableEnhancedFolderSharing: true diff --git a/src/plugins/reports-and-dashboards/index.ts b/src/plugins/reports-and-dashboards/index.ts index 58bd1794..caff4983 100644 --- a/src/plugins/reports-and-dashboards/index.ts +++ b/src/plugins/reports-and-dashboards/index.ts @@ -1,8 +1,8 @@ import { BrowserforcePlugin } from '../../plugin'; import { removeEmptyValues } from '../utils'; -import FolderSharing from './folder-sharing'; +import { FolderSharing } from './folder-sharing'; -export default class ReportsAndDashboards extends BrowserforcePlugin { +export class ReportsAndDashboards extends BrowserforcePlugin { public async retrieve(definition?) { const response = { folderSharing: {} diff --git a/src/plugins/salesforce-to-salesforce/index.e2e-spec.ts b/src/plugins/salesforce-to-salesforce/index.e2e-spec.ts index 7d33c2b5..6b80b5f2 100644 --- a/src/plugins/salesforce-to-salesforce/index.e2e-spec.ts +++ b/src/plugins/salesforce-to-salesforce/index.e2e-spec.ts @@ -1,7 +1,7 @@ import * as assert from 'assert'; import * as child from 'child_process'; import * as path from 'path'; -import SalesforceToSalesforce from '.'; +import { SalesforceToSalesforce } from '.'; describe(SalesforceToSalesforce.name, function() { this.slow('30s'); diff --git a/src/plugins/salesforce-to-salesforce/index.ts b/src/plugins/salesforce-to-salesforce/index.ts index dad0d47e..a3efc394 100644 --- a/src/plugins/salesforce-to-salesforce/index.ts +++ b/src/plugins/salesforce-to-salesforce/index.ts @@ -10,7 +10,7 @@ const SELECTORS = { SAVE_BUTTON: 'input[name="save"]' }; -export default class SalesforceToSalesforce extends BrowserforcePlugin { +export class SalesforceToSalesforce extends BrowserforcePlugin { public async retrieve() { const page = await this.browserforce.openPage(PATHS.BASE); await page.waitForSelector(SELECTORS.BASE); diff --git a/src/plugins/security/certificate-and-key-management/index.ts b/src/plugins/security/certificate-and-key-management/index.ts index b9121ac0..689cdcff 100644 --- a/src/plugins/security/certificate-and-key-management/index.ts +++ b/src/plugins/security/certificate-and-key-management/index.ts @@ -24,7 +24,7 @@ interface CertificateRecord { KeySize: string; } -export default class CertificateAndKeyManagement extends BrowserforcePlugin { +export class CertificateAndKeyManagement extends BrowserforcePlugin { public async retrieve(definition?) { const response = { certificates: [], importFromKeystore: [] }; if (definition && definition.certificates) { diff --git a/src/plugins/security/identity-provider/index.ts b/src/plugins/security/identity-provider/index.ts index dbc6610b..ee0d14f1 100644 --- a/src/plugins/security/identity-provider/index.ts +++ b/src/plugins/security/identity-provider/index.ts @@ -21,7 +21,7 @@ interface CertificateRecord { NamespacePrefix: string; } -export default class IdentityProvider extends BrowserforcePlugin { +export class IdentityProvider extends BrowserforcePlugin { public async retrieve(definition?) { const page = await this.browserforce.openPage(PATHS.EDIT_VIEW); await page.waitForSelector(SELECTORS.EDIT_BUTTON); diff --git a/src/plugins/security/index.e2e-spec.ts b/src/plugins/security/index.e2e-spec.ts index 7411d064..551755ee 100644 --- a/src/plugins/security/index.e2e-spec.ts +++ b/src/plugins/security/index.e2e-spec.ts @@ -1,8 +1,8 @@ import * as assert from 'assert'; import * as child from 'child_process'; import * as path from 'path'; -import CertificateAndKeyManagement from './certificate-and-key-management'; -import IdentityProvider from './identity-provider'; +import { CertificateAndKeyManagement } from './certificate-and-key-management'; +import { IdentityProvider } from './identity-provider'; describe(`${CertificateAndKeyManagement.name} and ${IdentityProvider.name}`, function() { this.slow('30s'); diff --git a/src/plugins/security/index.ts b/src/plugins/security/index.ts index 3cc8408e..60d96091 100644 --- a/src/plugins/security/index.ts +++ b/src/plugins/security/index.ts @@ -1,11 +1,11 @@ import { BrowserforcePlugin } from '../../plugin'; import { removeEmptyValues } from '../utils'; -import CertificateAndKeyManagement from './certificate-and-key-management'; -import IdentityProvider from './identity-provider'; -import LoginAccessPolicies from './login-access-policies'; -import Sharing from './sharing'; +import { CertificateAndKeyManagement } from './certificate-and-key-management'; +import { IdentityProvider } from './identity-provider'; +import { LoginAccessPolicies } from './login-access-policies'; +import { Sharing } from './sharing'; -export default class Security extends BrowserforcePlugin { +export class Security extends BrowserforcePlugin { public async retrieve(definition?) { const response = { certificateAndKeyManagement: {}, diff --git a/src/plugins/security/login-access-policies/index.e2e-spec.ts b/src/plugins/security/login-access-policies/index.e2e-spec.ts index e56a9a40..b589e72a 100644 --- a/src/plugins/security/login-access-policies/index.e2e-spec.ts +++ b/src/plugins/security/login-access-policies/index.e2e-spec.ts @@ -1,7 +1,7 @@ import * as assert from 'assert'; import * as child from 'child_process'; import * as path from 'path'; -import LoginAccessPolicies from '.'; +import { LoginAccessPolicies } from '.'; describe(LoginAccessPolicies.name, function() { this.slow('30s'); diff --git a/src/plugins/security/login-access-policies/index.ts b/src/plugins/security/login-access-policies/index.ts index 1f3a968b..e2fec6f0 100644 --- a/src/plugins/security/login-access-policies/index.ts +++ b/src/plugins/security/login-access-policies/index.ts @@ -9,7 +9,7 @@ const SELECTORS = { SAVE_BUTTON: 'input[id$=":save"]' }; -export default class LoginAccessPolicies extends BrowserforcePlugin { +export class LoginAccessPolicies extends BrowserforcePlugin { public async retrieve(definition?) { const page = await this.browserforce.openPage(PATHS.BASE); await page.waitForSelector(SELECTORS.ENABLED); diff --git a/src/plugins/security/sharing/index.e2e-spec.ts b/src/plugins/security/sharing/index.e2e-spec.ts index 94d2ebba..128799c1 100644 --- a/src/plugins/security/sharing/index.e2e-spec.ts +++ b/src/plugins/security/sharing/index.e2e-spec.ts @@ -1,7 +1,7 @@ import * as assert from 'assert'; import * as child from 'child_process'; import * as path from 'path'; -import Sharing from '.'; +import { Sharing } from '.'; describe.skip(Sharing.name, function() { this.slow('30s'); diff --git a/src/plugins/security/sharing/index.ts b/src/plugins/security/sharing/index.ts index 0e64d64a..88445764 100644 --- a/src/plugins/security/sharing/index.ts +++ b/src/plugins/security/sharing/index.ts @@ -11,7 +11,7 @@ const SELECTORS = { MODAL_DIALOG: 'Modal.confirm' }; -export default class Sharing extends BrowserforcePlugin { +export class Sharing extends BrowserforcePlugin { public async retrieve(definition?) { const page = await this.browserforce.openPage(PATHS.BASE); await page.waitForSelector(SELECTORS.EXTERNAL_SHARING_MODEL_BUTTON); diff --git a/test/browserforce.e2e-spec.ts b/test/browserforce.e2e-spec.ts index 220eb3c5..6d387c23 100644 --- a/test/browserforce.e2e-spec.ts +++ b/test/browserforce.e2e-spec.ts @@ -1,6 +1,6 @@ import { core, UX } from '@salesforce/command'; import * as assert from 'assert'; -import Browserforce from '../src/browserforce'; +import { Browserforce } from '../src/browserforce'; describe('Browser', function() { this.slow('30s'); diff --git a/test/config-parser.test.ts b/test/config-parser.test.ts index 2562fcb1..842bd0f5 100644 --- a/test/config-parser.test.ts +++ b/test/config-parser.test.ts @@ -1,5 +1,5 @@ import * as assert from 'assert'; -import ConfigParser from '../src/config-parser'; +import { ConfigParser } from '../src/config-parser'; import * as DRIVERS from '../src/plugins'; describe('ConfigParser', () => { @@ -15,7 +15,7 @@ describe('ConfigParser', () => { } }; const result = ConfigParser.parse(DRIVERS, definition); - assert.deepStrictEqual(result[0].Driver.default.name, 'Security'); + assert.deepStrictEqual(result[0].Driver.name, 'Security'); }); it('should fail parsing an invalid definition file', () => { const definition = { From 23ab3a4704bdfc0edf0aab656bd87ca2e47453da Mon Sep 17 00:00:00 2001 From: Matthias Rolke Date: Sat, 12 Jun 2021 23:59:31 +0200 Subject: [PATCH 5/6] refactor: add types --- _templates/plugin/new/index.ejs.t | 6 +- src/browserforce-command.ts | 20 +++-- src/browserforce.ts | 50 +++++++---- src/commands/browserforce/apply.ts | 2 +- src/commands/browserforce/plan.ts | 2 +- src/config-parser.ts | 18 +++- src/jsforce-utils.ts | 6 +- src/plugin.ts | 6 +- src/plugins/activity-settings/index.ts | 10 ++- src/plugins/communities/index.ts | 18 ++-- .../available-custom-objects/index.test.ts | 4 +- .../available-custom-objects/index.ts | 31 ++++--- src/plugins/customer-portal/enabled/index.ts | 11 ++- src/plugins/customer-portal/index.test.ts | 6 +- src/plugins/customer-portal/index.ts | 27 ++++-- .../customer-portal/portals/index.test.ts | 20 ++--- src/plugins/customer-portal/portals/index.ts | 84 ++++++++++++------- .../defer-sharing-calculation/index.ts | 9 +- src/plugins/density-settings/index.ts | 19 ++++- .../high-velocity-sales-settings/index.ts | 14 +++- .../high-velocity-sales-settings/page.ts | 5 +- src/plugins/home-page-layouts/index.ts | 16 +++- .../lightning-experience-settings/index.ts | 21 +++-- .../picklists/field-dependencies/index.ts | 12 ++- .../picklists/field-dependencies/pages.ts | 9 +- src/plugins/picklists/index.ts | 38 +++++++-- src/plugins/picklists/pages.ts | 40 +++++---- src/plugins/record-types/index.ts | 13 ++- src/plugins/record-types/pages.ts | 18 ++-- .../folder-sharing/index.ts | 11 ++- src/plugins/reports-and-dashboards/index.ts | 16 ++-- src/plugins/salesforce-to-salesforce/index.ts | 21 +++-- .../certificate-and-key-management/index.ts | 39 ++++++--- .../security/identity-provider/index.ts | 12 ++- src/plugins/security/index.ts | 37 +++++--- .../security/login-access-policies/index.ts | 11 ++- src/plugins/security/sharing/index.ts | 9 +- src/plugins/utils.ts | 9 +- test/config-parser.test.ts | 4 +- 39 files changed, 480 insertions(+), 224 deletions(-) diff --git a/_templates/plugin/new/index.ejs.t b/_templates/plugin/new/index.ejs.t index bc1a0c65..9a905fb5 100644 --- a/_templates/plugin/new/index.ejs.t +++ b/_templates/plugin/new/index.ejs.t @@ -14,7 +14,7 @@ const SELECTORS = { }; export class <%= h.changeCase.pascalCase(name) %> extends BrowserforcePlugin { - public async retrieve(definition?) { + public async retrieve(definition?: Config): Promise { const page = await this.browserforce.openPage(PATHS.BASE); await page.waitForSelector(SELECTORS.ENABLED); const response = { @@ -26,12 +26,12 @@ export class <%= h.changeCase.pascalCase(name) %> extends BrowserforcePlugin { return response; } - public async apply(config) { + public async apply(config: Config): Promise { const page = await this.browserforce.openPage(PATHS.BASE); await page.waitForSelector(SELECTORS.ENABLED); await page.$eval( SELECTORS.ENABLED, - (e: HTMLInputElement, v) => { + (e: HTMLInputElement, v: boolean) => { e.checked = v; }, config.enabled diff --git a/src/browserforce-command.ts b/src/browserforce-command.ts index 0e7af6e7..6a7dac81 100644 --- a/src/browserforce-command.ts +++ b/src/browserforce-command.ts @@ -1,4 +1,5 @@ import { core, flags, SfdxCommand } from '@salesforce/command'; +import { promises } from 'fs'; import * as path from 'path'; import { Browserforce } from './browserforce'; import { ConfigParser } from './config-parser'; @@ -31,13 +32,21 @@ export class BrowserforceCommand extends SfdxCommand { }; protected bf: Browserforce; + // eslint-disable-next-line @typescript-eslint/no-explicit-any protected settings: any[]; - public async init() { + public async init(): Promise { await super.init(); - const definition = await core.fs.readJson( - path.resolve(this.flags.definitionfile) + const definitionFileData = await promises.readFile( + path.resolve(this.flags.definitionfile), + 'utf8' ); + let definition; + try { + definition = JSON.parse(definitionFileData); + } catch (err) { + throw new Error('Failed parsing definitionfile'); + } // TODO: use require.resolve to dynamically load plugins from npm packages this.settings = ConfigParser.parse(DRIVERS, definition); this.bf = new Browserforce(this.org, this.ux.cli); @@ -46,11 +55,12 @@ export class BrowserforceCommand extends SfdxCommand { this.ux.stopSpinner(); } - public async run(): Promise { + public async run(): Promise { throw new Error('BrowserforceCommand should not be run directly'); } - public async finally(err: any) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public async finally(err: Error): Promise { this.ux.stopSpinner(); if (this.bf) { this.ux.startSpinner('logging out'); diff --git a/src/browserforce.ts b/src/browserforce.ts index 78baba6f..020d958d 100644 --- a/src/browserforce.ts +++ b/src/browserforce.ts @@ -1,6 +1,6 @@ import { core } from '@salesforce/command'; import pRetry, { AbortError } from 'p-retry'; -import { Browser, launch, Page } from 'puppeteer'; +import { Browser, Frame, launch, Page, WaitForOptions } from 'puppeteer'; import * as querystring from 'querystring'; import { parse, URL } from 'url'; @@ -10,17 +10,26 @@ const ERROR_DIV_SELECTOR = '#errorTitle'; const ERROR_DIVS_SELECTOR = 'div.errorMsg'; const VF_IFRAME_SELECTOR = 'iframe[name^=vfFrameId]'; +export interface Logger { + debug(message?: unknown, ...optionalParams: unknown[]): void; + error(message?: unknown, ...optionalParams: unknown[]): void; + info(message?: unknown, ...optionalParams: unknown[]): void; + trace(message?: unknown, ...optionalParams: unknown[]): void; + warn(message?: unknown, ...optionalParams: unknown[]): void; + [m: string]: unknown; +} + export class Browserforce { public org: core.Org; - public logger: core.Logger; + public logger: Logger; public browser: Browser; public page: Page; - constructor(org, logger?) { + constructor(org: core.Org, logger?: Logger) { this.org = org; this.logger = logger; } - public async login() { + public async login(): Promise { this.browser = await launch({ args: [ '--no-sandbox', @@ -39,12 +48,12 @@ export class Browserforce { return this; } - public async logout() { + public async logout(): Promise { await this.browser.close(); return this; } - public async resolveDomains() { + public async resolveDomains(): Promise { // resolve ip addresses of both LEX and classic domains const salesforceUrls = [ this.getInstanceUrl(), @@ -58,12 +67,15 @@ export class Browserforce { } } - public async throwPageErrors(page) { - return await throwPageErrors(page); + public async throwPageErrors(page: Page): Promise { + await throwPageErrors(page); } // path instead of url - public async openPage(urlPath, options?) { + public async openPage( + urlPath: string, + options?: WaitForOptions + ): Promise { let page; const result = await pRetry( async () => { @@ -101,7 +113,10 @@ export class Browserforce { this.logger.warn('trying frontdoor workaround...'); } // try opening page directly without frontdoor as login might have already been successful - urlPath = querystring.parse(parsedUrl.query).retURL; + const qsUrl = querystring.parse(parsedUrl.query); + urlPath = Array.isArray(qsUrl.retURL) + ? qsUrl.retURL[0] + : qsUrl.retURL; throw new Error('frontdoor error'); } else { // the url is not as expected @@ -151,14 +166,17 @@ export class Browserforce { // If LEX is enabled, the classic url will be opened in an iframe. // Wait for either the selector in the page or in the iframe. // returns the page or the frame - public async waitForSelectorInFrameOrPage(page, selector) { + public async waitForSelectorInFrameOrPage( + page: Page, + selector: string + ): Promise { await page.waitForSelector( `pierce/force-aloha-page ${VF_IFRAME_SELECTOR}, ${VF_IFRAME_SELECTOR}, ${selector}` ); const frameElementHandle = await page.$( `pierce/force-aloha-page ${VF_IFRAME_SELECTOR}, ${VF_IFRAME_SELECTOR}` ); - let frameOrPage = page; + let frameOrPage: Page | Frame = page; if (frameElementHandle) { const frame = await frameElementHandle.contentFrame(); if (frame) { @@ -169,7 +187,7 @@ export class Browserforce { return frameOrPage; } - public getMyDomain() { + public getMyDomain(): string { const instanceUrl = this.getInstanceUrl(); // acme.my.salesforce.com // acme--.csN.my.salesforce.com @@ -180,7 +198,7 @@ export class Browserforce { return null; } - public getInstanceDomain() { + public getInstanceDomain(): string { const instanceUrl = this.getInstanceUrl(); // csN.salesforce.com // acme--.csN.my.salesforce.com @@ -197,12 +215,12 @@ export class Browserforce { return null; } - public getInstanceUrl() { + public getInstanceUrl(): string { // sometimes the instanceUrl includes a trailing slash return this.org.getConnection().instanceUrl?.replace(/\/$/, ''); } - public getLightningUrl() { + public getLightningUrl(): string { const myDomain = this.getMyDomain(); const instanceDomain = this.getInstanceDomain(); const myDomainOrInstance = myDomain || instanceDomain; diff --git a/src/commands/browserforce/apply.ts b/src/commands/browserforce/apply.ts index 877f44b1..3a8ddf68 100644 --- a/src/commands/browserforce/apply.ts +++ b/src/commands/browserforce/apply.ts @@ -20,7 +20,7 @@ export default class BrowserforceApply extends BrowserforceCommand { ` ]; - public async run(): Promise { + public async run(): Promise { const logger = await core.Logger.root(); this.ux.log( `Applying definition file ${ diff --git a/src/commands/browserforce/plan.ts b/src/commands/browserforce/plan.ts index a4ba2d1f..3139c202 100644 --- a/src/commands/browserforce/plan.ts +++ b/src/commands/browserforce/plan.ts @@ -21,7 +21,7 @@ export default class BrowserforcePlanCommand extends BrowserforceCommand { ` ]; - public async run(): Promise { + public async run(): Promise { this.ux.log( `Generating plan with definition file ${ this.flags.definitionfile diff --git a/src/config-parser.ts b/src/config-parser.ts index 2cc2ac68..968b2c99 100644 --- a/src/config-parser.ts +++ b/src/config-parser.ts @@ -1,5 +1,21 @@ +import { BrowserforcePlugin } from './plugin'; + +type Drivers = { + [key: string]: unknown; +}; + +type Data = { + settings?: unknown; +}; + +type Config = { + Driver: typeof BrowserforcePlugin; + key: string; + value: unknown; +}; + export class ConfigParser { - public static parse(drivers, data) { + public static parse(drivers: Drivers, data: Data): Config[] { const settings = []; if (data && data.settings) { for (const driverName of Object.keys(data.settings)) { diff --git a/src/jsforce-utils.ts b/src/jsforce-utils.ts index 11c68596..608147fd 100644 --- a/src/jsforce-utils.ts +++ b/src/jsforce-utils.ts @@ -2,10 +2,10 @@ * workaround as the Metadata API (converted from XML) returns an object instead of an array of length 1 * @param prop result of a Metadata API call (array or object) */ -export function ensureArray( +export function ensureArray( // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - prop: any -): Array { + prop: T +): Array { if (Array.isArray(prop)) { return prop; } diff --git a/src/plugin.ts b/src/plugin.ts index f63fca9d..9f0f7de7 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -10,9 +10,9 @@ export abstract class BrowserforcePlugin { this.browserforce = browserforce; this.org = org; } - public abstract retrieve(definition?): Promise; - public diff(state, definition) { + public abstract retrieve(definition?: unknown): Promise; + public diff(state: unknown, definition: unknown): unknown { return jsonMergePatch.generate(state, definition); } - public abstract apply(plan: JSON): Promise; + public abstract apply(plan: unknown): Promise; } diff --git a/src/plugins/activity-settings/index.ts b/src/plugins/activity-settings/index.ts index ed427b09..8418985b 100644 --- a/src/plugins/activity-settings/index.ts +++ b/src/plugins/activity-settings/index.ts @@ -9,8 +9,12 @@ const SELECTORS = { SUBMIT_BUTTON: 'input[id="thePage:theForm:theBlock:buttons:submit"]' }; +type Config = { + allowUsersToRelateMultipleContactsToTasksAndEvents: boolean; +}; + export class ActivitySettings extends BrowserforcePlugin { - public async retrieve() { + public async retrieve(): Promise { const page = await this.browserforce.openPage(PATHS.BASE, { waitUntil: ['load', 'domcontentloaded', 'networkidle0'] }); @@ -24,7 +28,7 @@ export class ActivitySettings extends BrowserforcePlugin { return response; } - public async apply(config) { + public async apply(config: Config): Promise { if (config.allowUsersToRelateMultipleContactsToTasksAndEvents === false) { throw new Error( '`allowUsersToRelateMultipleContactsToTasksAndEvents` can only be disabled with help of the salesforce.com Support team' @@ -36,7 +40,7 @@ export class ActivitySettings extends BrowserforcePlugin { await page.waitForSelector(SELECTORS.MANY_WHO_PREF_INPUT); await page.$eval( SELECTORS.MANY_WHO_PREF_INPUT, - (e: HTMLInputElement, v) => { + (e: HTMLInputElement, v: boolean) => { e.checked = v; }, config.allowUsersToRelateMultipleContactsToTasksAndEvents diff --git a/src/plugins/communities/index.ts b/src/plugins/communities/index.ts index 145a5f0d..11899463 100644 --- a/src/plugins/communities/index.ts +++ b/src/plugins/communities/index.ts @@ -10,8 +10,13 @@ const SELECTORS = { SAVE_BUTTON: 'input[id$=":saveId"]' }; +type Config = { + enabled?: boolean; + domainName?: string; +}; + export class Communities extends BrowserforcePlugin { - public async retrieve() { + public async retrieve(): Promise { const page = await this.browserforce.openPage(PATHS.BASE, { waitUntil: ['load', 'domcontentloaded', 'networkidle0'] }); @@ -19,21 +24,20 @@ export class Communities extends BrowserforcePlugin { page, SELECTORS.BASE ); - const response = {}; + const response = { + enabled: true + }; const inputEnable = await frameOrPage.$(SELECTORS.ENABLE_CHECKBOX); if (inputEnable) { - response['enabled'] = await frameOrPage.$eval( + response.enabled = await frameOrPage.$eval( SELECTORS.ENABLE_CHECKBOX, (el: HTMLInputElement) => el.checked ); - } else { - // already enabled - response['enabled'] = true; } return response; } - public async apply(config) { + public async apply(config: Config): Promise { if (config.enabled === false) { throw new Error('`enabled` cannot be disabled once enabled'); } diff --git a/src/plugins/customer-portal/available-custom-objects/index.test.ts b/src/plugins/customer-portal/available-custom-objects/index.test.ts index 62ab31ba..1782c257 100644 --- a/src/plugins/customer-portal/available-custom-objects/index.test.ts +++ b/src/plugins/customer-portal/available-custom-objects/index.test.ts @@ -6,7 +6,7 @@ const tests = [ description: 'should only return necessary fields', source: [ { - id: 'p1', + _id: 'p1', name: 'Dummy', namespacePrefix: null, available: false @@ -20,7 +20,7 @@ const tests = [ ], expected: [ { - id: 'p1', + _id: 'p1', available: true } ] diff --git a/src/plugins/customer-portal/available-custom-objects/index.ts b/src/plugins/customer-portal/available-custom-objects/index.ts index 8e859dd2..65c88dfa 100644 --- a/src/plugins/customer-portal/available-custom-objects/index.ts +++ b/src/plugins/customer-portal/available-custom-objects/index.ts @@ -13,8 +13,17 @@ interface CustomObjectRecord { NamespacePrefix: string; } +export type Config = AvailableCustomObjectConfig[]; + +type AvailableCustomObjectConfig = { + name: string; + namespacePrefix?: string; + available: boolean; + _id?: string; +}; + export class CustomerPortalAvailableCustomObjects extends BrowserforcePlugin { - public async retrieve(definition?) { + public async retrieve(definition?: Config): Promise { const response = []; if (definition) { const availableCustomObjectList = definition @@ -39,10 +48,10 @@ export class CustomerPortalAvailableCustomObjects extends BrowserforcePlugin { page.url().indexOf('/one/one.app') >= 0 || page.url().indexOf('/lightning/') >= 0; const getObjectPageUrl = function(customObject, isLexUi = true) { - const classicUiPath = `${customObject.id}/e`; + const classicUiPath = `${customObject._id}/e`; if (isLexUi) { return `lightning/setup/ObjectManager/${ - customObject.id + customObject._id }/edit?nodeId=ObjectManager&address=${encodeURIComponent( `/${classicUiPath}` )}`; @@ -67,7 +76,7 @@ export class CustomerPortalAvailableCustomObjects extends BrowserforcePlugin { ); } const result = { - id: customObject.Id, + _id: customObject.Id, name: customObject.DeveloperName, namespacePrefix: customObject.NamespacePrefix }; @@ -91,7 +100,7 @@ export class CustomerPortalAvailableCustomObjects extends BrowserforcePlugin { return response; } - public diff(state, definition) { + public diff(state: Config, definition: Config): Config { const response = []; if (state && definition) { for (const availableCustomObject of definition) { @@ -106,8 +115,8 @@ export class CustomerPortalAvailableCustomObjects extends BrowserforcePlugin { ); }); // move id of existing object to new object to be retained and used - availableCustomObject.id = oldCustomObject.id; - delete oldCustomObject.id; + availableCustomObject._id = oldCustomObject._id; + delete oldCustomObject._id; const diff = jsonMergePatch.generate( oldCustomObject, availableCustomObject @@ -120,7 +129,7 @@ export class CustomerPortalAvailableCustomObjects extends BrowserforcePlugin { return response; } - public async apply(plan) { + public async apply(plan: Config): Promise { if (plan && plan.length) { const page = await this.browserforce.openPage('', { waitUntil: ['load', 'domcontentloaded', 'networkidle0'] @@ -130,12 +139,12 @@ export class CustomerPortalAvailableCustomObjects extends BrowserforcePlugin { page.url().indexOf('/one/one.app') >= 0 || page.url().indexOf('/lightning/') >= 0; const getObjectPageUrl = function(customObject, isLexUi = true) { - const classicUiPath = `${customObject.id}/e?options_9=${ + const classicUiPath = `${customObject._id}/e?options_9=${ customObject.available ? 1 : 0 - }&retURL=/${customObject.id}`; + }&retURL=/${customObject._id}`; if (isLexUi) { return `lightning/setup/ObjectManager/${ - customObject.id + customObject._id }/edit?nodeId=ObjectManager&address=${encodeURIComponent( `/${classicUiPath}` )}`; diff --git a/src/plugins/customer-portal/enabled/index.ts b/src/plugins/customer-portal/enabled/index.ts index 987db790..c29792f6 100644 --- a/src/plugins/customer-portal/enabled/index.ts +++ b/src/plugins/customer-portal/enabled/index.ts @@ -8,8 +8,11 @@ const SELECTORS = { SAVE_BUTTON: 'input[name="save"]' }; +export type Config = boolean; + export class CustomerPortalEnable extends BrowserforcePlugin { - public async retrieve(definition?) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public async retrieve(definition?: Config): Promise { const page = await this.browserforce.openPage(PATHS.EDIT_VIEW, { waitUntil: ['load', 'domcontentloaded', 'networkidle0'] }); @@ -21,13 +24,13 @@ export class CustomerPortalEnable extends BrowserforcePlugin { return response; } - public diff(state, definition) { + public diff(state: Config, definition: Config): Config { if (state !== definition) { return definition; } } - public async apply(plan) { + public async apply(plan: Config): Promise { if (plan === false) { throw new Error('`enabled` cannot be disabled once enabled'); } @@ -37,7 +40,7 @@ export class CustomerPortalEnable extends BrowserforcePlugin { await page.waitForSelector(SELECTORS.ENABLED); await page.$eval( SELECTORS.ENABLED, - (e: HTMLInputElement, v) => { + (e: HTMLInputElement, v: boolean) => { e.checked = v; }, plan diff --git a/src/plugins/customer-portal/index.test.ts b/src/plugins/customer-portal/index.test.ts index f020967c..854f161b 100644 --- a/src/plugins/customer-portal/index.test.ts +++ b/src/plugins/customer-portal/index.test.ts @@ -45,10 +45,10 @@ const tests = [ { name: 'Customer Portal Manager Standard', active: true, - id: 'a1' + _id: 'a1' } ], - id: 'p1' + _id: 'p1' } ] }, @@ -64,7 +64,7 @@ const tests = [ portals: [ { description: 'new description', - id: 'p1' + _id: 'p1' } ] } diff --git a/src/plugins/customer-portal/index.ts b/src/plugins/customer-portal/index.ts index 55e16563..d64fc3e3 100644 --- a/src/plugins/customer-portal/index.ts +++ b/src/plugins/customer-portal/index.ts @@ -1,11 +1,26 @@ import { BrowserforcePlugin } from '../../plugin'; import { removeEmptyValues } from '../utils'; -import { CustomerPortalAvailableCustomObjects } from './available-custom-objects'; -import { CustomerPortalEnable } from './enabled'; -import { CustomerPortalSetup } from './portals'; +import { + Config as CustomerPortalAvailableCustomObjectsConfig, + CustomerPortalAvailableCustomObjects +} from './available-custom-objects'; +import { + Config as CustomerPortalEnableConfig, + CustomerPortalEnable +} from './enabled'; +import { + Config as CustomerPortalSetupConfig, + CustomerPortalSetup +} from './portals'; + +type Config = { + enabled?: CustomerPortalEnableConfig; + portals?: CustomerPortalSetupConfig; + availableCustomObjects?: CustomerPortalAvailableCustomObjectsConfig; +}; export class CustomerPortal extends BrowserforcePlugin { - public async retrieve(definition?) { + public async retrieve(definition?: Config): Promise { const pluginEnable = new CustomerPortalEnable(this.browserforce, this.org); const response = { enabled: false, @@ -34,7 +49,7 @@ export class CustomerPortal extends BrowserforcePlugin { return response; } - public diff(state, definition) { + public diff(state: Config, definition: Config): Config { const pluginEnable = new CustomerPortalEnable(null, null); const pluginSetup = new CustomerPortalSetup(null, null); const pluginAvailableCustomObjects = new CustomerPortalAvailableCustomObjects( @@ -52,7 +67,7 @@ export class CustomerPortal extends BrowserforcePlugin { return removeEmptyValues(response); } - public async apply(config) { + public async apply(config: Config): Promise { if (config.enabled !== undefined) { const pluginEnable = new CustomerPortalEnable( this.browserforce, diff --git a/src/plugins/customer-portal/portals/index.test.ts b/src/plugins/customer-portal/portals/index.test.ts index 4c512610..fe4b3b93 100644 --- a/src/plugins/customer-portal/portals/index.test.ts +++ b/src/plugins/customer-portal/portals/index.test.ts @@ -6,7 +6,7 @@ const tests = [ description: 'should only return necessary portal fields', source: [ { - id: 'p1', + _id: 'p1', name: 'Customer Portal', description: 'Customer Portal', adminUser: 'User User', @@ -24,7 +24,7 @@ const tests = [ ], expected: [ { - id: 'p1', + _id: 'p1', description: 'new description' } ] @@ -41,10 +41,10 @@ const tests = [ { name: 'Customer Portal Manager Standard', active: true, - id: 'a1' + _id: 'a1' } ], - id: 'p1' + _id: 'p1' } ], target: [ @@ -60,10 +60,10 @@ const tests = [ ], expected: [ { - id: 'p1', + _id: 'p1', portalProfileMemberships: [ { - id: 'a1', + _id: 'a1', active: false } ] @@ -78,7 +78,7 @@ const tests = [ description: 'Customer Portal', adminUser: 'User User', portalProfileMemberships: [], - id: 'p1' + _id: 'p1' } ], target: [ @@ -89,7 +89,7 @@ const tests = [ ], expected: [ { - id: 'p1', + _id: 'p1', name: 'Foo Portal' } ] @@ -105,10 +105,10 @@ const tests = [ { name: 'Customer Portal Manager Standard', active: true, - id: 'a1' + _id: 'a1' } ], - id: 'p1' + _id: 'p1' } ], target: [ diff --git a/src/plugins/customer-portal/portals/index.ts b/src/plugins/customer-portal/portals/index.ts index 19e5b6d6..a8fdccd1 100644 --- a/src/plugins/customer-portal/portals/index.ts +++ b/src/plugins/customer-portal/portals/index.ts @@ -22,52 +22,74 @@ const SELECTORS = { PORTAL_PROFILE_MEMBERSHIP_CHECKBOXES: 'td.dataCell input' }; +export type Config = PortalConfig[]; + +type PortalConfig = { + adminUser?: string; + description?: string; + isSelfRegistrationActivated?: boolean; + name: string; + oldName?: string; + selfRegUserDefaultLicense?: string; + selfRegUserDefaultProfile?: string; + selfRegUserDefaultRole?: string; + portalProfileMemberships?: PortalProfileMembership[]; + _id?: string; +}; + +type PortalProfileMembership = { + name: string; + active: boolean; + _id?: string; +}; + export class CustomerPortalSetup extends BrowserforcePlugin { - public async retrieve(definition?) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public async retrieve(definition?: Config): Promise { const page = await this.browserforce.openPage(PATHS.LIST_VIEW); await page.waitForXPath(SELECTORS.LIST_VIEW_PORTAL_LINKS_XPATH); const customerPortalLinks = await page.$x( SELECTORS.LIST_VIEW_PORTAL_LINKS_XPATH ); - const response = await page.evaluate((...links) => { + const response: Config = await page.evaluate((...links) => { return links.map((a: HTMLAnchorElement) => { return { - id: a.pathname.split('/')[1], + _id: a.pathname.split('/')[1], name: a.text, portalProfileMemberships: [] }; }); }, ...customerPortalLinks); for (const portal of response) { - const portalPage = await this.browserforce.openPage(`${portal.id}/e`); + const portalPage = await this.browserforce.openPage(`${portal._id}/e`); await portalPage.waitForSelector(SELECTORS.PORTAL_DESCRIPTION); - portal['description'] = await portalPage.$eval( + portal.description = await portalPage.$eval( SELECTORS.PORTAL_DESCRIPTION, (el: HTMLInputElement) => el.value ); - portal['adminUser'] = await portalPage.$eval( + portal.adminUser = await portalPage.$eval( `#${SELECTORS.PORTAL_ADMIN_ID}`, (el: HTMLInputElement) => el.value ); - portal['isSelfRegistrationActivated'] = await portalPage.$eval( + portal.isSelfRegistrationActivated = await portalPage.$eval( `#${SELECTORS.PORTAL_IS_SELF_REGISTRATION_ACTIVATED_ID}`, (el: HTMLInputElement) => el.checked ); - portal['selfRegUserDefaultLicense'] = await portalPage.$eval( + portal.selfRegUserDefaultLicense = await portalPage.$eval( `#${SELECTORS.PORTAL_SELF_REG_USER_DEFAULT_LICENSE_ID}`, (el: HTMLSelectElement) => el.selectedOptions[0].text ); - portal['selfRegUserDefaultRole'] = await portalPage.$eval( + portal.selfRegUserDefaultRole = await portalPage.$eval( `#${SELECTORS.PORTAL_SELF_REG_USER_DEFAULT_ROLE_ID}`, (el: HTMLSelectElement) => el.selectedOptions[0].text ); - portal['selfRegUserDefaultProfile'] = await portalPage.$eval( + portal.selfRegUserDefaultProfile = await portalPage.$eval( `#${SELECTORS.PORTAL_SELF_REG_USER_DEFAULT_PROFILE_ID}`, (el: HTMLSelectElement) => el.selectedOptions[0].text ); // portalProfileMemberships const portalProfilePage = await this.browserforce.openPage( - `${PATHS.PORTAL_PROFILE_MEMBERSHIP}?portalId=${portal.id}&setupid=CustomerSuccessPortalSettings` + `${PATHS.PORTAL_PROFILE_MEMBERSHIP}?portalId=${portal._id}&setupid=CustomerSuccessPortalSettings` ); await portalProfilePage.waitForSelector(SELECTORS.PORTAL_ID); const profiles = await portalProfilePage.$$eval( @@ -82,7 +104,7 @@ export class CustomerPortalSetup extends BrowserforcePlugin { return inputs.map(input => { return { active: input.checked, - id: input.id + _id: input.id }; }); } @@ -92,15 +114,15 @@ export class CustomerPortalSetup extends BrowserforcePlugin { portalProfileMemberships.push({ name: profiles[i], active: checkboxes[i].active, - id: checkboxes[i].id + _id: checkboxes[i]._id }); } - portal['portalProfileMemberships'] = portalProfileMemberships; + portal.portalProfileMemberships = portalProfileMemberships; } return response; } - public diff(source, target) { + public diff(source: Config, target: Config): Config { const response = []; if (source && target) { for (const portal of target) { @@ -114,11 +136,11 @@ export class CustomerPortalSetup extends BrowserforcePlugin { `Portal with name '${portal.name} (oldName: ${portal.oldName})' not found. Setting up new Portals is not yet supported.` ); } - delete portal['oldName']; + delete portal.oldName; if (sourcePortal) { // move id of existing portal to new portal to be retained and used - portal.id = sourcePortal.id; - delete sourcePortal.id; + portal._id = sourcePortal._id; + delete sourcePortal._id; } if ( sourcePortal.portalProfileMemberships && @@ -131,15 +153,16 @@ export class CustomerPortalSetup extends BrowserforcePlugin { m => m.name === member.name ); if (sourceMember) { - member.id = sourceMember.id; - delete sourceMember.id; + member._id = sourceMember._id; + delete sourceMember._id; } else { throw new Error( `Could not find portal profile membership for '${member.name}'` ); } const membershipDiff = semanticallyCleanObject( - removeNullValues(jsonMergePatch.generate(sourceMember, member)) + removeNullValues(jsonMergePatch.generate(sourceMember, member)), + '_id' ); if (membershipDiff) { membershipResponse.push(membershipDiff); @@ -148,11 +171,12 @@ export class CustomerPortalSetup extends BrowserforcePlugin { delete sourcePortal.portalProfileMemberships; delete portal.portalProfileMemberships; if (membershipResponse.length) { - portal['portalProfileMemberships'] = membershipResponse; + portal.portalProfileMemberships = membershipResponse; } } const diff = semanticallyCleanObject( - removeNullValues(jsonMergePatch.generate(sourcePortal, portal)) + removeNullValues(jsonMergePatch.generate(sourcePortal, portal)), + '_id' ); if (diff) { response.push(diff); @@ -162,9 +186,9 @@ export class CustomerPortalSetup extends BrowserforcePlugin { return response; } - public async apply(config) { + public async apply(config: Config): Promise { for (const portal of config) { - if (portal.id) { + if (portal._id) { // everything that can be changed using the url const urlAttributes = {}; if (portal.name) { @@ -182,7 +206,7 @@ export class CustomerPortalSetup extends BrowserforcePlugin { ] = portal.isSelfRegistrationActivated ? 1 : 0; } const page = await this.browserforce.openPage( - `${portal.id}/e?${queryString.stringify(urlAttributes)}` + `${portal._id}/e?${queryString.stringify(urlAttributes)}` ); await page.waitForSelector(SELECTORS.PORTAL_DESCRIPTION); if (portal.selfRegUserDefaultLicense) { @@ -234,21 +258,21 @@ export class CustomerPortalSetup extends BrowserforcePlugin { }), page.click(SELECTORS.SAVE_BUTTON) ]); - if ((await page.url()).includes(portal.id)) { + if ((await page.url()).includes(portal._id)) { // error handling await page.waitForSelector(SELECTORS.PORTAL_DESCRIPTION); await this.browserforce.throwPageErrors(page); - throw new Error(`saving customer portal '${portal.id}' failed`); + throw new Error(`saving customer portal '${portal._id}' failed`); } // portalProfileMemberships if (portal.portalProfileMemberships) { const membershipUrlAttributes = {}; for (const member of portal.portalProfileMemberships) { - membershipUrlAttributes[member.id] = member.active ? 1 : 0; + membershipUrlAttributes[member._id] = member.active ? 1 : 0; } const portalProfilePage = await this.browserforce.openPage( `${PATHS.PORTAL_PROFILE_MEMBERSHIP}?portalId=${ - portal.id + portal._id }&setupid=CustomerSuccessPortalSettings&${queryString.stringify( membershipUrlAttributes )}` diff --git a/src/plugins/defer-sharing-calculation/index.ts b/src/plugins/defer-sharing-calculation/index.ts index 20ce2d67..bf3b71e9 100644 --- a/src/plugins/defer-sharing-calculation/index.ts +++ b/src/plugins/defer-sharing-calculation/index.ts @@ -9,8 +9,13 @@ const SELECTORS = { RECALCULATE_BUTTON: 'input[name="rule_recalc"]' }; +type Config = { + suspend: boolean; +}; + export class DeferSharingCalculation extends BrowserforcePlugin { - public async retrieve(definition?) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public async retrieve(definition?: Config): Promise { const page = await this.browserforce.openPage(PATHS.BASE); await page.waitForSelector(SELECTORS.SUSPEND_BUTTON); await page.waitForSelector(SELECTORS.RESUME_BUTTON); @@ -33,7 +38,7 @@ export class DeferSharingCalculation extends BrowserforcePlugin { }; } - public async apply(config) { + public async apply(config: Config): Promise { const page = await this.browserforce.openPage(PATHS.BASE); const button = config.suspend ? SELECTORS.SUSPEND_BUTTON diff --git a/src/plugins/density-settings/index.ts b/src/plugins/density-settings/index.ts index 56d2ab0f..3e284d3f 100644 --- a/src/plugins/density-settings/index.ts +++ b/src/plugins/density-settings/index.ts @@ -1,3 +1,4 @@ +import { ElementHandle, Page } from 'puppeteer'; import { BrowserforcePlugin } from '../../plugin'; const PATHS = { @@ -9,8 +10,18 @@ const SELECTORS = { 'pierce/one-density-visual-picker one-density-visual-picker-item input' }; +type Config = { + density: string; +}; + +type Density = { + value: string; + checked: boolean; + elementHandle: ElementHandle; +}; + export class DensitySettings extends BrowserforcePlugin { - public async retrieve() { + public async retrieve(): Promise { const page = await this.browserforce.openPage(PATHS.BASE, { waitUntil: ['load', 'domcontentloaded', 'networkidle0'] }); @@ -21,14 +32,14 @@ export class DensitySettings extends BrowserforcePlugin { }; } - public async apply(config) { + public async apply(config: Config): Promise { const page = await this.browserforce.openPage(PATHS.BASE, { waitUntil: ['load', 'domcontentloaded', 'networkidle0'] }); await this.setDensity(page, config.density); } - async getDensities(page) { + async getDensities(page: Page): Promise { await page.waitForSelector(SELECTORS.PICKER_ITEMS); const elementHandles = await page.$$(SELECTORS.PICKER_ITEMS); const result = await page.$$eval( @@ -46,7 +57,7 @@ export class DensitySettings extends BrowserforcePlugin { }); } - async setDensity(page, name) { + async setDensity(page: Page, name: string): Promise { const densities = await this.getDensities(page); const densityToSelect = densities.find(input => input.value === name); await Promise.all([ diff --git a/src/plugins/high-velocity-sales-settings/index.ts b/src/plugins/high-velocity-sales-settings/index.ts index 377f6cfc..560a8932 100644 --- a/src/plugins/high-velocity-sales-settings/index.ts +++ b/src/plugins/high-velocity-sales-settings/index.ts @@ -1,11 +1,17 @@ +import { Connection } from 'jsforce'; import { BrowserforcePlugin } from '../../plugin'; import { HighVelocitySalesSetupPage } from './page'; const MSG_NOT_AVAILABLE = `HighVelocitySales is not available in this organization. Please add 'HighVelocitySales' to your Scratch Org Features or purchase a license.`; +type Config = { + setUpAndEnable: boolean; +}; + export class HighVelocitySalesSettings extends BrowserforcePlugin { - public async retrieve(definition?) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public async retrieve(definition?: Config): Promise { const conn = this.org.getConnection(); const result = { setUpAndEnable: false }; try { @@ -29,7 +35,7 @@ export class HighVelocitySalesSettings extends BrowserforcePlugin { return result; } - public async apply(config) { + public async apply(config: Config): Promise { if (config.setUpAndEnable) { const page = new HighVelocitySalesSetupPage( await this.browserforce.openPage(HighVelocitySalesSetupPage.getUrl()) @@ -42,7 +48,9 @@ export class HighVelocitySalesSettings extends BrowserforcePlugin { } } -export async function disableHighVelocitySalesUsingMetadata(conn) { +export async function disableHighVelocitySalesUsingMetadata( + conn: Connection +): Promise { const settings = { fullName: 'HighVelocitySales', enableHighVelocitySalesSetup: 'false', diff --git a/src/plugins/high-velocity-sales-settings/page.ts b/src/plugins/high-velocity-sales-settings/page.ts index 84c0eb93..eeed84b3 100644 --- a/src/plugins/high-velocity-sales-settings/page.ts +++ b/src/plugins/high-velocity-sales-settings/page.ts @@ -1,3 +1,4 @@ +import { Page } from 'puppeteer'; import { throwPageErrors } from '../../browserforce'; const SET_UP_AND_ENABLE_HVS_BUTTON = 'button.setupAndEnableButton'; @@ -6,7 +7,7 @@ const ENABLE_TOGGLE = '#toggleHighVelocitySalesPref'; export class HighVelocitySalesSetupPage { private page; - constructor(page) { + constructor(page: Page) { this.page = page; } @@ -14,7 +15,7 @@ export class HighVelocitySalesSetupPage { return 'lightning/setup/HighVelocitySales/home'; } - public async setUpAndEnable() { + public async setUpAndEnable(): Promise { await this.page.waitForSelector(SET_UP_AND_ENABLE_HVS_BUTTON); await this.page.click(SET_UP_AND_ENABLE_HVS_BUTTON); await throwPageErrors(this.page); diff --git a/src/plugins/home-page-layouts/index.ts b/src/plugins/home-page-layouts/index.ts index 06426ca5..2f148535 100644 --- a/src/plugins/home-page-layouts/index.ts +++ b/src/plugins/home-page-layouts/index.ts @@ -20,8 +20,18 @@ interface HomePageLayoutRecord { Name: string; } +type Config = { + homePageLayoutAssignments: HomePageLayoutAssignment[]; +}; + +type HomePageLayoutAssignment = { + profile: string; + layout: string; +}; + export class HomePageLayouts extends BrowserforcePlugin { - public async retrieve(definition?) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public async retrieve(definition?: Config): Promise { const page = await this.browserforce.openPage(PATHS.BASE); await page.waitForSelector(SELECTORS.BASE); const profiles = await page.$$eval( @@ -57,7 +67,7 @@ export class HomePageLayouts extends BrowserforcePlugin { }; } - public diff(source, target) { + public diff(source: Config, target: Config): Config { const profileNames = target.homePageLayoutAssignments.map( assignment => assignment.profile ); @@ -67,7 +77,7 @@ export class HomePageLayouts extends BrowserforcePlugin { return jsonMergePatch.generate(source, target); } - public async apply(config) { + public async apply(config: Config): Promise { const profilesList = config.homePageLayoutAssignments .map(assignment => { return `'${assignment.profile}'`; diff --git a/src/plugins/lightning-experience-settings/index.ts b/src/plugins/lightning-experience-settings/index.ts index 028ba750..9c6c26d1 100644 --- a/src/plugins/lightning-experience-settings/index.ts +++ b/src/plugins/lightning-experience-settings/index.ts @@ -1,3 +1,4 @@ +import { ElementHandle, Page } from 'puppeteer'; import { BrowserforcePlugin } from '../../plugin'; const PATHS = { @@ -11,8 +12,18 @@ const SELECTORS = { STATES: `${THEME_ROW_SELECTOR} > td:nth-child(6) > lightning-primitive-cell-factory` }; +type Config = { + activeThemeName: string; +}; + +type Theme = { + developerName: string; + isActive: boolean; + rowElementHandle: ElementHandle; +}; + export class LightningExperienceSettings extends BrowserforcePlugin { - public async retrieve() { + public async retrieve(): Promise { const page = await this.browserforce.openPage(PATHS.BASE, { waitUntil: ['load', 'domcontentloaded', 'networkidle0'] }); @@ -24,7 +35,7 @@ export class LightningExperienceSettings extends BrowserforcePlugin { return response; } - public async apply(config) { + public async apply(config: Config): Promise { const page = await this.browserforce.openPage(PATHS.BASE, { waitUntil: ['load', 'domcontentloaded', 'networkidle0'] }); @@ -32,12 +43,12 @@ export class LightningExperienceSettings extends BrowserforcePlugin { await this.setActiveTheme(page, config.activeThemeName); } - async getThemeData(page) { + async getThemeData(page: Page): Promise { await page.waitForSelector(THEME_ROW_SELECTOR); const rowElementHandles = await page.$$(THEME_ROW_SELECTOR); await page.waitForSelector(SELECTORS.DEVELOPER_NAMES); const developerNames = await page.$$eval(SELECTORS.DEVELOPER_NAMES, cells => - cells.map(cell => cell.innerText) + cells.map((cell: HTMLTableCellElement) => cell.innerText) ); const states = await page.$$eval(SELECTORS.STATES, cells => cells.map( @@ -54,7 +65,7 @@ export class LightningExperienceSettings extends BrowserforcePlugin { }); } - async setActiveTheme(page, themeDeveloperName) { + async setActiveTheme(page: Page, themeDeveloperName: string): Promise { const data = await this.getThemeData(page); const theme = data.find( theme => theme.developerName === themeDeveloperName diff --git a/src/plugins/picklists/field-dependencies/index.ts b/src/plugins/picklists/field-dependencies/index.ts index 036be4f8..db42f7fa 100644 --- a/src/plugins/picklists/field-dependencies/index.ts +++ b/src/plugins/picklists/field-dependencies/index.ts @@ -1,8 +1,16 @@ import { BrowserforcePlugin } from '../../../plugin'; import { FieldDependencyPage, NewFieldDependencyPage } from './pages'; +export type FieldDependencyConfig = { + object: string; + dependentField: string; + controllingField: string; +}; + +export type Config = FieldDependencyConfig[]; + export class FieldDependencies extends BrowserforcePlugin { - public async retrieve(definition?) { + public async retrieve(definition?: Config): Promise { const conn = this.org.getConnection(); const dependentFieldNames = definition.map( f => `${f.object}.${f.dependentField}` @@ -20,7 +28,7 @@ export class FieldDependencies extends BrowserforcePlugin { return state; } - public async apply(plan) { + public async apply(plan: Config): Promise { const conn = this.org.getConnection(); const listMetadataResult = await conn.metadata.list([ { diff --git a/src/plugins/picklists/field-dependencies/pages.ts b/src/plugins/picklists/field-dependencies/pages.ts index 949873c6..7e5baed8 100644 --- a/src/plugins/picklists/field-dependencies/pages.ts +++ b/src/plugins/picklists/field-dependencies/pages.ts @@ -1,9 +1,10 @@ +import { Page } from 'puppeteer'; import { throwPageErrors } from '../../../browserforce'; export class FieldDependencyPage { private page; - constructor(page) { + constructor(page: Page) { this.page = page; } @@ -16,7 +17,7 @@ export class FieldDependencyPage { public async clickDeleteDependencyActionForField( customFieldId: string - ): Promise { + ): Promise { // wait for "new" button in field dependencies releated list header await this.page.waitForSelector( 'div.listRelatedObject div.pbHeader input[name="new"]' @@ -44,7 +45,7 @@ export class NewFieldDependencyPage { protected page; protected saveButton = 'input[name="save"]'; - constructor(page) { + constructor(page: Page) { this.page = page; } @@ -65,7 +66,7 @@ export class NewFieldDependencyPage { )}&retURL=/${customObjectId.substring(0, 15)}`; } - async save() { + async save(): Promise { await this.page.waitForSelector(this.saveButton); await Promise.all([ this.page.waitForNavigation(), diff --git a/src/plugins/picklists/index.ts b/src/plugins/picklists/index.ts index 9cda7bb5..2c10ccba 100644 --- a/src/plugins/picklists/index.ts +++ b/src/plugins/picklists/index.ts @@ -2,7 +2,10 @@ import { FileProperties } from 'jsforce'; import { ensureArray } from '../../jsforce-utils'; import { BrowserforcePlugin } from '../../plugin'; import { removeEmptyValues } from '../utils'; -import { FieldDependencies } from './field-dependencies'; +import { + Config as FieldDependenciesConfig, + FieldDependencies +} from './field-dependencies'; import { PicklistPage, DefaultPicklistAddPage, @@ -10,8 +13,25 @@ import { } from './pages'; import { determineStandardValueSetEditUrl } from './standard-value-set'; +type Config = { + picklistValues: PicklistValuesConfig[]; + fieldDependencies: FieldDependenciesConfig; +}; + +type PicklistValuesConfig = { + metadataType: string; + metadataFullName: string; + value?: string; + newValue?: string; + statusCategory?: string; + replaceAllBlankValues?: boolean; + active?: boolean; + absent?: boolean; + _newValueExists?: boolean; +}; + export class Picklists extends BrowserforcePlugin { - public async retrieve(definition?) { + public async retrieve(definition?: Config): Promise { const conn = this.org.getConnection(); const result = { picklistValues: [], fieldDependencies: [] }; if (definition.picklistValues) { @@ -40,7 +60,7 @@ export class Picklists extends BrowserforcePlugin { : undefined; state.absent = !valueMatch; state.active = valueMatch?.active; - state.newValueExists = + state._newValueExists = Boolean(newValueMatch) || action.newValue === null; result.picklistValues.push(state); } @@ -54,7 +74,7 @@ export class Picklists extends BrowserforcePlugin { return result; } - public diff(state, definition) { + public diff(state: Config, definition: Config): Config { const changes = {}; if (definition.picklistValues) { changes['picklistValues'] = definition.picklistValues.filter( @@ -68,12 +88,12 @@ export class Picklists extends BrowserforcePlugin { } // replacing a picklist value is not idempotent if ( - source.newValueExists && + source._newValueExists && (target.value !== undefined || target.replaceAllBlankValues) ) { return true; } - if (target.newValue && !source.newValueExists) { + if (target.newValue && !source._newValueExists) { // New value doesn't exist in org yet return true; } @@ -90,7 +110,7 @@ export class Picklists extends BrowserforcePlugin { return removeEmptyValues(changes); } - public async apply(config) { + public async apply(config: Config): Promise { const conn = this.org.getConnection(); if (config.picklistValues) { const fileProperties = await listMetadata( @@ -152,7 +172,7 @@ function getPicklistUrl( type: string, fullName: string, fileProperties?: Array -) { +): string { let picklistUrl; if (type === 'StandardValueSet') { picklistUrl = determineStandardValueSetEditUrl(fullName); @@ -165,7 +185,7 @@ function getPicklistUrl( return picklistUrl; } -async function listMetadata(conn, sobjectTypes) { +async function listMetadata(conn, sobjectTypes): Promise { let uniqueSobjectTypes = [...new Set(sobjectTypes)]; // don't list StandardValueSet as the FileProperties are broken uniqueSobjectTypes = uniqueSobjectTypes.filter(x => x !== 'StandardValueSet'); diff --git a/src/plugins/picklists/pages.ts b/src/plugins/picklists/pages.ts index c7b55e79..8b8ad7e3 100644 --- a/src/plugins/picklists/pages.ts +++ b/src/plugins/picklists/pages.ts @@ -1,5 +1,5 @@ import pRetry from 'p-retry'; -import { JSHandle } from 'puppeteer'; +import { JSHandle, Page } from 'puppeteer'; // table columns // (actions) | (label) | (API name) @@ -15,7 +15,7 @@ type PicklistValue = { export class PicklistPage { private page; - constructor(page) { + constructor(page: Page) { this.page = page; } @@ -27,7 +27,7 @@ export class PicklistPage { const innerTextJsHandles = await Promise.all( fullNameHandles.map(handle => handle.getProperty('innerText')) ); - const fullNames = await Promise.all( + const fullNames = await Promise.all( innerTextJsHandles.map(handle => handle.jsonValue()) ); return fullNames; @@ -58,7 +58,7 @@ export class PicklistPage { ]); } - public async clickReplaceActionButton(): Promise { + public async clickReplaceActionButton(): Promise { const REPLACE_ACTION_BUTTON = 'input[name="replace"]'; await this.page.waitForSelector(REPLACE_ACTION_BUTTON); await Promise.all([ @@ -70,7 +70,7 @@ export class PicklistPage { public async clickDeleteActionForValue( picklistValueApiName: string - ): Promise { + ): Promise { const xpath = `//tr[td[2][text() = "${picklistValueApiName}"]]//td[1]//a[contains(@href, "/setup/ui/picklist_masterdelete.jsp") and contains(@href, "deleteType=0")]`; await this.page.waitForXPath(xpath); const actionLinkHandles = await this.page.$x(xpath); @@ -93,7 +93,7 @@ export class PicklistPage { public async clickActivateDeactivateActionForValue( picklistValueApiName: string, active: boolean - ): Promise { + ): Promise { let xpath; let actionName; if (active) { @@ -126,11 +126,11 @@ export class DefaultPicklistAddPage { protected page; protected saveButton = 'input.btn[name="save"]'; - constructor(page) { + constructor(page: Page) { this.page = page; } - async add(newValue) { + async add(newValue: string): Promise { const TEXT_AREA = 'textarea'; if (newValue !== undefined && newValue !== null) { await this.page.waitForSelector(TEXT_AREA); @@ -139,7 +139,7 @@ export class DefaultPicklistAddPage { await this.save(); } - async save() { + async save(): Promise { await pRetry( async () => { await this.page.waitForSelector(this.saveButton); @@ -170,11 +170,11 @@ export class StatusPicklistAddPage { protected page; protected saveButton = 'input.btn[name="save"]'; - constructor(page) { + constructor(page: Page) { this.page = page; } - async add(newValue, statusCategory) { + async add(newValue: string, statusCategory: string): Promise { const LABEL_INPUT = 'input#p1'; const API_NAME_INPUT = 'input#p3'; const STATUS_CATEGORY_SELECTOR = 'select#p5'; @@ -187,7 +187,7 @@ export class StatusPicklistAddPage { await this.save(); } - async save() { + async save(): Promise { await pRetry( async () => { await this.page.waitForSelector(this.saveButton); @@ -218,11 +218,15 @@ export class PicklistReplacePage { protected page; protected saveButton = 'input[name="save"]'; - constructor(page) { + constructor(page: Page) { this.page = page; } - async replace(value, newValue, replaceAllBlankValues?) { + async replace( + value: string, + newValue: string, + replaceAllBlankValues?: boolean + ): Promise { const OLD_VALUE_SELECTOR = 'input#nf'; const NEW_VALUE_SELECTOR = 'select#nv'; const REPLACE_ALL_BLANK_VALUES_CHECKBOX = 'input#fnv'; @@ -241,7 +245,7 @@ export class PicklistReplacePage { await this.save(); } - async save() { + async save(): Promise { await pRetry( async () => { await this.page.waitForSelector(this.saveButton); @@ -269,12 +273,12 @@ export class PicklistReplacePage { } export class PicklistReplaceAndDeletePage extends PicklistReplacePage { - constructor(page) { + constructor(page: Page) { super(page); this.saveButton = 'input[name="delID"][type="submit"]'; } - async replaceAndDelete(newValue) { + async replaceAndDelete(newValue: string): Promise { const NEW_VALUE_SELECTOR = 'select#p13'; const REPLACE_WITH_BLANK_VALUE_RADIO_INPUT = 'input#ReplaceValueWithNullValue'; @@ -290,7 +294,7 @@ export class PicklistReplaceAndDeletePage extends PicklistReplacePage { } } -async function throwPageErrors(page) { +async function throwPageErrors(page: Page): Promise { const errorHandle = await page.$('div#validationError div.messageText'); if (errorHandle) { const errorMsg = await page.evaluate( diff --git a/src/plugins/record-types/index.ts b/src/plugins/record-types/index.ts index d61dd09b..d8e2ac83 100644 --- a/src/plugins/record-types/index.ts +++ b/src/plugins/record-types/index.ts @@ -2,8 +2,17 @@ import { Connection } from '@salesforce/command/node_modules/@salesforce/core'; import { BrowserforcePlugin } from '../../plugin'; import { RecordTypePage } from './pages'; +type Config = { + deletions: RecordTypeConfig[]; +}; + +type RecordTypeConfig = { + fullName: string; + replacement?: string; +}; + export class RecordTypes extends BrowserforcePlugin { - public async retrieve(definition?) { + public async retrieve(definition?: Config): Promise { const conn = this.org.getConnection(); const response = { deletions: [] @@ -45,7 +54,7 @@ export class RecordTypes extends BrowserforcePlugin { return response; } - public async apply(config) { + public async apply(config: Config): Promise { const conn = this.org.getConnection(); const recordTypeFileProperties = await listRecordTypes(conn); const recordTypes = await queryRecordTypes(conn); diff --git a/src/plugins/record-types/pages.ts b/src/plugins/record-types/pages.ts index b98672f4..b12b7099 100644 --- a/src/plugins/record-types/pages.ts +++ b/src/plugins/record-types/pages.ts @@ -1,11 +1,15 @@ +import { Page } from 'puppeteer'; + export class RecordTypePage { private page; - constructor(page) { + constructor(page: Page) { this.page = page; } - public async clickDeleteAction(recordTypeId: string): Promise { + public async clickDeleteAction( + recordTypeId: string + ): Promise { const xpath = `//a[contains(@href, "setup/ui/recordtypedelete.jsp?id=${recordTypeId.slice( 0, 15 @@ -30,11 +34,11 @@ export class RecordTypeDeletePage { protected page; protected saveButton = 'input[name="save"]'; - constructor(page) { + constructor(page: Page) { this.page = page; } - async replace(newRecordTypeId?: string) { + async replace(newRecordTypeId?: string): Promise { await this.throwOnMissingSaveButton(); const NEW_VALUE_SELECTOR = 'select#p2'; if (newRecordTypeId) { @@ -44,7 +48,7 @@ export class RecordTypeDeletePage { await this.save(); } - async save() { + async save(): Promise { await this.page.waitForSelector(this.saveButton); await Promise.all([ this.page.waitForNavigation(), @@ -53,7 +57,7 @@ export class RecordTypeDeletePage { await this.throwPageErrors(); } - async throwOnMissingSaveButton() { + async throwOnMissingSaveButton(): Promise { const saveButton = await this.page.$(this.saveButton); if (!saveButton) { const bodyHandle = await this.page.$('div.pbBody'); @@ -70,7 +74,7 @@ export class RecordTypeDeletePage { } } - async throwPageErrors() { + async throwPageErrors(): Promise { const errorHandle = await this.page.$( 'div#validationError div.messageText' ); diff --git a/src/plugins/reports-and-dashboards/folder-sharing/index.ts b/src/plugins/reports-and-dashboards/folder-sharing/index.ts index a52c1e7e..b1c9a920 100644 --- a/src/plugins/reports-and-dashboards/folder-sharing/index.ts +++ b/src/plugins/reports-and-dashboards/folder-sharing/index.ts @@ -9,8 +9,13 @@ const SELECTORS = { SAVE_BUTTON: 'input[id="saveButton"]' }; +export type Config = { + enableEnhancedFolderSharing: boolean; +}; + export class FolderSharing extends BrowserforcePlugin { - public async retrieve(definition?) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public async retrieve(definition?: Config): Promise { const response = { enableEnhancedFolderSharing: true }; @@ -41,7 +46,7 @@ export class FolderSharing extends BrowserforcePlugin { return response; } - public async apply(config) { + public async apply(config: Config): Promise { if (config.enableEnhancedFolderSharing === false) { throw new Error( '`enableEnhancedFolderSharing` cannot be disabled once enabled' @@ -51,7 +56,7 @@ export class FolderSharing extends BrowserforcePlugin { await page.waitForSelector(SELECTORS.ENABLE_CHECKBOX); await page.$eval( SELECTORS.ENABLE_CHECKBOX, - (e: HTMLInputElement, v) => { + (e: HTMLInputElement, v: boolean) => { e.checked = v; }, config.enableEnhancedFolderSharing diff --git a/src/plugins/reports-and-dashboards/index.ts b/src/plugins/reports-and-dashboards/index.ts index caff4983..b117effd 100644 --- a/src/plugins/reports-and-dashboards/index.ts +++ b/src/plugins/reports-and-dashboards/index.ts @@ -1,12 +1,14 @@ import { BrowserforcePlugin } from '../../plugin'; import { removeEmptyValues } from '../utils'; -import { FolderSharing } from './folder-sharing'; +import { Config as FolderSharingConfig, FolderSharing } from './folder-sharing'; + +type Config = { + folderSharing?: FolderSharingConfig; +}; export class ReportsAndDashboards extends BrowserforcePlugin { - public async retrieve(definition?) { - const response = { - folderSharing: {} - }; + public async retrieve(definition?: Config): Promise { + const response: Config = {}; if (definition) { if (definition.folderSharing) { const pluginFolderSharing = new FolderSharing( @@ -21,7 +23,7 @@ export class ReportsAndDashboards extends BrowserforcePlugin { return response; } - public diff(state, definition) { + public diff(state: Config, definition: Config): Config { const pluginFolderSharing = new FolderSharing(null, null); const response = { folderSharing: pluginFolderSharing.diff( @@ -32,7 +34,7 @@ export class ReportsAndDashboards extends BrowserforcePlugin { return removeEmptyValues(response); } - public async apply(plan) { + public async apply(plan: Config): Promise { if (plan.folderSharing) { const pluginFolderSharing = new FolderSharing( this.browserforce, diff --git a/src/plugins/salesforce-to-salesforce/index.ts b/src/plugins/salesforce-to-salesforce/index.ts index a3efc394..fbed9156 100644 --- a/src/plugins/salesforce-to-salesforce/index.ts +++ b/src/plugins/salesforce-to-salesforce/index.ts @@ -10,25 +10,28 @@ const SELECTORS = { SAVE_BUTTON: 'input[name="save"]' }; +type Config = { + enabled: boolean; +}; + export class SalesforceToSalesforce extends BrowserforcePlugin { - public async retrieve() { + public async retrieve(): Promise { const page = await this.browserforce.openPage(PATHS.BASE); await page.waitForSelector(SELECTORS.BASE); - const response = {}; + const response = { + enabled: true + }; const inputEnable = await page.$(SELECTORS.ENABLED); if (inputEnable) { - response['enabled'] = await page.$eval( + response.enabled = await page.$eval( SELECTORS.ENABLED, (el: HTMLInputElement) => el.checked ); - } else { - // already enabled - response['enabled'] = true; } return response; } - public async apply(config) { + public async apply(config: Config): Promise { if (config.enabled === false) { throw new Error('`enabled` cannot be disabled once enabled'); } @@ -38,7 +41,7 @@ export class SalesforceToSalesforce extends BrowserforcePlugin { await page.waitForSelector(SELECTORS.ENABLED); await page.$eval( SELECTORS.ENABLED, - (e: HTMLInputElement, v) => { + (e: HTMLInputElement, v: boolean) => { e.checked = v; }, config.enabled @@ -48,7 +51,7 @@ export class SalesforceToSalesforce extends BrowserforcePlugin { page.click(SELECTORS.SAVE_BUTTON) ]); const result = await this.retrieve(); - if (result['enabled'] !== config.enabled) { + if (result.enabled !== config.enabled) { throw new Error('setting was not applied as expected'); } }); diff --git a/src/plugins/security/certificate-and-key-management/index.ts b/src/plugins/security/certificate-and-key-management/index.ts index 689cdcff..9ffa3dcf 100644 --- a/src/plugins/security/certificate-and-key-management/index.ts +++ b/src/plugins/security/certificate-and-key-management/index.ts @@ -20,12 +20,31 @@ interface CertificateRecord { Id: SalesforceId; DeveloperName: string; MasterLabel: string; - OptionsIsPrivateKeyExportable: string; - KeySize: string; + OptionsIsPrivateKeyExportable: boolean; + KeySize: number; } +export type Config = { + certificates?: Certificate[]; + importFromKeystore?: KeyStore[]; +}; + +type Certificate = { + name: string; + label: string; + exportable?: boolean; + keySize?: number; + _id?: string; +}; + +type KeyStore = { + name: string; + filePath: string; + password?: string; +}; + export class CertificateAndKeyManagement extends BrowserforcePlugin { - public async retrieve(definition?) { + public async retrieve(definition?: Config): Promise { const response = { certificates: [], importFromKeystore: [] }; if (definition && definition.certificates) { const certificatesList = definition.certificates @@ -47,7 +66,7 @@ export class CertificateAndKeyManagement extends BrowserforcePlugin { ); if (existingCert) { response.certificates.push({ - id: existingCert.Id, + _id: existingCert.Id, name: existingCert.DeveloperName, label: existingCert.MasterLabel, exportable: existingCert.OptionsIsPrivateKeyExportable, @@ -59,8 +78,8 @@ export class CertificateAndKeyManagement extends BrowserforcePlugin { return response; } - public diff(state, definition) { - const response = { + public diff(state: Config, definition: Config): Config { + const response: Config = { certificates: [], importFromKeystore: [] }; @@ -71,8 +90,8 @@ export class CertificateAndKeyManagement extends BrowserforcePlugin { ); if (existingCert) { // move id from state to definition to be retained and used - cert.id = existingCert.id; - delete existingCert.id; + cert._id = existingCert._id; + delete existingCert._id; } response.certificates.push(jsonMergePatch.generate(existingCert, cert)); } @@ -83,10 +102,10 @@ export class CertificateAndKeyManagement extends BrowserforcePlugin { return removeEmptyValues(response); } - public async apply(plan) { + public async apply(plan: Config): Promise { if (plan.certificates) { for (const certificate of plan.certificates) { - if (certificate.id) { + if (certificate._id) { // update } else { // create new diff --git a/src/plugins/security/identity-provider/index.ts b/src/plugins/security/identity-provider/index.ts index ee0d14f1..92835693 100644 --- a/src/plugins/security/identity-provider/index.ts +++ b/src/plugins/security/identity-provider/index.ts @@ -21,8 +21,14 @@ interface CertificateRecord { NamespacePrefix: string; } +export type Config = { + enabled?: boolean; + certificate?: string; +}; + export class IdentityProvider extends BrowserforcePlugin { - public async retrieve(definition?) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public async retrieve(definition?: Config): Promise { const page = await this.browserforce.openPage(PATHS.EDIT_VIEW); await page.waitForSelector(SELECTORS.EDIT_BUTTON); const disableButton = await page.$(SELECTORS.DISABLE_BUTTON); @@ -39,11 +45,11 @@ export class IdentityProvider extends BrowserforcePlugin { return response; } - public diff(state, definition) { + public diff(state: Config, definition: Config): Config { return removeNullValues(jsonMergePatch.generate(state, definition)); } - public async apply(plan) { + public async apply(plan: Config): Promise { if (plan.enabled && plan.certificate && plan.certificate !== '') { // wait for cert to become available in Identity Provider UI await pRetry( diff --git a/src/plugins/security/index.ts b/src/plugins/security/index.ts index 60d96091..f6d07cd6 100644 --- a/src/plugins/security/index.ts +++ b/src/plugins/security/index.ts @@ -1,18 +1,29 @@ import { BrowserforcePlugin } from '../../plugin'; import { removeEmptyValues } from '../utils'; -import { CertificateAndKeyManagement } from './certificate-and-key-management'; -import { IdentityProvider } from './identity-provider'; -import { LoginAccessPolicies } from './login-access-policies'; -import { Sharing } from './sharing'; +import { + Config as CertificateAndKeyManagementConfig, + CertificateAndKeyManagement +} from './certificate-and-key-management'; +import { + Config as IdentityProviderConfig, + IdentityProvider +} from './identity-provider'; +import { + Config as LoginAccessPoliciesConfig, + LoginAccessPolicies +} from './login-access-policies'; +import { Config as SharingConfig, Sharing } from './sharing'; + +type Config = { + certificateAndKeyManagement?: CertificateAndKeyManagementConfig; + identityProvider?: IdentityProviderConfig; + loginAccessPolicies?: LoginAccessPoliciesConfig; + sharing?: SharingConfig; +}; export class Security extends BrowserforcePlugin { - public async retrieve(definition?) { - const response = { - certificateAndKeyManagement: {}, - identityProvider: {}, - loginAccessPolicies: {}, - sharing: {} - }; + public async retrieve(definition?: Config): Promise { + const response: Config = {}; if (definition) { if (definition.certificateAndKeyManagement) { const pluginCKM = new CertificateAndKeyManagement( @@ -49,7 +60,7 @@ export class Security extends BrowserforcePlugin { return response; } - public diff(state, definition) { + public diff(state: Config, definition: Config): Config { const pluginCKM = new CertificateAndKeyManagement(null, null); const pluginIdentityProvider = new IdentityProvider(null, null); const pluginLoginAccessPolicies = new LoginAccessPolicies(null, null); @@ -72,7 +83,7 @@ export class Security extends BrowserforcePlugin { return removeEmptyValues(response); } - public async apply(plan) { + public async apply(plan: Config): Promise { if (plan.certificateAndKeyManagement) { const pluginCKM = new CertificateAndKeyManagement( this.browserforce, diff --git a/src/plugins/security/login-access-policies/index.ts b/src/plugins/security/login-access-policies/index.ts index e2fec6f0..ecd825a1 100644 --- a/src/plugins/security/login-access-policies/index.ts +++ b/src/plugins/security/login-access-policies/index.ts @@ -9,8 +9,13 @@ const SELECTORS = { SAVE_BUTTON: 'input[id$=":save"]' }; +export type Config = { + administratorsCanLogInAsAnyUser: boolean; +}; + export class LoginAccessPolicies extends BrowserforcePlugin { - public async retrieve(definition?) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public async retrieve(definition?: Config): Promise { const page = await this.browserforce.openPage(PATHS.BASE); await page.waitForSelector(SELECTORS.ENABLED); const response = { @@ -22,12 +27,12 @@ export class LoginAccessPolicies extends BrowserforcePlugin { return response; } - public async apply(config) { + public async apply(config: Config): Promise { const page = await this.browserforce.openPage(PATHS.BASE); await page.waitForSelector(SELECTORS.ENABLED); await page.$eval( SELECTORS.ENABLED, - (e: HTMLInputElement, v) => { + (e: HTMLInputElement, v: boolean) => { e.checked = v; }, config.administratorsCanLogInAsAnyUser diff --git a/src/plugins/security/sharing/index.ts b/src/plugins/security/sharing/index.ts index 88445764..414612bb 100644 --- a/src/plugins/security/sharing/index.ts +++ b/src/plugins/security/sharing/index.ts @@ -11,8 +11,13 @@ const SELECTORS = { MODAL_DIALOG: 'Modal.confirm' }; +export type Config = { + enableExternalSharingModel: boolean; +}; + export class Sharing extends BrowserforcePlugin { - public async retrieve(definition?) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public async retrieve(definition?: Config): Promise { const page = await this.browserforce.openPage(PATHS.BASE); await page.waitForSelector(SELECTORS.EXTERNAL_SHARING_MODEL_BUTTON); const buttonOnclick = await page.$eval( @@ -25,7 +30,7 @@ export class Sharing extends BrowserforcePlugin { }; } - public async apply(config) { + public async apply(config: Config): Promise { const page = await this.browserforce.openPage(PATHS.BASE); await page.waitForSelector(SELECTORS.EXTERNAL_SHARING_MODEL_BUTTON); page.on('dialog', async dialog => { diff --git a/src/plugins/utils.ts b/src/plugins/utils.ts index 0d37c915..f956c9aa 100644 --- a/src/plugins/utils.ts +++ b/src/plugins/utils.ts @@ -1,4 +1,7 @@ -export function removeEmptyValues(obj) { +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export function removeEmptyValues(obj: any): any { if (!obj) { obj = {}; } @@ -18,7 +21,7 @@ export function removeEmptyValues(obj) { }, {}); } -export function removeNullValues(obj) { +export function removeNullValues(obj: any): any { if (!obj) { obj = {}; } @@ -31,7 +34,7 @@ export function removeNullValues(obj) { } // an object only containing an id is semantically empty -export function semanticallyCleanObject(obj, id = 'id') { +export function semanticallyCleanObject(obj: any, id = 'id'): any { if (!obj) { obj = {}; } diff --git a/test/config-parser.test.ts b/test/config-parser.test.ts index 842bd0f5..5268b5ff 100644 --- a/test/config-parser.test.ts +++ b/test/config-parser.test.ts @@ -25,8 +25,10 @@ describe('ConfigParser', () => { } } }; + // workaround to disable static type checking + const anonymousDefinition = JSON.parse(JSON.stringify(definition)); assert.throws(() => { - ConfigParser.parse(DRIVERS, definition); + ConfigParser.parse(DRIVERS, anonymousDefinition); }, /Missing 'settings' attribute in definition:/); }); it('should fail parsing a definition file with an invalid plugin', () => { From 5983d84789d3f1681c7fb6ffdbb8dd1e15e5f57e Mon Sep 17 00:00:00 2001 From: Matthias Rolke Date: Sat, 12 Jun 2021 18:30:35 +0200 Subject: [PATCH 6/6] fix: certificate and key management - don't try to enable identity provider with a non existing certificate - don't try to re-import existing certificate from keystore --- .../certificate-and-key-management/index.ts | 66 +++++++++++++------ .../security/identity-provider/index.ts | 8 ++- src/plugins/security/index.e2e-spec.ts | 34 ++++++++-- 3 files changed, 79 insertions(+), 29 deletions(-) diff --git a/src/plugins/security/certificate-and-key-management/index.ts b/src/plugins/security/certificate-and-key-management/index.ts index 9ffa3dcf..e91007cd 100644 --- a/src/plugins/security/certificate-and-key-management/index.ts +++ b/src/plugins/security/certificate-and-key-management/index.ts @@ -4,7 +4,11 @@ import * as jsonMergePatch from 'json-merge-patch'; import * as path from 'path'; import * as queryString from 'querystring'; import { BrowserforcePlugin } from '../../../plugin'; -import { removeEmptyValues } from '../../utils'; +import { + removeEmptyValues, + removeNullValues, + semanticallyCleanObject +} from '../../utils'; const PATHS = { CERT_PREFIX: '0P1', @@ -45,23 +49,27 @@ type KeyStore = { export class CertificateAndKeyManagement extends BrowserforcePlugin { public async retrieve(definition?: Config): Promise { - const response = { certificates: [], importFromKeystore: [] }; - if (definition && definition.certificates) { - const certificatesList = definition.certificates - .map(cert => { - return `'${cert.name}'`; - }) - .join(','); - const certificatesResponse = await this.org + const response: Config = { + certificates: [], + importFromKeystore: [] + }; + let existingCertificates; + if ( + definition?.certificates?.length || + definition?.importFromKeystore?.length + ) { + existingCertificates = await this.org .getConnection() .tooling.query( - `SELECT Id, DeveloperName, MasterLabel, OptionsIsPrivateKeyExportable, KeySize FROM Certificate WHERE DeveloperName IN (${certificatesList})`, + `SELECT Id, DeveloperName, MasterLabel, OptionsIsPrivateKeyExportable, KeySize FROM Certificate`, { scanAll: false } + // BUG in jsforce: query acts with scanAll:true and returns deleted CustomObjects. + // It cannot be disabled. ); - // BUG in jsforce: query acts with scanAll:true and returns deleted CustomObjects. - // It cannot be disabled. + } + if (definition?.certificates?.length) { for (const cert of definition.certificates) { - const existingCert = certificatesResponse.records.find( + const existingCert = existingCertificates.records.find( co => co.DeveloperName === cert.name ); if (existingCert) { @@ -75,6 +83,19 @@ export class CertificateAndKeyManagement extends BrowserforcePlugin { } } } + if (definition?.importFromKeystore?.length) { + for (const cert of definition.importFromKeystore) { + const existingCert = existingCertificates.records.find( + co => co.DeveloperName === cert.name + ); + if (existingCert) { + response.importFromKeystore.push({ + name: existingCert.DeveloperName, + filePath: null + }); + } + } + } return response; } @@ -85,19 +106,26 @@ export class CertificateAndKeyManagement extends BrowserforcePlugin { }; if (state && definition && state.certificates && definition.certificates) { for (const cert of definition.certificates) { - const existingCert = state.certificates.find( - c => c.name === cert.DeveloperName - ); + const existingCert = state.certificates.find(c => c.name === cert.name); if (existingCert) { // move id from state to definition to be retained and used cert._id = existingCert._id; delete existingCert._id; } - response.certificates.push(jsonMergePatch.generate(existingCert, cert)); + const certDiff = semanticallyCleanObject( + removeNullValues(jsonMergePatch.generate(existingCert, cert)), + '_id' + ); + if (certDiff) { + response.certificates.push(certDiff); + } } } - if (definition && definition.importFromKeystore) { - response.importFromKeystore = definition.importFromKeystore; + if (definition?.importFromKeystore?.length) { + response.importFromKeystore = + definition?.importFromKeystore?.filter( + cert => !state.importFromKeystore.find(c => c.name === cert.name) + ) || []; } return removeEmptyValues(response); } diff --git a/src/plugins/security/identity-provider/index.ts b/src/plugins/security/identity-provider/index.ts index 92835693..175df3f4 100644 --- a/src/plugins/security/identity-provider/index.ts +++ b/src/plugins/security/identity-provider/index.ts @@ -1,6 +1,6 @@ import { SalesforceId } from 'jsforce'; import * as jsonMergePatch from 'json-merge-patch'; -import pRetry from 'p-retry'; +import pRetry, { AbortError } from 'p-retry'; import { BrowserforcePlugin } from '../../../plugin'; import { removeNullValues } from '../../utils'; @@ -59,8 +59,10 @@ export class IdentityProvider extends BrowserforcePlugin { .tooling.query( `SELECT Id, DeveloperName FROM Certificate WHERE DeveloperName = '${plan.certificate}'` ); - if (!certsResponse.records.length) { - throw new Error(`Could not find Certificate '${plan.certificate}'`); + if (!certsResponse.totalSize) { + throw new AbortError( + `Could not find Certificate '${plan.certificate}'` + ); } const page = await this.browserforce.openPage(PATHS.EDIT_VIEW); await page.waitForSelector(SELECTORS.EDIT_BUTTON); diff --git a/src/plugins/security/index.e2e-spec.ts b/src/plugins/security/index.e2e-spec.ts index 551755ee..48205db6 100644 --- a/src/plugins/security/index.e2e-spec.ts +++ b/src/plugins/security/index.e2e-spec.ts @@ -1,3 +1,4 @@ +import { core } from '@salesforce/command'; import * as assert from 'assert'; import * as child from 'child_process'; import * as path from 'path'; @@ -49,6 +50,20 @@ describe(`${CertificateAndKeyManagement.name} and ${IdentityProvider.name}`, fun cmd.output.toString() ); }); + it('should not do anything if self-signed certificate is already available', () => { + const cmd = child.spawnSync(path.resolve('bin', 'run'), [ + 'browserforce:apply', + '-f', + path.resolve( + path.join(__dirname, 'identity-provider', 'create-cert-and-enable.json') + ) + ]); + assert.deepStrictEqual(cmd.status, 0, cmd.output.toString()); + assert( + /no action necessary/.test(cmd.output.toString()), + cmd.output.toString() + ); + }); it('should disable Identity Provider', () => { const cmd = child.spawnSync(path.resolve('bin', 'run'), [ 'browserforce:apply', @@ -63,7 +78,7 @@ describe(`${CertificateAndKeyManagement.name} and ${IdentityProvider.name}`, fun cmd.output.toString() ); }); - it.skip('should not do anything if self-signed certificate is already available', () => { + it('should import a cert from a keystore', () => { const cmd = child.spawnSync(path.resolve('bin', 'run'), [ 'browserforce:apply', '-f', @@ -71,17 +86,19 @@ describe(`${CertificateAndKeyManagement.name} and ${IdentityProvider.name}`, fun path.join( __dirname, 'certificate-and-key-management', - 'create-self-signed-cert.json' + 'import-from-keystore.json' ) ) ]); assert.deepStrictEqual(cmd.status, 0, cmd.output.toString()); assert( - /no action necessary/.test(cmd.output.toString()), + /changing 'certificateAndKeyManagement' to '{"importFromKeystore":\[.*"filePath"/.test( + cmd.output.toString() + ), cmd.output.toString() ); }); - it('should import a cert from a keystore', () => { + it('should not do anything if cert is already available in keystore', () => { const cmd = child.spawnSync(path.resolve('bin', 'run'), [ 'browserforce:apply', '-f', @@ -95,10 +112,13 @@ describe(`${CertificateAndKeyManagement.name} and ${IdentityProvider.name}`, fun ]); assert.deepStrictEqual(cmd.status, 0, cmd.output.toString()); assert( - /changing 'certificateAndKeyManagement' to '{"importFromKeystore":\[.*"filePath"/.test( - cmd.output.toString() - ), + /no action necessary/.test(cmd.output.toString()), cmd.output.toString() ); }); + it('should delete certificates using Metadata API', async () => { + const org = await core.Org.create({}); + const conn = org.getConnection(); + await conn.metadata.delete('Certificate', ['identity_provider', 'Dummy']); + }); });