Skip to main content

services-extension-consumption

Consume the salesforcedx-vscode-services extension API. Use when an extension depends on salesforcedx-vscode-services and you are registering commands, calling its services (Workspace, Connection, Project, Settings, FS, Channel, Media, prompts), watching files/config/target-org, or wiring the AllServicesLayer/runtime in extensionProvider.ts.

Aller à l'installation

Informations de source

Dépôt
forcedotcom/salesforcedx-vscode
Dernière activité de la source
16 septembre 2026 à 00:05
Langue détectée de SKILL.md
anglais
Étoiles
1 034
Forks
454

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Explorateur de fichiers
13 fichiers

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
services-extension-consumption
description
Consume the salesforcedx-vscode-services extension API. Use when an extension depends on salesforcedx-vscode-services and you are registering commands, calling its services (Workspace, Connection, Project, Settings, FS, Channel, Media, prompts), watching files/config/target-org, or wiring the AllServicesLayer/runtime in extensionProvider.ts.
review
always
# Consuming salesforcedx-vscode-services Extensions depending on `salesforcedx-vscode-services`. Examples: `salesforcedx-vscode-metadata`, `salesforcedx-vscode-org-browser`. ## Getting the API Use `ExtensionProviderService` from `@salesforce/effect-ext-utils`: ```typescript import { ExtensionProviderService, getServicesApi } from '@salesforce/effect-ext-utils'; const ExtensionProviderServiceLive = Layer.effect( ExtensionProviderService, Effect.sync(() => ({ getServicesApi })) ); // In an Effect.gen: const api = yield * (yield * ExtensionProviderService).getServicesApi; ``` ## Prebuilt vs Per-Extension Services `api.services.prebuiltServicesLayer` — shared service instances plus runtime configuration, including the redacting logger. Provide or merge this layer directly. `api.services.prebuiltServicesDependencies` — deprecated context-only compatibility field. It omits FiberRef runtime configuration; new consumers must use `prebuiltServicesLayer`. Shares singleton instances (caches, watchers) across extensions; avoids re-building stateful services. Per-extension layers (must build yourself): | Layer | Why | | --------------------------------------- | ---------------------------------------------------------- | | `ChannelServiceLayer(displayName)` | Own output channel | | `ErrorHandlerService.Default` | Depends on own ChannelService | | `ExtensionContextServiceLayer(context)` | Own `ExtensionContext` | | `SdkLayerFor(context)` | Own tracer (extension name/version in resource attributes) | | `ExtensionProviderServiceLive` | Local singleton | ## ExtensionContext Setup Preferred: import `buildAllServicesLayer` from `@salesforce/effect-ext-utils`. It reads `displayName` from `package.json`, falling back to the second arg. `services/extensionProvider.ts` only needs the mutable `AllServicesLayer` + setter: ```typescript // services/extensionProvider.ts import { buildAllServicesLayer } from '@salesforce/effect-ext-utils'; export let AllServicesLayer: ReturnType<typeof buildAllServicesLayer>; export const setAllServicesLayer = (layer: ReturnType<typeof buildAllServicesLayer>) => { AllServicesLayer = layer; }; ``` In `activate` — pass the context and a localized fallback channel name: ```typescript import { buildAllServicesLayer } from '@salesforce/effect-ext-utils'; import { nls } from './messages'; import { setAllServicesLayer } from './services/extensionProvider'; export const activate = async (context: vscode.ExtensionContext): Promise<void> => { setAllServicesLayer(buildAllServicesLayer(context, nls.localize('channel_name'))); await getRuntime().runPromise(activateEffect(context)); }; ``` Two patterns exist depending on whether the extension adds services beyond the shared base: - **Shared base only** (`core`, `apex`, `apex-testing`, `lightning`, `lwc`, `org`, `visualforce`): import `buildAllServicesLayer` directly from `@salesforce/effect-ext-utils` and pass it to `setAllServicesLayer` at activation. No local factory needed. - **Extension-specific services added** (`apex-debugger`, `apex-log`, `apex-oas`, `apex-replay-debugger`, `metadata`, `org-browser`, `soql`): define a local `buildAllServicesLayer` in `services/extensionProvider.ts` that calls `buildSharedServicesLayer` from `@salesforce/effect-ext-utils` and merges the extension's own Effect services via `Layer.mergeAll`. The extra services vary — `apex-oas` adds `ApexMetadataService` and `LLMService`; extensions with the notifications system add `NotificationModeService.Default`; `org-browser` adds `OrgBrowserRetrieveService`. ## Runtime vs provide - **Do**: Build `ManagedRuntime.make(AllServicesLayer)` and export `getRuntime()`. - **Do**: Export runtime disposal, clear the memo, and call it during extension deactivation. - **Do**: Use `getRuntime().runPromise(effect)` / `runFork(effect)` for ad-hoc execution. - **Don't**: Use `Effect.provide(AllServicesLayer)` at call sites — use the runtime instead. ```typescript export const disposeRuntime = async (): Promise<void> => { if (_runtime) { await _runtime.dispose(); _runtime = undefined; } }; export const deactivate = async (): Promise<void> => { await getRuntime().runPromise(deactivation()).finally(disposeRuntime); }; ``` ## Resource Lifecycle Prefer Effect scope ownership for resources created inside Effect services/layers: - Define resource-owning services with `scoped`. - Register VS Code `Disposable`s with `Effect.addFinalizer`. - Attach long-lived fibers to the owning scope with `Effect.forkIn`. - Dispose the owning `ManagedRuntime` on deactivation so layer finalizers run. - Don't expose `runDispose`/`dispose` solely for consumers to add to `context.subscriptions`. - Keep `context.subscriptions` for resources created outside an Effect scope. Allocation and cleanup stay together. See `../effect-best-practices/SKILL.md#effect-owned-resources`. ## Registering Commands Use `registerCommandWithRuntime`: ```typescript import { myCommandEffect } from './commands/myCommand'; const api = yield * (yield * ExtensionProviderService).getServicesApi; const registerCommand = api.services.registerCommandWithRuntime(getRuntime()); yield * registerCommand('sf.my.command', myCommandEffect); ``` Commands auto: - Register with ExtensionContext subscriptions - Wrap with error handling - Trace with observability spans - Handle Cancellation ### Activation ordering `activate()` awaits `getRuntime().runPromise(activateEffect(context))`; it does not detach the main activation Effect. Only work explicitly started with `Effect.fork*` continues after activation completes. Register all manifest-contributed UI before awaiting work that can be slow or unresolved: 1. Register tree/webview providers and put any returned `Disposable` in `context.subscriptions` when it is not scope-owned. 2. Restore the extension's persisted UI state and set its context keys. 3. Register every contributed command. 4. Set an extension-owned readiness context key only after steps 1-3 succeed, and use it to gate title/menu commands that would otherwise be visible. 5. Only then await connection resolution, target-org readiness, catalog hydration, or network work. Use `Effect.forkIn` for long-lived watchers that do not need to block activation. `when` clauses can expose a contributed command before its handler has registered. A context key owned by another extension, including `sf:has_target_org`, is a visibility hint, not proof that this extension has initialized. Do not make a contributed handler's registration depend on it. Keep target-org and authorization checks in the command implementation or shared service layer. ```typescript export const activateEffect = Effect.fn(`activation:${EXTENSION_NAME}`)(function* (context: vscode.ExtensionContext) { const api = yield* (yield* ExtensionProviderService).getServicesApi; const provider = new MyTreeProvider(); context.subscriptions.push(vscode.window.registerTreeDataProvider(VIEW_ID, provider)); yield* setInitialContext(); const registerCommand = api.services.registerCommandWithRuntime(getRuntime()); yield* registerCommand('sf.my.command', () => myCommand(provider)); yield* Effect.promise(() => vscode.commands.executeCommand('setContext', 'sf:myExtension.ready', true)); // Command registration must not wait for org-backed initialization. yield* api.services.ConnectionService.getConnection(); }); ``` ### Success handling `Effect.fn` accepts middleware args after the generator. Put success-side middleware **before** `catchTag`/`catchAll` — otherwise caught errors become successes. ```typescript export const deployActiveEditorCommand = Effect.fn('deploySourcePath.deployActiveEditor')( function* () { // ...core logic... }, // runs only on success — placed before catchTag withConfigurableSuccessNotification(nls.localize('command_succeeded_text', label)), // catches errors — placed after success middleware Effect.catchTag('NoActiveEditorError', () => Effect.promise(() => vscode.window.showErrorMessage(nls.localize('deploy_select_file_or_directory'))).pipe( Effect.as(undefined) ) ) ); ``` `withConfigurableSuccessNotification` wraps the effect with `Effect.tap`, so it only fires when the effect succeeds: ```typescript export const withConfigurableSuccessNotification = (message: string) => <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.tap(effect, () => Effect.sync(() => { const show = vscode.workspace.getConfiguration(SECTION).get<boolean>(KEY, false); if (show) void vscode.window.showInformationMessage(message); }) ); ``` ## Invoking `sf.org.login.web` Cross-extension / `executeCommand`: `vscode.commands.executeCommand('sf.org.login.web', instanceUrl?, reauthAliasOrUsername?)`. - No args: interactive flow (palette). - With `instanceUrl`: skips org-type quick pick. - Second arg applies only when `instanceUrl` was provided: trimmed non-empty string becomes the auth alias (access-token re-auth); else alias defaults to `reauth-vscodeOrg`. ## Basic Services Accessor pattern: call methods directly, don't assign to variable first. - [ChannelService](references/channel-service.md) - Output channel - [ComponentSetService](references/component-set-service.md) - Build component sets (source, manifest, URIs) - [MediaService](references/media-service.md) - Icons (ICONS) and NLS descriptions - [WorkspaceService](references/workspace-service.md) - Workspace info - [ConnectionService](references/connection-service.md) - Org connections - [ProjectService](references/project-service.md) - Project resolution, packageDirectories - [SettingsService](references/settings-service.md) - Settings read/write - [FsService](references/fs-service.md) - File ops (web-compatible), uri/path conversion, `HashableUri` (value-based URI equality for HashSet/HashMap keys) - [EditorService](references/editor-service.md) - Active editor changes and current URI - [Prompts](references/prompts.md) - QuickPick, InputBox, and UserCancellationError handling - [TerminalService](references/terminal-service.md) - Run argv commands (desktop-only) - [NotificationModeService](references/notification-mode-api.md) - Configurable success notifications ## Watchers ### File Watching `FileChangePubSub` — workspace FS (`**/*`), including project `.sf/config.json`. Filter `event.uri` / `uri.path` / `Utils.*`, not `uri.fsPath`. Global `~/.sf/config.json` and `~/.sfdx/alias.json`: `HostFileWatcher` (internal, `@salesforce/core/fs`). Not on the public API; services already watch them. See [FileChangePubSub vs HostFileWatcher](../../../packages/salesforcedx-vscode-services/CONTEXT.md#filechangepubsub-vs-hostfilewatcher). ```typescript import * as Stream from 'effect/Stream'; const pubsub = yield* api.services.FileChangePubSub; yield* Stream.fromPubSub(pubsub).pipe( Stream.filter(event => /* event.uri / uri.path / Utils.*; not uri.fsPath */), Stream.runForEach(event => Effect.sync(() => { // { type: 'create'|'change'|'delete', uri } }) ) ); ``` ### Config Watching Watch VS Code config changes: ```typescript import * as PubSub from 'effect/PubSub'; import * as Stream from 'effect/Stream'; import * as Duration from 'effect/Duration'; const pubsub = yield * PubSub.sliding<vscode.ConfigurationChangeEvent>(100); const disposable = vscode.workspace.onDidChangeConfiguration(event => { Effect.runSync(PubSub.publish(pubsub, event)); }); yield * Effect.addFinalizer(() => Effect.sync(() => { disposable?.dispose(); }) ); yield * Stream.fromPubSub(pubsub).pipe( Stream.filter(event => event.affectsConfiguration('section.setting')), Stream.debounce(Duration.millis(100)), Stream.runForEach(() => { // Handle config change }) ); ``` ### Target Org Changes Watch org changes via `TargetOrgRef` (SubscriptionRef): ```typescript const ref = yield * api.services.TargetOrgRef(); yield * ref.changes.pipe( Stream.map(org => org.orgId), Stream.changes, Stream.tap(orgId => { // Handle org change }), Stream.runForEach(() => { // Refresh UI, invalidate caches, etc. }) ); ``` `TargetOrgRef` is a `SubscriptionRef`: `ref.changes` already emits the current value first, so never prepend an explicit get. See the SubscriptionRef section of `../effect-best-practices/SKILL.md` for the mechanic (incl. `Stream.drop(1)` to skip the initial snapshot). Ref behavior (concise): - Default-org update: username from User SOQL when present; else AuthInfo login username on the connection. - `TargetOrgRef` snapshot without username: optional `ConfigUtil.getUsername()` (project default) before treating as no target org. - `TargetOrgRef` (`DefaultOrgInfoSchema`) value is always an object (never `undefined`); `orgId`/`devHubOrgId` are optional branded `OrgId` (`Schema.optional(OrgId)`, like `cliId`). ### Clearing the Default Org Call `ClearDefaultOrgRef()` to reset the in-process org ref (e.g., after deleting the default org): ```typescript yield* api.services.ClearDefaultOrgRef(); ``` Clears the reactive ref without rewriting config. Use when the CLI already mutated config but the in-process ref must reset to notify observers (e.g., the source tracking status bar icons). See `orgDeleteDefaultCommand` for an example.
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub