Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
543a726
feat: more apex templates, move to metadata ext
mshanemc Mar 19, 2026
f0a6fac
ci: require nls for quickpick valdiation and descriptions
mshanemc Mar 19, 2026
73a9f8a
refactor: less dead code
mshanemc Mar 19, 2026
2c64ee2
chore: dead code removal
mshanemc Mar 19, 2026
b61c4db
fix: don't show lwc command without a project
mshanemc Mar 19, 2026
650129c
test: wait for apex test to finish via notification
mshanemc Mar 19, 2026
a04ea76
test: clear channel between single/all
mshanemc Mar 19, 2026
3e5ca17
Merge remote-tracking branch 'origin/develop' into sm/unify-create-ap…
mshanemc Mar 19, 2026
218b069
Merge branch 'sm/apex-test-e2e-stability' into sm/unify-create-apex-c…
mshanemc Mar 19, 2026
8e491dc
Merge remote-tracking branch 'origin/develop' into sm/unify-create-ap…
mshanemc Mar 19, 2026
37b414b
test: make sure apex class create is hidden when not project
mshanemc Mar 19, 2026
73ddf76
test: don't refer to removed code
mshanemc Mar 19, 2026
08343d8
refactor: move trigger to metadata, runs on web
mshanemc Mar 19, 2026
8f1acf3
Merge remote-tracking branch 'origin/sm/unify-create-apex-class' into…
mshanemc Mar 19, 2026
82821ee
refactor: no placeholder on create commands
mshanemc Mar 20, 2026
c4f6a5d
refactor: share code for templates commands
mshanemc Mar 20, 2026
8598dbe
Merge remote-tracking branch 'origin/sm/unify-create-apex-class' into…
mshanemc Mar 20, 2026
d68464f
Merge remote-tracking branch 'origin/develop' into sm/unify-create-ap…
mshanemc Mar 20, 2026
8f585c8
Merge remote-tracking branch 'origin/sm/unify-create-apex-class' into…
mshanemc Mar 20, 2026
a333363
chore: casing
daphne-sfdc Mar 20, 2026
07207ae
chore: casing fix and lockfile
mshanemc Mar 20, 2026
e65f559
docs: how to fix automatically next time
mshanemc Mar 20, 2026
e213381
docs: more skills linking
mshanemc Mar 20, 2026
8c48640
test: soql e2e make sure command is ready first
mshanemc Mar 20, 2026
f25ad37
test: soql activation pause
mshanemc Mar 20, 2026
706e0ee
Merge remote-tracking branch 'origin/sm/unify-create-apex-class' into…
mshanemc Mar 20, 2026
af659c0
refactor: shared overwrite check and prompt-cancellation pattern
mshanemc Mar 20, 2026
f4c072a
Merge remote-tracking branch 'origin/sm/unify-create-apex-class' into…
mshanemc Mar 21, 2026
472dede
ci: verify hooks don't run when AI doesn't make edits
mshanemc Mar 21, 2026
d51ad2e
refactor: deduplication and renaming
mshanemc Mar 21, 2026
db18545
refactor: no vscode-utils
mshanemc Mar 21, 2026
a8a59e1
Merge remote-tracking branch 'origin/develop' into sm/unify-create-ap…
mshanemc Mar 21, 2026
93ea8ba
Merge remote-tracking branch 'origin/sm/unify-create-apex-class' into…
mshanemc Mar 21, 2026
1d74e11
refactor: open from vscode promise
mshanemc Mar 21, 2026
a8248c1
docs: rename and skill stuff
mshanemc Mar 21, 2026
54b3538
chore: model cleanup, shared dupe-check
mshanemc Mar 23, 2026
da93a40
Merge remote-tracking branch 'origin/sm/unify-create-apex-class' into…
mshanemc Mar 23, 2026
33b0070
refactor: apply patterns to apex script creations
mshanemc Mar 23, 2026
8e84d96
chore: extra label
mshanemc Mar 23, 2026
755e713
Merge remote-tracking branch 'origin/sm/unify-create-apex-class' into…
mshanemc Mar 23, 2026
c9aa4ea
refactor: choose output dir for apex scripts
mshanemc Mar 23, 2026
cf49de8
test: e2e adjustments for "chose path" on apex script
mshanemc Mar 23, 2026
8470167
refactor: dedupe the "choose another folder" option
mshanemc Mar 23, 2026
a4ff4e2
refactor: promptService naming
mshanemc Mar 23, 2026
9aac679
Merge remote-tracking branch 'origin/develop' into sm/unify-create-ap…
mshanemc Mar 23, 2026
99ad78e
Merge remote-tracking branch 'origin/sm/unify-create-apex-class' into…
mshanemc Mar 23, 2026
5bbfef8
refactor: apex test gen with shared services
mshanemc Mar 23, 2026
22a1937
refactor: consistent cwd via uri
mshanemc Mar 23, 2026
bbd1762
test: e2e adjustments for "pick folder on test class create"
mshanemc Mar 23, 2026
0f26912
docs: wireit rule
mshanemc Mar 23, 2026
2d3e57f
docs: wireit rule
mshanemc Mar 23, 2026
fc3bca6
Merge remote-tracking branch 'origin/sm/unify-create-apex-class' into…
mshanemc Mar 23, 2026
31b7ade
Merge remote-tracking branch 'origin/develop' into sm/unify-create-ap…
mshanemc Mar 23, 2026
828e0f2
Merge remote-tracking branch 'origin/sm/unify-create-apex-class' into…
mshanemc Mar 24, 2026
7459b8b
Merge remote-tracking branch 'origin/develop' into sm/trigger-templates
mshanemc Mar 25, 2026
02bd954
refactor: message sharing for class/trigger
mshanemc Mar 25, 2026
1f975fe
refactor: consistent uri handling
mshanemc Mar 25, 2026
4d49d5a
refactor: consistent directory picker
mshanemc Mar 25, 2026
4b045cd
Merge remote-tracking branch 'origin/develop' into sm/trigger-templates
mshanemc Mar 25, 2026
90abebf
refactor: helper cleanup
mshanemc Mar 25, 2026
4528492
revert: move soql changes to soql-without-utils branch
mshanemc Mar 25, 2026
d8c3132
chore: lockfile, lint
mshanemc Mar 25, 2026
bb37da0
refactor: conslidate api template api logic
mshanemc Mar 25, 2026
db70302
refactor: templates copy/load once
mshanemc Mar 25, 2026
5e5b798
Merge remote-tracking branch 'origin/develop' into sm/trigger-templates
mshanemc Mar 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions .claude/skills/services-extension-consumption/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
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, or implementing file/config watchers.
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, or implementing file/config watchers.
---

# Consuming salesforcedx-vscode-services
Expand Down Expand Up @@ -87,14 +87,19 @@ export const activate = async (context: vscode.ExtensionContext): Promise<void>

## Registering Commands

Use `registerCommandWithLayer` pre-loaded with AllServicesLayer:
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);
```

Expand All @@ -103,6 +108,7 @@ Commands auto:
- Register with ExtensionContext subscriptions
- Wrap with error handling
- Trace with observability spans
- Handle Cancellation

## Basic Services

Expand All @@ -116,6 +122,7 @@ Accessor pattern: call methods directly, don't assign to variable first.
- [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

## Watchers

Expand Down
102 changes: 102 additions & 0 deletions .claude/skills/services-extension-consumption/references/prompts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# VS Code Prompts

Best practices for `quickPick` and `quickInput` in Effect-TS.

## PromptService

### `considerUndefinedAsCancellation`

Converts `undefined` (Esc) or empty strings to `UserCancellationError`.

```typescript
const choice =
yield *
Effect.promise(() => vscode.window.showQuickPick(['a', 'b'])).pipe(
Effect.flatMap(promptService.considerUndefinedAsCancellation)
);
```

### `ensureMetadataOverwriteOrThrow`

Checks file existence; prompts for overwrite. Fails with `UserCancellationError` on cancel.

```typescript
yield * promptService.ensureMetadataOverwriteOrThrow({ uris });
```

## Handling Cancellations

### `UserCancellationError`

`Schema.TaggedError` for silent exits.

- **Silent**: `registerCommandWithLayer/Runtime` catch this and return `Effect.void` (no UI error).
- **Messages**: Only set `message` for trace/log details.

```typescript
// Silent exit
return yield * new UserCancellationError();

// Exit with log context
return yield * new UserCancellationError({ message: 'User cancelled overwrite' });
```

## Pattern: Wrapping Promises

Always wrap VS Code prompt promises in `Effect.promise`.

```typescript
const promptForTemplate = Effect.fn('promptForTemplate')(function* () {
const api = yield* (yield* ExtensionProviderService).getServicesApi;
const promptService = yield* api.services.PromptService;

return yield* Effect.promise(() =>
vscode.window.showQuickPick(
[
{ label: 'DefaultApexClass', description: '...' },
{ label: 'ApexException', description: '...' }
],
{ placeHolder: 'Select template' }
)
).pipe(
Effect.flatMap(promptService.considerUndefinedAsCancellation),
Effect.map(choice => choice.label)
);
});
```

## Template Helpers

Use `sfTemplateProjectHelpers.ts` for metadata generation.

### `promptForApexTypeName`

Validates Apex names.

```typescript
const className =
yield *
promptForApexTypeName({
prompt: nls.localize('apex_class_name_prompt')
});
```

- prompt text uses `apex_class_name_prompt`
- built-in validation strings: `apex_name_empty_error`, `apex_name_format_error`, `apex_class_name_max_length_error`, `apex_name_cannot_be_default`
- supply `messages` with localized overrides if you need custom copy or translations

### `promptForOutputDir`

Selects metadata package folder (apex classes/triggers, etc.).

```typescript
const outputDirUri = yield * promptForOutputDir(project, 'classes', 'Select output directory');
```

### `getApiVersion`

Waterfall: `sfdx-project.json` → connection → fallback.

```typescript
const apiVersion = yield * getApiVersion(project);
```
3 changes: 2 additions & 1 deletion .cursor/hooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"matcher": "git.*--no-verify"
}
],
"stop": [{ "command": ".cursor/hooks/verify-stop.sh" }]
"stop": [{ "command": ".cursor/hooks/verify-stop.sh" }],
"afterFileEdit": [{ "command": ".cursor/hooks/mark-edit.sh" }]
}
}
9 changes: 9 additions & 0 deletions .cursor/hooks/mark-edit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the goal of the stuff in this hooks folder is to not run the verifier stuff when there's no file edits;
this makes is possible to to plan/ask/etc while in a broken state and to cancel AI things when it goes down the wrong road without it trying to fix things.

# afterFileEdit hook: mark that an edit occurred in this session.
# We use CURSOR_TRACE_ID to distinguish sessions.

if [ -n "$CURSOR_TRACE_ID" ]; then
touch "/tmp/cursor_edit_${CURSOR_TRACE_ID}"
echo "[afterFileEdit] marked session ${CURSOR_TRACE_ID} as dirty" >&2
fi
exit 0
13 changes: 13 additions & 0 deletions .cursor/hooks/verify-stop.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ run_step() {
out=$(eval "$cmd" 2>&1) || fail "$step" "$out"
}

# Check if this agent session made any changes.
# We use CURSOR_TRACE_ID to track the session and a temporary file to mark if an edit occurred.
SESSION_MARKER="/tmp/cursor_edit_${CURSOR_TRACE_ID:-default}"

if [ ! -f "$SESSION_MARKER" ]; then
echo "[verify-stop] no edits in this session, skipping verification" >&2
exit 0
fi

echo "[verify-stop] edits detected in session, running verification" >&2
# Clean up marker for next run
rm -f "$SESSION_MARKER"

run_step "compile" "npm run compile" && echo "[verify-stop] compile ok" >&2
run_step "lint" "npm run lint" && echo "[verify-stop] lint ok" >&2

Expand Down
2 changes: 1 addition & 1 deletion docs/Build.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ When you add a dependency, run the bundling process to make sure that your dep i

**Web:** [esbuild-plugin-copy](https://www.npmjs.com/package/esbuild-plugin-copy) for static assets; `globbyOptions: { dot: true }` when copying templates with dot files.

**Web:** Manifest for extension assets — `vscode.workspace.fs.readDirectory` not supported on HTTPS extension URIs. Build-time manifest (e.g. services `generateTemplatesManifest`) + runtime `readFile` per path. See [templateService.ts](../packages/salesforcedx-vscode-services/src/core/templateService.ts).
**Web:** Manifest for extension assets — `vscode.workspace.fs.readDirectory` not supported on HTTPS extension URIs. Build-time manifest (e.g. services `generateTemplatesManifest`) + runtime `readFile` per path. Services template hydration runs once (`Effect.once`); templates root is memoized (`Effect.cached`). See [templateService.ts](../packages/salesforcedx-vscode-services/src/core/templateService.ts).

**ESBUILD_PLATFORM:** Bundle-time define (web.mjs injects `'web'` or `'node'`). Not a runtime check — value baked in at bundle; dead branches tree-shaken. Examples: [connectionService](../packages/salesforcedx-vscode-services/src/core/connectionService.ts), [templateService](../packages/salesforcedx-vscode-services/src/core/templateService.ts), [soql LSP client](../packages/salesforcedx-vscode-soql/src/lspClient/client.ts).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@

import { ExtensionProviderService } from '@salesforce/effect-ext-utils';
import * as Effect from 'effect/Effect';
import * as Option from 'effect/Option';
import * as vscode from 'vscode';
import { Utils } from 'vscode-uri';
import { nls } from '../messages';

const promptForScriptName = Effect.fn('promptForScriptName')(function* () {
const name = yield* Effect.promise(() =>
const api = yield* (yield* ExtensionProviderService).getServicesApi;
const promptService = yield* api.services.PromptService;

return yield* Effect.promise(() =>
vscode.window.showInputBox({
prompt: nls.localize('create_script_name_prompt'),
validateInput: (value: string) => {
Expand All @@ -23,59 +25,25 @@ const promptForScriptName = Effect.fn('promptForScriptName')(function* () {
return undefined;
}
})
).pipe(
Effect.map(raw => raw?.trim()),
Effect.flatMap(promptService.considerUndefinedAsCancellation)
);
return name?.trim() ? Option.some(name.trim()) : Option.none();
});

const CUSTOM_DIR_LABEL = `$(file-directory) ${nls.localize('create_script_custom_output_directory')}`;

const promptForOutputDir = Effect.fn('promptForOutputDir')(function* () {
const api = yield* (yield* ExtensionProviderService).getServicesApi;
const promptService = yield* api.services.PromptService;
const workspaceInfo = yield* api.services.WorkspaceService.getWorkspaceInfoOrThrow();

const defaultUri = Utils.joinPath(workspaceInfo.uri, 'scripts', 'apex');

const selected = yield* Effect.promise(() =>
vscode.window.showQuickPick(
[
{ label: defaultUri.fsPath, description: nls.localize('create_script_output_dir_default_description'), uri: defaultUri },
{ label: CUSTOM_DIR_LABEL, description: undefined, uri: undefined }
],
{
placeHolder: nls.localize('create_script_output_dir_prompt'),
matchOnDescription: true
}
)
).pipe(Effect.flatMap(choice => promptService.considerUndefinedAsCancellation(choice)));

if (selected.label === CUSTOM_DIR_LABEL) {
const folders = yield* Effect.promise(() =>
vscode.window.showOpenDialog({
canSelectFiles: false,
canSelectFolders: true,
canSelectMany: false,
defaultUri: workspaceInfo.uri,
openLabel: 'Select'
})
);
return folders?.[0];
}

return selected.uri;
});

export const createAnonymousApexScriptCommand = Effect.fn('ApexLog.Command.createAnonymousApexScript')(function* () {
const api = yield* (yield* ExtensionProviderService).getServicesApi;
const fsService = yield* api.services.FsService;
const promptService = yield* api.services.PromptService;
const workspaceInfo = yield* api.services.WorkspaceService.getWorkspaceInfoOrThrow();

const scriptNameOpt = yield* promptForScriptName();
if (Option.isNone(scriptNameOpt)) return;
const scriptName = scriptNameOpt.value;
const scriptName = yield* promptForScriptName();

const outputDir = yield* promptForOutputDir();
if (!outputDir) return;
const outputDir = yield* promptService.promptForOutputDir({
defaultUri: Utils.joinPath(workspaceInfo.uri, 'scripts', 'apex'),
description: nls.localize('create_script_output_dir_default_description'),
pickerPlaceHolder: nls.localize('create_script_output_dir_prompt')
});

const targetUri = Utils.joinPath(outputDir, `${scriptName}.apex`);
yield* promptService.ensureMetadataOverwriteOrThrow({ uris: [targetUri] });
Expand Down
6 changes: 2 additions & 4 deletions packages/salesforcedx-vscode-apex-log/src/messages/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@

export const messages = {
create_script_name_empty_error: 'Script name cannot be empty',
create_script_name_format_error:
'Name must start with a letter and contain only letters, numbers, and underscores',
create_script_name_format_error: 'Name must start with a letter and contain only letters, numbers, and underscores',
create_script_name_prompt: 'Enter script name',
log_get_no_logs: 'No Apex debug logs found',
log_get_pick_log: 'Select an Apex debug log to open',
Expand Down Expand Up @@ -50,6 +49,5 @@ export const messages = {
exec_anon_success: 'Anonymous Apex executed successfully',
open_log: 'Open Log',
create_script_output_dir_prompt: 'Select the target directory',
create_script_output_dir_default_description: '(default)',
create_script_custom_output_directory: 'Choose a Custom Directory'
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a bunch of this is now in a shared fn in services

create_script_output_dir_default_description: '(default)'
} as const;
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ test('Execute Anonymous Apex: document, selection, script creation, compile erro
// Wait for directory QuickPick list rows (InputBox has none; QuickPick has 2 options)
await quickInput.locator(QUICK_INPUT_LIST_ROW).first().waitFor({ state: 'visible', timeout: 10_000 });
await page.keyboard.press('Enter');
// Wait for directory QuickPick list rows (InputBox has none; QuickPick has 2 options)
await quickInput.locator(QUICK_INPUT_LIST_ROW).first().waitFor({ state: 'visible', timeout: 10_000 });
await page.keyboard.press('Enter');
const editor = page.locator(EDITOR_WITH_URI).first();
await editor.waitFor({ state: 'visible', timeout: 15_000 });
await expect(page.locator(TAB).filter({ hasText: /\.apex$/ })).toBeVisible({ timeout: 5000 });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,6 @@ type ApexGenerateUnitTestClassParams = {
readonly template?: ApexTestTemplate;
};

const fromProject = Effect.fn('getApiVersion.fromProject')(function* (project: SfProject) {
const projectJson = yield* Effect.tryPromise(() => project.retrieveSfProjectJson());
return String(projectJson.get<string>('sourceApiVersion'));
});

const fromConnection = Effect.fn('getApiVersion.fromConnection')(function* () {
const connectionService = yield* (yield* (yield* ExtensionProviderService).getServicesApi).services.ConnectionService;
const connection = yield* connectionService.getConnection();
return connection.version;
});

const getApiVersion = Effect.fn('getApiVersion')(function* (project: SfProject) {
return yield* fromProject(project).pipe(
Effect.orElse(() => fromConnection()),
Effect.catchAll(err =>
Effect.log('Could not determine API version, using default', { error: err }).pipe(Effect.as('65.0'))
)
);
});

const promptForOutputDir = Effect.fn('promptForOutputDir')(function* (project: SfProject) {
const api = yield* (yield* ExtensionProviderService).getServicesApi;
const promptService = yield* api.services.PromptService;
Expand Down Expand Up @@ -121,15 +101,14 @@ export const apexGenerateUnitTestClassCommand = Effect.fn('apexGenerateUnitTestC
const outputDirUri = params?.outputDir ?? outputDirectory ?? (yield* promptForOutputDir(project));

const workspaceInfo = yield* api.services.WorkspaceService.getWorkspaceInfoOrThrow();
const apiVersion = yield* getApiVersion(project);
const uris = [`${className}.cls`, `${className}.cls-meta.xml`].map(uri => Utils.joinPath(outputDirUri, uri));
yield* promptService.ensureMetadataOverwriteOrThrow({ uris });

const result = yield* api.services.TemplateService.create({
cwd: yield* fsService.uriToPath(workspaceInfo.uri),
templateType: api.services.TemplateType.ApexClass,
outputdir: outputDirUri,
options: { template, classname: className, apiversion: apiVersion }
options: { template, classname: className }
});
const channelService = yield* api.services.ChannelService;

Expand Down
12 changes: 0 additions & 12 deletions packages/salesforcedx-vscode-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -308,10 +308,6 @@
"command": "sf.internal.lightning.generate.interface",
"when": "explorerResourceIsFolder && sf:internal_dev"
},
{
"command": "sf.apex.generate.trigger",
"when": "explorerResourceIsFolder && resourceFilename == triggers && sf:project_opened"
},
{
"command": "sf.diff",
"when": "!explorerResourceIsFolder && sf:project_opened && resourceLangId != 'forcesourcemanifest' && sf:has_target_org && sf:show_shared_commands"
Expand Down Expand Up @@ -426,10 +422,6 @@
"command": "sf.rename.lightning.component",
"when": "false"
},
{
"command": "sf.apex.generate.trigger",
"when": "sf:project_opened"
},
{
"command": "sf.retrieve.source.path",
"when": "false"
Expand Down Expand Up @@ -617,10 +609,6 @@
"command": "sf.project.generate.with.manifest",
"title": "%project_generate_with_manifest_text%"
},
{
"command": "sf.apex.generate.trigger",
"title": "%apex_generate_trigger_text%"
},
{
"command": "sf.retrieve.source.path",
"title": "%retrieve_this_source_text%"
Expand Down
1 change: 0 additions & 1 deletion packages/salesforcedx-vscode-core/package.nls.ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
"alias_list_text": "SFDX: すべてのエイリアスを一覧表示",
"analytics_generate_template_text": "SFDX: サンプルの Analytics テンプレートを作成",
"apex_generate_class_text": "SFDX: Apex クラスを作成",
"apex_generate_trigger_text": "SFDX: Apex トリガを作成",
"config_list_text": "SFDX: すべての設定変数を一覧表示",
"conflict_detect_open_file": "ファイルを開く",
"conflict_detect_resolve_view": "Org Differences",
Expand Down
Loading
Loading