scc-universal 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +44 -0
- package/.cursor/agents/deep-researcher.md +142 -0
- package/.cursor/agents/doc-updater.md +219 -0
- package/.cursor/agents/eval-runner.md +335 -0
- package/.cursor/agents/learning-engine.md +210 -0
- package/.cursor/agents/loop-operator.md +245 -0
- package/.cursor/agents/refactor-cleaner.md +119 -0
- package/.cursor/agents/sf-admin-agent.md +127 -0
- package/.cursor/agents/sf-agentforce-agent.md +126 -0
- package/.cursor/agents/sf-apex-agent.md +117 -0
- package/.cursor/agents/sf-architect.md +426 -0
- package/.cursor/agents/sf-aura-reviewer.md +369 -0
- package/.cursor/agents/sf-bugfix-agent.md +101 -0
- package/.cursor/agents/sf-flow-agent.md +155 -0
- package/.cursor/agents/sf-integration-agent.md +141 -0
- package/.cursor/agents/sf-lwc-agent.md +123 -0
- package/.cursor/agents/sf-review-agent.md +357 -0
- package/.cursor/agents/sf-visualforce-reviewer.md +465 -0
- package/.cursor/hooks/adapter.js +81 -0
- package/.cursor/hooks/after-file-edit.js +26 -0
- package/.cursor/hooks/after-mcp-execution.js +12 -0
- package/.cursor/hooks/after-shell-execution.js +30 -0
- package/.cursor/hooks/after-tab-file-edit.js +12 -0
- package/.cursor/hooks/before-mcp-execution.js +11 -0
- package/.cursor/hooks/before-read-file.js +13 -0
- package/.cursor/hooks/before-shell-execution.js +29 -0
- package/.cursor/hooks/before-submit-prompt.js +23 -0
- package/.cursor/hooks/pre-compact.js +7 -0
- package/.cursor/hooks/session-end.js +10 -0
- package/.cursor/hooks/session-start.js +10 -0
- package/.cursor/hooks/stop.js +18 -0
- package/.cursor/hooks/subagent-start.js +10 -0
- package/.cursor/hooks/subagent-stop.js +10 -0
- package/.cursor/hooks.json +107 -0
- package/.cursor/skills/aside/SKILL.md +115 -0
- package/.cursor/skills/checkpoint/SKILL.md +50 -0
- package/.cursor/skills/configure-scc/SKILL.md +160 -0
- package/.cursor/skills/continuous-agent-loop/SKILL.md +260 -0
- package/.cursor/skills/mcp-server-patterns/SKILL.md +142 -0
- package/.cursor/skills/model-route/SKILL.md +81 -0
- package/.cursor/skills/prompt-optimizer/SKILL.md +366 -0
- package/.cursor/skills/refactor-clean/SKILL.md +133 -0
- package/.cursor/skills/resume-session/SKILL.md +111 -0
- package/.cursor/skills/save-session/SKILL.md +183 -0
- package/.cursor/skills/search-first/SKILL.md +140 -0
- package/.cursor/skills/security-scan/SKILL.md +142 -0
- package/.cursor/skills/sessions/SKILL.md +124 -0
- package/.cursor/skills/sf-agentforce-development/SKILL.md +449 -0
- package/.cursor/skills/sf-apex-async-patterns/SKILL.md +324 -0
- package/.cursor/skills/sf-apex-best-practices/SKILL.md +421 -0
- package/.cursor/skills/sf-apex-constraints/SKILL.md +79 -0
- package/.cursor/skills/sf-apex-cursor/SKILL.md +336 -0
- package/.cursor/skills/sf-apex-enterprise-patterns/SKILL.md +344 -0
- package/.cursor/skills/sf-apex-testing/SKILL.md +407 -0
- package/.cursor/skills/sf-api-design/SKILL.md +237 -0
- package/.cursor/skills/sf-approval-processes/SKILL.md +312 -0
- package/.cursor/skills/sf-aura-development/SKILL.md +260 -0
- package/.cursor/skills/sf-build-fix/SKILL.md +120 -0
- package/.cursor/skills/sf-data-modeling/SKILL.md +274 -0
- package/.cursor/skills/sf-debugging/SKILL.md +362 -0
- package/.cursor/skills/sf-deployment/SKILL.md +291 -0
- package/.cursor/skills/sf-deployment-constraints/SKILL.md +153 -0
- package/.cursor/skills/sf-devops-ci-cd/SKILL.md +322 -0
- package/.cursor/skills/sf-docs-lookup/SKILL.md +100 -0
- package/.cursor/skills/sf-e2e-testing/SKILL.md +321 -0
- package/.cursor/skills/sf-experience-cloud/SKILL.md +248 -0
- package/.cursor/skills/sf-flow-development/SKILL.md +376 -0
- package/.cursor/skills/sf-governor-limits/SKILL.md +319 -0
- package/.cursor/skills/sf-harness-audit/SKILL.md +139 -0
- package/.cursor/skills/sf-help/SKILL.md +156 -0
- package/.cursor/skills/sf-integration/SKILL.md +479 -0
- package/.cursor/skills/sf-lwc-constraints/SKILL.md +128 -0
- package/.cursor/skills/sf-lwc-development/SKILL.md +302 -0
- package/.cursor/skills/sf-lwc-testing/SKILL.md +387 -0
- package/.cursor/skills/sf-metadata-management/SKILL.md +285 -0
- package/.cursor/skills/sf-platform-events-cdc/SKILL.md +372 -0
- package/.cursor/skills/sf-quickstart/SKILL.md +170 -0
- package/.cursor/skills/sf-security/SKILL.md +330 -0
- package/.cursor/skills/sf-security-constraints/SKILL.md +125 -0
- package/.cursor/skills/sf-soql-constraints/SKILL.md +129 -0
- package/.cursor/skills/sf-soql-optimization/SKILL.md +353 -0
- package/.cursor/skills/sf-tdd-workflow/SKILL.md +332 -0
- package/.cursor/skills/sf-testing-constraints/SKILL.md +198 -0
- package/.cursor/skills/sf-trigger-constraints/SKILL.md +88 -0
- package/.cursor/skills/sf-trigger-frameworks/SKILL.md +343 -0
- package/.cursor/skills/sf-visualforce-development/SKILL.md +259 -0
- package/.cursor/skills/strategic-compact/SKILL.md +205 -0
- package/.cursor/skills/update-docs/SKILL.md +162 -0
- package/.cursor/skills/update-platform-docs/SKILL.md +86 -0
- package/.cursor-plugin/plugin.json +26 -0
- package/LICENSE +21 -0
- package/README.md +522 -0
- package/agents/deep-researcher.md +145 -0
- package/agents/doc-updater.md +222 -0
- package/agents/eval-runner.md +340 -0
- package/agents/learning-engine.md +211 -0
- package/agents/loop-operator.md +247 -0
- package/agents/refactor-cleaner.md +122 -0
- package/agents/sf-admin-agent.md +131 -0
- package/agents/sf-agentforce-agent.md +132 -0
- package/agents/sf-apex-agent.md +124 -0
- package/agents/sf-architect.md +435 -0
- package/agents/sf-aura-reviewer.md +372 -0
- package/agents/sf-bugfix-agent.md +105 -0
- package/agents/sf-flow-agent.md +159 -0
- package/agents/sf-integration-agent.md +146 -0
- package/agents/sf-lwc-agent.md +127 -0
- package/agents/sf-review-agent.md +366 -0
- package/agents/sf-visualforce-reviewer.md +468 -0
- package/assets/logo.svg +18 -0
- package/docs/ARCHITECTURE.md +133 -0
- package/docs/authoring-guide.md +373 -0
- package/docs/hook-development.md +578 -0
- package/docs/token-optimization.md +139 -0
- package/docs/workflow-examples.md +645 -0
- package/examples/agentforce-action/README.md +227 -0
- package/examples/apex-trigger-handler/README.md +114 -0
- package/examples/devops-pipeline/README.md +325 -0
- package/examples/flow-automation/README.md +188 -0
- package/examples/integration-pattern/README.md +416 -0
- package/examples/lwc-component/README.md +180 -0
- package/examples/platform-events/README.md +492 -0
- package/examples/scratch-org-setup/README.md +138 -0
- package/examples/security-audit/README.md +244 -0
- package/examples/visualforce-migration/README.md +314 -0
- package/hooks/hooks.json +338 -0
- package/hooks/memory-persistence/README.md +73 -0
- package/manifests/install-modules.json +217 -0
- package/manifests/install-profiles.json +17 -0
- package/mcp-configs/mcp-servers.json +19 -0
- package/package.json +89 -0
- package/schemas/hooks.schema.json +123 -0
- package/schemas/install-modules.schema.json +76 -0
- package/schemas/install-profiles.schema.json +28 -0
- package/schemas/install-state.schema.json +73 -0
- package/schemas/package-manager.schema.json +18 -0
- package/schemas/plugin.schema.json +112 -0
- package/schemas/scc-install-config.schema.json +29 -0
- package/schemas/state-store.schema.json +111 -0
- package/scripts/cli/install-apply.js +170 -0
- package/scripts/cli/uninstall.js +193 -0
- package/scripts/hooks/check-console-log.js +101 -0
- package/scripts/hooks/check-hook-enabled.js +17 -0
- package/scripts/hooks/check-platform-docs-age.js +48 -0
- package/scripts/hooks/cost-tracker.js +78 -0
- package/scripts/hooks/doc-file-warning.js +63 -0
- package/scripts/hooks/evaluate-session.js +98 -0
- package/scripts/hooks/governor-check.js +220 -0
- package/scripts/hooks/learning-observe.sh +206 -0
- package/scripts/hooks/mcp-health-check.js +588 -0
- package/scripts/hooks/post-bash-build-complete.js +34 -0
- package/scripts/hooks/post-bash-pr-created.js +43 -0
- package/scripts/hooks/post-edit-console-warn.js +61 -0
- package/scripts/hooks/post-edit-format.js +79 -0
- package/scripts/hooks/post-edit-typecheck.js +98 -0
- package/scripts/hooks/post-write.js +168 -0
- package/scripts/hooks/pre-bash-git-push-reminder.js +35 -0
- package/scripts/hooks/pre-bash-tmux-reminder.js +47 -0
- package/scripts/hooks/pre-compact.js +51 -0
- package/scripts/hooks/pre-tool-use.js +163 -0
- package/scripts/hooks/pre-write-doc-warn.js +9 -0
- package/scripts/hooks/quality-gate.js +251 -0
- package/scripts/hooks/run-with-flags-shell.sh +32 -0
- package/scripts/hooks/run-with-flags.js +135 -0
- package/scripts/hooks/session-end-marker.js +29 -0
- package/scripts/hooks/session-end.js +311 -0
- package/scripts/hooks/session-start.js +202 -0
- package/scripts/hooks/sfdx-scanner-check.js +142 -0
- package/scripts/hooks/sfdx-validate.js +119 -0
- package/scripts/hooks/stop-hook.js +170 -0
- package/scripts/hooks/suggest-compact.js +67 -0
- package/scripts/lib/agent-adapter.js +82 -0
- package/scripts/lib/apex-analysis.js +194 -0
- package/scripts/lib/hook-flags.js +74 -0
- package/scripts/lib/install-config.js +73 -0
- package/scripts/lib/install-executor.js +363 -0
- package/scripts/lib/install-state.js +121 -0
- package/scripts/lib/orchestration-session.js +299 -0
- package/scripts/lib/package-manager.js +124 -0
- package/scripts/lib/project-detect.js +228 -0
- package/scripts/lib/schema-validator.js +190 -0
- package/scripts/lib/skill-adapter.js +100 -0
- package/scripts/lib/state-store.js +376 -0
- package/scripts/lib/tmux-worktree-orchestrator.js +598 -0
- package/scripts/lib/utils.js +313 -0
- package/scripts/scc.js +164 -0
- package/skills/_reference/AGENTFORCE_PATTERNS.md +112 -0
- package/skills/_reference/APEX_CURSOR.md +159 -0
- package/skills/_reference/API_VERSIONS.md +78 -0
- package/skills/_reference/APPROVAL_PROCESSES.md +105 -0
- package/skills/_reference/ASYNC_PATTERNS.md +163 -0
- package/skills/_reference/AURA_COMPONENTS.md +146 -0
- package/skills/_reference/DATA_MIGRATION_PATTERNS.md +151 -0
- package/skills/_reference/DATA_MODELING.md +124 -0
- package/skills/_reference/DEBUGGING_TOOLS.md +140 -0
- package/skills/_reference/DEPLOYMENT_CHECKLIST.md +87 -0
- package/skills/_reference/DEPRECATIONS.md +79 -0
- package/skills/_reference/DOCKER_CI_PATTERNS.md +138 -0
- package/skills/_reference/ENTERPRISE_PATTERNS.md +122 -0
- package/skills/_reference/EXPERIENCE_CLOUD.md +143 -0
- package/skills/_reference/FLOW_PATTERNS.md +113 -0
- package/skills/_reference/GOVERNOR_LIMITS.md +77 -0
- package/skills/_reference/INTEGRATION_PATTERNS.md +105 -0
- package/skills/_reference/LWC_PATTERNS.md +79 -0
- package/skills/_reference/METADATA_TYPES.md +115 -0
- package/skills/_reference/NAMING_CONVENTIONS.md +84 -0
- package/skills/_reference/PACKAGE_DEVELOPMENT.md +150 -0
- package/skills/_reference/PLATFORM_EVENTS.md +121 -0
- package/skills/_reference/REPORTING_API.md +143 -0
- package/skills/_reference/SCRATCH_ORG_PATTERNS.md +126 -0
- package/skills/_reference/SECURITY_PATTERNS.md +127 -0
- package/skills/_reference/SHARING_MODEL.md +120 -0
- package/skills/_reference/SOQL_PATTERNS.md +119 -0
- package/skills/_reference/TESTING_STANDARDS.md +96 -0
- package/skills/_reference/TRIGGER_PATTERNS.md +114 -0
- package/skills/_reference/VISUALFORCE_PATTERNS.md +121 -0
- package/skills/aside/SKILL.md +118 -0
- package/skills/checkpoint/SKILL.md +53 -0
- package/skills/configure-scc/SKILL.md +163 -0
- package/skills/continuous-agent-loop/SKILL.md +264 -0
- package/skills/mcp-server-patterns/SKILL.md +146 -0
- package/skills/model-route/SKILL.md +84 -0
- package/skills/prompt-optimizer/SKILL.md +369 -0
- package/skills/refactor-clean/SKILL.md +136 -0
- package/skills/resume-session/SKILL.md +114 -0
- package/skills/save-session/SKILL.md +186 -0
- package/skills/search-first/SKILL.md +144 -0
- package/skills/security-scan/SKILL.md +146 -0
- package/skills/sessions/SKILL.md +127 -0
- package/skills/sf-agentforce-development/SKILL.md +450 -0
- package/skills/sf-apex-async-patterns/SKILL.md +326 -0
- package/skills/sf-apex-best-practices/SKILL.md +425 -0
- package/skills/sf-apex-constraints/SKILL.md +81 -0
- package/skills/sf-apex-cursor/SKILL.md +338 -0
- package/skills/sf-apex-enterprise-patterns/SKILL.md +348 -0
- package/skills/sf-apex-testing/SKILL.md +409 -0
- package/skills/sf-api-design/SKILL.md +238 -0
- package/skills/sf-approval-processes/SKILL.md +315 -0
- package/skills/sf-aura-development/SKILL.md +263 -0
- package/skills/sf-build-fix/SKILL.md +121 -0
- package/skills/sf-data-modeling/SKILL.md +278 -0
- package/skills/sf-debugging/SKILL.md +363 -0
- package/skills/sf-deployment/SKILL.md +295 -0
- package/skills/sf-deployment-constraints/SKILL.md +155 -0
- package/skills/sf-devops-ci-cd/SKILL.md +325 -0
- package/skills/sf-docs-lookup/SKILL.md +103 -0
- package/skills/sf-e2e-testing/SKILL.md +324 -0
- package/skills/sf-experience-cloud/SKILL.md +249 -0
- package/skills/sf-flow-development/SKILL.md +377 -0
- package/skills/sf-governor-limits/SKILL.md +323 -0
- package/skills/sf-harness-audit/SKILL.md +142 -0
- package/skills/sf-help/SKILL.md +159 -0
- package/skills/sf-integration/SKILL.md +483 -0
- package/skills/sf-lwc-constraints/SKILL.md +130 -0
- package/skills/sf-lwc-development/SKILL.md +303 -0
- package/skills/sf-lwc-testing/SKILL.md +388 -0
- package/skills/sf-metadata-management/SKILL.md +288 -0
- package/skills/sf-platform-events-cdc/SKILL.md +375 -0
- package/skills/sf-quickstart/SKILL.md +173 -0
- package/skills/sf-security/SKILL.md +334 -0
- package/skills/sf-security-constraints/SKILL.md +127 -0
- package/skills/sf-soql-constraints/SKILL.md +131 -0
- package/skills/sf-soql-optimization/SKILL.md +354 -0
- package/skills/sf-tdd-workflow/SKILL.md +336 -0
- package/skills/sf-testing-constraints/SKILL.md +200 -0
- package/skills/sf-trigger-constraints/SKILL.md +90 -0
- package/skills/sf-trigger-frameworks/SKILL.md +347 -0
- package/skills/sf-visualforce-development/SKILL.md +260 -0
- package/skills/strategic-compact/SKILL.md +208 -0
- package/skills/update-docs/SKILL.md +165 -0
- package/skills/update-platform-docs/SKILL.md +90 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# Agentforce Custom Action
|
|
2
|
+
|
|
3
|
+
Custom Agentforce action using `@InvocableMethod` with structured inputs/outputs for Flow and Agent integration. Requires API version 66.0+ (Spring '26).
|
|
4
|
+
|
|
5
|
+
## When to Use This Pattern
|
|
6
|
+
|
|
7
|
+
- Building custom actions for Agentforce agents
|
|
8
|
+
- Exposing Apex logic to Flows as invocable actions
|
|
9
|
+
- Creating reusable automation building blocks with typed inputs and outputs
|
|
10
|
+
- Integrating external services into Agentforce conversations
|
|
11
|
+
|
|
12
|
+
## Structure
|
|
13
|
+
|
|
14
|
+
```text
|
|
15
|
+
force-app/main/default/
|
|
16
|
+
classes/
|
|
17
|
+
CreateCaseAction.cls # Invocable action class
|
|
18
|
+
CreateCaseAction_Test.cls # Test class with mock data
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Invocable Action Class
|
|
22
|
+
|
|
23
|
+
```apex
|
|
24
|
+
public with sharing class CreateCaseAction {
|
|
25
|
+
|
|
26
|
+
public class ActionInput {
|
|
27
|
+
@InvocableVariable(required=true label='Subject' description='Case subject line')
|
|
28
|
+
public String subject;
|
|
29
|
+
|
|
30
|
+
@InvocableVariable(required=true label='Description' description='Detailed case description')
|
|
31
|
+
public String description;
|
|
32
|
+
|
|
33
|
+
@InvocableVariable(label='Priority' description='Case priority: Low, Medium, High, Critical')
|
|
34
|
+
public String priority;
|
|
35
|
+
|
|
36
|
+
@InvocableVariable(label='Account ID' description='Related account ID')
|
|
37
|
+
public Id accountId;
|
|
38
|
+
|
|
39
|
+
@InvocableVariable(label='Contact ID' description='Related contact ID')
|
|
40
|
+
public Id contactId;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public class ActionOutput {
|
|
44
|
+
@InvocableVariable(label='Case ID' description='ID of the created case')
|
|
45
|
+
public Id caseId;
|
|
46
|
+
|
|
47
|
+
@InvocableVariable(label='Case Number' description='Auto-generated case number')
|
|
48
|
+
public String caseNumber;
|
|
49
|
+
|
|
50
|
+
@InvocableVariable(label='Success' description='Whether the action succeeded')
|
|
51
|
+
public Boolean success;
|
|
52
|
+
|
|
53
|
+
@InvocableVariable(label='Error Message' description='Error details if action failed')
|
|
54
|
+
public String errorMessage;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@InvocableMethod(
|
|
58
|
+
label='Create Support Case'
|
|
59
|
+
description='Creates a new support case from Agentforce or Flow input'
|
|
60
|
+
category='Support'
|
|
61
|
+
)
|
|
62
|
+
public static List<ActionOutput> createCase(List<ActionInput> inputs) {
|
|
63
|
+
List<ActionOutput> outputs = new List<ActionOutput>();
|
|
64
|
+
|
|
65
|
+
for (ActionInput input : inputs) {
|
|
66
|
+
ActionOutput output = new ActionOutput();
|
|
67
|
+
try {
|
|
68
|
+
Case newCase = new Case(
|
|
69
|
+
Subject = input.subject,
|
|
70
|
+
Description = input.description,
|
|
71
|
+
Priority = String.isNotBlank(input.priority) ? input.priority : 'Medium',
|
|
72
|
+
Status = 'New',
|
|
73
|
+
Origin = 'Agentforce',
|
|
74
|
+
AccountId = input.accountId,
|
|
75
|
+
ContactId = input.contactId
|
|
76
|
+
);
|
|
77
|
+
insert newCase;
|
|
78
|
+
|
|
79
|
+
// Re-query for auto-generated fields
|
|
80
|
+
newCase = [SELECT Id, CaseNumber FROM Case WHERE Id = :newCase.Id LIMIT 1];
|
|
81
|
+
|
|
82
|
+
output.caseId = newCase.Id;
|
|
83
|
+
output.caseNumber = newCase.CaseNumber;
|
|
84
|
+
output.success = true;
|
|
85
|
+
} catch (Exception e) {
|
|
86
|
+
output.success = false;
|
|
87
|
+
output.errorMessage = e.getMessage();
|
|
88
|
+
}
|
|
89
|
+
outputs.add(output);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return outputs;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Test Class
|
|
98
|
+
|
|
99
|
+
```apex
|
|
100
|
+
@IsTest
|
|
101
|
+
private class CreateCaseAction_Test {
|
|
102
|
+
|
|
103
|
+
@TestSetup
|
|
104
|
+
static void setupData() {
|
|
105
|
+
Account testAccount = new Account(Name = 'Test Corp');
|
|
106
|
+
insert testAccount;
|
|
107
|
+
|
|
108
|
+
Contact testContact = new Contact(
|
|
109
|
+
FirstName = 'Jane',
|
|
110
|
+
LastName = 'Doe',
|
|
111
|
+
AccountId = testAccount.Id,
|
|
112
|
+
Email = 'jane.doe@testcorp.com'
|
|
113
|
+
);
|
|
114
|
+
insert testContact;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@IsTest
|
|
118
|
+
static void testCreateCase_Success() {
|
|
119
|
+
Account acct = [SELECT Id FROM Account LIMIT 1];
|
|
120
|
+
Contact con = [SELECT Id FROM Contact LIMIT 1];
|
|
121
|
+
|
|
122
|
+
CreateCaseAction.ActionInput input = new CreateCaseAction.ActionInput();
|
|
123
|
+
input.subject = 'Login issue';
|
|
124
|
+
input.description = 'Customer cannot log in to portal';
|
|
125
|
+
input.priority = 'High';
|
|
126
|
+
input.accountId = acct.Id;
|
|
127
|
+
input.contactId = con.Id;
|
|
128
|
+
|
|
129
|
+
Test.startTest();
|
|
130
|
+
List<CreateCaseAction.ActionOutput> results =
|
|
131
|
+
CreateCaseAction.createCase(new List<CreateCaseAction.ActionInput>{ input });
|
|
132
|
+
Test.stopTest();
|
|
133
|
+
|
|
134
|
+
System.assertEquals(1, results.size());
|
|
135
|
+
System.assertEquals(true, results[0].success);
|
|
136
|
+
System.assertNotEquals(null, results[0].caseId);
|
|
137
|
+
System.assertNotEquals(null, results[0].caseNumber);
|
|
138
|
+
|
|
139
|
+
Case created = [SELECT Subject, Priority, Origin FROM Case WHERE Id = :results[0].caseId];
|
|
140
|
+
System.assertEquals('Login issue', created.Subject);
|
|
141
|
+
System.assertEquals('High', created.Priority);
|
|
142
|
+
System.assertEquals('Agentforce', created.Origin);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
@IsTest
|
|
146
|
+
static void testCreateCase_DefaultPriority() {
|
|
147
|
+
CreateCaseAction.ActionInput input = new CreateCaseAction.ActionInput();
|
|
148
|
+
input.subject = 'General inquiry';
|
|
149
|
+
input.description = 'Question about features';
|
|
150
|
+
|
|
151
|
+
Test.startTest();
|
|
152
|
+
List<CreateCaseAction.ActionOutput> results =
|
|
153
|
+
CreateCaseAction.createCase(new List<CreateCaseAction.ActionInput>{ input });
|
|
154
|
+
Test.stopTest();
|
|
155
|
+
|
|
156
|
+
System.assertEquals(true, results[0].success);
|
|
157
|
+
Case created = [SELECT Priority FROM Case WHERE Id = :results[0].caseId];
|
|
158
|
+
System.assertEquals('Medium', created.Priority);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
@IsTest
|
|
162
|
+
static void testCreateCase_BulkInvocation() {
|
|
163
|
+
List<CreateCaseAction.ActionInput> inputs = new List<CreateCaseAction.ActionInput>();
|
|
164
|
+
for (Integer i = 0; i < 50; i++) {
|
|
165
|
+
CreateCaseAction.ActionInput input = new CreateCaseAction.ActionInput();
|
|
166
|
+
input.subject = 'Bulk case ' + i;
|
|
167
|
+
input.description = 'Description for case ' + i;
|
|
168
|
+
inputs.add(input);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
Test.startTest();
|
|
172
|
+
List<CreateCaseAction.ActionOutput> results = CreateCaseAction.createCase(inputs);
|
|
173
|
+
Test.stopTest();
|
|
174
|
+
|
|
175
|
+
System.assertEquals(50, results.size());
|
|
176
|
+
for (CreateCaseAction.ActionOutput output : results) {
|
|
177
|
+
System.assertEquals(true, output.success);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Flow Integration
|
|
184
|
+
|
|
185
|
+
Once the action is deployed, it appears in Flow Builder under **Action** elements:
|
|
186
|
+
|
|
187
|
+
1. Open Flow Builder and add an **Action** element
|
|
188
|
+
2. Search for "Create Support Case"
|
|
189
|
+
3. Map Flow variables to the `ActionInput` fields
|
|
190
|
+
4. Use the `ActionOutput` fields in subsequent Flow elements
|
|
191
|
+
|
|
192
|
+
```text
|
|
193
|
+
Flow: Case Creation from Chat
|
|
194
|
+
Step 1: Get Input (Screen / Agentforce prompt)
|
|
195
|
+
Step 2: Action → Create Support Case
|
|
196
|
+
- Subject = {!chatSubject}
|
|
197
|
+
- Description = {!chatDescription}
|
|
198
|
+
- Priority = {!selectedPriority}
|
|
199
|
+
- Account ID = {!currentAccountId}
|
|
200
|
+
Step 3: Decision → Check {!ActionOutput.success}
|
|
201
|
+
- True → Display confirmation with {!ActionOutput.caseNumber}
|
|
202
|
+
- False → Display {!ActionOutput.errorMessage}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Key Principles
|
|
206
|
+
|
|
207
|
+
- Always use `with sharing` to enforce record-level security
|
|
208
|
+
- Mark required inputs with `required=true` on `@InvocableVariable`
|
|
209
|
+
- Provide `label` and `description` on every variable for Flow/Agent discoverability
|
|
210
|
+
- Handle errors gracefully and return them in the output rather than throwing exceptions
|
|
211
|
+
- Design for bulk invocation: the method receives and returns `List<>`, not single records
|
|
212
|
+
- Re-query for auto-number fields after insert
|
|
213
|
+
|
|
214
|
+
## Common Pitfalls
|
|
215
|
+
|
|
216
|
+
- Forgetting to bulkify: inserting records inside a loop hits governor limits
|
|
217
|
+
- Not handling null optional inputs, which causes NullPointerException
|
|
218
|
+
- Missing `label` and `description` makes the action hard to find in Flow Builder
|
|
219
|
+
- Throwing unhandled exceptions breaks the entire Flow/Agent transaction
|
|
220
|
+
- Using `without sharing` inadvertently bypasses record access controls
|
|
221
|
+
|
|
222
|
+
## SCC Skills
|
|
223
|
+
|
|
224
|
+
- `/sf-agentforce-development` -- scaffold and review Agentforce actions
|
|
225
|
+
- `/sf-apex-best-practices` -- review the action class for best practices
|
|
226
|
+
- `/sf-tdd-workflow` -- write tests first, then implement the action
|
|
227
|
+
- `/sf-governor-limits` -- check governor limit compliance in bulk scenarios
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Apex Trigger Handler Pattern
|
|
2
|
+
|
|
3
|
+
Enterprise-grade trigger handler pattern with one trigger per object. Compatible with API version 66.0 (Spring '26).
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
force-app/main/default/
|
|
9
|
+
classes/
|
|
10
|
+
TriggerHandler.cls # Base handler class
|
|
11
|
+
AccountTriggerHandler.cls # Account-specific handler
|
|
12
|
+
AccountTriggerHandler_Test.cls
|
|
13
|
+
triggers/
|
|
14
|
+
AccountTrigger.trigger # Thin trigger — delegates to handler
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Base Handler
|
|
18
|
+
|
|
19
|
+
```apex
|
|
20
|
+
public virtual class TriggerHandler {
|
|
21
|
+
|
|
22
|
+
@TestVisible private Boolean isTriggerExecuting;
|
|
23
|
+
@TestVisible private Integer batchSize;
|
|
24
|
+
|
|
25
|
+
public TriggerHandler() {
|
|
26
|
+
this.isTriggerExecuting = Trigger.isExecuting;
|
|
27
|
+
this.batchSize = Trigger.size;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public void run() {
|
|
31
|
+
if (!validateRun()) return;
|
|
32
|
+
|
|
33
|
+
switch on Trigger.operationType {
|
|
34
|
+
when BEFORE_INSERT { beforeInsert(Trigger.new); }
|
|
35
|
+
when BEFORE_UPDATE { beforeUpdate(Trigger.new, Trigger.oldMap); }
|
|
36
|
+
when BEFORE_DELETE { beforeDelete(Trigger.old, Trigger.oldMap); }
|
|
37
|
+
when AFTER_INSERT { afterInsert(Trigger.new, Trigger.newMap); }
|
|
38
|
+
when AFTER_UPDATE { afterUpdate(Trigger.new, Trigger.oldMap, Trigger.newMap); }
|
|
39
|
+
when AFTER_DELETE { afterDelete(Trigger.old, Trigger.oldMap); }
|
|
40
|
+
when AFTER_UNDELETE { afterUndelete(Trigger.new, Trigger.newMap); }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@TestVisible
|
|
45
|
+
protected virtual Boolean validateRun() {
|
|
46
|
+
if (!this.isTriggerExecuting) {
|
|
47
|
+
throw new TriggerHandlerException('Trigger handler called outside of trigger execution');
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Override these in subclasses
|
|
53
|
+
protected virtual void beforeInsert(List<SObject> newRecords) {}
|
|
54
|
+
protected virtual void beforeUpdate(List<SObject> newRecords, Map<Id, SObject> oldMap) {}
|
|
55
|
+
protected virtual void beforeDelete(List<SObject> oldRecords, Map<Id, SObject> oldMap) {}
|
|
56
|
+
protected virtual void afterInsert(List<SObject> newRecords, Map<Id, SObject> newMap) {}
|
|
57
|
+
protected virtual void afterUpdate(List<SObject> newRecords, Map<Id, SObject> oldMap, Map<Id, SObject> newMap) {}
|
|
58
|
+
protected virtual void afterDelete(List<SObject> oldRecords, Map<Id, SObject> oldMap) {}
|
|
59
|
+
protected virtual void afterUndelete(List<SObject> newRecords, Map<Id, SObject> newMap) {}
|
|
60
|
+
|
|
61
|
+
public class TriggerHandlerException extends Exception {}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Trigger (Thin)
|
|
66
|
+
|
|
67
|
+
```apex
|
|
68
|
+
trigger AccountTrigger on Account (
|
|
69
|
+
before insert, before update, before delete,
|
|
70
|
+
after insert, after update, after delete, after undelete
|
|
71
|
+
) {
|
|
72
|
+
new AccountTriggerHandler().run();
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Handler Implementation
|
|
77
|
+
|
|
78
|
+
```apex
|
|
79
|
+
public class AccountTriggerHandler extends TriggerHandler {
|
|
80
|
+
|
|
81
|
+
protected override void beforeInsert(List<SObject> newRecords) {
|
|
82
|
+
List<Account> accounts = (List<Account>) newRecords;
|
|
83
|
+
for (Account acc : accounts) {
|
|
84
|
+
if (String.isBlank(acc.Industry)) {
|
|
85
|
+
acc.Industry = 'Other';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
protected override void afterUpdate(List<SObject> newRecords, Map<Id, SObject> oldMap, Map<Id, SObject> newMap) {
|
|
91
|
+
List<Account> accounts = (List<Account>) newRecords;
|
|
92
|
+
List<Account> changedAccounts = new List<Account>();
|
|
93
|
+
|
|
94
|
+
for (Account acc : accounts) {
|
|
95
|
+
Account oldAcc = (Account) oldMap.get(acc.Id);
|
|
96
|
+
if (acc.OwnerId != oldAcc.OwnerId) {
|
|
97
|
+
changedAccounts.add(acc);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!changedAccounts.isEmpty()) {
|
|
102
|
+
AccountService.handleOwnerChanges(changedAccounts);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Key Principles
|
|
109
|
+
|
|
110
|
+
- One trigger per object
|
|
111
|
+
- Trigger delegates to handler — no logic in the trigger itself
|
|
112
|
+
- Handler methods receive proper context (newRecords, oldMap, etc.)
|
|
113
|
+
- All operations are bulkified (iterate over collections)
|
|
114
|
+
- Business logic extracted to Service classes
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# CI/CD Pipeline for Salesforce
|
|
2
|
+
|
|
3
|
+
End-to-end DevOps pipeline with scratch org creation, source deployment, test execution, PMD scanning, and production deployment using GitHub Actions. Targets API version 66.0 (Spring '26).
|
|
4
|
+
|
|
5
|
+
## When to Use This Pattern
|
|
6
|
+
|
|
7
|
+
- Setting up continuous integration for a Salesforce DX project
|
|
8
|
+
- Automating test execution and code quality checks on every pull request
|
|
9
|
+
- Building a repeatable deployment pipeline from development to production
|
|
10
|
+
- Enforcing quality gates before merging code changes
|
|
11
|
+
|
|
12
|
+
## Structure
|
|
13
|
+
|
|
14
|
+
```text
|
|
15
|
+
.github/
|
|
16
|
+
workflows/
|
|
17
|
+
ci.yml # Pull request validation pipeline
|
|
18
|
+
deploy-production.yml # Production deployment pipeline
|
|
19
|
+
scripts/
|
|
20
|
+
scratch-org-setup.sh # Scratch org creation and data load
|
|
21
|
+
run-tests.sh # Test execution with coverage reporting
|
|
22
|
+
config/
|
|
23
|
+
project-scratch-def.json # Scratch org definition
|
|
24
|
+
sfdx-project.json # Project configuration
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Scratch Org Creation Script
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
#!/bin/bash
|
|
31
|
+
# scripts/scratch-org-setup.sh
|
|
32
|
+
# Creates a scratch org, pushes source, assigns permission sets, and loads data.
|
|
33
|
+
|
|
34
|
+
set -euo pipefail
|
|
35
|
+
|
|
36
|
+
ORG_ALIAS="${1:-ci-scratch}"
|
|
37
|
+
DURATION="${2:-1}"
|
|
38
|
+
|
|
39
|
+
echo "==> Creating scratch org: ${ORG_ALIAS} (duration: ${DURATION} days)"
|
|
40
|
+
sf org create scratch \
|
|
41
|
+
--definition-file config/project-scratch-def.json \
|
|
42
|
+
--alias "${ORG_ALIAS}" \
|
|
43
|
+
--duration-days "${DURATION}" \
|
|
44
|
+
--set-default \
|
|
45
|
+
--wait 10
|
|
46
|
+
|
|
47
|
+
echo "==> Pushing source to scratch org"
|
|
48
|
+
sf project deploy start --target-org "${ORG_ALIAS}" --wait 30
|
|
49
|
+
|
|
50
|
+
echo "==> Assigning permission sets"
|
|
51
|
+
sf org assign permset \
|
|
52
|
+
--name AppAdmin \
|
|
53
|
+
--target-org "${ORG_ALIAS}"
|
|
54
|
+
|
|
55
|
+
sf org assign permset \
|
|
56
|
+
--name IntegrationUser \
|
|
57
|
+
--target-org "${ORG_ALIAS}"
|
|
58
|
+
|
|
59
|
+
echo "==> Loading sample data"
|
|
60
|
+
sf data import tree \
|
|
61
|
+
--files data/Account.json,data/Contact.json,data/Opportunity.json \
|
|
62
|
+
--target-org "${ORG_ALIAS}"
|
|
63
|
+
|
|
64
|
+
echo "==> Scratch org ready: ${ORG_ALIAS}"
|
|
65
|
+
sf org open --target-org "${ORG_ALIAS}"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Scratch Org Definition
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"orgName": "SCC CI Scratch Org",
|
|
73
|
+
"edition": "Developer",
|
|
74
|
+
"features": ["EnableSetPasswordInApi", "Communities", "ServiceCloud"],
|
|
75
|
+
"settings": {
|
|
76
|
+
"lightningExperienceSettings": {
|
|
77
|
+
"enableS1DesktopEnabled": true
|
|
78
|
+
},
|
|
79
|
+
"securitySettings": {
|
|
80
|
+
"passwordPolicies": {
|
|
81
|
+
"enableSetPasswordInApi": true
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"languageSettings": {
|
|
85
|
+
"enableTranslationWorkbench": true
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Test Execution Script
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
#!/bin/bash
|
|
95
|
+
# scripts/run-tests.sh
|
|
96
|
+
# Runs all Apex tests with coverage and fails if coverage is below threshold.
|
|
97
|
+
|
|
98
|
+
set -euo pipefail
|
|
99
|
+
|
|
100
|
+
ORG_ALIAS="${1:-ci-scratch}"
|
|
101
|
+
COVERAGE_THRESHOLD="${2:-75}"
|
|
102
|
+
|
|
103
|
+
echo "==> Running all Apex tests with code coverage"
|
|
104
|
+
sf apex run test \
|
|
105
|
+
--target-org "${ORG_ALIAS}" \
|
|
106
|
+
--code-coverage \
|
|
107
|
+
--result-format human \
|
|
108
|
+
--output-dir test-results \
|
|
109
|
+
--wait 30 \
|
|
110
|
+
--test-level RunLocalTests
|
|
111
|
+
|
|
112
|
+
echo "==> Checking coverage threshold (${COVERAGE_THRESHOLD}%)"
|
|
113
|
+
COVERAGE=$(cat test-results/test-result-codecoverage.json \
|
|
114
|
+
| python3 -c "
|
|
115
|
+
import json, sys
|
|
116
|
+
data = json.load(sys.stdin)
|
|
117
|
+
total_lines = sum(r.get('totalLines', 0) for r in data)
|
|
118
|
+
covered_lines = sum(r.get('totalCovered', 0) for r in data)
|
|
119
|
+
pct = (covered_lines / total_lines * 100) if total_lines > 0 else 0
|
|
120
|
+
print(f'{pct:.1f}')
|
|
121
|
+
")
|
|
122
|
+
|
|
123
|
+
echo "==> Overall coverage: ${COVERAGE}%"
|
|
124
|
+
|
|
125
|
+
if (( $(echo "${COVERAGE} < ${COVERAGE_THRESHOLD}" | bc -l) )); then
|
|
126
|
+
echo "ERROR: Coverage ${COVERAGE}% is below threshold ${COVERAGE_THRESHOLD}%"
|
|
127
|
+
exit 1
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
echo "==> Coverage check passed"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## PMD Scanning Integration
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
#!/bin/bash
|
|
137
|
+
# Run SFDX Scanner with PMD rules as a CI gate
|
|
138
|
+
|
|
139
|
+
set -euo pipefail
|
|
140
|
+
|
|
141
|
+
echo "==> Running PMD security and best practices scan"
|
|
142
|
+
sf scanner run \
|
|
143
|
+
--target "force-app/main/default/classes/**/*.cls" \
|
|
144
|
+
--category "Security,Best Practices,Performance" \
|
|
145
|
+
--engine pmd \
|
|
146
|
+
--format json \
|
|
147
|
+
--outfile scanner-results.json \
|
|
148
|
+
--severity-threshold 2
|
|
149
|
+
|
|
150
|
+
VIOLATIONS=$(cat scanner-results.json | python3 -c "
|
|
151
|
+
import json, sys
|
|
152
|
+
data = json.load(sys.stdin)
|
|
153
|
+
count = len(data) if isinstance(data, list) else 0
|
|
154
|
+
print(count)
|
|
155
|
+
")
|
|
156
|
+
|
|
157
|
+
echo "==> Found ${VIOLATIONS} violation(s)"
|
|
158
|
+
|
|
159
|
+
if [ "${VIOLATIONS}" -gt 0 ]; then
|
|
160
|
+
echo "==> Violations found. See scanner-results.json for details."
|
|
161
|
+
sf scanner run \
|
|
162
|
+
--target "force-app/main/default/classes/**/*.cls" \
|
|
163
|
+
--category "Security,Best Practices,Performance" \
|
|
164
|
+
--engine pmd \
|
|
165
|
+
--format table \
|
|
166
|
+
--severity-threshold 2
|
|
167
|
+
exit 1
|
|
168
|
+
fi
|
|
169
|
+
|
|
170
|
+
echo "==> PMD scan passed"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## GitHub Actions: Pull Request Validation
|
|
174
|
+
|
|
175
|
+
```yaml
|
|
176
|
+
# .github/workflows/ci.yml
|
|
177
|
+
name: Salesforce CI
|
|
178
|
+
|
|
179
|
+
on:
|
|
180
|
+
pull_request:
|
|
181
|
+
branches: [main, develop]
|
|
182
|
+
paths:
|
|
183
|
+
- 'force-app/**'
|
|
184
|
+
- 'config/**'
|
|
185
|
+
- 'sfdx-project.json'
|
|
186
|
+
|
|
187
|
+
jobs:
|
|
188
|
+
validate:
|
|
189
|
+
runs-on: ubuntu-latest
|
|
190
|
+
steps:
|
|
191
|
+
- name: Checkout
|
|
192
|
+
uses: actions/checkout@v4
|
|
193
|
+
|
|
194
|
+
- name: Install Salesforce CLI
|
|
195
|
+
run: npm install -g @salesforce/cli
|
|
196
|
+
|
|
197
|
+
- name: Install SFDX Scanner
|
|
198
|
+
run: sf plugins install @salesforce/sfdx-scanner
|
|
199
|
+
|
|
200
|
+
- name: Authenticate Dev Hub
|
|
201
|
+
run: |
|
|
202
|
+
echo "${SFDX_AUTH_URL}" > auth.txt
|
|
203
|
+
sf org login sfdx-url --sfdx-url-file auth.txt --alias devhub --set-default-dev-hub
|
|
204
|
+
rm auth.txt
|
|
205
|
+
env:
|
|
206
|
+
SFDX_AUTH_URL: ${{ secrets.SFDX_DEVHUB_AUTH_URL }}
|
|
207
|
+
|
|
208
|
+
- name: Create Scratch Org
|
|
209
|
+
run: |
|
|
210
|
+
sf org create scratch \
|
|
211
|
+
--definition-file config/project-scratch-def.json \
|
|
212
|
+
--alias ci-scratch \
|
|
213
|
+
--duration-days 1 \
|
|
214
|
+
--set-default \
|
|
215
|
+
--wait 15
|
|
216
|
+
|
|
217
|
+
- name: Deploy Source
|
|
218
|
+
run: sf project deploy start --target-org ci-scratch --wait 30
|
|
219
|
+
|
|
220
|
+
- name: Run Apex Tests
|
|
221
|
+
run: |
|
|
222
|
+
sf apex run test \
|
|
223
|
+
--target-org ci-scratch \
|
|
224
|
+
--code-coverage \
|
|
225
|
+
--result-format human \
|
|
226
|
+
--output-dir test-results \
|
|
227
|
+
--wait 30 \
|
|
228
|
+
--test-level RunLocalTests
|
|
229
|
+
|
|
230
|
+
- name: Upload Test Results
|
|
231
|
+
if: always()
|
|
232
|
+
uses: actions/upload-artifact@v4
|
|
233
|
+
with:
|
|
234
|
+
name: apex-test-results
|
|
235
|
+
path: test-results/
|
|
236
|
+
|
|
237
|
+
- name: Run PMD Scanner
|
|
238
|
+
run: |
|
|
239
|
+
sf scanner run \
|
|
240
|
+
--target "force-app/main/default/classes/**/*.cls" \
|
|
241
|
+
--category "Security,Best Practices" \
|
|
242
|
+
--engine pmd \
|
|
243
|
+
--format table \
|
|
244
|
+
--severity-threshold 2
|
|
245
|
+
|
|
246
|
+
- name: Delete Scratch Org
|
|
247
|
+
if: always()
|
|
248
|
+
run: sf org delete scratch --target-org ci-scratch --no-prompt
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## GitHub Actions: Production Deployment
|
|
252
|
+
|
|
253
|
+
```yaml
|
|
254
|
+
# .github/workflows/deploy-production.yml
|
|
255
|
+
name: Deploy to Production
|
|
256
|
+
|
|
257
|
+
on:
|
|
258
|
+
push:
|
|
259
|
+
branches: [main]
|
|
260
|
+
paths:
|
|
261
|
+
- 'force-app/**'
|
|
262
|
+
|
|
263
|
+
jobs:
|
|
264
|
+
deploy:
|
|
265
|
+
runs-on: ubuntu-latest
|
|
266
|
+
environment: production
|
|
267
|
+
steps:
|
|
268
|
+
- name: Checkout
|
|
269
|
+
uses: actions/checkout@v4
|
|
270
|
+
|
|
271
|
+
- name: Install Salesforce CLI
|
|
272
|
+
run: npm install -g @salesforce/cli
|
|
273
|
+
|
|
274
|
+
- name: Authenticate Production
|
|
275
|
+
run: |
|
|
276
|
+
echo "${SFDX_AUTH_URL}" > auth.txt
|
|
277
|
+
sf org login sfdx-url --sfdx-url-file auth.txt --alias production
|
|
278
|
+
rm auth.txt
|
|
279
|
+
env:
|
|
280
|
+
SFDX_AUTH_URL: ${{ secrets.SFDX_PROD_AUTH_URL }}
|
|
281
|
+
|
|
282
|
+
- name: Validate Deployment (Check Only)
|
|
283
|
+
run: |
|
|
284
|
+
sf project deploy start \
|
|
285
|
+
--target-org production \
|
|
286
|
+
--test-level RunLocalTests \
|
|
287
|
+
--dry-run \
|
|
288
|
+
--wait 60
|
|
289
|
+
|
|
290
|
+
- name: Deploy to Production
|
|
291
|
+
run: |
|
|
292
|
+
sf project deploy start \
|
|
293
|
+
--target-org production \
|
|
294
|
+
--test-level RunLocalTests \
|
|
295
|
+
--wait 60
|
|
296
|
+
|
|
297
|
+
- name: Verify Deployment
|
|
298
|
+
run: |
|
|
299
|
+
sf project deploy report --target-org production
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## Key Principles
|
|
303
|
+
|
|
304
|
+
- Every pull request validates in a fresh scratch org to catch deployment issues early
|
|
305
|
+
- Tests run with `RunLocalTests` to avoid executing managed package tests
|
|
306
|
+
- PMD scanning gates the pipeline: violations above a severity threshold fail the build
|
|
307
|
+
- Production deployments use a `--dry-run` validation step before the real deploy
|
|
308
|
+
- Scratch orgs are deleted after each CI run to conserve limits
|
|
309
|
+
- Auth URLs are stored as encrypted GitHub Secrets, never committed to source
|
|
310
|
+
|
|
311
|
+
## Common Pitfalls
|
|
312
|
+
|
|
313
|
+
- Not deleting scratch orgs in CI, which exhausts the daily scratch org limit
|
|
314
|
+
- Hardcoding the Dev Hub username instead of using auth URL files
|
|
315
|
+
- Skipping `RunLocalTests` in validation, then discovering test failures during real deploy
|
|
316
|
+
- Forgetting to install required plugins (scanner) in the CI environment
|
|
317
|
+
- Not setting `--wait` timeouts, causing CI jobs to hang indefinitely
|
|
318
|
+
- Storing SFDX auth URLs or tokens in the repository instead of secrets
|
|
319
|
+
|
|
320
|
+
## SCC Skills
|
|
321
|
+
|
|
322
|
+
- `/sf-deployment` -- validate and deploy metadata to a target org
|
|
323
|
+
- `/sf-devops-ci-cd` -- CI/CD pipeline patterns with scratch orgs
|
|
324
|
+
- `/sf-build-fix` -- diagnose and fix deployment failures
|
|
325
|
+
- `/sf-apex-testing` -- run Apex tests with coverage analysis
|