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
آخر نشاط في المصدر
٢٣ يونيو ٢٠٢٦ في ٢٢:١٦
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
١١
التفرعات
٣

خيارات التثبيت

يُحدَّد 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