Skip to content

Commit f936bf8

Browse files
authored
feat: retrieve commands W-20175957 (#6746)
* feat: retrieve commands * feat: tracking reset W-20243032 (#6749)
1 parent c0e0740 commit f936bf8

34 files changed

Lines changed: 685 additions & 164 deletions

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"@commitlint/cli": "^19.8.1",
2525
"@commitlint/config-conventional": "^19.8.1",
2626
"@effect/eslint-plugin": "0.3.2",
27-
"@effect/language-service": "0.62.4",
27+
"@effect/language-service": "0.64.1",
2828
"@eslint/json": "^0.3.0",
2929
"@monorepo-utils/workspaces-to-typescript-project-references": "2.11.0",
3030
"@octokit/core": "^5",

packages/salesforcedx-vscode-metadata/package.json

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@
167167
]
168168
},
169169
"run:web": {
170-
"command": "npx vscode-test-web --browserType=chromium --browserOption=--disable-web-security --browserOption=--remote-debugging-port=9222 --extensionDevelopmentPath . --extensionPath ../salesforcedx-vscode-services --extensionPath ../salesforcedx-vscode-org-browser --open-devtools --port 3001",
170+
"command": "npx vscode-test-web --browserType=chromium --browserOption=--disable-web-security --browserOption=--remote-debugging-port=9222 --extensionDevelopmentPath . --extensionPath ../salesforcedx-vscode-services --extensionPath ../salesforcedx-vscode-org-browser --open-devtools --port 3001 --quality stable --verbose --printServerLog",
171171
"service": true,
172172
"dependencies": [
173173
"vscode:bundle",
@@ -240,6 +240,10 @@
240240
"command": "sf.view.remote.changes",
241241
"when": "salesforcedx-vscode-metadata.showSharedCommands"
242242
},
243+
{
244+
"command": "sf.source.tracking.reset.remote",
245+
"when": "salesforcedx-vscode-metadata.showSharedCommands && sf:target_org_has_change_tracking"
246+
},
243247
{
244248
"command": "sf.apex.generate.class",
245249
"when": "salesforcedx-vscode-metadata.showSharedCommands"
@@ -255,6 +259,22 @@
255259
{
256260
"command": "sf.deploy.in.manifest",
257261
"when": "salesforcedx-vscode-metadata.showSharedCommands && sf:has_target_org && resourceLangId == 'forcesourcemanifest'"
262+
},
263+
{
264+
"command": "sf.project.retrieve.start.ignore.conflicts",
265+
"when": "salesforcedx-vscode-metadata.showSharedCommands"
266+
},
267+
{
268+
"command": "sf.retrieve.source.path",
269+
"when": "never"
270+
},
271+
{
272+
"command": "sf.retrieve.current.source.file",
273+
"when": "salesforcedx-vscode-metadata.showSharedCommands && sf:has_target_org && resourceLangId != 'forcesourcemanifest'"
274+
},
275+
{
276+
"command": "sf.retrieve.in.manifest",
277+
"when": "salesforcedx-vscode-metadata.showSharedCommands && sf:has_target_org && resourceLangId == 'forcesourcemanifest'"
258278
}
259279
],
260280
"editor/context": [
@@ -265,6 +285,14 @@
265285
{
266286
"command": "sf.deploy.in.manifest",
267287
"when": "salesforcedx-vscode-metadata.showSharedCommands && sf:has_target_org && resourceLangId == 'forcesourcemanifest'"
288+
},
289+
{
290+
"command": "sf.retrieve.source.path",
291+
"when": "salesforcedx-vscode-metadata.showSharedCommands && sf:has_target_org && resourceLangId != 'forcesourcemanifest'"
292+
},
293+
{
294+
"command": "sf.retrieve.in.manifest",
295+
"when": "salesforcedx-vscode-metadata.showSharedCommands && sf:has_target_org && resourceLangId == 'forcesourcemanifest'"
268296
}
269297
],
270298
"explorer/context": [
@@ -275,6 +303,14 @@
275303
{
276304
"command": "sf.deploy.in.manifest",
277305
"when": "salesforcedx-vscode-metadata.showSharedCommands && sf:has_target_org && resourceLangId == 'forcesourcemanifest'"
306+
},
307+
{
308+
"command": "sf.retrieve.source.path",
309+
"when": "salesforcedx-vscode-metadata.showSharedCommands && sf:has_target_org && resourceLangId != 'forcesourcemanifest'"
310+
},
311+
{
312+
"command": "sf.retrieve.in.manifest",
313+
"when": "salesforcedx-vscode-metadata.showSharedCommands && sf:has_target_org && resourceLangId == 'forcesourcemanifest'"
278314
}
279315
]
280316
},
@@ -303,6 +339,10 @@
303339
"command": "sf.view.remote.changes",
304340
"title": "%view_remote_changes_text%"
305341
},
342+
{
343+
"command": "sf.source.tracking.reset.remote",
344+
"title": "%reset_remote_tracking_text%"
345+
},
306346
{
307347
"command": "sf.apex.generate.class",
308348
"title": "%apex_generate_class_text%"
@@ -318,6 +358,22 @@
318358
{
319359
"command": "sf.deploy.active.editor",
320360
"title": "%deploy_this_source_text%"
361+
},
362+
{
363+
"command": "sf.project.retrieve.start.ignore.conflicts",
364+
"title": "%project_retrieve_start_ignore_conflicts_default_org_text%"
365+
},
366+
{
367+
"command": "sf.retrieve.source.path",
368+
"title": "%retrieve_this_source_text%"
369+
},
370+
{
371+
"command": "sf.retrieve.current.source.file",
372+
"title": "%retrieve_this_source_text%"
373+
},
374+
{
375+
"command": "sf.retrieve.in.manifest",
376+
"title": "%retrieve_in_manifest_text%"
321377
}
322378
]
323379
}

packages/salesforcedx-vscode-metadata/package.nls.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,13 @@
66
"view_all_changes_text": "SFDX: View All Changes (Local and in Default Org)",
77
"view_local_changes_text": "SFDX: View Local Changes",
88
"view_remote_changes_text": "SFDX: View Changes in Default Org",
9+
"reset_remote_tracking_text": "SFDX: Reset Remote Tracking",
910
"apex_generate_class_text": "SFDX: Create Apex Class",
1011
"deploy_this_source_text": "SFDX: Deploy This Source to Org",
1112
"deploy_in_manifest_text": "SFDX: Deploy Source in Manifest to Org",
13+
"project_retrieve_start_ignore_conflicts_default_org_text": "SFDX: Pull Source from Default Org and Ignore Conflicts",
14+
"retrieve_this_source_text": "SFDX: Retrieve This Source from Org",
15+
"retrieve_in_manifest_text": "SFDX: Retrieve Source in Manifest from Org",
1216
"source_tracking_status_bar_local_changes": "Local Changes",
1317
"source_tracking_status_bar_remote_changes": "Remote Changes",
1418
"source_tracking_status_bar_conflicts": "Conflicts",

packages/salesforcedx-vscode-metadata/src/commands/deployManifest.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ const deployManifestEffect = (manifestUri?: URI) =>
2525
// For file:// URIs, path and fsPath are equivalent
2626
const manifestPath = process.env.ESBUILD_PLATFORM === 'web' ? resolved.path : resolved.fsPath;
2727

28-
const deployService = yield* api.services.MetadataDeployService;
29-
const componentSet = yield* deployService.ensureNonEmptyComponentSet(
30-
yield* deployService.getComponentSetFromManifest(manifestPath)
28+
const componentSetService = yield* api.services.ComponentSetService;
29+
const componentSet = yield* componentSetService.ensureNonEmptyComponentSet(
30+
yield* componentSetService.getComponentSetFromManifest(manifestPath)
3131
);
3232

3333
yield* deployComponentSet({ componentSet });

packages/salesforcedx-vscode-metadata/src/commands/deploySourcePath.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ import { deployComponentSet } from '../shared/deploy/deployComponentSet';
1515
const deployPaths = (paths: Set<string>) =>
1616
Effect.gen(function* () {
1717
const api = yield* (yield* ExtensionProviderService).getServicesApi;
18-
const deployService = yield* api.services.MetadataDeployService;
19-
const rawCS = yield* deployService.getComponentSetFromPaths(paths);
20-
const componentSet = yield* deployService.ensureNonEmptyComponentSet(rawCS);
18+
const componentSetService = yield* api.services.ComponentSetService;
19+
const componentSet = yield* componentSetService.ensureNonEmptyComponentSet(
20+
yield* componentSetService.getComponentSetFromPaths(paths)
21+
);
2122
yield* deployComponentSet({ componentSet });
2223
});
2324

packages/salesforcedx-vscode-metadata/src/commands/projectDeployStart.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,13 @@ const projectDeployStartEffect = (ignoreConflicts: boolean) =>
1818
yield* Effect.annotateCurrentSpan({ ignoreConflicts });
1919

2020
const api = yield* (yield* ExtensionProviderService).getServicesApi;
21-
const deployService = yield* api.services.MetadataDeployService;
22-
const componentSetUnvalidated = yield* deployService.getComponentSetForDeploy({ ignoreConflicts });
23-
const componentSet = yield* deployService.ensureNonEmptyComponentSet(componentSetUnvalidated);
21+
const [deployService, componentSetService] = yield* Effect.all(
22+
[api.services.MetadataDeployService, api.services.ComponentSetService],
23+
{ concurrency: 'unbounded' }
24+
);
25+
const componentSet = yield* componentSetService.ensureNonEmptyComponentSet(
26+
yield* deployService.getComponentSetForDeploy({ ignoreConflicts })
27+
);
2428

2529
yield* deployComponentSet({ componentSet });
2630
}).pipe(Effect.withSpan('projectDeployStart', { attributes: { ignoreConflicts } }), Effect.provide(AllServicesLayer));
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright (c) 2025, 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 * as Data from 'effect/Data';
9+
import * as Effect from 'effect/Effect';
10+
import * as vscode from 'vscode';
11+
import { AllServicesLayer, ExtensionProviderService } from '../services/extensionProvider';
12+
13+
class ResetRemoteTrackingError extends Data.TaggedError('ResetRemoteTrackingError')<{
14+
readonly cause: Error;
15+
}> {}
16+
17+
const resetRemoteTrackingEffect = Effect.fn('resetRemoteTracking')(function* () {
18+
const api = yield* (yield* ExtensionProviderService).getServicesApi;
19+
const [channelService, sourceTrackingService] = yield* Effect.all(
20+
[api.services.ChannelService, api.services.SourceTrackingService],
21+
{ concurrency: 'unbounded' }
22+
);
23+
24+
const tracking = yield* sourceTrackingService.getSourceTrackingOrThrow();
25+
26+
yield* channelService.appendToChannel('Resetting remote tracking...');
27+
28+
const resetCount = yield* Effect.tryPromise({
29+
try: () => tracking.resetRemoteTracking(),
30+
catch: error =>
31+
new ResetRemoteTrackingError({
32+
cause: error instanceof Error ? error : new Error(String(error))
33+
})
34+
}).pipe(Effect.withSpan('resetRemoteTracking'));
35+
36+
yield* channelService.appendToChannel(
37+
`Successfully reset remote tracking. ${resetCount} file${resetCount === 1 ? '' : 's'} updated.`
38+
);
39+
});
40+
41+
/** Reset remote tracking so remote changes go to zero and only changes after this point are tracked */
42+
export const resetRemoteTracking = async (): Promise<void> =>
43+
resetRemoteTrackingEffect().pipe(
44+
Effect.catchAll(error =>
45+
Effect.gen(function* () {
46+
const api = yield* (yield* ExtensionProviderService).getServicesApi;
47+
const channelService = yield* api.services.ChannelService;
48+
const errorMessage =
49+
error instanceof ResetRemoteTrackingError
50+
? error.cause.message
51+
: error instanceof Error
52+
? error.message
53+
: String(error);
54+
yield* channelService.appendToChannel(`Reset remote tracking failed: ${errorMessage}`);
55+
yield* Effect.promise(() => vscode.window.showErrorMessage(errorMessage));
56+
}).pipe(Effect.as(undefined))
57+
),
58+
Effect.provide(AllServicesLayer),
59+
Effect.runPromise
60+
);
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright (c) 2025, 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 * as Effect from 'effect/Effect';
9+
import * as vscode from 'vscode';
10+
import { URI } from 'vscode-uri';
11+
import { nls } from '../messages';
12+
import { AllServicesLayer, ExtensionProviderService } from '../services/extensionProvider';
13+
import { retrieveComponentSet } from '../shared/retrieve/retrieveComponentSet';
14+
15+
const retrieveManifestEffect = (manifestUri?: URI) =>
16+
Effect.gen(function* () {
17+
yield* Effect.annotateCurrentSpan({ manifestUri });
18+
const api = yield* (yield* ExtensionProviderService).getServicesApi;
19+
const resolved =
20+
manifestUri ??
21+
(yield* (yield* api.services.EditorService).getActiveEditorUri.pipe(
22+
Effect.catchTag('NoActiveEditorError', () => Effect.fail(new Error(nls.localize('retrieve_select_manifest'))))
23+
));
24+
// Use path instead of fsPath for memfs URIs (web environments) to avoid backslash conversion issues
25+
// For file:// URIs, path and fsPath are equivalent
26+
const manifestPath = process.env.ESBUILD_PLATFORM === 'web' ? resolved.path : resolved.fsPath;
27+
28+
const componentSetService = yield* api.services.ComponentSetService;
29+
const componentSet = yield* componentSetService.ensureNonEmptyComponentSet(
30+
yield* componentSetService.getComponentSetFromManifest(manifestPath)
31+
);
32+
33+
yield* retrieveComponentSet({ componentSet });
34+
}).pipe(Effect.withSpan('retrieveManifest', { attributes: { manifestUri } }), Effect.provide(AllServicesLayer));
35+
36+
/** Retrieve manifest from the default org */
37+
export const retrieveManifest = async (manifestUri?: URI): Promise<void> =>
38+
Effect.runPromise(
39+
retrieveManifestEffect(manifestUri).pipe(
40+
// handle all other errors generically
41+
Effect.catchAll(error =>
42+
Effect.promise(() => vscode.window.showErrorMessage(nls.localize('retrieve_failed', error.message)))
43+
),
44+
Effect.as(undefined),
45+
Effect.provide(AllServicesLayer)
46+
)
47+
);
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright (c) 2025, 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 * as Effect from 'effect/Effect';
9+
import * as vscode from 'vscode';
10+
import { URI } from 'vscode-uri';
11+
import { nls } from '../messages';
12+
import { AllServicesLayer, ExtensionProviderService } from '../services/extensionProvider';
13+
import { retrieveComponentSet } from '../shared/retrieve/retrieveComponentSet';
14+
15+
const retrievePaths = (paths: Set<string>) =>
16+
Effect.gen(function* () {
17+
const api = yield* (yield* ExtensionProviderService).getServicesApi;
18+
const componentSetService = yield* api.services.ComponentSetService;
19+
const componentSet = yield* componentSetService.ensureNonEmptyComponentSet(
20+
yield* componentSetService.getComponentSetFromPaths(paths)
21+
);
22+
yield* retrieveComponentSet({ componentSet });
23+
});
24+
25+
/** Retrieve source paths from the default org */
26+
const retrieveSourcePathsEffect = Effect.fn('retrieveSourcePaths')(function* (sourceUri: URI, uris: URI[]) {
27+
yield* Effect.annotateCurrentSpan({ sourceUri, uris });
28+
const paths = new Set([sourceUri.path, ...uris.map(uri => uri.path)]);
29+
return yield* retrievePaths(paths);
30+
});
31+
32+
/** Retrieve source paths from the default org */
33+
34+
// When a single file is selected and "Retrieve Source from Org" is executed,
35+
// sourceUri is passed, and the uris array contains a single element, the same
36+
// path as sourceUri.
37+
//
38+
// When multiple files are selected and "Retrieve Source from Org" is executed,
39+
// sourceUri is passed, and is the path to the first selected file, and the uris
40+
// array contains an array of all paths that were selected.
41+
//
42+
// When editing a file and "Retrieve This Source from Org" is executed,
43+
// sourceUri is passed, but uris is undefined.
44+
45+
export const retrieveSourcePaths = async (sourceUri: URI | undefined, uris: URI[] | undefined): Promise<void> => {
46+
const resolvedSourceUri =
47+
sourceUri ??
48+
(await Effect.runPromise(
49+
Effect.gen(function* () {
50+
const api = yield* (yield* ExtensionProviderService).getServicesApi;
51+
return yield* (yield* api.services.EditorService).getActiveEditorUri;
52+
})
53+
.pipe(Effect.provide(AllServicesLayer))
54+
.pipe(
55+
Effect.catchTag('NoActiveEditorError', () =>
56+
Effect.promise(() =>
57+
vscode.window.showErrorMessage(nls.localize('retrieve_select_file_or_directory'))
58+
).pipe(Effect.as(undefined))
59+
)
60+
)
61+
));
62+
63+
if (!resolvedSourceUri) {
64+
return;
65+
}
66+
67+
const resolvedUris = uris?.length ? uris : [resolvedSourceUri];
68+
await Effect.runPromise(
69+
retrieveSourcePathsEffect(resolvedSourceUri, resolvedUris).pipe(
70+
Effect.catchAll(error =>
71+
Effect.gen(function* () {
72+
const api = yield* (yield* ExtensionProviderService).getServicesApi;
73+
const channelService = yield* api.services.ChannelService;
74+
const errorMessage = error instanceof Error ? error.message : String(error);
75+
yield* channelService.appendToChannel(`Retrieve failed: ${errorMessage}`);
76+
yield* Effect.promise(() => vscode.window.showErrorMessage(errorMessage));
77+
}).pipe(Effect.provide(AllServicesLayer), Effect.as(undefined))
78+
),
79+
Effect.provide(AllServicesLayer)
80+
)
81+
);
82+
};

0 commit comments

Comments
 (0)