Skip to content

Commit 3ded96d

Browse files
committed
feat: sync customObj when their fields sync
1 parent 639d459 commit 3ded96d

8 files changed

+419
-337
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"test": "sf-test --require ts-node/register",
2828
"test:nuts": "nyc mocha \"**/*.nut.ts\" --slow 4500 --timeout 600000 --parallel",
2929
"test:nuts:commands": "nyc mocha \"**/commands/*.nut.ts\" --slow 4500 --timeout 600000 --parallel",
30-
"test:nuts:commands:basics": "nyc mocha \"**/commands/basics.nut.ts\" --slow 4500 --timeout 600000 --parallel"
30+
"test:nuts:commands:basics": "nyc mocha \"**/commands/basics.nut.ts\" --slow 4500 --timeout 600000"
3131
},
3232
"keywords": [
3333
"force",

src/shared/metadataKeys.ts

+21-10
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,37 @@
77
import { RemoteSyncInput } from './types';
88
import { getMetadataKey } from './remoteSourceTrackingService';
99

10+
// LWC can have child folders (ex: dynamic templates like /templates/noDataIllustration.html
11+
const pathAfterFullName = (fileResponse: RemoteSyncInput): string =>
12+
fileResponse && fileResponse.filePath
13+
? fileResponse.filePath.substr(fileResponse.filePath.indexOf(fileResponse.fullName))
14+
: '';
15+
1016
// handle all "weird" type/name translation between SourceMember and SDR FileResponse
17+
// These get de-duplicated in a set later
1118
export const getMetadataKeyFromFileResponse = (fileResponse: RemoteSyncInput): string[] => {
12-
// LWC can have child folders (ex: dynamic templates like LightningComponentResource__errorPanel/templates/noDataIllustration.html
13-
const pathAfterFullName = (): string =>
14-
fileResponse && fileResponse.filePath
15-
? fileResponse.filePath.substr(fileResponse.filePath.indexOf(fileResponse.fullName))
16-
: '';
17-
19+
// also create an element for the parent object
20+
if (fileResponse.type === 'CustomField' && fileResponse.filePath) {
21+
const splits = fileResponse.filePath.split('/');
22+
const objectFolderIndex = splits.indexOf('objects');
23+
return [
24+
getMetadataKey('CustomObject', splits[objectFolderIndex + 1]),
25+
getMetadataKey(fileResponse.type, fileResponse.fullName),
26+
];
27+
}
1828
// Aura/LWC need to have both the bundle level and file level keys
19-
// These get de-duplicated in a set later
2029
if (fileResponse.type === 'LightningComponentBundle' && fileResponse.filePath) {
2130
return [
22-
`LightningComponentResource__${pathAfterFullName()}`,
31+
`LightningComponentResource__${pathAfterFullName(fileResponse)}`,
2332
getMetadataKey(fileResponse.type, fileResponse.fullName),
2433
];
2534
}
2635
if (fileResponse.type === 'AuraDefinitionBundle' && fileResponse.filePath) {
27-
return [`AuraDefinition__${pathAfterFullName()}`, getMetadataKey(fileResponse.type, fileResponse.fullName)];
36+
return [
37+
`AuraDefinition__${pathAfterFullName(fileResponse)}`,
38+
getMetadataKey(fileResponse.type, fileResponse.fullName),
39+
];
2840
}
29-
3041
// standard key
3142
return [getMetadataKey(fileResponse.type, fileResponse.fullName)];
3243
};

src/shared/remoteSourceTrackingService.ts

+22-14
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
150150
try {
151151
await super.init();
152152
} catch (err) {
153-
throw SfdxError.wrap(err);
153+
throw SfdxError.wrap(err as Error);
154154
}
155155

156156
const contents = this.getTypedContents();
@@ -169,9 +169,15 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
169169
await this.write();
170170
} catch (e) {
171171
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
172-
if (e.name === 'INVALID_TYPE' && e.message.includes("sObject type 'SourceMember' is not supported")) {
172+
if (
173+
e instanceof SfdxError &&
174+
e.name === 'INVALID_TYPE' &&
175+
e.message.includes("sObject type 'SourceMember' is not supported")
176+
) {
173177
// non-source-tracked org E.G. DevHub or trailhead playground
174178
this.isSourceTrackedOrg = false;
179+
} else {
180+
throw e;
175181
}
176182
}
177183
}
@@ -195,20 +201,22 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
195201
// this can be super-repetitive on a large ExperienceBundle where there is an element for each file but only one Revision for the entire bundle
196202
// any item in an aura/LWC bundle needs to represent the top (bundle) level and the file itself
197203
// so we de-dupe via a set
198-
[...new Set(elements.map((element) => getMetadataKeyFromFileResponse(element)).flat())].map((metadataKey) => {
199-
const revision = revisions[metadataKey];
200-
if (revision && revision.lastRetrievedFromServer !== revision.serverRevisionCounter) {
201-
if (!quiet) {
202-
this.logger.debug(
203-
`Syncing ${metadataKey} revision from ${revision.lastRetrievedFromServer} to ${revision.serverRevisionCounter}`
204-
);
204+
Array.from(new Set(elements.map((element) => getMetadataKeyFromFileResponse(element)).flat())).map(
205+
(metadataKey) => {
206+
const revision = revisions[metadataKey];
207+
if (revision && revision.lastRetrievedFromServer !== revision.serverRevisionCounter) {
208+
if (!quiet) {
209+
this.logger.debug(
210+
`Syncing ${metadataKey} revision from ${revision.lastRetrievedFromServer} to ${revision.serverRevisionCounter}`
211+
);
212+
}
213+
revision.lastRetrievedFromServer = revision.serverRevisionCounter;
214+
this.setMemberRevision(metadataKey, revision);
215+
} else {
216+
this.logger.warn(`found no matching revision for ${metadataKey}`);
205217
}
206-
revision.lastRetrievedFromServer = revision.serverRevisionCounter;
207-
this.setMemberRevision(metadataKey, revision);
208-
} else {
209-
this.logger.warn(`found no matching revision for ${metadataKey}`);
210218
}
211-
});
219+
);
212220

213221
await this.write();
214222
}

src/sourceTracking.ts

+9-9
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,13 @@ export class SourceTracking {
6464
this.logger = Logger.childFromRoot('SourceTracking');
6565
}
6666

67-
public async deployLocalChanges({ overwrite = false, ignoreWarnings = false, wait = 33 }): Promise<void> {
68-
// TODO: this is basically the logic for a push
69-
}
67+
// public async deployLocalChanges({ overwrite = false, ignoreWarnings = false, wait = 33 }): Promise<void> {
68+
// // TODO: this is basically the logic for a push
69+
// }
7070

71-
public async retrieveRemoteChanges(): Promise<void> {
72-
// TODO: this is basically the logic for a pull
73-
}
71+
// public async retrieveRemoteChanges(): Promise<void> {
72+
// // TODO: this is basically the logic for a pull
73+
// }
7474

7575
/**
7676
* Get metadata changes made locally and in the org.
@@ -342,8 +342,8 @@ export class SourceTracking {
342342
}
343343
});
344344
return excludeUnresolvable
345-
? [...new Set(elementMap.values())].filter((changeResult) => changeResult.name && changeResult.type)
346-
: [...new Set(elementMap.values())];
345+
? Array.from(new Set(elementMap.values())).filter((changeResult) => changeResult.name && changeResult.type)
346+
: Array.from(new Set(elementMap.values()));
347347
}
348348

349349
public async getConflicts(): Promise<ChangeResult[]> {
@@ -377,7 +377,7 @@ export class SourceTracking {
377377
});
378378
});
379379
// deeply de-dupe
380-
return [...conflicts];
380+
return Array.from(conflicts);
381381
}
382382

383383
private ensureRelative(filePath: string): string {

test/nuts/commands/basics.nut.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,17 @@ describe('end-to-end-test for tracking with an org (single packageDir)', () => {
5050

5151
const remoteResult = execCmd<StatusResult[]>('source:status --json --remote', { ensureExitCode: 0 }).jsonOutput
5252
.result;
53-
console.log(remoteResult);
53+
// console.log(remoteResult);
5454
expect(remoteResult.length).to.equal(1);
5555
expect(remoteResult.some((item) => item.type === 'Profile')).to.equal(true);
56+
// expect(remoteResult.some((item) => item.type === 'FieldRestrictionRule')).to.equal(true);
57+
// expect(remoteResult.some((item) => item.type === 'Audience')).to.equal(true);
5658
});
5759

5860
it('can pull the remote profile', () => {
59-
const pushResult = execCmd<FileResponse[]>('source:pull --json', { ensureExitCode: 0 }).jsonOutput.result;
60-
console.log(pushResult);
61-
expect(pushResult.some((item) => item.type === 'Profile')).to.equal(true);
61+
const pullResult = execCmd<FileResponse[]>('source:pull --json', { ensureExitCode: 0 }).jsonOutput.result;
62+
console.log(pullResult);
63+
expect(pullResult.some((item) => item.type === 'Profile')).to.equal(true);
6264
});
6365

6466
it('sees no local or remote changes', () => {

test/unit/metadataKeys.test.ts

+15
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,19 @@ describe('metadataKeys', () => {
6262
]);
6363
});
6464
});
65+
66+
describe('object children', () => {
67+
it('creates a key for the object from a field', () => {
68+
const fileResponse = {
69+
fullName: 'Case.Product__c',
70+
type: 'CustomField',
71+
state: 'Created',
72+
filePath: 'force-app/main/default/objects/Case/fields/Product__c.field-meta.xml',
73+
};
74+
expect(getMetadataKeyFromFileResponse(fileResponse)).to.deep.equal([
75+
'CustomObject__Case',
76+
'CustomField__Case.Product__c',
77+
]);
78+
});
79+
});
6580
});

tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
"compilerOptions": {
44
"outDir": "./lib"
55
},
6-
"include": ["src/**/*.ts", "test/unit/metadataKeys.test.ts"]
6+
"include": ["src/**/*.ts"]
77
}

0 commit comments

Comments
 (0)