Skip to content

Commit c4689ee

Browse files
chore: merge main, resolve conflicts
2 parents 4672637 + cab4e49 commit c4689ee

File tree

6 files changed

+534
-1611
lines changed

6 files changed

+534
-1611
lines changed

CHANGELOG.md

Lines changed: 381 additions & 1450 deletions
Large diffs are not rendered by default.

METADATA_SUPPORT.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ This list compares metadata types found in Salesforce v60 with the [metadata reg
44

55
This repository is used by both the Salesforce CLIs and Salesforce's VSCode Extensions.
66

7-
Currently, there are 565/597 supported metadata types.
7+
Currently, there are 565/598 supported metadata types.
88
For status on any existing gaps, please search or file an issue in the [Salesforce CLI issues only repo](https://github.com/forcedotcom/cli/issues).
99
To contribute a new metadata type, please see the [Contributing Metadata Types to the Registry](./contributing/metadata.md)
1010

@@ -303,6 +303,7 @@ To contribute a new metadata type, please see the [Contributing Metadata Types t
303303
| GenAiFunction || Not supported, but support could be added |
304304
| GenAiPlanner || Not supported, but support could be added |
305305
| GenAiPlugin || Not supported, but support could be added |
306+
| GenAiPluginInstructionDef || Not supported, but support could be added |
306307
| GlobalValueSet || |
307308
| GlobalValueSetTranslation || |
308309
| GoogleAppsSettings || |

package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@salesforce/source-deploy-retrieve",
3-
"version": "11.1.0",
3+
"version": "11.1.1",
44
"description": "JavaScript library to run Salesforce metadata deploys and retrieves",
55
"main": "lib/src/index.js",
66
"author": "Salesforce",
@@ -36,8 +36,7 @@
3636
"jszip": "^3.10.1",
3737
"mime": "2.6.0",
3838
"minimatch": "^5.1.6",
39-
"proxy-agent": "^6.4.0",
40-
"ts-retry-promise": "^0.7.1"
39+
"proxy-agent": "^6.4.0"
4140
},
4241
"devDependencies": {
4342
"@jsforce/jsforce-node": "^3.1.0",

src/resolve/connectionResolver.ts

Lines changed: 45 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@
55
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
66
*/
77

8-
import { retry, NotRetryableError, RetryError } from 'ts-retry-promise';
9-
import { PollingClient, StatusResult, Connection, Logger, Messages, Lifecycle, SfError } from '@salesforce/core';
10-
import { Duration, ensureArray } from '@salesforce/kit';
8+
import { Connection, Logger, Messages, Lifecycle, SfError } from '@salesforce/core';
119
import { ensurePlainObject, ensureString, isPlainObject } from '@salesforce/ts-types';
1210
import { RegistryAccess } from '../registry/registryAccess';
1311
import { MetadataType } from '../registry/types';
@@ -30,7 +28,6 @@ export type ResolveConnectionResult = {
3028
* Resolve MetadataComponents from an org connection
3129
*/
3230
export class ConnectionResolver {
33-
protected logger: Logger;
3431
private connection: Connection;
3532
private registry: RegistryAccess;
3633

@@ -41,7 +38,6 @@ export class ConnectionResolver {
4138
public constructor(connection: Connection, registry = new RegistryAccess(), mdTypes?: string[]) {
4239
this.connection = connection;
4340
this.registry = registry;
44-
this.logger = Logger.childFromRoot(this.constructor.name);
4541
this.mdTypeNames = mdTypes?.length
4642
? // ensure the types passed in are valid per the registry
4743
mdTypes.filter((t) => this.registry.getTypeByName(t))
@@ -57,21 +53,21 @@ export class ConnectionResolver {
5753
const lifecycle = Lifecycle.getInstance();
5854

5955
const componentFromDescribe = (
60-
await Promise.all(this.mdTypeNames.map((type) => this.listMembers({ type })))
56+
await Promise.all(this.mdTypeNames.map((type) => listMembers(this.registry)(this.connection)({ type })))
6157
).flat();
6258

6359
for (const component of componentFromDescribe) {
6460
let componentType: MetadataType;
65-
if (typeof component.type === 'string' && component.type.length) {
61+
if (isNonEmptyString(component.type)) {
6662
componentType = this.registry.getTypeByName(component.type);
67-
} else if (typeof component.fileName === 'string' && component.fileName.length) {
63+
} else if (isNonEmptyString(component.fileName)) {
6864
// fix { type: { "$": { "xsi:nil": "true" } } }
6965
componentType = ensurePlainObject(
7066
this.registry.getTypeBySuffix(extName(component.fileName)),
7167
`No type found for ${component.fileName} when matching by suffix. Check the file extension.`
7268
);
7369
component.type = componentType.name;
74-
} else if (component.type === undefined && component.fileName === undefined) {
70+
} else if (!isNonEmptyString(component.type) && !isNonEmptyString(component.fileName)) {
7571
// has no type and has no filename! Warn and skip that component.
7672
// eslint-disable-next-line no-await-in-loop
7773
await Promise.all([
@@ -91,11 +87,10 @@ export class ConnectionResolver {
9187

9288
Aggregator.push(component);
9389
componentTypes.add(componentType);
94-
const folderContentType = componentType.folderContentType;
95-
if (folderContentType) {
90+
if (componentType.folderContentType) {
9691
childrenPromises.push(
97-
this.listMembers({
98-
type: this.registry.getTypeByName(folderContentType).name,
92+
listMembers(this.registry)(this.connection)({
93+
type: this.registry.getTypeByName(componentType.folderContentType).name,
9994
folder: component.fullName,
10095
})
10196
);
@@ -106,7 +101,7 @@ export class ConnectionResolver {
106101
const childTypes = componentType.children?.types;
107102
if (childTypes) {
108103
Object.values(childTypes).map((childType) => {
109-
childrenPromises.push(this.listMembers({ type: childType.name }));
104+
childrenPromises.push(listMembers(this.registry)(this.connection)({ type: childType.name }));
110105
});
111106
}
112107
}
@@ -125,93 +120,60 @@ export class ConnectionResolver {
125120
apiVersion: this.connection.getApiVersion(),
126121
};
127122
}
123+
}
128124

129-
private async listMembers(query: ListMetadataQuery): Promise<RelevantFileProperties[]> {
130-
let members: RelevantFileProperties[] = [];
131-
132-
const pollingOptions: PollingClient.Options = {
133-
frequency: Duration.milliseconds(1000),
134-
timeout: Duration.minutes(3),
135-
poll: async (): Promise<StatusResult> => {
136-
const res = ensureArray(await this.connection.metadata.list(query));
137-
return { completed: true, payload: res };
138-
},
139-
};
140-
141-
const pollingClient = await PollingClient.create(pollingOptions);
142-
143-
try {
144-
members = await pollingClient.subscribe();
145-
} catch (error) {
146-
// throw error if PollingClient timed out.
147-
if (error instanceof NotRetryableError) {
148-
throw NotRetryableError;
149-
}
150-
this.logger.debug((error as Error).message);
151-
members = [];
152-
}
153-
154-
// if the Metadata Type doesn't return a correct fileName then help it out
155-
for (const m of members) {
156-
if (typeof m.fileName == 'object') {
157-
const t = this.registry.getTypeByName(query.type);
158-
m.fileName = `${t.directoryName}/${m.fullName}.${t.suffix}`;
159-
}
160-
}
125+
const listMembers =
126+
(registry: RegistryAccess) =>
127+
(connection: Connection) =>
128+
async (query: ListMetadataQuery): Promise<RelevantFileProperties[]> => {
129+
const mdType = registry.getTypeByName(query.type);
161130

162131
// Workaround because metadata.list({ type: 'StandardValueSet' }) returns []
163-
if (query.type === this.registry.getRegistry().types.standardvalueset.name && members.length === 0) {
132+
if (mdType.name === registry.getRegistry().types.standardvalueset.name) {
133+
const members: RelevantFileProperties[] = [];
134+
164135
const standardValueSetPromises = standardValueSet.fullnames.map(async (standardValueSetFullName) => {
165136
try {
166-
// The 'singleRecordQuery' method was having connection errors, using `retry` resolves this
167-
// Note that this type of connection retry logic may someday be added to jsforce v2
168-
// Once that happens this logic could be reverted
169-
const standardValueSetRecord = await retry<StdValueSetRecord>(async () => {
170-
try {
171-
return await this.connection.singleRecordQuery(
172-
`SELECT Id, MasterLabel, Metadata FROM StandardValueSet WHERE MasterLabel = '${standardValueSetFullName}'`,
173-
{ tooling: true }
174-
);
175-
} catch (err) {
176-
// We exit the retry loop with `NotRetryableError` if we get an (expected) unsupported metadata type error
177-
const error = err as Error;
178-
if (error.message.includes('either inaccessible or not supported in Metadata API')) {
179-
this.logger.debug('Expected error:', error.message);
180-
throw new NotRetryableError(error.message);
181-
}
182-
183-
// Otherwise throw the err so we can retry again
184-
throw err;
185-
}
186-
});
137+
const standardValueSetRecord: StdValueSetRecord = await connection.singleRecordQuery(
138+
`SELECT Id, MasterLabel, Metadata FROM StandardValueSet WHERE MasterLabel = '${standardValueSetFullName}'`,
139+
{ tooling: true }
140+
);
187141

188142
return (
189143
standardValueSetRecord.Metadata.standardValue.length && {
190144
fullName: standardValueSetRecord.MasterLabel,
191-
fileName: `${this.registry.getRegistry().types.standardvalueset.directoryName}/${
192-
standardValueSetRecord.MasterLabel
193-
}.${this.registry.getRegistry().types.standardvalueset.suffix}`,
194-
type: this.registry.getRegistry().types.standardvalueset.name,
145+
fileName: `${mdType.directoryName}/${standardValueSetRecord.MasterLabel}.${mdType.suffix}`,
146+
type: mdType.name,
195147
}
196148
);
197149
} catch (err) {
198-
// error.message here will be overwritten by 'ts-retry-promise'
199-
// Example error.message from the library: "All retries failed" or "Met not retryable error"
200-
// 'ts-retry-promise' exposes the actual error on `error.lastError`
201-
const error = err as RetryError;
202-
203-
if (error.lastError?.message) {
204-
this.logger.debug(error.lastError.message);
205-
}
150+
const logger = Logger.childFromRoot('ConnectionResolver.listMembers');
151+
logger.debug(err);
206152
}
207153
});
208154
for await (const standardValueSetResult of standardValueSetPromises) {
209155
if (standardValueSetResult) {
210156
members.push(standardValueSetResult);
211157
}
212158
}
159+
return members;
213160
}
214161

215-
return members;
216-
}
217-
}
162+
try {
163+
return (await connection.metadata.list(query)).map(inferFilenamesFromType(mdType));
164+
} catch (error) {
165+
const logger = Logger.childFromRoot('ConnectionResolver.listMembers');
166+
logger.debug((error as Error).message);
167+
return [];
168+
}
169+
};
170+
171+
/* if the Metadata Type doesn't return a correct fileName then help it out */
172+
const inferFilenamesFromType =
173+
(metadataType: MetadataType) =>
174+
(member: RelevantFileProperties): RelevantFileProperties =>
175+
typeof member.fileName === 'object'
176+
? { ...member, fileName: `${metadataType.directoryName}/${member.fullName}.${metadataType.suffix}` }
177+
: member;
178+
179+
const isNonEmptyString = (value: string | undefined): boolean => typeof value === 'string' && value.length > 0;

0 commit comments

Comments
 (0)