@salesforce/core 8.15.1-dev.0 → 8.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/config/configAggregator.d.ts +8 -1
- package/lib/config/configAggregator.js +29 -13
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/org/authInfo.d.ts +0 -2
- package/lib/org/authInfo.js +0 -2
- package/lib/org/org.d.ts +0 -33
- package/lib/org/org.js +4 -112
- package/lib/stateAggregator/stateAggregator.d.ts +8 -0
- package/lib/stateAggregator/stateAggregator.js +20 -5
- package/lib/util/mutex.d.ts +49 -0
- package/lib/util/mutex.js +77 -0
- package/messages/org.md +0 -16
- package/package.json +2 -2
|
@@ -54,8 +54,9 @@ export type ConfigInfo = {
|
|
|
54
54
|
* ```
|
|
55
55
|
*/
|
|
56
56
|
export declare class ConfigAggregator extends AsyncOptionalCreatable<ConfigAggregator.Options> {
|
|
57
|
-
protected static instance: AsyncOptionalCreatable;
|
|
58
57
|
protected static encrypted: boolean;
|
|
58
|
+
protected static instance: AsyncOptionalCreatable | undefined;
|
|
59
|
+
private static readonly mutex;
|
|
59
60
|
private allowedProperties;
|
|
60
61
|
private readonly localConfig?;
|
|
61
62
|
private readonly globalConfig;
|
|
@@ -68,6 +69,12 @@ export declare class ConfigAggregator extends AsyncOptionalCreatable<ConfigAggre
|
|
|
68
69
|
constructor(options?: ConfigAggregator.Options);
|
|
69
70
|
private get config();
|
|
70
71
|
static create<P extends ConfigAggregator.Options, T extends AsyncOptionalCreatable<P>>(this: new (options?: P) => T, options?: P): Promise<T>;
|
|
72
|
+
/**
|
|
73
|
+
* Clear the cache to force reading from disk.
|
|
74
|
+
*
|
|
75
|
+
* *NOTE: Only call this method if you must and you know what you are doing.*
|
|
76
|
+
*/
|
|
77
|
+
static clearInstance(): Promise<void>;
|
|
71
78
|
/**
|
|
72
79
|
* Get the info for a given key. If the ConfigAggregator was not asynchronously created OR
|
|
73
80
|
* the {@link ConfigAggregator.reload} was not called, the config value may be encrypted.
|
|
@@ -11,6 +11,7 @@ const kit_1 = require("@salesforce/kit");
|
|
|
11
11
|
const ts_types_1 = require("@salesforce/ts-types");
|
|
12
12
|
const messages_1 = require("../messages");
|
|
13
13
|
const lifecycleEvents_1 = require("../lifecycleEvents");
|
|
14
|
+
const mutex_1 = require("../util/mutex");
|
|
14
15
|
const envVars_1 = require("./envVars");
|
|
15
16
|
const config_1 = require("./config");
|
|
16
17
|
;
|
|
@@ -31,8 +32,9 @@ const messages = new messages_1.Messages('@salesforce/core', 'config', new Map([
|
|
|
31
32
|
* ```
|
|
32
33
|
*/
|
|
33
34
|
class ConfigAggregator extends kit_1.AsyncOptionalCreatable {
|
|
34
|
-
static instance;
|
|
35
35
|
static encrypted = true;
|
|
36
|
+
static instance;
|
|
37
|
+
static mutex = new mutex_1.Mutex();
|
|
36
38
|
// Initialized in loadProperties
|
|
37
39
|
allowedProperties;
|
|
38
40
|
localConfig;
|
|
@@ -64,18 +66,32 @@ class ConfigAggregator extends kit_1.AsyncOptionalCreatable {
|
|
|
64
66
|
// Use typing from AsyncOptionalCreatable to support extending ConfigAggregator.
|
|
65
67
|
// We really don't want ConfigAggregator extended but typescript doesn't support a final.
|
|
66
68
|
static async create(options) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
69
|
+
return ConfigAggregator.mutex.lock(async () => {
|
|
70
|
+
let config = ConfigAggregator.instance;
|
|
71
|
+
if (!config) {
|
|
72
|
+
config = ConfigAggregator.instance = new this(options);
|
|
73
|
+
await config.init();
|
|
74
|
+
}
|
|
75
|
+
if (ConfigAggregator.encrypted) {
|
|
76
|
+
await config.loadProperties();
|
|
77
|
+
}
|
|
78
|
+
if (options?.customConfigMeta) {
|
|
79
|
+
config_1.Config.addAllowedProperties(options.customConfigMeta);
|
|
80
|
+
}
|
|
81
|
+
// console.log(ConfigAggregator.instance);
|
|
82
|
+
return ConfigAggregator.instance;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Clear the cache to force reading from disk.
|
|
87
|
+
*
|
|
88
|
+
* *NOTE: Only call this method if you must and you know what you are doing.*
|
|
89
|
+
*/
|
|
90
|
+
static async clearInstance() {
|
|
91
|
+
return ConfigAggregator.mutex.lock(() => {
|
|
92
|
+
ConfigAggregator.instance = undefined;
|
|
93
|
+
ConfigAggregator.encrypted = true; // Reset encryption flag as well
|
|
94
|
+
});
|
|
79
95
|
}
|
|
80
96
|
/**
|
|
81
97
|
* Get the info for a given key. If the ConfigAggregator was not asynchronously created OR
|
package/lib/index.d.ts
CHANGED
|
@@ -38,3 +38,4 @@ export { ScratchOrgLifecycleEvent, scratchOrgLifecycleEventName, scratchOrgLifec
|
|
|
38
38
|
export { ScratchOrgCache } from './org/scratchOrgCache';
|
|
39
39
|
export { default as ScratchOrgSettingsGenerator } from './org/scratchOrgSettingsGenerator';
|
|
40
40
|
export * from './util/sfdc';
|
|
41
|
+
export * from './util/mutex';
|
package/lib/index.js
CHANGED
|
@@ -118,4 +118,5 @@ var scratchOrgSettingsGenerator_1 = require("./org/scratchOrgSettingsGenerator")
|
|
|
118
118
|
Object.defineProperty(exports, "ScratchOrgSettingsGenerator", { enumerable: true, get: function () { return __importDefault(scratchOrgSettingsGenerator_1).default; } });
|
|
119
119
|
// Utility sub-modules
|
|
120
120
|
__exportStar(require("./util/sfdc"), exports);
|
|
121
|
+
__exportStar(require("./util/mutex"), exports);
|
|
121
122
|
//# sourceMappingURL=index.js.map
|
package/lib/org/authInfo.d.ts
CHANGED
|
@@ -259,8 +259,6 @@ export declare class AuthInfo extends AsyncOptionalCreatable<AuthInfo.Options> {
|
|
|
259
259
|
getFields(decrypt?: boolean): Readonly<AuthFields>;
|
|
260
260
|
/**
|
|
261
261
|
* Get the org front door (used for web based oauth flows)
|
|
262
|
-
*
|
|
263
|
-
* @deprecated Will be removed in the next major version. Use the `Org.getFrontDoorUrl()` method instead.
|
|
264
262
|
*/
|
|
265
263
|
getOrgFrontDoorUrl(): string;
|
|
266
264
|
/**
|
package/lib/org/authInfo.js
CHANGED
|
@@ -545,8 +545,6 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
|
|
|
545
545
|
}
|
|
546
546
|
/**
|
|
547
547
|
* Get the org front door (used for web based oauth flows)
|
|
548
|
-
*
|
|
549
|
-
* @deprecated Will be removed in the next major version. Use the `Org.getFrontDoorUrl()` method instead.
|
|
550
548
|
*/
|
|
551
549
|
getOrgFrontDoorUrl() {
|
|
552
550
|
const authFields = this.getFields(true);
|
package/lib/org/org.d.ts
CHANGED
|
@@ -152,39 +152,6 @@ export declare class Org extends AsyncOptionalCreatable<Org.Options> {
|
|
|
152
152
|
* @ignore
|
|
153
153
|
*/
|
|
154
154
|
constructor(options?: Org.Options);
|
|
155
|
-
/**
|
|
156
|
-
* Generate a URL to a metadata UI builder/setup section in an org.
|
|
157
|
-
*
|
|
158
|
-
* Bot: open in Agentforce Builder
|
|
159
|
-
* ApexPage: opens page
|
|
160
|
-
* Flow: open in Flow Builder
|
|
161
|
-
* FlexiPage: open in Lightning App Builder
|
|
162
|
-
* CustomObject: open in Object Manager
|
|
163
|
-
* ApexClass: open in Setup -> Apex Classes UI
|
|
164
|
-
*
|
|
165
|
-
* if you pass any other metadata type you'll get a path to Lightning App Builder
|
|
166
|
-
*
|
|
167
|
-
* @example
|
|
168
|
-
* // use SDR resolver:
|
|
169
|
-
* import { MetadataResolver } from '@salesforce/source-deploy-retrieve';
|
|
170
|
-
*
|
|
171
|
-
* const metadataResolver = new MetadataResolver();
|
|
172
|
-
* const components = metadataResolver.getComponentsFromPath(filePath);
|
|
173
|
-
* const typeName = components[0]?.type?.name;
|
|
174
|
-
*
|
|
175
|
-
* const metadataBuilderUrl = await org.getMetadataUIURL(typeName, filePath);
|
|
176
|
-
*
|
|
177
|
-
* @typeName Bot | ApexPage | Flow | FlexiPage | CustomObject | ApexClass
|
|
178
|
-
* @file Absolute file path to the metadata file
|
|
179
|
-
*/
|
|
180
|
-
getMetadataUIURL(typeName: string, file: string): Promise<string>;
|
|
181
|
-
/**
|
|
182
|
-
* Get a Frontdoor URL
|
|
183
|
-
*
|
|
184
|
-
* This uses the UI Bridge API to generate a single-use Frontdoor URL:
|
|
185
|
-
* https://help.salesforce.com/s/articleView?id=xcloud.frontdoor_singleaccess.htm&type=5
|
|
186
|
-
*/
|
|
187
|
-
getFrontDoorUrl(redirectUri?: string): Promise<string>;
|
|
188
155
|
/**
|
|
189
156
|
* create a sandbox from a production org
|
|
190
157
|
* 'this' needs to be a production org with sandbox licenses available
|
package/lib/org/org.js
CHANGED
|
@@ -29,13 +29,10 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
29
29
|
__setModuleDefault(result, mod);
|
|
30
30
|
return result;
|
|
31
31
|
};
|
|
32
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
33
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
34
|
-
};
|
|
35
32
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
33
|
exports.Org = exports.SandboxEvents = exports.OrgTypes = void 0;
|
|
37
34
|
exports.sandboxIsResumable = sandboxIsResumable;
|
|
38
|
-
const node_path_1 =
|
|
35
|
+
const node_path_1 = require("node:path");
|
|
39
36
|
const fs = __importStar(require("node:fs"));
|
|
40
37
|
const kit_1 = require("@salesforce/kit");
|
|
41
38
|
const ts_types_1 = require("@salesforce/ts-types");
|
|
@@ -56,7 +53,7 @@ const authInfo_1 = require("./authInfo");
|
|
|
56
53
|
const scratchOrgCreate_1 = require("./scratchOrgCreate");
|
|
57
54
|
const orgConfigProperties_1 = require("./orgConfigProperties");
|
|
58
55
|
;
|
|
59
|
-
const messages = new messages_1.Messages('@salesforce/core', 'org', new Map([["notADevHub", "The provided dev hub username %s is not a valid dev hub."], ["noUsernameFound", "No username found."], ["noDevHubFound", "Unable to associate this scratch org with a DevHub."], ["deleteOrgHubError", "The Dev Hub org cannot be deleted."], ["insufficientAccessToDelete", "You do not have the appropriate permissions to delete a scratch org. Please contact your Salesforce admin."], ["scratchOrgNotFound", "Attempting to delete an expired or deleted org"], ["sandboxDeleteFailed", "The sandbox org deletion failed with a result of %s."], ["sandboxNotFound", "We can't find a SandboxProcess for the sandbox %s."], ["sandboxInfoCreateFailed", "The sandbox org creation failed with a result of %s."], ["sandboxInfoRefreshFailed", "The sandbox org refresh failed with a result of %s."], ["missingAuthUsername", "The sandbox %s does not have an authorized username."], ["orgPollingTimeout", "Sandbox status is %s; timed out waiting for completion."], ["NotFoundOnDevHub", "The scratch org does not belong to the dev hub username %s."], ["AuthInfoOrgIdUndefined", "AuthInfo orgId is undefined."], ["sandboxCreateNotComplete", "The sandbox creation has not completed."], ["SandboxProcessNotFoundBySandboxName", "We can't find a SandboxProcess with the SandboxName %s."], ["MultiSandboxProcessNotFoundBySandboxName", "We found more than one SandboxProcess with the SandboxName %s."], ["sandboxNotResumable", "The sandbox %s cannot resume with status of %s."]
|
|
56
|
+
const messages = new messages_1.Messages('@salesforce/core', 'org', new Map([["notADevHub", "The provided dev hub username %s is not a valid dev hub."], ["noUsernameFound", "No username found."], ["noDevHubFound", "Unable to associate this scratch org with a DevHub."], ["deleteOrgHubError", "The Dev Hub org cannot be deleted."], ["insufficientAccessToDelete", "You do not have the appropriate permissions to delete a scratch org. Please contact your Salesforce admin."], ["scratchOrgNotFound", "Attempting to delete an expired or deleted org"], ["sandboxDeleteFailed", "The sandbox org deletion failed with a result of %s."], ["sandboxNotFound", "We can't find a SandboxProcess for the sandbox %s."], ["sandboxInfoCreateFailed", "The sandbox org creation failed with a result of %s."], ["sandboxInfoRefreshFailed", "The sandbox org refresh failed with a result of %s."], ["missingAuthUsername", "The sandbox %s does not have an authorized username."], ["orgPollingTimeout", "Sandbox status is %s; timed out waiting for completion."], ["NotFoundOnDevHub", "The scratch org does not belong to the dev hub username %s."], ["AuthInfoOrgIdUndefined", "AuthInfo orgId is undefined."], ["sandboxCreateNotComplete", "The sandbox creation has not completed."], ["SandboxProcessNotFoundBySandboxName", "We can't find a SandboxProcess with the SandboxName %s."], ["MultiSandboxProcessNotFoundBySandboxName", "We found more than one SandboxProcess with the SandboxName %s."], ["sandboxNotResumable", "The sandbox %s cannot resume with status of %s."]]));
|
|
60
57
|
var OrgTypes;
|
|
61
58
|
(function (OrgTypes) {
|
|
62
59
|
OrgTypes["Scratch"] = "scratch";
|
|
@@ -149,111 +146,6 @@ class Org extends kit_1.AsyncOptionalCreatable {
|
|
|
149
146
|
super(options);
|
|
150
147
|
this.options = options ?? {};
|
|
151
148
|
}
|
|
152
|
-
/**
|
|
153
|
-
* Generate a URL to a metadata UI builder/setup section in an org.
|
|
154
|
-
*
|
|
155
|
-
* Bot: open in Agentforce Builder
|
|
156
|
-
* ApexPage: opens page
|
|
157
|
-
* Flow: open in Flow Builder
|
|
158
|
-
* FlexiPage: open in Lightning App Builder
|
|
159
|
-
* CustomObject: open in Object Manager
|
|
160
|
-
* ApexClass: open in Setup -> Apex Classes UI
|
|
161
|
-
*
|
|
162
|
-
* if you pass any other metadata type you'll get a path to Lightning App Builder
|
|
163
|
-
*
|
|
164
|
-
* @example
|
|
165
|
-
* // use SDR resolver:
|
|
166
|
-
* import { MetadataResolver } from '@salesforce/source-deploy-retrieve';
|
|
167
|
-
*
|
|
168
|
-
* const metadataResolver = new MetadataResolver();
|
|
169
|
-
* const components = metadataResolver.getComponentsFromPath(filePath);
|
|
170
|
-
* const typeName = components[0]?.type?.name;
|
|
171
|
-
*
|
|
172
|
-
* const metadataBuilderUrl = await org.getMetadataUIURL(typeName, filePath);
|
|
173
|
-
*
|
|
174
|
-
* @typeName Bot | ApexPage | Flow | FlexiPage | CustomObject | ApexClass
|
|
175
|
-
* @file Absolute file path to the metadata file
|
|
176
|
-
*/
|
|
177
|
-
async getMetadataUIURL(typeName, file) {
|
|
178
|
-
const botFileNameToId = async (conn, filePath) => (await conn.singleRecordQuery(`SELECT id FROM BotDefinition WHERE DeveloperName='${node_path_1.default.basename(filePath, '.bot-meta.xml')}'`)).Id;
|
|
179
|
-
/** query flexipage via toolingAPI to get its ID (starts with 0M0) */
|
|
180
|
-
const flexiPageFilenameToId = async (conn, filePath) => (await conn.singleRecordQuery(`SELECT id FROM flexipage WHERE DeveloperName='${node_path_1.default.basename(filePath, '.flexipage-meta.xml')}'`, { tooling: true })).Id;
|
|
181
|
-
/** query the rest API to turn a flow's filepath into a FlowId (starts with 301) */
|
|
182
|
-
const flowFileNameToId = async (conn, filePath) => {
|
|
183
|
-
try {
|
|
184
|
-
const flow = await conn.singleRecordQuery(`SELECT DurableId FROM FlowVersionView WHERE FlowDefinitionView.ApiName = '${node_path_1.default.basename(filePath, '.flow-meta.xml')}' ORDER BY VersionNumber DESC LIMIT 1`);
|
|
185
|
-
return flow.DurableId;
|
|
186
|
-
}
|
|
187
|
-
catch (error) {
|
|
188
|
-
throw messages.createError('FlowIdNotFound', [filePath]);
|
|
189
|
-
}
|
|
190
|
-
};
|
|
191
|
-
const customObjectFileNameToId = async (conn, filePath) => {
|
|
192
|
-
try {
|
|
193
|
-
const customObject = await conn.singleRecordQuery(`SELECT Id FROM CustomObject WHERE DeveloperName = '${node_path_1.default.basename(filePath.replace(/__c/g, ''), '.object-meta.xml')}'`, {
|
|
194
|
-
tooling: true,
|
|
195
|
-
});
|
|
196
|
-
return customObject.Id;
|
|
197
|
-
}
|
|
198
|
-
catch (error) {
|
|
199
|
-
throw messages.createError('CustomObjectIdNotFound', [filePath]);
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
|
-
const apexClassFileNameToId = async (conn, filePath) => {
|
|
203
|
-
try {
|
|
204
|
-
const apexClass = await conn.singleRecordQuery(`SELECT Id FROM ApexClass WHERE Name = '${node_path_1.default.basename(filePath, '.cls')}'`, {
|
|
205
|
-
tooling: true,
|
|
206
|
-
});
|
|
207
|
-
return apexClass.Id;
|
|
208
|
-
}
|
|
209
|
-
catch (error) {
|
|
210
|
-
throw messages.createError('ApexClassIdNotFound', [filePath]);
|
|
211
|
-
}
|
|
212
|
-
};
|
|
213
|
-
let redirectUri = '';
|
|
214
|
-
switch (typeName) {
|
|
215
|
-
case 'ApexClass':
|
|
216
|
-
redirectUri = `lightning/setup/ApexClasses/page?address=%2F${await apexClassFileNameToId(this.connection, file)}`;
|
|
217
|
-
break;
|
|
218
|
-
case 'CustomObject':
|
|
219
|
-
redirectUri = `lightning/setup/ObjectManager/${await customObjectFileNameToId(this.connection, file)}/Details/view`;
|
|
220
|
-
break;
|
|
221
|
-
case 'Bot':
|
|
222
|
-
redirectUri = `/AiCopilot/copilotStudio.app#/copilot/builder?copilotId=${await botFileNameToId(this.connection, file)}`;
|
|
223
|
-
break;
|
|
224
|
-
case 'ApexPage':
|
|
225
|
-
redirectUri = `/apex/${node_path_1.default.basename(file).replace('.page-meta.xml', '').replace('.page', '')}`;
|
|
226
|
-
break;
|
|
227
|
-
case 'Flow':
|
|
228
|
-
redirectUri = `/builder_platform_interaction/flowBuilder.app?flowId=${await flowFileNameToId(this.connection, file)}`;
|
|
229
|
-
break;
|
|
230
|
-
case 'FlexiPage':
|
|
231
|
-
redirectUri = `/visualEditor/appBuilder.app?pageId=${await flexiPageFilenameToId(this.connection, file)}`;
|
|
232
|
-
break;
|
|
233
|
-
default:
|
|
234
|
-
redirectUri = '/lightning/setup/FlexiPageList/home';
|
|
235
|
-
break;
|
|
236
|
-
}
|
|
237
|
-
return this.getFrontDoorUrl(redirectUri);
|
|
238
|
-
}
|
|
239
|
-
/**
|
|
240
|
-
* Get a Frontdoor URL
|
|
241
|
-
*
|
|
242
|
-
* This uses the UI Bridge API to generate a single-use Frontdoor URL:
|
|
243
|
-
* https://help.salesforce.com/s/articleView?id=xcloud.frontdoor_singleaccess.htm&type=5
|
|
244
|
-
*/
|
|
245
|
-
async getFrontDoorUrl(redirectUri) {
|
|
246
|
-
// the `singleaccess` endpoint returns 403 when using an expired token and jsforce only triggers a token refresh on 401 so we check if it's valid first
|
|
247
|
-
await this.refreshAuth();
|
|
248
|
-
const singleAccessUrl = new URL('/services/oauth2/singleaccess', this.connection.instanceUrl);
|
|
249
|
-
if (redirectUri) {
|
|
250
|
-
singleAccessUrl.searchParams.append('redirect_uri', redirectUri);
|
|
251
|
-
}
|
|
252
|
-
const response = await this.connection.requestGet(singleAccessUrl.toString());
|
|
253
|
-
if (response.frontdoor_uri)
|
|
254
|
-
return response.frontdoor_uri;
|
|
255
|
-
throw new sfError_1.SfError(messages.getMessage('FrontdoorURLError')).setData(response);
|
|
256
|
-
}
|
|
257
149
|
/**
|
|
258
150
|
* create a sandbox from a production org
|
|
259
151
|
* 'this' needs to be a production org with sandbox licenses available
|
|
@@ -1033,7 +925,7 @@ class Org extends kit_1.AsyncOptionalCreatable {
|
|
|
1033
925
|
}
|
|
1034
926
|
async getLocalDataDir(orgDataPath) {
|
|
1035
927
|
const rootFolder = await config_1.Config.resolveRootFolder(false);
|
|
1036
|
-
return node_path_1.
|
|
928
|
+
return (0, node_path_1.join)(rootFolder, global_1.Global.SFDX_STATE_FOLDER, orgDataPath ? orgDataPath : 'orgs');
|
|
1037
929
|
}
|
|
1038
930
|
/**
|
|
1039
931
|
* Gets the sandboxProcessObject and then polls for it to complete.
|
|
@@ -1459,7 +1351,7 @@ class Org extends kit_1.AsyncOptionalCreatable {
|
|
|
1459
1351
|
async removeSourceTrackingFiles() {
|
|
1460
1352
|
try {
|
|
1461
1353
|
const rootFolder = await config_1.Config.resolveRootFolder(false);
|
|
1462
|
-
await fs.promises.rm(node_path_1.
|
|
1354
|
+
await fs.promises.rm((0, node_path_1.join)(rootFolder, global_1.Global.SF_STATE_FOLDER, 'orgs', this.getOrgId()), {
|
|
1463
1355
|
recursive: true,
|
|
1464
1356
|
force: true,
|
|
1465
1357
|
});
|
|
@@ -4,6 +4,7 @@ import { OrgAccessor } from './accessors/orgAccessor';
|
|
|
4
4
|
import { SandboxAccessor } from './accessors/sandboxAccessor';
|
|
5
5
|
export declare class StateAggregator extends AsyncOptionalCreatable {
|
|
6
6
|
private static instanceMap;
|
|
7
|
+
private static readonly mutex;
|
|
7
8
|
aliases: AliasAccessor;
|
|
8
9
|
orgs: OrgAccessor;
|
|
9
10
|
sandboxes: SandboxAccessor;
|
|
@@ -17,7 +18,14 @@ export declare class StateAggregator extends AsyncOptionalCreatable {
|
|
|
17
18
|
* Clear the cache to force reading from disk.
|
|
18
19
|
*
|
|
19
20
|
* *NOTE: Only call this method if you must and you know what you are doing.*
|
|
21
|
+
* *NOTE: This call is NOT thread-safe, so it should only be called when no other threads are using the StateAggregator.*
|
|
20
22
|
*/
|
|
21
23
|
static clearInstance(path?: string): void;
|
|
24
|
+
/**
|
|
25
|
+
* Clear the cache to force reading from disk in a thread-safe manner.
|
|
26
|
+
*
|
|
27
|
+
* *NOTE: Only call this method if you must and you know what you are doing.*
|
|
28
|
+
*/
|
|
29
|
+
static clearInstanceAsync(path?: string): Promise<void>;
|
|
22
30
|
protected init(): Promise<void>;
|
|
23
31
|
}
|
|
@@ -9,11 +9,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
9
9
|
exports.StateAggregator = void 0;
|
|
10
10
|
const kit_1 = require("@salesforce/kit");
|
|
11
11
|
const global_1 = require("../global");
|
|
12
|
+
const mutex_1 = require("../util/mutex");
|
|
12
13
|
const aliasAccessor_1 = require("./accessors/aliasAccessor");
|
|
13
14
|
const orgAccessor_1 = require("./accessors/orgAccessor");
|
|
14
15
|
const sandboxAccessor_1 = require("./accessors/sandboxAccessor");
|
|
15
16
|
class StateAggregator extends kit_1.AsyncOptionalCreatable {
|
|
16
17
|
static instanceMap = new Map();
|
|
18
|
+
static mutex = new mutex_1.Mutex();
|
|
17
19
|
aliases;
|
|
18
20
|
orgs;
|
|
19
21
|
sandboxes;
|
|
@@ -23,20 +25,33 @@ class StateAggregator extends kit_1.AsyncOptionalCreatable {
|
|
|
23
25
|
* HomeDir might be stubbed in tests
|
|
24
26
|
*/
|
|
25
27
|
static async getInstance() {
|
|
26
|
-
|
|
27
|
-
StateAggregator.instanceMap.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
return StateAggregator.mutex.lock(async () => {
|
|
29
|
+
if (!StateAggregator.instanceMap.has(global_1.Global.DIR)) {
|
|
30
|
+
StateAggregator.instanceMap.set(global_1.Global.DIR, await StateAggregator.create());
|
|
31
|
+
}
|
|
32
|
+
// TS assertion is valid because there either was one OR it was just now instantiated
|
|
33
|
+
return StateAggregator.instanceMap.get(global_1.Global.DIR);
|
|
34
|
+
});
|
|
31
35
|
}
|
|
32
36
|
/**
|
|
33
37
|
* Clear the cache to force reading from disk.
|
|
34
38
|
*
|
|
35
39
|
* *NOTE: Only call this method if you must and you know what you are doing.*
|
|
40
|
+
* *NOTE: This call is NOT thread-safe, so it should only be called when no other threads are using the StateAggregator.*
|
|
36
41
|
*/
|
|
37
42
|
static clearInstance(path = global_1.Global.DIR) {
|
|
38
43
|
StateAggregator.instanceMap.delete(path);
|
|
39
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Clear the cache to force reading from disk in a thread-safe manner.
|
|
47
|
+
*
|
|
48
|
+
* *NOTE: Only call this method if you must and you know what you are doing.*
|
|
49
|
+
*/
|
|
50
|
+
static async clearInstanceAsync(path = global_1.Global.DIR) {
|
|
51
|
+
return StateAggregator.mutex.lock(() => {
|
|
52
|
+
StateAggregator.clearInstance(path);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
40
55
|
async init() {
|
|
41
56
|
this.orgs = await orgAccessor_1.OrgAccessor.create();
|
|
42
57
|
this.sandboxes = await sandboxAccessor_1.SandboxAccessor.create();
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A mutual exclusion (mutex) class that ensures only one asynchronous operation
|
|
3
|
+
* can execute at a time, providing thread-safe execution of critical sections.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* const mutex = new Mutex();
|
|
8
|
+
*
|
|
9
|
+
* // Only one of these will execute at a time
|
|
10
|
+
* mutex.lock(async () => {
|
|
11
|
+
* // Critical section code here
|
|
12
|
+
* return someAsyncOperation();
|
|
13
|
+
* });
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare class Mutex {
|
|
17
|
+
/**
|
|
18
|
+
* Internal promise chain that maintains the mutex state.
|
|
19
|
+
* Each new lock acquisition is chained to this promise.
|
|
20
|
+
*
|
|
21
|
+
* @private
|
|
22
|
+
*/
|
|
23
|
+
private mutex;
|
|
24
|
+
/**
|
|
25
|
+
* Acquires the mutex lock and executes the provided function.
|
|
26
|
+
* The function will not execute until all previously queued operations complete.
|
|
27
|
+
*
|
|
28
|
+
* @template T - The return type of the function
|
|
29
|
+
* @param fn - The function to execute while holding the mutex lock. Can be synchronous or asynchronous.
|
|
30
|
+
* @returns A promise that resolves with the result of the function execution
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* const result = await mutex.lock(async () => {
|
|
35
|
+
* // This code is guaranteed to run exclusively
|
|
36
|
+
* return await someAsyncOperation();
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
lock<T>(fn: () => Promise<T> | T): Promise<T>;
|
|
41
|
+
/**
|
|
42
|
+
* Acquires the mutex by waiting for the current promise chain to resolve
|
|
43
|
+
* and returns a release function to unlock the mutex.
|
|
44
|
+
*
|
|
45
|
+
* @private
|
|
46
|
+
* @returns A promise that resolves to a function that releases the mutex lock
|
|
47
|
+
*/
|
|
48
|
+
private acquire;
|
|
49
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2025, salesforce.com, inc.
|
|
4
|
+
* All rights reserved.
|
|
5
|
+
* Licensed under the BSD 3-Clause license.
|
|
6
|
+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.Mutex = void 0;
|
|
10
|
+
/**
|
|
11
|
+
* A mutual exclusion (mutex) class that ensures only one asynchronous operation
|
|
12
|
+
* can execute at a time, providing thread-safe execution of critical sections.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const mutex = new Mutex();
|
|
17
|
+
*
|
|
18
|
+
* // Only one of these will execute at a time
|
|
19
|
+
* mutex.lock(async () => {
|
|
20
|
+
* // Critical section code here
|
|
21
|
+
* return someAsyncOperation();
|
|
22
|
+
* });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
class Mutex {
|
|
26
|
+
/**
|
|
27
|
+
* Internal promise chain that maintains the mutex state.
|
|
28
|
+
* Each new lock acquisition is chained to this promise.
|
|
29
|
+
*
|
|
30
|
+
* @private
|
|
31
|
+
*/
|
|
32
|
+
mutex = Promise.resolve();
|
|
33
|
+
/**
|
|
34
|
+
* Acquires the mutex lock and executes the provided function.
|
|
35
|
+
* The function will not execute until all previously queued operations complete.
|
|
36
|
+
*
|
|
37
|
+
* @template T - The return type of the function
|
|
38
|
+
* @param fn - The function to execute while holding the mutex lock. Can be synchronous or asynchronous.
|
|
39
|
+
* @returns A promise that resolves with the result of the function execution
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* const result = await mutex.lock(async () => {
|
|
44
|
+
* // This code is guaranteed to run exclusively
|
|
45
|
+
* return await someAsyncOperation();
|
|
46
|
+
* });
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
async lock(fn) {
|
|
50
|
+
const unlock = await this.acquire();
|
|
51
|
+
try {
|
|
52
|
+
return await fn();
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
unlock();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Acquires the mutex by waiting for the current promise chain to resolve
|
|
60
|
+
* and returns a release function to unlock the mutex.
|
|
61
|
+
*
|
|
62
|
+
* @private
|
|
63
|
+
* @returns A promise that resolves to a function that releases the mutex lock
|
|
64
|
+
*/
|
|
65
|
+
async acquire() {
|
|
66
|
+
let release;
|
|
67
|
+
const promise = new Promise((resolve) => {
|
|
68
|
+
release = resolve;
|
|
69
|
+
});
|
|
70
|
+
const currentMutex = this.mutex;
|
|
71
|
+
this.mutex = this.mutex.then(() => promise);
|
|
72
|
+
await currentMutex;
|
|
73
|
+
return release;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
exports.Mutex = Mutex;
|
|
77
|
+
//# sourceMappingURL=mutex.js.map
|
package/messages/org.md
CHANGED
|
@@ -69,19 +69,3 @@ We found more than one SandboxProcess with the SandboxName %s.
|
|
|
69
69
|
# sandboxNotResumable
|
|
70
70
|
|
|
71
71
|
The sandbox %s cannot resume with status of %s.
|
|
72
|
-
|
|
73
|
-
# FrontdoorURLError
|
|
74
|
-
|
|
75
|
-
Failed to generate a frontdoor URL.
|
|
76
|
-
|
|
77
|
-
# FlowIdNotFound
|
|
78
|
-
|
|
79
|
-
ID not found for Flow %s.
|
|
80
|
-
|
|
81
|
-
# CustomObjectIdNotFound
|
|
82
|
-
|
|
83
|
-
ID not found for custom object %s.
|
|
84
|
-
|
|
85
|
-
# ApexClassIdNotFound
|
|
86
|
-
|
|
87
|
-
ID not found for Apex class %s.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/core",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.17.0",
|
|
4
4
|
"description": "Core libraries to interact with SFDX projects, orgs, and APIs.",
|
|
5
5
|
"main": "lib/index",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"messageTransformer/messageTransformer.ts"
|
|
54
54
|
],
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@jsforce/jsforce-node": "^3.
|
|
56
|
+
"@jsforce/jsforce-node": "^3.8.2",
|
|
57
57
|
"@salesforce/kit": "^3.2.2",
|
|
58
58
|
"@salesforce/schemas": "^1.9.0",
|
|
59
59
|
"@salesforce/ts-types": "^2.0.10",
|