From bad6dfbbb9bab8d0b3466f8f2d069e132fb871c9 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Mon, 9 Oct 2023 13:24:59 -0500 Subject: [PATCH 01/67] feat(wip): crdt-configfile --- package.json | 2 +- src/config/config.ts | 19 ++- src/config/configFile.ts | 13 +- src/config/configStackTypes.ts | 24 +++ src/config/configStore.ts | 103 +++++-------- src/config/lwwMap.ts | 88 +++++++++++ src/config/lwwRegister.ts | 43 ++++++ src/config/tokensConfig.ts | 2 +- src/exported.ts | 4 +- src/org/org.ts | 2 +- src/org/orgConfigProperties.ts | 2 +- src/sfProject.ts | 2 +- .../accessors/aliasAccessor.ts | 4 +- src/stateAggregator/accessors/orgAccessor.ts | 3 +- .../accessors/tokenAccessor.ts | 2 +- src/testSetup.ts | 30 +--- src/util/uniqid.ts | 31 ++++ test/unit/config/configFileTest.ts | 8 +- test/unit/config/configStoreTest.ts | 97 ++++++------ test/unit/config/configTest.ts | 2 +- test/unit/config/lwwMapTest.ts | 139 ++++++++++++++++++ test/unit/projectTest.ts | 20 +-- .../accessors/aliasAccessorTest.ts | 3 +- .../accessors/orgAccessorTest.ts | 3 +- .../accessors/sandboxAccessorTest.ts | 3 +- .../accessors/tokenAccessorTest.ts | 3 +- test/unit/testSetupTest.ts | 3 +- yarn.lock | 50 ++++++- 28 files changed, 518 insertions(+), 187 deletions(-) create mode 100644 src/config/configStackTypes.ts create mode 100644 src/config/lwwMap.ts create mode 100644 src/config/lwwRegister.ts create mode 100644 src/util/uniqid.ts create mode 100644 test/unit/config/lwwMapTest.ts diff --git a/package.json b/package.json index 39b8a6f07d..1da0199a20 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "benchmark": "^2.1.4", "chai": "^4.3.10", "chai-string": "^1.5.0", - "eslint": "^8.50.0", + "eslint": "^8.51.0", "eslint-config-prettier": "^8.10.0", "eslint-config-salesforce": "^2.0.2", "eslint-config-salesforce-license": "^0.2.0", diff --git a/src/config/config.ts b/src/config/config.ts index 99cc21c69c..49280c2736 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -7,8 +7,8 @@ import { dirname as pathDirname, join as pathJoin } from 'path'; import * as fs from 'fs'; -import { keyBy, parseJsonMap, set } from '@salesforce/kit'; -import { Dictionary, ensure, isString, JsonPrimitive, Nullable } from '@salesforce/ts-types'; +import { keyBy, parseJsonMap } from '@salesforce/kit'; +import { Dictionary, ensure, isString, JsonCollection, JsonPrimitive, Nullable } from '@salesforce/ts-types'; import { Global } from '../global'; import { Logger } from '../logger/logger'; import { Messages } from '../messages'; @@ -17,7 +17,7 @@ import { SfdcUrl } from '../util/sfdcUrl'; import { ORG_CONFIG_ALLOWED_PROPERTIES, OrgConfigProperties } from '../org/orgConfigProperties'; import { Lifecycle } from '../lifecycleEvents'; import { ConfigFile } from './configFile'; -import { ConfigContents, ConfigValue } from './configStore'; +import { ConfigContents, ConfigValue } from './configStackTypes'; Messages.importMessagesDirectory(__dirname); const messages = Messages.loadMessages('@salesforce/core', 'config'); @@ -381,15 +381,14 @@ export class Config extends ConfigFile { public static async update(isGlobal: boolean, propertyName: string, value?: ConfigValue): Promise { const config = await Config.create({ isGlobal }); - const content = await config.read(); + await config.read(); - if (value == null) { - delete content[propertyName]; + if (value == null || value === undefined) { + config.unset(propertyName); } else { - set(content, propertyName, value); + config.set(propertyName, value); } - - return config.write(content); + return config.write(); } /** @@ -484,7 +483,7 @@ export class Config extends ConfigFile { * @param key The property to set. * @param value The value of the property. */ - public set(key: string, value: JsonPrimitive): ConfigProperties { + public set(key: string, value: JsonPrimitive | JsonCollection): ConfigProperties { const property = Config.allowedProperties.find((allowedProp) => allowedProp.key === key); if (!property) { diff --git a/src/config/configFile.ts b/src/config/configFile.ts index 1bb38b45d6..b987793206 100644 --- a/src/config/configFile.ts +++ b/src/config/configFile.ts @@ -15,7 +15,8 @@ import { Global } from '../global'; import { Logger } from '../logger/logger'; import { SfError } from '../sfError'; import { resolveProjectPath, resolveProjectPathSync } from '../util/internal'; -import { BaseConfigStore, ConfigContents } from './configStore'; +import { BaseConfigStore } from './configStore'; +import { ConfigContents } from './configStackTypes'; /** * Represents a json config file used to manage settings and state. Global config @@ -160,8 +161,12 @@ export class ConfigFile< // Only need to read config files once. They are kept up to date // internally and updated persistently via write(). if (!this.hasRead || force) { - this.logger.info(`Reading config file: ${this.getPath()}`); - const obj = parseJsonMap(await fs.promises.readFile(this.getPath(), 'utf8'), this.getPath()); + this.logger.info( + `Reading config file: ${this.getPath()} because ${ + !this.hasRead ? 'hasRead is false' : 'force parameter is true' + }` + ); + const obj = parseJsonMap

(await fs.promises.readFile(this.getPath(), 'utf8'), this.getPath()); this.setContentsFromObject(obj); } // Necessarily set this even when an error happens to avoid infinite re-reading. @@ -197,7 +202,7 @@ export class ConfigFile< // internally and updated persistently via write(). if (!this.hasRead || force) { this.logger.info(`Reading config file: ${this.getPath()}`); - const obj = parseJsonMap(fs.readFileSync(this.getPath(), 'utf8')); + const obj = parseJsonMap

(fs.readFileSync(this.getPath(), 'utf8')); this.setContentsFromObject(obj); } return this.getContents(); diff --git a/src/config/configStackTypes.ts b/src/config/configStackTypes.ts new file mode 100644 index 0000000000..420c76f29d --- /dev/null +++ b/src/config/configStackTypes.ts @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import { Dictionary, AnyJson } from '@salesforce/ts-types'; + +export type Key

= Extract; +/** + * The allowed types stored in a config store. + */ + +export type ConfigValue = AnyJson; +/** + * The type of entries in a config store defined by the key and value type of {@link ConfigContents}. + */ + +export type ConfigEntry = [string, ConfigValue]; +/** + * The type of content a config stores. + */ + +export type ConfigContents = Dictionary; diff --git a/src/config/configStore.ts b/src/config/configStore.ts index 52b4eb348a..a735461983 100644 --- a/src/config/configStore.ts +++ b/src/config/configStore.ts @@ -6,37 +6,12 @@ */ import { AsyncOptionalCreatable, cloneJson, set } from '@salesforce/kit'; -import { isPlainObject } from '@salesforce/ts-types'; -import { - AnyJson, - definiteEntriesOf, - definiteValuesOf, - Dictionary, - get, - isJsonMap, - isString, - JsonMap, - Optional, -} from '@salesforce/ts-types'; +import { entriesOf, isPlainObject } from '@salesforce/ts-types'; +import { definiteEntriesOf, definiteValuesOf, get, isJsonMap, isString, JsonMap, Optional } from '@salesforce/ts-types'; import { Crypto } from '../crypto/crypto'; import { SfError } from '../sfError'; - -/** - * The allowed types stored in a config store. - */ -export type ConfigValue = AnyJson; - -/** - * The type of entries in a config store defined by the key and value type of {@link ConfigContents}. - */ -export type ConfigEntry = [string, ConfigValue]; - -/** - * The type of content a config stores. - */ -export type ConfigContents = Dictionary; - -export type Key

= Extract; +import { LWWMap } from './lwwMap'; +import { ConfigContents, ConfigEntry, ConfigValue, Key } from './configStackTypes'; /** * An interface for a config object with a persistent store. @@ -85,7 +60,7 @@ export abstract class BaseConfigStore< protected crypto?: Crypto; // Initialized in setContents - private contents!: P; + private contents = new LWWMap

(); private statics = this.constructor as typeof BaseConfigStore; /** @@ -104,7 +79,7 @@ export abstract class BaseConfigStore< * Returns an array of {@link ConfigEntry} for each element in the config. */ public entries(): ConfigEntry[] { - return definiteEntriesOf(this.contents); + return definiteEntriesOf(this.contents.value ?? {}); } /** @@ -118,7 +93,7 @@ export abstract class BaseConfigStore< public get(key: string, decrypt?: boolean): V; public get>(key: K | string, decrypt = false): P[K] | ConfigValue { const k = key as string; - let value = this.getMethod(this.contents, k); + let value = this.getMethod(this.contents.value ?? {}, k); if (this.hasEncryption() && decrypt) { if (isJsonMap(value)) { @@ -144,24 +119,23 @@ export abstract class BaseConfigStore< /** * Returns a boolean asserting whether a value has been associated to the key in the config object or not. * - * @param key The key. Supports query key like `a.b[0]`. */ public has(key: string): boolean { - return !!this.getMethod(this.contents, key); + return this.contents.has(key) ?? false; } /** * Returns an array that contains the keys for each element in the config object. */ public keys(): Array> { - return Object.keys(this.contents) as Array>; + return Object.keys(this.contents.value ?? {}) as Array>; } /** * Sets the value for the key in the config object. This will override the existing value. * To do a partial update, use {@link BaseConfigStore.update}. * - * @param key The key. Supports query key like `a.b[0]`. + * @param key The key. * @param value The value. */ public set>(key: K, value: P[K]): void; @@ -174,7 +148,13 @@ export abstract class BaseConfigStore< value = this.encrypt(value) as P[K]; } } - this.setMethod(this.contents, key as string, value); + // undefined value means unset + if (value === undefined) { + this.unset(key as string); + } else { + // @ts-expect-error TODO: why isn't key guaranteed to be a string + this.contents.set(key, value); + } } /** @@ -198,16 +178,11 @@ export abstract class BaseConfigStore< * Returns `true` if an element in the config object existed and has been removed, or `false` if the element does not * exist. {@link BaseConfigStore.has} will return false afterwards. * - * @param key The key. Supports query key like `a.b[0]`. + * @param key The key */ public unset(key: string): boolean { if (this.has(key)) { - if (this.contents[key]) { - delete this.contents[key]; - } else { - // It is a query key, so just set it to undefined - this.setMethod(this.contents, key, undefined); - } + this.contents.delete(key); return true; } return false; @@ -220,21 +195,21 @@ export abstract class BaseConfigStore< * @param keys The keys. Supports query keys like `a.b[0]`. */ public unsetAll(keys: string[]): boolean { - return keys.reduce((val: boolean, key) => val && this.unset(key), true); + return keys.map((key) => this.unset(key)).every(Boolean); } /** * Removes all key/value pairs from the config object. */ public clear(): void { - this.contents = {} as P; + this.keys().map((key) => this.unset(key)); } /** * Returns an array that contains the values for each element in the config object. */ public values(): ConfigValue[] { - return definiteValuesOf(this.contents); + return definiteValuesOf(this.contents.value ?? {}); } /** @@ -248,13 +223,10 @@ export abstract class BaseConfigStore< * */ public getContents(decrypt = false): P { - if (!this.contents) { - this.setContents(); - } if (this.hasEncryption() && decrypt) { - return this.recursiveDecrypt(cloneJson(this.contents)) as P; + return this.recursiveDecrypt(cloneJson(this.contents?.value ?? {})) as P; } - return this.contents; + return this.contents?.value ?? ({} as P); } /** @@ -266,7 +238,9 @@ export abstract class BaseConfigStore< if (this.hasEncryption()) { contents = this.recursiveEncrypt(contents); } - this.contents = contents; + entriesOf(contents).map(([key, value]) => { + this.contents.set(key, value); + }); } /** @@ -275,10 +249,7 @@ export abstract class BaseConfigStore< * @param {function} actionFn The function `(key: string, value: ConfigValue) => void` to be called for each element. */ public forEach(actionFn: (key: string, value: ConfigValue) => void): void { - const entries = this.entries(); - for (const entry of entries) { - actionFn(entry[0], entry[1]); - } + this.entries().map((entry) => actionFn(entry[0], entry[1])); } /** @@ -290,11 +261,7 @@ export abstract class BaseConfigStore< */ public async awaitEach(actionFn: (key: string, value: ConfigValue) => Promise): Promise { const entries = this.entries(); - for (const entry of entries) { - // prevent ConfigFile collision bug - // eslint-disable-next-line no-await-in-loop - await actionFn(entry[0], entry[1]); - } + await Promise.all(entries.map((entry) => actionFn(entry[0], entry[1]))); } /** @@ -302,7 +269,7 @@ export abstract class BaseConfigStore< * Same as calling {@link ConfigStore.getContents} */ public toObject(): JsonMap { - return this.contents; + return this.contents.value ?? {}; } /** @@ -310,15 +277,15 @@ export abstract class BaseConfigStore< * * @param obj The object. */ - public setContentsFromObject(obj: U): void { - this.contents = (this.hasEncryption() ? this.recursiveEncrypt(obj) : {}) as P; - Object.entries(obj).forEach(([key, value]) => { - this.setMethod(this.contents, key, value); + public setContentsFromObject(obj: U): void { + const objForWrite = this.hasEncryption() ? this.recursiveEncrypt(obj) : obj; + entriesOf(objForWrite).map(([key, value]) => { + this.set(key, value); }); } protected getEncryptedKeys(): Array { - return [...(this.options?.encryptedKeys ?? []), ...(this.statics?.encryptedKeys || [])]; + return [...(this.options?.encryptedKeys ?? []), ...(this.statics?.encryptedKeys ?? [])]; } /** diff --git a/src/config/lwwMap.ts b/src/config/lwwMap.ts new file mode 100644 index 0000000000..e5441d4852 --- /dev/null +++ b/src/config/lwwMap.ts @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { entriesOf } from '@salesforce/ts-types'; +import { uniqid } from '../util/uniqid'; +import { LWWRegister } from './lwwRegister'; +import { ConfigContents, Key } from './configStackTypes'; + +export const SYMBOL_FOR_DELETED = 'UNIQUE_IDENTIFIER_FOR_DELETED' as const; + +type State

= { + [Property in keyof P]: LWWRegister['state']; +}; + +export class LWWMap

{ + public readonly id: string; + /** map of key to LWWRegister. Used for managing conflicts */ + #data = new Map>(); + + public constructor(id?: string, state?: State

) { + this.id = id ?? uniqid(); + + // create a new register for each key in the initial state + for (const [key, register] of entriesOf(state ?? {})) { + this.#data.set(key, new LWWRegister(this.id, register)); + } + } + + public get value(): P { + return Object.fromEntries( + Array.from(this.#data.entries()) + .filter(([, register]) => register.value !== SYMBOL_FOR_DELETED) + .map(([key, register]) => [key, register.value]) + ) as P; + } + + public get state(): State

{ + return Object.fromEntries( + Array.from(this.#data.entries()) + // .filter(([, register]) => Boolean(register)) + .map(([key, register]) => [key, register.state]) + ) as State

; + } + + public merge(state: State

): State

{ + // recursively merge each key's register with the incoming state for that key + for (const [key, remote] of entriesOf(state)) { + const local = this.#data.get(key); + // if the register already exists, merge it with the incoming state + if (local) local.merge(remote); + // otherwise, instantiate a new `LWWRegister` with the incoming state + else this.#data.set(key, new LWWRegister(this.id, remote)); + } + return this.state; + } + + public set>(key: K, value: P[K]): void { + // get the register at the given key + const register = this.#data.get(key); + + // if the register already exists, set the value + if (register) register.set(value); + // otherwise, instantiate a new `LWWRegister` with the value + else this.#data.set(key, new LWWRegister(this.id, { peer: this.id, timestamp: process.hrtime.bigint(), value })); + } + + // TODO: how to handle the deep `get` that is currently allowed ex: get('foo.bar.baz') + public get>(key: K): P[K] | undefined { + // map loses the typing + const value = this.#data.get(key)?.value; + if (value === SYMBOL_FOR_DELETED) return undefined; + return value as P[K]; + } + + public delete(key: string): void { + // set the register to null, if it exists + this.#data.get(key)?.set(SYMBOL_FOR_DELETED); + } + + public has(key: string): boolean { + // if a register doesn't exist or its value is null, the map doesn't contain the key + return this.#data.has(key) && this.#data.get(key)?.value !== SYMBOL_FOR_DELETED; + } +} diff --git a/src/config/lwwRegister.ts b/src/config/lwwRegister.ts new file mode 100644 index 0000000000..e47e78be81 --- /dev/null +++ b/src/config/lwwRegister.ts @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +type LWWRegisterState = { peer: string; timestamp: bigint; value: T }; + +/** a CRDT implementation. Uses timestamps to resolve conflicts when updating the value (last write wins) + * mostly based on https://jakelazaroff.com/words/an-interactive-intro-to-crdts/ + * + * @param T the type of the value stored in the register + */ +export class LWWRegister { + public readonly id: string; + public state: LWWRegisterState; + + public constructor(id: string, state: LWWRegisterState) { + this.id = id; + this.state = state; + } + + public get value(): T { + return this.state.value; + } + + public set(value: T): void { + // set the peer ID to the local ID, timestamp it and set the value + this.state = { peer: this.id, timestamp: process.hrtime.bigint(), value }; + } + + public merge(incoming: LWWRegisterState): LWWRegisterState { + // only update if the incoming timestamp is greater than the local timestamp + // console.log(`incoming: ${}`); + // console.log(`local: ${JSON.stringify(this.state)}`); + if (incoming.timestamp > this.state.timestamp) { + this.state = incoming; + } + // TODO: if the timestamps match, use the peer ID to break the tie (prefer self?) + return this.state; + } +} diff --git a/src/config/tokensConfig.ts b/src/config/tokensConfig.ts index 05ea646732..a284572e0d 100644 --- a/src/config/tokensConfig.ts +++ b/src/config/tokensConfig.ts @@ -9,7 +9,7 @@ import { Optional } from '@salesforce/ts-types'; import { SfTokens } from '../stateAggregator'; import { ConfigFile } from './configFile'; -import { ConfigContents, ConfigValue } from './configStore'; +import { ConfigContents, ConfigValue } from './configStackTypes'; export class TokensConfig extends ConfigFile { protected static encryptedKeys = [/token/i, /password/i, /secret/i]; diff --git a/src/exported.ts b/src/exported.ts index 0ac144309b..d774751f1c 100644 --- a/src/exported.ts +++ b/src/exported.ts @@ -16,8 +16,8 @@ export { TTLConfig } from './config/ttlConfig'; export { envVars, EnvironmentVariable, SUPPORTED_ENV_VARS, EnvVars } from './config/envVars'; -export { ConfigContents, ConfigEntry, ConfigStore, ConfigValue } from './config/configStore'; - +export { ConfigStore } from './config/configStore'; +export { ConfigEntry, ConfigContents, ConfigValue } from './config/configStackTypes'; export { SfTokens, StateAggregator } from './stateAggregator'; export { DeviceOauthService, DeviceCodeResponse, DeviceCodePollingResponse } from './deviceOauthService'; diff --git a/src/org/org.ts b/src/org/org.ts index c061b89b3f..be6d753d7a 100644 --- a/src/org/org.ts +++ b/src/org/org.ts @@ -26,7 +26,7 @@ import { import { HttpRequest, SaveResult } from 'jsforce'; import { Config } from '../config/config'; import { ConfigAggregator } from '../config/configAggregator'; -import { ConfigContents } from '../config/configStore'; +import { ConfigContents } from '../config/configStackTypes'; import { OrgUsersConfig } from '../config/orgUsersConfig'; import { Global } from '../global'; import { Lifecycle } from '../lifecycleEvents'; diff --git a/src/org/orgConfigProperties.ts b/src/org/orgConfigProperties.ts index 2d1eea7056..9be5d9d200 100644 --- a/src/org/orgConfigProperties.ts +++ b/src/org/orgConfigProperties.ts @@ -7,7 +7,7 @@ import { join as pathJoin } from 'path'; import { isString } from '@salesforce/ts-types'; -import { ConfigValue } from '../config/configStore'; +import { ConfigValue } from '../config/configStackTypes'; import { Messages } from '../messages'; import { SfdcUrl } from '../util/sfdcUrl'; import { validateApiVersion } from '../util/sfdc'; diff --git a/src/sfProject.ts b/src/sfProject.ts index b053fee020..52975a70cb 100644 --- a/src/sfProject.ts +++ b/src/sfProject.ts @@ -11,7 +11,7 @@ import { Dictionary, ensure, JsonMap, Nullable, Optional } from '@salesforce/ts- import { SfdcUrl } from './util/sfdcUrl'; import { ConfigAggregator } from './config/configAggregator'; import { ConfigFile } from './config/configFile'; -import { ConfigContents } from './config/configStore'; +import { ConfigContents } from './config/configStackTypes'; import { SchemaValidator } from './schema/validator'; import { resolveProjectPath, resolveProjectPathSync, SFDX_PROJECT_JSON } from './util/internal'; diff --git a/src/stateAggregator/accessors/aliasAccessor.ts b/src/stateAggregator/accessors/aliasAccessor.ts index b5701f53f9..9caa4540d2 100644 --- a/src/stateAggregator/accessors/aliasAccessor.ts +++ b/src/stateAggregator/accessors/aliasAccessor.ts @@ -16,7 +16,7 @@ import { AsyncOptionalCreatable, ensureArray } from '@salesforce/kit'; import { Nullable } from '@salesforce/ts-types'; import { Global } from '../../global'; import { AuthFields } from '../../org/authInfo'; -import { ConfigContents } from '../../config/configStore'; +import { ConfigContents } from '../../config/configStackTypes'; import { SfError } from '../../sfError'; import { SfToken } from './tokenAccessor'; @@ -193,7 +193,7 @@ export class AliasAccessor extends AsyncOptionalCreatable { /** * @deprecated the set/unset methods now write to the file when called. Use (un)setAndSave instead of calling (un)set and then calling write() */ - public async write(): Promise { + public async write(): Promise> { return Promise.resolve(this.getAll()); } diff --git a/src/stateAggregator/accessors/orgAccessor.ts b/src/stateAggregator/accessors/orgAccessor.ts index 2fa317aaac..38dc9d08f0 100644 --- a/src/stateAggregator/accessors/orgAccessor.ts +++ b/src/stateAggregator/accessors/orgAccessor.ts @@ -13,7 +13,7 @@ import { AuthInfoConfig } from '../../config/authInfoConfig'; import { Global } from '../../global'; import { AuthFields } from '../../org'; import { ConfigFile } from '../../config/configFile'; -import { ConfigContents } from '../../config/configStore'; +import { ConfigContents } from '../../config/configStackTypes'; import { Logger } from '../../logger/logger'; import { Messages } from '../../messages'; import { Lifecycle } from '../../lifecycleEvents'; @@ -26,6 +26,7 @@ function chunk(array: T[], chunkSize: number): T[][] { export abstract class BaseOrgAccessor extends AsyncOptionalCreatable { private configs: Map> = new Map(); + /** map of Org files by username */ private contents: Map = new Map(); private logger!: Logger; diff --git a/src/stateAggregator/accessors/tokenAccessor.ts b/src/stateAggregator/accessors/tokenAccessor.ts index 67068ed91d..072216c300 100644 --- a/src/stateAggregator/accessors/tokenAccessor.ts +++ b/src/stateAggregator/accessors/tokenAccessor.ts @@ -75,7 +75,7 @@ export class TokenAccessor extends AsyncOptionalCreatable { } /** - * Unet the token for the provided name. + * Unset the token for the provided name. * * @param name */ diff --git a/src/testSetup.ts b/src/testSetup.ts index 959ca57d71..8d2f7f511d 100644 --- a/src/testSetup.ts +++ b/src/testSetup.ts @@ -10,11 +10,9 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-call */ import * as fs from 'node:fs'; -import { randomBytes } from 'crypto'; import { EventEmitter } from 'events'; import { tmpdir as osTmpdir } from 'os'; import { basename, join as pathJoin, dirname } from 'path'; -import * as util from 'util'; import { SinonSandbox, SinonStatic, SinonStub } from 'sinon'; import { stubMethod } from '@salesforce/ts-sinon'; @@ -32,7 +30,7 @@ import { } from '@salesforce/ts-types'; import { ConfigAggregator } from './config/configAggregator'; import { ConfigFile } from './config/configFile'; -import { ConfigContents } from './config/configStore'; +import { ConfigContents } from './config/configStackTypes'; import { Connection } from './org/connection'; import { Crypto } from './crypto/crypto'; import { Logger } from './logger/logger'; @@ -45,6 +43,7 @@ import { OrgAccessor, StateAggregator } from './stateAggregator'; import { AuthFields, Org, SandboxFields, User, UserFields } from './org'; import { SandboxAccessor } from './stateAggregator/accessors/sandboxAccessor'; import { Global } from './global'; +import { uniqid } from './util/uniqid'; /** * Different parts of the system that are mocked out. They can be restored for @@ -434,28 +433,6 @@ export class TestContext { } } -/** - * A function to generate a unique id and return it in the context of a template, if supplied. - * - * A template is a string that can contain `${%s}` to be replaced with a unique id. - * If the template contains the "%s" placeholder, it will be replaced with the unique id otherwise the id will be appended to the template. - * - * @param options an object with the following properties: - * - template: a template string. - * - length: the length of the unique id as presented in hexadecimal. - */ -export function uniqid(options?: { template?: string; length?: number }): string { - const uniqueString = randomBytes(Math.ceil((options?.length ?? 32) / 2.0)) - .toString('hex') - .slice(0, options?.length ?? 32); - if (!options?.template) { - return uniqueString; - } - return options.template.includes('%s') - ? util.format(options.template, uniqueString) - : `${options.template}${uniqueString}`; -} - function getTestLocalPath(uid: string): string { return pathJoin(osTmpdir(), uid, 'sfdx_core', 'local'); } @@ -1073,7 +1050,8 @@ export class MockTestOrgData { config.password = crypto.encrypt(this.password); } - return config as AuthFields; + // remove "undefined" properties that don't exist in actual files + return Object.fromEntries(Object.entries(config).filter(([, v]) => v !== undefined)) as AuthFields; } /** diff --git a/src/util/uniqid.ts b/src/util/uniqid.ts new file mode 100644 index 0000000000..7ac2f3d628 --- /dev/null +++ b/src/util/uniqid.ts @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import { randomBytes } from 'crypto'; +import * as util from 'util'; + +/** + * A function to generate a unique id and return it in the context of a template, if supplied. + * + * A template is a string that can contain `${%s}` to be replaced with a unique id. + * If the template contains the "%s" placeholder, it will be replaced with the unique id otherwise the id will be appended to the template. + * + * @param options an object with the following properties: + * - template: a template string. + * - length: the length of the unique id as presented in hexadecimal. + */ + +export function uniqid(options?: { template?: string; length?: number }): string { + const uniqueString = randomBytes(Math.ceil((options?.length ?? 32) / 2)) + .toString('hex') + .slice(0, options?.length ?? 32); + if (!options?.template) { + return uniqueString; + } + return options.template.includes('%s') + ? util.format(options.template, uniqueString) + : `${options.template}${uniqueString}`; +} diff --git a/test/unit/config/configFileTest.ts b/test/unit/config/configFileTest.ts index 5e806d58a5..de54519442 100644 --- a/test/unit/config/configFileTest.ts +++ b/test/unit/config/configFileTest.ts @@ -212,8 +212,8 @@ describe('Config', () => { const expected = { test: 'test' }; const actual = await config.write(expected); - expect(expected).to.equal(actual); - expect(expected).to.equal(config.getContents()); + expect(expected).to.deep.equal(actual); + expect(expected).to.deep.equal(config.getContents()); // expect(mkdirpStub.called).to.be.true; expect(writeJson.called).to.be.true; }); @@ -225,8 +225,8 @@ describe('Config', () => { const expected = { test: 'test' }; const actual = config.writeSync(expected); - expect(expected).to.equal(actual); - expect(expected).to.equal(config.getContents()); + expect(expected).to.deep.equal(actual); + expect(expected).to.deep.equal(config.getContents()); expect(mkdirpStub.called).to.be.true; expect(writeJson.called).to.be.true; }); diff --git a/test/unit/config/configStoreTest.ts b/test/unit/config/configStoreTest.ts index 4d92c23cba..4378064be7 100644 --- a/test/unit/config/configStoreTest.ts +++ b/test/unit/config/configStoreTest.ts @@ -6,7 +6,8 @@ */ import { expect } from 'chai'; import { AuthInfoConfig } from '../../../src/config/authInfoConfig'; -import { BaseConfigStore, ConfigContents } from '../../../src/config/configStore'; +import { BaseConfigStore } from '../../../src/config/configStore'; +import { ConfigContents } from '../../../src/config/configStackTypes'; import { AuthFields } from '../../../src/org/authInfo'; import { TestContext } from '../../../src/testSetup'; @@ -145,56 +146,58 @@ describe('ConfigStore', () => { expect(config.get('owner', true).superPassword).to.equal(expected); }); - it('encrypts nested query key using dot notation', async () => { - const expected = 'a29djf0kq3dj90d3q'; - const config = await CarConfig.create(); - config.set('owner.creditCardNumber', expected); - // encrypted - expect(config.get('owner.creditCardNumber')).to.not.equal(expected); - // decrypted - expect(config.get('owner.creditCardNumber', true)).to.equal(expected); - }); + describe.skip('TODO: set with deep (dots/accessors) keys', () => { + it('encrypts nested query key using dot notation', async () => { + const expected = 'a29djf0kq3dj90d3q'; + const config = await CarConfig.create(); + config.set('owner.creditCardNumber', expected); + // encrypted + expect(config.get('owner.creditCardNumber')).to.not.equal(expected); + // decrypted + expect(config.get('owner.creditCardNumber', true)).to.equal(expected); + }); - it('encrypts nested query key using accessor with single quotes', async () => { - const expected = 'a29djf0kq3dj90d3q'; - const config = await CarConfig.create(); - config.set('owner["creditCardNumber"]', expected); - // encrypted - expect(config.get("owner['creditCardNumber']")).to.not.equal(expected); - // decrypted - expect(config.get("owner['creditCardNumber']", true)).to.equal(expected); - }); + it('encrypts nested query key using accessor with single quotes', async () => { + const expected = 'a29djf0kq3dj90d3q'; + const config = await CarConfig.create(); + config.set('owner["creditCardNumber"]', expected); + // encrypted + expect(config.get("owner['creditCardNumber']")).to.not.equal(expected); + // decrypted + expect(config.get("owner['creditCardNumber']", true)).to.equal(expected); + }); - it('encrypts nested query key using accessor with double quotes', async () => { - const expected = 'a29djf0kq3dj90d3q'; - const config = await CarConfig.create(); - config.set('owner["creditCardNumber"]', expected); - // encrypted - expect(config.get('owner["creditCardNumber"]')).to.not.equal(expected); - // decrypted - expect(config.get('owner["creditCardNumber"]', true)).to.equal(expected); - }); + it('encrypts nested query key using accessor with double quotes', async () => { + const expected = 'a29djf0kq3dj90d3q'; + const config = await CarConfig.create(); + config.set('owner["creditCardNumber"]', expected); + // encrypted + expect(config.get('owner["creditCardNumber"]')).to.not.equal(expected); + // decrypted + expect(config.get('owner["creditCardNumber"]', true)).to.equal(expected); + }); - it('encrypts nested query special key using accessor with single quotes', async () => { - const expected = 'a29djf0kq3dj90d3q'; - const config = await CarConfig.create(); - const query = `owner['${specialKey}']`; - config.set(query, expected); - // encrypted - expect(config.get(query)).to.not.equal(expected); - // decrypted - expect(config.get(query, true)).to.equal(expected); - }); + it('encrypts nested query special key using accessor with single quotes', async () => { + const expected = 'a29djf0kq3dj90d3q'; + const config = await CarConfig.create(); + const query = `owner['${specialKey}']`; + config.set(query, expected); + // encrypted + expect(config.get(query)).to.not.equal(expected); + // decrypted + expect(config.get(query, true)).to.equal(expected); + }); - it('encrypts nested query special key using accessor with double quotes', async () => { - const expected = 'a29djf0kq3dj90d3q'; - const config = await CarConfig.create(); - const query = `owner["${specialKey}"]`; - config.set(query, expected); - // encrypted - expect(config.get(query)).to.not.equal(expected); - // decrypted - expect(config.get(query, true)).to.equal(expected); + it('encrypts nested query special key using accessor with double quotes', async () => { + const expected = 'a29djf0kq3dj90d3q'; + const config = await CarConfig.create(); + const query = `owner["${specialKey}"]`; + config.set(query, expected); + // encrypted + expect(config.get(query)).to.not.equal(expected); + // decrypted + expect(config.get(query, true)).to.equal(expected); + }); }); it('decrypt returns copies', async () => { diff --git a/test/unit/config/configTest.ts b/test/unit/config/configTest.ts index ea621713ea..be6316d5f1 100644 --- a/test/unit/config/configTest.ts +++ b/test/unit/config/configTest.ts @@ -15,7 +15,7 @@ import { ensureString, JsonMap } from '@salesforce/ts-types'; import { expect } from 'chai'; import { Config, ConfigPropertyMeta } from '../../../src/config/config'; import { ConfigFile } from '../../../src/config/configFile'; -import { ConfigContents } from '../../../src/config/configStore'; +import { ConfigContents } from '../../../src/config/configStackTypes'; import { OrgConfigProperties } from '../../../src/exported'; import { shouldThrowSync, TestContext } from '../../../src/testSetup'; diff --git a/test/unit/config/lwwMapTest.ts b/test/unit/config/lwwMapTest.ts new file mode 100644 index 0000000000..eb1dabc302 --- /dev/null +++ b/test/unit/config/lwwMapTest.ts @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import { expect, config } from 'chai'; +import { LWWMap, SYMBOL_FOR_DELETED } from '../../../src/config/lwwMap'; + +config.truncateThreshold = 0; +describe('LWWMap', () => { + describe('all properties are known', () => { + const state = { + foo: { value: 'bar', timestamp: process.hrtime.bigint(), peer: 'a' }, + baz: { value: 'qux', timestamp: process.hrtime.bigint(), peer: 'a' }, + }; + let lwwMap: LWWMap<{ foo: string; baz: string; opt?: number; optNull?: null }>; + + beforeEach(() => { + lwwMap = new LWWMap('test', state); + }); + + it('should initialize with the correct state', () => { + expect(lwwMap.state).to.deep.equal(state); + }); + + it('should get the correct value for the entire object', () => { + expect(lwwMap.value).to.deep.equal({ foo: 'bar', baz: 'qux' }); + }); + + it('has when it has', () => { + expect(lwwMap.has('foo')).to.be.true; + }); + + it('sets a new key', () => { + lwwMap.set('opt', 4); + expect(lwwMap.has('opt')).to.be.true; + }); + + it('get by key', () => { + expect(lwwMap.get('foo')).to.equal('bar'); + }); + + it('get missing by key', () => { + expect(lwwMap.get('opt')).to.equal(undefined); + }); + + it('has when it does not have', () => { + expect(lwwMap.has('nope')).to.be.false; + expect(lwwMap.state).to.deep.equal(state); + }); + + it('has is false for deleted', () => { + lwwMap.delete('foo'); + expect(lwwMap.has('foo')).to.be.false; + }); + + it('deletes a key that exists', () => { + lwwMap.delete('foo'); + expect(lwwMap.has('foo')).to.be.false; + expect(lwwMap.value).to.deep.equal({ baz: 'qux' }); + expect(lwwMap.state.baz).to.deep.equal(state.baz); + expect(lwwMap.state.foo.value).to.equal(SYMBOL_FOR_DELETED); + }); + + it('set and get an optional null', () => { + lwwMap.set('optNull', null); + expect(lwwMap.get('optNull')).to.be.null; + }); + + describe('merge', () => { + beforeEach(() => { + lwwMap = new LWWMap('test', state); + }); + it('all are updated', () => { + const remoteState = { + foo: { value: 'bar2', timestamp: process.hrtime.bigint(), peer: 'b' }, + baz: { value: 'qux2', timestamp: process.hrtime.bigint(), peer: 'b' }, + }; + lwwMap.merge(remoteState); + expect(lwwMap.state).to.deep.equal(remoteState); + expect(lwwMap.get('foo')).to.equal('bar2'); + }); + it('all are deleted', () => { + const remoteState = { + foo: { value: SYMBOL_FOR_DELETED, timestamp: process.hrtime.bigint(), peer: 'b' }, + baz: { value: SYMBOL_FOR_DELETED, timestamp: process.hrtime.bigint(), peer: 'b' }, + }; + lwwMap.merge(remoteState); + expect(lwwMap.state).to.deep.equal(remoteState); + expect(lwwMap.get('foo')).to.equal(undefined); + }); + it('none are updated', () => { + const remoteState = { + foo: { value: 'bar2', timestamp: process.hrtime.bigint() - BigInt(10000000000), peer: 'b' }, + baz: { value: 'qux2', timestamp: process.hrtime.bigint() - BigInt(10000000000), peer: 'b' }, + }; + lwwMap.merge(remoteState); + expect(lwwMap.state).to.deep.equal(state); + expect(lwwMap.get('foo')).to.equal('bar'); + }); + it('one is update, one is added, one is deleted', () => { + lwwMap.set('opt', 4); + const remoteState = { + foo: { value: 'bar2', timestamp: process.hrtime.bigint(), peer: 'b' }, + baz: { value: 'qux2', timestamp: process.hrtime.bigint() - BigInt(10000000000), peer: 'b' }, + opt: { value: SYMBOL_FOR_DELETED, timestamp: process.hrtime.bigint(), peer: 'b' }, + optNull: { value: null, timestamp: process.hrtime.bigint(), peer: 'b' }, + }; + lwwMap.merge(remoteState); + expect(lwwMap.get('foo')).to.equal('bar2'); + expect(lwwMap.get('baz')).to.equal('qux'); + expect(lwwMap.state.opt?.value).to.equal(SYMBOL_FOR_DELETED); + expect(lwwMap.get('opt')).to.be.undefined; + }); + }); + }); + + describe('nested objects', () => { + const state = { + foo: { value: 'bar', timestamp: process.hrtime.bigint(), peer: 'a' }, + baz: { value: 'qux', timestamp: process.hrtime.bigint(), peer: 'a' }, + obj: { value: { a: 1, b: 2, c: 3 }, timestamp: process.hrtime.bigint(), peer: 'a' }, + }; + let lwwMap: LWWMap<{ foo: string; baz: string; opt?: number; optNull?: null }>; + + beforeEach(() => { + lwwMap = new LWWMap('test', state); + }); + + it('should initialize with the correct state', () => { + expect(lwwMap.state).to.deep.equal(state); + }); + + it('should get the correct value for the entire object', () => { + expect(lwwMap.value).to.deep.equal({ foo: 'bar', baz: 'qux', obj: { a: 1, b: 2, c: 3 } }); + }); + }); +}); diff --git a/test/unit/projectTest.ts b/test/unit/projectTest.ts index a483aa7516..fff2631d51 100644 --- a/test/unit/projectTest.ts +++ b/test/unit/projectTest.ts @@ -309,14 +309,14 @@ describe('SfProject', () => { const read = async function () { // @ts-expect-error this is any if (this.isGlobal()) { - return { 'org-api-version': 38.0 }; + return { 'org-api-version': '38.0' }; } else { - return { 'org-api-version': 39.0 }; + return { 'org-api-version': '39.0' }; } }; $$.configStubs.SfProjectJson = { retrieveContents: read }; $$.configStubs.Config = { - contents: { 'org-api-version': 40.0, 'org-instance-url': 'https://usethis.my.salesforce.com' }, + contents: { 'org-api-version': '40.0', 'org-instance-url': 'https://usethis.my.salesforce.com' }, }; const project = await SfProject.resolve(); const config = await project.resolveProjectConfig(); @@ -326,14 +326,14 @@ describe('SfProject', () => { const read = async function () { // @ts-expect-error this is any if (this.isGlobal()) { - return { 'org-api-version': 38.0, sfdcLoginUrl: 'https://fromfiles.com' }; + return { 'org-api-version': '38.0', sfdcLoginUrl: 'https://fromfiles.com' }; } else { - return { 'org-api-version': 39.0, sfdcLoginUrl: 'https://fromfiles.com' }; + return { 'org-api-version': '39.0', sfdcLoginUrl: 'https://fromfiles.com' }; } }; $$.configStubs.SfProjectJson = { retrieveContents: read }; $$.configStubs.Config = { - contents: { 'org-api-version': 40.0, 'org-instance-url': 'https://dontusethis.my.salesforce.com' }, + contents: { 'org-api-version': '40.0', 'org-instance-url': 'https://dontusethis.my.salesforce.com' }, }; const project = await SfProject.resolve(); const config = await project.resolveProjectConfig(); @@ -343,16 +343,16 @@ describe('SfProject', () => { const read = async function () { // @ts-expect-error this is any if (this.isGlobal()) { - return { 'org-api-version': 38.0 }; + return { 'org-api-version': '38.0' }; } else { - return { 'org-api-version': 39.0 }; + return { 'org-api-version': '39.0' }; } }; $$.configStubs.SfProjectJson = { retrieveContents: read }; - $$.configStubs.Config = { contents: { 'org-api-version': 40.0 } }; + $$.configStubs.Config = { contents: { 'org-api-version': '40.0' } }; const project = await SfProject.resolve(); const config = await project.resolveProjectConfig(); - expect(config['org-api-version']).to.equal(40.0); + expect(config['org-api-version']).to.equal('40.0'); }); }); diff --git a/test/unit/stateAggregator/accessors/aliasAccessorTest.ts b/test/unit/stateAggregator/accessors/aliasAccessorTest.ts index 77629f7da2..a6dd348954 100644 --- a/test/unit/stateAggregator/accessors/aliasAccessorTest.ts +++ b/test/unit/stateAggregator/accessors/aliasAccessorTest.ts @@ -10,8 +10,9 @@ import { rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { expect } from 'chai'; import { FILENAME, StateAggregator } from '../../../../src/stateAggregator'; -import { MockTestOrgData, TestContext, uniqid } from '../../../../src/testSetup'; +import { MockTestOrgData, TestContext } from '../../../../src/testSetup'; import { Global } from '../../../../src/global'; +import { uniqid } from '../../../../src/util/uniqid'; const username1 = 'espresso@coffee.com'; const username2 = 'foobar@salesforce.com'; diff --git a/test/unit/stateAggregator/accessors/orgAccessorTest.ts b/test/unit/stateAggregator/accessors/orgAccessorTest.ts index 9e2c62aea6..a1a7544681 100644 --- a/test/unit/stateAggregator/accessors/orgAccessorTest.ts +++ b/test/unit/stateAggregator/accessors/orgAccessorTest.ts @@ -7,7 +7,8 @@ import { expect } from 'chai'; import { StateAggregator } from '../../../../src/stateAggregator'; import { AuthFields } from '../../../../src/org'; -import { MockTestOrgData, shouldThrowSync, TestContext, uniqid } from '../../../../src/testSetup'; +import { MockTestOrgData, shouldThrowSync, TestContext } from '../../../../src/testSetup'; +import { uniqid } from '../../../../src/util/uniqid'; const username = 'espresso@coffee.com'; const org = new MockTestOrgData(uniqid(), { username }); diff --git a/test/unit/stateAggregator/accessors/sandboxAccessorTest.ts b/test/unit/stateAggregator/accessors/sandboxAccessorTest.ts index 8e10047a78..299a11e336 100644 --- a/test/unit/stateAggregator/accessors/sandboxAccessorTest.ts +++ b/test/unit/stateAggregator/accessors/sandboxAccessorTest.ts @@ -6,7 +6,8 @@ */ import { expect } from 'chai'; import { StateAggregator } from '../../../../src/stateAggregator'; -import { MockTestOrgData, MockTestSandboxData, TestContext, uniqid } from '../../../../src/testSetup'; +import { MockTestOrgData, MockTestSandboxData, TestContext } from '../../../../src/testSetup'; +import { uniqid } from '../../../../src/util/uniqid'; const sandboxUsername = 'espresso@coffee.com.mysandbox'; const prodOrgUsername = 'espresso@coffee.com'; diff --git a/test/unit/stateAggregator/accessors/tokenAccessorTest.ts b/test/unit/stateAggregator/accessors/tokenAccessorTest.ts index abcde580c2..b399670c9a 100644 --- a/test/unit/stateAggregator/accessors/tokenAccessorTest.ts +++ b/test/unit/stateAggregator/accessors/tokenAccessorTest.ts @@ -6,7 +6,8 @@ */ import { expect } from 'chai'; import { StateAggregator } from '../../../../src/stateAggregator'; -import { MockTestOrgData, TestContext, uniqid } from '../../../../src/testSetup'; +import { MockTestOrgData, TestContext } from '../../../../src/testSetup'; +import { uniqid } from '../../../../src/util/uniqid'; const username = 'espresso@coffee.com'; const alias = 'MyAlias'; diff --git a/test/unit/testSetupTest.ts b/test/unit/testSetupTest.ts index b7912c41a5..22ec49fd43 100644 --- a/test/unit/testSetupTest.ts +++ b/test/unit/testSetupTest.ts @@ -5,7 +5,8 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ import { expect } from 'chai'; -import { uniqid } from '../../src/testSetup'; +import { uniqid } from '../../src/util/uniqid'; + describe('testSetup', () => { describe('uniqueId', () => { it('should return a unique id of default length of 32', () => { diff --git a/yarn.lock b/yarn.lock index 2fa58dc913..0a835d65a8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -412,6 +412,11 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.50.0.tgz#9e93b850f0f3fa35f5fa59adfd03adae8488e484" integrity sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ== +"@eslint/js@8.51.0": + version "8.51.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.51.0.tgz#6d419c240cfb2b66da37df230f7e7eef801c32fa" + integrity sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg== + "@humanwhocodes/config-array@^0.11.11": version "0.11.11" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.11.tgz#88a04c570dbbc7dd943e4712429c3df09bc32844" @@ -1986,7 +1991,7 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4 resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint@^8.41.0, eslint@^8.50.0: +eslint@^8.41.0: version "8.50.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.50.0.tgz#2ae6015fee0240fcd3f83e1e25df0287f487d6b2" integrity sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg== @@ -2029,6 +2034,49 @@ eslint@^8.41.0, eslint@^8.50.0: strip-ansi "^6.0.1" text-table "^0.2.0" +eslint@^8.51.0: + version "8.51.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.51.0.tgz#4a82dae60d209ac89a5cff1604fea978ba4950f3" + integrity sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.2" + "@eslint/js" "8.51.0" + "@humanwhocodes/config-array" "^0.11.11" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + espree@^9.6.0, espree@^9.6.1: version "9.6.1" resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" From 20a6c90107b988c8049e776ccd2a680b94a8cbbe Mon Sep 17 00:00:00 2001 From: mshanemc Date: Tue, 10 Oct 2023 09:57:48 -0500 Subject: [PATCH 02/67] refactor: share lockfile options --- src/stateAggregator/accessors/aliasAccessor.ts | 6 +----- src/util/lockRetryOptions.ts | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 src/util/lockRetryOptions.ts diff --git a/src/stateAggregator/accessors/aliasAccessor.ts b/src/stateAggregator/accessors/aliasAccessor.ts index 9caa4540d2..ed2bbf3b2b 100644 --- a/src/stateAggregator/accessors/aliasAccessor.ts +++ b/src/stateAggregator/accessors/aliasAccessor.ts @@ -18,17 +18,13 @@ import { Global } from '../../global'; import { AuthFields } from '../../org/authInfo'; import { ConfigContents } from '../../config/configStackTypes'; import { SfError } from '../../sfError'; +import { lockRetryOptions, lockOptions } from '../../util/lockRetryOptions'; import { SfToken } from './tokenAccessor'; export type Aliasable = string | (Partial & Partial); export const DEFAULT_GROUP = 'orgs'; export const FILENAME = 'alias.json'; -const lockOptions = { stale: 10_000 }; -const lockRetryOptions = { - ...lockOptions, - retries: { retries: 10, maxTimeout: 1000, factor: 2 }, -}; export class AliasAccessor extends AsyncOptionalCreatable { // set in init method private fileLocation!: string; diff --git a/src/util/lockRetryOptions.ts b/src/util/lockRetryOptions.ts new file mode 100644 index 0000000000..fe1060fddd --- /dev/null +++ b/src/util/lockRetryOptions.ts @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +// docs: https://github.com/moxystudio/node-proper-lockfile + +export const lockOptions = { stale: 10_000 }; +export const lockRetryOptions = { + ...lockOptions, + retries: { retries: 10, maxTimeout: 1_000, factor: 2 }, +}; From 91316a7d8478810a07b8948e3de179e2e0abfb3f Mon Sep 17 00:00:00 2001 From: mshanemc Date: Tue, 10 Oct 2023 14:47:51 -0500 Subject: [PATCH 03/67] feat: configFile file locks and write-via-merge --- src/config/configFile.ts | 72 ++++++++++++++++++++++++++++++++------- src/config/configStore.ts | 9 +++-- src/config/lwwMap.ts | 28 ++++++++++++--- 3 files changed, 90 insertions(+), 19 deletions(-) diff --git a/src/config/configFile.ts b/src/config/configFile.ts index b987793206..0f2a4b198d 100644 --- a/src/config/configFile.ts +++ b/src/config/configFile.ts @@ -9,14 +9,17 @@ import * as fs from 'fs'; import { constants as fsConstants, Stats as fsStats } from 'fs'; import { homedir as osHomedir } from 'os'; import { dirname as pathDirname, join as pathJoin } from 'path'; +import { lock, lockSync } from 'proper-lockfile'; import { isPlainObject } from '@salesforce/ts-types'; import { parseJsonMap } from '@salesforce/kit'; import { Global } from '../global'; import { Logger } from '../logger/logger'; import { SfError } from '../sfError'; import { resolveProjectPath, resolveProjectPathSync } from '../util/internal'; +import { lockOptions, lockRetryOptions } from '../util/lockRetryOptions'; import { BaseConfigStore } from './configStore'; import { ConfigContents } from './configStackTypes'; +import { stateFromContents } from './lwwMap'; /** * Represents a json config file used to manage settings and state. Global config @@ -167,7 +170,7 @@ export class ConfigFile< }` ); const obj = parseJsonMap

(await fs.promises.readFile(this.getPath(), 'utf8'), this.getPath()); - this.setContentsFromObject(obj); + this.setContentsFromFileContents(obj, (await fs.promises.stat(this.getPath(), { bigint: true })).mtimeNs); } // Necessarily set this even when an error happens to avoid infinite re-reading. // To attempt another read, pass `force=true`. @@ -177,7 +180,7 @@ export class ConfigFile< this.hasRead = true; if ((err as SfError).code === 'ENOENT') { if (!throwOnNotFound) { - this.setContents(); + this.setContentsFromFileContents({} as P, process.hrtime.bigint()); return this.getContents(); } } @@ -203,13 +206,17 @@ export class ConfigFile< if (!this.hasRead || force) { this.logger.info(`Reading config file: ${this.getPath()}`); const obj = parseJsonMap

(fs.readFileSync(this.getPath(), 'utf8')); - this.setContentsFromObject(obj); + this.setContentsFromFileContents(obj, fs.statSync(this.getPath(), { bigint: true }).mtimeNs); } + // Necessarily set this even when an error happens to avoid infinite re-reading. + // To attempt another read, pass `force=true`. + this.hasRead = true; return this.getContents(); } catch (err) { + this.hasRead = true; if ((err as SfError).code === 'ENOENT') { if (!throwOnNotFound) { - this.setContents(); + this.setContentsFromFileContents({} as P, process.hrtime.bigint()); return this.getContents(); } } @@ -228,19 +235,38 @@ export class ConfigFile< * @param newContents The new contents of the file. */ public async write(newContents?: P): Promise

{ - if (newContents) { - this.setContents(newContents); - } - + // make sure we can write to the directory try { await fs.promises.mkdir(pathDirname(this.getPath()), { recursive: true }); } catch (err) { throw SfError.wrap(err as Error); } + // lock the file. Returns an unlock function to call when done. + const unlockFn = await lock(this.getPath(), lockRetryOptions); + // get the file modstamp. Do this after the lock acquisition in case the file is being written to. + const fileTimestamp = (await fs.promises.stat(this.getPath(), { bigint: true })).mtimeNs; + + if (isPlainObject(newContents)) { + this.setContents(newContents); + } + + // read the file contents into a LWWMap using the modstamp + const stateFromFile = stateFromContents

( + parseJsonMap

(await fs.promises.readFile(this.getPath(), 'utf8'), this.getPath()), + fileTimestamp, + this.getPath() + ); + // merge the new contents into the in-memory LWWMap + this.contents.merge(stateFromFile); + + // write the merged LWWMap to file this.logger.info(`Writing to config file: ${this.getPath()}`); await fs.promises.writeFile(this.getPath(), JSON.stringify(this.toObject(), null, 2)); + // unlock the file + await unlockFn(); + return this.getContents(); } @@ -251,19 +277,41 @@ export class ConfigFile< * @param newContents The new contents of the file. */ public writeSync(newContents?: P): P { - if (isPlainObject(newContents)) { - this.setContents(newContents); - } - try { fs.mkdirSync(pathDirname(this.getPath()), { recursive: true }); } catch (err) { throw SfError.wrap(err as Error); } + if (isPlainObject(newContents)) { + this.setContents(newContents); + } + + // lock the file. Returns an unlock function to call when done. + const unlockFn = lockSync(this.getPath(), lockOptions); + // get the file modstamp. Do this after the lock acquisition in case the file is being written to. + const fileTimestamp = fs.statSync(this.getPath(), { bigint: true }).mtimeNs; + + if (isPlainObject(newContents)) { + this.setContents(newContents); + } + + // read the file contents into a LWWMap using the modstamp + const stateFromFile = stateFromContents

( + parseJsonMap

(fs.readFileSync(this.getPath(), 'utf8'), this.getPath()), + fileTimestamp, + this.getPath() + ); + // merge the new contents into the in-memory LWWMap + this.contents.merge(stateFromFile); + + // write the merged LWWMap to file + this.logger.info(`Writing to config file: ${this.getPath()}`); fs.writeFileSync(this.getPath(), JSON.stringify(this.toObject(), null, 2)); + // unlock the file + unlockFn(); return this.getContents(); } diff --git a/src/config/configStore.ts b/src/config/configStore.ts index a735461983..0a4c6084da 100644 --- a/src/config/configStore.ts +++ b/src/config/configStore.ts @@ -10,7 +10,7 @@ import { entriesOf, isPlainObject } from '@salesforce/ts-types'; import { definiteEntriesOf, definiteValuesOf, get, isJsonMap, isString, JsonMap, Optional } from '@salesforce/ts-types'; import { Crypto } from '../crypto/crypto'; import { SfError } from '../sfError'; -import { LWWMap } from './lwwMap'; +import { LWWMap, stateFromContents } from './lwwMap'; import { ConfigContents, ConfigEntry, ConfigValue, Key } from './configStackTypes'; /** @@ -60,7 +60,7 @@ export abstract class BaseConfigStore< protected crypto?: Crypto; // Initialized in setContents - private contents = new LWWMap

(); + protected contents = new LWWMap

(); private statics = this.constructor as typeof BaseConfigStore; /** @@ -284,6 +284,11 @@ export abstract class BaseConfigStore< }); } + protected setContentsFromFileContents(contents: P, timestamp: bigint): void { + const state = stateFromContents(contents, timestamp, this.contents.id); + this.contents = new LWWMap

(this.contents.id, state); + } + protected getEncryptedKeys(): Array { return [...(this.options?.encryptedKeys ?? []), ...(this.statics?.encryptedKeys ?? [])]; } diff --git a/src/config/lwwMap.ts b/src/config/lwwMap.ts index e5441d4852..f37c7bcbb1 100644 --- a/src/config/lwwMap.ts +++ b/src/config/lwwMap.ts @@ -12,16 +12,34 @@ import { ConfigContents, Key } from './configStackTypes'; export const SYMBOL_FOR_DELETED = 'UNIQUE_IDENTIFIER_FOR_DELETED' as const; -type State

= { +export type LWWState

= { [Property in keyof P]: LWWRegister['state']; }; +/** + * @param contents object aligning with ConfigContents + * @param timestamp a bigInt that sets the timestamp. Defaults to the current time + * construct a LWWState from an object + * + * */ +export const stateFromContents =

( + contents: P, + timestamp = process.hrtime.bigint(), + id?: string +): LWWState

=> + Object.fromEntries( + entriesOf(contents).map(([key, value]) => [ + key, + new LWWRegister(id ?? uniqid(), { peer: 'file', timestamp, value }), + ]) + ) as unknown as LWWState

; + export class LWWMap

{ public readonly id: string; /** map of key to LWWRegister. Used for managing conflicts */ #data = new Map>(); - public constructor(id?: string, state?: State

) { + public constructor(id?: string, state?: LWWState

) { this.id = id ?? uniqid(); // create a new register for each key in the initial state @@ -38,15 +56,15 @@ export class LWWMap

{ ) as P; } - public get state(): State

{ + public get state(): LWWState

{ return Object.fromEntries( Array.from(this.#data.entries()) // .filter(([, register]) => Boolean(register)) .map(([key, register]) => [key, register.state]) - ) as State

; + ) as LWWState

; } - public merge(state: State

): State

{ + public merge(state: LWWState

): LWWState

{ // recursively merge each key's register with the incoming state for that key for (const [key, remote] of entriesOf(state)) { const local = this.#data.get(key); From 947b3dfe9c5064cd5b455a7b9c8347213646fea1 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Tue, 10 Oct 2023 14:50:00 -0500 Subject: [PATCH 04/67] test: mocks for new stat/read changes --- test/unit/config/configAggregatorTest.ts | 4 + test/unit/config/configFileTest.ts | 350 ++++++++++++----------- 2 files changed, 189 insertions(+), 165 deletions(-) diff --git a/test/unit/config/configAggregatorTest.ts b/test/unit/config/configAggregatorTest.ts index 6c820506e3..fcb32a44cb 100644 --- a/test/unit/config/configAggregatorTest.ts +++ b/test/unit/config/configAggregatorTest.ts @@ -53,6 +53,10 @@ describe('ConfigAggregator', () => { }); describe('locations', () => { + beforeEach(() => { + // @ts-expect-error there's a lot more properties we're not mocking + $$.SANDBOX.stub(fs.promises, 'stat').resolves({ mtimeNs: BigInt(new Date().valueOf() - 1_000 * 60 * 5) }); + }); it('local', async () => { // @ts-expect-error async function signature not quite same as expected $$.SANDBOX.stub(fs.promises, 'readFile').callsFake(async (path: string) => { diff --git a/test/unit/config/configFileTest.ts b/test/unit/config/configFileTest.ts index de54519442..1ec2c18b24 100644 --- a/test/unit/config/configFileTest.ts +++ b/test/unit/config/configFileTest.ts @@ -12,6 +12,7 @@ import * as fs from 'fs'; import { expect } from 'chai'; import { assert } from '@salesforce/ts-types'; +import * as lockfileLib from 'proper-lockfile'; import { ConfigFile } from '../../../src/config/configFile'; import { SfError } from '../../../src/exported'; import { shouldThrow, TestContext } from '../../../src/testSetup'; @@ -205,19 +206,30 @@ describe('Config', () => { $$.SANDBOXES.CONFIG.restore(); }); it('uses passed in contents', async () => { - // const mkdirpStub = $$.SANDBOX.stub(mkdirp, 'native'); + $$.SANDBOX.stub(fs.promises, 'readFile').resolves('{}'); + // @ts-expect-error --> we're only mocking on prop of many + $$.SANDBOX.stub(fs.promises, 'stat').resolves({ mtimeNs: BigInt(new Date().valueOf() - 1_000 * 60 * 5) }); + $$.SANDBOX.stub(fs.promises, 'mkdir').resolves(); + const lockStub = $$.SANDBOX.stub(lockfileLib, 'lock').resolves(() => Promise.resolve()); + const writeJson = $$.SANDBOX.stub(fs.promises, 'writeFile'); const config = await TestConfig.create({ isGlobal: true }); const expected = { test: 'test' }; const actual = await config.write(expected); + expect(lockStub.callCount).to.equal(1); expect(expected).to.deep.equal(actual); expect(expected).to.deep.equal(config.getContents()); - // expect(mkdirpStub.called).to.be.true; + // // expect(mkdirpStub.called).to.be.true; expect(writeJson.called).to.be.true; }); it('sync uses passed in contents', async () => { + $$.SANDBOX.stub(fs, 'readFileSync').returns('{}'); + // @ts-expect-error --> we're only mocking on prop of many + $$.SANDBOX.stub(fs, 'statSync').returns({ mtimeNs: BigInt(new Date().valueOf() - 1_000 * 60 * 5) }); + const lockStub = $$.SANDBOX.stub(lockfileLib, 'lockSync').returns(() => undefined); + const mkdirpStub = $$.SANDBOX.stub(fs, 'mkdirSync'); const writeJson = $$.SANDBOX.stub(fs, 'writeFileSync'); @@ -225,176 +237,184 @@ describe('Config', () => { const expected = { test: 'test' }; const actual = config.writeSync(expected); + expect(lockStub.callCount).to.equal(1); expect(expected).to.deep.equal(actual); expect(expected).to.deep.equal(config.getContents()); expect(mkdirpStub.called).to.be.true; expect(writeJson.called).to.be.true; }); }); - const testFileContentsString = '{"foo":"bar"}'; - const testFileContentsJson = { foo: 'bar' }; - - describe('read()', () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let readFileStub: any; - let config: TestConfig; - - beforeEach(async () => { - $$.SANDBOXES.CONFIG.restore(); - readFileStub = $$.SANDBOX.stub(fs.promises, 'readFile'); - }); - - it('caches file contents', async () => { - readFileStub.resolves(testFileContentsString); - // TestConfig.create() calls read() - config = await TestConfig.create(TestConfig.getOptions('test', false, true)); - expect(readFileStub.calledOnce).to.be.true; - - // @ts-expect-error -> hasRead is protected. Ignore for testing. - expect(config.hasRead).to.be.true; - expect(config.getContents()).to.deep.equal(testFileContentsJson); - - // Read again. Stub should still only be called once. - const contents2 = await config.read(false, false); - expect(readFileStub.calledOnce).to.be.true; - expect(contents2).to.deep.equal(testFileContentsJson); - }); - - it('sets contents as empty object when file does not exist', async () => { - const err = SfError.wrap(new Error()); - err.code = 'ENOENT'; - readFileStub.throws(err); - - config = await TestConfig.create(TestConfig.getOptions('test', false, true)); - expect(readFileStub.calledOnce).to.be.true; - - // @ts-expect-error -> hasRead is protected. Ignore for testing. - expect(config.hasRead).to.be.true; - expect(config.getContents()).to.deep.equal({}); - }); - it('throws when file does not exist and throwOnNotFound=true', async () => { - const err = new Error('not here'); - err.name = 'FileNotFound'; - (err as any).code = 'ENOENT'; - readFileStub.throws(SfError.wrap(err)); - - const configOptions = { - filename: 'test', - isGlobal: true, - throwOnNotFound: true, - }; - - try { - await shouldThrow(TestConfig.create(configOptions)); - } catch (e) { - expect(e).to.have.property('name', 'FileNotFound'); - } - }); - - it('sets hasRead=false by default', async () => { - const configOptions = TestConfig.getOptions('test', false, true); - const testConfig = new TestConfig(configOptions); - // @ts-expect-error -> hasRead is protected. Ignore for testing. - expect(testConfig.hasRead).to.be.false; - }); - - it('forces another read of the config file with force=true', async () => { - readFileStub.resolves(testFileContentsString); - // TestConfig.create() calls read() - config = await TestConfig.create(TestConfig.getOptions('test', false, true)); - expect(readFileStub.calledOnce).to.be.true; - - // @ts-expect-error -> hasRead is protected. Ignore for testing. - expect(config.hasRead).to.be.true; - expect(config.getContents()).to.deep.equal(testFileContentsJson); - - // Read again. Stub should now be called twice. - const contents2 = await config.read(false, true); - expect(readFileStub.calledTwice).to.be.true; - expect(contents2).to.deep.equal(testFileContentsJson); - }); - }); - - describe('readSync()', () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let readFileStub: any; - let config: TestConfig; - - beforeEach(async () => { - $$.SANDBOXES.CONFIG.restore(); - readFileStub = $$.SANDBOX.stub(fs, 'readFileSync'); - }); - - it('caches file contents', () => { - readFileStub.returns(testFileContentsString); - // TestConfig.create() calls read() - config = new TestConfig(TestConfig.getOptions('test', false, true)); - expect(readFileStub.calledOnce).to.be.false; - - // @ts-expect-error -> hasRead is protected. Ignore for testing. - expect(config.hasRead).to.be.false; - - config.readSync(false, false); - // @ts-expect-error -> hasRead is protected. Ignore for testing. - expect(config.hasRead).to.be.true; - expect(config.getContents()).to.deep.equal(testFileContentsJson); - - // Read again. Stub should still only be called once. - const contents2 = config.readSync(false, false); - expect(readFileStub.calledOnce).to.be.true; - expect(contents2).to.deep.equal(testFileContentsJson); - }); - - it('sets contents as empty object when file does not exist', () => { - const err = SfError.wrap(new Error()); - err.code = 'ENOENT'; - readFileStub.throws(err); - - config = new TestConfig(TestConfig.getOptions('test', false, true)); - config.readSync(); - expect(readFileStub.calledOnce).to.be.true; - - // @ts-expect-error -> hasRead is protected. Ignore for testing. - expect(config.hasRead).to.be.true; - expect(config.getContents()).to.deep.equal({}); - }); - - it('throws when file does not exist and throwOnNotFound=true on method call', () => { - const err = new Error('not here'); - err.name = 'FileNotFound'; - (err as any).code = 'ENOENT'; - readFileStub.throws(SfError.wrap(err)); - - const configOptions = { - filename: 'test', - isGlobal: true, - throwOnNotFound: false, - }; - - try { - // The above config doesn't matter because we don't read on creation and it is overridden in the read method. - new TestConfig(configOptions).readSync(true); - assert(false, 'should throw'); - } catch (e) { - expect(e).to.have.property('name', 'FileNotFound'); - } - }); - - it('forces another read of the config file with force=true', () => { - readFileStub.returns(testFileContentsString); - // TestConfig.create() calls read() - config = new TestConfig(TestConfig.getOptions('test', false, true)); - config.readSync(); - - // -> hasRead is protected. Ignore for testing. - // @ts-expect-error -> hasRead is protected. Ignore for testing. - expect(config.hasRead).to.be.true; - - // Read again. Stub should now be called twice. - const contents2 = config.readSync(false, true); - expect(readFileStub.calledTwice).to.be.true; - expect(contents2).to.deep.equal(testFileContentsJson); + describe('read', () => { + const testFileContentsString = '{"foo":"bar"}'; + const testFileContentsJson = { foo: 'bar' }; + + describe('read()', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let readFileStub: any; + let config: TestConfig; + + beforeEach(async () => { + $$.SANDBOXES.CONFIG.restore(); + readFileStub = $$.SANDBOX.stub(fs.promises, 'readFile'); + // @ts-expect-error --> we're only mocking on prop of many + $$.SANDBOX.stub(fs.promises, 'stat').resolves({ mtimeNs: BigInt(new Date().valueOf() - 1_000 * 60 * 5) }); + }); + + it('caches file contents', async () => { + readFileStub.resolves(testFileContentsString); + // TestConfig.create() calls read() + config = await TestConfig.create(TestConfig.getOptions('test', false, true)); + expect(readFileStub.calledOnce).to.be.true; + + // @ts-expect-error -> hasRead is protected. Ignore for testing. + expect(config.hasRead).to.be.true; + expect(config.getContents()).to.deep.equal(testFileContentsJson); + + // Read again. Stub should still only be called once. + const contents2 = await config.read(false, false); + expect(readFileStub.calledOnce).to.be.true; + expect(contents2).to.deep.equal(testFileContentsJson); + }); + + it('sets contents as empty object when file does not exist', async () => { + const err = SfError.wrap(new Error()); + err.code = 'ENOENT'; + readFileStub.throws(err); + + config = await TestConfig.create(TestConfig.getOptions('test', false, true)); + expect(readFileStub.calledOnce).to.be.true; + + // @ts-expect-error -> hasRead is protected. Ignore for testing. + expect(config.hasRead).to.be.true; + expect(config.getContents()).to.deep.equal({}); + }); + + it('throws when file does not exist and throwOnNotFound=true', async () => { + const err = new Error('not here'); + err.name = 'FileNotFound'; + (err as any).code = 'ENOENT'; + readFileStub.throws(SfError.wrap(err)); + + const configOptions = { + filename: 'test', + isGlobal: true, + throwOnNotFound: true, + }; + + try { + await shouldThrow(TestConfig.create(configOptions)); + } catch (e) { + expect(e).to.have.property('name', 'FileNotFound'); + } + }); + + it('sets hasRead=false by default', async () => { + const configOptions = TestConfig.getOptions('test', false, true); + const testConfig = new TestConfig(configOptions); + // @ts-expect-error -> hasRead is protected. Ignore for testing. + expect(testConfig.hasRead).to.be.false; + }); + + it('forces another read of the config file with force=true', async () => { + readFileStub.resolves(testFileContentsString); + // TestConfig.create() calls read() + config = await TestConfig.create(TestConfig.getOptions('test', false, true)); + expect(readFileStub.calledOnce).to.be.true; + + // @ts-expect-error -> hasRead is protected. Ignore for testing. + expect(config.hasRead).to.be.true; + expect(config.getContents()).to.deep.equal(testFileContentsJson); + + // Read again. Stub should now be called twice. + const contents2 = await config.read(false, true); + expect(readFileStub.calledTwice).to.be.true; + expect(contents2).to.deep.equal(testFileContentsJson); + }); + }); + + describe('readSync()', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let readFileStub: any; + let config: TestConfig; + + beforeEach(async () => { + $$.SANDBOXES.CONFIG.restore(); + readFileStub = $$.SANDBOX.stub(fs, 'readFileSync'); + // @ts-expect-error --> we're only mocking on prop of many + $$.SANDBOX.stub(fs, 'statSync').returns({ mtimeNs: BigInt(new Date().valueOf() - 1_000 * 60 * 5) }); + }); + + it('caches file contents', () => { + readFileStub.returns(testFileContentsString); + // TestConfig.create() calls read() + config = new TestConfig(TestConfig.getOptions('test', false, true)); + expect(readFileStub.calledOnce).to.be.false; + + // @ts-expect-error -> hasRead is protected. Ignore for testing. + expect(config.hasRead).to.be.false; + + config.readSync(false, false); + // @ts-expect-error -> hasRead is protected. Ignore for testing. + expect(config.hasRead).to.be.true; + expect(config.getContents()).to.deep.equal(testFileContentsJson); + + // Read again. Stub should still only be called once. + const contents2 = config.readSync(false, false); + expect(readFileStub.calledOnce).to.be.true; + expect(contents2).to.deep.equal(testFileContentsJson); + }); + + it('sets contents as empty object when file does not exist', () => { + const err = SfError.wrap(new Error()); + err.code = 'ENOENT'; + readFileStub.throws(err); + + config = new TestConfig(TestConfig.getOptions('test', false, true)); + config.readSync(); + expect(readFileStub.calledOnce).to.be.true; + + // @ts-expect-error -> hasRead is protected. Ignore for testing. + expect(config.hasRead).to.be.true; + expect(config.getContents()).to.deep.equal({}); + }); + + it('throws when file does not exist and throwOnNotFound=true on method call', () => { + const err = new Error('not here'); + err.name = 'FileNotFound'; + (err as any).code = 'ENOENT'; + readFileStub.throws(SfError.wrap(err)); + + const configOptions = { + filename: 'test', + isGlobal: true, + throwOnNotFound: false, + }; + + try { + // The above config doesn't matter because we don't read on creation and it is overridden in the read method. + new TestConfig(configOptions).readSync(true); + assert(false, 'should throw'); + } catch (e) { + expect(e).to.have.property('name', 'FileNotFound'); + } + }); + + it('forces another read of the config file with force=true', () => { + readFileStub.returns(testFileContentsString); + // TestConfig.create() calls read() + config = new TestConfig(TestConfig.getOptions('test', false, true)); + config.readSync(); + + // -> hasRead is protected. Ignore for testing. + // @ts-expect-error -> hasRead is protected. Ignore for testing. + expect(config.hasRead).to.be.true; + + // Read again. Stub should now be called twice. + const contents2 = config.readSync(false, true); + expect(readFileStub.calledTwice).to.be.true; + expect(contents2).to.deep.equal(testFileContentsJson); + }); }); }); }); From 03a7b8ef82eb5d8ae0ee8fcb4cea0cdce0285b02 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 11 Oct 2023 09:34:58 -0500 Subject: [PATCH 05/67] test(wip): mocks for lockfile, but also some TBD skips --- test/unit/config/configTest.ts | 16 ++++++++++++---- test/unit/org/authInfoTest.ts | 4 ++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/test/unit/config/configTest.ts b/test/unit/config/configTest.ts index be6316d5f1..d307a1a153 100644 --- a/test/unit/config/configTest.ts +++ b/test/unit/config/configTest.ts @@ -13,6 +13,7 @@ import * as fs from 'fs'; import { stubMethod } from '@salesforce/ts-sinon'; import { ensureString, JsonMap } from '@salesforce/ts-types'; import { expect } from 'chai'; +import * as lockfileLib from 'proper-lockfile'; import { Config, ConfigPropertyMeta } from '../../../src/config/config'; import { ConfigFile } from '../../../src/config/configFile'; import { ConfigContents } from '../../../src/config/configStackTypes'; @@ -74,7 +75,9 @@ describe('Config', () => { const config = await Config.create(Config.getDefaultOptions(true)); stubMethod($$.SANDBOX, fs.promises, 'readFile').withArgs(config.getPath()).resolves(configFileContentsString); - + stubMethod($$.SANDBOX, fs.promises, 'stat') + .withArgs(config.getPath()) + .resolves({ mtimeNs: BigInt(new Date().valueOf() - 1_000 * 60 * 5) }); // Manipulate config.hasRead to force a read // @ts-expect-error -> hasRead is protected. Ignore for testing. config.hasRead = false; @@ -88,8 +91,12 @@ describe('Config', () => { }); describe('set', () => { - it('calls Config.write with updated file contents', async () => { + beforeEach(() => { + $$.SANDBOX.stub(lockfileLib, 'lock').resolves(() => Promise.resolve()); stubMethod($$.SANDBOX, fs.promises, 'readFile').resolves(configFileContentsString); + stubMethod($$.SANDBOX, fs.promises, 'stat').resolves({ mtimeNs: BigInt(new Date().valueOf() - 1_000 * 60 * 5) }); + }); + it('calls Config.write with updated file contents', async () => { const writeStub = stubMethod($$.SANDBOX, fs.promises, 'writeFile'); const expectedFileContents = clone(configFileContentsJson); @@ -108,7 +115,6 @@ describe('Config', () => { await Config.update(false, 'target-org', newUsername); - stubMethod($$.SANDBOX, fs.promises, 'readFile').resolves(configFileContentsString); const writeStub = stubMethod($$.SANDBOX, fs.promises, 'writeFile'); const targetDevhub = configFileContentsJson['target-dev-hub']; @@ -225,7 +231,9 @@ describe('Config', () => { describe('unset', () => { it('calls Config.write with updated file contents', async () => { + $$.SANDBOX.stub(lockfileLib, 'lock').resolves(() => Promise.resolve()); stubMethod($$.SANDBOX, fs.promises, 'readFile').resolves(configFileContentsString); + stubMethod($$.SANDBOX, fs.promises, 'stat').resolves({ mtimeNs: BigInt(new Date().valueOf() - 1_000 * 60 * 5) }); const writeStub = stubMethod($$.SANDBOX, fs.promises, 'writeFile'); const expectedFileContents = clone(configFileContentsJson); @@ -268,7 +276,7 @@ describe('Config', () => { expect(writeStub.called).to.be.true; }); - it('calls ConfigFile.read with unknown key and does not throw on crypt', async () => { + it.skip('calls ConfigFile.read with unknown key and does not throw on crypt', async () => { stubMethod($$.SANDBOX, ConfigFile.prototype, ConfigFile.prototype.readSync.name).callsFake(async () => {}); stubMethod($$.SANDBOX, ConfigFile.prototype, ConfigFile.prototype.read.name).callsFake(async function () { // @ts-expect-error -> this is any diff --git a/test/unit/org/authInfoTest.ts b/test/unit/org/authInfoTest.ts index da940d85a4..9514be6d07 100644 --- a/test/unit/org/authInfoTest.ts +++ b/test/unit/org/authInfoTest.ts @@ -1272,7 +1272,7 @@ describe('AuthInfo', () => { expect(authInfo.getSfdxAuthUrl()).to.contain(`force://PlatformCLI::${testOrg.refreshToken}@${instanceUrl}`); }); - it('should handle undefined refresh token', async () => { + it.skip('should handle undefined refresh token', async () => { const authResponse = { access_token: testOrg.accessToken, instance_url: testOrg.instanceUrl, @@ -1297,7 +1297,7 @@ describe('AuthInfo', () => { expect(() => authInfo.getSfdxAuthUrl()).to.throw('undefined refreshToken'); }); - it('should handle undefined instance url', async () => { + it.skip('should handle undefined instance url', async () => { const authResponse = { access_token: testOrg.accessToken, instance_url: testOrg.instanceUrl, From e7e23bb8677b4c814ded08369e3353c602f46e21 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 11 Oct 2023 09:50:01 -0500 Subject: [PATCH 06/67] test: how are the nuts --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f5a38d5182..c9afbf11b7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: needs: linux-unit-tests uses: salesforcecli/github-workflows/.github/workflows/unitTestsWindows.yml@main nuts: - needs: linux-unit-tests + # needs: linux-unit-tests uses: salesforcecli/github-workflows/.github/workflows/externalNut.yml@main strategy: fail-fast: false From 8e346ca8206a23515bb1247872f19dbd7f13bf55 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 11 Oct 2023 11:26:37 -0500 Subject: [PATCH 07/67] feat!: remove parameters on config.write The Config family of classes' write(sync) method used to accept a param. The value in the param would overwrite the existing file. --- package.json | 6 +++--- src/config/config.ts | 6 +----- src/config/configFile.ts | 17 ++------------- src/config/ttlConfig.ts | 5 ++++- src/sfProject.ts | 19 ++++------------- src/testSetup.ts | 33 ++++++++---------------------- test/unit/config/configFileTest.ts | 14 +++++++------ test/unit/config/configTest.ts | 2 +- test/unit/projectTest.ts | 3 ++- yarn.lock | 8 ++++---- 10 files changed, 38 insertions(+), 75 deletions(-) diff --git a/package.json b/package.json index 1da0199a20..5937cf1f7c 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "@salesforce/core", - "version": "5.3.4", + "version": "6.0.0", "description": "Core libraries to interact with SFDX projects, orgs, and APIs.", "main": "lib/exported", "types": "lib/exported.d.ts", "license": "BSD-3-Clause", "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "scripts": { "build": "wireit", @@ -48,7 +48,7 @@ "faye": "^1.4.0", "form-data": "^4.0.0", "js2xmlparser": "^4.0.1", - "jsforce": "^2.0.0-beta.27", + "jsforce": "^2.0.0-beta.28", "jsonwebtoken": "9.0.2", "jszip": "3.10.1", "pino": "^8.15.1", diff --git a/src/config/config.ts b/src/config/config.ts index 49280c2736..63ef144953 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -446,11 +446,7 @@ export class Config extends ConfigFile { * * @param newContents The new Config value to persist. */ - public async write(newContents?: ConfigProperties): Promise { - if (newContents != null) { - this.setContents(newContents); - } - + public async write(): Promise { await this.cryptProperties(true); await super.write(); diff --git a/src/config/configFile.ts b/src/config/configFile.ts index 0f2a4b198d..0697482021 100644 --- a/src/config/configFile.ts +++ b/src/config/configFile.ts @@ -10,7 +10,6 @@ import { constants as fsConstants, Stats as fsStats } from 'fs'; import { homedir as osHomedir } from 'os'; import { dirname as pathDirname, join as pathJoin } from 'path'; import { lock, lockSync } from 'proper-lockfile'; -import { isPlainObject } from '@salesforce/ts-types'; import { parseJsonMap } from '@salesforce/kit'; import { Global } from '../global'; import { Logger } from '../logger/logger'; @@ -234,7 +233,7 @@ export class ConfigFile< * * @param newContents The new contents of the file. */ - public async write(newContents?: P): Promise

{ + public async write(): Promise

{ // make sure we can write to the directory try { await fs.promises.mkdir(pathDirname(this.getPath()), { recursive: true }); @@ -247,10 +246,6 @@ export class ConfigFile< // get the file modstamp. Do this after the lock acquisition in case the file is being written to. const fileTimestamp = (await fs.promises.stat(this.getPath(), { bigint: true })).mtimeNs; - if (isPlainObject(newContents)) { - this.setContents(newContents); - } - // read the file contents into a LWWMap using the modstamp const stateFromFile = stateFromContents

( parseJsonMap

(await fs.promises.readFile(this.getPath(), 'utf8'), this.getPath()), @@ -276,26 +271,18 @@ export class ConfigFile< * * @param newContents The new contents of the file. */ - public writeSync(newContents?: P): P { + public writeSync(): P { try { fs.mkdirSync(pathDirname(this.getPath()), { recursive: true }); } catch (err) { throw SfError.wrap(err as Error); } - if (isPlainObject(newContents)) { - this.setContents(newContents); - } - // lock the file. Returns an unlock function to call when done. const unlockFn = lockSync(this.getPath(), lockOptions); // get the file modstamp. Do this after the lock acquisition in case the file is being written to. const fileTimestamp = fs.statSync(this.getPath(), { bigint: true }).mtimeNs; - if (isPlainObject(newContents)) { - this.setContents(newContents); - } - // read the file contents into a LWWMap using the modstamp const stateFromFile = stateFromContents

( parseJsonMap

(fs.readFileSync(this.getPath(), 'utf8'), this.getPath()), diff --git a/src/config/ttlConfig.ts b/src/config/ttlConfig.ts index afc2874b96..4eeb497b7d 100644 --- a/src/config/ttlConfig.ts +++ b/src/config/ttlConfig.ts @@ -46,7 +46,10 @@ export class TTLConfig extends C protected async init(): Promise { const contents = await this.read(this.options.throwOnNotFound); const date = new Date().getTime(); - this.setContents(Object.fromEntries(Object.entries(contents).filter(([, value]) => !this.isExpired(date, value)))); + // delete all the expired entries + Object.entries(contents) + .filter(([, value]) => !this.isExpired(date, value)) + .map(([key]) => this.unset(key)); } // eslint-disable-next-line class-methods-use-this diff --git a/src/sfProject.ts b/src/sfProject.ts index 52975a70cb..06008c66b3 100644 --- a/src/sfProject.ts +++ b/src/sfProject.ts @@ -113,18 +113,12 @@ export class SfProjectJson extends ConfigFile { return contents; } - public async write(newContents?: ConfigContents): Promise { - if (newContents) { - this.setContents(newContents); - } + public async write(): Promise { this.validateKeys(); return super.write(); } - public writeSync(newContents?: ConfigContents): ConfigContents { - if (newContents) { - this.setContents(newContents); - } + public writeSync(): ConfigContents { this.validateKeys(); return super.writeSync(); } @@ -334,13 +328,8 @@ export class SfProjectJson extends ConfigFile { if (!/^.{15,18}$/.test(id)) { throw messages.createError('invalidId', [id]); } - - const contents = this.getContents(); - if (!contents.packageAliases) { - contents.packageAliases = {}; - } - contents.packageAliases[alias] = id; - this.setContents(contents); + const newAliases = { ...(this.getContents().packageAliases ?? {}), [alias]: id }; + this.contents.set('packageAliases', newAliases); } /** diff --git a/src/testSetup.ts b/src/testSetup.ts index 8d2f7f511d..4b84c4d9dc 100644 --- a/src/testSetup.ts +++ b/src/testSetup.ts @@ -86,10 +86,6 @@ export interface ConfigStub { * A function to conditionally read based on the config instance. The `this` value will be the config instance. */ retrieveContents?: () => Promise; - /** - * A function to conditionally set based on the config instance. The `this` value will be the config instance. - */ - updateContents?: () => Promise; } /** @@ -533,7 +529,7 @@ export const stubContext = (testContext: TestContext): Record const readSync = function (this: ConfigFile, newContents?: JsonMap): JsonMap { const stub = initStubForRead(this); - this.setContentsFromObject(newContents ?? stub.contents ?? {}); + this.setContentsFromFileContents(newContents ?? stub.contents ?? {}, BigInt(Date.now())); return this.getContents(); }; @@ -556,33 +552,22 @@ export const stubContext = (testContext: TestContext): Record // @ts-expect-error: muting exact type match for stub readSync stubs.configReadSync = testContext.SANDBOXES.CONFIG.stub(ConfigFile.prototype, 'readSync').callsFake(readSync); - const writeSync = function (this: ConfigFile, newContents?: ConfigContents): void { - if (!testContext.configStubs[this.constructor.name]) { - testContext.configStubs[this.constructor.name] = {}; - } - const stub = testContext.configStubs[this.constructor.name]; - if (!stub) return; + const writeSync = function (this: ConfigFile): void { + testContext.configStubs[this.constructor.name] ??= {}; + const stub = testContext.configStubs[this.constructor.name] ?? {}; - this.setContents(newContents ?? this.getContents()); stub.contents = this.toObject(); }; - const write = async function (this: ConfigFile, newContents?: ConfigContents): Promise { - if (!testContext.configStubs[this.constructor.name]) { - testContext.configStubs[this.constructor.name] = {}; - } - const stub = testContext.configStubs[this.constructor.name]; - if (!stub) return; + const write = async function (this: ConfigFile): Promise { + testContext.configStubs[this.constructor.name] ??= {}; + const stub = testContext.configStubs[this.constructor.name] ?? {}; if (stub.writeFn) { - return stub.writeFn.call(this, newContents); + return stub.writeFn.call(this); } - if (stub.updateContents) { - writeSync.call(this, await stub.updateContents.call(this)); - } else { - writeSync.call(this); - } + writeSync.call(this); }; stubs.configWriteSync = stubMethod(testContext.SANDBOXES.CONFIG, ConfigFile.prototype, 'writeSync').callsFake( diff --git a/test/unit/config/configFileTest.ts b/test/unit/config/configFileTest.ts index 1ec2c18b24..07e4827804 100644 --- a/test/unit/config/configFileTest.ts +++ b/test/unit/config/configFileTest.ts @@ -208,7 +208,7 @@ describe('Config', () => { it('uses passed in contents', async () => { $$.SANDBOX.stub(fs.promises, 'readFile').resolves('{}'); // @ts-expect-error --> we're only mocking on prop of many - $$.SANDBOX.stub(fs.promises, 'stat').resolves({ mtimeNs: BigInt(new Date().valueOf() - 1_000 * 60 * 5) }); + $$.SANDBOX.stub(fs.promises, 'stat').resolves({ mtimeNs: BigInt(Date.now() - 1_000 * 60 * 5) }); $$.SANDBOX.stub(fs.promises, 'mkdir').resolves(); const lockStub = $$.SANDBOX.stub(lockfileLib, 'lock').resolves(() => Promise.resolve()); @@ -217,7 +217,8 @@ describe('Config', () => { const config = await TestConfig.create({ isGlobal: true }); const expected = { test: 'test' }; - const actual = await config.write(expected); + config.set('test', 'test'); + const actual = await config.write(); expect(lockStub.callCount).to.equal(1); expect(expected).to.deep.equal(actual); expect(expected).to.deep.equal(config.getContents()); @@ -227,7 +228,7 @@ describe('Config', () => { it('sync uses passed in contents', async () => { $$.SANDBOX.stub(fs, 'readFileSync').returns('{}'); // @ts-expect-error --> we're only mocking on prop of many - $$.SANDBOX.stub(fs, 'statSync').returns({ mtimeNs: BigInt(new Date().valueOf() - 1_000 * 60 * 5) }); + $$.SANDBOX.stub(fs, 'statSync').returns({ mtimeNs: BigInt(Date.now() - 1_000 * 60 * 5) }); const lockStub = $$.SANDBOX.stub(lockfileLib, 'lockSync').returns(() => undefined); const mkdirpStub = $$.SANDBOX.stub(fs, 'mkdirSync'); @@ -236,7 +237,8 @@ describe('Config', () => { const config = await TestConfig.create({ isGlobal: true }); const expected = { test: 'test' }; - const actual = config.writeSync(expected); + config.set('test', 'test'); + const actual = config.writeSync(); expect(lockStub.callCount).to.equal(1); expect(expected).to.deep.equal(actual); expect(expected).to.deep.equal(config.getContents()); @@ -258,7 +260,7 @@ describe('Config', () => { $$.SANDBOXES.CONFIG.restore(); readFileStub = $$.SANDBOX.stub(fs.promises, 'readFile'); // @ts-expect-error --> we're only mocking on prop of many - $$.SANDBOX.stub(fs.promises, 'stat').resolves({ mtimeNs: BigInt(new Date().valueOf() - 1_000 * 60 * 5) }); + $$.SANDBOX.stub(fs.promises, 'stat').resolves({ mtimeNs: BigInt(Date.now() - 1_000 * 60 * 5) }); }); it('caches file contents', async () => { @@ -342,7 +344,7 @@ describe('Config', () => { $$.SANDBOXES.CONFIG.restore(); readFileStub = $$.SANDBOX.stub(fs, 'readFileSync'); // @ts-expect-error --> we're only mocking on prop of many - $$.SANDBOX.stub(fs, 'statSync').returns({ mtimeNs: BigInt(new Date().valueOf() - 1_000 * 60 * 5) }); + $$.SANDBOX.stub(fs, 'statSync').returns({ mtimeNs: BigInt(Date.now() - 1_000 * 60 * 5) }); }); it('caches file contents', () => { diff --git a/test/unit/config/configTest.ts b/test/unit/config/configTest.ts index d307a1a153..0c405b36e2 100644 --- a/test/unit/config/configTest.ts +++ b/test/unit/config/configTest.ts @@ -281,7 +281,7 @@ describe('Config', () => { stubMethod($$.SANDBOX, ConfigFile.prototype, ConfigFile.prototype.read.name).callsFake(async function () { // @ts-expect-error -> this is any // eslint-disable-next-line @typescript-eslint/no-unsafe-call - this.setContentsFromObject({ unknown: 'unknown config key and value' }); + this.setContentsFromFileContents({ unknown: 'unknown config key and value' }, BigInt(Date.now())); }); const config = await Config.create({ isGlobal: true }); diff --git a/test/unit/projectTest.ts b/test/unit/projectTest.ts index fff2631d51..cf995ce199 100644 --- a/test/unit/projectTest.ts +++ b/test/unit/projectTest.ts @@ -28,7 +28,8 @@ describe('SfProject', () => { describe('json', () => { it('allows uppercase packaging aliases on write', async () => { const json = await SfProjectJson.create(); - await json.write({ packageAliases: { MyName: 'somePackage' } }); + json.set('packageAliases', { MyName: 'somePackage' }); + await json.write(); // @ts-expect-error possibly undefined expect($$.getConfigStubContents('SfProjectJson').packageAliases['MyName']).to.equal('somePackage'); }); diff --git a/yarn.lock b/yarn.lock index 0a835d65a8..9c12f516e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3080,10 +3080,10 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== -jsforce@^2.0.0-beta.27: - version "2.0.0-beta.27" - resolved "https://registry.yarnpkg.com/jsforce/-/jsforce-2.0.0-beta.27.tgz#ba822b18b77bea4529491060a9b07bc1009735ac" - integrity sha512-d9dDWWeHwayRKPo8FJBAIUyk8sNXGSHwdUjR6al3yK0YKci27Jc1XfNaQTxEAuymHQJVaCb1gxTKqmA4uznFdQ== +jsforce@^2.0.0-beta.28: + version "2.0.0-beta.28" + resolved "https://registry.yarnpkg.com/jsforce/-/jsforce-2.0.0-beta.28.tgz#5fd8d9b8e5efc798698793b147e00371f3d74e8f" + integrity sha512-tTmKRhr4yWNinhmurY/tiiltLFQq9RQ+gpYAt3wjFdCGjzd49/wqYQIFw4SsI3+iLjxXnc0uTgGwdAkDjxDWnA== dependencies: "@babel/runtime" "^7.12.5" "@babel/runtime-corejs3" "^7.12.5" From cdd36fb0641375cd0237dbedba345f06747a4c62 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 11 Oct 2023 15:30:16 -0500 Subject: [PATCH 08/67] refactor: defaults via spread --- src/sfProject.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/sfProject.ts b/src/sfProject.ts index 06008c66b3..3b664d76aa 100644 --- a/src/sfProject.ts +++ b/src/sfProject.ts @@ -129,12 +129,7 @@ export class SfProjectJson extends ConfigFile { // eslint-disable-next-line class-methods-use-this public getDefaultOptions(options?: ConfigFile.Options): ConfigFile.Options { - const defaultOptions: ConfigFile.Options = { - isState: false, - }; - - Object.assign(defaultOptions, options ?? {}); - return defaultOptions; + return { ...{ isState: false }, ...(options ?? {}) }; } /** From dddd83b5975615715c2c79ab9440398a2283a290 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 11 Oct 2023 15:31:02 -0500 Subject: [PATCH 09/67] refactor: configFile.write handles file creation --- src/config/configFile.ts | 79 +++++++++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/src/config/configFile.ts b/src/config/configFile.ts index 0697482021..c7655434e4 100644 --- a/src/config/configFile.ts +++ b/src/config/configFile.ts @@ -241,26 +241,36 @@ export class ConfigFile< throw SfError.wrap(err as Error); } + let unlockFn: (() => Promise) | undefined; // lock the file. Returns an unlock function to call when done. - const unlockFn = await lock(this.getPath(), lockRetryOptions); - // get the file modstamp. Do this after the lock acquisition in case the file is being written to. - const fileTimestamp = (await fs.promises.stat(this.getPath(), { bigint: true })).mtimeNs; - - // read the file contents into a LWWMap using the modstamp - const stateFromFile = stateFromContents

( - parseJsonMap

(await fs.promises.readFile(this.getPath(), 'utf8'), this.getPath()), - fileTimestamp, - this.getPath() - ); - // merge the new contents into the in-memory LWWMap - this.contents.merge(stateFromFile); - + try { + unlockFn = await lock(this.getPath(), lockRetryOptions); + // get the file modstamp. Do this after the lock acquisition in case the file is being written to. + const fileTimestamp = (await fs.promises.stat(this.getPath(), { bigint: true })).mtimeNs; + + // read the file contents into a LWWMap using the modstamp + const stateFromFile = stateFromContents

( + parseJsonMap

(await fs.promises.readFile(this.getPath(), 'utf8'), this.getPath()), + fileTimestamp, + this.getPath() + ); + // merge the new contents into the in-memory LWWMap + this.contents.merge(stateFromFile); + } catch (err) { + if (err instanceof Error && err.message.includes('ENOENT')) { + this.logger.debug(`No file found at ${this.getPath()}. Write will create it.`); + } else { + throw err; + } + } // write the merged LWWMap to file this.logger.info(`Writing to config file: ${this.getPath()}`); await fs.promises.writeFile(this.getPath(), JSON.stringify(this.toObject(), null, 2)); // unlock the file - await unlockFn(); + if (typeof unlockFn !== 'undefined') { + await unlockFn(); + } return this.getContents(); } @@ -278,27 +288,38 @@ export class ConfigFile< throw SfError.wrap(err as Error); } - // lock the file. Returns an unlock function to call when done. - const unlockFn = lockSync(this.getPath(), lockOptions); - // get the file modstamp. Do this after the lock acquisition in case the file is being written to. - const fileTimestamp = fs.statSync(this.getPath(), { bigint: true }).mtimeNs; - - // read the file contents into a LWWMap using the modstamp - const stateFromFile = stateFromContents

( - parseJsonMap

(fs.readFileSync(this.getPath(), 'utf8'), this.getPath()), - fileTimestamp, - this.getPath() - ); - // merge the new contents into the in-memory LWWMap - this.contents.merge(stateFromFile); + let unlockFn: (() => void) | undefined; + try { + // lock the file. Returns an unlock function to call when done. + unlockFn = lockSync(this.getPath(), lockOptions); + // get the file modstamp. Do this after the lock acquisition in case the file is being written to. + const fileTimestamp = fs.statSync(this.getPath(), { bigint: true }).mtimeNs; + + // read the file contents into a LWWMap using the modstamp + const stateFromFile = stateFromContents

( + parseJsonMap

(fs.readFileSync(this.getPath(), 'utf8'), this.getPath()), + fileTimestamp, + this.getPath() + ); + // merge the new contents into the in-memory LWWMap + this.contents.merge(stateFromFile); + } catch (err) { + if (err instanceof Error && err.message.includes('ENOENT')) { + this.logger.debug(`No file found at ${this.getPath()}. Write will create it.`); + } else { + throw err; + } + } // write the merged LWWMap to file this.logger.info(`Writing to config file: ${this.getPath()}`); fs.writeFileSync(this.getPath(), JSON.stringify(this.toObject(), null, 2)); - // unlock the file - unlockFn(); + if (typeof unlockFn !== 'undefined') { + // unlock the file + unlockFn(); + } return this.getContents(); } From a039b7ffcc9ed0e0a7a076d503902c936708b2e0 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 11 Oct 2023 17:03:06 -0500 Subject: [PATCH 10/67] test: soiGenerator uses testSetup for projects, auth --- src/testSetup.ts | 1 + test/unit/org/scratchOrgInfoGeneratorTest.ts | 586 ++++++++++--------- 2 files changed, 307 insertions(+), 280 deletions(-) diff --git a/src/testSetup.ts b/src/testSetup.ts index 4b84c4d9dc..94c93d5364 100644 --- a/src/testSetup.ts +++ b/src/testSetup.ts @@ -517,6 +517,7 @@ export const stubContext = (testContext: TestContext): Record stubMethod(testContext.SANDBOXES.PROJECT, SfProjectJson.prototype, 'doesPackageExist').callsFake(() => true); const initStubForRead = (configFile: ConfigFile): ConfigStub => { + testContext.configStubs[configFile.constructor.name] ??= {}; const stub: ConfigStub = testContext.configStubs[configFile.constructor.name] ?? {}; // init calls read calls getPath which sets the path on the config file the first time. // Since read is now stubbed, make sure to call getPath to initialize it. diff --git a/test/unit/org/scratchOrgInfoGeneratorTest.ts b/test/unit/org/scratchOrgInfoGeneratorTest.ts index c096241125..186a1da9b6 100644 --- a/test/unit/org/scratchOrgInfoGeneratorTest.ts +++ b/test/unit/org/scratchOrgInfoGeneratorTest.ts @@ -9,7 +9,7 @@ import * as fs from 'fs'; import * as sinon from 'sinon'; import { expect } from 'chai'; import { stubMethod, stubInterface } from '@salesforce/ts-sinon'; -import { shouldThrow } from '../../../src/testSetup'; +import { MockTestOrgData, shouldThrow, TestContext } from '../../../src/testSetup'; import { Org, Connection } from '../../../src/org'; import { ScratchOrgInfoPayload, @@ -28,12 +28,17 @@ Messages.importMessagesDirectory(__dirname); const messages = Messages.loadMessages('@salesforce/core', 'scratchOrgInfoGenerator'); describe('scratchOrgInfoGenerator', () => { + const $$ = new TestContext(); + describe('ancestorIds', () => { - const sandbox = sinon.createSandbox(); - beforeEach(() => { - stubMethod(sandbox, Org, 'create').resolves(Org.prototype); - stubMethod(sandbox, Org.prototype, 'getConnection').returns(Connection.prototype); - stubMethod(sandbox, Connection.prototype, 'singleRecordQuery') + const testData = new MockTestOrgData(); + + beforeEach(async () => { + await $$.stubAuths(testData); + + stubMethod($$.SANDBOX, Org, 'create').resolves(Org.prototype); + stubMethod($$.SANDBOX, Org.prototype, 'getConnection').returns(Connection.prototype); + stubMethod($$.SANDBOX, Connection.prototype, 'singleRecordQuery') .withArgs(`SELECT Id FROM Package2Version WHERE SubscriberPackageVersionId = '${packageVersionSubscriberId}'`, { tooling: true, }) @@ -55,64 +60,68 @@ describe('scratchOrgInfoGenerator', () => { ) .resolves({ Id: packageId, IsReleased: true }); }); - afterEach(() => { - sandbox.restore(); - }); - - after(() => { - sandbox.restore(); - }); describe('basic ids', () => { it('Should parse 05i ancestorId keys in sfdx-project.json', async () => { - const projectJson = stubInterface(sandbox, { - getPackageDirectories: () => [ - { path: 'foo', package: 'fooPkgName', versionNumber: '4.7.0.NEXT', ancestorId: packageId }, - ], - get: (arg: string) => { - if (arg === 'packageAliases') { - return { alias: packageId }; - } + $$.setConfigStubContents('SfProjectJson', { + contents: { + packageDirectories: [ + { + path: 'foo', + package: 'fooPkgName', + versionNumber: '4.7.0.NEXT', + ancestorId: packageId, + default: true, + }, + ], + packageAliases: { alias: packageId }, }, }); expect( await getAncestorIds( {} as unknown as ScratchOrgInfoPayload, - projectJson as unknown as SfProjectJson, + await SfProjectJson.create(), await Org.create({}) ) ).to.equal(packageId); }); it('Should parse 04t ancestorId keys in sfdx-project.json', async () => { - const projectJson = stubInterface(sandbox, { - getPackageDirectories: () => [ - { path: 'foo', package: 'fooPkgName', versionNumber: '4.7.0.NEXT', ancestorId: packageVersionSubscriberId }, - ], - get: (arg: string) => { - if (arg === 'packageAliases') { - return { alias: packageId }; - } + $$.setConfigStubContents('SfProjectJson', { + contents: { + packageDirectories: [ + { + path: 'foo', + package: 'fooPkgName', + versionNumber: '4.7.0.NEXT', + ancestorId: packageVersionSubscriberId, + default: true, + }, + ], + packageAliases: { alias: packageId }, }, }); expect( await getAncestorIds( {} as unknown as ScratchOrgInfoPayload, - projectJson as unknown as SfProjectJson, + await SfProjectJson.create(), await Org.create({}) ) ).to.equal(packageId); }); it('Should throw on a bad ID', async () => { - const projectJson = stubInterface(sandbox, { - getPackageDirectories: () => [ - { path: 'foo', package: 'fooPkgName', versionNumber: '4.7.0.NEXT', ancestorId: 'ABC' }, - ], + $$.setConfigStubContents('SfProjectJson', { + contents: { + packageDirectories: [ + { path: 'foo', package: 'fooPkgName', versionNumber: '4.7.0.NEXT', ancestorId: 'ABC', default: true }, + ], + }, }); + try { await shouldThrow( getAncestorIds( { package2AncestorIds: 'foo' } as unknown as ScratchOrgInfoPayload, - projectJson as unknown as SfProjectJson, + await SfProjectJson.create(), await Org.create({}) ) ); @@ -121,27 +130,27 @@ describe('scratchOrgInfoGenerator', () => { } }); it('Should throw on a bad returned Id', async () => { - const projectJson = stubInterface(sandbox, { - getPackageDirectories: () => [ - { - path: 'foo', - package: packageId, - versionNumber: '4.7.0.NEXT', - ancestorId: 'ABC', - ancestorVersion: '4.0.0.0', - }, - ], - get: (arg: string) => { - if (arg === 'packageAliases') { - return { alias: packageId }; - } + $$.setConfigStubContents('SfProjectJson', { + contents: { + packageDirectories: [ + { + path: 'foo', + package: packageId, + versionNumber: '4.7.0.NEXT', + ancestorId: 'ABC', + ancestorVersion: '4.0.0.0', + default: true, + }, + ], + packageAliases: { alias: packageId }, }, }); + try { await shouldThrow( getAncestorIds( { ancestorId: 'foABCo' } as unknown as ScratchOrgInfoPayload, - projectJson as unknown as SfProjectJson, + await SfProjectJson.create(), await Org.create({}) ) ); @@ -153,21 +162,20 @@ describe('scratchOrgInfoGenerator', () => { describe('multiple ancestors', () => { it('Should merge duplicated ancestors', async () => { - const projectJson = stubInterface(sandbox, { - getPackageDirectories: () => [ - { path: 'foo', package: 'fooPkgName', versionNumber: '4.7.0.NEXT', ancestorId: packageId }, - { path: 'bar', package: 'barPkgName', versionNumber: '4.7.0.NEXT', ancestorId: packageId }, - ], - get: (arg: string) => { - if (arg === 'packageAliases') { - return { alias: packageId }; - } + $$.setConfigStubContents('SfProjectJson', { + contents: { + packageDirectories: [ + { path: 'foo', package: 'fooPkgName', versionNumber: '4.7.0.NEXT', ancestorId: packageId, default: true }, + { path: 'bar', package: 'barPkgName', versionNumber: '4.7.0.NEXT', ancestorId: packageId }, + ], + packageAliases: { alias: packageId }, }, }); + expect( await getAncestorIds( {} as unknown as ScratchOrgInfoPayload, - projectJson as unknown as SfProjectJson, + await SfProjectJson.create(), await Org.create({}) ) ).to.equal(packageId); @@ -175,21 +183,26 @@ describe('scratchOrgInfoGenerator', () => { it('Should join multiple ancestors', async () => { const otherPackageId = packageId.replace('W', 'Q'); - const projectJson = stubInterface(sandbox, { - getPackageDirectories: () => [ - { path: 'foo', package: 'fooPkgName', versionNumber: '4.7.0.NEXT', ancestorId: packageId }, - { path: 'bar', package: 'barPkgName', versionNumber: '4.7.0.NEXT', ancestorId: otherPackageId }, - ], - get: (arg: string) => { - if (arg === 'packageAliases') { - return { alias: packageId }; - } + $$.setConfigStubContents('SfProjectJson', { + contents: { + packageDirectories: [ + { + path: 'foo', + package: 'fooPkgName', + versionNumber: '4.7.0.NEXT', + ancestorId: packageId, + default: true, + }, + { path: 'bar', package: 'barPkgName', versionNumber: '4.7.0.NEXT', ancestorId: otherPackageId }, + ], + packageAliases: { alias: packageId }, }, }); + expect( await getAncestorIds( {} as unknown as ScratchOrgInfoPayload, - projectJson as unknown as SfProjectJson, + await SfProjectJson.create(), await Org.create({}) ) ).to.equal(`${packageId};${otherPackageId}`); @@ -198,16 +211,25 @@ describe('scratchOrgInfoGenerator', () => { describe('unusual projects', () => { it('Should return an error if the package2AncestorIds key is included in the scratch org definition', async () => { - const projectJson = stubInterface(sandbox, { - getPackageDirectories: () => [ - { path: 'foo', package: 'fooPkgName', versionNumber: '4.7.0.NEXT', ancestorId: packageVersionSubscriberId }, - ], + $$.setConfigStubContents('SfProjectJson', { + contents: { + packageDirectories: [ + { + path: 'foo', + package: 'fooPkgName', + versionNumber: '4.7.0.NEXT', + ancestorId: packageVersionSubscriberId, + default: true, + }, + ], + }, }); + try { await shouldThrow( getAncestorIds( { package2AncestorIds: 'foo' } as unknown as ScratchOrgInfoPayload, - projectJson as unknown as SfProjectJson, + await SfProjectJson.create(), await Org.create({}) ) ); @@ -218,27 +240,21 @@ describe('scratchOrgInfoGenerator', () => { } }); - it('Should not fail with an empty packageDirectories key', async () => { - const projectJson = stubInterface(sandbox, { - getPackageDirectories: () => [], - }); - expect( - await getAncestorIds( - {} as unknown as ScratchOrgInfoPayload, - projectJson as unknown as SfProjectJson, - await Org.create({}) - ) - ).to.equal(''); - }); - it('Should return empty string with no ancestors', async () => { - const projectJson = stubInterface(sandbox, { - getPackageDirectories: () => [{ path: 'foo', package: 'fooPkgName' }], + $$.setConfigStubContents('SfProjectJson', { + contents: { + packageDirectories: [ + { + path: 'foo', + package: 'fooPkgName', + }, + ], + }, }); expect( await getAncestorIds( {} as unknown as ScratchOrgInfoPayload, - projectJson as unknown as SfProjectJson, + await SfProjectJson.create(), await Org.create({}) ) ).to.equal(''); @@ -247,43 +263,47 @@ describe('scratchOrgInfoGenerator', () => { describe('aliases as ancestorId', () => { it('Should resolve an alias', async () => { - const projectJson = stubInterface(sandbox, { - getPackageDirectories: () => [ - { path: 'foo', package: 'fooPkgName', versionNumber: '4.7.0.NEXT', ancestorId: 'alias' }, - ], - get: (arg: string) => { - if (arg === 'packageAliases') { - return { alias: packageId }; - } + $$.setConfigStubContents('SfProjectJson', { + contents: { + packageDirectories: [ + { + path: 'foo', + package: 'fooPkgName', + versionNumber: '4.7.0.NEXT', + ancestorId: 'alias', + default: true, + }, + ], + packageAliases: { alias: packageId }, }, }); expect( await getAncestorIds( {} as unknown as ScratchOrgInfoPayload, - projectJson as unknown as SfProjectJson, + await SfProjectJson.create(), await Org.create({}) ) ).to.equal(packageId); }); it('Should resolve an alias packageAliases is undefined', async () => { - const projectJson = stubInterface(sandbox, { - getPackageDirectories: () => [ - { path: 'foo', package: 'fooPkgName', versionNumber: '4.7.0.NEXT', ancestorId: 'alias' }, - ], - get: (arg: string) => { - if (arg === 'packageAliases') { - return undefined; - } + $$.setConfigStubContents('SfProjectJson', { + contents: { + packageDirectories: [ + { + path: 'foo', + package: 'fooPkgName', + versionNumber: '4.7.0.NEXT', + ancestorId: 'alias', + default: true, + }, + ], }, }); + try { await shouldThrow( - getAncestorIds( - {} as unknown as ScratchOrgInfoPayload, - projectJson as unknown as SfProjectJson, - await Org.create({}) - ) + getAncestorIds({} as unknown as ScratchOrgInfoPayload, await SfProjectJson.create(), await Org.create({})) ); } catch (err) { expect(err).to.exist; @@ -291,21 +311,25 @@ describe('scratchOrgInfoGenerator', () => { }); it('Should throw on unresolvable alias', async () => { - const projectJson = stubInterface(sandbox, { - getPackageDirectories: () => [ - { path: 'foo', package: 'fooPkgName', versionNumber: '4.7.0.NEXT', ancestorId: 'alias' }, - ], - get: (arg: string) => { - if (arg === 'packageAliases') { - return { notTheAlias: packageId }; - } + $$.setConfigStubContents('SfProjectJson', { + contents: { + packageDirectories: [ + { + path: 'foo', + package: 'fooPkgName', + versionNumber: '4.7.0.NEXT', + ancestorId: 'alias', + default: true, + }, + ], + packageAliases: { notTheAlias: packageId }, }, }); try { await shouldThrow( getAncestorIds( { package2AncestorIds: 'foo' } as unknown as ScratchOrgInfoPayload, - projectJson as unknown as SfProjectJson, + await SfProjectJson.create(), await Org.create({}) ) ); @@ -317,23 +341,24 @@ describe('scratchOrgInfoGenerator', () => { describe('ancestorVersion', () => { it('Should throw on an invalid ancestorVersion', async () => { - const projectJson = stubInterface(sandbox, { - getPackageDirectories: () => [ - { path: 'foo', package: 'fooPkgName', versionNumber: '4.7.0.NEXT', ancestorVersion: '5.0' }, - ], - get: (arg: string) => { - if (arg === 'packageAliases') { - return { alias: packageId }; - } + $$.setConfigStubContents('SfProjectJson', { + contents: { + packageDirectories: [ + { + path: 'foo', + package: 'fooPkgName', + versionNumber: '4.7.0.NEXT', + ancestorVersion: '5.0', + default: true, + }, + ], + packageAliases: { alias: packageId }, }, }); + try { await shouldThrow( - getAncestorIds( - {} as unknown as ScratchOrgInfoPayload, - projectJson as unknown as SfProjectJson, - await Org.create({}) - ) + getAncestorIds({} as unknown as ScratchOrgInfoPayload, await SfProjectJson.create(), await Org.create({})) ); } catch (err) { expect(err).to.exist; @@ -343,99 +368,110 @@ describe('scratchOrgInfoGenerator', () => { }); it('Should resolve an alias ancestor version', async () => { - const projectJson = stubInterface(sandbox, { - getPackageDirectories: () => [ - { path: 'foo', package: 'fooPkgName', versionNumber: '4.7.0.NEXT', ancestorVersion: '4.0.0.0' }, - ], - get: (arg: string) => { - if (arg === 'packageAliases') { - return { fooPkgName: packageId }; - } + $$.setConfigStubContents('SfProjectJson', { + contents: { + packageDirectories: [ + { + path: 'foo', + package: 'fooPkgName', + versionNumber: '4.7.0.NEXT', + ancestorVersion: '4.0.0.0', + default: true, + }, + ], + packageAliases: { fooPkgName: packageId }, }, }); expect( await getAncestorIds( {} as unknown as ScratchOrgInfoPayload, - projectJson as unknown as SfProjectJson, + await SfProjectJson.create(), await Org.create({}) ) ).to.equal(packageId); }); it('Should resolve via HIGHEST ancestorVersion', async () => { - const projectJson = stubInterface(sandbox, { - getPackageDirectories: () => [ - { path: 'foo', package: 'fooPkgName', versionNumber: '4.7.0.NEXT', ancestorVersion: 'HIGHEST' }, - ], - get: (arg: string) => { - if (arg === 'packageAliases') { - return { fooPkgName: packageId }; - } + $$.setConfigStubContents('SfProjectJson', { + contents: { + packageDirectories: [ + { + path: 'foo', + package: 'fooPkgName', + versionNumber: '4.7.0.NEXT', + ancestorVersion: 'HIGHEST', + default: true, + }, + ], + packageAliases: { fooPkgName: packageId }, }, }); + expect( await getAncestorIds( {} as unknown as ScratchOrgInfoPayload, - projectJson as unknown as SfProjectJson, + await SfProjectJson.create(), await Org.create({}) ) ).to.equal(packageId); }); it('Should resolve via NONE ancestorVersion', async () => { - const projectJson = stubInterface(sandbox, { - getPackageDirectories: () => [ - { path: 'foo', package: 'fooPkgName', versionNumber: '4.7.0.NEXT', ancestorVersion: 'NONE' }, - ], - get: (arg: string) => { - if (arg === 'packageAliases') { - return { fooPkgName: packageId }; - } + $$.setConfigStubContents('SfProjectJson', { + contents: { + packageDirectories: [ + { + path: 'foo', + package: 'fooPkgName', + versionNumber: '4.7.0.NEXT', + ancestorVersion: 'NONE', + default: true, + }, + ], + packageAliases: { fooPkgName: packageId }, }, }); + expect( await getAncestorIds( {} as unknown as ScratchOrgInfoPayload, - projectJson as unknown as SfProjectJson, + await SfProjectJson.create(), await Org.create({}) ) ).to.equal(''); }); it('Should resolve via HIGHEST ancestorId', async () => { - const projectJson = stubInterface(sandbox, { - getPackageDirectories: () => [ - { path: 'foo', package: 'fooPkgName', versionNumber: '4.7.0.NEXT', ancestorId: 'HIGHEST' }, - ], - get: (arg: string) => { - if (arg === 'packageAliases') { - return { fooPkgName: packageId }; - } + $$.setConfigStubContents('SfProjectJson', { + contents: { + packageDirectories: [ + { path: 'foo', package: 'fooPkgName', versionNumber: '4.7.0.NEXT', ancestorId: 'HIGHEST', default: true }, + ], + packageAliases: { fooPkgName: packageId }, }, }); + expect( await getAncestorIds( {} as unknown as ScratchOrgInfoPayload, - projectJson as unknown as SfProjectJson, + await SfProjectJson.create(), await Org.create({}) ) ).to.equal(packageId); }); it('Should resolve via NONE ancestorId', async () => { - const projectJson = stubInterface(sandbox, { - getPackageDirectories: () => [ - { path: 'foo', package: 'fooPkgName', versionNumber: '4.7.0.NEXT', ancestorId: 'NONE' }, - ], - get: (arg: string) => { - if (arg === 'packageAliases') { - return { fooPkgName: packageId }; - } + $$.setConfigStubContents('SfProjectJson', { + contents: { + packageDirectories: [ + { path: 'foo', package: 'fooPkgName', versionNumber: '4.7.0.NEXT', ancestorId: 'NONE', default: true }, + ], + packageAliases: { fooPkgName: packageId }, }, }); expect( await getAncestorIds( {} as unknown as ScratchOrgInfoPayload, - projectJson as unknown as SfProjectJson, + await SfProjectJson.create(), await Org.create({}) ) ).to.equal(''); @@ -561,113 +597,103 @@ describe('scratchOrgInfoGenerator', () => { }); describe('generateScratchOrgInfo', () => { - const sandbox = sinon.createSandbox(); - let getAuthInfoFieldsStub: sinon.SinonStub; - beforeEach(() => { - stubMethod(sandbox, Org, 'create').resolves(Org.prototype); - stubMethod(sandbox, Org.prototype, 'getConnection').returns(Connection.prototype); - getAuthInfoFieldsStub = stubMethod(sandbox, Connection.prototype, 'getAuthInfoFields').returns({ - clientId: '1234', + const testData = new MockTestOrgData(); + describe('with clientId', () => { + beforeEach(async () => { + await $$.stubAuths(testData); }); - stubMethod(sandbox, SfProjectJson, 'create').returns(SfProjectJson.prototype); - }); - - afterEach(() => { - sandbox.restore(); - }); - after(() => { - sandbox.restore(); - }); - it('Generates empty package2AncestorIds scratch org property', async () => { - expect( - await generateScratchOrgInfo({ - hubOrg: await Org.create({}), - scratchOrgInfoPayload: {} as ScratchOrgInfoPayload, - nonamespace: false, - ignoreAncestorIds: false, - }) - ).to.deep.equal({ - orgName: 'Company', - package2AncestorIds: '', - connectedAppConsumerKey: '1234', - connectedAppCallbackUrl: 'http://localhost:1717/OauthRedirect', + it('Generates empty package2AncestorIds scratch org property', async () => { + expect( + await generateScratchOrgInfo({ + hubOrg: await Org.create({ aliasOrUsername: testData.username }), + scratchOrgInfoPayload: {} as ScratchOrgInfoPayload, + nonamespace: false, + ignoreAncestorIds: false, + }) + ).to.deep.equal({ + orgName: 'Company', + package2AncestorIds: '', + connectedAppConsumerKey: testData.clientId, + connectedAppCallbackUrl: 'http://localhost:1717/OauthRedirect', + }); }); - }); - it('Generates empty package2AncestorIds scratch org property with hasPackages', async () => { - stubMethod(sandbox, SfProjectJson.prototype, 'hasPackages').returns(true); - stubMethod(sandbox, SfProjectJson.prototype, 'isGlobal').returns(true); - expect( - await generateScratchOrgInfo({ - hubOrg: await Org.create({}), - scratchOrgInfoPayload: {} as ScratchOrgInfoPayload, - nonamespace: false, - ignoreAncestorIds: false, - }) - ).to.deep.equal({ - orgName: 'Company', - package2AncestorIds: '', - connectedAppConsumerKey: '1234', - connectedAppCallbackUrl: 'http://localhost:1717/OauthRedirect', + it('Generates empty package2AncestorIds scratch org property with hasPackages', async () => { + // stubMethod(sandbox, SfProjectJson.prototype, 'hasPackages').returns(true); + // stubMethod(sandbox, SfProjectJson.prototype, 'isGlobal').returns(true); + expect( + await generateScratchOrgInfo({ + hubOrg: await Org.create({ aliasOrUsername: testData.username }), + scratchOrgInfoPayload: {} as ScratchOrgInfoPayload, + nonamespace: false, + ignoreAncestorIds: false, + }) + ).to.deep.equal({ + orgName: 'Company', + package2AncestorIds: '', + connectedAppConsumerKey: testData.clientId, + connectedAppCallbackUrl: 'http://localhost:1717/OauthRedirect', + }); }); - }); - it('Generates the package2AncestorIds scratch org property with ignoreAncestorIds', async () => { - expect( - await generateScratchOrgInfo({ - hubOrg: await Org.create({}), - scratchOrgInfoPayload: {} as ScratchOrgInfoPayload, - nonamespace: false, - ignoreAncestorIds: true, - }) - ).to.deep.equal({ - orgName: 'Company', - package2AncestorIds: '', - connectedAppConsumerKey: '1234', - connectedAppCallbackUrl: 'http://localhost:1717/OauthRedirect', + it('Generates the package2AncestorIds scratch org property with ignoreAncestorIds', async () => { + expect( + await generateScratchOrgInfo({ + hubOrg: await Org.create({ aliasOrUsername: testData.username }), + scratchOrgInfoPayload: {} as ScratchOrgInfoPayload, + nonamespace: false, + ignoreAncestorIds: true, + }) + ).to.deep.equal({ + orgName: 'Company', + package2AncestorIds: '', + connectedAppConsumerKey: testData.clientId, + connectedAppCallbackUrl: 'http://localhost:1717/OauthRedirect', + }); }); }); - it('Generates the package2AncestorIds scratch org property with ignoreAncestorIds no clientId with connectedAppConsumerKey', async () => { - getAuthInfoFieldsStub.restore(); - stubMethod(sandbox, Connection.prototype, 'getAuthInfoFields').returns({ - clientId: undefined, + describe('no client id', () => { + const orgWithoutClientId = new MockTestOrgData(); + // @ts-expect-error - cannot assign undefined to string + orgWithoutClientId.clientId = undefined; + beforeEach(async () => { + // $$.restore(); + await $$.stubAuths(orgWithoutClientId); }); - expect( - await generateScratchOrgInfo({ - hubOrg: await Org.create({}), - scratchOrgInfoPayload: { - connectedAppConsumerKey: '1234', - } as ScratchOrgInfoPayload, - nonamespace: false, - ignoreAncestorIds: true, - }) - ).to.deep.equal({ - orgName: 'Company', - package2AncestorIds: '', - connectedAppConsumerKey: '1234', - connectedAppCallbackUrl: 'http://localhost:1717/OauthRedirect', - }); - }); - it('Generates the package2AncestorIds scratch org property with ignoreAncestorIds no clientId with no connectedAppConsumerKey and orgName', async () => { - getAuthInfoFieldsStub.restore(); - stubMethod(sandbox, Connection.prototype, 'getAuthInfoFields').returns({ - clientId: undefined, + it('Generates the package2AncestorIds scratch org property with ignoreAncestorIds no clientId with connectedAppConsumerKey', async () => { + expect( + await generateScratchOrgInfo({ + hubOrg: await Org.create({ aliasOrUsername: orgWithoutClientId.username }), + scratchOrgInfoPayload: { + connectedAppConsumerKey: orgWithoutClientId.clientId, + } as ScratchOrgInfoPayload, + nonamespace: false, + ignoreAncestorIds: true, + }) + ).to.deep.equal({ + orgName: 'Company', + package2AncestorIds: '', + connectedAppConsumerKey: 'PlatformCLI', + connectedAppCallbackUrl: 'http://localhost:1717/OauthRedirect', + }); }); - expect( - await generateScratchOrgInfo({ - hubOrg: await Org.create({}), - scratchOrgInfoPayload: { - orgName: 'MyOrgName', - // @ts-expect-error - cannot assign undefined to string - connectedAppConsumerKey: undefined, - }, - nonamespace: false, - ignoreAncestorIds: true, - }) - ).to.deep.equal({ - orgName: 'MyOrgName', - package2AncestorIds: '', - connectedAppConsumerKey: 'PlatformCLI', - connectedAppCallbackUrl: 'http://localhost:1717/OauthRedirect', + it('Generates the package2AncestorIds scratch org property with ignoreAncestorIds no clientId with no connectedAppConsumerKey and orgName', async () => { + expect( + await generateScratchOrgInfo({ + hubOrg: await Org.create({ aliasOrUsername: testData.username }), + scratchOrgInfoPayload: { + orgName: 'MyOrgName', + // @ts-expect-error - cannot assign undefined to string + connectedAppConsumerKey: undefined, + }, + nonamespace: false, + ignoreAncestorIds: true, + }) + ).to.deep.equal({ + orgName: 'MyOrgName', + package2AncestorIds: '', + connectedAppConsumerKey: 'PlatformCLI', + connectedAppCallbackUrl: 'http://localhost:1717/OauthRedirect', + }); }); }); }); From bde5005c9d688e179c55fa8a5a94de0611321571 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 11 Oct 2023 17:39:40 -0500 Subject: [PATCH 11/67] feat: typing for what goes in the org-user file --- src/config/orgUsersConfig.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/config/orgUsersConfig.ts b/src/config/orgUsersConfig.ts index 089688af65..b14e1f500e 100644 --- a/src/config/orgUsersConfig.ts +++ b/src/config/orgUsersConfig.ts @@ -8,10 +8,13 @@ import { Global } from '../global'; import { ConfigFile } from './configFile'; +type UserConfig = { + usernames: string[]; +}; /** * A config file that stores usernames for an org. */ -export class OrgUsersConfig extends ConfigFile { +export class OrgUsersConfig extends ConfigFile { /** * Constructor. * **Do not directly construct instances of this class -- use {@link OrgUsersConfig.create} instead.** From a36f946e06647eb61d0ec6ba22ad919dc6bcc294 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 11 Oct 2023 17:40:38 -0500 Subject: [PATCH 12/67] test: un-skip more tests --- test/unit/config/configTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/config/configTest.ts b/test/unit/config/configTest.ts index 0c405b36e2..ccd73f1878 100644 --- a/test/unit/config/configTest.ts +++ b/test/unit/config/configTest.ts @@ -276,7 +276,7 @@ describe('Config', () => { expect(writeStub.called).to.be.true; }); - it.skip('calls ConfigFile.read with unknown key and does not throw on crypt', async () => { + it('calls ConfigFile.read with unknown key and does not throw on crypt', async () => { stubMethod($$.SANDBOX, ConfigFile.prototype, ConfigFile.prototype.readSync.name).callsFake(async () => {}); stubMethod($$.SANDBOX, ConfigFile.prototype, ConfigFile.prototype.read.name).callsFake(async function () { // @ts-expect-error -> this is any From 13dd0851c727c707ad1439b2a707a8f25e4e7d19 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 11 Oct 2023 17:42:12 -0500 Subject: [PATCH 13/67] refactor: set configs username prop, no mutating --- src/org/org.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/org/org.ts b/src/org/org.ts index be6d753d7a..08a5880bb8 100644 --- a/src/org/org.ts +++ b/src/org/org.ts @@ -798,13 +798,13 @@ export class Org extends AsyncOptionalCreatable { this.logger.debug(`removing username ${authInfo.getFields().username}`); const orgConfig: OrgUsersConfig = await this.retrieveOrgUsersConfig(); - - const contents: ConfigContents = await orgConfig.read(); + const contents = await orgConfig.read(); const targetUser = authInfo.getFields().username; - const usernames = (contents.usernames ?? []) as string[]; - contents.usernames = usernames.filter((username) => username !== targetUser); + const usernames = (contents.usernames ?? []).filter((username) => username !== targetUser); + + orgConfig.set('usernames', usernames); await orgConfig.write(); return this; } From 469373fd2d4e74ba5f7d924cc3b5eda9338c1ddc Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 11 Oct 2023 17:56:48 -0500 Subject: [PATCH 14/67] test: remove more skips --- test/unit/org/authInfoTest.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/unit/org/authInfoTest.ts b/test/unit/org/authInfoTest.ts index 9514be6d07..7313908860 100644 --- a/test/unit/org/authInfoTest.ts +++ b/test/unit/org/authInfoTest.ts @@ -1272,7 +1272,7 @@ describe('AuthInfo', () => { expect(authInfo.getSfdxAuthUrl()).to.contain(`force://PlatformCLI::${testOrg.refreshToken}@${instanceUrl}`); }); - it.skip('should handle undefined refresh token', async () => { + it('should handle undefined refresh token', async () => { const authResponse = { access_token: testOrg.accessToken, instance_url: testOrg.instanceUrl, @@ -1292,12 +1292,11 @@ describe('AuthInfo', () => { }); // delete the refresh token - delete authInfo.getFields().refreshToken; - + authInfo.update({ ...authInfo.getFields(), refreshToken: undefined }); expect(() => authInfo.getSfdxAuthUrl()).to.throw('undefined refreshToken'); }); - it.skip('should handle undefined instance url', async () => { + it('should handle undefined instance url', async () => { const authResponse = { access_token: testOrg.accessToken, instance_url: testOrg.instanceUrl, @@ -1317,7 +1316,7 @@ describe('AuthInfo', () => { }); // delete the instance url - delete authInfo.getFields().instanceUrl; + authInfo.update({ ...authInfo.getFields(), instanceUrl: undefined }); expect(() => authInfo.getSfdxAuthUrl()).to.throw('undefined instanceUrl'); }); From 57d56ce119b3d82146f18082bbbeebf8e9aca8a4 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Thu, 12 Oct 2023 17:05:30 -0500 Subject: [PATCH 15/67] test: ttlConfig expiration logic --- src/config/ttlConfig.ts | 2 +- test/unit/config/ttlConfigTest.ts | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/config/ttlConfig.ts b/src/config/ttlConfig.ts index 4eeb497b7d..4b822084cd 100644 --- a/src/config/ttlConfig.ts +++ b/src/config/ttlConfig.ts @@ -48,7 +48,7 @@ export class TTLConfig extends C const date = new Date().getTime(); // delete all the expired entries Object.entries(contents) - .filter(([, value]) => !this.isExpired(date, value)) + .filter(([, value]) => this.isExpired(date, value)) .map(([key]) => this.unset(key)); } diff --git a/test/unit/config/ttlConfigTest.ts b/test/unit/config/ttlConfigTest.ts index cd7f1eb82c..5cbb5fceb7 100644 --- a/test/unit/config/ttlConfigTest.ts +++ b/test/unit/config/ttlConfigTest.ts @@ -96,4 +96,31 @@ describe('TTLConfig', () => { expect(isExpired).to.be.false; }); }); + + describe.only('filters expired keys on init', () => { + $$.setConfigStubContents('TestConfig', { + contents: { + old: { + value: 1, + timestamp: new Date().getTime() - Duration.days(7).milliseconds, + }, + current: { + value: 2, + timestamp: new Date().getTime(), + }, + future: { + value: 3, + timestamp: new Date().getTime() + Duration.days(7).milliseconds, + }, + }, + }); + + it('should omit expired keys', async () => { + const config = await TestConfig.create(); + const keys = config.keys(); + expect(keys).to.include('current'); + expect(keys).to.include('future'); + expect(keys).to.not.include('old'); + }); + }); }); From 41423d63db353ebb65d4e4b8e058718a7b997590 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Thu, 12 Oct 2023 18:06:48 -0500 Subject: [PATCH 16/67] feat!: make AuthInfo.getFields return readonly --- src/org/authInfo.ts | 4 +++- src/org/user.ts | 3 +-- test/unit/org/authInfoTest.ts | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/org/authInfo.ts b/src/org/authInfo.ts index c9ddca993e..e7b49bf724 100644 --- a/src/org/authInfo.ts +++ b/src/org/authInfo.ts @@ -664,8 +664,10 @@ export class AuthInfo extends AsyncOptionalCreatable { * Get the authorization fields. * * @param decrypt Decrypt the fields. + * + * Returns a ReadOnly object of the fields. If you need to modify the fields, use AuthInfo.update() */ - public getFields(decrypt?: boolean): AuthFields { + public getFields(decrypt?: boolean): Readonly { return this.stateAggregator.orgs.get(this.username, decrypt) ?? {}; } diff --git a/src/org/user.ts b/src/org/user.ts index 83d6a71794..718df9bcb2 100644 --- a/src/org/user.ts +++ b/src/org/user.ts @@ -389,8 +389,7 @@ export class User extends AsyncCreatable { }); // Update the auth info object with created user id. - const newUserAuthFields: AuthFields = newUserAuthInfo.getFields(); - newUserAuthFields.userId = refreshTokenSecret.userId; + newUserAuthInfo.update({ userId: refreshTokenSecret.userId }); // Make sure we can connect and if so save the auth info. await this.describeUserAndSave(newUserAuthInfo); diff --git a/test/unit/org/authInfoTest.ts b/test/unit/org/authInfoTest.ts index 7313908860..3d2b5e2b96 100644 --- a/test/unit/org/authInfoTest.ts +++ b/test/unit/org/authInfoTest.ts @@ -1267,7 +1267,7 @@ describe('AuthInfo', () => { }); // delete the client secret - delete authInfo.getFields().clientSecret; + authInfo.update({ clientSecret: undefined }); const instanceUrl = testOrg.instanceUrl.replace('https://', ''); expect(authInfo.getSfdxAuthUrl()).to.contain(`force://PlatformCLI::${testOrg.refreshToken}@${instanceUrl}`); }); From c5926f07519a393094f79f004ecb3610e80d3473 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Fri, 13 Oct 2023 08:12:09 -0500 Subject: [PATCH 17/67] refactor: restore testSetup.uniqid export --- src/testSetup.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/testSetup.ts b/src/testSetup.ts index 94c93d5364..8cc16ba09e 100644 --- a/src/testSetup.ts +++ b/src/testSetup.ts @@ -45,6 +45,8 @@ import { SandboxAccessor } from './stateAggregator/accessors/sandboxAccessor'; import { Global } from './global'; import { uniqid } from './util/uniqid'; +// this was previously exported from the testSetup module +export { uniqid }; /** * Different parts of the system that are mocked out. They can be restored for * individual tests. Test's stubs should always go on the DEFAULT which is exposed From 2481288216924512cbff98d353fa3f346f5ce4c7 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Fri, 13 Oct 2023 10:13:06 -0500 Subject: [PATCH 18/67] chore: bump dev-scripts --- .eslintrc.js => .eslintrc.cjs | 0 package.json | 2 +- test/{.eslintrc.js => .eslintrc.cjs} | 2 +- yarn.lock | 12 ++++++------ 4 files changed, 8 insertions(+), 8 deletions(-) rename .eslintrc.js => .eslintrc.cjs (100%) rename test/{.eslintrc.js => .eslintrc.cjs} (97%) diff --git a/.eslintrc.js b/.eslintrc.cjs similarity index 100% rename from .eslintrc.js rename to .eslintrc.cjs diff --git a/package.json b/package.json index 5937cf1f7c..1e6ef4c599 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ }, "devDependencies": { "@salesforce/dev-config": "^4.0.1", - "@salesforce/dev-scripts": "^5.4.2", + "@salesforce/dev-scripts": "^5.11.0", "@salesforce/prettier-config": "^0.0.3", "@salesforce/ts-sinon": "^1.4.16", "@types/benchmark": "^2.1.3", diff --git a/test/.eslintrc.js b/test/.eslintrc.cjs similarity index 97% rename from test/.eslintrc.js rename to test/.eslintrc.cjs index f1c7d4ff18..43cb31b5c2 100644 --- a/test/.eslintrc.js +++ b/test/.eslintrc.cjs @@ -9,7 +9,7 @@ // See more at https://github.com/forcedotcom/sfdx-dev-packages/tree/master/packages/dev-scripts module.exports = { - extends: '../.eslintrc.js', + extends: '../.eslintrc.cjs', // Allow describe and it env: { mocha: true }, rules: { diff --git a/yarn.lock b/yarn.lock index 9c12f516e9..e1caa7aad3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -526,10 +526,10 @@ resolved "https://registry.yarnpkg.com/@salesforce/dev-config/-/dev-config-4.0.1.tgz#662ffaa4409713553aaf68eed93e7d2429c3ff0e" integrity sha512-0zMjXG4Vjlu/mB7zbuKSXfXiP7CEZBwsPtYqNgburk/wZIU9KcMspLwVBDUxmUj9ltRksD9o1ubRUblN5M3Z0g== -"@salesforce/dev-scripts@^5.4.2": - version "5.4.2" - resolved "https://registry.yarnpkg.com/@salesforce/dev-scripts/-/dev-scripts-5.4.2.tgz#77aacf9a4743fe951d9de9cbc9524a70bb46054e" - integrity sha512-D54yF+NDDa+50A28YyvG8aO5t3tIMIIZ7Q+ewWmUXdpFhqUfwq1k6/Y2tZy+rE9Z8fwWB2DQSD9dhaZavxOgrw== +"@salesforce/dev-scripts@^5.11.0": + version "5.11.0" + resolved "https://registry.yarnpkg.com/@salesforce/dev-scripts/-/dev-scripts-5.11.0.tgz#e5632f0e46f2d821710ca06bb6d60378399b17cd" + integrity sha512-DLgjqBsYc0AiBb5BPiSMSJrwoP9ceAFePPcB6xvLrH9gas+8X3z79vc4xzlBhwzsF1WJsSOoVVTtJbPDwxvF0g== dependencies: "@commitlint/cli" "^17.1.2" "@commitlint/config-conventional" "^17.1.0" @@ -539,7 +539,7 @@ "@types/mocha" "^9.0.0" "@types/node" "^15.6.1" "@types/sinon" "10.0.11" - chai "^4.3.7" + chai "^4.3.8" chalk "^4.0.0" cosmiconfig "^7.0.0" eslint "^8.41.0" @@ -1321,7 +1321,7 @@ chai-string@^1.5.0: resolved "https://registry.yarnpkg.com/chai-string/-/chai-string-1.5.0.tgz#0bdb2d8a5f1dbe90bc78ec493c1c1c180dd4d3d2" integrity sha512-sydDC3S3pNAQMYwJrs6dQX0oBQ6KfIPuOZ78n7rocW0eJJlsHPh2t3kwW7xfwYA/1Bf6/arGtSUo16rxR2JFlw== -chai@^4.3.10, chai@^4.3.7: +chai@^4.3.10, chai@^4.3.8: version "4.3.10" resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.10.tgz#d784cec635e3b7e2ffb66446a63b4e33bd390384" integrity sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g== From 4e02b0a7c384e8a9fb24132af10ef6dcafb00160 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Fri, 13 Oct 2023 10:22:04 -0500 Subject: [PATCH 19/67] chore: bump jsforce deps --- yarn.lock | 107 ++++++++++++++++-------------------------------------- 1 file changed, 32 insertions(+), 75 deletions(-) diff --git a/yarn.lock b/yarn.lock index e1caa7aad3..41633991e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -156,19 +156,19 @@ integrity sha512-RSKRfYX20dyH+elbJK2uqAkVyucL+xXzhqlMD5/ZXx+dAAwpyB7HsvnHe/ZUGOF+xLr5Wx9/JoXVTj6BQE2/oA== "@babel/runtime-corejs3@^7.12.5": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.19.1.tgz#f0cbbe7edda7c4109cd253bb1dee99aba4594ad9" - integrity sha512-j2vJGnkopRzH+ykJ8h68wrHnEUmtK//E723jjixiAl/PPf6FhqY/vYRcMVlNydRKQjQsTsYEjpx+DZMIvnGk/g== + version "7.23.2" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.23.2.tgz#a5cd9d8b408fb946b2f074b21ea40c04e516795c" + integrity sha512-54cIh74Z1rp4oIjsHjqN+WM4fMyCBYe+LpZ9jWm51CZ1fbH3SkAzQD/3XLoNkjbJ7YEmjobLXyvQrFypRHOrXw== dependencies: - core-js-pure "^3.25.1" - regenerator-runtime "^0.13.4" + core-js-pure "^3.30.2" + regenerator-runtime "^0.14.0" "@babel/runtime@^7.12.5": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.0.tgz#22b11c037b094d27a8a2504ea4dcff00f50e2259" - integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA== + version "7.23.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.2.tgz#062b0ac103261d68a966c4c7baf2ae3e62ec3885" + integrity sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg== dependencies: - regenerator-runtime "^0.13.4" + regenerator-runtime "^0.14.0" "@babel/template@^7.18.6": version "7.18.6" @@ -407,11 +407,6 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.50.0": - version "8.50.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.50.0.tgz#9e93b850f0f3fa35f5fa59adfd03adae8488e484" - integrity sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ== - "@eslint/js@8.51.0": version "8.51.0" resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.51.0.tgz#6d419c240cfb2b66da37df230f7e7eef801c32fa" @@ -1557,15 +1552,15 @@ convert-source-map@^1.7.0: dependencies: safe-buffer "~5.1.1" -core-js-pure@^3.25.1: - version "3.25.5" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.25.5.tgz#79716ba54240c6aa9ceba6eee08cf79471ba184d" - integrity sha512-oml3M22pHM+igfWHDfdLVq2ShWmjM2V4L+dQEBs0DWVIqEm9WHCwGAlZ6BmyBQGy5sFrJmcx+856D9lVKyGWYg== +core-js-pure@^3.30.2: + version "3.33.0" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.33.0.tgz#938a28754b4d82017a7a8cbd2727b1abecc63591" + integrity sha512-FKSIDtJnds/YFIEaZ4HszRX7hkxGpNKM7FC9aJ9WLJbSd3lD4vOltFuVIBLR8asSx9frkTSqL0dw90SKQxgKrg== core-js@^3.6.4: - version "3.25.5" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.25.5.tgz#e86f651a2ca8a0237a5f064c2fe56cef89646e27" - integrity sha512-nbm6eZSjm+ZuBQxCUPQKQCoUEfFOXjUZ8dTTyikyKaWrTYmAVbykQfwsKE5dBK88u3QCkCrzsx/PPlKfhsvgpw== + version "3.33.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.33.0.tgz#70366dbf737134761edb017990cf5ce6c6369c40" + integrity sha512-HoZr92+ZjFEKar5HS6MC776gYslNOKHt75mEBKWKnPeFDpZ6nH5OeF3S6HFT1mUAUZKrzkez05VboaX8myjSuw== core-util-is@~1.0.0: version "1.0.3" @@ -1991,50 +1986,7 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4 resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint@^8.41.0: - version "8.50.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.50.0.tgz#2ae6015fee0240fcd3f83e1e25df0287f487d6b2" - integrity sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@eslint-community/regexpp" "^4.6.1" - "@eslint/eslintrc" "^2.1.2" - "@eslint/js" "8.50.0" - "@humanwhocodes/config-array" "^0.11.11" - "@humanwhocodes/module-importer" "^1.0.1" - "@nodelib/fs.walk" "^1.2.8" - ajv "^6.12.4" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.3.2" - doctrine "^3.0.0" - escape-string-regexp "^4.0.0" - eslint-scope "^7.2.2" - eslint-visitor-keys "^3.4.3" - espree "^9.6.1" - esquery "^1.4.2" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" - find-up "^5.0.0" - glob-parent "^6.0.2" - globals "^13.19.0" - graphemer "^1.4.0" - ignore "^5.2.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - is-path-inside "^3.0.3" - js-yaml "^4.1.0" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash.merge "^4.6.2" - minimatch "^3.1.2" - natural-compare "^1.4.0" - optionator "^0.9.3" - strip-ansi "^6.0.1" - text-table "^0.2.0" - -eslint@^8.51.0: +eslint@^8.41.0, eslint@^8.51.0: version "8.51.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.51.0.tgz#4a82dae60d209ac89a5cff1604fea978ba4950f3" integrity sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA== @@ -3637,9 +3589,9 @@ no-case@^3.0.4: tslib "^2.0.3" node-fetch@^2.6.1: - version "2.6.9" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.9.tgz#7c7f744b5cc6eb5fd404e0c7a9fec630a55657e6" - integrity sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg== + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== dependencies: whatwg-url "^5.0.0" @@ -4198,10 +4150,15 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" -regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4: - version "0.13.9" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" - integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== +regenerator-runtime@^0.13.3: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + +regenerator-runtime@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" + integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== regexp.prototype.flags@^1.5.0: version "1.5.0" @@ -4354,9 +4311,9 @@ samsam@1.3.0: integrity sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg== sax@>=0.6.0: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + version "1.3.0" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.3.0.tgz#a5dbe77db3be05c9d1ee7785dbd3ea9de51593d0" + integrity sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA== secure-json-parse@^2.4.0: version "2.7.0" From 70633b9f06bb551bc85384be164d2f685618e68d Mon Sep 17 00:00:00 2001 From: svc-cli-bot Date: Fri, 13 Oct 2023 16:01:56 +0000 Subject: [PATCH 20/67] chore(release): 6.0.1-crdt.0 [skip ci] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b8343899a3..509daa2c18 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@salesforce/core", - "version": "6.0.0", + "version": "6.0.1-crdt.0", "description": "Core libraries to interact with SFDX projects, orgs, and APIs.", "main": "lib/exported", "types": "lib/exported.d.ts", From e4407a771a04bdd0b4af4f5dbcc6354830b4c6a7 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Fri, 13 Oct 2023 13:26:58 -0500 Subject: [PATCH 21/67] chore: use v5 for consistent resolutions --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b8343899a3..34b4e8c2bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@salesforce/core", - "version": "6.0.0", + "version": "5.4.0-crdt.0", "description": "Core libraries to interact with SFDX projects, orgs, and APIs.", "main": "lib/exported", "types": "lib/exported.d.ts", From a45bbf553468a77b7d6af7dfbe1bc2b22a1f2a30 Mon Sep 17 00:00:00 2001 From: svc-cli-bot Date: Fri, 13 Oct 2023 18:29:29 +0000 Subject: [PATCH 22/67] chore(release): 5.4.0-crdt.1 [skip ci] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 34b4e8c2bb..98b958fb63 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@salesforce/core", - "version": "5.4.0-crdt.0", + "version": "5.4.0-crdt.1", "description": "Core libraries to interact with SFDX projects, orgs, and APIs.", "main": "lib/exported", "types": "lib/exported.d.ts", From 6904cf2f50254c0385fd499a6a7773846ab232d2 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Fri, 13 Oct 2023 15:28:15 -0500 Subject: [PATCH 23/67] refactor: further reduce contents replacement --- src/config/configStore.ts | 2 +- src/stateAggregator/accessors/orgAccessor.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/config/configStore.ts b/src/config/configStore.ts index 0a4c6084da..51ac8f5990 100644 --- a/src/config/configStore.ts +++ b/src/config/configStore.ts @@ -222,7 +222,7 @@ export abstract class BaseConfigStore< * @param decrypt: decrypt all data in the config. A clone of the data will be returned. * */ - public getContents(decrypt = false): P { + public getContents(decrypt = false): Readonly

{ if (this.hasEncryption() && decrypt) { return this.recursiveDecrypt(cloneJson(this.contents?.value ?? {})) as P; } diff --git a/src/stateAggregator/accessors/orgAccessor.ts b/src/stateAggregator/accessors/orgAccessor.ts index 38dc9d08f0..44b7428a40 100644 --- a/src/stateAggregator/accessors/orgAccessor.ts +++ b/src/stateAggregator/accessors/orgAccessor.ts @@ -7,7 +7,7 @@ import * as fs from 'fs'; import * as path from 'path'; -import { Nullable } from '@salesforce/ts-types'; +import { Nullable, entriesOf } from '@salesforce/ts-types'; import { AsyncOptionalCreatable, isEmpty } from '@salesforce/kit'; import { AuthInfoConfig } from '../../config/authInfoConfig'; import { Global } from '../../global'; @@ -168,10 +168,10 @@ export abstract class BaseOrgAccessor config.set(key, value)); + if (!config.has('username')) { + config.set('username', username); + } } else { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore From f6f677f697c1f2005a3fe02aed71d6ae777b0c84 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Mon, 16 Oct 2023 09:40:49 -0500 Subject: [PATCH 24/67] chore: downgrade jsforce --- package.json | 2 +- yarn.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 34b4e8c2bb..3a2dbc42c3 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "faye": "^1.4.0", "form-data": "^4.0.0", "js2xmlparser": "^4.0.1", - "jsforce": "^2.0.0-beta.28", + "jsforce": "^2.0.0-beta.27", "jsonwebtoken": "9.0.2", "jszip": "3.10.1", "pino": "^8.15.6", diff --git a/yarn.lock b/yarn.lock index 5dca2284bb..007a740704 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3032,7 +3032,7 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== -jsforce@^2.0.0-beta.28: +jsforce@^2.0.0-beta.27: version "2.0.0-beta.28" resolved "https://registry.yarnpkg.com/jsforce/-/jsforce-2.0.0-beta.28.tgz#5fd8d9b8e5efc798698793b147e00371f3d74e8f" integrity sha512-tTmKRhr4yWNinhmurY/tiiltLFQq9RQ+gpYAt3wjFdCGjzd49/wqYQIFw4SsI3+iLjxXnc0uTgGwdAkDjxDWnA== From 5a85d53c1b1627971948e8f0a42d5ef167abc138 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Mon, 16 Oct 2023 10:03:37 -0500 Subject: [PATCH 25/67] ci: don't update jsforce to latest --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c9afbf11b7..5baf0c244d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,7 +39,7 @@ jobs: command: 'yarn test:nuts' os: ${{ matrix.os }} useCache: false - preSwapCommands: 'yarn upgrade jsforce@beta; npx yarn-deduplicate; yarn install' + preSwapCommands: 'npx yarn-deduplicate; yarn install' preExternalBuildCommands: 'shx rm -rf node_modules/@salesforce/sf-plugins-core/node_modules/@salesforce/core' secrets: inherit # hidden until we fix source-testkit to better handle jwt From f3abda113935c3619b5cb6ab051b34cd1e669106 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Mon, 16 Oct 2023 10:12:55 -0500 Subject: [PATCH 26/67] ci: pdr tracking xnuts --- .github/workflows/test.yml | 40 ++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5baf0c244d..d30d3eec8d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -40,26 +40,24 @@ jobs: os: ${{ matrix.os }} useCache: false preSwapCommands: 'npx yarn-deduplicate; yarn install' - preExternalBuildCommands: 'shx rm -rf node_modules/@salesforce/sf-plugins-core/node_modules/@salesforce/core' + preExternalBuildCommands: 'shx rm -rf node_modules/@salesforce/sf-plugins-core/node_modules/@salesforce/core node_modules/@salesforce/cli-plugins-testkit/node_modules/@salesforce/core' secrets: inherit # hidden until we fix source-testkit to better handle jwt - # deployRetrieveNuts: - # needs: unit-tests - # uses: salesforcecli/github-workflows/.github/workflows/externalNut.yml@main - # strategy: - # fail-fast: false - # matrix: - # os: ['windows-latest-16x', 'ubuntu-latest-16x'] - # command: - # - yarn test:nuts:deploy:metadata:manifest - # - yarn test:nuts:deploy:metadata:metadata - # - yarn test:nuts:deploy:metadata:source-dir - # - yarn test:nuts:deploy:metadata:test-level - # - yarn test:nuts:static - # with: - # packageName: '@salesforce/core' - # externalProjectGitUrl: 'https://github.com/salesforcecli/plugin-deploy-retrieve' - # preSwapCommands: 'yarn upgrade jsforce@beta; npx yarn-deduplicate; yarn install' - # command: ${{ matrix.command }} - # os: ${{ matrix.os }} - # secrets: inherit + deployRetrieveNuts: + needs: linux-unit-tests + uses: salesforcecli/github-workflows/.github/workflows/externalNut.yml@main + strategy: + fail-fast: false + matrix: + os: ['ubuntu-latest'] + command: + - 'yarn test:nuts:tracking' + with: + packageName: '@salesforce/core' + externalProjectGitUrl: 'https://github.com/salesforcecli/plugin-deploy-retrieve' + preSwapCommands: 'npx yarn-deduplicate; yarn install' + command: ${{ matrix.command }} + os: ${{ matrix.os }} + preExternalBuildCommands: 'shx rm -rf node_modules/@salesforce/sf-plugins-core/node_modules/@salesforce/core node_modules/@salesforce/source-deploy-retrieve/node_modules/@salesforce/core node_modules/@salesforce/source-tracking/node_modules/@salesforce/core node_modules/@salesforce/cli-plugins-testkit/node_modules/@salesforce/core node_modules/@salesforce/source-testkit/node_modules/@salesforce/core' + secrets: + TESTKIT_AUTH_URL: ${{ secrets.TESTKIT_AUTH_URL }} From 79540e6f50c39f2b618a128b14b170225548731d Mon Sep 17 00:00:00 2001 From: mshanemc Date: Mon, 16 Oct 2023 10:18:48 -0500 Subject: [PATCH 27/67] test: pin jsforce to 27 --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d30d3eec8d..5558797b65 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,7 +39,7 @@ jobs: command: 'yarn test:nuts' os: ${{ matrix.os }} useCache: false - preSwapCommands: 'npx yarn-deduplicate; yarn install' + preSwapCommands: 'yarn upgrade jsforce@2.0.0-beta.27; npx yarn-deduplicate; yarn install' preExternalBuildCommands: 'shx rm -rf node_modules/@salesforce/sf-plugins-core/node_modules/@salesforce/core node_modules/@salesforce/cli-plugins-testkit/node_modules/@salesforce/core' secrets: inherit # hidden until we fix source-testkit to better handle jwt @@ -55,7 +55,7 @@ jobs: with: packageName: '@salesforce/core' externalProjectGitUrl: 'https://github.com/salesforcecli/plugin-deploy-retrieve' - preSwapCommands: 'npx yarn-deduplicate; yarn install' + preSwapCommands: 'yarn upgrade jsforce@2.0.0-beta.27; npx yarn-deduplicate; yarn install' command: ${{ matrix.command }} os: ${{ matrix.os }} preExternalBuildCommands: 'shx rm -rf node_modules/@salesforce/sf-plugins-core/node_modules/@salesforce/core node_modules/@salesforce/source-deploy-retrieve/node_modules/@salesforce/core node_modules/@salesforce/source-tracking/node_modules/@salesforce/core node_modules/@salesforce/cli-plugins-testkit/node_modules/@salesforce/core node_modules/@salesforce/source-testkit/node_modules/@salesforce/core' From 2cb98df83041d4870d3e4ade9f7a5c33dca92ded Mon Sep 17 00:00:00 2001 From: mshanemc Date: Tue, 17 Oct 2023 14:29:47 -0500 Subject: [PATCH 28/67] test: not data, yes pdr --- .github/workflows/test.yml | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5558797b65..a26466e14d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: externalProjectGitUrl: - https://github.com/salesforcecli/plugin-auth - https://github.com/salesforcecli/plugin-custom-metadata - - https://github.com/salesforcecli/plugin-data + # - https://github.com/salesforcecli/plugin-data - https://github.com/salesforcecli/plugin-env - https://github.com/salesforcecli/plugin-limits - https://github.com/salesforcecli/plugin-org @@ -49,8 +49,21 @@ jobs: strategy: fail-fast: false matrix: - os: ['ubuntu-latest'] + os: ['ubuntu-latest', 'windows-latest'] command: + - 'yarn test:nuts:convert' + - 'yarn test:nuts:deb' + - 'yarn test:nuts:delete' + - 'yarn test:nuts:deploy:metadata:manifest' + - 'yarn test:nuts:deploy:metadata:metadata' + - 'yarn test:nuts:deploy:metadata:metadata-dir' + - 'yarn test:nuts:deploy:metadata:source-dir' + - 'yarn test:nuts:deploy:metadata:test-level' + - 'yarn test:nuts:destructive' + - 'yarn test:nuts:manifest' + - 'yarn test:nuts:retrieve' + - 'yarn test:nuts:specialTypes' + - 'yarn test:nuts:static' - 'yarn test:nuts:tracking' with: packageName: '@salesforce/core' From c03ec4de0b4509bfa10b46b504f22a1cdacb6ed6 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 18 Oct 2023 09:39:03 -0500 Subject: [PATCH 29/67] ci: try forcing to jsforce top-level --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a26466e14d..120469c32b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -40,7 +40,7 @@ jobs: os: ${{ matrix.os }} useCache: false preSwapCommands: 'yarn upgrade jsforce@2.0.0-beta.27; npx yarn-deduplicate; yarn install' - preExternalBuildCommands: 'shx rm -rf node_modules/@salesforce/sf-plugins-core/node_modules/@salesforce/core node_modules/@salesforce/cli-plugins-testkit/node_modules/@salesforce/core' + preExternalBuildCommands: 'shx rm -rf node_modules/@salesforce/core/node_modules/jsforce shx rm -rf node_modules/@salesforce/sf-plugins-core/node_modules/@salesforce/core node_modules/@salesforce/cli-plugins-testkit/node_modules/@salesforce/core' secrets: inherit # hidden until we fix source-testkit to better handle jwt deployRetrieveNuts: @@ -71,6 +71,6 @@ jobs: preSwapCommands: 'yarn upgrade jsforce@2.0.0-beta.27; npx yarn-deduplicate; yarn install' command: ${{ matrix.command }} os: ${{ matrix.os }} - preExternalBuildCommands: 'shx rm -rf node_modules/@salesforce/sf-plugins-core/node_modules/@salesforce/core node_modules/@salesforce/source-deploy-retrieve/node_modules/@salesforce/core node_modules/@salesforce/source-tracking/node_modules/@salesforce/core node_modules/@salesforce/cli-plugins-testkit/node_modules/@salesforce/core node_modules/@salesforce/source-testkit/node_modules/@salesforce/core' + preExternalBuildCommands: 'shx rm -rf node_modules/@salesforce/core/node_modules/jsforce shx rm -rf node_modules/@salesforce/sf-plugins-core/node_modules/@salesforce/core node_modules/@salesforce/cli-plugins-testkit/node_modules/@salesforce/core' secrets: TESTKIT_AUTH_URL: ${{ secrets.TESTKIT_AUTH_URL }} From 1f857743d4e5f576b2f3aab5d118dff1b54cf438 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 18 Oct 2023 10:18:35 -0500 Subject: [PATCH 30/67] test: restore plugin-data tests --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 120469c32b..45b10f340b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: externalProjectGitUrl: - https://github.com/salesforcecli/plugin-auth - https://github.com/salesforcecli/plugin-custom-metadata - # - https://github.com/salesforcecli/plugin-data + - https://github.com/salesforcecli/plugin-data - https://github.com/salesforcecli/plugin-env - https://github.com/salesforcecli/plugin-limits - https://github.com/salesforcecli/plugin-org From 60bfafda89c11e2781692514098cdc9306d94ed3 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 18 Oct 2023 10:20:45 -0500 Subject: [PATCH 31/67] test: remove core from more libraries to force this version --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 45b10f340b..3192c82084 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -40,7 +40,7 @@ jobs: os: ${{ matrix.os }} useCache: false preSwapCommands: 'yarn upgrade jsforce@2.0.0-beta.27; npx yarn-deduplicate; yarn install' - preExternalBuildCommands: 'shx rm -rf node_modules/@salesforce/core/node_modules/jsforce shx rm -rf node_modules/@salesforce/sf-plugins-core/node_modules/@salesforce/core node_modules/@salesforce/cli-plugins-testkit/node_modules/@salesforce/core' + preExternalBuildCommands: 'shx rm -rf node_modules/@salesforce/core/node_modules/jsforce shx rm -rf node_modules/@salesforce/sf-plugins-core/node_modules/@salesforce/core node_modules/@salesforce/cli-plugins-testkit/node_modules/@salesforce/core node_modules/@salesforce/source-tracking/node_modules/@salesforce/core node_modules/@salesforce/source-deploy-retrieve/node_modules/@salesforce/core' secrets: inherit # hidden until we fix source-testkit to better handle jwt deployRetrieveNuts: @@ -71,6 +71,6 @@ jobs: preSwapCommands: 'yarn upgrade jsforce@2.0.0-beta.27; npx yarn-deduplicate; yarn install' command: ${{ matrix.command }} os: ${{ matrix.os }} - preExternalBuildCommands: 'shx rm -rf node_modules/@salesforce/core/node_modules/jsforce shx rm -rf node_modules/@salesforce/sf-plugins-core/node_modules/@salesforce/core node_modules/@salesforce/cli-plugins-testkit/node_modules/@salesforce/core' + preExternalBuildCommands: 'shx rm -rf node_modules/@salesforce/core/node_modules/jsforce shx rm -rf node_modules/@salesforce/sf-plugins-core/node_modules/@salesforce/core node_modules/@salesforce/cli-plugins-testkit/node_modules/@salesforce/core node_modules/@salesforce/source-tracking/node_modules/@salesforce/core node_modules/@salesforce/source-deploy-retrieve/node_modules/@salesforce/core' secrets: TESTKIT_AUTH_URL: ${{ secrets.TESTKIT_AUTH_URL }} From 1f0bc062eec637f545334678c20a0a3d606d415a Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 18 Oct 2023 11:33:05 -0500 Subject: [PATCH 32/67] chore: comment about jsforce nut versioning --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3192c82084..183343b302 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,6 +39,7 @@ jobs: command: 'yarn test:nuts' os: ${{ matrix.os }} useCache: false + # we shouldn't be hardcoding the jsforce version here, but 28 is broken. preSwapCommands: 'yarn upgrade jsforce@2.0.0-beta.27; npx yarn-deduplicate; yarn install' preExternalBuildCommands: 'shx rm -rf node_modules/@salesforce/core/node_modules/jsforce shx rm -rf node_modules/@salesforce/sf-plugins-core/node_modules/@salesforce/core node_modules/@salesforce/cli-plugins-testkit/node_modules/@salesforce/core node_modules/@salesforce/source-tracking/node_modules/@salesforce/core node_modules/@salesforce/source-deploy-retrieve/node_modules/@salesforce/core' secrets: inherit @@ -68,6 +69,7 @@ jobs: with: packageName: '@salesforce/core' externalProjectGitUrl: 'https://github.com/salesforcecli/plugin-deploy-retrieve' + # we shouldn't be hardcoding the jsforce version here, but 28 is broken. preSwapCommands: 'yarn upgrade jsforce@2.0.0-beta.27; npx yarn-deduplicate; yarn install' command: ${{ matrix.command }} os: ${{ matrix.os }} From cd809317a14ea2ad6ca92b99bb0ade1f1b42c9a2 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 18 Oct 2023 16:30:17 -0500 Subject: [PATCH 33/67] test: run all ttlConfig tests --- test/unit/config/ttlConfigTest.ts | 34 +++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/test/unit/config/ttlConfigTest.ts b/test/unit/config/ttlConfigTest.ts index 5cbb5fceb7..59bf3823e9 100644 --- a/test/unit/config/ttlConfigTest.ts +++ b/test/unit/config/ttlConfigTest.ts @@ -97,25 +97,25 @@ describe('TTLConfig', () => { }); }); - describe.only('filters expired keys on init', () => { - $$.setConfigStubContents('TestConfig', { - contents: { - old: { - value: 1, - timestamp: new Date().getTime() - Duration.days(7).milliseconds, - }, - current: { - value: 2, - timestamp: new Date().getTime(), - }, - future: { - value: 3, - timestamp: new Date().getTime() + Duration.days(7).milliseconds, + describe('filters expired keys on init', () => { + it('should omit expired keys', async () => { + $$.setConfigStubContents('TestConfig', { + contents: { + old: { + value: 1, + timestamp: new Date().getTime() - Duration.days(7).milliseconds, + }, + current: { + value: 2, + timestamp: new Date().getTime(), + }, + future: { + value: 3, + timestamp: new Date().getTime() + Duration.days(7).milliseconds, + }, }, - }, - }); + }); - it('should omit expired keys', async () => { const config = await TestConfig.create(); const keys = config.keys(); expect(keys).to.include('current'); From 1b8145df23a80490a3c0c52732db7117d0ff6b7c Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 18 Oct 2023 16:33:16 -0500 Subject: [PATCH 34/67] feat!: remove everything about tokenConfig --- src/config/tokensConfig.ts | 31 ------ src/config/ttlConfig.ts | 1 + src/exported.ts | 2 +- src/org/authRemover.ts | 12 --- .../accessors/aliasAccessor.ts | 5 +- .../accessors/tokenAccessor.ts | 98 ------------------- src/stateAggregator/index.ts | 1 - src/stateAggregator/stateAggregator.ts | 3 - src/testSetup.ts | 8 -- .../accessors/aliasAccessorTest.ts | 19 ---- .../accessors/tokenAccessorTest.ts | 94 ------------------ 11 files changed, 4 insertions(+), 270 deletions(-) delete mode 100644 src/config/tokensConfig.ts delete mode 100644 src/stateAggregator/accessors/tokenAccessor.ts delete mode 100644 test/unit/stateAggregator/accessors/tokenAccessorTest.ts diff --git a/src/config/tokensConfig.ts b/src/config/tokensConfig.ts deleted file mode 100644 index a284572e0d..0000000000 --- a/src/config/tokensConfig.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2022, salesforce.com, inc. - * All rights reserved. - * Licensed under the BSD 3-Clause license. - * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ -/* eslint-disable class-methods-use-this */ - -import { Optional } from '@salesforce/ts-types'; -import { SfTokens } from '../stateAggregator'; -import { ConfigFile } from './configFile'; -import { ConfigContents, ConfigValue } from './configStackTypes'; - -export class TokensConfig extends ConfigFile { - protected static encryptedKeys = [/token/i, /password/i, /secret/i]; - public static getDefaultOptions(): ConfigFile.Options { - return { - isGlobal: true, - isState: true, - filename: 'tokens.json', - }; - } - - protected getMethod(contents: ConfigContents, key: string): Optional { - return contents[key]; - } - - protected setMethod(contents: ConfigContents, key: string, value?: ConfigValue): void { - contents[key] = value; - } -} diff --git a/src/config/ttlConfig.ts b/src/config/ttlConfig.ts index 4b822084cd..ebe1e31e0c 100644 --- a/src/config/ttlConfig.ts +++ b/src/config/ttlConfig.ts @@ -46,6 +46,7 @@ export class TTLConfig extends C protected async init(): Promise { const contents = await this.read(this.options.throwOnNotFound); const date = new Date().getTime(); + // delete all the expired entries Object.entries(contents) .filter(([, value]) => this.isExpired(date, value)) diff --git a/src/exported.ts b/src/exported.ts index d774751f1c..6f5e0f8879 100644 --- a/src/exported.ts +++ b/src/exported.ts @@ -18,7 +18,7 @@ export { envVars, EnvironmentVariable, SUPPORTED_ENV_VARS, EnvVars } from './con export { ConfigStore } from './config/configStore'; export { ConfigEntry, ConfigContents, ConfigValue } from './config/configStackTypes'; -export { SfTokens, StateAggregator } from './stateAggregator'; +export { StateAggregator } from './stateAggregator'; export { DeviceOauthService, DeviceCodeResponse, DeviceCodePollingResponse } from './deviceOauthService'; diff --git a/src/org/authRemover.ts b/src/org/authRemover.ts index fef9a8471e..e966fcdc3a 100644 --- a/src/org/authRemover.ts +++ b/src/org/authRemover.ts @@ -55,7 +55,6 @@ export class AuthRemover extends AsyncOptionalCreatable { this.logger.debug(`Removing authorization for user ${username}`); await this.unsetConfigValues(username); await this.unsetAliases(username); - await this.unsetTokens(username); await this.stateAggregator.orgs.remove(username); } @@ -186,15 +185,4 @@ export class AuthRemover extends AsyncOptionalCreatable { existingAliases.forEach((alias) => this.stateAggregator.aliases.unset(alias)); await this.stateAggregator.aliases.write(); } - - private async unsetTokens(username: string): Promise { - this.logger.debug(`Clearing tokens for username: ${username}`); - const tokens = this.stateAggregator.tokens.getAll(); - for (const [key, token] of Object.entries(tokens)) { - if (token.user === username) { - this.stateAggregator.tokens.unset(key); - } - } - await this.stateAggregator.tokens.write(); - } } diff --git a/src/stateAggregator/accessors/aliasAccessor.ts b/src/stateAggregator/accessors/aliasAccessor.ts index ed2bbf3b2b..88ae3bb70b 100644 --- a/src/stateAggregator/accessors/aliasAccessor.ts +++ b/src/stateAggregator/accessors/aliasAccessor.ts @@ -19,9 +19,8 @@ import { AuthFields } from '../../org/authInfo'; import { ConfigContents } from '../../config/configStackTypes'; import { SfError } from '../../sfError'; import { lockRetryOptions, lockOptions } from '../../util/lockRetryOptions'; -import { SfToken } from './tokenAccessor'; -export type Aliasable = string | (Partial & Partial); +export type Aliasable = string | Partial; export const DEFAULT_GROUP = 'orgs'; export const FILENAME = 'alias.json'; @@ -264,7 +263,7 @@ export class AliasAccessor extends AsyncOptionalCreatable { */ const getNameOf = (entity: Aliasable): string => { if (typeof entity === 'string') return entity; - const aliaseeName = entity.username ?? entity.user; + const aliaseeName = entity.username; if (!aliaseeName) { throw new SfError(`Invalid aliasee, it must contain a user or username property: ${JSON.stringify(entity)}`); } diff --git a/src/stateAggregator/accessors/tokenAccessor.ts b/src/stateAggregator/accessors/tokenAccessor.ts deleted file mode 100644 index 072216c300..0000000000 --- a/src/stateAggregator/accessors/tokenAccessor.ts +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2021, salesforce.com, inc. - * All rights reserved. - * Licensed under the BSD 3-Clause license. - * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ - -import { AsyncOptionalCreatable } from '@salesforce/kit'; -import { JsonMap, Optional } from '@salesforce/ts-types'; -import { TokensConfig } from '../../config/tokensConfig'; - -export type SfToken = { - token: string; - url: string; - user?: string; - timestamp: string; -} & JsonMap; - -export type SfTokens = { - [key: string]: SfToken; -}; - -export class TokenAccessor extends AsyncOptionalCreatable { - private config!: TokensConfig; - - /** - * Return all tokens. - * - * @param decrypt - * @returns {SfTokens} - */ - public getAll(decrypt = false): SfTokens { - return this.config.getContents(decrypt) || {}; - } - - /** - * Return a token for the provided name. - * - * @param name - * @param decrypt - * @returns {Optional} - */ - public get(name: string, decrypt = false): Optional { - return this.config.get(name, decrypt); - } - - /** - * Return true if a given name has a token associated with it. - * - * @param name - * @returns {boolean} - */ - public has(name: string): boolean { - return !!this.getAll()[name]; - } - - /** - * Set the token for the provided name. - * - * @param name - * @param token - */ - public set(name: string, token: Partial): void { - this.config.set(name, token); - } - - /** - * Update the token for the provided name. - * - * @param name - * @param token - */ - public update(name: string, token: Partial): void { - this.config.update(name, token); - } - - /** - * Unset the token for the provided name. - * - * @param name - */ - public unset(name: string): void { - this.config.unset(name); - } - - /** - * Write the contents to the token file. - * - * @returns {Promise} - */ - public async write(): Promise { - return this.config.write(); - } - - protected async init(): Promise { - this.config = await TokensConfig.create(); - } -} diff --git a/src/stateAggregator/index.ts b/src/stateAggregator/index.ts index 381b749a7b..c064c03d09 100644 --- a/src/stateAggregator/index.ts +++ b/src/stateAggregator/index.ts @@ -7,5 +7,4 @@ export * from './accessors/orgAccessor'; export * from './accessors/aliasAccessor'; -export * from './accessors/tokenAccessor'; export * from './stateAggregator'; diff --git a/src/stateAggregator/stateAggregator.ts b/src/stateAggregator/stateAggregator.ts index 395bb08a57..3259b1f91b 100644 --- a/src/stateAggregator/stateAggregator.ts +++ b/src/stateAggregator/stateAggregator.ts @@ -10,13 +10,11 @@ import { Global } from '../global'; import { AliasAccessor } from './accessors/aliasAccessor'; import { OrgAccessor } from './accessors/orgAccessor'; import { SandboxAccessor } from './accessors/sandboxAccessor'; -import { TokenAccessor } from './accessors/tokenAccessor'; export class StateAggregator extends AsyncOptionalCreatable { private static instanceMap: Map = new Map(); public aliases!: AliasAccessor; public orgs!: OrgAccessor; public sandboxes!: SandboxAccessor; - public tokens!: TokenAccessor; /** * Reuse a StateAggregator if one was already created for the current global state directory @@ -44,6 +42,5 @@ export class StateAggregator extends AsyncOptionalCreatable { this.orgs = await OrgAccessor.create(); this.sandboxes = await SandboxAccessor.create(); this.aliases = await AliasAccessor.create(); - this.tokens = await TokenAccessor.create(); } } diff --git a/src/testSetup.ts b/src/testSetup.ts index 8cc16ba09e..2f95a0abe6 100644 --- a/src/testSetup.ts +++ b/src/testSetup.ts @@ -121,7 +121,6 @@ export class TestContext { AuthInfoConfig?: ConfigStub; Config?: ConfigStub; SfProjectJson?: ConfigStub; - TokensConfig?: ConfigStub; OrgUsersConfig?: ConfigStub; } = {}; /** @@ -388,13 +387,6 @@ export class TestContext { await ConfigAggregator.create(); } - /** - * Stub the tokens in the global token config file. - */ - public stubTokens(tokens: Record): void { - this.configStubs.TokensConfig = { contents: tokens }; - } - public restore(): void { restoreContext(this); } diff --git a/test/unit/stateAggregator/accessors/aliasAccessorTest.ts b/test/unit/stateAggregator/accessors/aliasAccessorTest.ts index a6dd348954..ef3754915a 100644 --- a/test/unit/stateAggregator/accessors/aliasAccessorTest.ts +++ b/test/unit/stateAggregator/accessors/aliasAccessorTest.ts @@ -20,7 +20,6 @@ const alias1 = 'MyAlias'; const alias2 = 'MyOtherAlias'; const alias3 = 'MyThirdAlias'; const org = new MockTestOrgData(uniqid(), { username: username1 }); -const token = { token: '123', url: 'https://login.salesforce.com', user: username1 }; describe('AliasAccessor', () => { const $$ = new TestContext(); @@ -32,10 +31,6 @@ describe('AliasAccessor', () => { [alias3]: username1, }); - $$.setConfigStubContents('TokensConfig', { - contents: { [username1]: token }, - }); - await $$.stubAuths(org); }); @@ -121,13 +116,6 @@ describe('AliasAccessor', () => { const aliases = stateAggregator.aliases.getAll(org.username); expect(aliases).to.include('foobar'); }); - - it('should set an alias for a token', async () => { - const stateAggregator = await StateAggregator.getInstance(); - stateAggregator.aliases.set('foobar', token); - const aliases = stateAggregator.aliases.getAll(token.user); - expect(aliases).to.include('foobar'); - }); }); describe('setAndSave', () => { @@ -150,13 +138,6 @@ describe('AliasAccessor', () => { const aliases = stateAggregator.aliases.getAll(org.username); expect(aliases).to.include('foobar'); }); - - it('should set an alias for a token', async () => { - const stateAggregator = await StateAggregator.getInstance(); - await stateAggregator.aliases.setAndSave('foobar', token); - const aliases = stateAggregator.aliases.getAll(token.user); - expect(aliases).to.include('foobar'); - }); }); describe('unsetAll', () => { diff --git a/test/unit/stateAggregator/accessors/tokenAccessorTest.ts b/test/unit/stateAggregator/accessors/tokenAccessorTest.ts deleted file mode 100644 index b399670c9a..0000000000 --- a/test/unit/stateAggregator/accessors/tokenAccessorTest.ts +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2020, salesforce.com, inc. - * All rights reserved. - * Licensed under the BSD 3-Clause license. - * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ -import { expect } from 'chai'; -import { StateAggregator } from '../../../../src/stateAggregator'; -import { MockTestOrgData, TestContext } from '../../../../src/testSetup'; -import { uniqid } from '../../../../src/util/uniqid'; - -const username = 'espresso@coffee.com'; -const alias = 'MyAlias'; -const org = new MockTestOrgData(uniqid(), { username }); -const token = { token: '123', url: 'https://login.salesforce.com', user: username }; - -describe('TokenAccessor', () => { - const $$ = new TestContext(); - - beforeEach(async () => { - $$.stubAliases({ [alias]: username }); - - $$.setConfigStubContents('TokensConfig', { - contents: { [username]: token }, - }); - - await $$.stubAuths(org); - }); - - describe('getAll', () => { - it('should return all the tokens', async () => { - const stateAggregator = await StateAggregator.getInstance(); - const tokens = stateAggregator.tokens.getAll(); - expect(tokens).to.deep.equal({ [username]: token }); - }); - }); - - describe('get', () => { - it('should return token that corresponds to a username', async () => { - const stateAggregator = await StateAggregator.getInstance(); - const result = stateAggregator.tokens.get(username); - expect(result).to.deep.equal(token); - }); - }); - - describe('has', () => { - it('should return true if token exists', async () => { - const stateAggregator = await StateAggregator.getInstance(); - const result = stateAggregator.tokens.has(username); - expect(result).to.deep.equal(true); - }); - - it('should return false if token does not exist', async () => { - const stateAggregator = await StateAggregator.getInstance(); - const result = stateAggregator.tokens.has('DOES_NOT_EXIST'); - expect(result).to.deep.equal(false); - }); - }); - - describe('set', () => { - it('should set the token', async () => { - const stateAggregator = await StateAggregator.getInstance(); - const newToken = { - username: 'foobar@baz.com', - token: '123', - url: 'https://login.salesforce.com', - timestamp: new Date().toISOString(), - }; - stateAggregator.tokens.set('foobar@baz.com', newToken); - const result = stateAggregator.tokens.has('foobar@baz.com'); - expect(result).to.be.true; - }); - }); - - describe('update', () => { - it('should update the token', async () => { - const stateAggregator = await StateAggregator.getInstance(); - const instanceUrl = 'https://login.salesforce.com'; - const newToken = { ...token, instanceUrl }; - stateAggregator.tokens.update(username, newToken); - const result = stateAggregator.tokens.get(username); - expect(result?.instanceUrl).to.deep.equal(instanceUrl); - }); - }); - - describe('unset', () => { - it('should remove the token', async () => { - const stateAggregator = await StateAggregator.getInstance(); - stateAggregator.tokens.unset(username); - const result = stateAggregator.tokens.get(username); - expect(result).to.be.undefined; - }); - }); -}); From b0790f383aa207e436368a237d3cca8e1a5a944a Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 18 Oct 2023 17:33:11 -0500 Subject: [PATCH 35/67] feat!: drop support for lodash-style deep get/set --- src/config/configStore.ts | 36 +++++----------- src/config/lwwMap.ts | 1 - test/unit/config/configStoreTest.ts | 66 +++-------------------------- 3 files changed, 15 insertions(+), 88 deletions(-) diff --git a/src/config/configStore.ts b/src/config/configStore.ts index 51ac8f5990..4de168bb0f 100644 --- a/src/config/configStore.ts +++ b/src/config/configStore.ts @@ -5,9 +5,9 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import { AsyncOptionalCreatable, cloneJson, set } from '@salesforce/kit'; +import { AsyncOptionalCreatable, cloneJson } from '@salesforce/kit'; import { entriesOf, isPlainObject } from '@salesforce/ts-types'; -import { definiteEntriesOf, definiteValuesOf, get, isJsonMap, isString, JsonMap, Optional } from '@salesforce/ts-types'; +import { definiteEntriesOf, definiteValuesOf, isJsonMap, isString, JsonMap, Optional } from '@salesforce/ts-types'; import { Crypto } from '../crypto/crypto'; import { SfError } from '../sfError'; import { LWWMap, stateFromContents } from './lwwMap'; @@ -85,24 +85,23 @@ export abstract class BaseConfigStore< /** * Returns the value associated to the key, or undefined if there is none. * - * @param key The key. Supports query key like `a.b[0]`. + * @param key The key (object property) * @param decrypt If it is an encrypted key, decrypt the value. * If the value is an object, a clone will be returned. */ public get>(key: K, decrypt?: boolean): P[K]; public get(key: string, decrypt?: boolean): V; public get>(key: K | string, decrypt = false): P[K] | ConfigValue { - const k = key as string; - let value = this.getMethod(this.contents.value ?? {}, k); + const rawValue = this.contents.get(key as K); if (this.hasEncryption() && decrypt) { - if (isJsonMap(value)) { - value = this.recursiveDecrypt(cloneJson(value), k); - } else if (this.isCryptoKey(k)) { - value = this.decrypt(value); + if (isJsonMap(rawValue)) { + return this.recursiveDecrypt(cloneJson(rawValue), key); + } else if (this.isCryptoKey(key)) { + return this.decrypt(rawValue) as P[K] | ConfigValue; } } - return value as P[K]; + return rawValue as P[K] | ConfigValue; } /** @@ -302,21 +301,6 @@ export abstract class BaseConfigStore< return this.getEncryptedKeys().length > 0; } - // Allows extended classes the ability to override the set method. i.e. maybe they want - // nested object set from kit. - // eslint-disable-next-line class-methods-use-this - protected setMethod(contents: ConfigContents, key: string, value?: ConfigValue): void { - set(contents, key, value); - } - - // Allows extended classes the ability to override the get method. i.e. maybe they want - // nested object get from ts-types. - // NOTE: Key must stay string to be reliably overwritten. - // eslint-disable-next-line class-methods-use-this - protected getMethod(contents: ConfigContents, key: string): Optional { - return get(contents, key) as ConfigValue; - } - // eslint-disable-next-line class-methods-use-this protected initialContents(): P { return {} as P; @@ -389,7 +373,7 @@ export abstract class BaseConfigStore< return this.crypto.isEncrypted(value) ? value : this.crypto.encrypt(value); } - protected decrypt(value: unknown): Optional { + protected decrypt(value: unknown): string | undefined { if (!value) return; if (!this.crypto) throw new SfError('crypto is not initialized', 'CryptoNotInitializedError'); if (!isString(value)) diff --git a/src/config/lwwMap.ts b/src/config/lwwMap.ts index f37c7bcbb1..2cf99cb096 100644 --- a/src/config/lwwMap.ts +++ b/src/config/lwwMap.ts @@ -86,7 +86,6 @@ export class LWWMap

{ else this.#data.set(key, new LWWRegister(this.id, { peer: this.id, timestamp: process.hrtime.bigint(), value })); } - // TODO: how to handle the deep `get` that is currently allowed ex: get('foo.bar.baz') public get>(key: K): P[K] | undefined { // map loses the typing const value = this.#data.get(key)?.value; diff --git a/test/unit/config/configStoreTest.ts b/test/unit/config/configStoreTest.ts index 4378064be7..96204f7b2c 100644 --- a/test/unit/config/configStoreTest.ts +++ b/test/unit/config/configStoreTest.ts @@ -68,7 +68,6 @@ describe('ConfigStore', () => { config.get('1').a = 'b'; expect(config.get('1').a).to.equal('b'); - expect(config.get('1.a')).to.equal('b'); }); it('updates the object reference', async () => { @@ -146,60 +145,6 @@ describe('ConfigStore', () => { expect(config.get('owner', true).superPassword).to.equal(expected); }); - describe.skip('TODO: set with deep (dots/accessors) keys', () => { - it('encrypts nested query key using dot notation', async () => { - const expected = 'a29djf0kq3dj90d3q'; - const config = await CarConfig.create(); - config.set('owner.creditCardNumber', expected); - // encrypted - expect(config.get('owner.creditCardNumber')).to.not.equal(expected); - // decrypted - expect(config.get('owner.creditCardNumber', true)).to.equal(expected); - }); - - it('encrypts nested query key using accessor with single quotes', async () => { - const expected = 'a29djf0kq3dj90d3q'; - const config = await CarConfig.create(); - config.set('owner["creditCardNumber"]', expected); - // encrypted - expect(config.get("owner['creditCardNumber']")).to.not.equal(expected); - // decrypted - expect(config.get("owner['creditCardNumber']", true)).to.equal(expected); - }); - - it('encrypts nested query key using accessor with double quotes', async () => { - const expected = 'a29djf0kq3dj90d3q'; - const config = await CarConfig.create(); - config.set('owner["creditCardNumber"]', expected); - // encrypted - expect(config.get('owner["creditCardNumber"]')).to.not.equal(expected); - // decrypted - expect(config.get('owner["creditCardNumber"]', true)).to.equal(expected); - }); - - it('encrypts nested query special key using accessor with single quotes', async () => { - const expected = 'a29djf0kq3dj90d3q'; - const config = await CarConfig.create(); - const query = `owner['${specialKey}']`; - config.set(query, expected); - // encrypted - expect(config.get(query)).to.not.equal(expected); - // decrypted - expect(config.get(query, true)).to.equal(expected); - }); - - it('encrypts nested query special key using accessor with double quotes', async () => { - const expected = 'a29djf0kq3dj90d3q'; - const config = await CarConfig.create(); - const query = `owner["${specialKey}"]`; - config.set(query, expected); - // encrypted - expect(config.get(query)).to.not.equal(expected); - // decrypted - expect(config.get(query, true)).to.equal(expected); - }); - }); - it('decrypt returns copies', async () => { const expected = 'a29djf0kq3dj90d3q'; const config = await CarConfig.create(); @@ -213,7 +158,6 @@ describe('ConfigStore', () => { decryptedOwner.creditCardNumber = 'invalid'; expect(config.get('owner').creditCardNumber).to.not.equal('invalid'); expect(config.get('owner', true).creditCardNumber).to.equal(expected); - expect(config.get('owner.creditCardNumber', true)).to.equal(expected); }); // Ensures accessToken and refreshToken are both decrypted upon config.get() @@ -236,12 +180,12 @@ describe('ConfigStore', () => { const config = await CarConfig.create(); const owner = { name: 'Bob', creditCardNumber: expected }; config.set('owner', owner); - const encryptedCreditCardNumber = config.get('owner.creditCardNumber'); + const encryptedCreditCardNumber = config.get('owner').creditCardNumber; const contents = config.getContents(); contents.owner.name = 'Tim'; config.setContents(contents); - expect(config.get('owner.name')).to.equal(contents.owner.name); - expect(config.get('owner.creditCardNumber')).to.equal(encryptedCreditCardNumber); + expect(config.get('owner').name).to.equal(contents.owner.name); + expect(config.get('owner').creditCardNumber).to.equal(encryptedCreditCardNumber); }); it('updates encrypted object', async () => { @@ -252,8 +196,8 @@ describe('ConfigStore', () => { config.update('owner', { creditCardNumber: expected }); - expect(config.get('owner.name')).to.equal(owner.name); - expect(config.get('owner.creditCardNumber', true)).to.equal(expected); + expect(config.get('owner').name).to.equal(owner.name); + expect(config.get('owner', true).creditCardNumber).to.equal(expected); }); }); }); From af58bc9f3cd2c2e391a3058acbf7a19abd1e1abe Mon Sep 17 00:00:00 2001 From: mshanemc Date: Thu, 19 Oct 2023 08:16:11 -0500 Subject: [PATCH 36/67] feat!: setContents becomes protected --- src/config/configStore.ts | 29 ++++++++++++++--------------- test/unit/config/configStoreTest.ts | 1 + 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/config/configStore.ts b/src/config/configStore.ts index 4de168bb0f..c33ff092d6 100644 --- a/src/config/configStore.ts +++ b/src/config/configStore.ts @@ -38,7 +38,6 @@ export interface ConfigStore

{ // Content methods getContents(): P; - setContents(contents?: P): void; } /** @@ -228,20 +227,6 @@ export abstract class BaseConfigStore< return this.contents?.value ?? ({} as P); } - /** - * Sets the entire config contents. - * - * @param contents The contents. - */ - public setContents(contents: P = {} as P): void { - if (this.hasEncryption()) { - contents = this.recursiveEncrypt(contents); - } - entriesOf(contents).map(([key, value]) => { - this.contents.set(key, value); - }); - } - /** * Invokes `actionFn` once for each key-value pair present in the config object. * @@ -288,6 +273,20 @@ export abstract class BaseConfigStore< this.contents = new LWWMap

(this.contents.id, state); } + /** + * Sets the entire config contents. + * + * @param contents The contents. + */ + protected setContents(contents: P = {} as P): void { + if (this.hasEncryption()) { + contents = this.recursiveEncrypt(contents); + } + entriesOf(contents).map(([key, value]) => { + this.contents.set(key, value); + }); + } + protected getEncryptedKeys(): Array { return [...(this.options?.encryptedKeys ?? []), ...(this.statics?.encryptedKeys ?? [])]; } diff --git a/test/unit/config/configStoreTest.ts b/test/unit/config/configStoreTest.ts index 96204f7b2c..12b6c510f7 100644 --- a/test/unit/config/configStoreTest.ts +++ b/test/unit/config/configStoreTest.ts @@ -183,6 +183,7 @@ describe('ConfigStore', () => { const encryptedCreditCardNumber = config.get('owner').creditCardNumber; const contents = config.getContents(); contents.owner.name = 'Tim'; + // @ts-expect-error private method config.setContents(contents); expect(config.get('owner').name).to.equal(contents.owner.name); expect(config.get('owner').creditCardNumber).to.equal(encryptedCreditCardNumber); From 7412d103703cfe2df2211546fcf2e6d93a689bc0 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Thu, 19 Oct 2023 10:13:39 -0500 Subject: [PATCH 37/67] fix: permissionSet type changes --- src/org/permissionSetAssignment.ts | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/org/permissionSetAssignment.ts b/src/org/permissionSetAssignment.ts index 883af9f2b3..e7ab900b1d 100644 --- a/src/org/permissionSetAssignment.ts +++ b/src/org/permissionSetAssignment.ts @@ -7,8 +7,8 @@ import { EOL } from 'os'; import { mapKeys, upperFirst } from '@salesforce/kit'; -import { hasArray, Optional } from '@salesforce/ts-types'; -import { QueryResult, Record } from 'jsforce'; +import type { Optional } from '@salesforce/ts-types'; +import type { QueryResult, Record } from 'jsforce'; import { Logger } from '../logger/logger'; import { Messages } from '../messages'; import { SfError } from '../sfError'; @@ -94,19 +94,18 @@ export class PermissionSetAssignment { .sobject('PermissionSetAssignment') .create(mapKeys(assignment, (value: unknown, key: string) => upperFirst(key))); - if (hasArray(createResponse, 'errors') && createResponse.errors.length > 0) { - let message = messages.getMessage('errorsEncounteredCreatingAssignment'); - - const errors = createResponse.errors; - if (errors && errors.length > 0) { - message = `${message}:${EOL}`; - errors.forEach((_message) => { - message = `${message}${_message as string}${EOL}`; - }); - throw new SfError(message, 'errorsEncounteredCreatingAssignment'); - } else { + if (createResponse.success === false) { + if (!createResponse.errors?.length) { throw messages.createError('notSuccessfulButNoErrorsReported'); } + const message = [messages.getMessage('errorsEncounteredCreatingAssignment')] + .concat( + (createResponse.errors ?? []).map((error) => + error.fields ? `${error.message} on fields ${error.fields.join(',')}` : error.message + ) + ) + .join(EOL); + throw new SfError(message, 'errorsEncounteredCreatingAssignment'); } else { return assignment; } From 68bab5d0cd80a0d4086f020a985d67f3b5ddf0f8 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Thu, 19 Oct 2023 10:36:54 -0500 Subject: [PATCH 38/67] refactor: no id/peer required --- src/config/configFile.ts | 6 +- src/config/configStore.ts | 5 +- src/config/lwwMap.ts | 28 +++---- src/config/lwwRegister.ts | 16 +--- src/org/permissionSetAssignment.ts | 10 ++- test/unit/config/configStoreTest.ts | 22 +++--- test/unit/config/lwwMapTest.ts | 83 ++++++++++++++------ test/unit/org/permissionSetAssignmentTest.ts | 6 +- 8 files changed, 101 insertions(+), 75 deletions(-) diff --git a/src/config/configFile.ts b/src/config/configFile.ts index c7655434e4..a6d10bdbe0 100644 --- a/src/config/configFile.ts +++ b/src/config/configFile.ts @@ -251,8 +251,7 @@ export class ConfigFile< // read the file contents into a LWWMap using the modstamp const stateFromFile = stateFromContents

( parseJsonMap

(await fs.promises.readFile(this.getPath(), 'utf8'), this.getPath()), - fileTimestamp, - this.getPath() + fileTimestamp ); // merge the new contents into the in-memory LWWMap this.contents.merge(stateFromFile); @@ -298,8 +297,7 @@ export class ConfigFile< // read the file contents into a LWWMap using the modstamp const stateFromFile = stateFromContents

( parseJsonMap

(fs.readFileSync(this.getPath(), 'utf8'), this.getPath()), - fileTimestamp, - this.getPath() + fileTimestamp ); // merge the new contents into the in-memory LWWMap this.contents.merge(stateFromFile); diff --git a/src/config/configStore.ts b/src/config/configStore.ts index c33ff092d6..fb13ed1675 100644 --- a/src/config/configStore.ts +++ b/src/config/configStore.ts @@ -180,6 +180,7 @@ export abstract class BaseConfigStore< */ public unset(key: string): boolean { if (this.has(key)) { + // @ts-expect-error TODO: why can't TS compile tighter types for keys of P this.contents.delete(key); return true; } @@ -269,8 +270,8 @@ export abstract class BaseConfigStore< } protected setContentsFromFileContents(contents: P, timestamp: bigint): void { - const state = stateFromContents(contents, timestamp, this.contents.id); - this.contents = new LWWMap

(this.contents.id, state); + const state = stateFromContents(contents, timestamp); + this.contents = new LWWMap

(state); } /** diff --git a/src/config/lwwMap.ts b/src/config/lwwMap.ts index 2cf99cb096..431452878f 100644 --- a/src/config/lwwMap.ts +++ b/src/config/lwwMap.ts @@ -6,7 +6,6 @@ */ import { entriesOf } from '@salesforce/ts-types'; -import { uniqid } from '../util/uniqid'; import { LWWRegister } from './lwwRegister'; import { ConfigContents, Key } from './configStackTypes'; @@ -24,27 +23,20 @@ export type LWWState

= { * */ export const stateFromContents =

( contents: P, - timestamp = process.hrtime.bigint(), - id?: string + timestamp = process.hrtime.bigint() ): LWWState

=> Object.fromEntries( - entriesOf(contents).map(([key, value]) => [ - key, - new LWWRegister(id ?? uniqid(), { peer: 'file', timestamp, value }), - ]) + entriesOf(contents).map(([key, value]) => [key, new LWWRegister({ timestamp, value })]) ) as unknown as LWWState

; export class LWWMap

{ - public readonly id: string; /** map of key to LWWRegister. Used for managing conflicts */ #data = new Map>(); - public constructor(id?: string, state?: LWWState

) { - this.id = id ?? uniqid(); - + public constructor(state?: LWWState

) { // create a new register for each key in the initial state for (const [key, register] of entriesOf(state ?? {})) { - this.#data.set(key, new LWWRegister(this.id, register)); + this.#data.set(key, new LWWRegister(register)); } } @@ -71,7 +63,7 @@ export class LWWMap

{ // if the register already exists, merge it with the incoming state if (local) local.merge(remote); // otherwise, instantiate a new `LWWRegister` with the incoming state - else this.#data.set(key, new LWWRegister(this.id, remote)); + else this.#data.set(key, new LWWRegister(remote)); } return this.state; } @@ -83,7 +75,7 @@ export class LWWMap

{ // if the register already exists, set the value if (register) register.set(value); // otherwise, instantiate a new `LWWRegister` with the value - else this.#data.set(key, new LWWRegister(this.id, { peer: this.id, timestamp: process.hrtime.bigint(), value })); + else this.#data.set(key, new LWWRegister({ timestamp: process.hrtime.bigint(), value })); } public get>(key: K): P[K] | undefined { @@ -93,9 +85,11 @@ export class LWWMap

{ return value as P[K]; } - public delete(key: string): void { - // set the register to null, if it exists - this.#data.get(key)?.set(SYMBOL_FOR_DELETED); + public delete>(key: K): void { + this.#data.set( + key, + new LWWRegister({ timestamp: process.hrtime.bigint(), value: SYMBOL_FOR_DELETED }) + ); } public has(key: string): boolean { diff --git a/src/config/lwwRegister.ts b/src/config/lwwRegister.ts index e47e78be81..0e164afbf0 100644 --- a/src/config/lwwRegister.ts +++ b/src/config/lwwRegister.ts @@ -5,7 +5,7 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -type LWWRegisterState = { peer: string; timestamp: bigint; value: T }; +type LWWRegisterState = { timestamp: bigint; value: T }; /** a CRDT implementation. Uses timestamps to resolve conflicts when updating the value (last write wins) * mostly based on https://jakelazaroff.com/words/an-interactive-intro-to-crdts/ @@ -13,31 +13,21 @@ type LWWRegisterState = { peer: string; timestamp: bigint; value: T }; * @param T the type of the value stored in the register */ export class LWWRegister { - public readonly id: string; - public state: LWWRegisterState; - - public constructor(id: string, state: LWWRegisterState) { - this.id = id; - this.state = state; - } + public constructor(public state: LWWRegisterState) {} public get value(): T { return this.state.value; } public set(value: T): void { - // set the peer ID to the local ID, timestamp it and set the value - this.state = { peer: this.id, timestamp: process.hrtime.bigint(), value }; + this.state = { timestamp: process.hrtime.bigint(), value }; } public merge(incoming: LWWRegisterState): LWWRegisterState { // only update if the incoming timestamp is greater than the local timestamp - // console.log(`incoming: ${}`); - // console.log(`local: ${JSON.stringify(this.state)}`); if (incoming.timestamp > this.state.timestamp) { this.state = incoming; } - // TODO: if the timestamps match, use the peer ID to break the tie (prefer self?) return this.state; } } diff --git a/src/org/permissionSetAssignment.ts b/src/org/permissionSetAssignment.ts index e7ab900b1d..bb3cbfeff4 100644 --- a/src/org/permissionSetAssignment.ts +++ b/src/org/permissionSetAssignment.ts @@ -100,9 +100,13 @@ export class PermissionSetAssignment { } const message = [messages.getMessage('errorsEncounteredCreatingAssignment')] .concat( - (createResponse.errors ?? []).map((error) => - error.fields ? `${error.message} on fields ${error.fields.join(',')}` : error.message - ) + (createResponse.errors ?? []).map((error) => { + // note: the types for jsforce SaveError don't have "string[]" error, + // but there was a UT for that at https://github.com/forcedotcom/sfdx-core/blob/7412d103703cfe2df2211546fcf2e6d93a689bc0/test/unit/org/permissionSetAssignmentTest.ts#L146 + // which could either be hallucination or a real response we've seen, so I'm preserving that behavior. + if (typeof error === 'string') return error; + return error.fields ? `${error.message} on fields ${error.fields.join(',')}` : error.message; + }) ) .join(EOL); throw new SfError(message, 'errorsEncounteredCreatingAssignment'); diff --git a/test/unit/config/configStoreTest.ts b/test/unit/config/configStoreTest.ts index 12b6c510f7..00d047f5f8 100644 --- a/test/unit/config/configStoreTest.ts +++ b/test/unit/config/configStoreTest.ts @@ -8,7 +8,6 @@ import { expect } from 'chai'; import { AuthInfoConfig } from '../../../src/config/authInfoConfig'; import { BaseConfigStore } from '../../../src/config/configStore'; import { ConfigContents } from '../../../src/config/configStackTypes'; -import { AuthFields } from '../../../src/org/authInfo'; import { TestContext } from '../../../src/testSetup'; const specialKey = 'spe@cial.property'; @@ -88,14 +87,15 @@ describe('ConfigStore', () => { it('throws if crypto is not initialized', () => { const config = new CarConfig({}); - expect(() => config.set('owner.creditCardNumber', 'n/a')) + expect(() => config.update('owner', { creditCardNumber: 'n/a' })) .to.throw() .property('name', 'CryptoNotInitializedError'); }); it('throws if value is not strings', async () => { const config = await CarConfig.create(); - expect(() => config.set('owner.creditCardNumber', 12)) + // // @ts-expect-error it should be a string, but testing what happens when it's not + expect(() => config.update('owner', { creditCardNumber: 12 })) .to.throw() .property('name', 'InvalidCryptoValueError'); }); @@ -113,7 +113,7 @@ describe('ConfigStore', () => { it('encrypts nested key', async () => { const expected = 'a29djf0kq3dj90d3q'; const config = await CarConfig.create(); - config.set('owner', { + config.update('owner', { name: 'Bob', creditCardNumber: expected, phone: '707-bob-cell', @@ -149,7 +149,7 @@ describe('ConfigStore', () => { const expected = 'a29djf0kq3dj90d3q'; const config = await CarConfig.create(); const owner = { name: 'Bob', creditCardNumber: expected }; - // I would love for this to throw an error, but the current typing doesn't quite work like get does. + // // @ts-expect-error that's not a full owner, not all required props are set config.set('owner', owner); const decryptedOwner = config.get('owner', true); @@ -167,18 +167,19 @@ describe('ConfigStore', () => { const refreshToken = '5678'; const config = await AuthInfoConfig.create({}); const auth = { accessToken, refreshToken }; - config.set('auth', auth); + config.setContentsFromObject(auth); - expect(config.get('auth').accessToken).to.not.equal(accessToken); - expect(config.get('auth').refreshToken).to.not.equal(refreshToken); - expect(config.get('auth', true).accessToken).to.equal(accessToken); - expect(config.get('auth', true).refreshToken).to.equal(refreshToken); + expect(config.get('accessToken')).to.not.equal(accessToken); + expect(config.get('refreshToken')).to.not.equal(refreshToken); + expect(config.get('accessToken', true)).to.equal(accessToken); + expect(config.get('refreshToken', true)).to.equal(refreshToken); }); it('does not fail when saving an already encrypted object', async () => { const expected = 'a29djf0kq3dj90d3q'; const config = await CarConfig.create(); const owner = { name: 'Bob', creditCardNumber: expected }; + // // @ts-expect-error incomplete owner config.set('owner', owner); const encryptedCreditCardNumber = config.get('owner').creditCardNumber; const contents = config.getContents(); @@ -193,6 +194,7 @@ describe('ConfigStore', () => { const expected = 'a29djf0kq3dj90d3q'; const config = await CarConfig.create(); const owner = { name: 'Bob', creditCardNumber: 'old credit card number' }; + // // @ts-expect-error incomplete owner config.set('owner', owner); config.update('owner', { creditCardNumber: expected }); diff --git a/test/unit/config/lwwMapTest.ts b/test/unit/config/lwwMapTest.ts index eb1dabc302..3726e1e6cc 100644 --- a/test/unit/config/lwwMapTest.ts +++ b/test/unit/config/lwwMapTest.ts @@ -5,19 +5,20 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ import { expect, config } from 'chai'; -import { LWWMap, SYMBOL_FOR_DELETED } from '../../../src/config/lwwMap'; +import { LWWMap, LWWState, SYMBOL_FOR_DELETED } from '../../../src/config/lwwMap'; config.truncateThreshold = 0; describe('LWWMap', () => { + type TestType = { foo: string; baz: string; opt?: number; optNull?: null }; describe('all properties are known', () => { const state = { - foo: { value: 'bar', timestamp: process.hrtime.bigint(), peer: 'a' }, - baz: { value: 'qux', timestamp: process.hrtime.bigint(), peer: 'a' }, + foo: { value: 'bar', timestamp: process.hrtime.bigint() }, + baz: { value: 'qux', timestamp: process.hrtime.bigint() }, }; - let lwwMap: LWWMap<{ foo: string; baz: string; opt?: number; optNull?: null }>; + let lwwMap: LWWMap; beforeEach(() => { - lwwMap = new LWWMap('test', state); + lwwMap = new LWWMap(state); }); it('should initialize with the correct state', () => { @@ -70,31 +71,31 @@ describe('LWWMap', () => { describe('merge', () => { beforeEach(() => { - lwwMap = new LWWMap('test', state); + lwwMap = new LWWMap(state); }); it('all are updated', () => { const remoteState = { - foo: { value: 'bar2', timestamp: process.hrtime.bigint(), peer: 'b' }, - baz: { value: 'qux2', timestamp: process.hrtime.bigint(), peer: 'b' }, - }; + foo: { value: 'bar2', timestamp: process.hrtime.bigint() }, + baz: { value: 'qux2', timestamp: process.hrtime.bigint() }, + } satisfies LWWState; lwwMap.merge(remoteState); expect(lwwMap.state).to.deep.equal(remoteState); expect(lwwMap.get('foo')).to.equal('bar2'); }); it('all are deleted', () => { const remoteState = { - foo: { value: SYMBOL_FOR_DELETED, timestamp: process.hrtime.bigint(), peer: 'b' }, - baz: { value: SYMBOL_FOR_DELETED, timestamp: process.hrtime.bigint(), peer: 'b' }, - }; + foo: { value: SYMBOL_FOR_DELETED, timestamp: process.hrtime.bigint() }, + baz: { value: SYMBOL_FOR_DELETED, timestamp: process.hrtime.bigint() }, + } satisfies LWWState; lwwMap.merge(remoteState); expect(lwwMap.state).to.deep.equal(remoteState); expect(lwwMap.get('foo')).to.equal(undefined); }); it('none are updated', () => { const remoteState = { - foo: { value: 'bar2', timestamp: process.hrtime.bigint() - BigInt(10000000000), peer: 'b' }, - baz: { value: 'qux2', timestamp: process.hrtime.bigint() - BigInt(10000000000), peer: 'b' }, - }; + foo: { value: 'bar2', timestamp: process.hrtime.bigint() - BigInt(10000000000) }, + baz: { value: 'qux2', timestamp: process.hrtime.bigint() - BigInt(10000000000) }, + } satisfies LWWState; lwwMap.merge(remoteState); expect(lwwMap.state).to.deep.equal(state); expect(lwwMap.get('foo')).to.equal('bar'); @@ -102,11 +103,11 @@ describe('LWWMap', () => { it('one is update, one is added, one is deleted', () => { lwwMap.set('opt', 4); const remoteState = { - foo: { value: 'bar2', timestamp: process.hrtime.bigint(), peer: 'b' }, - baz: { value: 'qux2', timestamp: process.hrtime.bigint() - BigInt(10000000000), peer: 'b' }, - opt: { value: SYMBOL_FOR_DELETED, timestamp: process.hrtime.bigint(), peer: 'b' }, - optNull: { value: null, timestamp: process.hrtime.bigint(), peer: 'b' }, - }; + foo: { value: 'bar2', timestamp: process.hrtime.bigint() }, + baz: { value: 'qux2', timestamp: process.hrtime.bigint() - BigInt(10000000000) }, + opt: { value: SYMBOL_FOR_DELETED, timestamp: process.hrtime.bigint() }, + optNull: { value: null, timestamp: process.hrtime.bigint() }, + } satisfies LWWState; lwwMap.merge(remoteState); expect(lwwMap.get('foo')).to.equal('bar2'); expect(lwwMap.get('baz')).to.equal('qux'); @@ -116,16 +117,50 @@ describe('LWWMap', () => { }); }); + describe('open-ended objects', () => { + type OpenEndedType = TestType & { [key: string]: string }; + const initialState = { + foo: { value: 'bar', timestamp: process.hrtime.bigint() }, + baz: { value: 'qux', timestamp: process.hrtime.bigint() }, + } satisfies LWWState; + + it('set a new prop', () => { + const lwwMap = new LWWMap(initialState); + lwwMap.set('thing', 'whatever'); + expect(lwwMap.get('thing')).to.equal('whatever'); + expect(lwwMap.value).to.deep.equal({ foo: 'bar', baz: 'qux', thing: 'whatever' }); + expect(lwwMap.state.thing.value).to.equal('whatever'); + }); + + it('set and delete new prop', () => { + const lwwMap = new LWWMap(initialState); + lwwMap.set('thing', 'whatever'); + expect(lwwMap.get('thing')).to.equal('whatever'); + lwwMap.delete('thing'); + expect(lwwMap.get('thing')).to.be.undefined; + expect(lwwMap.value).to.deep.equal({ foo: 'bar', baz: 'qux' }); + expect(lwwMap.state.thing.value).to.equal(SYMBOL_FOR_DELETED); + }); + + it('delete a non-existent prop', () => { + const lwwMap = new LWWMap(initialState); + expect(lwwMap.get('gone')).to.be.undefined; + lwwMap.delete('gone'); + expect(lwwMap.get('gone')).to.be.undefined; + expect(lwwMap.value).to.deep.equal({ foo: 'bar', baz: 'qux' }); + expect(lwwMap.state.gone.value).to.equal(SYMBOL_FOR_DELETED); + }); + }); describe('nested objects', () => { const state = { - foo: { value: 'bar', timestamp: process.hrtime.bigint(), peer: 'a' }, - baz: { value: 'qux', timestamp: process.hrtime.bigint(), peer: 'a' }, - obj: { value: { a: 1, b: 2, c: 3 }, timestamp: process.hrtime.bigint(), peer: 'a' }, + foo: { value: 'bar', timestamp: process.hrtime.bigint() }, + baz: { value: 'qux', timestamp: process.hrtime.bigint() }, + obj: { value: { a: 1, b: 2, c: 3 }, timestamp: process.hrtime.bigint() }, }; let lwwMap: LWWMap<{ foo: string; baz: string; opt?: number; optNull?: null }>; beforeEach(() => { - lwwMap = new LWWMap('test', state); + lwwMap = new LWWMap(state); }); it('should initialize with the correct state', () => { diff --git a/test/unit/org/permissionSetAssignmentTest.ts b/test/unit/org/permissionSetAssignmentTest.ts index fefd599ebc..662f845cc9 100644 --- a/test/unit/org/permissionSetAssignmentTest.ts +++ b/test/unit/org/permissionSetAssignmentTest.ts @@ -4,13 +4,15 @@ * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import { expect } from 'chai'; +import { expect, config } from 'chai'; import { AuthInfo } from '../../../src/org/authInfo'; import { Connection } from '../../../src/org/connection'; import { Org } from '../../../src/org/org'; import { PermissionSetAssignment } from '../../../src/org/permissionSetAssignment'; import { MockTestOrgData, shouldThrow, TestContext } from '../../../src/testSetup'; +config.truncateThreshold = 0; + describe('permission set assignment tests', () => { const $$ = new TestContext(); let userTestData: MockTestOrgData; @@ -144,7 +146,7 @@ describe('permission set assignment tests', () => { $$.SANDBOX.stub(Connection.prototype, 'sobject').callsFake(() => ({ // @ts-expect-error - type mismatch create() { - return Promise.resolve({ errors: ['error one', 'error two'] }); + return Promise.resolve({ success: false, errors: ['error one', 'error two'] }); }, })); From 20e32bd37c8d37cc145fc6608a7cc23cf3c6fb78 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Thu, 19 Oct 2023 11:39:17 -0500 Subject: [PATCH 39/67] refactor: tighten up property keys --- src/config/config.ts | 19 +++++++++++++------ src/config/configStore.ts | 28 ++++++++++++---------------- test/unit/config/configStoreTest.ts | 8 ++++---- 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/src/config/config.ts b/src/config/config.ts index 63ef144953..d3460758f8 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -8,7 +8,7 @@ import { dirname as pathDirname, join as pathJoin } from 'path'; import * as fs from 'fs'; import { keyBy, parseJsonMap } from '@salesforce/kit'; -import { Dictionary, ensure, isString, JsonCollection, JsonPrimitive, Nullable } from '@salesforce/ts-types'; +import { Dictionary, ensure, isString, Nullable } from '@salesforce/ts-types'; import { Global } from '../global'; import { Logger } from '../logger/logger'; import { Messages } from '../messages'; @@ -17,7 +17,7 @@ import { SfdcUrl } from '../util/sfdcUrl'; import { ORG_CONFIG_ALLOWED_PROPERTIES, OrgConfigProperties } from '../org/orgConfigProperties'; import { Lifecycle } from '../lifecycleEvents'; import { ConfigFile } from './configFile'; -import { ConfigContents, ConfigValue } from './configStackTypes'; +import { ConfigContents, ConfigValue, Key } from './configStackTypes'; Messages.importMessagesDirectory(__dirname); const messages = Messages.loadMessages('@salesforce/core', 'config'); @@ -287,7 +287,10 @@ export const SFDX_ALLOWED_PROPERTIES = [ // Generic global config properties. Specific properties can be loaded like orgConfigProperties.ts. export const SfProperty: { [index: string]: ConfigPropertyMeta } = {}; -export type ConfigProperties = { [index: string]: JsonPrimitive }; +/* A very loose type to account for the possibility of plugins adding properties via configMeta. + * The class itself is doing runtime validation to check property keys and values. + */ +export type ConfigProperties = ConfigContents; /** * The files where sfdx config values are stored for projects and the global space. @@ -378,7 +381,11 @@ export class Config extends ConfigFile { * @param propertyName The name of the property to set. * @param value The property value. */ - public static async update(isGlobal: boolean, propertyName: string, value?: ConfigValue): Promise { + public static async update>( + isGlobal: boolean, + propertyName: K, + value?: ConfigProperties[K] + ): Promise { const config = await Config.create({ isGlobal }); await config.read(); @@ -479,7 +486,7 @@ export class Config extends ConfigFile { * @param key The property to set. * @param value The value of the property. */ - public set(key: string, value: JsonPrimitive | JsonCollection): ConfigProperties { + public set>(key: K, value: ConfigProperties[K]): ConfigProperties { const property = Config.allowedProperties.find((allowedProp) => allowedProp.key === key); if (!property) { @@ -491,7 +498,7 @@ export class Config extends ConfigFile { return this.set(property.newKey, value); } - if (property.input) { + if (value !== undefined && property.input) { if (property.input?.validator(value)) { super.set(property.key, value); } else { diff --git a/src/config/configStore.ts b/src/config/configStore.ts index fb13ed1675..046c553692 100644 --- a/src/config/configStore.ts +++ b/src/config/configStore.ts @@ -136,9 +136,7 @@ export abstract class BaseConfigStore< * @param key The key. * @param value The value. */ - public set>(key: K, value: P[K]): void; - public set(key: string, value: V): void; - public set>(key: K | string, value: P[K] | ConfigValue): void { + public set>(key: K, value: P[K]): void { if (this.hasEncryption()) { if (isJsonMap(value)) { value = this.recursiveEncrypt(value, key as string) as P[K]; @@ -146,11 +144,10 @@ export abstract class BaseConfigStore< value = this.encrypt(value) as P[K]; } } - // undefined value means unset + // set(key, undefined) means unset if (value === undefined) { - this.unset(key as string); + this.unset(key); } else { - // @ts-expect-error TODO: why isn't key guaranteed to be a string this.contents.set(key, value); } } @@ -159,17 +156,17 @@ export abstract class BaseConfigStore< * Updates the value for the key in the config object. If the value is an object, it * will be merged with the existing object. * - * @param key The key. Supports query key like `a.b[0]`. + * @param key The key. * @param value The value. */ - public update>(key: K, value: Partial): void; - public update(key: string, value: Partial): void; - public update>(key: K | string, value: Partial | Partial): void { + public update>(key: K, value: Partial): void { const existingValue = this.get(key, true); if (isPlainObject(existingValue) && isPlainObject(value)) { - value = Object.assign({}, existingValue, value); + const mergedValue = Object.assign({}, existingValue, value); + this.set(key, mergedValue as P[K]); + } else { + this.set(key, value as P[K]); } - this.set(key, value); } /** @@ -178,9 +175,8 @@ export abstract class BaseConfigStore< * * @param key The key */ - public unset(key: string): boolean { + public unset>(key: K): boolean { if (this.has(key)) { - // @ts-expect-error TODO: why can't TS compile tighter types for keys of P this.contents.delete(key); return true; } @@ -193,7 +189,7 @@ export abstract class BaseConfigStore< * * @param keys The keys. Supports query keys like `a.b[0]`. */ - public unsetAll(keys: string[]): boolean { + public unsetAll(keys: Array>): boolean { return keys.map((key) => this.unset(key)).every(Boolean); } @@ -262,7 +258,7 @@ export abstract class BaseConfigStore< * * @param obj The object. */ - public setContentsFromObject(obj: U): void { + public setContentsFromObject(obj: P): void { const objForWrite = this.hasEncryption() ? this.recursiveEncrypt(obj) : obj; entriesOf(objForWrite).map(([key, value]) => { this.set(key, value); diff --git a/test/unit/config/configStoreTest.ts b/test/unit/config/configStoreTest.ts index 00d047f5f8..7dd2805d98 100644 --- a/test/unit/config/configStoreTest.ts +++ b/test/unit/config/configStoreTest.ts @@ -94,7 +94,7 @@ describe('ConfigStore', () => { it('throws if value is not strings', async () => { const config = await CarConfig.create(); - // // @ts-expect-error it should be a string, but testing what happens when it's not + // @ts-expect-error it should be a string, but testing what happens when it's not expect(() => config.update('owner', { creditCardNumber: 12 })) .to.throw() .property('name', 'InvalidCryptoValueError'); @@ -149,7 +149,7 @@ describe('ConfigStore', () => { const expected = 'a29djf0kq3dj90d3q'; const config = await CarConfig.create(); const owner = { name: 'Bob', creditCardNumber: expected }; - // // @ts-expect-error that's not a full owner, not all required props are set + // @ts-expect-error that's not a full owner, not all required props are set config.set('owner', owner); const decryptedOwner = config.get('owner', true); @@ -179,7 +179,7 @@ describe('ConfigStore', () => { const expected = 'a29djf0kq3dj90d3q'; const config = await CarConfig.create(); const owner = { name: 'Bob', creditCardNumber: expected }; - // // @ts-expect-error incomplete owner + // @ts-expect-error incomplete owner config.set('owner', owner); const encryptedCreditCardNumber = config.get('owner').creditCardNumber; const contents = config.getContents(); @@ -194,7 +194,7 @@ describe('ConfigStore', () => { const expected = 'a29djf0kq3dj90d3q'; const config = await CarConfig.create(); const owner = { name: 'Bob', creditCardNumber: 'old credit card number' }; - // // @ts-expect-error incomplete owner + // @ts-expect-error incomplete owner config.set('owner', owner); config.update('owner', { creditCardNumber: expected }); From f1f20a6ae44d3a3b19d2e1cf12974144f699ea4b Mon Sep 17 00:00:00 2001 From: mshanemc Date: Thu, 19 Oct 2023 11:53:30 -0500 Subject: [PATCH 40/67] test: more timestamp offset for bigInt UT --- test/unit/config/lwwMapTest.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/unit/config/lwwMapTest.ts b/test/unit/config/lwwMapTest.ts index 3726e1e6cc..ba7c12b36b 100644 --- a/test/unit/config/lwwMapTest.ts +++ b/test/unit/config/lwwMapTest.ts @@ -8,6 +8,8 @@ import { expect, config } from 'chai'; import { LWWMap, LWWState, SYMBOL_FOR_DELETED } from '../../../src/config/lwwMap'; config.truncateThreshold = 0; +const OLD_TIMESTAMP_OFFSET = BigInt(10_000_000_000_000); + describe('LWWMap', () => { type TestType = { foo: string; baz: string; opt?: number; optNull?: null }; describe('all properties are known', () => { @@ -93,8 +95,8 @@ describe('LWWMap', () => { }); it('none are updated', () => { const remoteState = { - foo: { value: 'bar2', timestamp: process.hrtime.bigint() - BigInt(10000000000) }, - baz: { value: 'qux2', timestamp: process.hrtime.bigint() - BigInt(10000000000) }, + foo: { value: 'bar2', timestamp: process.hrtime.bigint() - OLD_TIMESTAMP_OFFSET }, + baz: { value: 'qux2', timestamp: process.hrtime.bigint() - OLD_TIMESTAMP_OFFSET }, } satisfies LWWState; lwwMap.merge(remoteState); expect(lwwMap.state).to.deep.equal(state); @@ -104,7 +106,7 @@ describe('LWWMap', () => { lwwMap.set('opt', 4); const remoteState = { foo: { value: 'bar2', timestamp: process.hrtime.bigint() }, - baz: { value: 'qux2', timestamp: process.hrtime.bigint() - BigInt(10000000000) }, + baz: { value: 'qux2', timestamp: process.hrtime.bigint() - OLD_TIMESTAMP_OFFSET }, opt: { value: SYMBOL_FOR_DELETED, timestamp: process.hrtime.bigint() }, optNull: { value: null, timestamp: process.hrtime.bigint() }, } satisfies LWWState; From a332085b790df5fd9062694eaeec5d3857b23d67 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Thu, 19 Oct 2023 12:15:04 -0500 Subject: [PATCH 41/67] feat!: don't export findUppercaseKeys --- src/sfProject.ts | 2 +- src/util/findUppercaseKeys.ts | 25 ++++++++++ src/util/sfdc.ts | 41 ---------------- test/unit/util/findUppercaseKeysTest.ts | 63 +++++++++++++++++++++++++ test/unit/util/sfdcTest.ts | 55 --------------------- 5 files changed, 89 insertions(+), 97 deletions(-) create mode 100644 src/util/findUppercaseKeys.ts create mode 100644 test/unit/util/findUppercaseKeysTest.ts diff --git a/src/sfProject.ts b/src/sfProject.ts index 3b664d76aa..49a2f8cf30 100644 --- a/src/sfProject.ts +++ b/src/sfProject.ts @@ -17,8 +17,8 @@ import { SchemaValidator } from './schema/validator'; import { resolveProjectPath, resolveProjectPathSync, SFDX_PROJECT_JSON } from './util/internal'; import { SfError } from './sfError'; -import { findUpperCaseKeys } from './util/sfdc'; import { Messages } from './messages'; +import { findUpperCaseKeys } from './util/findUppercaseKeys'; Messages.importMessagesDirectory(__dirname); const messages = Messages.loadMessages('@salesforce/core', 'config'); diff --git a/src/util/findUppercaseKeys.ts b/src/util/findUppercaseKeys.ts new file mode 100644 index 0000000000..3452647d56 --- /dev/null +++ b/src/util/findUppercaseKeys.ts @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { JsonMap, Optional, isJsonMap, asJsonMap, AnyJson } from '@salesforce/ts-types'; +import { findKey } from '@salesforce/kit'; + +export const findUpperCaseKeys = (data?: JsonMap, sectionBlocklist: string[] = []): Optional => { + let key: Optional; + findKey(data, (val: AnyJson, k: string) => { + if (/^[A-Z]/.test(k)) { + key = k; + } else if (isJsonMap(val)) { + if (sectionBlocklist.includes(k)) { + return key; + } + key = findUpperCaseKeys(asJsonMap(val)); + } + return key; + }); + return key; +}; diff --git a/src/util/sfdc.ts b/src/util/sfdc.ts index 848d936a7f..c4fee36f75 100644 --- a/src/util/sfdc.ts +++ b/src/util/sfdc.ts @@ -5,9 +5,6 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import { findKey } from '@salesforce/kit'; -import { AnyJson, asJsonMap, isJsonMap, JsonMap, Optional } from '@salesforce/ts-types'; - /** * Converts an 18 character Salesforce ID to 15 characters. * @@ -40,20 +37,6 @@ export const validateApiVersion = (value: string): boolean => value == null || / */ export const validateEmail = (value: string): boolean => /^[^.][^@]*@[^.]+(\.[^.\s]+)+$/.test(value); -/** - * - * @deprecated use `new SfdcUrl(url).isInternalUrl()` - * Tests whether a given url is an internal Salesforce domain - * - * @param url - */ -export const isInternalUrl = (url: string): boolean => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires - const SfdcUrl = require('./sfdcUrl'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call - return new SfdcUrl(url).isInternalUrl() as boolean; -}; - /** * Tests whether a Salesforce ID is in the correct format, a 15- or 18-character length string with only letters and numbers * @@ -71,30 +54,6 @@ export const validatePathDoesNotContainInvalidChars = (value: string): boolean = // eslint-disable-next-line no-useless-escape !/[\["\?<>\|\]]+/.test(value); -/** - * @deprecated - * // TODO: move this to a module-scope function in sfProject - * Returns the first key within the object that has an upper case first letter. - * - * @param data The object in which to check key casing. - * @param sectionBlocklist properties in the object to exclude from the search. e.g. a blocklist of `["a"]` and data of `{ "a": { "B" : "b"}}` would ignore `B` because it is in the object value under `a`. - */ -export const findUpperCaseKeys = (data?: JsonMap, sectionBlocklist: string[] = []): Optional => { - let key: Optional; - findKey(data, (val: AnyJson, k: string) => { - if (/^[A-Z]/.test(k)) { - key = k; - } else if (isJsonMap(val)) { - if (sectionBlocklist.includes(k)) { - return key; - } - key = findUpperCaseKeys(asJsonMap(val)); - } - return key; - }); - return key; -}; - export const accessTokenRegex = /(00D\w{12,15})![.\w]*/; export const sfdxAuthUrlRegex = /force:\/\/([a-zA-Z0-9._-]+):([a-zA-Z0-9._-]*):([a-zA-Z0-9._-]+={0,2})@([a-zA-Z0-9._-]+)/; diff --git a/test/unit/util/findUppercaseKeysTest.ts b/test/unit/util/findUppercaseKeysTest.ts new file mode 100644 index 0000000000..27b91a7d6b --- /dev/null +++ b/test/unit/util/findUppercaseKeysTest.ts @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { expect } from 'chai'; +import { findUpperCaseKeys } from '../../../src/util/findUppercaseKeys'; + +describe('findUpperCaseKeys', () => { + it('should return the first upper case key', () => { + const testObj = { + lowercase: true, + UpperCase: false, + nested: { camelCase: true }, + }; + expect(findUpperCaseKeys(testObj)).to.equal('UpperCase'); + }); + + it('should return the first nested upper case key', () => { + const testObj = { + lowercase: true, + uppercase: false, + nested: { NestedUpperCase: true }, + }; + expect(findUpperCaseKeys(testObj)).to.equal('NestedUpperCase'); + }); + + it('should return undefined when no upper case key is found', () => { + const testObj = { + lowercase: true, + uppercase: false, + nested: { camelCase: true }, + }; + expect(findUpperCaseKeys(testObj)).to.be.undefined; + }); + + it('should return the first nested upper case key unless blocklisted', () => { + const testObj = { + lowercase: true, + uppercase: false, + nested: { NestedUpperCase: true }, + }; + expect(findUpperCaseKeys(testObj, ['nested'])).to.equal(undefined); + }); + + it('handles keys starting with numbers', () => { + const testObj = { + '1abc': true, + Abc: false, + nested: { '2abc': true }, + }; + expect(findUpperCaseKeys(testObj)).to.equal('Abc'); + }); + it('handles keys starting with numbers', () => { + const testObj = { + '1abc': true, + nested: { '2abc': true, Upper: false }, + }; + expect(findUpperCaseKeys(testObj)).to.equal('Upper'); + }); +}); diff --git a/test/unit/util/sfdcTest.ts b/test/unit/util/sfdcTest.ts index 07724f0fde..205049032a 100644 --- a/test/unit/util/sfdcTest.ts +++ b/test/unit/util/sfdcTest.ts @@ -6,7 +6,6 @@ */ import { expect } from 'chai'; import { - findUpperCaseKeys, matchesAccessToken, trimTo15, validateApiVersion, @@ -103,60 +102,6 @@ describe('util/sfdc', () => { }); }); - describe('findUpperCaseKeys', () => { - it('should return the first upper case key', () => { - const testObj = { - lowercase: true, - UpperCase: false, - nested: { camelCase: true }, - }; - expect(findUpperCaseKeys(testObj)).to.equal('UpperCase'); - }); - - it('should return the first nested upper case key', () => { - const testObj = { - lowercase: true, - uppercase: false, - nested: { NestedUpperCase: true }, - }; - expect(findUpperCaseKeys(testObj)).to.equal('NestedUpperCase'); - }); - - it('should return undefined when no upper case key is found', () => { - const testObj = { - lowercase: true, - uppercase: false, - nested: { camelCase: true }, - }; - expect(findUpperCaseKeys(testObj)).to.be.undefined; - }); - - it('should return the first nested upper case key unless blocklisted', () => { - const testObj = { - lowercase: true, - uppercase: false, - nested: { NestedUpperCase: true }, - }; - expect(findUpperCaseKeys(testObj, ['nested'])).to.equal(undefined); - }); - - it('handles keys starting with numbers', () => { - const testObj = { - '1abc': true, - Abc: false, - nested: { '2abc': true }, - }; - expect(findUpperCaseKeys(testObj)).to.equal('Abc'); - }); - it('handles keys starting with numbers', () => { - const testObj = { - '1abc': true, - nested: { '2abc': true, Upper: false }, - }; - expect(findUpperCaseKeys(testObj)).to.equal('Upper'); - }); - }); - describe('matchesAccessToken', () => { it('should return true for a valid access token', () => { expect( From b184d511c47cd0da4136714d5d4fd93cca1c2561 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Thu, 19 Oct 2023 12:56:51 -0500 Subject: [PATCH 42/67] test: nuts wait for UT --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 183343b302..94b6bea37a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,8 +14,8 @@ jobs: windows-unit-tests: needs: linux-unit-tests uses: salesforcecli/github-workflows/.github/workflows/unitTestsWindows.yml@main - nuts: - # needs: linux-unit-tests + xNuts: + needs: linux-unit-tests uses: salesforcecli/github-workflows/.github/workflows/externalNut.yml@main strategy: fail-fast: false @@ -44,7 +44,7 @@ jobs: preExternalBuildCommands: 'shx rm -rf node_modules/@salesforce/core/node_modules/jsforce shx rm -rf node_modules/@salesforce/sf-plugins-core/node_modules/@salesforce/core node_modules/@salesforce/cli-plugins-testkit/node_modules/@salesforce/core node_modules/@salesforce/source-tracking/node_modules/@salesforce/core node_modules/@salesforce/source-deploy-retrieve/node_modules/@salesforce/core' secrets: inherit # hidden until we fix source-testkit to better handle jwt - deployRetrieveNuts: + pdrNuts: needs: linux-unit-tests uses: salesforcecli/github-workflows/.github/workflows/externalNut.yml@main strategy: From 903fc59d1a3b62ee6c99c155d72918b7d5813488 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Thu, 19 Oct 2023 15:29:20 -0500 Subject: [PATCH 43/67] feat!: remove SchemaPrinter --- src/exported.ts | 1 - src/schema/printer.ts | 309 ------------------------------------------ 2 files changed, 310 deletions(-) delete mode 100644 src/schema/printer.ts diff --git a/src/exported.ts b/src/exported.ts index 6f5e0f8879..7e995bc87f 100644 --- a/src/exported.ts +++ b/src/exported.ts @@ -77,7 +77,6 @@ export { OrgConfigProperties, ORG_CONFIG_ALLOWED_PROPERTIES } from './org/orgCon export { PackageDir, NamedPackageDir, PackageDirDependency, SfProject, SfProjectJson } from './sfProject'; export { SchemaValidator } from './schema/validator'; -export { SchemaPrinter } from './schema/printer'; export { SfError } from './sfError'; diff --git a/src/schema/printer.ts b/src/schema/printer.ts deleted file mode 100644 index 8eab57b3d8..0000000000 --- a/src/schema/printer.ts +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Copyright (c) 2020, salesforce.com, inc. - * All rights reserved. - * Licensed under the BSD 3-Clause license. - * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ -/* eslint-disable class-methods-use-this */ - -import { - asJsonArray, - asJsonMap, - asNumber, - asString, - isJsonMap, - JsonArray, - JsonMap, - Optional, -} from '@salesforce/ts-types'; -import { Logger } from '../logger/logger'; -import { SfError } from '../sfError'; - -/** - * Renders schema properties. By default, this is simply an identity transform. Subclasses may provide more - * interesting decorations of each values, such as ANSI coloring. - * - * @deprecated - * - */ -export class SchemaPropertyRenderer { - /** - * Renders a name. - * - * @param name The name value to render. - */ - public renderName(name: string): string { - return name; - } - - /** - * Renders a title. - * - * @param title The title value to render. - */ - public renderTitle(title: string): string { - return title; - } - - /** - * Renders a description. - * - * @param description The description value to render. - */ - public renderDescription(description: string): string { - return description; - } - - /** - * Renders a type. - * - * @param propertyType The type value to render. - */ - public renderType(propertyType: string): string { - return propertyType; - } -} - -/** - * Prints a JSON schema in a human-friendly format. - * - * @deprecated - * remaining reference: https://github.com/salesforcecli/plugin-data/blob/cc1bdfa2c707f93a6da96beea8117b25f9612d4a/src/commands/data/import/tree.ts#L75 - * - * ``` - * import chalk from 'chalk'; - * class MyPropertyRenderer extends SchemaPropertyRenderer { - * renderName(name) { return chalk.bold.blue(name); } - * } - * - * const printer = new SchemaPrinter(logger, schema, new MyPropertyRenderer()); - * printer.getLines().forEach(console.log); - * ``` - */ -export class SchemaPrinter { - private logger: Logger; - private lines: string[] = []; - - /** - * Constructs a new `SchemaPrinter`. - * - * @param logger The logger to use when emitting the printed schema. - * @param schema The schema to print. - * @param propertyRenderer The property renderer. - */ - public constructor( - logger: Logger, - private schema: JsonMap, - private propertyRenderer: SchemaPropertyRenderer = new SchemaPropertyRenderer() - ) { - this.logger = logger.child('SchemaPrinter'); - - if (!this.schema.properties && !this.schema.items) { - // No need to add to messages, since this should never happen. In fact, - // this will cause a test failure if there is a command that uses a schema - // with no properties defined. - throw new SfError('There is no purpose to print a schema with no properties or items'); - } - - const startLevel = 0; - const add = this.addFn(startLevel); - - // For object schemas, print out the "header" and first level properties differently - if (this.schema.properties) { - if (typeof this.schema.description === 'string') { - // Output the overall schema description before printing the properties - add(this.schema.description); - add(''); - } - - Object.keys(this.schema.properties).forEach((key) => { - const properties = asJsonMap(this.schema.properties); - if (!properties) { - return; - } - this.parseProperty(key, asJsonMap(properties[key]), startLevel); - add(''); - }); - } else { - this.parseProperty('schema', this.schema, startLevel); - } - } - - /** - * Gets a read-only array of ready-to-display lines. - */ - public getLines(): readonly string[] { - return this.lines; - } - - /** - * Gets a ready-to-display line by index. - * - * @param index The line index to get. - */ - public getLine(index: number): string { - return this.lines[index]; - } - - /** - * Prints the accumulated set of schema lines as info log lines to the logger. - */ - public print(): void { - this.lines.forEach((line) => this.logger.info(line)); - } - - private addFn(level: number): (line: string) => void { - const indent = ' '.repeat(level * 4); - return (line: string) => { - this.lines.push(`${indent}${line}`); - }; - } - - private parseProperty(name: string, rawProperty?: JsonMap, level = 0): void { - if (!rawProperty) { - return; - } - - const add = this.addFn(level); - const property = new SchemaProperty(this.logger, this.schema, name, rawProperty, this.propertyRenderer); - - add(property.renderHeader()); - - if (property.type === 'object' && property.properties) { - Object.keys(property.properties).forEach((key) => { - this.parseProperty(key, property.getProperty(key), level + 1); - }); - } - if (property.type === 'array') { - add(` ${property.renderArrayHeader()}`); - if (property.items && property.items.type === 'object' && property.items.properties) { - Object.keys(property.items.properties).forEach((key) => { - const items = asJsonMap(property.items); - if (!items) { - return; - } - const properties = asJsonMap(items.properties); - if (!properties) { - return; - } - this.parseProperty(key, asJsonMap(properties[key]), level + 2); - }); - } - } - if (property.required) { - add(`Required: ${property.required.join(', ')}`); - } - } -} - -class SchemaProperty { - public constructor( - private readonly logger: Logger, - private readonly schema: JsonMap, - private readonly name: string, - private rawProperty: JsonMap, - private propertyRenderer: SchemaPropertyRenderer - ) { - this.name = name; - - // Capture the referenced definition, if specified - if (typeof this.rawProperty.$ref === 'string') { - // Copy the referenced property while adding the original property's properties on top of that -- - // if they are defined here, they take precedence over referenced definition properties. - this.rawProperty = Object.assign({}, resolveRef(this.schema, this.rawProperty), rawProperty); - } - - const oneOfs = asJsonArray(this.rawProperty.oneOf); - if (oneOfs && !this.rawProperty.type) { - this.rawProperty.type = oneOfs.map((value) => (isJsonMap(value) ? value.type ?? value.$ref : value)).join('|'); - } - - // Handle items references - if (isJsonMap(this.items) && this.items && this.items.$ref) { - Object.assign(this.items, resolveRef(this.schema, this.items)); - } - } - - public get title(): Optional { - return asString(this.rawProperty.title); - } - - public get description(): Optional { - return asString(this.rawProperty.description); - } - - public get type(): Optional { - return asString(this.rawProperty.type); - } - - public get required(): Optional { - return asJsonArray(this.rawProperty.required); - } - - public get properties(): Optional { - return asJsonMap(this.rawProperty.properties); - } - - public get items(): Optional { - return asJsonMap(this.rawProperty.items); - } - - public get minItems(): Optional { - return asNumber(this.rawProperty.minItems); - } - - public getProperty(key: string): Optional { - const properties = this.getProperties(); - return asJsonMap(properties?.[key]); - } - - public getProperties(): Optional { - return asJsonMap(this.rawProperty.properties); - } - - public renderName(): string { - return this.propertyRenderer.renderName(this.name); - } - - public renderTitle(): string { - return this.propertyRenderer.renderTitle(this.title ?? ''); - } - - public renderDescription(): string { - return this.propertyRenderer.renderDescription(this.description ?? ''); - } - - public renderType(): string { - return this.propertyRenderer.renderType(this.type ?? ''); - } - - public renderHeader(): string { - return `${this.renderName()}(${this.renderType()}) - ${this.renderTitle()}: ${this.renderDescription()}`; - } - - public renderArrayHeader(): string { - if (!this.items) { - return ''; - } - const minItems = this.minItems ? ` - min ${this.minItems}` : ''; - const prop = new SchemaProperty(this.logger, this.schema, 'items', this.items, this.propertyRenderer); - return `items(${prop.renderType()}${minItems}) - ${prop.renderTitle()}: ${prop.renderDescription()}`; - } -} - -/** - * Get the referenced definition by following the reference path on the current schema. - * - * @param schema The source schema containing the property containing a `$ref` field. - * @param property The property that contains the `$ref` field. - */ -function resolveRef(schema: JsonMap, property: JsonMap): JsonMap | null { - const ref = property.$ref; - if (!ref || typeof ref !== 'string') { - return null; - } - return ref.split('/').reduce((prev, key) => { - const next = prev[key]; - return key === '#' ? schema : isJsonMap(next) ? next : {}; - }, property); -} From 25a525ac62f26c2616d062dd3ddc08acee9ef20b Mon Sep 17 00:00:00 2001 From: mshanemc Date: Thu, 19 Oct 2023 16:57:40 -0500 Subject: [PATCH 44/67] perf: parallelize clear writes --- src/config/config.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/config/config.ts b/src/config/config.ts index d3460758f8..54a0b8df08 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -402,13 +402,12 @@ export class Config extends ConfigFile { * Clear all the configured properties both local and global. */ public static async clear(): Promise { - const globalConfig = await Config.create({ isGlobal: true }); - globalConfig.clear(); - await globalConfig.write(); + const [globalConfig, localConfig] = await Promise.all([Config.create({ isGlobal: true }), Config.create()]); - const localConfig = await Config.create(); + globalConfig.clear(); localConfig.clear(); - await localConfig.write(); + + await Promise.all([globalConfig.write(), localConfig.write()]); } public static getPropertyConfigMeta(propertyName: string): Nullable { From 86f3213de1c17f78874db784b274774e6005e3c8 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Fri, 20 Oct 2023 13:41:46 -0500 Subject: [PATCH 45/67] fix!: remove accidental export of getLoginAudienceCombos --- src/exported.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/exported.ts b/src/exported.ts index 7e995bc87f..f49904ac17 100644 --- a/src/exported.ts +++ b/src/exported.ts @@ -104,6 +104,6 @@ export { scratchOrgLifecycleStages, } from './org/scratchOrgLifecycleEvents'; export { ScratchOrgCache } from './org/scratchOrgCache'; + // Utility sub-modules export * from './util/sfdc'; -export * from './util/sfdcUrl'; From b3ba85b8b59c654bd05b67b4467ce6321bc447a4 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Fri, 20 Oct 2023 16:57:26 -0500 Subject: [PATCH 46/67] refactor: crdt patterns on sfdxConfig --- src/config/config.ts | 178 +++++++++++++++++++++++-------------------- 1 file changed, 95 insertions(+), 83 deletions(-) diff --git a/src/config/config.ts b/src/config/config.ts index 54a0b8df08..02e5675141 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -18,6 +18,7 @@ import { ORG_CONFIG_ALLOWED_PROPERTIES, OrgConfigProperties } from '../org/orgCo import { Lifecycle } from '../lifecycleEvents'; import { ConfigFile } from './configFile'; import { ConfigContents, ConfigValue, Key } from './configStackTypes'; +import { LWWState, stateFromContents } from './lwwMap'; Messages.importMessagesDirectory(__dirname); const messages = Messages.loadMessages('@salesforce/core', 'config'); @@ -292,6 +293,8 @@ export const SfProperty: { [index: string]: ConfigPropertyMeta } = {}; */ export type ConfigProperties = ConfigContents; +const sfdxPropKeys = new Set(Object.values(SfdxPropertyKeys) as string[]); + /** * The files where sfdx config values are stored for projects and the global space. * @@ -312,28 +315,24 @@ export class Config extends ConfigFile { ...ORG_CONFIG_ALLOWED_PROPERTIES, ]; - private sfdxConfig: SfdxConfig; + private sfdxPath?: string; public constructor(options?: ConfigFile.Options) { - super( - Object.assign( - { - isGlobal: false, - }, - options ?? {}, - { - // Don't let consumers of config override this. If they really really want to, - // they can extend this class. - isState: true, - filename: Config.getFileName(), - stateFolder: Global.SF_STATE_FOLDER, - } - ) - ); + super({ + ...{ isGlobal: false }, + ...(options ?? {}), + // Don't let consumers of config override this. If they really really want to, + // they can extend this class. + isState: true, + filename: Config.getFileName(), + stateFolder: Global.SF_STATE_FOLDER, + }); // Resolve the config path on creation. this.getPath(); - this.sfdxConfig = new SfdxConfig(this.options, this); + if (Global.SFDX_INTEROPERABILITY) { + this.sfdxPath = buildSfdxPath(this.options); + } } /** @@ -428,10 +427,12 @@ export class Config extends ConfigFile { */ public async read(force = true): Promise { try { - const config = await super.read(false, force); - // Merge .sfdx/sfdx-config.json and .sf/config.json - const merged = this.sfdxConfig.merge(config); - this.setContents(merged); + await super.read(false, force); + if (Global.SFDX_INTEROPERABILITY) { + // will exist if Global.SFDX_INTEROPERABILITY is enabled + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.contents.merge(stateFromSfdxFileSync(this.sfdxPath!, this)); + } await this.cryptProperties(false); return this.getContents(); } finally { @@ -440,10 +441,13 @@ export class Config extends ConfigFile { } public readSync(force = true): ConfigProperties { - const config = super.readSync(false, force); - // Merge .sfdx/sfdx-config.json and .sf/config.json - const merged = this.sfdxConfig.merge(config); - this.setContents(merged); + super.readSync(false, force); + if (Global.SFDX_INTEROPERABILITY) { + // will exist if Global.SFDX_INTEROPERABILITY is enabled + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.contents.merge(stateFromSfdxFileSync(this.sfdxPath!, this)); + } + return this.getContents(); } @@ -455,9 +459,14 @@ export class Config extends ConfigFile { public async write(): Promise { await this.cryptProperties(true); + // super.write will merge the contents if the target file had newer properties await super.write(); - if (Global.SFDX_INTEROPERABILITY) await this.sfdxConfig.write(); + if (Global.SFDX_INTEROPERABILITY) { + // will exist if Global.SFDX_INTEROPERABILITY is enabled + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await writeToSfdx(this.sfdxPath!, this.getContents()); + } await this.cryptProperties(false); return this.getContents(); @@ -593,7 +602,7 @@ export class Config extends ConfigFile { export class SfdxConfig { private sfdxPath: string; public constructor(private options: ConfigFile.Options = {}, private config: Config) { - this.sfdxPath = this.getSfdxPath(); + this.sfdxPath = buildSfdxPath(this.options); } /** @@ -603,11 +612,9 @@ export class SfdxConfig { if (!Global.SFDX_INTEROPERABILITY) return config; const sfdxConfig = this.readSync(); - const sfdxPropKeys = Object.values(SfdxPropertyKeys) as string[]; - // Get a list of config keys that are NOT provided by SfdxPropertyKeys const nonSfdxPropKeys = Config.getAllowedProperties() - .filter((p) => !sfdxPropKeys.includes(p.key)) + .filter((p) => !sfdxPropKeys.has(p.key)) .map((p) => p.key); // Remove any config from .sf that isn't also in .sfdx @@ -620,67 +627,72 @@ export class SfdxConfig { return Object.assign(config, sfdxConfig); } - public async write(config = this.config.toObject()): Promise { - try { - const translated = this.translate(config as ConfigProperties, 'toOld'); - const sfdxPath = this.getSfdxPath(); - await fs.promises.mkdir(pathDirname(sfdxPath), { recursive: true }); - await fs.promises.writeFile(sfdxPath, JSON.stringify(translated, null, 2)); - } catch (error) { - /* Do nothing */ - } - } - private readSync(): ConfigProperties { try { - const contents = parseJsonMap(fs.readFileSync(this.getSfdxPath(), 'utf8')); - return this.translate(contents, 'toNew'); + const contents = parseJsonMap(fs.readFileSync(this.sfdxPath, 'utf8')); + return translateToSf(contents, this.config); } catch (error) { /* Do nothing */ return {}; } } +} - private getSfdxPath(): string { - if (!this.sfdxPath) { - const stateFolder = Global.SFDX_STATE_FOLDER; - const fileName = SFDX_CONFIG_FILE_NAME; - - // Don't let users store config files in homedir without being in the state folder. - let configRootFolder = this.options.rootFolder - ? this.options.rootFolder - : ConfigFile.resolveRootFolderSync(!!this.options.isGlobal); - - if (this.options.isGlobal === true || this.options.isState === true) { - configRootFolder = pathJoin(configRootFolder, stateFolder); - } +/** + * If toOld is specified: migrate all deprecated configs back to their original key. + * - For example, target-org will be renamed to defaultusername. + */ +const translateToSfdx = (sfContents: ConfigProperties): ConfigProperties => + Object.fromEntries( + Object.entries(sfContents).map(([key, value]) => { + const propConfig = Config.getAllowedProperties().find((c) => c.newKey === key) ?? ({} as ConfigPropertyMeta); + return propConfig.deprecated && propConfig.newKey ? [propConfig.key, value] : [key, value]; + }) + ); - this.sfdxPath = pathJoin(configRootFolder, fileName); - } - return this.sfdxPath; - } +/** + * If toOld is specified: migrate all deprecated configs to the new key. + * - For example, target-org will be renamed to defaultusername. + */ +const translateToSf = (sfdxContents: ConfigProperties, SfConfig: Config): ConfigProperties => + Object.fromEntries( + Object.entries(sfdxContents).map(([key, value]) => { + const propConfig = SfConfig.getPropertyConfig(key); + return propConfig.deprecated && propConfig.newKey ? [propConfig.newKey, value] : [key, value]; + }) + ); + +/** given the ConfigFile options, calculate the full path where the config file goes */ +const buildSfdxPath = (options: ConfigFile.Options): string => { + // Don't let users store config files in homedir without being in the state folder. + const configRootFolder = options.rootFolder ?? ConfigFile.resolveRootFolderSync(!!options.isGlobal); + const rootWithState = + options.isGlobal === true || options.isState === true + ? pathJoin(configRootFolder, Global.SFDX_STATE_FOLDER) + : configRootFolder; + + return pathJoin(rootWithState, SFDX_CONFIG_FILE_NAME); +}; - /** - * If toNew is specified: migrate all deprecated configs with a newKey to the newKey. - * - For example, defaultusername will be renamed to target-org. - * - * If toOld is specified: migrate all deprecated configs back to their original key. - * - For example, target-org will be renamed to defaultusername. - */ - private translate(contents: ConfigProperties, direction: 'toNew' | 'toOld'): ConfigProperties { - const translated = {} as ConfigProperties; - for (const [key, value] of Object.entries(contents)) { - const propConfig = - direction === 'toNew' - ? this.config.getPropertyConfig(key) - : Config.getAllowedProperties().find((c) => c.newKey === key) ?? ({} as ConfigPropertyMeta); - if (propConfig.deprecated && propConfig.newKey) { - const normalizedKey = direction === 'toNew' ? propConfig.newKey : propConfig.key; - translated[normalizedKey] = value; - } else { - translated[key] = value; - } - } - return translated; +/** + * writes (in an unsafe way) the configuration file to the sfdx file location. + * Make sure you call ConfigFile.write and getContents so that the contents passed here are not cross-saving something + */ +const writeToSfdx = async (path: string, contents: ConfigProperties): Promise => { + try { + const translated = translateToSfdx(contents); + await fs.promises.mkdir(pathDirname(path), { recursive: true }); + await fs.promises.writeFile(path, JSON.stringify(translated, null, 2)); + } catch (error) { + /* Do nothing */ } -} +}; + +/** turn the sfdx config file into a LWWState based on its contents and its timestamp */ +const stateFromSfdxFileSync = (filePath: string, config: Config): LWWState => { + const fileContents = fs.readFileSync(filePath, 'utf8'); + const mtimeNs = fs.statSync(filePath, { bigint: true }).mtimeNs; + const translatedContents = translateToSf(parseJsonMap(fileContents, filePath), config); + // get the file timestamp + return stateFromContents(translatedContents, mtimeNs); +}; From f33dc5d0d37ccdf4f0e2766c9e5da44bf1558861 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Fri, 20 Oct 2023 17:08:57 -0500 Subject: [PATCH 47/67] test: safe when no sfdx config file --- src/config/config.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/config/config.ts b/src/config/config.ts index 02e5675141..069460e051 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -18,7 +18,7 @@ import { ORG_CONFIG_ALLOWED_PROPERTIES, OrgConfigProperties } from '../org/orgCo import { Lifecycle } from '../lifecycleEvents'; import { ConfigFile } from './configFile'; import { ConfigContents, ConfigValue, Key } from './configStackTypes'; -import { LWWState, stateFromContents } from './lwwMap'; +import { LWWMap, LWWState, stateFromContents } from './lwwMap'; Messages.importMessagesDirectory(__dirname); const messages = Messages.loadMessages('@salesforce/core', 'config'); @@ -690,9 +690,13 @@ const writeToSfdx = async (path: string, contents: ConfigProperties): Promise => { - const fileContents = fs.readFileSync(filePath, 'utf8'); - const mtimeNs = fs.statSync(filePath, { bigint: true }).mtimeNs; - const translatedContents = translateToSf(parseJsonMap(fileContents, filePath), config); - // get the file timestamp - return stateFromContents(translatedContents, mtimeNs); + try { + const fileContents = fs.readFileSync(filePath, 'utf8'); + const mtimeNs = fs.statSync(filePath, { bigint: true }).mtimeNs; + const translatedContents = translateToSf(parseJsonMap(fileContents, filePath), config); + // get the file timestamp + return stateFromContents(translatedContents, mtimeNs); + } catch (e) { + return {}; + } }; From 5eaeb2ffc8a40a199ff914de482ffd732cb58551 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Fri, 20 Oct 2023 17:11:05 -0500 Subject: [PATCH 48/67] test: catch when sfdx-config.json isn't there --- src/config/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/config.ts b/src/config/config.ts index 069460e051..79b17fdbf9 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -18,7 +18,7 @@ import { ORG_CONFIG_ALLOWED_PROPERTIES, OrgConfigProperties } from '../org/orgCo import { Lifecycle } from '../lifecycleEvents'; import { ConfigFile } from './configFile'; import { ConfigContents, ConfigValue, Key } from './configStackTypes'; -import { LWWMap, LWWState, stateFromContents } from './lwwMap'; +import { LWWState, stateFromContents } from './lwwMap'; Messages.importMessagesDirectory(__dirname); const messages = Messages.loadMessages('@salesforce/core', 'config'); From 80f21a3a44e25443bd59259a80e69c23f6917986 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Mon, 23 Oct 2023 13:59:06 -0500 Subject: [PATCH 49/67] feat: remove sfdxConfig class --- src/config/config.ts | 41 ----------------------------------------- 1 file changed, 41 deletions(-) diff --git a/src/config/config.ts b/src/config/config.ts index 79b17fdbf9..c9caf6ea0e 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -293,8 +293,6 @@ export const SfProperty: { [index: string]: ConfigPropertyMeta } = {}; */ export type ConfigProperties = ConfigContents; -const sfdxPropKeys = new Set(Object.values(SfdxPropertyKeys) as string[]); - /** * The files where sfdx config values are stored for projects and the global space. * @@ -599,45 +597,6 @@ export class Config extends ConfigFile { } } -export class SfdxConfig { - private sfdxPath: string; - public constructor(private options: ConfigFile.Options = {}, private config: Config) { - this.sfdxPath = buildSfdxPath(this.options); - } - - /** - * If Global.SFDX_INTEROPERABILITY is enabled, merge the sfdx config into the sf config - */ - public merge(config: ConfigProperties): ConfigProperties | undefined { - if (!Global.SFDX_INTEROPERABILITY) return config; - const sfdxConfig = this.readSync(); - - // Get a list of config keys that are NOT provided by SfdxPropertyKeys - const nonSfdxPropKeys = Config.getAllowedProperties() - .filter((p) => !sfdxPropKeys.has(p.key)) - .map((p) => p.key); - - // Remove any config from .sf that isn't also in .sfdx - // This handles the scenario where a config has been deleted - // from .sfdx and we want to mirror that change in .sf - for (const key of nonSfdxPropKeys) { - if (!sfdxConfig[key]) delete config[key]; - } - - return Object.assign(config, sfdxConfig); - } - - private readSync(): ConfigProperties { - try { - const contents = parseJsonMap(fs.readFileSync(this.sfdxPath, 'utf8')); - return translateToSf(contents, this.config); - } catch (error) { - /* Do nothing */ - return {}; - } - } -} - /** * If toOld is specified: migrate all deprecated configs back to their original key. * - For example, target-org will be renamed to defaultusername. From 67e6f5ae13bb2714f0cb399367c17cd4dd065fbc Mon Sep 17 00:00:00 2001 From: mshanemc Date: Mon, 23 Oct 2023 15:36:01 -0500 Subject: [PATCH 50/67] test: back to jsforce latest --- .github/workflows/test.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 94b6bea37a..4226fd0bea 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,8 +39,7 @@ jobs: command: 'yarn test:nuts' os: ${{ matrix.os }} useCache: false - # we shouldn't be hardcoding the jsforce version here, but 28 is broken. - preSwapCommands: 'yarn upgrade jsforce@2.0.0-beta.27; npx yarn-deduplicate; yarn install' + preSwapCommands: 'yarn upgrade jsforce@beta; npx yarn-deduplicate; yarn install' preExternalBuildCommands: 'shx rm -rf node_modules/@salesforce/core/node_modules/jsforce shx rm -rf node_modules/@salesforce/sf-plugins-core/node_modules/@salesforce/core node_modules/@salesforce/cli-plugins-testkit/node_modules/@salesforce/core node_modules/@salesforce/source-tracking/node_modules/@salesforce/core node_modules/@salesforce/source-deploy-retrieve/node_modules/@salesforce/core' secrets: inherit # hidden until we fix source-testkit to better handle jwt @@ -69,8 +68,7 @@ jobs: with: packageName: '@salesforce/core' externalProjectGitUrl: 'https://github.com/salesforcecli/plugin-deploy-retrieve' - # we shouldn't be hardcoding the jsforce version here, but 28 is broken. - preSwapCommands: 'yarn upgrade jsforce@2.0.0-beta.27; npx yarn-deduplicate; yarn install' + preSwapCommands: 'yarn upgrade jsforce@beta; npx yarn-deduplicate; yarn install' command: ${{ matrix.command }} os: ${{ matrix.os }} preExternalBuildCommands: 'shx rm -rf node_modules/@salesforce/core/node_modules/jsforce shx rm -rf node_modules/@salesforce/sf-plugins-core/node_modules/@salesforce/core node_modules/@salesforce/cli-plugins-testkit/node_modules/@salesforce/core node_modules/@salesforce/source-tracking/node_modules/@salesforce/core node_modules/@salesforce/source-deploy-retrieve/node_modules/@salesforce/core' From 938e5dae52f9a9430a43f319290fe2080a129393 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Mon, 23 Oct 2023 15:57:25 -0500 Subject: [PATCH 51/67] chore: bump deps for xnuts --- package.json | 4 ++-- yarn.lock | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index c9ce8bc7c0..26857bb410 100644 --- a/package.json +++ b/package.json @@ -39,9 +39,9 @@ "messageTransformer/messageTransformer.ts" ], "dependencies": { - "@salesforce/kit": "^3.0.14", + "@salesforce/kit": "^3.0.15", "@salesforce/schemas": "^1.6.0", - "@salesforce/ts-types": "^2.0.8", + "@salesforce/ts-types": "^2.0.9", "@types/semver": "^7.5.3", "ajv": "^8.12.0", "change-case": "^4.1.2", diff --git a/yarn.lock b/yarn.lock index a2bc319a7a..9c3cf1f839 100644 --- a/yarn.lock +++ b/yarn.lock @@ -560,12 +560,12 @@ typescript "^4.9.5" wireit "^0.9.5" -"@salesforce/kit@^3.0.14": - version "3.0.14" - resolved "https://registry.yarnpkg.com/@salesforce/kit/-/kit-3.0.14.tgz#b2231c856531eca86fa2d9779c7852c820d7c3ed" - integrity sha512-NkUlztz+ArRrUKexIxVOlBJxIovjCuyk3SbW7mQv2klwp/OH/HoR6PDZU0gxLBglum/Dd4BgB+HN2FNqg8jmoQ== +"@salesforce/kit@^3.0.15": + version "3.0.15" + resolved "https://registry.yarnpkg.com/@salesforce/kit/-/kit-3.0.15.tgz#713df3f5767f874c70a2e731c7cb5ba677989559" + integrity sha512-XkA8jsuLvVnyP460dAbU3pBFP2IkmmmsVxMQVifcKKbNWaIBbZBzAfj+vdaQfnvZyflLhsrFT3q2xkb0vHouPg== dependencies: - "@salesforce/ts-types" "^2.0.8" + "@salesforce/ts-types" "^2.0.9" tslib "^2.6.2" "@salesforce/prettier-config@^0.0.3": @@ -594,6 +594,13 @@ dependencies: tslib "^2.6.2" +"@salesforce/ts-types@^2.0.9": + version "2.0.9" + resolved "https://registry.yarnpkg.com/@salesforce/ts-types/-/ts-types-2.0.9.tgz#66bff7b41720065d6b01631b6f6a3ccca02857c5" + integrity sha512-boUD9jw5vQpTCPCCmK/NFTWjSuuW+lsaxOynkyNXLW+zxOc4GDjhtKc4j0vWZJQvolpafbyS8ZLFHZJvs12gYA== + dependencies: + tslib "^2.6.2" + "@sinonjs/commons@^1", "@sinonjs/commons@^1.3.0", "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.8.1": version "1.8.3" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" From 02bc9d6c13fcdf132c13de920b3bc13d3c22372b Mon Sep 17 00:00:00 2001 From: mshanemc Date: Tue, 24 Oct 2023 08:47:39 -0500 Subject: [PATCH 52/67] refactor: remove barrel files --- src/config/authInfoConfig.ts | 2 +- src/config/sandboxProcessCache.ts | 2 +- src/deviceOauthService.ts | 3 ++- src/exported.ts | 4 ++-- src/org/authInfo.ts | 2 +- src/org/authRemover.ts | 4 ++-- src/org/index.ts | 13 ------------- src/org/org.ts | 2 +- src/org/scratchOrgCreate.ts | 2 +- src/stateAggregator/accessors/orgAccessor.ts | 2 +- src/stateAggregator/accessors/sandboxAccessor.ts | 2 +- src/stateAggregator/index.ts | 10 ---------- src/testSetup.ts | 7 +++++-- src/webOAuthServer.ts | 2 +- test/unit/deviceOauthServiceTest.ts | 3 ++- test/unit/org/authInfoTest.ts | 7 +++++-- test/unit/org/authRemoverTest.ts | 4 ++-- test/unit/org/connectionTest.ts | 2 +- test/unit/org/orgTest.ts | 15 +++++---------- test/unit/org/scratchOrgCreateTest.ts | 3 ++- test/unit/org/scratchOrgInfoApiTest.ts | 4 +++- test/unit/org/scratchOrgInfoGeneratorTest.ts | 3 ++- test/unit/org/scratchOrgSettingsGeneratorTest.ts | 3 ++- test/unit/org/userTest.ts | 6 +++++- .../accessors/aliasAccessorTest.ts | 3 ++- .../stateAggregator/accessors/orgAccessorTest.ts | 4 ++-- .../accessors/sandboxAccessorTest.ts | 2 +- test/unit/status/streamingClientTest.ts | 2 +- 28 files changed, 54 insertions(+), 64 deletions(-) delete mode 100644 src/org/index.ts delete mode 100644 src/stateAggregator/index.ts diff --git a/src/config/authInfoConfig.ts b/src/config/authInfoConfig.ts index 9ce5abcde4..f901f6e32c 100644 --- a/src/config/authInfoConfig.ts +++ b/src/config/authInfoConfig.ts @@ -5,7 +5,7 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import { AuthFields } from '../org'; +import { AuthFields } from '../org/authInfo'; import { ConfigFile } from './configFile'; /** diff --git a/src/config/sandboxProcessCache.ts b/src/config/sandboxProcessCache.ts index 9a36d28ffa..56dac4e790 100644 --- a/src/config/sandboxProcessCache.ts +++ b/src/config/sandboxProcessCache.ts @@ -5,7 +5,7 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ import { Duration } from '@salesforce/kit'; -import { SandboxProcessObject, SandboxRequest } from '../org'; +import { SandboxProcessObject, SandboxRequest } from '../org/org'; import { Global } from '../global'; import { TTLConfig } from './ttlConfig'; diff --git a/src/deviceOauthService.ts b/src/deviceOauthService.ts index e565c64620..fdd7733381 100644 --- a/src/deviceOauthService.ts +++ b/src/deviceOauthService.ts @@ -13,7 +13,8 @@ import { HttpRequest, OAuth2Config } from 'jsforce'; import { ensureString, JsonMap, Nullable } from '@salesforce/ts-types'; import * as FormData from 'form-data'; import { Logger } from './logger/logger'; -import { AuthInfo, DEFAULT_CONNECTED_APP_INFO, SFDX_HTTP_HEADERS } from './org'; +import { AuthInfo, DEFAULT_CONNECTED_APP_INFO } from './org/authInfo'; +import { SFDX_HTTP_HEADERS } from './org/connection'; import { SfError } from './sfError'; import { Messages } from './messages'; diff --git a/src/exported.ts b/src/exported.ts index f49904ac17..3ba065e927 100644 --- a/src/exported.ts +++ b/src/exported.ts @@ -18,7 +18,7 @@ export { envVars, EnvironmentVariable, SUPPORTED_ENV_VARS, EnvVars } from './con export { ConfigStore } from './config/configStore'; export { ConfigEntry, ConfigContents, ConfigValue } from './config/configStackTypes'; -export { StateAggregator } from './stateAggregator'; +export { StateAggregator } from './stateAggregator/stateAggregator'; export { DeviceOauthService, DeviceCodeResponse, DeviceCodePollingResponse } from './deviceOauthService'; @@ -70,7 +70,7 @@ export { OrgTypes, ResultEvent, ScratchOrgRequest, -} from './org'; +} from './org/org'; export { OrgConfigProperties, ORG_CONFIG_ALLOWED_PROPERTIES } from './org/orgConfigProperties'; diff --git a/src/org/authInfo.ts b/src/org/authInfo.ts index 26f7d1b60d..262bf1f6a1 100644 --- a/src/org/authInfo.ts +++ b/src/org/authInfo.ts @@ -34,7 +34,7 @@ import { ConfigAggregator } from '../config/configAggregator'; import { Logger } from '../logger/logger'; import { SfError } from '../sfError'; import { matchesAccessToken, trimTo15 } from '../util/sfdc'; -import { StateAggregator } from '../stateAggregator'; +import { StateAggregator } from '../stateAggregator/stateAggregator'; import { Messages } from '../messages'; import { getLoginAudienceCombos, SfdcUrl } from '../util/sfdcUrl'; import { Connection, SFDX_HTTP_HEADERS } from './connection'; diff --git a/src/org/authRemover.ts b/src/org/authRemover.ts index e966fcdc3a..1b2a3e211a 100644 --- a/src/org/authRemover.ts +++ b/src/org/authRemover.ts @@ -10,9 +10,9 @@ import { JsonMap } from '@salesforce/ts-types'; import { ConfigAggregator } from '../config/configAggregator'; import { Logger } from '../logger/logger'; import { Messages } from '../messages'; -import { StateAggregator } from '../stateAggregator'; +import { StateAggregator } from '../stateAggregator/stateAggregator'; import { OrgConfigProperties } from './orgConfigProperties'; -import { AuthFields } from '.'; +import { AuthFields } from './authInfo'; Messages.importMessagesDirectory(__dirname); const messages = Messages.loadMessages('@salesforce/core', 'auth'); diff --git a/src/org/index.ts b/src/org/index.ts deleted file mode 100644 index 684f6b8d31..0000000000 --- a/src/org/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright (c) 2020, salesforce.com, inc. - * All rights reserved. - * Licensed under the BSD 3-Clause license. - * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ - -export * from './authInfo'; -export * from './authRemover'; -export * from './connection'; -export * from './org'; -export * from './permissionSetAssignment'; -export * from './user'; diff --git a/src/org/org.ts b/src/org/org.ts index 08a5880bb8..e434db4bcd 100644 --- a/src/org/org.ts +++ b/src/org/org.ts @@ -35,7 +35,7 @@ import { SfError } from '../sfError'; import { trimTo15 } from '../util/sfdc'; import { WebOAuthServer } from '../webOAuthServer'; import { Messages } from '../messages'; -import { StateAggregator } from '../stateAggregator'; +import { StateAggregator } from '../stateAggregator/stateAggregator'; import { PollingClient } from '../status/pollingClient'; import { StatusResult } from '../status/types'; import { Connection, SingleRecordQueryErrors } from './connection'; diff --git a/src/org/scratchOrgCreate.ts b/src/org/scratchOrgCreate.ts index 38c143ac37..ce2cc3e647 100644 --- a/src/org/scratchOrgCreate.ts +++ b/src/org/scratchOrgCreate.ts @@ -11,7 +11,7 @@ import { Logger } from '../logger/logger'; import { ConfigAggregator } from '../config/configAggregator'; import { OrgConfigProperties } from '../org/orgConfigProperties'; import { SfProject } from '../sfProject'; -import { StateAggregator } from '../stateAggregator'; +import { StateAggregator } from '../stateAggregator/stateAggregator'; import { Org } from './org'; import { authorizeScratchOrg, diff --git a/src/stateAggregator/accessors/orgAccessor.ts b/src/stateAggregator/accessors/orgAccessor.ts index 44b7428a40..21ecb7e394 100644 --- a/src/stateAggregator/accessors/orgAccessor.ts +++ b/src/stateAggregator/accessors/orgAccessor.ts @@ -11,7 +11,7 @@ import { Nullable, entriesOf } from '@salesforce/ts-types'; import { AsyncOptionalCreatable, isEmpty } from '@salesforce/kit'; import { AuthInfoConfig } from '../../config/authInfoConfig'; import { Global } from '../../global'; -import { AuthFields } from '../../org'; +import { AuthFields } from '../../org/authInfo'; import { ConfigFile } from '../../config/configFile'; import { ConfigContents } from '../../config/configStackTypes'; import { Logger } from '../../logger/logger'; diff --git a/src/stateAggregator/accessors/sandboxAccessor.ts b/src/stateAggregator/accessors/sandboxAccessor.ts index 69f756433d..ecdd4c744c 100644 --- a/src/stateAggregator/accessors/sandboxAccessor.ts +++ b/src/stateAggregator/accessors/sandboxAccessor.ts @@ -7,7 +7,7 @@ /* eslint-disable class-methods-use-this */ import { SandboxOrgConfig } from '../../config/sandboxOrgConfig'; -import { SandboxFields } from '../../org'; +import { SandboxFields } from '../../org/org'; import { BaseOrgAccessor } from './orgAccessor'; export class SandboxAccessor extends BaseOrgAccessor { diff --git a/src/stateAggregator/index.ts b/src/stateAggregator/index.ts deleted file mode 100644 index c064c03d09..0000000000 --- a/src/stateAggregator/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright (c) 2021, salesforce.com, inc. - * All rights reserved. - * Licensed under the BSD 3-Clause license. - * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ - -export * from './accessors/orgAccessor'; -export * from './accessors/aliasAccessor'; -export * from './stateAggregator'; diff --git a/src/testSetup.ts b/src/testSetup.ts index 2f95a0abe6..2ddbeb3968 100644 --- a/src/testSetup.ts +++ b/src/testSetup.ts @@ -39,8 +39,11 @@ import { SfError } from './sfError'; import { SfProject, SfProjectJson } from './sfProject'; import * as aliasAccessorEntireFile from './stateAggregator/accessors/aliasAccessor'; import { CometClient, CometSubscription, Message, StreamingExtension } from './status/streamingClient'; -import { OrgAccessor, StateAggregator } from './stateAggregator'; -import { AuthFields, Org, SandboxFields, User, UserFields } from './org'; +import { StateAggregator } from './stateAggregator/stateAggregator'; +import { OrgAccessor } from './stateAggregator/accessors/orgAccessor'; +import { Org, SandboxFields } from './org/org'; +import { AuthFields } from './org/authInfo'; +import { User, UserFields } from './org/user'; import { SandboxAccessor } from './stateAggregator/accessors/sandboxAccessor'; import { Global } from './global'; import { uniqid } from './util/uniqid'; diff --git a/src/webOAuthServer.ts b/src/webOAuthServer.ts index 9ba93cea60..10c1dd6f33 100644 --- a/src/webOAuthServer.ts +++ b/src/webOAuthServer.ts @@ -16,7 +16,7 @@ import { OAuth2 } from 'jsforce'; import { AsyncCreatable, Env, set, toNumber } from '@salesforce/kit'; import { asString, get, Nullable } from '@salesforce/ts-types'; import { Logger } from './logger/logger'; -import { AuthInfo, DEFAULT_CONNECTED_APP_INFO } from './org'; +import { AuthInfo, DEFAULT_CONNECTED_APP_INFO } from './org/authInfo'; import { SfError } from './sfError'; import { Messages } from './messages'; import { SfProjectJson } from './sfProject'; diff --git a/test/unit/deviceOauthServiceTest.ts b/test/unit/deviceOauthServiceTest.ts index bca1766a6c..60478daaa5 100644 --- a/test/unit/deviceOauthServiceTest.ts +++ b/test/unit/deviceOauthServiceTest.ts @@ -12,7 +12,8 @@ import { StubbedType, stubInterface, stubMethod } from '@salesforce/ts-sinon'; import { expect } from 'chai'; import { MockTestOrgData, TestContext } from '../../src/testSetup'; import { DeviceOauthService } from '../../src/deviceOauthService'; -import { AuthFields, AuthInfo, DEFAULT_CONNECTED_APP_INFO, SFDX_HTTP_HEADERS } from '../../src/org'; +import { AuthFields, AuthInfo, DEFAULT_CONNECTED_APP_INFO } from '../../src/org/authInfo'; +import { SFDX_HTTP_HEADERS } from '../../src/org/connection'; const deviceCodeResponse = { device_code: '1234', diff --git a/test/unit/org/authInfoTest.ts b/test/unit/org/authInfoTest.ts index 1979224ebe..e971e732cf 100644 --- a/test/unit/org/authInfoTest.ts +++ b/test/unit/org/authInfoTest.ts @@ -20,11 +20,14 @@ import { Transport } from 'jsforce/lib/transport'; import { OAuth2 } from 'jsforce'; import { SinonSpy, SinonStub, match } from 'sinon'; -import { AuthFields, AuthInfo, Org } from '../../../src/org'; +import { Org } from '../../../src/org/org'; +import { AuthFields, AuthInfo } from '../../../src/org/authInfo'; import { JwtOAuth2Config } from '../../../src/org/authInfo'; import { MockTestOrgData, shouldThrow, shouldThrowSync, TestContext } from '../../../src/testSetup'; import { OrgConfigProperties } from '../../../src/org/orgConfigProperties'; -import { AliasAccessor, OrgAccessor, StateAggregator } from '../../../src/stateAggregator'; +import { StateAggregator } from '../../../src/stateAggregator/stateAggregator'; +import { AliasAccessor } from '../../../src/stateAggregator/accessors/aliasAccessor'; +import { OrgAccessor } from '../../../src/stateAggregator/accessors/orgAccessor'; import { Crypto } from '../../../src/crypto/crypto'; import { Config } from '../../../src/config/config'; import { SfdcUrl } from '../../../src/util/sfdcUrl'; diff --git a/test/unit/org/authRemoverTest.ts b/test/unit/org/authRemoverTest.ts index ac696881bd..9a0cb62bcf 100644 --- a/test/unit/org/authRemoverTest.ts +++ b/test/unit/org/authRemoverTest.ts @@ -9,10 +9,10 @@ import { spyMethod } from '@salesforce/ts-sinon'; import { expect } from 'chai'; import { AuthRemover } from '../../../src/org/authRemover'; import { Config } from '../../../src/config/config'; -import { AliasAccessor } from '../../../src/stateAggregator'; +import { AliasAccessor } from '../../../src/stateAggregator/accessors/aliasAccessor'; import { MockTestOrgData, shouldThrow, TestContext } from '../../../src/testSetup'; import { OrgConfigProperties } from '../../../src/org/orgConfigProperties'; -import { AuthFields } from '../../../src/org'; +import { AuthFields } from '../../../src/org/authInfo'; import { SfError } from '../../../src/sfError'; describe('AuthRemover', () => { diff --git a/test/unit/org/connectionTest.ts b/test/unit/org/connectionTest.ts index 4944625d00..4b33d6a806 100644 --- a/test/unit/org/connectionTest.ts +++ b/test/unit/org/connectionTest.ts @@ -10,7 +10,7 @@ import { expect } from 'chai'; import { fromStub, StubbedType, stubInterface, stubMethod } from '@salesforce/ts-sinon'; import { Duration } from '@salesforce/kit'; import { Connection as JSForceConnection, HttpRequest } from 'jsforce'; -import { AuthInfo } from '../../../src/org'; +import { AuthInfo } from '../../../src/org/authInfo'; import { MyDomainResolver } from '../../../src/status/myDomainResolver'; import { Connection, DNS_ERROR_NAME, SFDX_HTTP_HEADERS, SingleRecordQueryErrors } from '../../../src/org/connection'; import { shouldThrow, shouldThrowSync, TestContext } from '../../../src/testSetup'; diff --git a/test/unit/org/orgTest.ts b/test/unit/org/orgTest.ts index 76f468d03d..e4d3e5f28a 100644 --- a/test/unit/org/orgTest.ts +++ b/test/unit/org/orgTest.ts @@ -16,15 +16,10 @@ import { assert, expect, config as chaiConfig } from 'chai'; import { OAuth2 } from 'jsforce'; import { Transport } from 'jsforce/lib/transport'; import { SinonSpy, SinonStub } from 'sinon'; -import { - AuthInfo, - Connection, - Org, - SandboxEvents, - SandboxProcessObject, - SandboxUserAuthResponse, - SingleRecordQueryErrors, -} from '../../../src/org'; +import { Org, SandboxEvents, SandboxProcessObject, SandboxUserAuthResponse } from '../../../src/org/org'; +import { AuthInfo } from '../../../src/org/authInfo'; +import {} from '../../../src/org/connection'; +import { Connection, SingleRecordQueryErrors } from '../../../src/org/connection'; import { Config } from '../../../src/config/config'; import { ConfigAggregator } from '../../../src/config/configAggregator'; import { ConfigFile } from '../../../src/config/configFile'; @@ -32,7 +27,7 @@ import { OrgUsersConfig } from '../../../src/config/orgUsersConfig'; import { Global } from '../../../src/global'; import { MockTestOrgData, shouldThrow, shouldThrowSync, TestContext } from '../../../src/testSetup'; import { MyDomainResolver } from '../../../src/status/myDomainResolver'; -import { StateAggregator } from '../../../src/stateAggregator'; +import { StateAggregator } from '../../../src/stateAggregator/stateAggregator'; import { OrgConfigProperties } from '../../../src/org/orgConfigProperties'; import { Messages } from '../../../src/messages'; import { SfError } from '../../../src/sfError'; diff --git a/test/unit/org/scratchOrgCreateTest.ts b/test/unit/org/scratchOrgCreateTest.ts index 56912f564a..d47d6606b3 100644 --- a/test/unit/org/scratchOrgCreateTest.ts +++ b/test/unit/org/scratchOrgCreateTest.ts @@ -8,7 +8,8 @@ import { expect } from 'chai'; import * as sinon from 'sinon'; import { Duration } from '@salesforce/kit'; import { Connection } from 'jsforce'; -import { AuthInfo, Org } from '../../../src/org'; +import { Org } from '../../../src/org/org'; +import { AuthInfo } from '../../../src/org/authInfo'; import { SfProject, SfProjectJson } from '../../../src/sfProject'; import { scratchOrgCreate, ScratchOrgCreateOptions, scratchOrgResume } from '../../../src/org/scratchOrgCreate'; import { ScratchOrgCache } from '../../../src/org/scratchOrgCache'; diff --git a/test/unit/org/scratchOrgInfoApiTest.ts b/test/unit/org/scratchOrgInfoApiTest.ts index 80f6b81193..06c03d38dc 100644 --- a/test/unit/org/scratchOrgInfoApiTest.ts +++ b/test/unit/org/scratchOrgInfoApiTest.ts @@ -10,7 +10,9 @@ import * as sinon from 'sinon'; import { assert, expect } from 'chai'; import { Duration, env } from '@salesforce/kit'; import { stubMethod } from '@salesforce/ts-sinon'; -import { AuthInfo, Connection, Org } from '../../../src/org'; +import { Org } from '../../../src/org/org'; +import { Connection } from '../../../src/org/connection'; +import { AuthInfo } from '../../../src/org/authInfo'; import { shouldThrow } from '../../../src/testSetup'; import { MyDomainResolver } from '../../../src/status/myDomainResolver'; import SettingsGenerator from '../../../src/org/scratchOrgSettingsGenerator'; diff --git a/test/unit/org/scratchOrgInfoGeneratorTest.ts b/test/unit/org/scratchOrgInfoGeneratorTest.ts index 186a1da9b6..52e2e3f0bf 100644 --- a/test/unit/org/scratchOrgInfoGeneratorTest.ts +++ b/test/unit/org/scratchOrgInfoGeneratorTest.ts @@ -10,7 +10,8 @@ import * as sinon from 'sinon'; import { expect } from 'chai'; import { stubMethod, stubInterface } from '@salesforce/ts-sinon'; import { MockTestOrgData, shouldThrow, TestContext } from '../../../src/testSetup'; -import { Org, Connection } from '../../../src/org'; +import { Org } from '../../../src/org/org'; +import { Connection } from '../../../src/org/connection'; import { ScratchOrgInfoPayload, getAncestorIds, diff --git a/test/unit/org/scratchOrgSettingsGeneratorTest.ts b/test/unit/org/scratchOrgSettingsGeneratorTest.ts index f198fe9b60..eb33424623 100644 --- a/test/unit/org/scratchOrgSettingsGeneratorTest.ts +++ b/test/unit/org/scratchOrgSettingsGeneratorTest.ts @@ -8,7 +8,8 @@ import * as path from 'path'; import * as sinon from 'sinon'; import { expect } from 'chai'; -import { Org, Connection } from '../../../src/org'; +import { Org } from '../../../src/org/org'; +import { Connection } from '../../../src/org/connection'; import { validateApiVersion } from '../../../src/util/sfdc'; import { ZipWriter } from '../../../src/util/zipWriter'; import { ScratchOrgInfo } from '../../../src/org/scratchOrgTypes'; diff --git a/test/unit/org/userTest.ts b/test/unit/org/userTest.ts index 9ebd4c1a94..22b7a36cb4 100644 --- a/test/unit/org/userTest.ts +++ b/test/unit/org/userTest.ts @@ -9,7 +9,11 @@ import { AnyJson, isString } from '@salesforce/ts-types'; import { expect } from 'chai'; import { SecureBuffer } from '../../../src/crypto/secureBuffer'; import { MockTestOrgData, shouldThrow, shouldThrowSync, TestContext } from '../../../src/testSetup'; -import { DefaultUserFields, User, PermissionSetAssignment, Org, Connection, AuthInfo } from '../../../src/org'; +import { Org } from '../../../src/org/org'; +import { DefaultUserFields, User } from '../../../src/org/user'; +import { AuthInfo } from '../../../src/org/authInfo'; +import { Connection } from '../../../src/org/connection'; +import { PermissionSetAssignment } from '../../../src/org/permissionSetAssignment'; describe('User Tests', () => { const $$ = new TestContext(); diff --git a/test/unit/stateAggregator/accessors/aliasAccessorTest.ts b/test/unit/stateAggregator/accessors/aliasAccessorTest.ts index ef3754915a..b6ba320930 100644 --- a/test/unit/stateAggregator/accessors/aliasAccessorTest.ts +++ b/test/unit/stateAggregator/accessors/aliasAccessorTest.ts @@ -9,7 +9,8 @@ import { existsSync } from 'node:fs'; import { rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { expect } from 'chai'; -import { FILENAME, StateAggregator } from '../../../../src/stateAggregator'; +import { StateAggregator } from '../../../../src/stateAggregator/stateAggregator'; +import { FILENAME } from '../../../../src/stateAggregator/accessors/aliasAccessor'; import { MockTestOrgData, TestContext } from '../../../../src/testSetup'; import { Global } from '../../../../src/global'; import { uniqid } from '../../../../src/util/uniqid'; diff --git a/test/unit/stateAggregator/accessors/orgAccessorTest.ts b/test/unit/stateAggregator/accessors/orgAccessorTest.ts index a1a7544681..37fd5c9a9a 100644 --- a/test/unit/stateAggregator/accessors/orgAccessorTest.ts +++ b/test/unit/stateAggregator/accessors/orgAccessorTest.ts @@ -5,8 +5,8 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ import { expect } from 'chai'; -import { StateAggregator } from '../../../../src/stateAggregator'; -import { AuthFields } from '../../../../src/org'; +import { StateAggregator } from '../../../../src/stateAggregator/stateAggregator'; +import { AuthFields } from '../../../../src/org/authInfo'; import { MockTestOrgData, shouldThrowSync, TestContext } from '../../../../src/testSetup'; import { uniqid } from '../../../../src/util/uniqid'; diff --git a/test/unit/stateAggregator/accessors/sandboxAccessorTest.ts b/test/unit/stateAggregator/accessors/sandboxAccessorTest.ts index 299a11e336..da39794513 100644 --- a/test/unit/stateAggregator/accessors/sandboxAccessorTest.ts +++ b/test/unit/stateAggregator/accessors/sandboxAccessorTest.ts @@ -5,7 +5,7 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ import { expect } from 'chai'; -import { StateAggregator } from '../../../../src/stateAggregator'; +import { StateAggregator } from '../../../../src/stateAggregator/stateAggregator'; import { MockTestOrgData, MockTestSandboxData, TestContext } from '../../../../src/testSetup'; import { uniqid } from '../../../../src/util/uniqid'; diff --git a/test/unit/status/streamingClientTest.ts b/test/unit/status/streamingClientTest.ts index 1ba42851b1..2b9890cd79 100644 --- a/test/unit/status/streamingClientTest.ts +++ b/test/unit/status/streamingClientTest.ts @@ -10,7 +10,7 @@ import { SinonSpyCall } from 'sinon'; import { Duration } from '@salesforce/kit'; import { get, JsonMap } from '@salesforce/ts-types'; import { CometClient, StatusResult, StreamingClient } from '../../../src/status/streamingClient'; -import { Connection } from '../../../src/org'; +import { Connection } from '../../../src/org/connection'; // import { Crypto } from '../../../src/crypto/crypto'; import { Org } from '../../../src/org/org'; import { SfError } from '../../../src/sfError'; From d2e31b4a186bcd5e435233273b0e36c3afeeabed Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 25 Oct 2023 08:49:23 -0500 Subject: [PATCH 53/67] refactor: example imports and shared timestamp fn --- src/config/configStore.ts | 9 ++++- src/config/lwwRegister.ts | 8 +++- src/testSetup.ts | 2 +- src/util/time.ts | 9 +++++ test/unit/config/configTest.ts | 2 +- test/unit/config/lwwMapTest.ts | 37 ++++++++++--------- typedocExamples/orgExamples.ts | 2 +- .../status/streamingClientExamples.ts | 2 +- typedocExamples/userExamples.ts | 2 +- 9 files changed, 47 insertions(+), 26 deletions(-) create mode 100644 src/util/time.ts diff --git a/src/config/configStore.ts b/src/config/configStore.ts index 046c553692..389b846e37 100644 --- a/src/config/configStore.ts +++ b/src/config/configStore.ts @@ -10,6 +10,7 @@ import { entriesOf, isPlainObject } from '@salesforce/ts-types'; import { definiteEntriesOf, definiteValuesOf, isJsonMap, isString, JsonMap, Optional } from '@salesforce/ts-types'; import { Crypto } from '../crypto/crypto'; import { SfError } from '../sfError'; +import { nowBigInt } from '../util/time'; import { LWWMap, stateFromContents } from './lwwMap'; import { ConfigContents, ConfigEntry, ConfigValue, Key } from './configStackTypes'; @@ -265,8 +266,12 @@ export abstract class BaseConfigStore< }); } - protected setContentsFromFileContents(contents: P, timestamp: bigint): void { - const state = stateFromContents(contents, timestamp); + /** Keep ConfigFile concurrency-friendly. + * Avoid using this unless you're reading the file for the first time + * and guaranteed to no be cross-saving existing contents + * */ + protected setContentsFromFileContents(contents: P, timestamp?: bigint): void { + const state = stateFromContents(contents, timestamp ?? nowBigInt()); this.contents = new LWWMap

(state); } diff --git a/src/config/lwwRegister.ts b/src/config/lwwRegister.ts index 0e164afbf0..9e63ee97c9 100644 --- a/src/config/lwwRegister.ts +++ b/src/config/lwwRegister.ts @@ -5,6 +5,8 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ +import { nowBigInt } from '../util/time'; + type LWWRegisterState = { timestamp: bigint; value: T }; /** a CRDT implementation. Uses timestamps to resolve conflicts when updating the value (last write wins) @@ -19,8 +21,12 @@ export class LWWRegister { return this.state.value; } + public get timestamp(): bigint { + return this.state.timestamp; + } + public set(value: T): void { - this.state = { timestamp: process.hrtime.bigint(), value }; + this.state = { timestamp: nowBigInt(), value }; } public merge(incoming: LWWRegisterState): LWWRegisterState { diff --git a/src/testSetup.ts b/src/testSetup.ts index 2ddbeb3968..87f13a4cc4 100644 --- a/src/testSetup.ts +++ b/src/testSetup.ts @@ -527,7 +527,7 @@ export const stubContext = (testContext: TestContext): Record const readSync = function (this: ConfigFile, newContents?: JsonMap): JsonMap { const stub = initStubForRead(this); - this.setContentsFromFileContents(newContents ?? stub.contents ?? {}, BigInt(Date.now())); + this.setContentsFromFileContents(newContents ?? stub.contents ?? {}); return this.getContents(); }; diff --git a/src/util/time.ts b/src/util/time.ts new file mode 100644 index 0000000000..f61cea95ef --- /dev/null +++ b/src/util/time.ts @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import { performance } from 'node:perf_hooks'; + +export const nowBigInt = (): bigint => BigInt((performance.now() + performance.timeOrigin) * 1_000_000); diff --git a/test/unit/config/configTest.ts b/test/unit/config/configTest.ts index ccd73f1878..0c0b27750b 100644 --- a/test/unit/config/configTest.ts +++ b/test/unit/config/configTest.ts @@ -281,7 +281,7 @@ describe('Config', () => { stubMethod($$.SANDBOX, ConfigFile.prototype, ConfigFile.prototype.read.name).callsFake(async function () { // @ts-expect-error -> this is any // eslint-disable-next-line @typescript-eslint/no-unsafe-call - this.setContentsFromFileContents({ unknown: 'unknown config key and value' }, BigInt(Date.now())); + this.setContentsFromFileContents({ unknown: 'unknown config key and value' }); }); const config = await Config.create({ isGlobal: true }); diff --git a/test/unit/config/lwwMapTest.ts b/test/unit/config/lwwMapTest.ts index ba7c12b36b..316589fc9a 100644 --- a/test/unit/config/lwwMapTest.ts +++ b/test/unit/config/lwwMapTest.ts @@ -6,16 +6,17 @@ */ import { expect, config } from 'chai'; import { LWWMap, LWWState, SYMBOL_FOR_DELETED } from '../../../src/config/lwwMap'; +import { nowBigInt } from '../../../src/util/time'; config.truncateThreshold = 0; -const OLD_TIMESTAMP_OFFSET = BigInt(10_000_000_000_000); +const TIMESTAMP_OFFSET = BigInt(1_000_000); describe('LWWMap', () => { type TestType = { foo: string; baz: string; opt?: number; optNull?: null }; describe('all properties are known', () => { const state = { - foo: { value: 'bar', timestamp: process.hrtime.bigint() }, - baz: { value: 'qux', timestamp: process.hrtime.bigint() }, + foo: { value: 'bar', timestamp: nowBigInt() }, + baz: { value: 'qux', timestamp: nowBigInt() }, }; let lwwMap: LWWMap; @@ -77,8 +78,8 @@ describe('LWWMap', () => { }); it('all are updated', () => { const remoteState = { - foo: { value: 'bar2', timestamp: process.hrtime.bigint() }, - baz: { value: 'qux2', timestamp: process.hrtime.bigint() }, + foo: { value: 'bar2', timestamp: nowBigInt() }, + baz: { value: 'qux2', timestamp: nowBigInt() }, } satisfies LWWState; lwwMap.merge(remoteState); expect(lwwMap.state).to.deep.equal(remoteState); @@ -86,8 +87,8 @@ describe('LWWMap', () => { }); it('all are deleted', () => { const remoteState = { - foo: { value: SYMBOL_FOR_DELETED, timestamp: process.hrtime.bigint() }, - baz: { value: SYMBOL_FOR_DELETED, timestamp: process.hrtime.bigint() }, + foo: { value: SYMBOL_FOR_DELETED, timestamp: nowBigInt() }, + baz: { value: SYMBOL_FOR_DELETED, timestamp: nowBigInt() }, } satisfies LWWState; lwwMap.merge(remoteState); expect(lwwMap.state).to.deep.equal(remoteState); @@ -95,8 +96,8 @@ describe('LWWMap', () => { }); it('none are updated', () => { const remoteState = { - foo: { value: 'bar2', timestamp: process.hrtime.bigint() - OLD_TIMESTAMP_OFFSET }, - baz: { value: 'qux2', timestamp: process.hrtime.bigint() - OLD_TIMESTAMP_OFFSET }, + foo: { value: 'bar2', timestamp: nowBigInt() - TIMESTAMP_OFFSET }, + baz: { value: 'qux2', timestamp: nowBigInt() - TIMESTAMP_OFFSET }, } satisfies LWWState; lwwMap.merge(remoteState); expect(lwwMap.state).to.deep.equal(state); @@ -105,10 +106,10 @@ describe('LWWMap', () => { it('one is update, one is added, one is deleted', () => { lwwMap.set('opt', 4); const remoteState = { - foo: { value: 'bar2', timestamp: process.hrtime.bigint() }, - baz: { value: 'qux2', timestamp: process.hrtime.bigint() - OLD_TIMESTAMP_OFFSET }, - opt: { value: SYMBOL_FOR_DELETED, timestamp: process.hrtime.bigint() }, - optNull: { value: null, timestamp: process.hrtime.bigint() }, + foo: { value: 'bar2', timestamp: nowBigInt() }, + baz: { value: 'qux2', timestamp: nowBigInt() - TIMESTAMP_OFFSET }, + opt: { value: SYMBOL_FOR_DELETED, timestamp: nowBigInt() + TIMESTAMP_OFFSET }, + optNull: { value: null, timestamp: nowBigInt() }, } satisfies LWWState; lwwMap.merge(remoteState); expect(lwwMap.get('foo')).to.equal('bar2'); @@ -122,8 +123,8 @@ describe('LWWMap', () => { describe('open-ended objects', () => { type OpenEndedType = TestType & { [key: string]: string }; const initialState = { - foo: { value: 'bar', timestamp: process.hrtime.bigint() }, - baz: { value: 'qux', timestamp: process.hrtime.bigint() }, + foo: { value: 'bar', timestamp: nowBigInt() }, + baz: { value: 'qux', timestamp: nowBigInt() }, } satisfies LWWState; it('set a new prop', () => { @@ -155,9 +156,9 @@ describe('LWWMap', () => { }); describe('nested objects', () => { const state = { - foo: { value: 'bar', timestamp: process.hrtime.bigint() }, - baz: { value: 'qux', timestamp: process.hrtime.bigint() }, - obj: { value: { a: 1, b: 2, c: 3 }, timestamp: process.hrtime.bigint() }, + foo: { value: 'bar', timestamp: nowBigInt() }, + baz: { value: 'qux', timestamp: nowBigInt() }, + obj: { value: { a: 1, b: 2, c: 3 }, timestamp: nowBigInt() }, }; let lwwMap: LWWMap<{ foo: string; baz: string; opt?: number; optNull?: null }>; diff --git a/typedocExamples/orgExamples.ts b/typedocExamples/orgExamples.ts index 2879f5f6ad..1e7f911af2 100644 --- a/typedocExamples/orgExamples.ts +++ b/typedocExamples/orgExamples.ts @@ -1,6 +1,6 @@ import { AuthInfo } from '../src/org/authInfo'; import { Connection } from '../src/org/connection'; -import { Org } from '../src/org'; +import { Org } from '../src/org/org'; // tslint:disable export const orgExamples = { diff --git a/typedocExamples/status/streamingClientExamples.ts b/typedocExamples/status/streamingClientExamples.ts index 72efabc54c..adf18da5f5 100644 --- a/typedocExamples/status/streamingClientExamples.ts +++ b/typedocExamples/status/streamingClientExamples.ts @@ -1,5 +1,5 @@ import { ensureJsonMap, ensureString, JsonCollection, JsonMap } from '@salesforce/ts-types'; -import { Org } from '../../src/org'; +import { Org } from '../../src/org/org'; import { StreamingClient, StatusResult } from '../../src/status/streamingClient'; import { HttpRequest } from 'jsforce'; diff --git a/typedocExamples/userExamples.ts b/typedocExamples/userExamples.ts index dc6545d938..05c6cb2c7e 100644 --- a/typedocExamples/userExamples.ts +++ b/typedocExamples/userExamples.ts @@ -1,5 +1,5 @@ import { DefaultUserFields, User, UserFields } from '../src/org/user'; -import { Org } from '../src/org'; +import { Org } from '../src/org/org'; import { Connection } from '../src/org/connection'; import { AuthInfo } from '../src/org/authInfo'; From ead7330269a8f3121a4b4e6129c84a79825a26d7 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 25 Oct 2023 10:58:27 -0500 Subject: [PATCH 54/67] test: concurrency tests in configFile --- src/config/configFile.ts | 18 +- src/config/lwwMap.ts | 28 ++- .../unit/config/configFileConcurrency.test.ts | 193 ++++++++++++++++++ test/unit/config/lwwMapTest.ts | 43 ++-- test/unit/org/orgTest.ts | 3 +- 5 files changed, 252 insertions(+), 33 deletions(-) create mode 100644 test/unit/config/configFileConcurrency.test.ts diff --git a/src/config/configFile.ts b/src/config/configFile.ts index a6d10bdbe0..8349159f01 100644 --- a/src/config/configFile.ts +++ b/src/config/configFile.ts @@ -8,7 +8,7 @@ import * as fs from 'fs'; import { constants as fsConstants, Stats as fsStats } from 'fs'; import { homedir as osHomedir } from 'os'; -import { dirname as pathDirname, join as pathJoin } from 'path'; +import { basename, dirname as pathDirname, join as pathJoin } from 'path'; import { lock, lockSync } from 'proper-lockfile'; import { parseJsonMap } from '@salesforce/kit'; import { Global } from '../global'; @@ -179,7 +179,7 @@ export class ConfigFile< this.hasRead = true; if ((err as SfError).code === 'ENOENT') { if (!throwOnNotFound) { - this.setContentsFromFileContents({} as P, process.hrtime.bigint()); + this.setContentsFromFileContents({} as P); return this.getContents(); } } @@ -215,7 +215,7 @@ export class ConfigFile< this.hasRead = true; if ((err as SfError).code === 'ENOENT') { if (!throwOnNotFound) { - this.setContentsFromFileContents({} as P, process.hrtime.bigint()); + this.setContentsFromFileContents({} as P); return this.getContents(); } } @@ -244,7 +244,16 @@ export class ConfigFile< let unlockFn: (() => Promise) | undefined; // lock the file. Returns an unlock function to call when done. try { - unlockFn = await lock(this.getPath(), lockRetryOptions); + if (await this.exists()) { + unlockFn = await lock(this.getPath(), lockRetryOptions); + } else { + // lock the entire directory to keep others from trying to create the file while we are + unlockFn = await lock(basename(this.getPath()), lockRetryOptions); + this.logger.debug( + `No file found at ${this.getPath()}. Write will create it. Locking the entire directory until file is written.` + ); + } + // get the file modstamp. Do this after the lock acquisition in case the file is being written to. const fileTimestamp = (await fs.promises.stat(this.getPath(), { bigint: true })).mtimeNs; @@ -253,6 +262,7 @@ export class ConfigFile< parseJsonMap

(await fs.promises.readFile(this.getPath(), 'utf8'), this.getPath()), fileTimestamp ); + // merge the new contents into the in-memory LWWMap this.contents.merge(stateFromFile); } catch (err) { diff --git a/src/config/lwwMap.ts b/src/config/lwwMap.ts index 431452878f..0cd5b4d258 100644 --- a/src/config/lwwMap.ts +++ b/src/config/lwwMap.ts @@ -6,6 +6,7 @@ */ import { entriesOf } from '@salesforce/ts-types'; +import { nowBigInt } from '../util/time'; import { LWWRegister } from './lwwRegister'; import { ConfigContents, Key } from './configStackTypes'; @@ -19,15 +20,18 @@ export type LWWState

= { * @param contents object aligning with ConfigContents * @param timestamp a bigInt that sets the timestamp. Defaults to the current time * construct a LWWState from an object - * + * @param keysToCheckForDeletion if a key is in this array, AND not in the contents, it will be marked as deleted * */ -export const stateFromContents =

( - contents: P, - timestamp = process.hrtime.bigint() -): LWWState

=> +export const stateFromContents =

(contents: P, timestamp?: bigint): LWWState

=> Object.fromEntries( - entriesOf(contents).map(([key, value]) => [key, new LWWRegister({ timestamp, value })]) - ) as unknown as LWWState

; + entriesOf(contents).map( + ([key, value]): [keyof P, LWWRegister['state']] => [ + key, + new LWWRegister({ timestamp: timestamp ?? nowBigInt(), value }), + ] + ) + // I'd love to get rid of this ASsertion but don't know how. + ) as LWWState

; export class LWWMap

{ /** map of key to LWWRegister. Used for managing conflicts */ @@ -56,7 +60,10 @@ export class LWWMap

{ ) as LWWState

; } + // Merge top-level properties of the incoming state with the current state. + // The value with the latest timestamp wins. public merge(state: LWWState

): LWWState

{ + // properties that are in the incoming state but not the current state might have been deleted. // recursively merge each key's register with the incoming state for that key for (const [key, remote] of entriesOf(state)) { const local = this.#data.get(key); @@ -75,7 +82,7 @@ export class LWWMap

{ // if the register already exists, set the value if (register) register.set(value); // otherwise, instantiate a new `LWWRegister` with the value - else this.#data.set(key, new LWWRegister({ timestamp: process.hrtime.bigint(), value })); + else this.#data.set(key, new LWWRegister({ timestamp: nowBigInt(), value })); } public get>(key: K): P[K] | undefined { @@ -88,7 +95,10 @@ export class LWWMap

{ public delete>(key: K): void { this.#data.set( key, - new LWWRegister({ timestamp: process.hrtime.bigint(), value: SYMBOL_FOR_DELETED }) + new LWWRegister({ + timestamp: nowBigInt(), + value: SYMBOL_FOR_DELETED, + }) ); } diff --git a/test/unit/config/configFileConcurrency.test.ts b/test/unit/config/configFileConcurrency.test.ts new file mode 100644 index 0000000000..f9f47ef29e --- /dev/null +++ b/test/unit/config/configFileConcurrency.test.ts @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import { join } from 'node:path'; +import { tmpdir } from 'node:os'; +import { rm } from 'node:fs/promises'; +import { expect } from 'chai'; +import { sleep } from '@salesforce/kit'; +import { ConfigFile } from '../../../src/exported'; + +const FILENAME = 'concurrency.json'; +const sharedLocation = join('sfdx-core-ut', 'test', 'configFile'); + +class TestConfig extends ConfigFile { + public static getOptions( + filename: string, + isGlobal: boolean, + isState?: boolean, + filePath?: string + ): ConfigFile.Options { + return { + rootFolder: tmpdir(), + filename, + isGlobal, + isState, + filePath, + }; + } + + public static getFileName() { + return FILENAME; + } +} + +/* file and node - clock timestamps aren't precise enough to run in a UT. + * the goal of this and the `sleep` is to put a bit of space between operations + * to simulate real-world concurrency where it's unlikely to hit the same ms + */ +const SLEEP_FUDGE_MS = 5; + +describe('concurrency', () => { + beforeEach(async () => { + await rm(join(tmpdir(), '.sfdx', 'sfdx-core-ut'), { recursive: true, force: true }); + }); + afterEach(async () => { + await rm(join(tmpdir(), '.sfdx', 'sfdx-core-ut'), { recursive: true, force: true }); + }); + + it('merges in new props from file saved since a prop was set in memory', async () => { + const config1 = new TestConfig(TestConfig.getOptions(FILENAME, true, true, sharedLocation)); + config1.set('x', 0); + await sleep(SLEEP_FUDGE_MS); + const config2 = new TestConfig(TestConfig.getOptions(FILENAME, true, true, sharedLocation)); + config2.set('x', 1); + await config2.write(); + + const c1output = await config1.write(); + expect(c1output.x).to.equal(1); + }); + + it('newer props beat older files', async () => { + const config1 = new TestConfig(TestConfig.getOptions(FILENAME, true, true, sharedLocation)); + + const config2 = new TestConfig(TestConfig.getOptions(FILENAME, true, true, sharedLocation)); + config2.set('x', 1); + await config2.write(); + await sleep(SLEEP_FUDGE_MS); + + config1.set('x', 0); + await sleep(SLEEP_FUDGE_MS); + + const c1output = await config1.write(); + expect(c1output.x).to.equal(0); + }); + + it('"deleted" (missing) props in a file do not delete from contents"', async () => { + const config1 = new TestConfig(TestConfig.getOptions(FILENAME, true, true, sharedLocation)); + config1.set('x', 0); + await sleep(SLEEP_FUDGE_MS); + + const config2 = new TestConfig(TestConfig.getOptions(FILENAME, true, true, sharedLocation)); + config2.set('x', 1); + config2.unset('x'); + await sleep(SLEEP_FUDGE_MS); + + await config2.write(); + await sleep(SLEEP_FUDGE_MS); + + const c1output = await config1.write(); + expect(c1output.x).to.equal(0); + }); + + it('newer deleted props beat older files', async () => { + const config1 = new TestConfig(TestConfig.getOptions(FILENAME, true, true, sharedLocation)); + config1.set('x', 0); + await sleep(SLEEP_FUDGE_MS); + + const config2 = new TestConfig(TestConfig.getOptions(FILENAME, true, true, sharedLocation)); + config2.set('x', 1); + + await config2.write(); + await sleep(SLEEP_FUDGE_MS); + + config1.unset('x'); + + const c1output = await config1.write(); + expect(c1output.x).to.equal(undefined); + }); + + it('deleted in both memory and file', async () => { + const config1 = new TestConfig(TestConfig.getOptions(FILENAME, true, true, sharedLocation)); + config1.set('x', 0); + await sleep(SLEEP_FUDGE_MS); + + const config2 = new TestConfig(TestConfig.getOptions(FILENAME, true, true, sharedLocation)); + config2.set('x', 1); + + await config2.write(); + await sleep(SLEEP_FUDGE_MS); + + config2.unset('x'); + await config2.write(); + + await sleep(SLEEP_FUDGE_MS); + config1.unset('x'); + + const c1output = await config1.write(); + expect(c1output.x).to.equal(undefined); + }); + + it('parallel writes from different processes produce valid json when file exists', async () => { + const config1 = new TestConfig(TestConfig.getOptions(FILENAME, true, true, sharedLocation)); + await config1.write(); + const config2 = new TestConfig(TestConfig.getOptions(FILENAME, true, true, sharedLocation)); + const config3 = new TestConfig(TestConfig.getOptions(FILENAME, true, true, sharedLocation)); + const config4 = new TestConfig(TestConfig.getOptions(FILENAME, true, true, sharedLocation)); + + config1.set('x', 0); + await sleep(SLEEP_FUDGE_MS); + config2.set('x', 1); + config2.set('b', 2); + await sleep(SLEEP_FUDGE_MS); + config3.set('x', 2); + config3.set('c', 3); + await sleep(SLEEP_FUDGE_MS); + config4.set('x', 3); + await sleep(SLEEP_FUDGE_MS); + + // simulate non-deterministic parallel operations from different processes. We can't guarantee the order of files, + // so this might result in a set prop being deleted. + await Promise.all([config1.write(), config2.write(), config3.write(), config4.write()]); + await Promise.all([config1.read(), config2.read(), config3.read(), config4.read()]); + // timestamps on the files are treated as the stamp for all props, + // since we lose the CRDT prop - level timestamps when writing to json + expect(config1.get('x')).to.be.greaterThanOrEqual(0).and.lessThanOrEqual(3); + if (config1.has('b')) { + expect(config1.get('b')).to.equal(2); + } + if (config1.has('c')) { + expect(config1.get('c')).to.equal(3); + } + if (config2.has('b')) { + expect(config2.get('b')).to.equal(2); + } + if (config2.has('c')) { + expect(config2.get('c')).to.equal(3); + } + }); + + it('parallel writes, reverse order, with deletes', async () => { + const config1 = new TestConfig(TestConfig.getOptions('test', true, true, sharedLocation)); + const config2 = new TestConfig(TestConfig.getOptions('test', true, true, sharedLocation)); + const config3 = new TestConfig(TestConfig.getOptions('test', true, true, sharedLocation)); + const config4 = new TestConfig(TestConfig.getOptions('test', true, true, sharedLocation)); + + config1.set('x', 7); + await sleep(SLEEP_FUDGE_MS); + config2.set('x', 8); + await sleep(SLEEP_FUDGE_MS); + config3.set('x', 9); + await sleep(SLEEP_FUDGE_MS); + config4.unset('x'); + + await Promise.all([config4.write(), config3.write(), config2.write(), config1.write()]); + await Promise.all([config1.read(), config2.read(), config3.read(), config4.read()]); + if (config4.has('x')) { + expect(config4.get('x')).to.be.greaterThanOrEqual(7).and.lessThanOrEqual(9); + } + }); +}); diff --git a/test/unit/config/lwwMapTest.ts b/test/unit/config/lwwMapTest.ts index 316589fc9a..cba9eb4d97 100644 --- a/test/unit/config/lwwMapTest.ts +++ b/test/unit/config/lwwMapTest.ts @@ -9,18 +9,21 @@ import { LWWMap, LWWState, SYMBOL_FOR_DELETED } from '../../../src/config/lwwMap import { nowBigInt } from '../../../src/util/time'; config.truncateThreshold = 0; -const TIMESTAMP_OFFSET = BigInt(1_000_000); +// unit is ns. 1_000_000_000 ns = 1s +const TIMESTAMP_OFFSET = BigInt(1_000_000_000); describe('LWWMap', () => { type TestType = { foo: string; baz: string; opt?: number; optNull?: null }; + let state: LWWState; + describe('all properties are known', () => { - const state = { - foo: { value: 'bar', timestamp: nowBigInt() }, - baz: { value: 'qux', timestamp: nowBigInt() }, - }; let lwwMap: LWWMap; beforeEach(() => { + state = { + foo: { value: 'bar', timestamp: nowBigInt() }, + baz: { value: 'qux', timestamp: nowBigInt() }, + }; lwwMap = new LWWMap(state); }); @@ -154,24 +157,28 @@ describe('LWWMap', () => { expect(lwwMap.state.gone.value).to.equal(SYMBOL_FOR_DELETED); }); }); - describe('nested objects', () => { - const state = { +}); + +describe('nested objects', () => { + type NestedOpenEndedObject = { foo: string; baz: string; opt?: number; optNull?: null; obj: Record }; + + let state: LWWState; + let lwwMap: LWWMap; + + beforeEach(() => { + state = { foo: { value: 'bar', timestamp: nowBigInt() }, baz: { value: 'qux', timestamp: nowBigInt() }, obj: { value: { a: 1, b: 2, c: 3 }, timestamp: nowBigInt() }, }; - let lwwMap: LWWMap<{ foo: string; baz: string; opt?: number; optNull?: null }>; - - beforeEach(() => { - lwwMap = new LWWMap(state); - }); + lwwMap = new LWWMap(state); + }); - it('should initialize with the correct state', () => { - expect(lwwMap.state).to.deep.equal(state); - }); + it('should initialize with the correct state', () => { + expect(lwwMap.state).to.deep.equal(state); + }); - it('should get the correct value for the entire object', () => { - expect(lwwMap.value).to.deep.equal({ foo: 'bar', baz: 'qux', obj: { a: 1, b: 2, c: 3 } }); - }); + it('should get the correct value for the entire object', () => { + expect(lwwMap.value).to.deep.equal({ foo: 'bar', baz: 'qux', obj: { a: 1, b: 2, c: 3 } }); }); }); diff --git a/test/unit/org/orgTest.ts b/test/unit/org/orgTest.ts index e4d3e5f28a..f5d82a29c1 100644 --- a/test/unit/org/orgTest.ts +++ b/test/unit/org/orgTest.ts @@ -640,9 +640,8 @@ describe('Org Tests', () => { expect(info).has.property('value', org0Username); const org1Username = orgs[1].getUsername(); - + assert(org1Username); const stateAggregator = await StateAggregator.getInstance(); - // @ts-expect-error: user is nullable stateAggregator.aliases.set('foo', org1Username); const user = stateAggregator.aliases.getUsername('foo'); expect(user).eq(org1Username); From 37ed65b5897e12410a9f60803e020b9ec4c8ceac Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 25 Oct 2023 11:34:15 -0500 Subject: [PATCH 55/67] chore: remove unused unexported code --- src/util/jsonXmlTools.ts | 55 ------------------ test/unit/util/jsonXmlToolsTest.ts | 90 ------------------------------ 2 files changed, 145 deletions(-) delete mode 100644 src/util/jsonXmlTools.ts delete mode 100644 test/unit/util/jsonXmlToolsTest.ts diff --git a/src/util/jsonXmlTools.ts b/src/util/jsonXmlTools.ts deleted file mode 100644 index 52d02ec402..0000000000 --- a/src/util/jsonXmlTools.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2021, salesforce.com, inc. - * All rights reserved. - * Licensed under the BSD 3-Clause license. - * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ - -import { promises as fs } from 'fs'; -import * as jsToXml from 'js2xmlparser'; -import { JsonMap } from '@salesforce/ts-types'; -import { IOptions } from 'js2xmlparser/lib/options'; - -export interface JSONasXML { - json: JsonMap; - type: string; - options?: IOptions; -} - -export interface WriteJSONasXMLInputs extends JSONasXML { - path: string; -} - -export const standardOptions: IOptions = { - declaration: { - include: true, - encoding: 'UTF-8', - version: '1.0', - }, - format: { - doubleQuotes: true, - }, -}; - -export const writeJSONasXML = async ({ - path, - json, - type, - options = standardOptions, -}: WriteJSONasXMLInputs): Promise => { - const xml = jsToXml.parse(type, fixExistingDollarSign(json), options); - return fs.writeFile(path, xml); -}; - -export const JsonAsXml = ({ json, type, options = standardOptions }: JSONasXML): string => - jsToXml.parse(type, fixExistingDollarSign(json), options); - -export const fixExistingDollarSign = (existing: WriteJSONasXMLInputs['json']): Record => { - const existingCopy = { ...existing } as Record; - if (existingCopy.$) { - const temp = existingCopy.$; - delete existingCopy.$; - existingCopy['@'] = temp; - } - return existingCopy; -}; diff --git a/test/unit/util/jsonXmlToolsTest.ts b/test/unit/util/jsonXmlToolsTest.ts deleted file mode 100644 index d30bbae6ba..0000000000 --- a/test/unit/util/jsonXmlToolsTest.ts +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2021, salesforce.com, inc. - * All rights reserved. - * Licensed under the BSD 3-Clause license. - * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ - -import { promises as fs } from 'fs'; -import { expect } from 'chai'; -import { SinonStub } from 'sinon'; -import { stubMethod } from '@salesforce/ts-sinon'; -import { shouldThrow, TestContext } from '../../../src/testSetup'; -import { writeJSONasXML, fixExistingDollarSign } from '../../../src/util/jsonXmlTools'; - -const XML_PATH = '/tmp/myXml.xml'; -const TEST_JSON = { - name: 'Anna', - address: 'Ocean Drive 101', - phone: 123456789, - alias: ['Anita', 'Anny'], - cars: { - primary: { - brand: 'Honda', - model: 'Civic', - year: '2016', - }, - }, -}; -const TEST_XML = - '\n\n Anna\n

Ocean Drive 101
\n 123456789\n Anita\n Anny\n \n \n Honda\n Civic\n 2016\n \n \n'; -const DOLLARSIGN_OBJECT = { - $: 'value', -}; - -describe('jsonXmlTools', () => { - const $$ = new TestContext(); - - let fsWriteFileStub: SinonStub; - beforeEach(() => { - fsWriteFileStub = stubMethod($$.SANDBOX, fs, 'writeFile').callsFake((fsPath, xml) => { - expect(fsPath).to.be.a('string').and.to.have.length.greaterThan(0).and.to.be.equal(XML_PATH); - expect(xml).to.be.a('string').and.to.have.length.greaterThan(0); - return Promise.resolve(); - }); - }); - - afterEach(() => { - $$.SANDBOX.restore(); - }); - - it('writes json as xml', async () => { - const result = await writeJSONasXML({ - path: XML_PATH, - type: 'RecordType', - json: TEST_JSON, - }); - expect(fsWriteFileStub.callCount).to.be.equal(1); - expect(fsWriteFileStub.firstCall.args[1]).to.equal(TEST_XML); - // undefined means write operation succeeded https://nodejs.org/api/fs.html#fs_fspromises_writefile_file_data_options - expect(result).to.be.undefined; - }); - - it('fails to write json as xml but fails', async () => { - fsWriteFileStub.restore(); - fsWriteFileStub = stubMethod($$.SANDBOX, fs, 'writeFile').rejects(); - try { - // create() calls read() which calls schemaValidate() - await shouldThrow( - writeJSONasXML({ - path: XML_PATH, - type: 'RecordType', - json: TEST_JSON, - }) - ); - } catch (e) { - const error = e as Error; - expect(error.name).to.equal('Error'); - expect(error.message).to.equal('Error'); - } - }); -}); - -describe('fixExistingDollarSign', () => { - it('fixes existing dollar sing key in object', () => { - const result = fixExistingDollarSign(DOLLARSIGN_OBJECT); - expect(result).to.deep.equal({ - '@': 'value', - }); - }); -}); From cdfdc792104ab1b3ef2361873aea812db14fe1b6 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Thu, 26 Oct 2023 15:35:09 -0500 Subject: [PATCH 56/67] docs: v5 to v6 migration --- MIGRATING_V5-V6.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 MIGRATING_V5-V6.md diff --git a/MIGRATING_V5-V6.md b/MIGRATING_V5-V6.md new file mode 100644 index 0000000000..1d24cc8650 --- /dev/null +++ b/MIGRATING_V5-V6.md @@ -0,0 +1,29 @@ +# Migrating `@salesforce/core` from v5 to v6 + +v5 contains breaking changes to the classes that extend from ConfigStore/ConfigFile. We'll call these the "config stack" as a shorthand + +We've had a series of bugs where files controlled by the config stack could lose data or become corrupted when multiple processes try to write them concurrently. + +V6 introducing a file locking mechanism previously only available on the `alias` file and a process of resolving conflicts based on timestamps. + +But that comes with breaking changes to to reduce the risk of "getting around" the safeties. + +## Breaking changes related to the Config Stack + +- AuthInfo.getFields now returns a read-only object. Use AuthInfo.update to change values in the fields. +- `setContents` method is no longer available in the ConfigFile stack. Use `update` to merge in multiple props, or `set/unset` on a single prop. +- `write` and `writeSync` method no longer accepts a param. Use other methods (`set`, `unset`, `update) to make modifications, then call write()/writeSync() to do the write. +- the use of lodash-style `get`/`set` (ex: `set('foo.bar.baz[0]', 3)`) no longer works. +- You can no longer override the `setMethod` and `getMethod`` when extending classes built on ConfigFile. Technically you could override get/set, but DON'T! +- Everything related to tokens/tokenConfig is gone + +## Unrelated changes that we did because it's a major version + +- node18+ only, compiles to es2022 +- move `uniqid`` to a shared function, outside of testSetup + +## Previously deprecated items that are now removed + +- removed sfdc.isInternalUrl. Use new SfdcUrl(url).isInternalUrl() +- removed sfdc.findUppercaseKeys. There is no replacement. +- removed SchemaPrinter. There is no replacement. From b0f12e2e11d9d77e4c3ffecdd5ca32017bb53705 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Thu, 26 Oct 2023 16:38:19 -0500 Subject: [PATCH 57/67] refactor: extract duplicat code, log more file contents --- src/config/configFile.ts | 64 ++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/config/configFile.ts b/src/config/configFile.ts index 8349159f01..2cdeb88580 100644 --- a/src/config/configFile.ts +++ b/src/config/configFile.ts @@ -163,7 +163,7 @@ export class ConfigFile< // Only need to read config files once. They are kept up to date // internally and updated persistently via write(). if (!this.hasRead || force) { - this.logger.info( + this.logger.debug( `Reading config file: ${this.getPath()} because ${ !this.hasRead ? 'hasRead is false' : 'force parameter is true' }` @@ -203,7 +203,7 @@ export class ConfigFile< // Only need to read config files once. They are kept up to date // internally and updated persistently via write(). if (!this.hasRead || force) { - this.logger.info(`Reading config file: ${this.getPath()}`); + this.logger.debug(`Reading config file: ${this.getPath()}`); const obj = parseJsonMap

(fs.readFileSync(this.getPath(), 'utf8')); this.setContentsFromFileContents(obj, fs.statSync(this.getPath(), { bigint: true }).mtimeNs); } @@ -256,25 +256,14 @@ export class ConfigFile< // get the file modstamp. Do this after the lock acquisition in case the file is being written to. const fileTimestamp = (await fs.promises.stat(this.getPath(), { bigint: true })).mtimeNs; - - // read the file contents into a LWWMap using the modstamp - const stateFromFile = stateFromContents

( - parseJsonMap

(await fs.promises.readFile(this.getPath(), 'utf8'), this.getPath()), - fileTimestamp - ); - - // merge the new contents into the in-memory LWWMap - this.contents.merge(stateFromFile); + const fileContents = parseJsonMap

(await fs.promises.readFile(this.getPath(), 'utf8'), this.getPath()); + this.logAndMergeContents(fileTimestamp, fileContents); } catch (err) { - if (err instanceof Error && err.message.includes('ENOENT')) { - this.logger.debug(`No file found at ${this.getPath()}. Write will create it.`); - } else { - throw err; - } + this.handleWriteError(err); } // write the merged LWWMap to file - this.logger.info(`Writing to config file: ${this.getPath()}`); - await fs.promises.writeFile(this.getPath(), JSON.stringify(this.toObject(), null, 2)); + this.logger.debug(`Writing to config file: ${this.getPath()}`); + await fs.promises.writeFile(this.getPath(), JSON.stringify(this.getContents(), null, 2)); // unlock the file if (typeof unlockFn !== 'undefined') { @@ -303,25 +292,15 @@ export class ConfigFile< unlockFn = lockSync(this.getPath(), lockOptions); // get the file modstamp. Do this after the lock acquisition in case the file is being written to. const fileTimestamp = fs.statSync(this.getPath(), { bigint: true }).mtimeNs; - - // read the file contents into a LWWMap using the modstamp - const stateFromFile = stateFromContents

( - parseJsonMap

(fs.readFileSync(this.getPath(), 'utf8'), this.getPath()), - fileTimestamp - ); - // merge the new contents into the in-memory LWWMap - this.contents.merge(stateFromFile); + const fileContents = parseJsonMap

(fs.readFileSync(this.getPath(), 'utf8'), this.getPath()); + this.logAndMergeContents(fileTimestamp, fileContents); } catch (err) { - if (err instanceof Error && err.message.includes('ENOENT')) { - this.logger.debug(`No file found at ${this.getPath()}. Write will create it.`); - } else { - throw err; - } + this.handleWriteError(err); } // write the merged LWWMap to file - this.logger.info(`Writing to config file: ${this.getPath()}`); + this.logger.trace(`Writing to config file: ${this.getPath()}`); fs.writeFileSync(this.getPath(), JSON.stringify(this.toObject(), null, 2)); if (typeof unlockFn !== 'undefined') { @@ -437,6 +416,27 @@ export class ConfigFile< // Read the file, which also sets the path and throws any errors around project paths. await this.read(this.options.throwOnNotFound); } + + // method exists to share code between write() and writeSync() + private logAndMergeContents(fileTimestamp: bigint, fileContents: P): void { + this.logger.debug(`Existing file contents on filesystem (timestamp: ${fileTimestamp.toString()}`, fileContents); + this.logger.debug('Contents in configFile in-memory', this.getContents()); + + // read the file contents into a LWWMap using the modstamp + const stateFromFile = stateFromContents

(fileContents, fileTimestamp); + // merge the new contents into the in-memory LWWMap + this.contents.merge(stateFromFile); + this.logger.debug('Result from merge', this.getContents()); + } + + // shared error handling for both write() and writeSync() + private handleWriteError(err: unknown): void { + if (err instanceof Error && err.message.includes('ENOENT')) { + this.logger.debug(`No file found at ${this.getPath()}. Write will create it.`); + } else { + throw err; + } + } } export namespace ConfigFile { From 2a3dbeccf08f8732fae22419e50278a78647cf6b Mon Sep 17 00:00:00 2001 From: mshanemc Date: Fri, 27 Oct 2023 16:39:04 -0500 Subject: [PATCH 58/67] chore: pr feedback --- MIGRATING_V5-V6.md | 5 +++-- src/config/configStore.ts | 18 +++--------------- src/config/lwwMap.ts | 4 +--- test/unit/config/configStoreTest.ts | 11 ----------- 4 files changed, 7 insertions(+), 31 deletions(-) diff --git a/MIGRATING_V5-V6.md b/MIGRATING_V5-V6.md index 1d24cc8650..21a8bdac38 100644 --- a/MIGRATING_V5-V6.md +++ b/MIGRATING_V5-V6.md @@ -13,8 +13,9 @@ But that comes with breaking changes to to reduce the risk of "getting around" t - AuthInfo.getFields now returns a read-only object. Use AuthInfo.update to change values in the fields. - `setContents` method is no longer available in the ConfigFile stack. Use `update` to merge in multiple props, or `set/unset` on a single prop. - `write` and `writeSync` method no longer accepts a param. Use other methods (`set`, `unset`, `update) to make modifications, then call write()/writeSync() to do the write. -- the use of lodash-style `get`/`set` (ex: `set('foo.bar.baz[0]', 3)`) no longer works. -- You can no longer override the `setMethod` and `getMethod`` when extending classes built on ConfigFile. Technically you could override get/set, but DON'T! +- the use of lodash-style `get`/`set`/`unset`/`unsetAll` (ex: `set('foo.bar.baz[0]', 3)`) no longer works. +- `awaitEach` is removed +- You can no longer override the `setMethod` and `getMethod` when extending classes built on ConfigFile. Technically you could override get/set, but DON'T! - Everything related to tokens/tokenConfig is gone ## Unrelated changes that we did because it's a major version diff --git a/src/config/configStore.ts b/src/config/configStore.ts index 389b846e37..99f4a42460 100644 --- a/src/config/configStore.ts +++ b/src/config/configStore.ts @@ -35,7 +35,6 @@ export interface ConfigStore

{ values(): ConfigValue[]; forEach(actionFn: (key: string, value: ConfigValue) => void): void; - awaitEach(actionFn: (key: string, value: ConfigValue) => Promise): Promise; // Content methods getContents(): P; @@ -188,7 +187,7 @@ export abstract class BaseConfigStore< * Returns `true` if all elements in the config object existed and have been removed, or `false` if all the elements * do not exist (some may have been removed). {@link BaseConfigStore.has(key)} will return false afterwards. * - * @param keys The keys. Supports query keys like `a.b[0]`. + * @param keys The keys */ public unsetAll(keys: Array>): boolean { return keys.map((key) => this.unset(key)).every(Boolean); @@ -234,18 +233,6 @@ export abstract class BaseConfigStore< this.entries().map((entry) => actionFn(entry[0], entry[1])); } - /** - * Asynchronously invokes `actionFn` once for each key-value pair present in the config object. - * - * @param {function} actionFn The function `(key: string, value: ConfigValue) => Promise` to be called for - * each element. - * @returns {Promise} - */ - public async awaitEach(actionFn: (key: string, value: ConfigValue) => Promise): Promise { - const entries = this.entries(); - await Promise.all(entries.map((entry) => actionFn(entry[0], entry[1]))); - } - /** * Convert the config object to a JSON object. Returns the config contents. * Same as calling {@link ConfigStore.getContents} @@ -266,7 +253,8 @@ export abstract class BaseConfigStore< }); } - /** Keep ConfigFile concurrency-friendly. + /** + * Keep ConfigFile concurrency-friendly. * Avoid using this unless you're reading the file for the first time * and guaranteed to no be cross-saving existing contents * */ diff --git a/src/config/lwwMap.ts b/src/config/lwwMap.ts index 0cd5b4d258..0a298a7bfa 100644 --- a/src/config/lwwMap.ts +++ b/src/config/lwwMap.ts @@ -54,9 +54,7 @@ export class LWWMap

{ public get state(): LWWState

{ return Object.fromEntries( - Array.from(this.#data.entries()) - // .filter(([, register]) => Boolean(register)) - .map(([key, register]) => [key, register.state]) + Array.from(this.#data.entries()).map(([key, register]) => [key, register.state]) ) as LWWState

; } diff --git a/test/unit/config/configStoreTest.ts b/test/unit/config/configStoreTest.ts index 7dd2805d98..577cff023e 100644 --- a/test/unit/config/configStoreTest.ts +++ b/test/unit/config/configStoreTest.ts @@ -48,17 +48,6 @@ describe('ConfigStore', () => { }); expect(st).to.equal('1a2b'); }); - it('await each value', async () => { - const config = await TestConfig.create(); - config.set('1', 'a'); - config.set('2', 'b'); - - let st = ''; - await config.awaitEach(async (key, val) => { - st += `${key}${val}`; - }); - expect(st).to.equal('1a2b'); - }); it('returns the object reference', async () => { const config = new TestConfig<{ '1': { a: string } }>(); From 9d99e6174905dfd7c1df6d299799ac7706756735 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Tue, 31 Oct 2023 09:58:38 -0500 Subject: [PATCH 59/67] refactor: steve review --- MIGRATING_V5-V6.md | 2 +- src/config/config.ts | 25 ++++++++++++++----------- src/config/configFile.ts | 26 +++++++++++++++----------- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/MIGRATING_V5-V6.md b/MIGRATING_V5-V6.md index 21a8bdac38..afcec25d78 100644 --- a/MIGRATING_V5-V6.md +++ b/MIGRATING_V5-V6.md @@ -6,7 +6,7 @@ We've had a series of bugs where files controlled by the config stack could lose V6 introducing a file locking mechanism previously only available on the `alias` file and a process of resolving conflicts based on timestamps. -But that comes with breaking changes to to reduce the risk of "getting around" the safeties. +But that comes with breaking changes to reduce the risk of "getting around" the safeties. ## Breaking changes related to the Config Stack diff --git a/src/config/config.ts b/src/config/config.ts index c9caf6ea0e..1d14a49d5f 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -387,7 +387,7 @@ export class Config extends ConfigFile { await config.read(); - if (value == null || value === undefined) { + if (value == null) { config.unset(propertyName); } else { config.set(propertyName, value); @@ -598,8 +598,8 @@ export class Config extends ConfigFile { } /** - * If toOld is specified: migrate all deprecated configs back to their original key. - * - For example, target-org will be renamed to defaultusername. + * convert from "new" to "old" config names + * - For example, `target-org` will be renamed to `defaultusername`. */ const translateToSfdx = (sfContents: ConfigProperties): ConfigProperties => Object.fromEntries( @@ -610,8 +610,8 @@ const translateToSfdx = (sfContents: ConfigProperties): ConfigProperties => ); /** - * If toOld is specified: migrate all deprecated configs to the new key. - * - For example, target-org will be renamed to defaultusername. + * convert from "old" to "new" config names + * - For example, `defaultusername` will be renamed to `target-org` */ const translateToSf = (sfdxContents: ConfigProperties, SfConfig: Config): ConfigProperties => Object.fromEntries( @@ -642,20 +642,23 @@ const writeToSfdx = async (path: string, contents: ConfigProperties): Promise => { +const stateFromSfdxFileSync = (path: string, config: Config): LWWState => { try { - const fileContents = fs.readFileSync(filePath, 'utf8'); - const mtimeNs = fs.statSync(filePath, { bigint: true }).mtimeNs; - const translatedContents = translateToSf(parseJsonMap(fileContents, filePath), config); + const fileContents = fs.readFileSync(path, 'utf8'); + const mtimeNs = fs.statSync(path, { bigint: true }).mtimeNs; + const translatedContents = translateToSf(parseJsonMap(fileContents, path), config); // get the file timestamp return stateFromContents(translatedContents, mtimeNs); } catch (e) { + const logger = Logger.childFromRoot('core:config:stateFromSfdxFileSync'); + logger.debug(`Error reading state from sfdx config file at ${path}: ${e instanceof Error ? e.message : ''}`); return {}; } }; diff --git a/src/config/configFile.ts b/src/config/configFile.ts index 2cdeb88580..f727af2434 100644 --- a/src/config/configFile.ts +++ b/src/config/configFile.ts @@ -254,7 +254,6 @@ export class ConfigFile< ); } - // get the file modstamp. Do this after the lock acquisition in case the file is being written to. const fileTimestamp = (await fs.promises.stat(this.getPath(), { bigint: true })).mtimeNs; const fileContents = parseJsonMap

(await fs.promises.readFile(this.getPath(), 'utf8'), this.getPath()); this.logAndMergeContents(fileTimestamp, fileContents); @@ -263,11 +262,13 @@ export class ConfigFile< } // write the merged LWWMap to file this.logger.debug(`Writing to config file: ${this.getPath()}`); - await fs.promises.writeFile(this.getPath(), JSON.stringify(this.getContents(), null, 2)); - - // unlock the file - if (typeof unlockFn !== 'undefined') { - await unlockFn(); + try { + await fs.promises.writeFile(this.getPath(), JSON.stringify(this.getContents(), null, 2)); + } finally { + // unlock the file + if (typeof unlockFn !== 'undefined') { + await unlockFn(); + } } return this.getContents(); @@ -301,12 +302,15 @@ export class ConfigFile< // write the merged LWWMap to file this.logger.trace(`Writing to config file: ${this.getPath()}`); - fs.writeFileSync(this.getPath(), JSON.stringify(this.toObject(), null, 2)); - - if (typeof unlockFn !== 'undefined') { - // unlock the file - unlockFn(); + try { + fs.writeFileSync(this.getPath(), JSON.stringify(this.toObject(), null, 2)); + } finally { + if (typeof unlockFn !== 'undefined') { + // unlock the file + unlockFn(); + } } + return this.getContents(); } From 29c59e013f0cf25407430bd0d4752e4df8686ab5 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 8 Nov 2023 15:06:53 -0600 Subject: [PATCH 60/67] feat: reusable file locks outside of ConfigFile --- src/config/configFile.ts | 55 +++-------------------- src/exported.ts | 2 +- src/util/fileLocking.ts | 97 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 49 deletions(-) create mode 100644 src/util/fileLocking.ts diff --git a/src/config/configFile.ts b/src/config/configFile.ts index f727af2434..40042b1f04 100644 --- a/src/config/configFile.ts +++ b/src/config/configFile.ts @@ -8,14 +8,13 @@ import * as fs from 'fs'; import { constants as fsConstants, Stats as fsStats } from 'fs'; import { homedir as osHomedir } from 'os'; -import { basename, dirname as pathDirname, join as pathJoin } from 'path'; -import { lock, lockSync } from 'proper-lockfile'; +import { join as pathJoin } from 'path'; import { parseJsonMap } from '@salesforce/kit'; import { Global } from '../global'; import { Logger } from '../logger/logger'; import { SfError } from '../sfError'; import { resolveProjectPath, resolveProjectPathSync } from '../util/internal'; -import { lockOptions, lockRetryOptions } from '../util/lockRetryOptions'; +import { lockInit, lockInitSync } from '../util/fileLocking'; import { BaseConfigStore } from './configStore'; import { ConfigContents } from './configStackTypes'; import { stateFromContents } from './lwwMap'; @@ -168,6 +167,7 @@ export class ConfigFile< !this.hasRead ? 'hasRead is false' : 'force parameter is true' }` ); + const obj = parseJsonMap

(await fs.promises.readFile(this.getPath(), 'utf8'), this.getPath()); this.setContentsFromFileContents(obj, (await fs.promises.stat(this.getPath(), { bigint: true })).mtimeNs); } @@ -234,26 +234,10 @@ export class ConfigFile< * @param newContents The new contents of the file. */ public async write(): Promise

{ - // make sure we can write to the directory - try { - await fs.promises.mkdir(pathDirname(this.getPath()), { recursive: true }); - } catch (err) { - throw SfError.wrap(err as Error); - } + const lockResponse = await lockInit(this.getPath()); - let unlockFn: (() => Promise) | undefined; // lock the file. Returns an unlock function to call when done. try { - if (await this.exists()) { - unlockFn = await lock(this.getPath(), lockRetryOptions); - } else { - // lock the entire directory to keep others from trying to create the file while we are - unlockFn = await lock(basename(this.getPath()), lockRetryOptions); - this.logger.debug( - `No file found at ${this.getPath()}. Write will create it. Locking the entire directory until file is written.` - ); - } - const fileTimestamp = (await fs.promises.stat(this.getPath(), { bigint: true })).mtimeNs; const fileContents = parseJsonMap

(await fs.promises.readFile(this.getPath(), 'utf8'), this.getPath()); this.logAndMergeContents(fileTimestamp, fileContents); @@ -261,15 +245,7 @@ export class ConfigFile< this.handleWriteError(err); } // write the merged LWWMap to file - this.logger.debug(`Writing to config file: ${this.getPath()}`); - try { - await fs.promises.writeFile(this.getPath(), JSON.stringify(this.getContents(), null, 2)); - } finally { - // unlock the file - if (typeof unlockFn !== 'undefined') { - await unlockFn(); - } - } + await lockResponse.writeAndUnlock(JSON.stringify(this.getContents(), null, 2)); return this.getContents(); } @@ -281,16 +257,8 @@ export class ConfigFile< * @param newContents The new contents of the file. */ public writeSync(): P { + const lockResponse = lockInitSync(this.getPath()); try { - fs.mkdirSync(pathDirname(this.getPath()), { recursive: true }); - } catch (err) { - throw SfError.wrap(err as Error); - } - - let unlockFn: (() => void) | undefined; - try { - // lock the file. Returns an unlock function to call when done. - unlockFn = lockSync(this.getPath(), lockOptions); // get the file modstamp. Do this after the lock acquisition in case the file is being written to. const fileTimestamp = fs.statSync(this.getPath(), { bigint: true }).mtimeNs; const fileContents = parseJsonMap

(fs.readFileSync(this.getPath(), 'utf8'), this.getPath()); @@ -300,16 +268,7 @@ export class ConfigFile< } // write the merged LWWMap to file - - this.logger.trace(`Writing to config file: ${this.getPath()}`); - try { - fs.writeFileSync(this.getPath(), JSON.stringify(this.toObject(), null, 2)); - } finally { - if (typeof unlockFn !== 'undefined') { - // unlock the file - unlockFn(); - } - } + lockResponse.writeAndUnlock(JSON.stringify(this.getContents(), null, 2)); return this.getContents(); } diff --git a/src/exported.ts b/src/exported.ts index 3ba065e927..c067a98232 100644 --- a/src/exported.ts +++ b/src/exported.ts @@ -89,7 +89,7 @@ export { MyDomainResolver } from './status/myDomainResolver'; export { DefaultUserFields, REQUIRED_FIELDS, User, UserFields } from './org/user'; export { PermissionSetAssignment, PermissionSetAssignmentFields } from './org/permissionSetAssignment'; - +export { lockInit } from './util/fileLocking'; export { ScratchOrgCreateOptions, ScratchOrgCreateResult, diff --git a/src/util/fileLocking.ts b/src/util/fileLocking.ts new file mode 100644 index 0000000000..0a6efb6277 --- /dev/null +++ b/src/util/fileLocking.ts @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import * as fs from 'node:fs'; +import { dirname } from 'node:path'; +import { lock, lockSync } from 'proper-lockfile'; +import { SfError } from '../sfError'; +import { Logger } from '../logger/logger'; +import { lockRetryOptions } from './lockRetryOptions'; + +type LockInitResponse = { writeAndUnlock: (data: string) => Promise; unlock: () => Promise }; +type LockInitSyncResponse = { writeAndUnlock: (data: string) => void; unlock: () => void }; + +/** + * + *This method exists as a separate function so it can be used by ConfigFile OR outside of ConfigFile. + * + * @param filePath where to save the file + * @returns 2 functions: + * - writeAndUnlock: a function that takes the data to write and writes it to the file, then unlocks the file whether write succeeded or not + * - unlock: a function that unlocks the file (in case you don't end up calling writeAndUnlock) + */ +export const lockInit = async (filePath: string): Promise => { + // make sure we can write to the directory + try { + await fs.promises.mkdir(dirname(filePath), { recursive: true }); + } catch (err) { + throw SfError.wrap(err as Error); + } + + const [unlock] = await Promise.all( + fs.existsSync(filePath) + ? // if the file exists, wait for it to be unlocked + [lock(filePath, lockRetryOptions)] + : // lock the entire directory to keep others from trying to create the file while we are + [ + lock(dirname(filePath), lockRetryOptions), + ( + await Logger.child('fileLocking.lockInit') + ).debug( + `No file found at ${filePath}. Write will create it. Locking the entire directory until file is written.` + ), + ] + ); + + return { + writeAndUnlock: async (data: string): Promise => { + const logger = await Logger.child('fileLocking.writeAndUnlock'); + logger.debug(`Writing to file: ${filePath}`); + try { + await fs.promises.writeFile(filePath, data); + } finally { + await unlock(); + } + }, + unlock, + }; +}; + +/** + * prefer async {@link lockInit}. + * See its documentation for details. + */ +export const lockInitSync = (filePath: string): LockInitSyncResponse => { + // make sure we can write to the directory + try { + fs.mkdirSync(dirname(filePath), { recursive: true }); + } catch (err) { + throw SfError.wrap(err as Error); + } + + const [unlock] = fs.existsSync(filePath) + ? // if the file exists, wait for it to be unlocked + [lockSync(filePath, lockRetryOptions)] + : // lock the entire directory to keep others from trying to create the file while we are + [ + lockSync(dirname(filePath), lockRetryOptions), + Logger.childFromRoot('fileLocking.lockInit').debug( + `No file found at ${filePath}. Write will create it. Locking the entire directory until file is written.` + ), + ]; + return { + writeAndUnlock: (data: string): void => { + const logger = Logger.childFromRoot('fileLocking.writeAndUnlock'); + logger.debug(`Writing to file: ${filePath}`); + try { + fs.writeFileSync(filePath, data); + } finally { + unlock(); + } + }, + unlock, + }; +}; From 9fe6f49f44d2ff613f20b8a78de7d27f306dc276 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Thu, 9 Nov 2023 16:28:47 -0600 Subject: [PATCH 61/67] refactor: dev-scripts --- package.json | 30 +- src/config/config.ts | 13 +- src/config/configFile.ts | 8 +- src/config/configStore.ts | 4 +- src/config/envVars.ts | 2 +- src/config/lwwMap.ts | 2 +- src/crypto/crypto.ts | 6 +- src/crypto/keyChainImpl.ts | 14 +- src/crypto/secureBuffer.ts | 2 +- src/global.ts | 6 +- src/logger/logger.ts | 6 +- src/logger/memoryLogger.ts | 2 +- src/logger/transformStream.ts | 2 +- src/messages.ts | 10 +- src/org/authInfo.ts | 12 +- src/org/connection.ts | 2 +- src/org/org.ts | 4 +- src/org/orgConfigProperties.ts | 2 +- src/org/permissionSetAssignment.ts | 2 +- src/org/scratchOrgInfoGenerator.ts | 2 +- src/org/scratchOrgSettingsGenerator.ts | 4 +- src/org/user.ts | 2 +- src/schema/validator.ts | 4 +- src/sfError.ts | 2 +- src/sfProject.ts | 4 +- src/stateAggregator/accessors/orgAccessor.ts | 4 +- src/status/myDomainResolver.ts | 6 +- src/status/streamingClient.ts | 8 +- src/status/types.ts | 2 +- src/testSetup.ts | 6 +- src/util/checkLightningDomain.ts | 2 +- src/util/directoryWriter.ts | 10 +- src/util/internal.ts | 4 +- src/util/sfdcUrl.ts | 9 +- src/util/structuredWriter.ts | 2 +- src/util/uniqid.ts | 4 +- src/util/zipWriter.ts | 2 +- src/webOAuthServer.ts | 10 +- test/unit/config/configAggregatorTest.ts | 2 +- test/unit/config/configFileTest.ts | 4 +- test/unit/config/configTest.ts | 2 +- test/unit/crypto/cryptoKeyFailuresTest.ts | 6 +- test/unit/crypto/cryptoTest.ts | 2 +- test/unit/crypto/keyChainImplTest.ts | 2 +- test/unit/crypto/keyChainTest.ts | 37 +- test/unit/crypto/secureStringTest.ts | 2 +- test/unit/messagesTest.ts | 6 +- test/unit/org/authInfoTest.ts | 4 +- test/unit/org/orgTest.ts | 8 +- test/unit/org/scratchOrgInfoGeneratorTest.ts | 2 +- .../org/scratchOrgSettingsGeneratorTest.ts | 2 +- test/unit/projectTest.ts | 2 +- test/unit/schema/validatorTest.ts | 4 +- test/unit/status/myDomainResolverTest.ts | 4 +- test/unit/util/directoryWriterTest.ts | 8 +- test/unit/util/zipWriterTest.ts | 6 +- test/unit/webOauthServerTest.ts | 2 +- yarn.lock | 970 +++++++++--------- 58 files changed, 628 insertions(+), 663 deletions(-) diff --git a/package.json b/package.json index c44e0d955d..938bc0a9cd 100644 --- a/package.json +++ b/package.json @@ -59,41 +59,17 @@ "ts-retry-promise": "^0.7.1" }, "devDependencies": { - "@salesforce/dev-config": "^4.1.0", - "@salesforce/dev-scripts": "^5.11.0", - "@salesforce/prettier-config": "^0.0.3", + "@salesforce/dev-scripts": "^6.0.3", "@salesforce/ts-sinon": "^1.4.18", "@types/benchmark": "^2.1.3", "@types/chai-string": "^1.4.4", "@types/jsonwebtoken": "9.0.3", - "@types/lodash": "^4.14.200", "@types/proper-lockfile": "^4.1.2", - "@types/shelljs": "0.8.13", - "@typescript-eslint/eslint-plugin": "^5.62.0", - "@typescript-eslint/parser": "^5.62.0", "benchmark": "^2.1.4", - "chai": "^4.3.10", "chai-string": "^1.5.0", - "eslint": "^8.53.0", - "eslint-config-prettier": "^8.10.0", - "eslint-config-salesforce": "^2.0.2", - "eslint-config-salesforce-license": "^0.2.0", - "eslint-config-salesforce-typescript": "^1.1.2", - "eslint-plugin-header": "^3.1.1", - "eslint-plugin-import": "^2.28.1", - "eslint-plugin-jsdoc": "^43.2.0", - "husky": "^7.0.4", - "lodash": "^4.17.21", - "mocha": "^9.1.3", - "nyc": "^15.1.0", - "prettier": "^2.8.7", - "pretty-quick": "^3.1.3", - "shelljs": "0.8.5", - "sinon": "^14.0.2", - "ts-node": "^10.4.0", + "ts-node": "^10.9.1", "ttypescript": "^1.5.15", - "typescript": "^4.9.5", - "wireit": "^0.14.0" + "typescript": "^4.9.5" }, "repository": { "type": "git", diff --git a/src/config/config.ts b/src/config/config.ts index 1d14a49d5f..7af17722d5 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -5,8 +5,8 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import { dirname as pathDirname, join as pathJoin } from 'path'; -import * as fs from 'fs'; +import { dirname as pathDirname, join as pathJoin } from 'node:path'; +import * as fs from 'node:fs'; import { keyBy, parseJsonMap } from '@salesforce/kit'; import { Dictionary, ensure, isString, Nullable } from '@salesforce/ts-types'; import { Global } from '../global'; @@ -96,7 +96,7 @@ export const SF_ALLOWED_PROPERTIES = [ key: SfConfigProperties.DISABLE_TELEMETRY, description: messages.getMessage(SfConfigProperties.DISABLE_TELEMETRY), input: { - validator: (value: ConfigValue): boolean => value == null || ['true', 'false'].includes(value.toString()), + validator: (value: ConfigValue): boolean => value == null || isBooleanOrBooleanString(value), failedMessage: messages.getMessage('invalidBooleanConfigValue'), }, }, @@ -248,7 +248,7 @@ export const SFDX_ALLOWED_PROPERTIES = [ deprecated: true, description: messages.getMessage(SfdxPropertyKeys.DISABLE_TELEMETRY), input: { - validator: (value: ConfigValue): boolean => value == null || ['true', 'false'].includes(value.toString()), + validator: (value: ConfigValue): boolean => value == null || isBooleanOrBooleanString(value), failedMessage: messages.getMessage('invalidBooleanConfigValue'), }, }, @@ -265,7 +265,7 @@ export const SFDX_ALLOWED_PROPERTIES = [ newKey: 'org-metadata-rest-deploy', deprecated: true, input: { - validator: (value: ConfigValue): boolean => value != null && ['true', 'false'].includes(value.toString()), + validator: (value: ConfigValue): boolean => value != null && isBooleanOrBooleanString(value), failedMessage: messages.getMessage('invalidBooleanConfigValue'), }, }, @@ -662,3 +662,6 @@ const stateFromSfdxFileSync = (path: string, config: Config): LWWState + (typeof value === 'string' && ['true', 'false'].includes(value)) || typeof value === 'boolean'; diff --git a/src/config/configFile.ts b/src/config/configFile.ts index 40042b1f04..97b21b707d 100644 --- a/src/config/configFile.ts +++ b/src/config/configFile.ts @@ -5,10 +5,10 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import * as fs from 'fs'; -import { constants as fsConstants, Stats as fsStats } from 'fs'; -import { homedir as osHomedir } from 'os'; -import { join as pathJoin } from 'path'; +import * as fs from 'node:fs'; +import { constants as fsConstants, Stats as fsStats } from 'node:fs'; +import { homedir as osHomedir } from 'node:os'; +import { join as pathJoin } from 'node:path'; import { parseJsonMap } from '@salesforce/kit'; import { Global } from '../global'; import { Logger } from '../logger/logger'; diff --git a/src/config/configStore.ts b/src/config/configStore.ts index 99f4a42460..4529dda4e0 100644 --- a/src/config/configStore.ts +++ b/src/config/configStore.ts @@ -356,7 +356,7 @@ export abstract class BaseConfigStore< if (!this.crypto) throw new SfError('crypto is not initialized', 'CryptoNotInitializedError'); if (!isString(value)) throw new SfError( - `can only encrypt strings but found: ${typeof value} : ${value.toString()}`, + `can only encrypt strings but found: ${typeof value} : ${JSON.stringify(value)}`, 'InvalidCryptoValueError' ); return this.crypto.isEncrypted(value) ? value : this.crypto.encrypt(value); @@ -367,7 +367,7 @@ export abstract class BaseConfigStore< if (!this.crypto) throw new SfError('crypto is not initialized', 'CryptoNotInitializedError'); if (!isString(value)) throw new SfError( - `can only encrypt strings but found: ${typeof value} : ${value.toString()}`, + `can only encrypt strings but found: ${typeof value} : ${JSON.stringify(value)}`, 'InvalidCryptoValueError' ); return this.crypto.isEncrypted(value) ? this.crypto.decrypt(value) : value; diff --git a/src/config/envVars.ts b/src/config/envVars.ts index 202a881530..9116758727 100644 --- a/src/config/envVars.ts +++ b/src/config/envVars.ts @@ -4,7 +4,7 @@ * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import { join as pathJoin } from 'path'; +import { join as pathJoin } from 'node:path'; import { Dictionary, Nullable } from '@salesforce/ts-types'; import { camelCase, snakeCase } from 'change-case'; import { Env } from '@salesforce/kit'; diff --git a/src/config/lwwMap.ts b/src/config/lwwMap.ts index 0a298a7bfa..953ad9ba87 100644 --- a/src/config/lwwMap.ts +++ b/src/config/lwwMap.ts @@ -35,7 +35,7 @@ export const stateFromContents =

(contents: P, timesta export class LWWMap

{ /** map of key to LWWRegister. Used for managing conflicts */ - #data = new Map>(); + #data = new Map>(); public constructor(state?: LWWState

) { // create a new register for each key in the initial state diff --git a/src/crypto/crypto.ts b/src/crypto/crypto.ts index 880a698bbe..ef151d2c1f 100644 --- a/src/crypto/crypto.ts +++ b/src/crypto/crypto.ts @@ -6,9 +6,9 @@ */ /* eslint-disable @typescript-eslint/ban-types */ -import * as crypto from 'crypto'; -import * as os from 'os'; -import { join as pathJoin } from 'path'; +import * as crypto from 'node:crypto'; +import * as os from 'node:os'; +import { join as pathJoin } from 'node:path'; import { ensure, Nullable, Optional } from '@salesforce/ts-types'; import { AsyncOptionalCreatable, env } from '@salesforce/kit'; import { Logger } from '../logger/logger'; diff --git a/src/crypto/keyChainImpl.ts b/src/crypto/keyChainImpl.ts index ddcfe18315..01e6e7f1cf 100644 --- a/src/crypto/keyChainImpl.ts +++ b/src/crypto/keyChainImpl.ts @@ -5,12 +5,12 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import * as childProcess from 'child_process'; -import * as nodeFs from 'fs'; -import * as fs from 'fs'; -import * as os from 'os'; -import { homedir } from 'os'; -import * as path from 'path'; +import * as childProcess from 'node:child_process'; +import * as nodeFs from 'node:fs'; +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import { homedir } from 'node:os'; +import * as path from 'node:path'; import { asString, ensureString, Nullable } from '@salesforce/ts-types'; import { parseJsonMap } from '@salesforce/kit'; import { Global } from '../global'; @@ -46,7 +46,7 @@ const isExe = (mode: number, gid: number, uid: number): boolean => { return Boolean( mode & parseInt('0001', 8) || - (mode & parseInt('0010', 8) && process.getgid && gid === process.getgid()) || + Boolean(mode & parseInt('0010', 8) && process.getgid && gid === process.getgid()) || (mode & parseInt('0100', 8) && process.getuid && uid === process.getuid()) ); }; diff --git a/src/crypto/secureBuffer.ts b/src/crypto/secureBuffer.ts index ec9cf0bd8f..6ef4a92ae5 100644 --- a/src/crypto/secureBuffer.ts +++ b/src/crypto/secureBuffer.ts @@ -5,7 +5,7 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import * as crypto from 'crypto'; +import * as crypto from 'node:crypto'; import { ensure, Optional } from '@salesforce/ts-types'; const cipherName = 'aes-256-cbc'; diff --git a/src/global.ts b/src/global.ts index 598258e1c8..0f58158198 100644 --- a/src/global.ts +++ b/src/global.ts @@ -5,9 +5,9 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; import { env } from '@salesforce/kit'; import { SfError } from './sfError'; diff --git a/src/logger/logger.ts b/src/logger/logger.ts index 2ac684ee51..9a54921d69 100644 --- a/src/logger/logger.ts +++ b/src/logger/logger.ts @@ -4,8 +4,8 @@ * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import * as os from 'os'; -import * as path from 'path'; +import * as os from 'node:os'; +import * as path from 'node:path'; import { Logger as PinoLogger, pino } from 'pino'; import { Env } from '@salesforce/kit'; @@ -171,7 +171,7 @@ export class Logger { level, enabled, }; - if (options.useMemoryLogger || Global.getEnvironmentMode() === Mode.TEST || !enabled) { + if (Boolean(options.useMemoryLogger) || Global.getEnvironmentMode() === Mode.TEST || !enabled) { this.memoryLogger = new MemoryLogger(); this.pinoLogger = pino( { diff --git a/src/logger/memoryLogger.ts b/src/logger/memoryLogger.ts index b077eb7cef..a88be2c61a 100644 --- a/src/logger/memoryLogger.ts +++ b/src/logger/memoryLogger.ts @@ -4,7 +4,7 @@ * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import { Writable } from 'stream'; +import { Writable } from 'node:stream'; import { unwrapArray } from '../util/unwrapArray'; import { filterSecrets } from './filters'; diff --git a/src/logger/transformStream.ts b/src/logger/transformStream.ts index f0e02bb96a..e813204be4 100644 --- a/src/logger/transformStream.ts +++ b/src/logger/transformStream.ts @@ -4,7 +4,7 @@ * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import { pipeline, Transform } from 'stream'; +import { pipeline, Transform } from 'node:stream'; import { unwrapArray } from '../util/unwrapArray'; import { filterSecrets } from './filters'; diff --git a/src/messages.ts b/src/messages.ts index 4a162bc8f9..5b40c5ef24 100644 --- a/src/messages.ts +++ b/src/messages.ts @@ -5,10 +5,10 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; -import * as util from 'util'; +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import * as util from 'node:util'; import { AnyJson, asString, ensureJsonMap, ensureString, isJsonMap, isObject } from '@salesforce/ts-types'; import { ensureArray, NamedError, upperFirst } from '@salesforce/kit'; import { SfError } from './sfError'; @@ -544,7 +544,7 @@ export class Messages { // 'myMessage' -> `MyMessageWarning` // 'myMessageError' -> `MyMessageError` // 'warning.myMessage' -> `MyMessageWarning` - const name = `${upperFirst(key.replace(searchValue, ''))}${labelRegExp.exec(key) || preserveName ? '' : label}`; + const name = `${upperFirst(key.replace(searchValue, ''))}${labelRegExp.exec(key) ?? preserveName ? '' : label}`; const message = this.getMessage(key, tokens); let actions; try { diff --git a/src/org/authInfo.ts b/src/org/authInfo.ts index 262bf1f6a1..ed837cfedd 100644 --- a/src/org/authInfo.ts +++ b/src/org/authInfo.ts @@ -6,10 +6,10 @@ */ /* eslint-disable class-methods-use-this */ -import { randomBytes } from 'crypto'; -import { resolve as pathResolve } from 'path'; -import * as os from 'os'; -import * as fs from 'fs'; +import { randomBytes } from 'node:crypto'; +import { resolve as pathResolve } from 'node:path'; +import * as os from 'node:os'; +import * as fs from 'node:fs'; import { Record as RecordType } from 'jsforce'; import { AsyncOptionalCreatable, cloneJson, env, isEmpty, parseJson, parseJsonMap } from '@salesforce/kit'; import { @@ -394,7 +394,7 @@ export class AuthInfo extends AsyncOptionalCreatable { const logger = await Logger.child('Common', { tag: 'identifyPossibleScratchOrgs' }); // return if we already know the hub org, we know it is a devhub or prod-like, or no orgId present - if (fields.isDevHub || fields.devHubUsername || !fields.orgId) return; + if (Boolean(fields.isDevHub) || Boolean(fields.devHubUsername) || !fields.orgId) return; logger.debug('getting devHubs and prod orgs to identify scratch orgs and sandboxes'); @@ -715,7 +715,7 @@ export class AuthInfo extends AsyncOptionalCreatable { */ public async handleAliasAndDefaultSettings(sideEffects: AuthSideEffects): Promise { if ( - sideEffects.alias || + Boolean(sideEffects.alias) || sideEffects.setDefault || sideEffects.setDefaultDevHub || typeof sideEffects.setTracksSource === 'boolean' diff --git a/src/org/connection.ts b/src/org/connection.ts index 19680cfb22..cbc5838a77 100644 --- a/src/org/connection.ts +++ b/src/org/connection.ts @@ -7,7 +7,7 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ -import { URL } from 'url'; +import { URL } from 'node:url'; import { AsyncResult, DeployOptions, DeployResultLocator } from 'jsforce/api/metadata'; import { Duration, env, maxBy } from '@salesforce/kit'; import { asString, ensure, isString, JsonMap, Optional } from '@salesforce/ts-types'; diff --git a/src/org/org.ts b/src/org/org.ts index e434db4bcd..1382094343 100644 --- a/src/org/org.ts +++ b/src/org/org.ts @@ -6,8 +6,8 @@ */ /* eslint-disable class-methods-use-this */ -import { join as pathJoin } from 'path'; -import * as fs from 'fs'; +import { join as pathJoin } from 'node:path'; +import * as fs from 'node:fs'; import { AsyncOptionalCreatable, Duration } from '@salesforce/kit'; import { AnyFunction, diff --git a/src/org/orgConfigProperties.ts b/src/org/orgConfigProperties.ts index 9be5d9d200..7a68256098 100644 --- a/src/org/orgConfigProperties.ts +++ b/src/org/orgConfigProperties.ts @@ -5,7 +5,7 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import { join as pathJoin } from 'path'; +import { join as pathJoin } from 'node:path'; import { isString } from '@salesforce/ts-types'; import { ConfigValue } from '../config/configStackTypes'; import { Messages } from '../messages'; diff --git a/src/org/permissionSetAssignment.ts b/src/org/permissionSetAssignment.ts index bb3cbfeff4..663b5cdf29 100644 --- a/src/org/permissionSetAssignment.ts +++ b/src/org/permissionSetAssignment.ts @@ -5,7 +5,7 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import { EOL } from 'os'; +import { EOL } from 'node:os'; import { mapKeys, upperFirst } from '@salesforce/kit'; import type { Optional } from '@salesforce/ts-types'; import type { QueryResult, Record } from 'jsforce'; diff --git a/src/org/scratchOrgInfoGenerator.ts b/src/org/scratchOrgInfoGenerator.ts index 0b03001fad..de0302d988 100644 --- a/src/org/scratchOrgInfoGenerator.ts +++ b/src/org/scratchOrgInfoGenerator.ts @@ -4,7 +4,7 @@ * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import { promises as fs } from 'fs'; +import { promises as fs } from 'node:fs'; import { parseJson } from '@salesforce/kit'; import { ensureString } from '@salesforce/ts-types'; import { SfProjectJson } from '../sfProject'; diff --git a/src/org/scratchOrgSettingsGenerator.ts b/src/org/scratchOrgSettingsGenerator.ts index e5b9460af2..575bf0c421 100644 --- a/src/org/scratchOrgSettingsGenerator.ts +++ b/src/org/scratchOrgSettingsGenerator.ts @@ -5,7 +5,7 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import * as path from 'path'; +import * as path from 'node:path'; import { isEmpty, env, upperFirst, Duration } from '@salesforce/kit'; import { ensureObject, JsonMap } from '@salesforce/ts-types'; import * as js2xmlparser from 'js2xmlparser'; @@ -291,7 +291,7 @@ export default class SettingsGenerator { fullName: string; problem: string; }; - if (status !== RequestStatus.Succeeded) { + if (status !== RequestStatus.Succeeded.toString()) { const componentFailures = ensureObject<{ componentFailures: FailureMessage | FailureMessage[]; }>(result.details).componentFailures; diff --git a/src/org/user.ts b/src/org/user.ts index 718df9bcb2..e0edb73d7a 100644 --- a/src/org/user.ts +++ b/src/org/user.ts @@ -5,7 +5,7 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import { EOL } from 'os'; +import { EOL } from 'node:os'; import { AsyncCreatable, lowerFirst, mapKeys, omit, parseJsonMap, upperFirst } from '@salesforce/kit'; import { asJsonArray, asNumber, ensureJsonMap, ensureString, isJsonMap, Many } from '@salesforce/ts-types'; import type { HttpRequest, HttpResponse, QueryResult, Schema, SObjectUpdateRecord } from 'jsforce'; diff --git a/src/schema/validator.ts b/src/schema/validator.ts index 2d16f9406c..ab4baa6078 100644 --- a/src/schema/validator.ts +++ b/src/schema/validator.ts @@ -5,8 +5,8 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import * as path from 'path'; -import * as fs from 'fs'; +import * as path from 'node:path'; +import * as fs from 'node:fs'; import Ajv, { DefinedError } from 'ajv'; import { AnyJson, JsonMap } from '@salesforce/ts-types'; import { getJsonValuesByName, parseJsonMap } from '@salesforce/kit'; diff --git a/src/sfError.ts b/src/sfError.ts index 4ffc2cb594..c7af7a4043 100644 --- a/src/sfError.ts +++ b/src/sfError.ts @@ -75,7 +75,7 @@ export class SfError extends NamedError { } // eslint-disable-next-line @typescript-eslint/no-explicit-any - public get code(): string | undefined | any { + public get code(): any { return this.#code ?? this.name; } diff --git a/src/sfProject.ts b/src/sfProject.ts index a0ef9c9ef0..1161641c5a 100644 --- a/src/sfProject.ts +++ b/src/sfProject.ts @@ -4,8 +4,8 @@ * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import { basename, dirname, isAbsolute, normalize, resolve, sep } from 'path'; -import * as fs from 'fs'; +import { basename, dirname, isAbsolute, normalize, resolve, sep } from 'node:path'; +import * as fs from 'node:fs'; import { defaults, env } from '@salesforce/kit'; import { Dictionary, ensure, JsonMap, Nullable, Optional } from '@salesforce/ts-types'; import { SfdcUrl } from './util/sfdcUrl'; diff --git a/src/stateAggregator/accessors/orgAccessor.ts b/src/stateAggregator/accessors/orgAccessor.ts index 21ecb7e394..3a44bc8e89 100644 --- a/src/stateAggregator/accessors/orgAccessor.ts +++ b/src/stateAggregator/accessors/orgAccessor.ts @@ -5,8 +5,8 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import * as fs from 'fs'; -import * as path from 'path'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; import { Nullable, entriesOf } from '@salesforce/ts-types'; import { AsyncOptionalCreatable, isEmpty } from '@salesforce/kit'; import { AuthInfoConfig } from '../../config/authInfoConfig'; diff --git a/src/status/myDomainResolver.ts b/src/status/myDomainResolver.ts index 25dc42ceea..b93574c568 100644 --- a/src/status/myDomainResolver.ts +++ b/src/status/myDomainResolver.ts @@ -5,9 +5,9 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import { lookup, resolveCname } from 'dns'; -import { URL } from 'url'; -import { promisify } from 'util'; +import { lookup, resolveCname } from 'node:dns'; +import { URL } from 'node:url'; +import { promisify } from 'node:util'; import { ensureString } from '@salesforce/ts-types'; diff --git a/src/status/streamingClient.ts b/src/status/streamingClient.ts index 700a911555..248b6d8719 100644 --- a/src/status/streamingClient.ts +++ b/src/status/streamingClient.ts @@ -7,7 +7,7 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ -import { resolve as resolveUrl } from 'url'; +import { resolve as resolveUrl } from 'node:url'; import { AsyncOptionalCreatable, Duration, Env, env, set } from '@salesforce/kit/lib'; import { AnyFunction, AnyJson, ensure, ensureString, JsonMap } from '@salesforce/ts-types/lib'; import * as Faye from 'faye'; @@ -215,7 +215,7 @@ export class StreamingClient extends AsyncOptionalCreatable { - let timeout: NodeJS.Timer; + let timeout: NodeJS.Timeout; return new Promise((resolve, reject) => { timeout = setTimeout(() => { @@ -243,7 +243,7 @@ export class StreamingClient extends AsyncOptionalCreatable Promise): Promise { - let timeout: NodeJS.Timer; + let timeout: NodeJS.Timeout; // This outer promise is to hold the streaming promise chain open until the streaming processor // says it's complete. @@ -323,7 +323,7 @@ export class StreamingClient extends AsyncOptionalCreatable} The resolved ip address or never * @throws {@link SfError} If can't resolve DNS. */ - public async checkLightningDomain(): Promise { + public async checkLightningDomain(): Promise { const quantity = ensureNumber(new Env().getNumber('SFDX_DOMAIN_RETRY', 240)); const timeout = new Duration(quantity, Duration.Unit.SECONDS); diff --git a/src/util/structuredWriter.ts b/src/util/structuredWriter.ts index 119c095a48..5f8fad2cea 100644 --- a/src/util/structuredWriter.ts +++ b/src/util/structuredWriter.ts @@ -4,7 +4,7 @@ * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import { Readable } from 'stream'; +import { Readable } from 'node:stream'; export interface StructuredWriter { addToStore(contents: string | Readable | Buffer, path: string): Promise; diff --git a/src/util/uniqid.ts b/src/util/uniqid.ts index 7ac2f3d628..f64fd759d5 100644 --- a/src/util/uniqid.ts +++ b/src/util/uniqid.ts @@ -4,8 +4,8 @@ * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import { randomBytes } from 'crypto'; -import * as util from 'util'; +import { randomBytes } from 'node:crypto'; +import * as util from 'node:util'; /** * A function to generate a unique id and return it in the context of a template, if supplied. diff --git a/src/util/zipWriter.ts b/src/util/zipWriter.ts index a53bf89f1b..aad397ba92 100644 --- a/src/util/zipWriter.ts +++ b/src/util/zipWriter.ts @@ -5,7 +5,7 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import { Readable, Writable } from 'stream'; +import { Readable, Writable } from 'node:stream'; import * as JSZip from 'jszip'; import { Logger } from '../logger/logger'; import { SfError } from '../sfError'; diff --git a/src/webOAuthServer.ts b/src/webOAuthServer.ts index 10c1dd6f33..dc8841774e 100644 --- a/src/webOAuthServer.ts +++ b/src/webOAuthServer.ts @@ -7,11 +7,11 @@ /* eslint-disable class-methods-use-this */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ -import * as http from 'http'; -import { parse as parseQueryString } from 'querystring'; -import { parse as parseUrl } from 'url'; -import { Socket } from 'net'; -import { EventEmitter } from 'events'; +import * as http from 'node:http'; +import { parse as parseQueryString } from 'node:querystring'; +import { parse as parseUrl } from 'node:url'; +import { Socket } from 'node:net'; +import { EventEmitter } from 'node:events'; import { OAuth2 } from 'jsforce'; import { AsyncCreatable, Env, set, toNumber } from '@salesforce/kit'; import { asString, get, Nullable } from '@salesforce/ts-types'; diff --git a/test/unit/config/configAggregatorTest.ts b/test/unit/config/configAggregatorTest.ts index fcb32a44cb..db7761f8db 100644 --- a/test/unit/config/configAggregatorTest.ts +++ b/test/unit/config/configAggregatorTest.ts @@ -6,7 +6,7 @@ */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-call */ -import * as fs from 'fs'; +import * as fs from 'node:fs'; import { assert, expect, config as chaiConfig } from 'chai'; import { Config, ConfigProperties, SFDX_ALLOWED_PROPERTIES, SfdxPropertyKeys } from '../../../src/config/config'; import { ConfigAggregator, ConfigInfo } from '../../../src/config/configAggregator'; diff --git a/test/unit/config/configFileTest.ts b/test/unit/config/configFileTest.ts index 07e4827804..b07ca95452 100644 --- a/test/unit/config/configFileTest.ts +++ b/test/unit/config/configFileTest.ts @@ -7,8 +7,8 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-call */ -import * as Path from 'path'; -import * as fs from 'fs'; +import * as Path from 'node:path'; +import * as fs from 'node:fs'; import { expect } from 'chai'; import { assert } from '@salesforce/ts-types'; diff --git a/test/unit/config/configTest.ts b/test/unit/config/configTest.ts index 0c0b27750b..a732a117ec 100644 --- a/test/unit/config/configTest.ts +++ b/test/unit/config/configTest.ts @@ -9,7 +9,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ -import * as fs from 'fs'; +import * as fs from 'node:fs'; import { stubMethod } from '@salesforce/ts-sinon'; import { ensureString, JsonMap } from '@salesforce/ts-types'; import { expect } from 'chai'; diff --git a/test/unit/crypto/cryptoKeyFailuresTest.ts b/test/unit/crypto/cryptoKeyFailuresTest.ts index f7af64c085..4525d24ca0 100644 --- a/test/unit/crypto/cryptoKeyFailuresTest.ts +++ b/test/unit/crypto/cryptoKeyFailuresTest.ts @@ -6,9 +6,9 @@ */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ -import * as childProcess from 'child_process'; -import * as _crypto from 'crypto'; -import * as os from 'os'; +import * as childProcess from 'node:child_process'; +import * as _crypto from 'node:crypto'; +import * as os from 'node:os'; import { AnyJson } from '@salesforce/ts-types'; import { assert, expect } from 'chai'; import { Crypto } from '../../../src/crypto/crypto'; diff --git a/test/unit/crypto/cryptoTest.ts b/test/unit/crypto/cryptoTest.ts index 02cc8f1f05..461c018e4f 100644 --- a/test/unit/crypto/cryptoTest.ts +++ b/test/unit/crypto/cryptoTest.ts @@ -6,7 +6,7 @@ */ /* eslint-disable @typescript-eslint/ban-types */ -import * as os from 'os'; +import * as os from 'node:os'; import { stubMethod } from '@salesforce/ts-sinon'; import { expect } from 'chai'; import { Crypto } from '../../../src/crypto/crypto'; diff --git a/test/unit/crypto/keyChainImplTest.ts b/test/unit/crypto/keyChainImplTest.ts index c597dba2b7..fa2e370b63 100644 --- a/test/unit/crypto/keyChainImplTest.ts +++ b/test/unit/crypto/keyChainImplTest.ts @@ -11,7 +11,7 @@ /* eslint-disable no-underscore-dangle */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -import * as fs from 'fs'; +import * as fs from 'node:fs'; import { assert, expect } from 'chai'; import { KeychainAccess, keyChainImpl } from '../../../src/crypto/keyChainImpl'; import { shouldThrow, TestContext } from '../../../src/testSetup'; diff --git a/test/unit/crypto/keyChainTest.ts b/test/unit/crypto/keyChainTest.ts index 36d890cb5d..ce743232b9 100644 --- a/test/unit/crypto/keyChainTest.ts +++ b/test/unit/crypto/keyChainTest.ts @@ -5,11 +5,10 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import * as path from 'path'; -import * as fs from 'fs'; +import * as path from 'node:path'; +import * as fs from 'node:fs'; import { Nullable } from '@salesforce/ts-types'; import { expect } from 'chai'; -import * as _ from 'lodash'; import { retrieveKeychain } from '../../../src/crypto/keyChain'; import { GenericUnixKeychainAccess, @@ -36,7 +35,7 @@ describe('keyChain', () => { process.env.USE_GENERIC_UNIX_KEYCHAIN = OLD_GENERIC_VAL ?? ''; }); - it('should return OSX keychain and lib secret', () => { + it('should return OSX keychain and lib secret', async () => { $$.SANDBOX.stub(keyChainImpl.linux, 'validateProgram').resolves(); const testArray = [ @@ -47,18 +46,26 @@ describe('keyChain', () => { }, ]; - const promiseArray = testArray.map((obj) => retrieveKeychain(obj.osName)); - - return Promise.all(promiseArray).then((_keychains) => { - _.forEach(_keychains, (_keychain: any) => { - expect(_keychain).to.have.property('osImpl'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call - const program = _keychain['osImpl'].getProgram(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call - const testArrayMeta = _.find(testArray, (elem: any) => program.includes(elem.validateString)); - expect(testArrayMeta == null).to.be.false; - }); + const keyChains = await Promise.all(testArray.map((obj) => retrieveKeychain(obj.osName))); + keyChains.map((kc) => { + expect(kc).to.have.property('osImpl'); + // @ts-expect-error osImpl is a private member + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call + const program = kc['osImpl'].getProgram(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call + const testArrayMeta = testArray.find((elem) => program.includes(elem.validateString)); + expect(testArrayMeta == null).to.be.false; }); + // return Promise.all(promiseArray).then((_keychains) => { + // _.forEach(_keychains, (_keychain: any) => { + // expect(_keychain).to.have.property('osImpl'); + // // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call + // const program = _keychain['osImpl'].getProgram(); + // // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call + // const testArrayMeta = _.find(testArray, (elem: any) => program.includes(elem.validateString)); + // expect(testArrayMeta == null).to.be.false; + // }); + // }); }); it('should return generic unix for OSX and Linux', async () => { diff --git a/test/unit/crypto/secureStringTest.ts b/test/unit/crypto/secureStringTest.ts index e7548433ef..9eb6e2752f 100644 --- a/test/unit/crypto/secureStringTest.ts +++ b/test/unit/crypto/secureStringTest.ts @@ -4,7 +4,7 @@ * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import * as crypto from 'crypto'; +import * as crypto from 'node:crypto'; import { expect } from 'chai'; import { stub } from 'sinon'; import { SecureBuffer } from '../../../src/crypto/secureBuffer'; diff --git a/test/unit/messagesTest.ts b/test/unit/messagesTest.ts index e283481a54..fa53054305 100644 --- a/test/unit/messagesTest.ts +++ b/test/unit/messagesTest.ts @@ -7,9 +7,9 @@ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ -import * as fs from 'fs'; -import * as path from 'path'; -import { EOL } from 'os'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { EOL } from 'node:os'; import { cloneJson } from '@salesforce/kit'; import { assert, expect } from 'chai'; import { SinonStub } from 'sinon'; diff --git a/test/unit/org/authInfoTest.ts b/test/unit/org/authInfoTest.ts index e971e732cf..cbb41d5972 100644 --- a/test/unit/org/authInfoTest.ts +++ b/test/unit/org/authInfoTest.ts @@ -9,8 +9,8 @@ /* eslint-disable @typescript-eslint/ban-types */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ -import * as pathImport from 'path'; -import * as dns from 'dns'; +import * as pathImport from 'node:path'; +import * as dns from 'node:dns'; import * as jwt from 'jsonwebtoken'; import { cloneJson, env, includes } from '@salesforce/kit'; import { spyMethod, stubMethod } from '@salesforce/ts-sinon'; diff --git a/test/unit/org/orgTest.ts b/test/unit/org/orgTest.ts index f5d82a29c1..53879aff38 100644 --- a/test/unit/org/orgTest.ts +++ b/test/unit/org/orgTest.ts @@ -4,10 +4,10 @@ * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import { deepStrictEqual, fail } from 'assert'; -import * as fs from 'fs'; -import { constants as fsConstants } from 'fs'; -import { join as pathJoin } from 'path'; +import { deepStrictEqual, fail } from 'node:assert'; +import * as fs from 'node:fs'; +import { constants as fsConstants } from 'node:fs'; +import { join as pathJoin } from 'node:path'; import { format } from 'node:util'; import { Duration, set } from '@salesforce/kit'; import { spyMethod, stubMethod } from '@salesforce/ts-sinon'; diff --git a/test/unit/org/scratchOrgInfoGeneratorTest.ts b/test/unit/org/scratchOrgInfoGeneratorTest.ts index 52e2e3f0bf..e8c5d4f9b0 100644 --- a/test/unit/org/scratchOrgInfoGeneratorTest.ts +++ b/test/unit/org/scratchOrgInfoGeneratorTest.ts @@ -5,7 +5,7 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import * as fs from 'fs'; +import * as fs from 'node:fs'; import * as sinon from 'sinon'; import { expect } from 'chai'; import { stubMethod, stubInterface } from '@salesforce/ts-sinon'; diff --git a/test/unit/org/scratchOrgSettingsGeneratorTest.ts b/test/unit/org/scratchOrgSettingsGeneratorTest.ts index eb33424623..9d137f5080 100644 --- a/test/unit/org/scratchOrgSettingsGeneratorTest.ts +++ b/test/unit/org/scratchOrgSettingsGeneratorTest.ts @@ -5,7 +5,7 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import * as path from 'path'; +import * as path from 'node:path'; import * as sinon from 'sinon'; import { expect } from 'chai'; import { Org } from '../../../src/org/org'; diff --git a/test/unit/projectTest.ts b/test/unit/projectTest.ts index cf995ce199..24a7abe4d7 100644 --- a/test/unit/projectTest.ts +++ b/test/unit/projectTest.ts @@ -7,7 +7,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-call */ -import { join, sep } from 'path'; +import { join, sep } from 'node:path'; import { expect } from 'chai'; import { env } from '@salesforce/kit'; diff --git a/test/unit/schema/validatorTest.ts b/test/unit/schema/validatorTest.ts index 2ad950ce85..4730efffe9 100644 --- a/test/unit/schema/validatorTest.ts +++ b/test/unit/schema/validatorTest.ts @@ -4,8 +4,8 @@ * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import * as fs from 'fs'; -import * as path from 'path'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; import { AnyJson, isJsonMap, JsonCollection, JsonMap } from '@salesforce/ts-types'; import { expect } from 'chai'; import * as sinon from 'sinon'; diff --git a/test/unit/status/myDomainResolverTest.ts b/test/unit/status/myDomainResolverTest.ts index e9ac414ba8..c381323221 100644 --- a/test/unit/status/myDomainResolverTest.ts +++ b/test/unit/status/myDomainResolverTest.ts @@ -4,8 +4,8 @@ * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import * as dns from 'dns'; -import { URL } from 'url'; +import * as dns from 'node:dns'; +import { URL } from 'node:url'; import { Duration, Env } from '@salesforce/kit'; import { AnyFunction } from '@salesforce/ts-types'; import { expect } from 'chai'; diff --git a/test/unit/util/directoryWriterTest.ts b/test/unit/util/directoryWriterTest.ts index 1d4a8bda23..5af625ca92 100644 --- a/test/unit/util/directoryWriterTest.ts +++ b/test/unit/util/directoryWriterTest.ts @@ -4,10 +4,10 @@ * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import * as os from 'os'; -import * as fs from 'fs'; -import * as path from 'path'; -import { Readable } from 'stream'; +import * as os from 'node:os'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { Readable } from 'node:stream'; import * as chai from 'chai'; import * as chaiString from 'chai-string'; import { DirectoryWriter } from '../../../src/util/directoryWriter'; diff --git a/test/unit/util/zipWriterTest.ts b/test/unit/util/zipWriterTest.ts index 4ff6fe4821..94427b834e 100644 --- a/test/unit/util/zipWriterTest.ts +++ b/test/unit/util/zipWriterTest.ts @@ -4,9 +4,9 @@ * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import * as os from 'os'; -import * as fs from 'fs'; -import * as path from 'path'; +import * as os from 'node:os'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; import { expect } from 'chai'; import * as sinon from 'sinon'; import * as JSZip from 'jszip'; diff --git a/test/unit/webOauthServerTest.ts b/test/unit/webOauthServerTest.ts index f193cf08e8..4f2749e996 100644 --- a/test/unit/webOauthServerTest.ts +++ b/test/unit/webOauthServerTest.ts @@ -6,7 +6,7 @@ */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ -import * as http from 'http'; +import * as http from 'node:http'; import { expect } from 'chai'; import { assert } from '@salesforce/ts-types'; diff --git a/yarn.lock b/yarn.lock index a5f146ee72..816d5aa61e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -122,10 +122,10 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-validator-identifier@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" - integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== +"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== "@babel/helper-validator-option@^7.18.6": version "7.18.6" @@ -371,26 +371,26 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@es-joy/jsdoccomment@~0.38.0": - version "0.38.0" - resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.38.0.tgz#2e74f8d824b4a4ec831eaabd4c3548fb11eae5cd" - integrity sha512-TFac4Bnv0ZYNkEeDnOWHQhaS1elWlvOCQxH06iHeu5iffs+hCaLVIZJwF+FqksQi68R4i66Pu+4DfFGvble+Uw== +"@es-joy/jsdoccomment@~0.40.1": + version "0.40.1" + resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.40.1.tgz#13acd77fb372ed1c83b7355edd865a3b370c9ec4" + integrity sha512-YORCdZSusAlBrFpZ77pJjc5r1bQs5caPWtAu+WWmiSo+8XaUzseapVrfAtiRFbQWnrBxxLLEwF6f6ZG/UgCQCg== dependencies: - comment-parser "1.3.1" + comment-parser "1.4.0" esquery "^1.5.0" jsdoc-type-pratt-parser "~4.0.0" -"@eslint-community/eslint-utils@^4.2.0": - version "4.3.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.3.0.tgz#a556790523a351b4e47e9d385f47265eaaf9780a" - integrity sha512-v3oplH6FYCULtFuCeqyuTd9D2WKO937Dxdq+GmHOLL72TTRriLxz2VLlNfkZRsvj6PKnOPAtuT6dwrs/pA5DvA== +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== dependencies: eslint-visitor-keys "^3.3.0" -"@eslint-community/regexpp@^4.4.0", "@eslint-community/regexpp@^4.6.1": - version "4.6.2" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.6.2.tgz#1816b5f6948029c5eaacb0703b850ee0cb37d8f8" - integrity sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw== +"@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": + version "4.10.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" + integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== "@eslint/eslintrc@^2.1.3": version "2.1.3" @@ -516,49 +516,41 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@salesforce/dev-config@^4.0.1", "@salesforce/dev-config@^4.1.0": +"@salesforce/dev-config@^4.0.1": version "4.1.0" resolved "https://registry.yarnpkg.com/@salesforce/dev-config/-/dev-config-4.1.0.tgz#e529576466d074e7a5f1441236510fef123da01e" integrity sha512-2iDDepiIwjXHS5IVY7pwv8jMo4xWosJ7p/UTj+lllpB/gnJiYLhjJPE4Z3FCGFKyvfg5jGaimCd8Ca6bLGsCQA== -"@salesforce/dev-scripts@^5.11.0": - version "5.11.0" - resolved "https://registry.yarnpkg.com/@salesforce/dev-scripts/-/dev-scripts-5.11.0.tgz#e5632f0e46f2d821710ca06bb6d60378399b17cd" - integrity sha512-DLgjqBsYc0AiBb5BPiSMSJrwoP9ceAFePPcB6xvLrH9gas+8X3z79vc4xzlBhwzsF1WJsSOoVVTtJbPDwxvF0g== +"@salesforce/dev-scripts@^6.0.3": + version "6.0.3" + resolved "https://registry.yarnpkg.com/@salesforce/dev-scripts/-/dev-scripts-6.0.3.tgz#6cf4504fd0ec8b4685e729b26685eed60f9c8b26" + integrity sha512-WLl1N07oNeRywdypwUrebX/kCkSm3IzmAQpUt4q4Sk8r4vTWv5b6F0pHLv0pGS8/QWNJT7xWGZDF1lgJBHOsmA== dependencies: "@commitlint/cli" "^17.1.2" "@commitlint/config-conventional" "^17.1.0" "@salesforce/dev-config" "^4.0.1" "@salesforce/prettier-config" "^0.0.3" "@types/chai" "^4.2.11" - "@types/mocha" "^9.0.0" - "@types/node" "^15.6.1" - "@types/sinon" "10.0.11" - chai "^4.3.8" + "@types/mocha" "^10.0.3" + "@types/node" "^18" + "@types/sinon" "^10.0.20" + chai "^4.3.10" chalk "^4.0.0" cosmiconfig "^7.0.0" - eslint "^8.41.0" - eslint-config-prettier "^8.8.0" - eslint-config-salesforce "^2.0.1" - eslint-config-salesforce-license "^0.2.0" - eslint-config-salesforce-typescript "^1.1.1" - eslint-plugin-header "^3.1.1" - eslint-plugin-import "^2.27.5" - eslint-plugin-jsdoc "^43.0.5" - eslint-plugin-prefer-arrow "^1.2.1" + eslint-config-salesforce-typescript "^3.0.1" husky "^7.0.4" - mocha "^9.1.3" + mocha "^10.2.0" nyc "^15.1.0" - prettier "^2.7.1" + prettier "^2.8.8" pretty-quick "^3.1.0" shelljs "~0.8.4" sinon "10.0.0" source-map-support "~0.5.19" - ts-node "^10.0.0" + ts-node "^10.9.1" typedoc "0.23.16" typedoc-plugin-missing-exports "0.23.0" typescript "^4.9.5" - wireit "^0.9.5" + wireit "^0.14.1" "@salesforce/kit@^3.0.15": version "3.0.15" @@ -601,13 +593,6 @@ dependencies: type-detect "4.0.8" -"@sinonjs/commons@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-2.0.0.tgz#fd4ca5b063554307e8327b4564bd56d3b73924a3" - integrity sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg== - dependencies: - type-detect "4.0.8" - "@sinonjs/fake-timers@^6.0.0", "@sinonjs/fake-timers@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40" @@ -615,20 +600,6 @@ dependencies: "@sinonjs/commons" "^1.7.0" -"@sinonjs/fake-timers@^7.0.4": - version "7.1.2" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz#2524eae70c4910edccf99b2f4e6efc5894aff7b5" - integrity sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg== - dependencies: - "@sinonjs/commons" "^1.7.0" - -"@sinonjs/fake-timers@^9.1.2": - version "9.1.2" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz#4eaab737fab77332ab132d396a3c0d364bd0ea8c" - integrity sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw== - dependencies: - "@sinonjs/commons" "^1.7.0" - "@sinonjs/formatio@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-2.0.0.tgz#84db7e9eb5531df18a8c5e0bfb6e449e55e654b2" @@ -662,15 +633,6 @@ lodash.get "^4.4.2" type-detect "^4.0.8" -"@sinonjs/samsam@^7.0.1": - version "7.0.1" - resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-7.0.1.tgz#5b5fa31c554636f78308439d220986b9523fc51f" - integrity sha512-zsAk2Jkiq89mhZovB2LLOdTCxJF4hqqTToGP0ASWlhp4I1hqOjcfmZGafXntCN7MDC6yySH0mFHrYtHceOeLmw== - dependencies: - "@sinonjs/commons" "^2.0.0" - lodash.get "^4.4.2" - type-detect "^4.0.8" - "@sinonjs/text-encoding@^0.7.1": version "0.7.1" resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" @@ -713,18 +675,10 @@ resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.1.tgz#e2c6e73e0bdeb2521d00756d099218e9f5d90a04" integrity sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ== -"@types/glob@~7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" - integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== - dependencies: - "@types/minimatch" "*" - "@types/node" "*" - -"@types/json-schema@^7.0.9": - version "7.0.11" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" - integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== +"@types/json-schema@^7.0.12": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== "@types/json5@^0.0.29": version "0.0.29" @@ -738,12 +692,7 @@ dependencies: "@types/node" "*" -"@types/lodash@^4.14.200": - version "4.14.200" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.200.tgz#435b6035c7eba9cdf1e039af8212c9e9281e7149" - integrity sha512-YI/M/4HRImtNf3pJgbF+W6FrXovqj+T+/HpENLTooK9PnkacBsDpeP3IpHab40CClUfhNmdM2WTNP2sa2dni5Q== - -"@types/minimatch@*", "@types/minimatch@^3.0.3": +"@types/minimatch@^3.0.3": version "3.0.5" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== @@ -753,26 +702,23 @@ resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== -"@types/mocha@^9.0.0": - version "9.1.1" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.1.tgz#e7c4f1001eefa4b8afbd1eee27a237fee3bf29c4" - integrity sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw== +"@types/mocha@^10.0.3": + version "10.0.4" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.4.tgz#b5331955ebca216604691fd4fcd2dbdc2bd559a4" + integrity sha512-xKU7bUjiFTIttpWaIZ9qvgg+22O1nmbA+HRxdlR+u6TWsGfmFdXrheJoK4fFxrHNVIOBDvDNKZG+LYBpMHpX3w== -"@types/node@*": - version "18.0.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.3.tgz#463fc47f13ec0688a33aec75d078a0541a447199" - integrity sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ== +"@types/node@*", "@types/node@^18": + version "18.18.9" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.18.9.tgz#5527ea1832db3bba8eb8023ce8497b7d3f299592" + integrity sha512-0f5klcuImLnG4Qreu9hPj/rEfFq6YRc5n2mAjSsH+ec/mJL+3voBH0+8T7o8RpFjH7ovc+TRsL/c7OYIQsPTfQ== + dependencies: + undici-types "~5.26.4" "@types/node@^12.19.9": version "12.20.55" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== -"@types/node@^15.6.1": - version "15.14.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-15.14.9.tgz#bc43c990c3c9be7281868bbc7b8fdd6e2b57adfa" - integrity sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A== - "@types/normalize-package-data@^2.4.0": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" @@ -795,23 +741,15 @@ resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.2.tgz#ed279a64fa438bb69f2480eda44937912bb7480a" integrity sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow== -"@types/semver@^7.3.12", "@types/semver@^7.5.4": - version "7.5.4" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.4.tgz#0a41252ad431c473158b22f9bfb9a63df7541cff" - integrity sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ== +"@types/semver@^7.5.0", "@types/semver@^7.5.4": + version "7.5.5" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.5.tgz#deed5ab7019756c9c90ea86139106b0346223f35" + integrity sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg== -"@types/shelljs@0.8.13": - version "0.8.13" - resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.8.13.tgz#a94bf7f2b82b7cd9f4496bbe063c3adb0868a650" - integrity sha512-++uMLOQSLlse1kCfEOwhgmHuaABZwinkylmUKCpvcEGZUov3TtM+gJZloSkW/W+9pEAEg/VBOwiSR05oqJsa5A== - dependencies: - "@types/glob" "~7.2.0" - "@types/node" "*" - -"@types/sinon@10.0.11": - version "10.0.11" - resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-10.0.11.tgz#8245827b05d3fc57a6601bd35aee1f7ad330fc42" - integrity sha512-dmZsHlBsKUtBpHriNjlK0ndlvEh8dcb9uV9Afsbt89QIyydpC7NcR+nWlAhASfy3GHnxTl4FX/aKE7XZUt/B4g== +"@types/sinon@^10.0.20": + version "10.0.20" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-10.0.20.tgz#f1585debf4c0d99f9938f4111e5479fb74865146" + integrity sha512-2APKKruFNCAZgx3daAyACGzWuJ028VVCUDk6o2rw/Z4PXT0ogwdV4KUegW0MwVs0Zu59auPXbbuBJHF12Sx1Eg== dependencies: "@types/sinonjs__fake-timers" "*" @@ -820,94 +758,90 @@ resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz#bf2e02a3dbd4aecaf95942ecd99b7402e03fad5e" integrity sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA== -"@typescript-eslint/eslint-plugin@^5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz#aeef0328d172b9e37d9bab6dbc13b87ed88977db" - integrity sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag== +"@typescript-eslint/eslint-plugin@^6.9.1": + version "6.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.10.0.tgz#cfe2bd34e26d2289212946b96ab19dcad64b661a" + integrity sha512-uoLj4g2OTL8rfUQVx2AFO1hp/zja1wABJq77P6IclQs6I/m9GLrm7jCdgzZkvWdDCQf1uEvoa8s8CupsgWQgVg== dependencies: - "@eslint-community/regexpp" "^4.4.0" - "@typescript-eslint/scope-manager" "5.62.0" - "@typescript-eslint/type-utils" "5.62.0" - "@typescript-eslint/utils" "5.62.0" + "@eslint-community/regexpp" "^4.5.1" + "@typescript-eslint/scope-manager" "6.10.0" + "@typescript-eslint/type-utils" "6.10.0" + "@typescript-eslint/utils" "6.10.0" + "@typescript-eslint/visitor-keys" "6.10.0" debug "^4.3.4" graphemer "^1.4.0" - ignore "^5.2.0" - natural-compare-lite "^1.4.0" - semver "^7.3.7" - tsutils "^3.21.0" - -"@typescript-eslint/parser@^5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.62.0.tgz#1b63d082d849a2fcae8a569248fbe2ee1b8a56c7" - integrity sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA== - dependencies: - "@typescript-eslint/scope-manager" "5.62.0" - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/typescript-estree" "5.62.0" + ignore "^5.2.4" + natural-compare "^1.4.0" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/parser@^6.9.1": + version "6.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.10.0.tgz#578af79ae7273193b0b6b61a742a2bc8e02f875a" + integrity sha512-+sZwIj+s+io9ozSxIWbNB5873OSdfeBEH/FR0re14WLI6BaKuSOnnwCJ2foUiu8uXf4dRp1UqHP0vrZ1zXGrog== + dependencies: + "@typescript-eslint/scope-manager" "6.10.0" + "@typescript-eslint/types" "6.10.0" + "@typescript-eslint/typescript-estree" "6.10.0" + "@typescript-eslint/visitor-keys" "6.10.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" - integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w== +"@typescript-eslint/scope-manager@6.10.0": + version "6.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.10.0.tgz#b0276118b13d16f72809e3cecc86a72c93708540" + integrity sha512-TN/plV7dzqqC2iPNf1KrxozDgZs53Gfgg5ZHyw8erd6jd5Ta/JIEcdCheXFt9b1NYb93a1wmIIVW/2gLkombDg== dependencies: - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/visitor-keys" "5.62.0" + "@typescript-eslint/types" "6.10.0" + "@typescript-eslint/visitor-keys" "6.10.0" -"@typescript-eslint/type-utils@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a" - integrity sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew== +"@typescript-eslint/type-utils@6.10.0": + version "6.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.10.0.tgz#1007faede067c78bdbcef2e8abb31437e163e2e1" + integrity sha512-wYpPs3hgTFblMYwbYWPT3eZtaDOjbLyIYuqpwuLBBqhLiuvJ+9sEp2gNRJEtR5N/c9G1uTtQQL5AhV0fEPJYcg== dependencies: - "@typescript-eslint/typescript-estree" "5.62.0" - "@typescript-eslint/utils" "5.62.0" + "@typescript-eslint/typescript-estree" "6.10.0" + "@typescript-eslint/utils" "6.10.0" debug "^4.3.4" - tsutils "^3.21.0" + ts-api-utils "^1.0.1" -"@typescript-eslint/types@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" - integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== +"@typescript-eslint/types@6.10.0": + version "6.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.10.0.tgz#f4f0a84aeb2ac546f21a66c6e0da92420e921367" + integrity sha512-36Fq1PWh9dusgo3vH7qmQAj5/AZqARky1Wi6WpINxB6SkQdY5vQoT2/7rW7uBIsPDcvvGCLi4r10p0OJ7ITAeg== -"@typescript-eslint/typescript-estree@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" - integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== +"@typescript-eslint/typescript-estree@6.10.0": + version "6.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.10.0.tgz#667381eed6f723a1a8ad7590a31f312e31e07697" + integrity sha512-ek0Eyuy6P15LJVeghbWhSrBCj/vJpPXXR+EpaRZqou7achUWL8IdYnMSC5WHAeTWswYQuP2hAZgij/bC9fanBg== dependencies: - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/visitor-keys" "5.62.0" + "@typescript-eslint/types" "6.10.0" + "@typescript-eslint/visitor-keys" "6.10.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" - semver "^7.3.7" - tsutils "^3.21.0" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/utils@6.10.0": + version "6.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.10.0.tgz#4d76062d94413c30e402c9b0df8c14aef8d77336" + integrity sha512-v+pJ1/RcVyRc0o4wAGux9x42RHmAjIGzPRo538Z8M1tVx6HOnoQBCX/NoadHQlZeC+QO2yr4nNSFWOoraZCAyg== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@types/json-schema" "^7.0.12" + "@types/semver" "^7.5.0" + "@typescript-eslint/scope-manager" "6.10.0" + "@typescript-eslint/types" "6.10.0" + "@typescript-eslint/typescript-estree" "6.10.0" + semver "^7.5.4" -"@typescript-eslint/utils@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" - integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== +"@typescript-eslint/visitor-keys@6.10.0": + version "6.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.10.0.tgz#b9eaf855a1ac7e95633ae1073af43d451e8f84e3" + integrity sha512-xMGluxQIEtOM7bqFCo+rCMh5fqI+ZxV5RUUOa29iVPz1OgCZrtc7rFnz5cLUazlkPKYqX+75iuDq7m0HQ48nCg== dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@types/json-schema" "^7.0.9" - "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.62.0" - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/typescript-estree" "5.62.0" - eslint-scope "^5.1.1" - semver "^7.3.7" - -"@typescript-eslint/visitor-keys@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" - integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== - dependencies: - "@typescript-eslint/types" "5.62.0" - eslint-visitor-keys "^3.3.0" - -"@ungap/promise-all-settled@1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" - integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== + "@typescript-eslint/types" "6.10.0" + eslint-visitor-keys "^3.4.1" "@ungap/structured-clone@^1.2.0": version "1.2.0" @@ -1075,15 +1009,15 @@ array-ify@^1.0.0: resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" integrity sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng== -array-includes@^3.1.6: - version "3.1.6" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.6.tgz#9e9e720e194f198266ba9e18c29e6a9b0e4b225f" - integrity sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw== +array-includes@^3.1.7: + version "3.1.7" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.7.tgz#8cd2e01b26f7a3086cbc87271593fe921c62abda" + integrity sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - get-intrinsic "^1.1.3" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" is-string "^1.0.7" array-union@^2.1.0: @@ -1091,45 +1025,46 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== -array.prototype.findlastindex@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.2.tgz#bc229aef98f6bd0533a2bc61ff95209875526c9b" - integrity sha512-tb5thFFlUcp7NdNF6/MpDk/1r/4awWG1FIz3YqDf+/zJSTezBb+/5WViH41obXULHVpDzoiCLpJ/ZO9YbJMsdw== +array.prototype.findlastindex@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz#b37598438f97b579166940814e2c0493a4f50207" + integrity sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" es-shim-unscopables "^1.0.0" - get-intrinsic "^1.1.3" + get-intrinsic "^1.2.1" -array.prototype.flat@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz#ffc6576a7ca3efc2f46a143b9d1dda9b4b3cf5e2" - integrity sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA== +array.prototype.flat@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" + integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" es-shim-unscopables "^1.0.0" -array.prototype.flatmap@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz#1aae7903c2100433cb8261cd4ed310aab5c4a183" - integrity sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ== +array.prototype.flatmap@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" + integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" es-shim-unscopables "^1.0.0" -arraybuffer.prototype.slice@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz#9b5ea3868a6eebc30273da577eb888381c0044bb" - integrity sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw== +arraybuffer.prototype.slice@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz#98bd561953e3e74bb34938e77647179dfe6e9f12" + integrity sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw== dependencies: array-buffer-byte-length "^1.0.0" call-bind "^1.0.2" define-properties "^1.2.0" + es-abstract "^1.22.1" get-intrinsic "^1.2.1" is-array-buffer "^3.0.2" is-shared-array-buffer "^1.0.2" @@ -1252,6 +1187,11 @@ buffer@^6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" +builtin-modules@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== + caching-transform@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/caching-transform/-/caching-transform-4.0.0.tgz#00d297a4206d71e2163c39eaffa8157ac0651f0f" @@ -1262,13 +1202,14 @@ caching-transform@^4.0.0: package-hash "^4.0.0" write-file-atomic "^3.0.0" -call-bind@^1.0.0, call-bind@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== +call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.4, call-bind@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.5.tgz#6fa2b7845ce0ea49bf4d8b9ef64727a2c2e2e513" + integrity sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ== dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" + function-bind "^1.1.2" + get-intrinsic "^1.2.1" + set-function-length "^1.1.1" callsites@^3.0.0: version "3.1.0" @@ -1321,7 +1262,7 @@ chai-string@^1.5.0: resolved "https://registry.yarnpkg.com/chai-string/-/chai-string-1.5.0.tgz#0bdb2d8a5f1dbe90bc78ec493c1c1c180dd4d3d2" integrity sha512-sydDC3S3pNAQMYwJrs6dQX0oBQ6KfIPuOZ78n7rocW0eJJlsHPh2t3kwW7xfwYA/1Bf6/arGtSUo16rxR2JFlw== -chai@^4.3.10, chai@^4.3.8: +chai@^4.3.10: version "4.3.10" resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.10.tgz#d784cec635e3b7e2ffb66446a63b4e33bd390384" integrity sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g== @@ -1404,6 +1345,18 @@ chokidar@3.5.3, chokidar@^3.5.3: optionalDependencies: fsevents "~2.3.2" +ci-info@^3.8.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +clean-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clean-regexp/-/clean-regexp-1.0.0.tgz#8df7c7aae51fd36874e8f8d05b9180bc11a3fed7" + integrity sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw== + dependencies: + escape-string-regexp "^1.0.5" + clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" @@ -1489,10 +1442,10 @@ commander@^4.0.1: resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== -comment-parser@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.3.1.tgz#3d7ea3adaf9345594aedee6563f422348f165c1b" - integrity sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA== +comment-parser@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.4.0.tgz#0f8c560f59698193854f12884c20c0e39a26d32c" + integrity sha512-QLyTNiZ2KDOibvFPlZ6ZngVsZ/0gYnE6uTXi5aoDg8ed3AkJAz4sEje3Y8a29hQ1s6A99MZXe47fLAXQ1rTqaw== commondir@^1.0.1: version "1.0.1" @@ -1639,20 +1592,13 @@ dateformat@^4.6.3: resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.6.3.tgz#556fa6497e5217fedb78821424f8a1c22fa3f4b5" integrity sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA== -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: +debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" -debug@4.3.3: - version "4.3.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" - integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== - dependencies: - ms "2.1.2" - debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" @@ -1697,6 +1643,15 @@ default-require-extensions@^3.0.0: dependencies: strip-bom "^4.0.0" +define-data-property@^1.0.1, define-data-property@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.1.tgz#c35f7cd0ab09883480d12ac5cb213715587800b3" + integrity sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ== + dependencies: + get-intrinsic "^1.2.1" + gopd "^1.0.1" + has-property-descriptors "^1.0.0" + define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.0.tgz#52988570670c9eacedd8064f4a990f2405849bd5" @@ -1725,11 +1680,6 @@ diff@^4.0.1, diff@^4.0.2: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== -diff@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40" - integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw== - dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -1797,26 +1747,26 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.19.0, es-abstract@^1.20.4, es-abstract@^1.21.2: - version "1.22.1" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.1.tgz#8b4e5fc5cefd7f1660f0f8e1a52900dfbc9d9ccc" - integrity sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw== +es-abstract@^1.22.1: + version "1.22.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.3.tgz#48e79f5573198de6dee3589195727f4f74bc4f32" + integrity sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA== dependencies: array-buffer-byte-length "^1.0.0" - arraybuffer.prototype.slice "^1.0.1" + arraybuffer.prototype.slice "^1.0.2" available-typed-arrays "^1.0.5" - call-bind "^1.0.2" + call-bind "^1.0.5" es-set-tostringtag "^2.0.1" es-to-primitive "^1.2.1" - function.prototype.name "^1.1.5" - get-intrinsic "^1.2.1" + function.prototype.name "^1.1.6" + get-intrinsic "^1.2.2" get-symbol-description "^1.0.0" globalthis "^1.0.3" gopd "^1.0.1" - has "^1.0.3" has-property-descriptors "^1.0.0" has-proto "^1.0.1" has-symbols "^1.0.3" + hasown "^2.0.0" internal-slot "^1.0.5" is-array-buffer "^3.0.2" is-callable "^1.2.7" @@ -1824,23 +1774,23 @@ es-abstract@^1.19.0, es-abstract@^1.20.4, es-abstract@^1.21.2: is-regex "^1.1.4" is-shared-array-buffer "^1.0.2" is-string "^1.0.7" - is-typed-array "^1.1.10" + is-typed-array "^1.1.12" is-weakref "^1.0.2" - object-inspect "^1.12.3" + object-inspect "^1.13.1" object-keys "^1.1.1" object.assign "^4.1.4" - regexp.prototype.flags "^1.5.0" - safe-array-concat "^1.0.0" + regexp.prototype.flags "^1.5.1" + safe-array-concat "^1.0.1" safe-regex-test "^1.0.0" - string.prototype.trim "^1.2.7" - string.prototype.trimend "^1.0.6" - string.prototype.trimstart "^1.0.6" + string.prototype.trim "^1.2.8" + string.prototype.trimend "^1.0.7" + string.prototype.trimstart "^1.0.7" typed-array-buffer "^1.0.0" typed-array-byte-length "^1.0.0" typed-array-byte-offset "^1.0.0" typed-array-length "^1.0.4" unbox-primitive "^1.0.2" - which-typed-array "^1.1.10" + which-typed-array "^1.1.13" es-set-tostringtag@^2.0.1: version "2.0.1" @@ -1887,34 +1837,45 @@ escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== -eslint-config-prettier@^8.10.0, eslint-config-prettier@^8.8.0: - version "8.10.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz#3a06a662130807e2502fc3ff8b4143d8a0658e11" - integrity sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg== +eslint-config-prettier@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz#eb25485946dd0c66cd216a46232dc05451518d1f" + integrity sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw== eslint-config-salesforce-license@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/eslint-config-salesforce-license/-/eslint-config-salesforce-license-0.2.0.tgz#323193f1aa15dd33fbf108d25fc1210afc11065e" integrity sha512-DJdBvgj82Erum82YMe+YvG/o6ukna3UA++lRl0HSTldj0VlBl3Q8hzCp97nRXZHra6JH1I912yievZzklXDw6w== -eslint-config-salesforce-typescript@^1.1.1, eslint-config-salesforce-typescript@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/eslint-config-salesforce-typescript/-/eslint-config-salesforce-typescript-1.1.2.tgz#86a303e37899122bd9706c72b95ed288408a4f45" - integrity sha512-34RnT1fIo5cWdKwUH6IlfVKvVAQEH5R75K1O/c29U9qW5ZjmdfJ8NtI/4z143mhCM7wB9FsJROdKMJEISGThkA== +eslint-config-salesforce-typescript@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/eslint-config-salesforce-typescript/-/eslint-config-salesforce-typescript-3.0.2.tgz#25e9e8797a109828a63a3893a15613c8ecefa1bf" + integrity sha512-3eNN2dKojedhxbsOGocXaU+83XoeOzc8pCiDjOvlUc7B9IYTgv7UwOgmIIoESmuHKcvsCDDFDttfLCSOCOaRtQ== + dependencies: + "@typescript-eslint/eslint-plugin" "^6.9.1" + "@typescript-eslint/parser" "^6.9.1" + eslint "^8.52.0" + eslint-config-prettier "^9.0.0" + eslint-config-salesforce "^2.0.2" + eslint-config-salesforce-license "^0.2.0" + eslint-plugin-header "^3.1.1" + eslint-plugin-import "^2.29.0" + eslint-plugin-jsdoc "^46.8.2" + eslint-plugin-unicorn "^49.0.0" -eslint-config-salesforce@^2.0.1, eslint-config-salesforce@^2.0.2: +eslint-config-salesforce@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/eslint-config-salesforce/-/eslint-config-salesforce-2.0.2.tgz#38eb2d8eb2824c66967ed9b45bc92082eba2f225" integrity sha512-3jbrI+QFu/KaQbPYIBxItB3okqUtA4EBCGiR6s2kcUMIZBLBBGAURW0k62f9WAv1EagR3eUoO0m9ru7LTj2F5Q== -eslint-import-resolver-node@^0.3.7: - version "0.3.7" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz#83b375187d412324a1963d84fa664377a23eb4d7" - integrity sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA== +eslint-import-resolver-node@^0.3.9: + version "0.3.9" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" + integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== dependencies: debug "^3.2.7" - is-core-module "^2.11.0" - resolve "^1.22.1" + is-core-module "^2.13.0" + resolve "^1.22.4" eslint-module-utils@^2.8.0: version "2.8.0" @@ -1928,55 +1889,63 @@ eslint-plugin-header@^3.1.1: resolved "https://registry.yarnpkg.com/eslint-plugin-header/-/eslint-plugin-header-3.1.1.tgz#6ce512432d57675265fac47292b50d1eff11acd6" integrity sha512-9vlKxuJ4qf793CmeeSrZUvVClw6amtpghq3CuWcB5cUNnWHQhgcqy5eF8oVKFk1G3Y/CbchGfEaw3wiIJaNmVg== -eslint-plugin-import@^2.27.5, eslint-plugin-import@^2.28.1: - version "2.28.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.28.1.tgz#63b8b5b3c409bfc75ebaf8fb206b07ab435482c4" - integrity sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A== +eslint-plugin-import@^2.29.0: + version "2.29.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz#8133232e4329ee344f2f612885ac3073b0b7e155" + integrity sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg== dependencies: - array-includes "^3.1.6" - array.prototype.findlastindex "^1.2.2" - array.prototype.flat "^1.3.1" - array.prototype.flatmap "^1.3.1" + array-includes "^3.1.7" + array.prototype.findlastindex "^1.2.3" + array.prototype.flat "^1.3.2" + array.prototype.flatmap "^1.3.2" debug "^3.2.7" doctrine "^2.1.0" - eslint-import-resolver-node "^0.3.7" + eslint-import-resolver-node "^0.3.9" eslint-module-utils "^2.8.0" - has "^1.0.3" - is-core-module "^2.13.0" + hasown "^2.0.0" + is-core-module "^2.13.1" is-glob "^4.0.3" minimatch "^3.1.2" - object.fromentries "^2.0.6" - object.groupby "^1.0.0" - object.values "^1.1.6" + object.fromentries "^2.0.7" + object.groupby "^1.0.1" + object.values "^1.1.7" semver "^6.3.1" tsconfig-paths "^3.14.2" -eslint-plugin-jsdoc@^43.0.5, eslint-plugin-jsdoc@^43.2.0: - version "43.2.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-43.2.0.tgz#9d0df2329100a6956635f26211d0723c3ff91f15" - integrity sha512-Hst7XUfqh28UmPD52oTXmjaRN3d0KrmOZdgtp4h9/VHUJD3Evoo82ZGXi1TtRDWgWhvqDIRI63O49H0eH7NrZQ== +eslint-plugin-jsdoc@^46.8.2: + version "46.8.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.8.2.tgz#3e6b1c93e91e38fe01874d45da121b56393c54a5" + integrity sha512-5TSnD018f3tUJNne4s4gDWQflbsgOycIKEUBoCLn6XtBMgNHxQFmV8vVxUtiPxAQq8lrX85OaSG/2gnctxw9uQ== dependencies: - "@es-joy/jsdoccomment" "~0.38.0" + "@es-joy/jsdoccomment" "~0.40.1" are-docs-informative "^0.0.2" - comment-parser "1.3.1" + comment-parser "1.4.0" debug "^4.3.4" escape-string-regexp "^4.0.0" esquery "^1.5.0" - semver "^7.5.0" + is-builtin-module "^3.2.1" + semver "^7.5.4" spdx-expression-parse "^3.0.1" -eslint-plugin-prefer-arrow@^1.2.1: - version "1.2.3" - resolved "https://registry.yarnpkg.com/eslint-plugin-prefer-arrow/-/eslint-plugin-prefer-arrow-1.2.3.tgz#e7fbb3fa4cd84ff1015b9c51ad86550e55041041" - integrity sha512-J9I5PKCOJretVuiZRGvPQxCbllxGAV/viI20JO3LYblAodofBxyMnZAJ+WGeClHgANnSJberTNoFWWjrWKBuXQ== - -eslint-scope@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== +eslint-plugin-unicorn@^49.0.0: + version "49.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-49.0.0.tgz#4449ea954d7e1455eec8518f9417d7021b245fa8" + integrity sha512-0fHEa/8Pih5cmzFW5L7xMEfUTvI9WKeQtjmKpTUmY+BiFCDxkxrTdnURJOHKykhtwIeyYsxnecbGvDCml++z4Q== dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" + "@babel/helper-validator-identifier" "^7.22.20" + "@eslint-community/eslint-utils" "^4.4.0" + ci-info "^3.8.0" + clean-regexp "^1.0.0" + esquery "^1.5.0" + indent-string "^4.0.0" + is-builtin-module "^3.2.1" + jsesc "^3.0.2" + pluralize "^8.0.0" + read-pkg-up "^7.0.1" + regexp-tree "^0.1.27" + regjsparser "^0.10.0" + semver "^7.5.4" + strip-indent "^3.0.0" eslint-scope@^7.2.2: version "7.2.2" @@ -1991,7 +1960,7 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4 resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint@^8.41.0, eslint@^8.53.0: +eslint@^8.52.0: version "8.53.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.53.0.tgz#14f2c8244298fcae1f46945459577413ba2697ce" integrity sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag== @@ -2063,11 +2032,6 @@ esrecurse@^4.3.0: dependencies: estraverse "^5.2.0" -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - estraverse@^5.1.0, estraverse@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" @@ -2315,22 +2279,22 @@ fsevents@~2.3.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +function-bind@^1.1.1, function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== -function.prototype.name@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621" - integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== +function.prototype.name@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" + integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.0" - functions-have-names "^1.2.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + functions-have-names "^1.2.3" -functions-have-names@^1.2.2, functions-have-names@^1.2.3: +functions-have-names@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== @@ -2350,15 +2314,15 @@ get-func-name@^2.0.0, get-func-name@^2.0.2: resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== -get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" - integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.2.tgz#281b7622971123e1ef4b3c90fd7539306da93f3b" + integrity sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA== dependencies: - function-bind "^1.1.1" - has "^1.0.3" + function-bind "^1.1.2" has-proto "^1.0.1" has-symbols "^1.0.3" + hasown "^2.0.0" get-package-type@^0.1.0: version "0.1.0" @@ -2500,11 +2464,6 @@ graphemer@^1.4.0: resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== -growl@1.10.5: - version "1.10.5" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" - integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== - hard-rejection@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" @@ -2564,6 +2523,13 @@ hasha@^5.0.0: is-stream "^2.0.0" type-fest "^0.8.0" +hasown@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" + integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== + dependencies: + function-bind "^1.1.2" + he@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" @@ -2642,10 +2608,10 @@ ieee754@^1.2.1: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -ignore@^5.1.4, ignore@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" - integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== +ignore@^5.1.4, ignore@^5.2.0, ignore@^5.2.4: + version "5.2.4" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" + integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== immediate@~3.0.5: version "3.0.6" @@ -2757,17 +2723,24 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" +is-builtin-module@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169" + integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== + dependencies: + builtin-modules "^3.3.0" + is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-core-module@^2.11.0, is-core-module@^2.12.0, is-core-module@^2.13.0, is-core-module@^2.5.0: - version "2.13.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" - integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== +is-core-module@^2.13.0, is-core-module@^2.13.1, is-core-module@^2.5.0: + version "2.13.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== dependencies: - has "^1.0.3" + hasown "^2.0.0" is-date-object@^1.0.1: version "1.0.5" @@ -2876,16 +2849,12 @@ is-text-path@^1.0.1: dependencies: text-extensions "^1.0.0" -is-typed-array@^1.1.10, is-typed-array@^1.1.9: - version "1.1.10" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f" - integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A== +is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.9: + version "1.1.12" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" + integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.0" + which-typed-array "^1.1.11" is-typedarray@^1.0.0: version "1.0.0" @@ -3038,6 +3007,16 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +jsesc@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" + integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== + jsforce@^2.0.0-beta.28: version "2.0.0-beta.28" resolved "https://registry.yarnpkg.com/jsforce/-/jsforce-2.0.0-beta.28.tgz#5fd8d9b8e5efc798698793b147e00371f3d74e8f" @@ -3299,7 +3278,7 @@ lodash.upperfirst@^4.3.1: resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce" integrity sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg== -lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.4: +lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.4: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -3434,12 +3413,12 @@ min-indent@^1.0.0: resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== -minimatch@4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-4.2.1.tgz#40d9d511a46bdc4e563c22c3080cde9c0d8299b4" - integrity sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g== +minimatch@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" + integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== dependencies: - brace-expansion "^1.1.7" + brace-expansion "^2.0.1" minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" @@ -3469,32 +3448,29 @@ minimist@^1.2.0, minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -mocha@^9.1.3: - version "9.2.2" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.2.2.tgz#d70db46bdb93ca57402c809333e5a84977a88fb9" - integrity sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g== +mocha@^10.2.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.2.0.tgz#1fd4a7c32ba5ac372e03a17eef435bd00e5c68b8" + integrity sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg== dependencies: - "@ungap/promise-all-settled" "1.1.2" ansi-colors "4.1.1" browser-stdout "1.3.1" chokidar "3.5.3" - debug "4.3.3" + debug "4.3.4" diff "5.0.0" escape-string-regexp "4.0.0" find-up "5.0.0" glob "7.2.0" - growl "1.10.5" he "1.2.0" js-yaml "4.1.0" log-symbols "4.1.0" - minimatch "4.2.1" + minimatch "5.0.1" ms "2.1.3" - nanoid "3.3.1" + nanoid "3.3.3" serialize-javascript "6.0.0" strip-json-comments "3.1.1" supports-color "8.1.1" - which "2.0.2" - workerpool "6.2.0" + workerpool "6.2.1" yargs "16.2.0" yargs-parser "20.2.4" yargs-unparser "2.0.0" @@ -3538,15 +3514,10 @@ mute-stream@0.0.8: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== -nanoid@3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35" - integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw== - -natural-compare-lite@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" - integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== +nanoid@3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" + integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== natural-compare@^1.4.0: version "1.4.0" @@ -3575,17 +3546,6 @@ nise@^4.1.0: just-extend "^4.0.2" path-to-regexp "^1.7.0" -nise@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/nise/-/nise-5.1.2.tgz#a7b8909c216b3491fd4fc0b124efb69f3939b449" - integrity sha512-+gQjFi8v+tkfCuSCxfURHLhRhniE/+IaYbIphxAN2JRR9SHKhY8hgXpaXiYfHdw+gcGe4buxgbprBQFab9FkhA== - dependencies: - "@sinonjs/commons" "^2.0.0" - "@sinonjs/fake-timers" "^7.0.4" - "@sinonjs/text-encoding" "^0.7.1" - just-extend "^4.0.2" - path-to-regexp "^1.7.0" - no-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" @@ -3678,10 +3638,10 @@ nyc@^15.1.0: test-exclude "^6.0.0" yargs "^15.0.2" -object-inspect@^1.12.3, object-inspect@^1.9.0: - version "1.12.3" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" - integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== +object-inspect@^1.13.1, object-inspect@^1.9.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" + integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== object-keys@^1.1.1: version "1.1.1" @@ -3698,33 +3658,33 @@ object.assign@^4.1.4: has-symbols "^1.0.3" object-keys "^1.1.1" -object.fromentries@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.6.tgz#cdb04da08c539cffa912dcd368b886e0904bfa73" - integrity sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg== +object.fromentries@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.7.tgz#71e95f441e9a0ea6baf682ecaaf37fa2a8d7e616" + integrity sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" -object.groupby@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.0.tgz#cb29259cf90f37e7bac6437686c1ea8c916d12a9" - integrity sha512-70MWG6NfRH9GnbZOikuhPPYzpUpof9iW2J9E4dW7FXTqPNb6rllE6u39SKwwiNh8lCwX3DDb5OgcKGiEBrTTyw== +object.groupby@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.1.tgz#d41d9f3c8d6c778d9cbac86b4ee9f5af103152ee" + integrity sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ== dependencies: call-bind "^1.0.2" define-properties "^1.2.0" - es-abstract "^1.21.2" + es-abstract "^1.22.1" get-intrinsic "^1.2.1" -object.values@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.6.tgz#4abbaa71eba47d63589d402856f908243eea9b1d" - integrity sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw== +object.values@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.7.tgz#617ed13272e7e1071b43973aa1655d9291b8442a" + integrity sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" on-exit-leak-free@^2.1.0: version "2.1.0" @@ -3975,17 +3935,22 @@ platform@^1.3.3: resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.6.tgz#48b4ce983164b209c2d45a107adb31f473a6e7a7" integrity sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg== +pluralize@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prettier@^2.7.1, prettier@^2.8.7: - version "2.8.7" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.7.tgz#bb79fc8729308549d28fe3a98fce73d2c0656450" - integrity sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw== +prettier@^2.8.8: + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== -pretty-quick@^3.1.0, pretty-quick@^3.1.3: +pretty-quick@^3.1.0: version "3.1.3" resolved "https://registry.yarnpkg.com/pretty-quick/-/pretty-quick-3.1.3.tgz#15281108c0ddf446675157ca40240099157b638e" integrity sha512-kOCi2FJabvuh1as9enxYmrnBC6tVMoVOenMaBqRfsvBHB0cbpYHjdQEpSglpASDFEXVwplpcGR4CLEaisYAFcA== @@ -4166,14 +4131,26 @@ regenerator-runtime@^0.14.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== -regexp.prototype.flags@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz#fe7ce25e7e4cca8db37b6634c8a2c7009199b9cb" - integrity sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA== +regexp-tree@^0.1.27: + version "0.1.27" + resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd" + integrity sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA== + +regexp.prototype.flags@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e" + integrity sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg== dependencies: call-bind "^1.0.2" define-properties "^1.2.0" - functions-have-names "^1.2.3" + set-function-name "^2.0.0" + +regjsparser@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.10.0.tgz#b1ed26051736b436f22fdec1c8f72635f9f44892" + integrity sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA== + dependencies: + jsesc "~0.5.0" release-zalgo@^1.0.0: version "1.0.0" @@ -4219,12 +4196,12 @@ resolve-global@1.0.0, resolve-global@^1.0.0: dependencies: global-dirs "^0.1.1" -resolve@>=1.9.0, resolve@^1.1.6, resolve@^1.10.0, resolve@^1.22.1: - version "1.22.3" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.3.tgz#4b4055349ffb962600972da1fdc33c46a4eb3283" - integrity sha512-P8ur/gp/AmbEzjr729bZnLjXK5Z+4P0zhIJgBgzqRih7hL7BOukHGtSTA3ACMY467GRFz3duQsi0bDZdR7DKdw== +resolve@>=1.9.0, resolve@^1.1.6, resolve@^1.10.0, resolve@^1.22.4: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== dependencies: - is-core-module "^2.12.0" + is-core-module "^2.13.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" @@ -4272,13 +4249,13 @@ rxjs@^6.6.0: dependencies: tslib "^1.9.0" -safe-array-concat@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.0.tgz#2064223cba3c08d2ee05148eedbc563cd6d84060" - integrity sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ== +safe-array-concat@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz#91686a63ce3adbea14d61b14c99572a8ff84754c" + integrity sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q== dependencies: call-bind "^1.0.2" - get-intrinsic "^1.2.0" + get-intrinsic "^1.2.1" has-symbols "^1.0.3" isarray "^2.0.5" @@ -4343,7 +4320,7 @@ semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.4, semver@^7.3.7, semver@^7.5.0, semver@^7.5.4: +semver@^7.3.4, semver@^7.5.4: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -4376,6 +4353,25 @@ set-blocking@^2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== +set-function-length@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.1.1.tgz#4bc39fafb0307224a33e106a7d35ca1218d659ed" + integrity sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ== + dependencies: + define-data-property "^1.1.1" + get-intrinsic "^1.2.1" + gopd "^1.0.1" + has-property-descriptors "^1.0.0" + +set-function-name@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" + integrity sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA== + dependencies: + define-data-property "^1.0.1" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.0" + setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" @@ -4393,7 +4389,7 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shelljs@0.8.5, shelljs@~0.8.4: +shelljs@~0.8.4: version "0.8.5" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== @@ -4437,18 +4433,6 @@ sinon@10.0.0: nise "^4.1.0" supports-color "^7.1.0" -sinon@^14.0.2: - version "14.0.2" - resolved "https://registry.yarnpkg.com/sinon/-/sinon-14.0.2.tgz#585a81a3c7b22cf950762ac4e7c28eb8b151c46f" - integrity sha512-PDpV0ZI3ZCS3pEqx0vpNp6kzPhHrLx72wA0G+ZLaaJjLIYeE0n8INlgaohKuGy7hP0as5tbUd23QWu5U233t+w== - dependencies: - "@sinonjs/commons" "^2.0.0" - "@sinonjs/fake-timers" "^9.1.2" - "@sinonjs/samsam" "^7.0.1" - diff "^5.0.0" - nise "^5.1.2" - supports-color "^7.2.0" - sinon@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/sinon/-/sinon-5.1.1.tgz#19c59810ffb733ea6e76a28b94a71fc4c2f523b8" @@ -4559,32 +4543,32 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string.prototype.trim@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz#a68352740859f6893f14ce3ef1bb3037f7a90533" - integrity sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg== +string.prototype.trim@^1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz#f9ac6f8af4bd55ddfa8895e6aea92a96395393bd" + integrity sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" -string.prototype.trimend@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533" - integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ== +string.prototype.trimend@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz#1bb3afc5008661d73e2dc015cd4853732d6c471e" + integrity sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" -string.prototype.trimstart@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4" - integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA== +string.prototype.trimstart@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz#d4cdb44b83a4737ffbac2d406e405d43d0184298" + integrity sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" string_decoder@^1.1.1: version "1.3.0" @@ -4648,7 +4632,7 @@ supports-color@^5.3.0, supports-color@^5.4.0: dependencies: has-flag "^3.0.0" -supports-color@^7.1.0, supports-color@^7.2.0: +supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -4737,7 +4721,12 @@ trim-newlines@^3.0.0: resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== -ts-node@^10.0.0, ts-node@^10.4.0, ts-node@^10.8.1: +ts-api-utils@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331" + integrity sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg== + +ts-node@^10.8.1, ts-node@^10.9.1: version "10.9.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== @@ -4771,7 +4760,7 @@ tsconfig-paths@^3.14.2: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^1.8.1, tslib@^1.9.0: +tslib@^1.9.0: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -4781,13 +4770,6 @@ tslib@^2.0.3, tslib@^2.6.1, tslib@^2.6.2: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== -tsutils@^3.21.0: - version "3.21.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" - integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== - dependencies: - tslib "^1.8.1" - ttypescript@^1.5.15: version "1.5.15" resolved "https://registry.yarnpkg.com/ttypescript/-/ttypescript-1.5.15.tgz#e45550ad69289d06d3bc3fd4a3c87e7c1ef3eba7" @@ -4915,6 +4897,11 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -5043,28 +5030,28 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q== -which-typed-array@^1.1.10: - version "1.1.11" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.11.tgz#99d691f23c72aab6768680805a271b69761ed61a" - integrity sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew== +which-typed-array@^1.1.11, which-typed-array@^1.1.13: + version "1.1.13" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.13.tgz#870cd5be06ddb616f504e7b039c4c24898184d36" + integrity sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow== dependencies: available-typed-arrays "^1.0.5" - call-bind "^1.0.2" + call-bind "^1.0.4" for-each "^0.3.3" gopd "^1.0.1" has-tostringtag "^1.0.0" -which@2.0.2, which@^2.0.1: +which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" -wireit@^0.14.0: - version "0.14.0" - resolved "https://registry.yarnpkg.com/wireit/-/wireit-0.14.0.tgz#8ecb922280d9b3a493bed520900ab458e0f38727" - integrity sha512-CwHhyWduARI7zOoyyQvxH6U/5GTy8p6RxTe2LpwIhE4Y8PbaTk0iH8dLkeGMvxETVBkxCgQnF2GfzYKXMSmYSw== +wireit@^0.14.1: + version "0.14.1" + resolved "https://registry.yarnpkg.com/wireit/-/wireit-0.14.1.tgz#83b63598503573db6722ad49b1fe15b57ee71890" + integrity sha512-q5sixPM/vKQEpyaub6J9QoHAFAF9g4zBdnjoYelH9/RLAekcUf3x1dmFLACGZ6nYjqehCsTlXC1irmzU7znPhA== dependencies: braces "^3.0.2" chokidar "^3.5.3" @@ -5072,21 +5059,10 @@ wireit@^0.14.0: jsonc-parser "^3.0.0" proper-lockfile "^4.1.2" -wireit@^0.9.5: - version "0.9.5" - resolved "https://registry.yarnpkg.com/wireit/-/wireit-0.9.5.tgz#7c3622f6ff5e63b7fac05783baf82f967f52562c" - integrity sha512-dKKNAwLxQjbPPMrltPxMUFKvLy2z6hlVjvR/qitvPm8GEQyb/1QYBG7ObvOQLsi95uAXpkWLJXBYkCKeVcMVgA== - dependencies: - braces "^3.0.2" - chokidar "^3.5.3" - fast-glob "^3.2.11" - jsonc-parser "^3.0.0" - proper-lockfile "^4.1.2" - -workerpool@6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.0.tgz#827d93c9ba23ee2019c3ffaff5c27fccea289e8b" - integrity sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A== +workerpool@6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" + integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== wrap-ansi@^6.2.0: version "6.2.0" From 7a940f3b937370731529acd832e48c5c01259923 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Thu, 9 Nov 2023 17:00:23 -0600 Subject: [PATCH 62/67] test: remove plugin-env from external nuts (it has none) --- .github/workflows/test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4226fd0bea..720e7f6f0b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,7 +25,6 @@ jobs: - https://github.com/salesforcecli/plugin-auth - https://github.com/salesforcecli/plugin-custom-metadata - https://github.com/salesforcecli/plugin-data - - https://github.com/salesforcecli/plugin-env - https://github.com/salesforcecli/plugin-limits - https://github.com/salesforcecli/plugin-org - https://github.com/salesforcecli/plugin-schema From 3e9c988223f285deda794e0ef2a7ed5cab91425f Mon Sep 17 00:00:00 2001 From: mshanemc Date: Thu, 9 Nov 2023 17:01:37 -0600 Subject: [PATCH 63/67] test: add apex, comunity nuts --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 720e7f6f0b..a0fa618c50 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,9 @@ jobs: matrix: os: ['ubuntu-latest', 'windows-latest'] externalProjectGitUrl: + - https://github.com/salesforcecli/plugin-apex - https://github.com/salesforcecli/plugin-auth + - https://github.com/salesforcecli/plugin-community - https://github.com/salesforcecli/plugin-custom-metadata - https://github.com/salesforcecli/plugin-data - https://github.com/salesforcecli/plugin-limits From d5b345f9179e64497b4f42fcef8fd45dce1f9d19 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Fri, 10 Nov 2023 08:33:29 -0600 Subject: [PATCH 64/67] ci: also remove core from apex-node lib --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a0fa618c50..e9d02ccab7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -41,7 +41,7 @@ jobs: os: ${{ matrix.os }} useCache: false preSwapCommands: 'yarn upgrade jsforce@beta; npx yarn-deduplicate; yarn install' - preExternalBuildCommands: 'shx rm -rf node_modules/@salesforce/core/node_modules/jsforce shx rm -rf node_modules/@salesforce/sf-plugins-core/node_modules/@salesforce/core node_modules/@salesforce/cli-plugins-testkit/node_modules/@salesforce/core node_modules/@salesforce/source-tracking/node_modules/@salesforce/core node_modules/@salesforce/source-deploy-retrieve/node_modules/@salesforce/core' + preExternalBuildCommands: 'shx rm -rf node_modules/@salesforce/core/node_modules/jsforce shx rm -rf node_modules/@salesforce/sf-plugins-core/node_modules/@salesforce/core node_modules/@salesforce/cli-plugins-testkit/node_modules/@salesforce/core node_modules/@salesforce/source-tracking/node_modules/@salesforce/core node_modules/@salesforce/source-deploy-retrieve/node_modules/@salesforce/core node_modules/@salesforce/apex-node/node_modules/@salesforce/core' secrets: inherit # hidden until we fix source-testkit to better handle jwt pdrNuts: From 4e66905b9c0bd8997c2b995f11d3f36648ec15cb Mon Sep 17 00:00:00 2001 From: mshanemc Date: Fri, 10 Nov 2023 11:12:31 -0600 Subject: [PATCH 65/67] feat: ts5 and ts-patch --- .sfdevrc.json | 2 +- README.md | 6 ++--- package.json | 6 ++--- src/messageTransformer.ts | 4 +-- yarn.lock | 52 +++++++++++++++++++++++++++++---------- 5 files changed, 48 insertions(+), 22 deletions(-) diff --git a/.sfdevrc.json b/.sfdevrc.json index ac25b4a163..e5ff37e5b5 100644 --- a/.sfdevrc.json +++ b/.sfdevrc.json @@ -4,7 +4,7 @@ }, "wireit": { "compile": { - "command": "ttsc -p . --pretty --incremental", + "command": "tspc -p . --pretty --incremental", "files": ["src/**/*.ts", "tsconfig.json", "messages", "messageTransformer"], "output": ["lib/**", "*.tsbuildinfo"], "clean": "if-file-deleted" diff --git a/README.md b/README.md index 233705ac90..4989db6d02 100644 --- a/README.md +++ b/README.md @@ -34,11 +34,11 @@ The Messages class, by default, loads message text during run time. It's optimiz If you're using @salesforce/core or other code that uses its Messages class in a bundler (webpack, esbuild, etc) it may struggle with these runtime references. -src/messageTransformer will "inline" the messages into the js files during TS compile using `https://github.com/cevek/ttypescript`. +src/messageTransformer will "inline" the messages into the js files during TS compile using `https://github.com/nonara/ts-patch`. In your plugin or library, -`yarn add --dev ttypescript` +`yarn add --dev ts-patch` > tsconfig.json @@ -54,7 +54,7 @@ In your plugin or library, ```json "wireit": { "compile": { - "command": "ttsc -p . --pretty --incremental", + "command": "tspc -p . --pretty --incremental", "files": [ "src/**/*.ts", "tsconfig.json", diff --git a/package.json b/package.json index cebaf279cd..3b5c2b1f64 100644 --- a/package.json +++ b/package.json @@ -68,8 +68,8 @@ "benchmark": "^2.1.4", "chai-string": "^1.5.0", "ts-node": "^10.9.1", - "ttypescript": "^1.5.15", - "typescript": "^4.9.5" + "ts-patch": "^3.0.2", + "typescript": "^5.2.2" }, "repository": { "type": "git", @@ -86,7 +86,7 @@ ] }, "compile": { - "command": "ttsc -p . --pretty --incremental", + "command": "tspc -p . --pretty --incremental", "files": [ "src/**/*.ts", "tsconfig.json", diff --git a/src/messageTransformer.ts b/src/messageTransformer.ts index 7242ffa26b..789f8dd12d 100644 --- a/src/messageTransformer.ts +++ b/src/messageTransformer.ts @@ -29,7 +29,7 @@ export const messageTransformer = (): ts.TransformerFactory => { if (ts.isExpressionStatement(node) && node.getText().includes('importMessagesDirectory')) { // importMessagesDirectory now happens at compile, not in runtime // returning undefined removes the node - return undefined; + return ts.factory.createEmptyStatement(); } if ( // transform a runtime load call into hardcoded messages values @@ -57,7 +57,7 @@ export const messageTransformer = (): ts.TransformerFactory => { // it might be a node that contains one of the things we're interested in, so keep digging return ts.visitEachChild(node, visitor, context); }; - return ts.visitNode(sourceFile, visitor); + return ts.visitNode(sourceFile, visitor, ts.isSourceFile); }; return transformerFactory; }; diff --git a/yarn.lock b/yarn.lock index 1995683d6d..5a6d56b8a5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1292,7 +1292,7 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.0.0, chalk@^4.1.0: +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -2416,6 +2416,15 @@ global-dirs@^0.1.1: dependencies: ini "^1.3.4" +global-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" + integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== + dependencies: + ini "^1.3.5" + kind-of "^6.0.2" + which "^1.3.1" + globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -2649,7 +2658,7 @@ inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ini@^1.3.4: +ini@^1.3.4, ini@^1.3.5: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== @@ -3149,7 +3158,7 @@ jws@^3.2.2: jwa "^1.4.1" safe-buffer "^5.0.1" -kind-of@^6.0.3: +kind-of@^6.0.2, kind-of@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== @@ -3443,7 +3452,7 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.2.0, minimist@^1.2.6: +minimist@^1.2.0, minimist@^1.2.6, minimist@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -4196,7 +4205,7 @@ resolve-global@1.0.0, resolve-global@^1.0.0: dependencies: global-dirs "^0.1.1" -resolve@>=1.9.0, resolve@^1.1.6, resolve@^1.10.0, resolve@^1.22.4: +resolve@^1.1.6, resolve@^1.10.0, resolve@^1.22.2, resolve@^1.22.4: version "1.22.8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -4320,7 +4329,7 @@ semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.4, semver@^7.5.4: +semver@^7.3.4, semver@^7.3.8, semver@^7.5.4: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -4745,6 +4754,18 @@ ts-node@^10.8.1, ts-node@^10.9.1: v8-compile-cache-lib "^3.0.1" yn "3.1.1" +ts-patch@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/ts-patch/-/ts-patch-3.0.2.tgz#cbdf88e4dfb596e4dab8f2c8269361d33270a0ba" + integrity sha512-iTg8euqiNsNM1VDfOsVIsP0bM4kAVXU38n7TGQSkky7YQX/syh6sDPIRkvSS0HjT8ZOr0pq1h+5Le6jdB3hiJQ== + dependencies: + chalk "^4.1.2" + global-prefix "^3.0.0" + minimist "^1.2.8" + resolve "^1.22.2" + semver "^7.3.8" + strip-ansi "^6.0.1" + ts-retry-promise@^0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/ts-retry-promise/-/ts-retry-promise-0.7.1.tgz#176d6eee6415f07b6c7c286d3657355e284a6906" @@ -4770,13 +4791,6 @@ tslib@^2.0.3, tslib@^2.6.1, tslib@^2.6.2: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== -ttypescript@^1.5.15: - version "1.5.15" - resolved "https://registry.yarnpkg.com/ttypescript/-/ttypescript-1.5.15.tgz#e45550ad69289d06d3bc3fd4a3c87e7c1ef3eba7" - integrity sha512-48ykDNHzFnPMnv4hYX1P8Q84TvCZyL1QlFxeuxsuZ48X2+ameBgPenvmCkHJtoOSxpoWTWi8NcgNrRnVDOmfSg== - dependencies: - resolve ">=1.9.0" - tunnel-agent@*: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -4887,6 +4901,11 @@ typescript@^4.6.4, typescript@^4.9.5: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== +typescript@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" + integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" @@ -5041,6 +5060,13 @@ which-typed-array@^1.1.11, which-typed-array@^1.1.13: gopd "^1.0.1" has-tostringtag "^1.0.0" +which@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" From 515b5a8042709771aae529f0ea82e95dae62f5cb Mon Sep 17 00:00:00 2001 From: mshanemc Date: Mon, 13 Nov 2023 13:45:20 -0600 Subject: [PATCH 66/67] chore: bump major --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e83e1d20a5..0e595f55ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@salesforce/core", - "version": "5.4.0-crdt.1", + "version": "6.0.0", "description": "Core libraries to interact with SFDX projects, orgs, and APIs.", "main": "lib/exported", "types": "lib/exported.d.ts", From ac78de4f76f133b7cbe14f3f5b55169e73f97033 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Tue, 14 Nov 2023 11:03:26 -0600 Subject: [PATCH 67/67] refactor: revert #980 in favor of a jsforce solution --- messages/org.md | 10 ---------- src/org/org.ts | 12 ++---------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/messages/org.md b/messages/org.md index 97524ef844..d4dddcc81e 100644 --- a/messages/org.md +++ b/messages/org.md @@ -65,13 +65,3 @@ We found more than one SandboxProcess with the SandboxName %s. # sandboxNotResumable The sandbox %s cannot resume with status of %s. - -# UnexpectedResponse - -Unexpected response from the platform - -# UnexpectedResponse.actions - -- Check that the instance URL is correct and the org still exists. - -- See what's at the URL that's causing the problem: %s. diff --git a/src/org/org.ts b/src/org/org.ts index 7b7d2b199c..4910bc1095 100644 --- a/src/org/org.ts +++ b/src/org/org.ts @@ -698,17 +698,9 @@ export class Org extends AsyncOptionalCreatable { url: this.getConnection().baseUrl(), method: 'GET', }; + const conn = this.getConnection(); - try { - await conn.request(requestInfo); - } catch (e) { - // an html error page like https://computing-connect-6970-dev-ed.scratch.my.salesforce.com/services/data/v50.0 - // where the message is an entire html page - if (e instanceof Error && (e.name.includes('ERROR_HTTP') || e.message.includes('