Skip to content

Commit db8cb3e

Browse files
authored
fix: include parent in manifest when retrieving - W-18650723 (#1568)
* fix: include parent in manifest when retrieving * chore: update core lib to match PDR * chore: update core lib to latest
1 parent bb4ff65 commit db8cb3e

File tree

11 files changed

+315
-10
lines changed

11 files changed

+315
-10
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"node": ">=18.0.0"
2626
},
2727
"dependencies": {
28-
"@salesforce/core": "^8.11.0",
28+
"@salesforce/core": "^8.11.4",
2929
"@salesforce/kit": "^3.2.3",
3030
"@salesforce/ts-types": "^2.0.12",
3131
"@salesforce/types": "^1.3.0",

src/collections/componentSet.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ export class ComponentSet extends LazyCollection<MetadataComponent> {
8181
// all components stored here, regardless of what manifest they belong to
8282
private components = new DecodeableMap<string, DecodeableMap<string, SourceComponent>>();
8383

84+
// whether this component set is being used for a deploy
85+
// @ts-expect-error this is currently not used but could be used in the future
86+
private forDeploy = false;
87+
// whether this component set is being used for a retrieve
88+
private forRetrieve = false;
89+
8490
// internal component maps used by this.getObject() when building manifests.
8591
private destructiveComponents = {
8692
[DestructiveChangesType.PRE]: new DecodeableMap<string, DecodeableMap<string, SourceComponent>>(),
@@ -348,6 +354,8 @@ export class ComponentSet extends LazyCollection<MetadataComponent> {
348354
throw new SfError(messages.getMessage('error_no_source_to_deploy'), 'ComponentSetError');
349355
}
350356

357+
this.forDeploy = true;
358+
351359
if (
352360
typeof options.usernameOrConnection !== 'string' &&
353361
this.apiVersion &&
@@ -384,6 +392,8 @@ export class ComponentSet extends LazyCollection<MetadataComponent> {
384392
apiVersion: this.apiVersion,
385393
});
386394

395+
this.forRetrieve = true;
396+
387397
if (
388398
typeof options.usernameOrConnection !== 'string' &&
389399
this.apiVersion &&
@@ -427,8 +437,9 @@ export class ComponentSet extends LazyCollection<MetadataComponent> {
427437
.flatMap((c) => c.getChildren())
428438
.map((child) => addToTypeMap({ typeMap, type: child.type, fullName: child.fullName, destructiveType }));
429439

430-
// logic: if this is a decomposed type, skip its inclusion in the manifest if the parent is "empty"
440+
// logic: if this is a decomposed type not being retrieved, skip its inclusion in the manifest if the parent is "empty"
431441
if (
442+
!this.forRetrieve &&
432443
type.strategies?.transformer === 'decomposed' &&
433444
// exclude (ex: CustomObjectTranslation) where there are no addressable children
434445
Object.values(type.children?.types ?? {}).some((t) => t.unaddressableWithoutParent !== true) &&

test/collections/componentSet.test.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -980,7 +980,7 @@ describe('ComponentSet', () => {
980980
]);
981981
});
982982

983-
it('omits empty parents from the package manifest', async () => {
983+
it('omits empty parents from the package manifest when not a retrieve', async () => {
984984
const set = new ComponentSet([
985985
DECOMPOSED_CHILD_COMPONENT_1_EMPTY,
986986
DECOMPOSED_CHILD_COMPONENT_2_EMPTY,
@@ -999,6 +999,30 @@ describe('ComponentSet', () => {
999999
},
10001000
]);
10011001
});
1002+
1003+
it('does not omit empty parents from the package manifest for retrieves', async () => {
1004+
const set = new ComponentSet([
1005+
DECOMPOSED_CHILD_COMPONENT_1_EMPTY,
1006+
DECOMPOSED_CHILD_COMPONENT_2_EMPTY,
1007+
DECOMPOSED_COMPONENT_EMPTY,
1008+
]);
1009+
// @ts-expect-error modifying private property
1010+
set.forRetrieve = true;
1011+
expect((await set.getObject()).Package.types).to.deep.equal([
1012+
{
1013+
name: DECOMPOSED_CHILD_COMPONENT_1_EMPTY.type.name,
1014+
members: [DECOMPOSED_CHILD_COMPONENT_1_EMPTY.fullName],
1015+
},
1016+
{
1017+
name: DECOMPOSED_COMPONENT_EMPTY.type.name,
1018+
members: [DECOMPOSED_COMPONENT_EMPTY.fullName],
1019+
},
1020+
{
1021+
name: DECOMPOSED_CHILD_COMPONENT_2_EMPTY.type.name,
1022+
members: [DECOMPOSED_CHILD_COMPONENT_2_EMPTY.fullName],
1023+
},
1024+
]);
1025+
});
10021026
});
10031027

10041028
describe('getPackageXml', () => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
3+
<fields>
4+
<fullName>Title__c</fullName>
5+
<externalId>false</externalId>
6+
<label>Title</label>
7+
<length>30</length>
8+
<required>false</required>
9+
<trackTrending>false</trackTrending>
10+
<type>Text</type>
11+
<unique>false</unique>
12+
</fields>
13+
</CustomObject>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
3+
<types>
4+
<members>Broker__c.Title__c</members>
5+
<name>CustomField</name>
6+
</types>
7+
<version>59.0</version>
8+
</Package>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
3+
<fields>
4+
<fullName>Title__c</fullName>
5+
<externalId>false</externalId>
6+
<label>Title</label>
7+
<length>30</length>
8+
<required>false</required>
9+
<trackTrending>false</trackTrending>
10+
<type>Text</type>
11+
<unique>false</unique>
12+
</fields>
13+
</CustomObject>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
3+
<types>
4+
<members>Broker__c.Title__c</members>
5+
<name>CustomField</name>
6+
</types>
7+
<types>
8+
<members>Broker__c</members>
9+
<name>CustomObject</name>
10+
</types>
11+
<version>59.0</version>
12+
</Package>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
3+
<fields>
4+
<fullName>Title__c</fullName>
5+
<externalId>false</externalId>
6+
<label>Title</label>
7+
<length>30</length>
8+
<required>false</required>
9+
<trackTrending>false</trackTrending>
10+
<type>Text</type>
11+
<unique>false</unique>
12+
</fields>
13+
</CustomObject>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
3+
<types>
4+
<members>Broker__c.Title__c</members>
5+
<name>CustomField</name>
6+
</types>
7+
<version>63.0</version>
8+
</Package>

test/snapshot/sampleProjects/customObjects-and-children/snapshots.test.ts

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ import * as path from 'node:path';
99
import {
1010
FORCE_APP,
1111
MDAPI_OUT,
12+
dirEntsToPaths,
1213
dirsAreIdentical,
1314
fileSnap,
1415
mdapiToSource,
1516
sourceToMdapi,
1617
} from '../../helper/conversions';
18+
import { MetadataConverter } from '../../../../src/convert/metadataConverter';
19+
import { ComponentSetBuilder } from '../../../../src/collections/componentSetBuilder';
1720

1821
// we don't want failing tests outputting over each other
1922
/* eslint-disable no-await-in-loop */
@@ -49,3 +52,138 @@ describe('Custom objects and children', () => {
4952
]);
5053
});
5154
});
55+
56+
/** Return only the files involved in the conversion */
57+
const getConvertedFilePaths = async (outputDir: string): Promise<string[]> =>
58+
dirEntsToPaths(
59+
await fs.promises.readdir(outputDir, {
60+
recursive: true,
61+
withFileTypes: true,
62+
})
63+
);
64+
65+
describe('CustomField with empty CustomObject - retrieve', () => {
66+
const testDir = path.join('test', 'snapshot', 'sampleProjects', 'customObjects-and-children');
67+
68+
// The directory of snapshots containing expected conversion results
69+
const snapshotsDir = path.join(testDir, '__snapshots__');
70+
71+
// MDAPI format of the original source
72+
const sourceDir = path.join(testDir, 'originalMdapi2');
73+
74+
// The directory where metadata is converted as part of retrieve testing
75+
const retrieveOutput = path.join(testDir, 'retrieveOutput');
76+
77+
// This test verifies that 1 custom field with an empty parent
78+
// does not omit the parent from the package manifest for a retrieve when
79+
// converting from source to mdapi.
80+
it('verify md files retrieve', async () => {
81+
const cs = await ComponentSetBuilder.build({
82+
metadata: {
83+
metadataEntries: ['CustomObject:Broker__c'],
84+
directoryPaths: [sourceDir],
85+
},
86+
projectDir: testDir,
87+
});
88+
89+
const sourceOutputDir = path.join(retrieveOutput, 'source');
90+
const mdOutputDir = path.join(retrieveOutput, 'mdapi');
91+
92+
await new MetadataConverter().convert(cs, 'source', {
93+
type: 'directory',
94+
outputDirectory: sourceOutputDir,
95+
genUniqueDir: false,
96+
});
97+
98+
const cs2 = await ComponentSetBuilder.build({
99+
metadata: {
100+
metadataEntries: ['CustomObject:Broker__c'],
101+
directoryPaths: [sourceOutputDir],
102+
},
103+
projectDir: testDir,
104+
});
105+
106+
// @ts-expect-error modifying private property
107+
cs2.forRetrieve = true;
108+
109+
await new MetadataConverter().convert(cs2, 'metadata', {
110+
type: 'directory',
111+
outputDirectory: mdOutputDir,
112+
genUniqueDir: false,
113+
});
114+
115+
const convertedFiles = await getConvertedFilePaths(mdOutputDir);
116+
for (const file of convertedFiles) {
117+
await fileSnap(file, testDir);
118+
}
119+
const expectedOutputDir = path.join(snapshotsDir, 'verify-md-files-retrieve.expected', 'retrieveOutput', 'mdapi');
120+
await dirsAreIdentical(expectedOutputDir, mdOutputDir);
121+
});
122+
123+
after(async () => {
124+
await Promise.all([fs.promises.rm(retrieveOutput, { recursive: true, force: true })]);
125+
});
126+
});
127+
128+
describe('CustomField with empty CustomObject - deploy', () => {
129+
const testDir = path.join('test', 'snapshot', 'sampleProjects', 'customObjects-and-children');
130+
131+
// The directory of snapshots containing expected conversion results
132+
const snapshotsDir = path.join(testDir, '__snapshots__');
133+
134+
// MDAPI format of the original source
135+
const sourceDir = path.join(testDir, 'originalMdapi2');
136+
137+
// The directory where metadata is converted as part of deploy testing
138+
const deployOutput = path.join(testDir, 'deployOutput');
139+
140+
// This test verifies that 1 custom field with an empty parent
141+
// omits the parent from the package manifest for a deploy when
142+
// converting from source to mdapi.
143+
it('verify md files deploy', async () => {
144+
const cs = await ComponentSetBuilder.build({
145+
metadata: {
146+
metadataEntries: ['CustomObject:Broker__c'],
147+
directoryPaths: [sourceDir],
148+
},
149+
projectDir: testDir,
150+
});
151+
152+
const sourceOutputDir = path.join(deployOutput, 'source');
153+
const mdOutputDir = path.join(deployOutput, 'mdapi');
154+
155+
await new MetadataConverter().convert(cs, 'source', {
156+
type: 'directory',
157+
outputDirectory: sourceOutputDir,
158+
genUniqueDir: false,
159+
});
160+
161+
const cs2 = await ComponentSetBuilder.build({
162+
metadata: {
163+
metadataEntries: ['CustomObject:Broker__c'],
164+
directoryPaths: [sourceOutputDir],
165+
},
166+
projectDir: testDir,
167+
});
168+
169+
// @ts-expect-error modifying private property
170+
cs2.forDeploy = true;
171+
172+
await new MetadataConverter().convert(cs2, 'metadata', {
173+
type: 'directory',
174+
outputDirectory: mdOutputDir,
175+
genUniqueDir: false,
176+
});
177+
178+
const convertedFiles = await getConvertedFilePaths(mdOutputDir);
179+
for (const file of convertedFiles) {
180+
await fileSnap(file, testDir);
181+
}
182+
const expectedOutputDir = path.join(snapshotsDir, 'verify-md-files-deploy.expected', 'deployOutput', 'mdapi');
183+
await dirsAreIdentical(expectedOutputDir, mdOutputDir);
184+
});
185+
186+
after(async () => {
187+
await Promise.all([fs.promises.rm(deployOutput, { recursive: true, force: true })]);
188+
});
189+
});

0 commit comments

Comments
 (0)