salesforce-development
Salesforce プラットフォーム開発に特化したスキルで、Lightning Web Components(LWC)、Apex トリガー・クラス、REST/Bulk API、Connected Apps、さらにスクラッチ org や第2世代パッケージ(2GP)を含む Salesforce DX の実践的なパターンを提供します。
description の原文を見る
Expert patterns for Salesforce platform development including Lightning Web Components (LWC), Apex triggers and classes, REST/Bulk APIs, Connected Apps, and Salesforce DX with scratch orgs and 2nd generation packages (2GP).
SKILL.md 本文
Salesforce Development
Salesforce プラットフォーム開発の専門的なパターン。Lightning Web Components (LWC)、Apex トリガーとクラス、REST/Bulk API、Connected Apps、Salesforce DX(scratch orgs と 2nd generation packages (2GP))に対応しています。
Patterns
Lightning Web Component with Wire Service
@wire デコレーターを使用して、Lightning Data Service または Apex メソッドでリアクティブなデータバインディングを実現します。@wire は LWC のリアクティブアーキテクチャに適合し、Salesforce のパフォーマンス最適化を実現します。
// myComponent.js
import { LightningElement, wire, api } from 'lwc';
import { getRecord, getFieldValue } from 'lightning/uiRecordApi';
import getRelatedRecords from '@salesforce/apex/MyController.getRelatedRecords';
import ACCOUNT_NAME from '@salesforce/schema/Account.Name';
import ACCOUNT_INDUSTRY from '@salesforce/schema/Account.Industry';
const FIELDS = [ACCOUNT_NAME, ACCOUNT_INDUSTRY];
export default class MyComponent extends LightningElement {
@api recordId; // Passed from parent or record page
// Wire to Lightning Data Service (preferred for single records)
@wire(getRecord, { recordId: '$recordId', fields: FIELDS })
account;
// Wire to Apex method (for complex queries)
@wire(getRelatedRecords, { accountId: '$recordId' })
wiredRecords({ error, data }) {
if (data) {
this.relatedRecords = data;
this.error = undefined;
} else if (error) {
this.error = error;
this.relatedRecords = undefined;
}
}
get accountName() {
return getFieldValue(this.account.data, ACCOUNT_NAME);
}
get isLoading() {
return !this.account.data && !this.account.error;
}
// Reactive: changing recordId automatically re-fetches
}
// myComponent.html
<template>
<lightning-card title={accountName}>
<template if:true={isLoading}>
<lightning-spinner alternative-text="Loading"></lightning-spinner>
</template>
<template if:true={account.data}>
<p>Industry: {industry}</p>
</template>
<template if:true={error}>
<p class="slds-text-color_error">{error.body.message}</p>
</template>
</lightning-card>
</template>
// MyController.cls
public with sharing class MyController {
@AuraEnabled(cacheable=true)
public static List<Contact> getRelatedRecords(Id accountId) {
return [
SELECT Id, Name, Email, Phone
FROM Contact
WHERE AccountId = :accountId
WITH SECURITY_ENFORCED
LIMIT 100
];
}
}
Context
- LWC コンポーネント構築
- Salesforce データ取得
- リアクティブ UI
Bulkified Apex Trigger with Handler Pattern
Apex トリガーはトランザクション当たり 200 件以上のレコードを処理するため、bulkify される必要があります。関心の分離、テスト可能性、再帰防止のためにハンドラーパターンを使用します。
// AccountTrigger.trigger
trigger AccountTrigger on Account (
before insert, before update, before delete,
after insert, after update, after delete, after undelete
) {
new AccountTriggerHandler().run();
}
// TriggerHandler.cls (base class)
public virtual class TriggerHandler {
// Recursion prevention
private static Set<String> executedHandlers = new Set<String>();
public void run() {
String handlerName = String.valueOf(this).split(':')[0];
// Prevent recursion
String contextKey = handlerName + '_' + Trigger.operationType;
if (executedHandlers.contains(contextKey)) {
return;
}
executedHandlers.add(contextKey);
switch on Trigger.operationType {
when BEFORE_INSERT { this.beforeInsert(); }
when BEFORE_UPDATE { this.beforeUpdate(); }
when BEFORE_DELETE { this.beforeDelete(); }
when AFTER_INSERT { this.afterInsert(); }
when AFTER_UPDATE { this.afterUpdate(); }
when AFTER_DELETE { this.afterDelete(); }
when AFTER_UNDELETE { this.afterUndelete(); }
}
}
// Override in child classes
protected virtual void beforeInsert() {}
protected virtual void beforeUpdate() {}
protected virtual void beforeDelete() {}
protected virtual void afterInsert() {}
protected virtual void afterUpdate() {}
protected virtual void afterDelete() {}
protected virtual void afterUndelete() {}
}
// AccountTriggerHandler.cls
public class AccountTriggerHandler extends TriggerHandler {
private List<Account> newAccounts;
private List<Account> oldAccounts;
private Map<Id, Account> newMap;
private Map<Id, Account> oldMap;
public AccountTriggerHandler() {
this.newAccounts = (List<Account>) Trigger.new;
this.oldAccounts = (List<Account>) Trigger.old;
this.newMap = (Map<Id, Account>) Trigger.newMap;
this.oldMap = (Map<Id, Account>) Trigger.oldMap;
}
protected override void afterInsert() {
createDefaultContacts();
notifySlack();
}
protected override void afterUpdate() {
handleIndustryChange();
}
// BULKIFIED: Query once, update once
private void createDefaultContacts() {
List<Contact> contactsToInsert = new List<Contact>();
for (Account acc : newAccounts) {
if (acc.Type == 'Prospect') {
contactsToInsert.add(new Contact(
AccountId = acc.Id,
LastName = 'Primary Contact',
Email = 'contact@' + acc.Website
));
}
}
if (!contactsToInsert.isEmpty()) {
insert contactsToInsert; // Single DML for all
}
}
private void handleIndustryChange() {
Set<Id> changedAccountIds = new Set<Id>();
for (Account acc : newAccounts) {
Account oldAcc = oldMap.get(acc.Id);
if (acc.Industry != oldAcc.Industry) {
changedAccountIds.add(acc.Id);
}
}
if (!changedAccountIds.isEmpty()) {
// Queue async processing for heavy work
System.enqueueJob(new IndustryChangeQueueable(changedAccountIds));
}
}
private void notifySlack() {
// Offload callouts to async
List<Id> accountIds = new List<Id>(newMap.keySet());
System.enqueueJob(new SlackNotificationQueueable(accountIds));
}
}
Context
- Apex トリガー
- データ操作
- オートメーション
Queueable Apex for Async Processing
Queueable Apex を非同期処理に使用します。非プリミティブ型のサポート、AsyncApexJob による監視、ジョブチェーンに対応します。制限:トランザクション当たり 50 ジョブ、チェーン時に 1 つの子ジョブ。
// IndustryChangeQueueable.cls
public class IndustryChangeQueueable implements Queueable, Database.AllowsCallouts {
private Set<Id> accountIds;
private Integer retryCount;
public IndustryChangeQueueable(Set<Id> accountIds) {
this(accountIds, 0);
}
public IndustryChangeQueueable(Set<Id> accountIds, Integer retryCount) {
this.accountIds = accountIds;
this.retryCount = retryCount;
}
public void execute(QueueableContext context) {
try {
// Query with fresh data
List<Account> accounts = [
SELECT Id, Name, Industry, OwnerId
FROM Account
WHERE Id IN :accountIds
WITH SECURITY_ENFORCED
];
// Process and make callout
for (Account acc : accounts) {
syncToExternalSystem(acc);
}
// Update records
updateRelatedOpportunities(accountIds);
} catch (Exception e) {
handleError(e);
}
}
private void syncToExternalSystem(Account acc) {
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:ExternalCRM/accounts');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
req.setBody(JSON.serialize(new Map<String, Object>{
'salesforceId' => acc.Id,
'name' => acc.Name,
'industry' => acc.Industry
}));
Http http = new Http();
HttpResponse res = http.send(req);
if (res.getStatusCode() != 200 && res.getStatusCode() != 201) {
throw new CalloutException('Sync failed: ' + res.getBody());
}
}
private void updateRelatedOpportunities(Set<Id> accIds) {
List<Opportunity> oppsToUpdate = [
SELECT Id, Industry__c, AccountId
FROM Opportunity
WHERE AccountId IN :accIds
WITH SECURITY_ENFORCED
];
Map<Id, Account> accountMap = new Map<Id, Account>([
SELECT Id, Industry FROM Account WHERE Id IN :accIds
]);
for (Opportunity opp : oppsToUpdate) {
opp.Industry__c = accountMap.get(opp.AccountId).Industry;
}
if (!oppsToUpdate.isEmpty()) {
update oppsToUpdate;
}
}
private void handleError(Exception e) {
// Log error
System.debug(LoggingLevel.ERROR, 'Queueable failed: ' + e.getMessage());
// Retry with exponential backoff (max 3 retries)
if (retryCount < 3) {
// Chain new job for retry
System.enqueueJob(new IndustryChangeQueueable(accountIds, retryCount + 1));
} else {
// Create error record for monitoring
insert new Integration_Error__c(
Type__c = 'Industry Sync',
Message__c = e.getMessage(),
Stack_Trace__c = e.getStackTraceString(),
Record_Ids__c = String.join(new List<Id>(accountIds), ',')
);
}
}
}
Context
- 非同期処理
- 長時間実行操作
- トリガーからのコールアウト
REST API Integration with Connected App
外部統合では Connected App と OAuth 2.0 を使用します。サーバー間通信では JWT Bearer フロー、ユーザー向けアプリでは Web Server フロー を使用します。常に Named Credentials を使用して安全なコールアウト設定を行います。
// Node.js - JWT Bearer Flow (server-to-server)
import jwt from 'jsonwebtoken';
import fs from 'fs';
class SalesforceClient {
private accessToken: string | null = null;
private instanceUrl: string | null = null;
private tokenExpiry: number = 0;
constructor(
private clientId: string,
private username: string,
private privateKeyPath: string,
private loginUrl: string = 'https://login.salesforce.com'
) {}
async authenticate(): Promise<void> {
// Check if token is still valid (5 min buffer)
if (this.accessToken && Date.now() < this.tokenExpiry - 300000) {
return;
}
const privateKey = fs.readFileSync(this.privateKeyPath, 'utf8');
// Create JWT assertion
const claim = {
iss: this.clientId,
sub: this.username,
aud: this.loginUrl,
exp: Math.floor(Date.now() / 1000) + 300 // 5 minutes
};
const assertion = jwt.sign(claim, privateKey, { algorithm: 'RS256' });
// Exchange JWT for access token
const response = await fetch(`${this.loginUrl}/services/oauth2/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Auth failed: ${error.error_description}`);
}
const data = await response.json();
this.accessToken = data.access_token;
this.instanceUrl = data.instance_url;
this.tokenExpiry = Date.now() + 7200000; // 2 hours
}
async query(soql: string): Promise<any> {
await this.authenticate();
const response = await fetch(
`${this.instanceUrl}/services/data/v59.0/query?q=${encodeURIComponent(soql)}`,
{
headers: {
'Authorization': `Bearer ${this.accessToken}`,
'Content-Type': 'application/json'
}
}
);
if (!response.ok) {
await this.handleError(response);
}
return response.json();
}
async createRecord(sobject: string, data: object): Promise<any> {
await this.authenticate();
const response = await fetch(
`${this.instanceUrl}/services/data/v59.0/sobjects/${sobject}`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${this.accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
}
);
if (!response.ok) {
await this.handleError(response);
}
return response.json();
}
private async handleError(response: Response): Promise<never> {
const error = await response.json();
if (response.status === 401) {
// Token expired, clear and retry
this.accessToken = null;
throw new Error('Session expired, retry required');
}
throw new Error(`API Error: ${JSON.stringify(error)}`);
}
}
// Usage
const sf = new SalesforceClient(
process.env.SF_CLIENT_ID!,
process.env.SF_USERNAME!,
'./certificates/server.key'
);
const accounts = await sf.query(
"SELECT Id, Name FROM Account WHERE CreatedDate = TODAY"
);
Context
- 外部統合
- REST API アクセス
- Connected Apps
Bulk API 2.0 for Large Data Operations
10K 件以上のレコードに対する操作には Bulk API 2.0 を使用します。ジョブベースのワークフローによる非同期処理です。REST API の一部で、オリジナルの Bulk API と比較してストリーミングラインインターフェースです。
// Node.js - Bulk API 2.0 insert
class SalesforceBulkClient extends SalesforceClient {
async bulkInsert(sobject: string, records: object[]): Promise<any> {
await this.authenticate();
// Step 1: Create job
const job = await this.createBulkJob(sobject, 'insert');
try {
// Step 2: Upload data (CSV format)
await this.uploadJobData(job.id, records);
// Step 3: Close job to start processing
await this.closeJob(job.id);
// Step 4: Poll for completion
return await this.waitForJobCompletion(job.id);
} catch (error) {
// Abort job on error
await this.abortJob(job.id);
throw error;
}
}
private async createBulkJob(sobject: string, operation: string): Promise<any> {
const response = await fetch(
`${this.instanceUrl}/services/data/v59.0/jobs/ingest`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${this.accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
object: sobject,
operation,
contentType: 'CSV',
lineEnding: 'LF'
})
}
);
return response.json();
}
private async uploadJobData(jobId: string, records: object[]): Promise<void> {
// Convert to CSV
const csv = this.recordsToCSV(records);
await fetch(
`${this.instanceUrl}/services/data/v59.0/jobs/ingest/${jobId}/batches`,
{
method: 'PUT',
headers: {
'Authorization': `Bearer ${this.accessToken}`,
'Content-Type': 'text/csv'
},
body: csv
}
);
}
private async closeJob(jobId: string): Promise<void> {
await fetch(
`${this.instanceUrl}/services/data/v59.0/jobs/ingest/${jobId}`,
{
method: 'PATCH',
headers: {
'Authorization': `Bearer ${this.accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ state: 'UploadComplete' })
}
);
}
private async waitForJobCompletion(jobId: string): Promise<any> {
const maxWaitTime = 10 * 60 * 1000; // 10 minutes
const pollInterval = 5000; // 5 seconds
const startTime = Date.now();
while (Date.now() - startTime < maxWaitTime) {
const response = await fetch(
`${this.instanceUrl}/services/data/v59.0/jobs/ingest/${jobId}`,
{
headers: { 'Authorization': `Bearer ${this.accessToken}` }
}
);
const job = await response.json();
if (job.state === 'JobComplete') {
// Get results
return {
success: job.numberRecordsProcessed - job.numberRecordsFailed,
failed: job.numberRecordsFailed,
failedResults: job.numberRecordsFailed > 0
? await this.getFailedResults(jobId)
: []
};
}
if (job.state === 'Failed' || job.state === 'Aborted') {
throw new Error(`Bulk job failed: ${job.state}`);
}
await new Promise(r => setTimeout(r, pollInterval));
}
throw new Error('Bulk job timeout');
}
private async getFailedResults(jobId: string): Promise<any[]> {
const response = await fetch(
`${this.instanceUrl}/services/data/v59.0/jobs/ingest/${jobId}/failedResults`,
{
headers: { 'Authorization': `Bearer ${this.accessToken}` }
}
);
const csv = await response.text();
return this.parseCSV(csv);
}
private recordsToCSV(records: object[]): string {
if (records.length === 0) return '';
const headers = Object.keys(records[0]);
const rows = records.map(r =>
headers.map(h => this.escapeCSV(r[h])).join(',')
);
return [headers.join(','), ...rows].join('\n');
}
private escapeCSV(value: any): string {
if (value === null || value === undefined) return '';
const str = String(value);
if (str.includes(',') || str.includes('"') || str.includes('\n')) {
return `"${str.replace(/"/g, '""')}"`;
}
return str;
}
}
Context
- 大規模データボリューム
- データマイグレーション
- バルク操作
Salesforce DX with Scratch Orgs
ソースドリブン開発で、分離されたテストのための使い捨て scratch orgs を使用します。Scratch orgs は 7-30 日間存在し、sandbox リフレッシュの制限と異なり、一日を通して作成できます。
// project-scratch-def.json - Scratch org definition
{
"orgName": "MyApp Dev Org",
"edition": "Developer",
"features": ["EnableSetPasswordInApi", "Communities"],
"settings": {
"lightningExperienceSettings": {
"enableS1DesktopEnabled": true
},
"mobileSettings": {
"enableS1EncryptedStoragePref2": false
},
"securitySettings": {
"passwordPolicies": {
"enableSetPasswordInApi": true
}
}
}
}
// sfdx-project.json - Project configuration
{
"packageDirectories": [
{
"path": "force-app",
"default": true,
"package": "MyPackage",
"versionName": "ver 1.0",
"versionNumber": "1.0.0.NEXT",
"dependencies": [
{
"package": "SomePackage@2.0.0"
}
]
}
],
"namespace": "myns",
"sfdcLoginUrl": "https://login.salesforce.com",
"sourceApiVersion": "59.0"
}
開発ワークフローコマンド
# 1. Create scratch org
sf org create scratch \
--definition-file config/project-scratch-def.json \
--alias myapp-dev \
--duration-days 7 \
--set-default
# 2. Push source to scratch org
sf project deploy start --target-org myapp-dev
# 3. Assign permission set
sf org assign permset --name MyApp_Admin --target-org myapp-dev
# 4. Import sample data
sf data import tree --plan data/sample-data-plan.json --target-org myapp-dev
# 5. Open org
sf org open --target-org myapp-dev
# 6. Run tests
sf apex run test \
--code-coverage \
--result-format human \
--wait 10 \
--target-org myapp-dev
# 7. Pull changes back
sf project retrieve start --target-org myapp-dev
Context
- 開発ワークフロー
- CI/CD
- テスト
2nd Generation Package (2GP) Development
2GP はソースドリブンでモジュラーなパッケージングで 1GP に置き換わります。Dev Hub と 2GP が有効化され、namespace がリンクされ、プロモートされたパッケージに対して 75% のコードカバレッジが必要です。
# Enable Dev Hub and 2GP in Setup:
# Setup > Dev Hub > Enable Dev Hub
# Setup > Dev Hub > Enable Unlocked Packages and 2GP
# Link namespace (required for managed packages)
sf package create \
--name "MyManagedPackage" \
--package-type Managed \
--path force-app \
--target-dev-hub DevHub
# Create package version (beta)
sf package version create \
--package "MyManagedPackage" \
--installation-key-bypass \
--wait 30 \
--code-coverage \
--target-dev-hub DevHub
# Check version status
sf package version list --packages "MyManagedPackage" --target-dev-hub DevHub
# Promote to released (requires 75% coverage)
sf package version promote \
--package "MyManagedPackage@1.0.0-1" \
--target-dev-hub DevHub
# Install in sandbox for testing
sf package install \
--package "MyManagedPackage@1.0.0-1" \
--target-org MySandbox \
--wait 20
CI/CD パイプライン (GitHub Actions)
# .github/workflows/salesforce-ci.yml
name: Salesforce CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Salesforce CLI
run: npm install -g @salesforce/cli
- name: Authenticate Dev Hub
run: |
echo "${{ secrets.SFDX_AUTH_URL }}" > auth.txt
sf org login sfdx-url --sfdx-url-file auth.txt --alias DevHub --set-default-dev-hub
- name: Create Scratch Org
run: |
sf org create scratch \
--definition-file config/project-scratch-def.json \
--alias ci-scratch \
--duration-days 1 \
--set-default
- name: Deploy Source
run: sf project deploy start --target-org ci-scratch
- name: Run Tests
run: |
sf apex run test \
--code-coverage \
--result-format human \
--wait 20 \
--target-org ci-scratch
- name: Delete Scratch Org
if: always()
run: sf org delete scratch --target-org ci-scratch --no-prompt
Context
- パッケージング
- ISV 開発
- AppExchange
Sharp Edges
Governor Limits Apply Per Transaction, Not Per Record
重大度:CRITICAL
ガバナー制限はトランザクション単位で適用され、レコード単位ではありません。
@wire Results Are Cached and May Be Stale
重大度:HIGH
@wire の結果はキャッシュされており、古い可能性があります。
LWC Properties Are Case-Sensitive
重大度:MEDIUM
LWC プロパティは大文字と小文字を区別します。
Null Pointer Exceptions in Apex Collections
重大度:HIGH
Apex コレクションの null ポインター例外。
Trigger Recursion Causes Infinite Loops
重大度:CRITICAL
トリガーの再帰は無限ループを引き起こします。
Cannot Make Callouts from Synchronous Triggers
重大度:HIGH
同期トリガーからコールアウトを行うことはできません。
Cannot Mix Setup and Non-Setup DML
重大度:HIGH
Setup と Non-Setup DML を混在させることはできません。
Dynamic SOQL Is Vulnerable to Injection
重大度:CRITICAL
動的 SOQL はインジェクション攻撃に脆弱です。
Scratch Orgs Expire and Lose All Data
重大度:MEDIUM
Scratch orgs は有効期限が切れ、すべてのデータが失われます。
API Version Mismatches Cause Silent Failures
重大度:MEDIUM
API バージョンの不一致はサイレント失敗を引き起こします。
Validation Checks
SOQL Query Inside Loop
重大度:ERROR
ループ内の SOQL はバルクデータでガバナー制限例外を引き起こします。
メッセージ:SOQL query inside loop. Query once outside the loop and use a Map.
DML Operation Inside Loop
重大度:ERROR
ループ内の DML は 150 ステートメント制限に達します。
メッセージ:DML operation inside loop. Collect records and perform single DML outside loop.
HTTP Callout in Trigger
重大度:ERROR
同期トリガーはコールアウトを行うことができません。
メッセージ:Callout in trigger. Use @future(callout=true) or Queueable with Database.AllowsCallouts.
Potential SOQL Injection
重大度:ERROR
文字列連結による動的 SOQL は脆弱です。
メッセージ:Dynamic SOQL with concatenation. Use bind variables or String.escapeSingleQuotes().
Missing WITH SECURITY_ENFORCED
重大度:WARNING
SOQL は FLS/CRUD 権限を強制すべきです。
メッセージ:SOQL without security enforcement. Add WITH SECURITY_ENFORCED.
Hardcoded Salesforce ID
重大度:WARNING
レコード ID は orgs 間で異なります。
メッセージ:Hardcoded Salesforce ID. Query by DeveloperName or ExternalId instead.
Hardcoded Credentials
重大度:ERROR
認証情報は Named Credentials またはカスタムメタデータを使用する必要があります。
メッセージ:Hardcoded credentials. Use Named Credentials or Custom Metadata.
Direct DOM Manipulation in LWC
重大度:WARNING
LWC はシャドウ DOM を使用し、直接操作はカプセル化を破壊します。
メッセージ:Direct DOM access in LWC. Use this.template.querySelector() or data binding.
Reactive Property Without @track
重大度:INFO
複雑なオブジェクトプロパティはリアクティブ性のために @track が必要です。
メッセージ:Object assignment may need @track for reactivity (post-Spring '20 objects are auto-tracked).
Wire Without Refresh After DML
重大度:WARNING
キャッシュされた wire データは更新後に古くなります。
メッセージ:DML after @wire without refreshApex. Data may be stale.
Collaboration
Delegation Triggers
- ユーザーが外部 API 統合が必要 -> backend (REST API 設計、外部システム同期)
- ユーザーが LWC を超えた複雑な UI が必要 -> frontend (React/Next.js を使用したカスタムポータル)
- ユーザーが HubSpot 統合が必要 -> hubspot-integration (Salesforce-HubSpot 同期パターン)
- ユーザーがデータウェアハウス同期が必要 -> data-engineer (Salesforce から warehouse への ETL)
- ユーザーが決済処理が必要 -> stripe-integration (Salesforce Billing を超えて)
- ユーザーが高度な認証が必要 -> auth-specialist (SSO、SAML、カスタムポータル)
When to Use
- ユーザーが言及または暗示:salesforce
- ユーザーが言及または暗示:sfdc
- ユーザーが言及または暗示:apex
- ユーザーが言及または暗示:lwc
- ユーザーが言及または暗示:lightning web components
- ユーザーが言及または暗示:sfdx
- ユーザーが言及または暗示:scratch org
- ユーザーが言及または暗示:visualforce
- ユーザーが言及または暗示:soql
- ユーザーが言及または暗示:governor limits
- ユーザーが言及または暗示:connected app
Limitations
- このスキルは、上記で説明されているスコープと明確に一致するタスクの場合のみ使用してください。
- 出力を環境固有の検証、テスト、またはエキスパートレビューの代替として扱わないでください。
- 必要な入力、権限、安全境界、または成功基準が不足している場合は、停止して説明を求めてください。
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- sickn33
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/sickn33/antigravity-awesome-skills / ライセンス: MIT
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。