Skip to content

Commit 639d459

Browse files
committed
feat: push supporting bundle types
1 parent ed53428 commit 639d459

8 files changed

+69
-40
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,5 @@ node_modules
3939
# os specific files
4040
.DS_Store
4141
.idea
42+
43+
testProj

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,16 @@ You should use the class named sourceTracking.
88

99
## TODO
1010

11+
consolidate the conflict logic into SourceTracking. Should return a ChangeResult[] of the conflicts so commands can display.
12+
1113
after pushing ebikes (testProj) the remote changes are showing in source tracking (they should have been polled for and retrieved!)
1214

1315
can migrate maxRevision.json to its new home
1416

1517
integration testing
1618

19+
why does push take so long?
20+
1721
Push can have partial successes and needs a proper status code ex:
1822

1923
```json

src/commands/source/pull.ts

+13-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { SfdxProject, Org } from '@salesforce/core';
1212
import { ComponentSet, ComponentStatus } from '@salesforce/source-deploy-retrieve';
1313
import { writeConflictTable } from '../../writeConflictTable';
1414
import { SourceTracking, ChangeResult } from '../../sourceTracking';
15-
1615
export default class SourcePull extends SfdxCommand {
1716
public static description = 'get local changes';
1817
protected static readonly flagsConfig: FlagsConfig = {
@@ -67,14 +66,25 @@ export default class SourcePull extends SfdxCommand {
6766
const changesToDeleteWithFilePaths = tracking.populateFilePaths(changesToDelete);
6867
// delete the files
6968
const filenames = changesToDeleteWithFilePaths
69+
// TODO: test that this works for undefined, string and string[]
7070
.map((change) => change.filenames as string[])
7171
.flat()
7272
.filter(Boolean);
7373
await Promise.all(filenames.map((filename) => unlink(filename)));
7474
await Promise.all([
7575
tracking.updateLocalTracking({ deletedFiles: filenames }),
7676
tracking.updateRemoteTracking(
77-
changesToDeleteWithFilePaths.map((change) => ({ type: change.type as string, name: change.name as string }))
77+
changesToDeleteWithFilePaths
78+
.map((change) =>
79+
change && change.filenames
80+
? change.filenames.map((filename) => ({
81+
type: change.type as string,
82+
fullName: change.name as string,
83+
filePath: filename,
84+
}))
85+
: []
86+
)
87+
.flat()
7888
),
7989
]);
8090
}
@@ -113,9 +123,8 @@ export default class SourcePull extends SfdxCommand {
113123
tracking.updateLocalTracking({
114124
files: successes.map((fileResponse) => fileResponse.filePath as string).filter(Boolean),
115125
}),
116-
// calling with no metadata types gets the latest sourceMembers from the org
117126
tracking.updateRemoteTracking(
118-
successes.map((fileResponse) => ({ name: fileResponse.fullName, type: fileResponse.type }))
127+
successes.map((success) => ({ filePath: success.filePath, type: success.type, fullName: success.fullName }))
119128
),
120129
]);
121130
return retrieveResult.getFileResponses();

src/commands/source/push.ts

+3-16
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@
88
import { FlagsConfig, flags, SfdxCommand } from '@salesforce/command';
99
import { SfdxProject, Org } from '@salesforce/core';
1010
import { ComponentSet, FileResponse, ComponentStatus } from '@salesforce/source-deploy-retrieve';
11-
import { MetadataKeyPair, SourceTracking, stringGuard } from '../../sourceTracking';
11+
import { SourceTracking, stringGuard } from '../../sourceTracking';
1212
import { writeConflictTable } from '../../writeConflictTable';
13-
1413
export default class SourcePush extends SfdxCommand {
1514
public static description = 'get local changes';
1615
protected static readonly flagsConfig: FlagsConfig = {
@@ -66,7 +65,7 @@ export default class SourcePush extends SfdxCommand {
6665

6766
if (deletes.length > 0) {
6867
this.ux.warn(
69-
`Delete not yet implemented in SDR. Would have deleted ${deletes.length > 0 ? deletes.join(',') : 'nothing'}`
68+
`Delete not yet implemented. Would have deleted ${deletes.length > 0 ? deletes.join(',') : 'nothing'}`
7069
);
7170
}
7271

@@ -82,21 +81,9 @@ export default class SourcePush extends SfdxCommand {
8281
if (!this.flags.json) {
8382
this.ux.logJson(result.response);
8483
}
85-
// and update the remote tracking
86-
const successComponentKeys = (
87-
Array.isArray(result.response.details.componentSuccesses)
88-
? result.response.details.componentSuccesses
89-
: [result.response.details.componentSuccesses]
90-
)
91-
.map((success) =>
92-
success?.fullName && success?.componentType
93-
? { name: success?.fullName, type: success?.componentType }
94-
: undefined
95-
)
96-
.filter(Boolean) as MetadataKeyPair[]; // we don't want package.xml
9784

9885
// this includes polling for sourceMembers
99-
await tracking.updateRemoteTracking(successComponentKeys);
86+
await tracking.updateRemoteTracking(successes);
10087
return result.getFileResponses();
10188
}
10289
}

src/shared/remoteSourceTrackingService.ts

+17-10
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import { ConfigFile, Logger, Org, SfdxError, Messages } from '@salesforce/core';
1313
import { Dictionary, Optional } from '@salesforce/ts-types';
1414
import { Duration, env, toNumber } from '@salesforce/kit';
1515
import { retryDecorator } from 'ts-retry-promise';
16+
import { RemoteSyncInput } from '../shared/types';
17+
import { getMetadataKeyFromFileResponse } from './metadataKeys';
1618

1719
export type MemberRevision = {
1820
serverRevisionCounter: number;
@@ -53,6 +55,7 @@ export namespace RemoteSourceTrackingService {
5355
export const getMetadataKey = (metadataType: string, metadataName: string): string => {
5456
return `${metadataType}__${metadataName}`;
5557
};
58+
5659
/**
5760
* This service handles source tracking of metadata between a local project and an org.
5861
* Source tracking state is persisted to .sfdx/orgs/<orgId>/maxRevision.json.
@@ -178,19 +181,21 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
178181
* pass in a set of metadata keys (type__name like 'ApexClass__MyClass').\
179182
* it sets their last retrieved revision to the current revision counter from the server.
180183
*/
181-
public async syncNamedElementsByKey(metadataKeys: string[]): Promise<void> {
182-
if (metadataKeys.length === 0) {
184+
public async syncSpecifiedElements(elements: RemoteSyncInput[]): Promise<void> {
185+
if (elements.length === 0) {
183186
return;
184187
}
185-
const quiet = metadataKeys.length > 100;
188+
const quiet = elements.length > 100;
186189
if (quiet) {
187-
this.logger.debug(`Syncing ${metadataKeys.length} Revisions by key`);
190+
this.logger.debug(`Syncing ${elements.length} Revisions by key`);
188191
}
189192

190-
// makes sure we have updated SourceMember data for the org; uses cache
191-
await this.retrieveUpdates();
192193
const revisions = this.getSourceMembers();
193-
metadataKeys.map((metadataKey) => {
194+
195+
// 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
196+
// any item in an aura/LWC bundle needs to represent the top (bundle) level and the file itself
197+
// so we de-dupe via a set
198+
[...new Set(elements.map((element) => getMetadataKeyFromFileResponse(element)).flat())].map((metadataKey) => {
194199
const revision = revisions[metadataKey];
195200
if (revision && revision.lastRetrievedFromServer !== revision.serverRevisionCounter) {
196201
if (!quiet) {
@@ -201,11 +206,13 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
201206
revision.lastRetrievedFromServer = revision.serverRevisionCounter;
202207
this.setMemberRevision(metadataKey, revision);
203208
} else {
204-
this.logger.warn(`could not find remote tracking entry for ${metadataKey}`);
209+
this.logger.warn(`found no matching revision for ${metadataKey}`);
205210
}
206211
});
212+
207213
await this.write();
208214
}
215+
209216
/**
210217
* Returns the `ChangeElement` currently being tracked given a metadata key,
211218
* or `undefined` if not found.
@@ -381,9 +388,9 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
381388
// to sync the retrieved SourceMembers; meaning it will update the lastRetrievedFromServer
382389
// field to the SourceMember's RevisionCounter, and update the serverMaxRevisionCounter
383390
// to the highest RevisionCounter.
384-
public async retrieveUpdates(sync = false): Promise<RemoteChangeElement[]> {
391+
public async retrieveUpdates({ sync = false, cache = true } = {}): Promise<RemoteChangeElement[]> {
385392
// Always track new SourceMember data, or update tracking when we sync.
386-
const queriedSourceMembers = await this.querySourceMembersFrom();
393+
const queriedSourceMembers = await this.querySourceMembersFrom({ useCache: cache });
387394
if (queriedSourceMembers.length || sync) {
388395
await this.trackSourceMembers(queriedSourceMembers, sync);
389396
}

src/shared/types.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/*
2+
* Copyright (c) 2020, salesforce.com, inc.
3+
* All rights reserved.
4+
* Licensed under the BSD 3-Clause license.
5+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
8+
import { FileResponse } from '@salesforce/source-deploy-retrieve';
9+
10+
export type RemoteSyncInput = Pick<FileResponse, 'fullName' | 'filePath' | 'type'>;

src/sourceTracking.ts

+19-9
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { ComponentSet, MetadataResolver, SourceComponent } from '@salesforce/sou
1010

1111
import { RemoteSourceTrackingService, RemoteChangeElement, getMetadataKey } from './shared/remoteSourceTrackingService';
1212
import { ShadowRepo } from './shared/localShadowRepo';
13+
import { RemoteSyncInput } from './shared/types';
1314

1415
export const getKeyFromObject = (element: RemoteChangeElement | ChangeResult): string => {
1516
if (element.type && element.name) {
@@ -21,11 +22,6 @@ export const getKeyFromObject = (element: RemoteChangeElement | ChangeResult): s
2122
// external users of SDR might need to convert a fileResponse to a key
2223
export const getKeyFromStrings = getMetadataKey;
2324

24-
export interface MetadataKeyPair {
25-
type: string;
26-
name: string;
27-
}
28-
2925
export interface ChangeOptions {
3026
origin?: 'local' | 'remote';
3127
state: 'add' | 'delete' | 'changed' | 'unchanged' | 'moved';
@@ -44,6 +40,11 @@ export type ChangeResult = Partial<RemoteChangeElement> & {
4440
filenames?: string[];
4541
};
4642

43+
export interface ConflictError {
44+
message: string;
45+
name: 'conflict';
46+
conflicts: ChangeResult[];
47+
}
4748
export class SourceTracking {
4849
private orgId: string;
4950
private projectPath: string;
@@ -63,6 +64,14 @@ export class SourceTracking {
6364
this.logger = Logger.childFromRoot('SourceTracking');
6465
}
6566

67+
public async deployLocalChanges({ overwrite = false, ignoreWarnings = false, wait = 33 }): Promise<void> {
68+
// TODO: this is basically the logic for a push
69+
}
70+
71+
public async retrieveRemoteChanges(): Promise<void> {
72+
// TODO: this is basically the logic for a pull
73+
}
74+
6675
/**
6776
* Get metadata changes made locally and in the org.
6877
*
@@ -125,11 +134,12 @@ export class SourceTracking {
125134
/**
126135
* Mark remote source tracking files that we have received to the latest version
127136
*/
128-
public async updateRemoteTracking(metadataKeys: MetadataKeyPair[]): Promise<void> {
137+
public async updateRemoteTracking(fileResponses: RemoteSyncInput[]): Promise<void> {
129138
await this.ensureRemoteTracking();
130-
await this.remoteSourceTrackingService.syncNamedElementsByKey(
131-
metadataKeys.map((input) => getKeyFromStrings(input.type, input.name))
132-
);
139+
// TODO: poll for source tracking to be complete
140+
// to make sure we have the updates before syncing the ones from metadataKeys
141+
await this.remoteSourceTrackingService.retrieveUpdates({ cache: false });
142+
await this.remoteSourceTrackingService.syncSpecifiedElements(fileResponses);
133143
}
134144

135145
/**

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"]
6+
"include": ["src/**/*.ts", "test/unit/metadataKeys.test.ts"]
77
}

0 commit comments

Comments
 (0)