Skip to main content

services-extension-consumption

Guidelines for consuming salesforcedx-vscode-services extension API. Use when working with extensions that have extensionDependency on salesforcedx-vscode-services, registering commands, using Workspace/Connection/Project/Settings/FS/Channel/Media services, quickpick/quickInput, implementing file/config watchers, editing extensionProvider.ts, buildAllServicesLayer, AllServicesLayer, setAllServicesLayer, prebuiltServicesDependencies, or Layer composition for VS Code extensions.

インストールへ移動

ソース情報

リポジトリ
forcedotcom/apex-language-support
ソースの最終更新活動
2026年6月23日 22:16
検出された SKILL.md の言語
英語
スター
11
フォーク
3

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

ファイルエクスプローラー
12 ファイル

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
services-extension-consumption
description
Guidelines for consuming salesforcedx-vscode-services extension API. Use when working with extensions that have extensionDependency on salesforcedx-vscode-services, registering commands, using Workspace/Connection/Project/Settings/FS/Channel/Media services, quickpick/quickInput, implementing file/config watchers, editing extensionProvider.ts, buildAllServicesLayer, AllServicesLayer, setAllServicesLayer, prebuiltServicesDependencies, or Layer composition for VS Code extensions.
# 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.prebuiltServicesDependencies` — pre-built `Context.Context` from services extension activation. Wrap with `Layer.succeedContext(...)`. 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)); }; ``` Legacy inline pattern: a local `buildAllServicesLayer` factory wraps `Layer.unwrapEffect(...)` in `services/extensionProvider.ts`. Migrate to the shared helper when touching these — drop the factory, import `buildAllServicesLayer` from `@salesforce/effect-ext-utils`, pass the fallback name at the call site: ```typescript export const buildAllServicesLayer = (context: ExtensionContext) => Layer.unwrapEffect( Effect.gen(function* () { const extensionProvider = yield* ExtensionProviderService; const api = yield* extensionProvider.getServicesApi; const channelLayer = api.services.ChannelServiceLayer( context.extension.packageJSON.displayName ?? 'My Extension' ); const errorHandlerWithChannel = Layer.provide(api.services.ErrorHandlerService.Default, channelLayer); return Layer.mergeAll( Layer.succeedContext(api.services.prebuiltServicesDependencies), ExtensionProviderServiceLive, errorHandlerWithChannel, api.services.ExtensionContextServiceLayer(context), api.services.SdkLayerFor(context), channelLayer ); }).pipe(Effect.provide(ExtensionProviderServiceLive)) ); ``` ## Runtime vs provide - **Do**: Build `ManagedRuntime.make(AllServicesLayer)` and export `getRuntime()`. - **Do**: Use `getRuntime().runPromise(effect)` / `runFork(effect)` for ad-hoc execution. - **Don't**: Use `Effect.provide(AllServicesLayer)` at call sites — use the runtime instead. - **Exception**: `registerCommandWithLayer(AllServicesLayer)` — keep passing the Layer; it internally uses provide. ## Registering Commands Use `registerCommandWithLayer` (for layers) or `registerCommandWithRuntime` (for runtimes): ```typescript import { myCommandEffect } from './commands/myCommand'; const api = yield * (yield * ExtensionProviderService).getServicesApi; // Using Layer const registerCommand = api.services.registerCommandWithLayer(AllServicesLayer); yield * registerCommand('sf.my.command', myCommandEffect); // Using Runtime 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 ### 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) and uri/path conversion - [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 shell commands (desktop-only) ## Watchers ### File Watching FileWatcherService exposes a PubSub of all workspace file changes (`**/*`). Subscribe and filter: ```typescript import * as PubSub from 'effect/PubSub'; import * as Stream from 'effect/Stream'; const fileWatcher = yield * api.services.FileWatcherService; yield* Stream.fromPubSub(fileWatcher.pubsub).pipe( Stream.filter(event => /* match event.uri to your pattern */), Stream.runForEach(event => Effect.sync(() => { // Handle event: { 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. }) ); ``` **`ref.changes` always emits the current value as element 0**, then future changes. Never prepend an explicit get: ```typescript // WRONG — the fromEffect/get is redundant; .changes already emits current value first Stream.concat(Stream.fromEffect(SubscriptionRef.get(ref)), ref.changes) Stream.concat(Stream.make(yield* SubscriptionRef.get(ref)), ref.changes) // CORRECT ref.changes ``` To suppress the initial snapshot (e.g. avoid triggering a refresh before a tree provider is ready), use `Stream.drop(1)`. 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 — see `salesforcedx-vscode-org` `orgDisplay`. - `TargetOrgRef` value is always an object (never `undefined`); only fields like `orgId` within it are optional. ## Complete Example Pattern ```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; }; // services/runtime.ts import * as ManagedRuntime from 'effect/ManagedRuntime'; import { AllServicesLayer } from './extensionProvider'; const createRuntime = () => ManagedRuntime.make(AllServicesLayer); let _runtime: ReturnType<typeof createRuntime> | undefined; export const getRuntime = () => (_runtime ??= createRuntime()); // index.ts import { buildAllServicesLayer } from '@salesforce/effect-ext-utils'; import { nls } from './messages'; import { myCommandEffect } from './commands/myCommand'; import { AllServicesLayer, setAllServicesLayer } from './services/extensionProvider'; import { getRuntime } from './services/runtime'; export const activate = async (context: vscode.ExtensionContext) => { setAllServicesLayer(buildAllServicesLayer(context, nls.localize('channel_name'))); await getRuntime().runPromise(activateEffect(context)); }; export const activateEffect = Effect.fn(`activation:${EXTENSION_NAME}`)(function* (_context: vscode.ExtensionContext) { const api = yield* (yield* ExtensionProviderService).getServicesApi; yield* api.services.ChannelService.appendToChannel('Extension activating'); const registerCommand = api.services.registerCommandWithLayer(AllServicesLayer); yield* registerCommand('sf.my.command', myCommandEffect); yield* api.services.ChannelService.appendToChannel('Extension activation complete.'); }); ``` ## Common Patterns - Start with `Layer.succeedContext(api.services.prebuiltServicesDependencies)` — don't add individual `*.Default` for services already there - Only add per-extension layers on top - `import { ICONS }` outside Effect; `MediaService` inside Effect - `ChannelServiceLayer` before `ErrorHandlerService` - Pass `context` to `SdkLayerFor` (extracts name/version from ExtensionContext) - `Effect.forkIn(..., yield* getExtensionScope())` for watcher cleanup on deactivation - `registerCommandWithLayer` for all commands (tracing + error handling) - Use `getRuntime().runPromise` / `runFork` instead of `Effect.provide(AllServicesLayer)` for execution ## Don't: rebuild services already in prebuiltServicesDependencies ```typescript // WRONG — creates new singleton instances, duplicating caches/watchers/state return Layer.mergeAll( ExtensionProviderServiceLive, api.services.ExtensionContextServiceLayer(context), api.services.FsService.Default, // ← already in prebuilt api.services.AliasService.Default, // ← already in prebuilt api.services.SdkLayerFor(context), channelLayer, errorHandlerWithChannel ); // CORRECT — share the already-built singletons return Layer.mergeAll( Layer.succeedContext(api.services.prebuiltServicesDependencies),
GitHubで見る
この SKILL.md は非常に大きいため、SkillsMP では最初のセクションだけを表示しています。 GitHubで見る