@jayree/sfdx-plugin-org
Version:
A Salesforce CLI plugin containing commands to configure State and Country/Territory Picklists and other org settings.
319 lines • 14 kB
JavaScript
/*
* Copyright 2026, jayree
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import playwright from 'playwright-chromium';
import { tabletojson } from 'tabletojson';
import Debug from 'debug';
import { configSelectors, deactivateStates } from './countrystateconfig.js';
import { readLaunchOptionsFromProject } from './utils.js';
const debug = Debug('jayree:x:y');
export class PuppeteerStateTasks {
currentAddTask;
currentDeactivateTask;
addTasks;
deactivateTasks;
nextAddTaskIndex = -1;
nextDeactivateTaskIndex = -1;
browser;
context;
auth;
countrycode;
countries;
language;
category;
ISOData;
constructor(auth) {
this.auth = auth;
}
static async setHTMLInputElementValue(page, element, newvalue) {
element = element.replace(/:/g, '\\:');
const elementCurrentValue = await page.evaluate((s) => {
const result = document.querySelector(s);
if (result != null) {
return result.value;
}
else {
return '';
}
}, element);
if (!(elementCurrentValue === newvalue)) {
const elementDisabled = await page.evaluate((s) => {
const result = document.querySelector(s);
if (result != null) {
return result.disabled;
}
else {
return true;
}
}, element);
if (!elementDisabled) {
await page.fill(element, newvalue);
return 'changed';
}
else {
return 'disabled';
}
}
else {
return 'unchanged';
}
}
static async setHTMLInputElementChecked(page, element, newstate, waitForEnable) {
element = element.replace(/:/g, '\\:');
const elementCheckedState = await page.evaluate((s) => document.querySelector(s)?.checked, element);
if (!elementCheckedState === newstate) {
if (waitForEnable) {
await page.waitForFunction((s) => {
const val = document.querySelector(s)?.disabled;
return val === false;
}, element, {
timeout: 0,
});
}
const elementDisabledState = await page.evaluate((s) => document.querySelector(s)?.disabled, element);
if (!elementDisabledState) {
await page.click(element);
return 'changed';
}
else {
return 'disabled';
}
}
else {
return 'unchanged';
}
}
async validateParameterCountryCode(countrycode) {
const page = await this.context.newPage();
try {
if (!this.countries) {
await page.goto('https://www.iso.org/obp/ui/#search/code', {
waitUntil: 'networkidle',
});
await page.waitForSelector('.v-grid-tablewrapper');
await page.selectOption('.v-select-select', '8');
await page.waitForFunction(() => document.querySelector('.paging-align-fix')?.innerHTML === '');
let converted = [];
do {
// eslint-disable-next-line no-await-in-loop
const table = await page.evaluate(() => document.querySelector('.v-grid-tablewrapper')?.outerHTML);
converted = tabletojson.convert(table)[0];
} while (converted.length !== converted.map((x) => x['Alpha-2 code']).filter(Boolean).length);
this.countries = converted
.map((x) => ({ name: `${x['English short name']} (${x['Alpha-2 code']})`, value: x['Alpha-2 code'] }))
.filter(Boolean)
.sort((a, b) => {
const x = a.value;
const y = b.value;
return x < y ? -1 : x > y ? 1 : 0;
});
}
if (this.countries.map((x) => x.value).includes(countrycode)) {
await page.goto(`https://www.iso.org/obp/ui/#iso:code:3166:${countrycode}`, {
waitUntil: 'networkidle',
});
await page.waitForSelector('.tablesorter', { state: 'visible' });
this.countrycode = countrycode.toUpperCase();
const table = await page.evaluate(() => document.querySelector('table#subdivision')?.outerHTML);
// eslint-disable-next-line @typescript-eslint/no-shadow
const converted = tabletojson.convert(table)[0];
const jsonParsed = {};
if (typeof converted !== 'undefined') {
converted.forEach((value) => {
const keyval = value['Subdivision category'];
delete value['Subdivision category'];
if (jsonParsed[keyval] === undefined) {
jsonParsed[keyval] = [];
}
jsonParsed[keyval].push(value);
});
}
this.ISOData = jsonParsed;
}
else {
this.countrycode = undefined;
}
}
catch (error) {
debug(error);
}
await page.close();
return { selected: this.countrycode, values: this.countries };
}
validateParameterCategory(category) {
let categories;
if (this.ISOData) {
categories = Object.keys(this.ISOData);
}
if (categories?.includes(category)) {
this.category = category;
}
else if (categories) {
if (categories.length === 1) {
this.category = categories[0];
}
}
else {
this.category = undefined;
}
return { selected: this.category, values: categories };
}
validateParameterLanguage(language) {
let languagecodes;
if (this.category && this.ISOData) {
languagecodes = this.ISOData[this.category]
.map((v) => v['Language code'])
.filter((v, i, s) => s.indexOf(v) === i);
}
if (languagecodes?.includes(language)) {
this.language = language;
}
else if (languagecodes) {
if (languagecodes.length === 1) {
this.language = languagecodes[0];
}
}
else {
this.language = undefined;
}
return { selected: this.language, values: languagecodes };
}
validateData() {
if (this.countrycode === undefined) {
// throw Error('The country code element was not found');
throw Error('Expected --countrycode= to be one of: ' + this.countries.map((x) => x.value).toString());
}
if (this.category === undefined) {
throw Error('Expected --category= to be one of: ' + Object.keys(this.ISOData).toString());
}
if (this.language === undefined) {
const languagecodes = this.ISOData[this.category]
.map((v) => v['Language code'])
.filter((v, i, s) => s.indexOf(v) === i);
throw Error('Expected --language to be one of: ' + languagecodes.toString());
}
this.addTasks = this.ISOData[this.category].filter((v) => v['Language code'] === this.language);
this.deactivateTasks = deactivateStates[this.countrycode]?.[this.category]
? deactivateStates[this.countrycode][this.category]
: [];
return { add: this.addTasks, deactivate: this.deactivateTasks };
}
async setCountryIntegrationValue() {
const page = await this.context.newPage();
await page.goto(this.auth.instanceUrl +
`/i18n/ConfigureCountry.apexp?countryIso=${this.countrycode}&setupid=AddressCleanerOverview`, {
waitUntil: 'networkidle',
});
const setCountrySelector = configSelectors.setCountry;
const editIntValResult = await PuppeteerStateTasks.setHTMLInputElementValue(page, setCountrySelector.editIntVal, this.countrycode);
debug({ editIntValResult });
await page.click(setCountrySelector.save.replace(/:/g, '\\:'));
await page.waitForSelector('.message.confirmM3', { state: 'visible' });
return editIntValResult === 'changed' ? true : false;
}
async executeAdd() {
const task = this.currentAddTask;
const page = await this.context.newPage();
const countrycode = task['3166-2 code'].split('-')[0];
const stateintVal = task['3166-2 code'].split('*')[0];
const stateIsoCode = task['3166-2 code'].split('-')[1].split('*')[0];
const stateName = task['Local variant'] !== '' ? task['Local variant'] : task['Subdivision name'].split('(')[0].trim();
// if (Object.keys(CSconfig.fix).includes(countrycode)) {
// if (Object.keys(CSconfig.fix[countrycode]).includes(stateIsoCode)) {
// // this.ux.log(`Fix ${stateintVal}: ${stateIsoCode} -> ${CSconfig.fix[countrycode][stateIsoCode]}`);
// stateIsoCode = CSconfig.fix[countrycode][stateIsoCode];
// }
// }
await page.goto(this.auth.instanceUrl +
`/i18n/ConfigureState.apexp?countryIso=${countrycode}&setupid=AddressCleanerOverview&stateIso=${stateIsoCode}`, {
waitUntil: 'networkidle',
});
let update = true;
if (await page.$('#errorTitle')) {
update = false;
}
if (update === false) {
await page.goto(this.auth.instanceUrl +
`/i18n/ConfigureNewState.apexp?countryIso=${countrycode}&setupid=AddressCleanerOverview`, {
waitUntil: 'networkidle',
});
await page.waitForSelector('.mainTitle');
}
const selector = update ? configSelectors.update : configSelectors.create;
const editNameResult = await PuppeteerStateTasks.setHTMLInputElementValue(page, selector.editName, stateName);
const editIsoCodeResult = await PuppeteerStateTasks.setHTMLInputElementValue(page, selector.editIsoCode, stateIsoCode);
const editIntValResult = await PuppeteerStateTasks.setHTMLInputElementValue(page, selector.editIntVal, stateintVal);
const editActiveResult = await PuppeteerStateTasks.setHTMLInputElementChecked(page, selector.editActive, true, false);
const editVisibleResult = await PuppeteerStateTasks.setHTMLInputElementChecked(page, selector.editVisible, true, true);
debug({ editNameResult, editIsoCodeResult, editIntValResult, editActiveResult, editVisibleResult });
await page.click(selector.save.replace(/:/g, '\\:'));
await page.waitForSelector('.message.confirmM3', { state: 'visible' });
await page.close();
if (update) {
if (editNameResult === 'changed' || editIntValResult === 'changed' || editVisibleResult === 'changed') {
return 'updated';
}
}
else if (editNameResult === 'changed' || editIntValResult === 'changed' || editVisibleResult === 'changed') {
return 'created';
}
return 'skipped';
}
async executeDeactivate() {
const task = this.currentDeactivateTask;
const page = await this.context.newPage();
const countrycode = task.split('-')[0];
const stateIsoCode = task.split('-')[1].split('*')[0];
await page.goto(this.auth.instanceUrl +
`/i18n/ConfigureState.apexp?countryIso=${countrycode}&setupid=AddressCleanerOverview&stateIso=${stateIsoCode}`, {
waitUntil: 'networkidle',
});
const selector = configSelectors.update;
const editVisibleResult = await PuppeteerStateTasks.setHTMLInputElementChecked(page, selector.editVisible, false, false);
const editActiveResult = await PuppeteerStateTasks.setHTMLInputElementChecked(page, selector.editActive, false, false);
debug({ editVisibleResult, editActiveResult });
await page.click(selector.save.replace(/:/g, '\\:'));
await page.waitForSelector('.message.confirmM3', { state: 'visible' });
await page.close();
return editVisibleResult === 'changed' ? true : false;
}
getNextAdd() {
this.nextAddTaskIndex = this.nextAddTaskIndex + 1;
this.currentAddTask = this.addTasks[this.nextAddTaskIndex];
return this;
}
getNextDeactivate() {
this.nextDeactivateTaskIndex = this.nextDeactivateTaskIndex + 1;
this.currentDeactivateTask = this.deactivateTasks[this.nextDeactivateTaskIndex];
return this;
}
async close() {
if (this.browser) {
await this.browser.close();
}
}
async open() {
if (!this.browser) {
this.browser = await playwright['chromium'].launch(await readLaunchOptionsFromProject());
this.context = await this.browser.newContext();
const login = await this.context.newPage();
await login.goto(`${this.auth.instanceUrl}/secur/frontdoor.jsp?sid=${this.auth.accessToken}`, {
waitUntil: 'networkidle',
timeout: 300_000,
});
}
}
}
//# sourceMappingURL=statetasks.js.map