@salesforce/afv-skills 1.5.1 → 1.5.3

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.
Files changed (78) hide show
  1. package/README.md +16 -416
  2. package/package.json +5 -3
  3. package/skills/building-ui-bundle-app/SKILL.md +325 -0
  4. package/skills/building-ui-bundle-frontend/SKILL.md +122 -0
  5. package/skills/{building-webapp-react-components → building-ui-bundle-frontend}/implementation/component.md +1 -1
  6. package/skills/creating-b2b-commerce-store/SKILL.md +169 -0
  7. package/skills/creating-b2b-commerce-store/references/store-vs-storefront.md +169 -0
  8. package/skills/deploying-ui-bundle/SKILL.md +77 -0
  9. package/skills/generating-apex/CREDITS.md +30 -0
  10. package/skills/generating-apex/SKILL.md +342 -189
  11. package/skills/generating-apex/assets/abstract.cls +12 -9
  12. package/skills/generating-apex/assets/batch.cls +7 -8
  13. package/skills/generating-apex/assets/domain.cls +5 -6
  14. package/skills/generating-apex/assets/dto.cls +11 -12
  15. package/skills/generating-apex/assets/exception.cls +1 -2
  16. package/skills/generating-apex/assets/interface.cls +2 -3
  17. package/skills/generating-apex/assets/invocable.cls +114 -0
  18. package/skills/generating-apex/assets/queueable.cls +6 -7
  19. package/skills/generating-apex/assets/rest-resource.cls +300 -0
  20. package/skills/generating-apex/assets/schedulable.cls +7 -8
  21. package/skills/generating-apex/assets/selector.cls +7 -8
  22. package/skills/generating-apex/assets/service.cls +4 -5
  23. package/skills/generating-apex/assets/trigger.cls +45 -0
  24. package/skills/generating-apex/assets/utility.cls +5 -6
  25. package/skills/generating-apex/references/AccountDeduplicationBatch.cls +7 -8
  26. package/skills/generating-apex/references/AccountSelector.cls +10 -11
  27. package/skills/generating-apex/references/AccountService.cls +9 -10
  28. package/skills/generating-apex-test/CREDITS.md +30 -0
  29. package/skills/generating-apex-test/SKILL.md +165 -74
  30. package/skills/generating-apex-test/assets/test-class-template.cls +25 -56
  31. package/skills/generating-apex-test/assets/test-data-factory-template.cls +0 -1
  32. package/skills/generating-apex-test/references/assertion-patterns.md +38 -95
  33. package/skills/generating-apex-test/references/async-testing.md +59 -142
  34. package/skills/generating-apex-test/references/mocking-patterns.md +77 -76
  35. package/skills/generating-apex-test/references/test-data-factory.md +29 -130
  36. package/skills/generating-experience-react-site/SKILL.md +9 -9
  37. package/skills/generating-experience-react-site/docs/configure-metadata-digital-experience.md +1 -1
  38. package/skills/generating-flexipage/SKILL.md +28 -12
  39. package/skills/generating-ui-bundle-features/SKILL.md +45 -0
  40. package/skills/generating-ui-bundle-metadata/SKILL.md +106 -0
  41. package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/SKILL.md +5 -5
  42. package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/references/constraints.md +2 -2
  43. package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/references/examples.md +1 -1
  44. package/skills/{implementing-webapp-file-upload → implementing-ui-bundle-file-upload}/SKILL.md +11 -11
  45. package/skills/searching-media/SKILL.md +1 -1
  46. package/skills/uplifting-components-to-slds2/SKILL.md +236 -0
  47. package/skills/uplifting-components-to-slds2/references/color-hooks-decision-guide.md +438 -0
  48. package/skills/uplifting-components-to-slds2/references/common-patterns.md +87 -0
  49. package/skills/uplifting-components-to-slds2/references/examples.md +443 -0
  50. package/skills/uplifting-components-to-slds2/references/migration-checklist.md +67 -0
  51. package/skills/uplifting-components-to-slds2/references/non-color-hooks-decision-guide.md +333 -0
  52. package/skills/uplifting-components-to-slds2/references/rule-lwc-token-to-slds-hook.md +135 -0
  53. package/skills/uplifting-components-to-slds2/references/rule-no-deprecated-tokens-slds1.md +211 -0
  54. package/skills/uplifting-components-to-slds2/references/rule-no-hardcoded-values.md +160 -0
  55. package/skills/uplifting-components-to-slds2/references/rule-no-slds-class-overrides.md +126 -0
  56. package/skills/{using-webapp-salesforce-data → using-ui-bundle-salesforce-data}/SKILL.md +52 -25
  57. package/skills/using-ui-bundle-salesforce-data/references/mutation-query-generation.md +140 -0
  58. package/skills/using-ui-bundle-salesforce-data/references/query-testing.md +78 -0
  59. package/skills/using-ui-bundle-salesforce-data/references/read-query-generation.md +307 -0
  60. package/skills/using-ui-bundle-salesforce-data/references/schema-introspection.md +53 -0
  61. package/skills/using-ui-bundle-salesforce-data/references/ui-bundle-integration.md +221 -0
  62. package/skills/{using-webapp-salesforce-data → using-ui-bundle-salesforce-data/scripts}/graphql-search.sh +75 -23
  63. package/skills/building-webapp-data-visualization/SKILL.md +0 -72
  64. package/skills/building-webapp-data-visualization/implementation/bar-line-chart.md +0 -316
  65. package/skills/building-webapp-data-visualization/implementation/dashboard-layout.md +0 -189
  66. package/skills/building-webapp-data-visualization/implementation/donut-chart.md +0 -181
  67. package/skills/building-webapp-data-visualization/implementation/stat-card.md +0 -150
  68. package/skills/building-webapp-react-components/SKILL.md +0 -96
  69. package/skills/configuring-webapp-csp-trusted-sites/SKILL.md +0 -90
  70. package/skills/configuring-webapp-metadata/SKILL.md +0 -158
  71. package/skills/creating-webapp/SKILL.md +0 -138
  72. package/skills/deploying-webapp-to-salesforce/SKILL.md +0 -226
  73. package/skills/installing-webapp-features/SKILL.md +0 -210
  74. /package/skills/{building-webapp-react-components → building-ui-bundle-frontend}/implementation/header-footer.md +0 -0
  75. /package/skills/{building-webapp-react-components → building-ui-bundle-frontend}/implementation/page.md +0 -0
  76. /package/skills/{configuring-webapp-csp-trusted-sites/implementation/metadata-format.md → generating-ui-bundle-metadata/implementation/csp-metadata-format.md} +0 -0
  77. /package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/references/style-tokens.md +0 -0
  78. /package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/references/troubleshooting.md +0 -0
@@ -4,7 +4,7 @@
4
4
 
5
5
  Apex doesn't allow real HTTP callouts in tests. Use `HttpCalloutMock` interface.
6
6
 
7
- ### Basic Mock Implementation
7
+ ### Basic Mock
8
8
 
9
9
  ```apex
10
10
  @isTest
@@ -33,34 +33,28 @@ public class MockHttpResponse implements HttpCalloutMock {
33
33
  ```apex
34
34
  @isTest
35
35
  static void shouldProcessApiResponse_WhenCalloutSucceeds() {
36
- // Given
37
36
  String mockResponse = '{"status": "success", "data": [{"id": "123"}]}';
38
37
  Test.setMock(HttpCalloutMock.class, new MockHttpResponse(200, mockResponse));
39
-
40
- // When
38
+
41
39
  Test.startTest();
42
40
  List<ExternalRecord> results = MyIntegrationService.fetchRecords();
43
41
  Test.stopTest();
44
-
45
- // Then
46
- System.assertEquals(1, results.size(), 'Should parse one record from response');
47
- System.assertEquals('123', results[0].externalId, 'Should extract correct ID');
42
+
43
+ Assert.areEqual(1, results.size(), 'Should parse one record from response');
44
+ Assert.areEqual('123', results[0].externalId, 'Should extract correct ID');
48
45
  }
49
46
 
50
47
  @isTest
51
48
  static void shouldHandleError_WhenCalloutFails() {
52
- // Given
53
49
  String errorResponse = '{"error": "Unauthorized"}';
54
50
  Test.setMock(HttpCalloutMock.class, new MockHttpResponse(401, errorResponse));
55
-
56
- // When
51
+
57
52
  Test.startTest();
58
53
  CalloutResult result = MyIntegrationService.fetchRecords();
59
54
  Test.stopTest();
60
-
61
- // Then
62
- System.assertEquals(false, result.isSuccess, 'Should indicate failure');
63
- System.assert(result.errorMessage.contains('Unauthorized'), 'Should capture error');
55
+
56
+ Assert.areEqual(false, result.isSuccess, 'Should indicate failure');
57
+ Assert.isTrue(result.errorMessage.contains('Unauthorized'), 'Should capture error');
64
58
  }
65
59
  ```
66
60
 
@@ -71,83 +65,101 @@ For services making multiple callouts:
71
65
  ```apex
72
66
  @isTest
73
67
  public class MultiRequestMock implements HttpCalloutMock {
74
-
68
+
75
69
  private Map<String, HttpResponse> endpointResponses;
76
-
70
+
77
71
  public MultiRequestMock(Map<String, HttpResponse> responses) {
78
72
  this.endpointResponses = responses;
79
73
  }
80
-
74
+
81
75
  public HTTPResponse respond(HTTPRequest req) {
82
76
  String endpoint = req.getEndpoint();
83
-
84
77
  for (String key : endpointResponses.keySet()) {
85
78
  if (endpoint.contains(key)) {
86
79
  return endpointResponses.get(key);
87
80
  }
88
81
  }
89
-
90
- // Default 404 if no match
91
82
  HttpResponse res = new HttpResponse();
92
83
  res.setStatusCode(404);
93
84
  res.setBody('{"error": "Not found"}');
94
85
  return res;
95
86
  }
96
87
  }
97
-
98
- // Usage:
99
- Map<String, HttpResponse> mocks = new Map<String, HttpResponse>();
100
-
101
- HttpResponse authResponse = new HttpResponse();
102
- authResponse.setStatusCode(200);
103
- authResponse.setBody('{"token": "abc123"}');
104
- mocks.put('/oauth/token', authResponse);
105
-
106
- HttpResponse dataResponse = new HttpResponse();
107
- dataResponse.setStatusCode(200);
108
- dataResponse.setBody('{"records": []}');
109
- mocks.put('/api/records', dataResponse);
110
-
111
- Test.setMock(HttpCalloutMock.class, new MultiRequestMock(mocks));
112
88
  ```
113
89
 
114
- ## StaticResourceCalloutMock
90
+ ### StaticResourceCalloutMock
115
91
 
116
- For complex response bodies, store JSON in Static Resources:
92
+ Use when response JSON is large or complex:
117
93
 
118
94
  ```apex
119
95
  @isTest
120
96
  static void shouldParseComplexResponse() {
121
97
  StaticResourceCalloutMock mock = new StaticResourceCalloutMock();
122
- mock.setStaticResource('TestApiResponse'); // Static Resource name
98
+ mock.setStaticResource('TestApiResponse');
123
99
  mock.setStatusCode(200);
124
100
  mock.setHeader('Content-Type', 'application/json');
125
-
126
101
  Test.setMock(HttpCalloutMock.class, mock);
127
-
102
+
128
103
  Test.startTest();
129
104
  Result r = MyService.callExternalApi();
130
105
  Test.stopTest();
131
-
132
- System.assertNotEquals(null, r, 'Should parse response');
106
+
107
+ Assert.isNotNull(r, 'Should parse response');
108
+ }
109
+ ```
110
+
111
+ ## SOSL Mocking
112
+
113
+ SOSL returns empty results in tests by default. Call `Test.setFixedSearchResults(List<Id>)` before the search:
114
+
115
+ ```apex
116
+ @isTest
117
+ static void shouldReturnSearchResults() {
118
+ Account acc = TestDataFactory.createAccount(true);
119
+ Test.setFixedSearchResults(new List<Id>{ acc.Id });
120
+
121
+ Test.startTest();
122
+ List<Account> results = MyService.searchAccounts('Test');
123
+ Test.stopTest();
124
+
125
+ Assert.areEqual(1, results.size(), 'Should return mocked search result');
126
+ }
127
+ ```
128
+
129
+ ## DML Mocking (Constructor Injection)
130
+
131
+ Design for testability — use a public constructor for production and a `@TestVisible private` constructor that accepts mock interfaces:
132
+
133
+ ```apex
134
+ public class MyService {
135
+ private IDML dmlHandler;
136
+
137
+ public MyService() {
138
+ this.dmlHandler = new DMLHandler();
139
+ }
140
+
141
+ @TestVisible
142
+ private MyService(IDML dmlHandler) {
143
+ this.dmlHandler = dmlHandler;
144
+ }
145
+
146
+ public void createRecords(List<Account> accounts) {
147
+ dmlHandler.doInsert(accounts);
148
+ }
133
149
  }
134
150
  ```
135
151
 
136
- ## Stub API (Enterprise Pattern)
152
+ ## Stub API (System.StubProvider)
137
153
 
138
- For mocking Apex class dependencies using `System.StubProvider`:
154
+ For mocking Apex class dependencies:
139
155
 
140
156
  ```apex
141
157
  @isTest
142
158
  public class MyServiceMock implements System.StubProvider {
143
-
159
+
144
160
  public Object handleMethodCall(
145
- Object stubbedObject,
146
- String stubbedMethodName,
147
- Type returnType,
148
- List<Type> paramTypes,
149
- List<String> paramNames,
150
- List<Object> args
161
+ Object stubbedObject, String stubbedMethodName, Type returnType,
162
+ List<Type> paramTypes, List<String> paramNames, List<Object> args
151
163
  ) {
152
164
  if (stubbedMethodName == 'getAccountData') {
153
165
  return new AccountData('Mock Account', 'Active');
@@ -156,42 +168,36 @@ public class MyServiceMock implements System.StubProvider {
156
168
  }
157
169
  }
158
170
 
159
- // Usage in test:
160
171
  @isTest
161
172
  static void shouldUseAccountData() {
162
173
  MyServiceMock mockProvider = new MyServiceMock();
163
- IMyService mockService = (IMyService)Test.createStub(IMyService.class, mockProvider);
164
-
165
- // Inject mock into class under test
174
+ IMyService mockService = (IMyService) Test.createStub(IMyService.class, mockProvider);
175
+
166
176
  MyController controller = new MyController(mockService);
167
-
177
+
168
178
  Test.startTest();
169
179
  String result = controller.displayAccountInfo();
170
180
  Test.stopTest();
171
-
172
- System.assert(result.contains('Mock Account'), 'Should use mocked data');
181
+
182
+ Assert.isTrue(result.contains('Mock Account'), 'Should use mocked data');
173
183
  }
174
184
  ```
175
185
 
176
- ## Email Mocking
186
+ ## Email Testing
177
187
 
178
- Apex sends real emails by default. Use limits to verify:
188
+ Apex doesn't actually send emails in tests. Use limits to verify:
179
189
 
180
190
  ```apex
181
191
  @isTest
182
192
  static void shouldSendEmail_WhenTriggered() {
183
193
  Integer emailsBefore = Limits.getEmailInvocations();
184
-
194
+
185
195
  Test.startTest();
186
196
  MyService.sendNotification(testContact);
187
197
  Test.stopTest();
188
-
189
- // Verify email was queued (not actually sent in tests)
190
- System.assertEquals(
191
- emailsBefore + 1,
192
- Limits.getEmailInvocations(),
193
- 'One email should be sent'
194
- );
198
+
199
+ Assert.areEqual(emailsBefore + 1, Limits.getEmailInvocations(),
200
+ 'One email should be sent');
195
201
  }
196
202
  ```
197
203
 
@@ -201,19 +207,14 @@ static void shouldSendEmail_WhenTriggered() {
201
207
  @isTest
202
208
  static void shouldPublishEvent_WhenRecordCreated() {
203
209
  Test.startTest();
204
-
205
- // Enable event delivery in test context
206
210
  Test.enableChangeDataCapture();
207
-
211
+
208
212
  Account acc = TestDataFactory.createAccount(true);
209
-
210
- // Deliver events
211
213
  Test.getEventBus().deliver();
212
-
213
214
  Test.stopTest();
214
215
 
215
216
  // Query platform event trigger results
216
217
  List<EventLog__c> logs = [SELECT Id FROM EventLog__c WHERE AccountId__c = :acc.Id];
217
- System.assertEquals(1, logs.size(), 'Event handler should create log record');
218
+ Assert.areEqual(1, logs.size(), 'Event handler should create log record');
218
219
  }
219
220
  ```
@@ -1,113 +1,18 @@
1
1
  # TestDataFactory Patterns
2
2
 
3
- ## Overview
3
+ For the base class template, see [assets/test-data-factory-template.cls](../assets/test-data-factory-template.cls).
4
4
 
5
- TestDataFactory is a centralized utility class for creating test records with sensible defaults. It ensures consistent test data across all test classes and reduces duplication.
5
+ ## Design Rules
6
6
 
7
- ## Base Template
8
-
9
- ```apex
10
- @isTest
11
- public class TestDataFactory {
12
-
13
- // ============ ACCOUNTS ============
14
-
15
- public static List<Account> createAccounts(Integer count, Boolean doInsert) {
16
- List<Account> accounts = new List<Account>();
17
- for (Integer i = 0; i < count; i++) {
18
- accounts.add(new Account(
19
- Name = 'Test Account ' + i,
20
- BillingStreet = '123 Test St',
21
- BillingCity = 'San Francisco',
22
- BillingState = 'CA',
23
- BillingPostalCode = '94105',
24
- BillingCountry = 'USA',
25
- Industry = 'Technology',
26
- Type = 'Customer'
27
- ));
28
- }
29
- if (doInsert) insert accounts;
30
- return accounts;
31
- }
32
-
33
- public static Account createAccount(Boolean doInsert) {
34
- return createAccounts(1, doInsert)[0];
35
- }
36
-
37
- // ============ CONTACTS ============
38
-
39
- public static List<Contact> createContacts(List<Account> accounts, Integer countPerAccount, Boolean doInsert) {
40
- List<Contact> contacts = new List<Contact>();
41
- Integer index = 0;
42
- for (Account acc : accounts) {
43
- for (Integer i = 0; i < countPerAccount; i++) {
44
- contacts.add(new Contact(
45
- FirstName = 'Test',
46
- LastName = 'Contact ' + index,
47
- Email = 'test.contact' + index + '@example.com',
48
- Phone = '555-000-' + String.valueOf(index).leftPad(4, '0'),
49
- AccountId = acc.Id
50
- ));
51
- index++;
52
- }
53
- }
54
- if (doInsert) insert contacts;
55
- return contacts;
56
- }
57
-
58
- // ============ OPPORTUNITIES ============
59
-
60
- public static List<Opportunity> createOpportunities(List<Account> accounts, Integer countPerAccount, Boolean doInsert) {
61
- List<Opportunity> opps = new List<Opportunity>();
62
- Integer index = 0;
63
- for (Account acc : accounts) {
64
- for (Integer i = 0; i < countPerAccount; i++) {
65
- opps.add(new Opportunity(
66
- Name = 'Test Opportunity ' + index,
67
- AccountId = acc.Id,
68
- StageName = 'Prospecting',
69
- CloseDate = Date.today().addDays(30),
70
- Amount = 10000 + (index * 1000)
71
- ));
72
- index++;
73
- }
74
- }
75
- if (doInsert) insert opps;
76
- return opps;
77
- }
78
-
79
- // ============ USERS ============
80
-
81
- public static User createUser(String profileName, Boolean doInsert) {
82
- Profile p = [SELECT Id FROM Profile WHERE Name = :profileName LIMIT 1];
83
- String uniqueKey = String.valueOf(DateTime.now().getTime());
84
-
85
- User u = new User(
86
- FirstName = 'Test',
87
- LastName = 'User ' + uniqueKey,
88
- Email = 'testuser' + uniqueKey + '@example.com',
89
- Username = 'testuser' + uniqueKey + '@example.com.test',
90
- Alias = 'tuser',
91
- TimeZoneSidKey = 'America/Los_Angeles',
92
- LocaleSidKey = 'en_US',
93
- EmailEncodingKey = 'UTF-8',
94
- LanguageLocaleKey = 'en_US',
95
- ProfileId = p.Id
96
- );
97
- if (doInsert) insert u;
98
- return u;
99
- }
100
-
101
- // ============ CUSTOM OBJECTS ============
102
-
103
- // Add methods for your custom objects following the same pattern:
104
- // public static List<MyObject__c> createMyObjects(Integer count, Boolean doInsert) { ... }
105
- }
106
- ```
7
+ 1. **Always accept a `doInsert` flag** — lets callers modify records before insert
8
+ 2. **Append loop index to all fields that participate in matching rules** — prevents `DUPLICATES_DETECTED` errors from active Duplicate Rules
9
+ 3. **Single-record methods delegate to bulk** — e.g., `createAccount(doInsert)` calls `createAccounts(1, doInsert)[0]`
10
+ 4. **Return created records** — enables chaining and further manipulation
11
+ 5. **Set all required fields** — include fields enforced by validation rules, not just schema-required fields
107
12
 
108
13
  ## Field Override Pattern
109
14
 
110
- Allow callers to override default values:
15
+ Allow callers to override default values without creating new factory methods:
111
16
 
112
17
  ```apex
113
18
  public static Account createAccount(Map<String, Object> fieldOverrides, Boolean doInsert) {
@@ -115,12 +20,9 @@ public static Account createAccount(Map<String, Object> fieldOverrides, Boolean
115
20
  Name = 'Test Account',
116
21
  Industry = 'Technology'
117
22
  );
118
-
119
- // Apply overrides
120
23
  for (String fieldName : fieldOverrides.keySet()) {
121
24
  acc.put(fieldName, fieldOverrides.get(fieldName));
122
25
  }
123
-
124
26
  if (doInsert) insert acc;
125
27
  return acc;
126
28
  }
@@ -132,23 +34,6 @@ Account acc = TestDataFactory.createAccount(new Map<String, Object>{
132
34
  }, true);
133
35
  ```
134
36
 
135
- ## Handling Required Fields and Validation Rules
136
-
137
- ```apex
138
- public static Account createAccountWithRequiredFields(Boolean doInsert) {
139
- Account acc = new Account(
140
- Name = 'Test Account',
141
- // Required custom fields
142
- External_Id__c = 'EXT-' + String.valueOf(DateTime.now().getTime()),
143
- // Fields required by validation rules
144
- Phone = '555-123-4567',
145
- Website = 'https://example.com'
146
- );
147
- if (doInsert) insert acc;
148
- return acc;
149
- }
150
- ```
151
-
152
37
  ## Record Type Support
153
38
 
154
39
  ```apex
@@ -157,7 +42,7 @@ public static Account createAccountByRecordType(String recordTypeName, Boolean d
157
42
  .getRecordTypeInfosByDeveloperName()
158
43
  .get(recordTypeName)
159
44
  .getRecordTypeId();
160
-
45
+
161
46
  Account acc = new Account(
162
47
  Name = 'Test Account',
163
48
  RecordTypeId = recordTypeId
@@ -167,10 +52,24 @@ public static Account createAccountByRecordType(String recordTypeName, Boolean d
167
52
  }
168
53
  ```
169
54
 
170
- ## Best Practices
55
+ ## Handling Duplicate Rules
171
56
 
172
- 1. **Always include doInsert parameter** - Allows flexibility for tests that need to modify records before insert
173
- 2. **Use unique identifiers** - Include index or timestamp in Name/Email fields to avoid duplicates
174
- 3. **Set all required fields** - Include all fields required by validation rules
175
- 4. **Return the created records** - Enables chaining and further manipulation
176
- 5. **Create bulk methods first** - Single record methods should call bulk methods with count=1
57
+ When unique field values alone are not sufficient, use `Database.insert()` with a `DuplicateRuleHeader`:
58
+
59
+ ```apex
60
+ public static List<Account> createAccountsAllowDuplicates(Integer count, Boolean doInsert) {
61
+ List<Account> accounts = new List<Account>();
62
+ for (Integer i = 0; i < count; i++) {
63
+ accounts.add(new Account(
64
+ Name = 'Test Account ' + i,
65
+ Phone = '555-000-' + String.valueOf(i).leftPad(4, '0')
66
+ ));
67
+ }
68
+ if (doInsert) {
69
+ Database.DMLOptions dml = new Database.DMLOptions();
70
+ dml.DuplicateRuleHeader.allowSave = true;
71
+ Database.insert(accounts, dml);
72
+ }
73
+ return accounts;
74
+ }
75
+ ```
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  name: generating-experience-react-site
3
- description: "Use this skill when users need to create or configure a Salesforce Digital Experience Site specifically for hosting a React web application. Trigger when users mention creating an Experience site for a React app, setting up a React site on Salesforce, configuring Network/CustomSite/DigitalExperience metadata for a web app, or deploying site infrastructure for a React application. Also trigger when users mention site URL path prefixes, app namespaces, appDevName, guest access configuration, DigitalExperienceConfig, DigitalExperienceBundle, or sfdc_cms__site content types in the context of React apps. Always use this skill for any React web application site creation or site infrastructure configuration work, even if the user just says \"create a site for my React app\" or \"set up the site for my web application.\""
3
+ description: "Use this skill when users need to create or configure a Salesforce Digital Experience Site specifically for hosting a React UI bundle. Trigger when users mention creating an Experience site for a React app, setting up a React site on Salesforce, configuring Network/CustomSite/DigitalExperience metadata for a UI bundle, or deploying site infrastructure for a React application. Also trigger when users mention site URL path prefixes, app namespaces, appDevName, guest access configuration, DigitalExperienceConfig, DigitalExperienceBundle, or sfdc_cms__site content types in the context of React apps. Always use this skill for any React UI bundle site creation or site infrastructure configuration work, even if the user just says \"create a site for my React app\" or \"set up the site for my UI bundle.\""
4
4
  ---
5
5
 
6
- # Digital Experience Site for React Web Applications
7
- Create and configure Digital Experience Sites that host React web applications on Salesforce. This skill generates the minimum necessary site infrastructure — Network, CustomSite, DigitalExperienceConfig, DigitalExperienceBundle, and the `sfdc_cms__site` content type — so a React app can be served from Salesforce.
6
+ # Digital Experience Site for React UI Bundles
7
+ Create and configure Digital Experience Sites that host React UI bundles on Salesforce. This skill generates the minimum necessary site infrastructure — Network, CustomSite, DigitalExperienceConfig, DigitalExperienceBundle, and the `sfdc_cms__site` content type — so a React app can be served from Salesforce.
8
8
 
9
- React sites differ from standard LWR sites: they don't need routes, views, theme layouts, or branding sets. The site acts as a thin container (`appContainer: true`) that delegates rendering to the React application referenced by `appSpace`.
9
+ React sites differ from standard LWR sites: they don't need routes, views, theme layouts, or branding sets. The site acts as a thin container (`appContainer: true`) that delegates rendering to the React UI bundle referenced by `appSpace`.
10
10
 
11
11
  ## Required Properties
12
12
  Resolve all five properties before generating any metadata. Each has a fallback chain — work through each option in order until a value is found.
@@ -14,9 +14,9 @@ Resolve all five properties before generating any metadata. Each has a fallback
14
14
  | Property | Format | How to Resolve |
15
15
  |----------|--------|----------------|
16
16
  | **siteName** | `UpperCamelCase` (e.g., `MyCommunity`) | Ask user or derive from context |
17
- | **siteUrlPathPrefix** | `kebab-case` (e.g., `my-community`) | User-provided, or convert siteName to kebab-case |
17
+ | **siteUrlPathPrefix** | `All lowercase` (e.g., `mycommunity`) | User-provided, or convert siteName to all lowercase with alphanumeric characters only |
18
18
  | **appNamespace** | String | `namespace` in `sfdx-project.json` → `sf data query -q "SELECT NamespacePrefix FROM Organization" --target-org ${usernameOrAlias}` → default `c` |
19
- | **appDevName** | String | `webApplication` metadata in the project → `sf data query -q "SELECT DeveloperName FROM WebApplication" --target-org ${usernameOrAlias}` → default to siteName |
19
+ | **appDevName** | String | `UIBundle` metadata in the project → `sf data query -q "SELECT DeveloperName FROM UIBundle" --target-org ${usernameOrAlias}` → default to siteName |
20
20
  | **enableGuestAccess** | Boolean | Ask user whether unauthenticated guest users can access site APIs → default `false` |
21
21
 
22
22
  The `appNamespace` and `appDevName` properties connect the site to the correct React application. Getting these wrong means the site deploys but shows a blank page, so take care to resolve them from real project data.
@@ -26,7 +26,7 @@ The `appNamespace` and `appDevName` properties connect the site to the correct R
26
26
  Determine values for all five properties before constructing anything. Use the resolution strategies in the table above, falling through each option until a value is found.
27
27
 
28
28
  ### Step 2: Create the Project Structure
29
- Call the `get_metadata_api_context` MCP tool to retrieve schemas for `Network`, `CustomSite`, `DigitalExperienceConfig`, and `DigitalExperienceBundle` metadata types. These schemas define the valid XML structure for each file.
29
+ Use available Salesforce metadata schema and field context for `Network`, `CustomSite`, `DigitalExperienceConfig`, and `DigitalExperienceBundle` to ensure each file uses valid structure.
30
30
 
31
31
  Create any files and directories that don't already exist, using these paths:
32
32
 
@@ -63,7 +63,7 @@ Use the default templates in the docs below. Values in `{braces}` are resolved p
63
63
  - Read entire file contents, replace placeholders (e.g. `{siteName}`) with the resolved values, then use the expanded templates to populate the metadata XML/JSON content.
64
64
 
65
65
  ### Step 4: Resolve Additional Configurations
66
- Address any extra configurations the user requests. Use the schemas returned by `get_metadata_api_context` in Step 2 to understand each field's purpose, and update only the minimum necessary fields.
66
+ Address any extra configurations the user requests. Use the metadata sections and field context identified in Step 2 to understand each fields purpose and constraints, then update only the minimum necessary fields.
67
67
 
68
68
  ## Verification Checklist
69
69
  Before deploying, confirm:
@@ -71,7 +71,7 @@ Before deploying, confirm:
71
71
  - [ ] All five required properties are resolved
72
72
  - [ ] All metadata directories and files exist per the project structure
73
73
  - [ ] All metadata fields are populated per the templates and user requests
74
- - [ ] `appSpace` in `content.json` matches an existing `WebApplication` metadata record
74
+ - [ ] `appSpace` in `content.json` matches an existing `UIBundle` metadata record
75
75
  - [ ] Deployment validates successfully:
76
76
  ```bash
77
77
  sf project deploy validate --metadata Network CustomSite DigitalExperienceConfig DigitalExperienceBundle DigitalExperience --target-org ${usernameOrAlias}
@@ -3,7 +3,7 @@
3
3
  ## Purpose
4
4
  These configuration files create **net-new, default** DigitalExperience content records (`sfdc_cms__site` type) for a Digital Experience React Site. They are not intended to edit or modify existing DigitalExperience content. Use these templates only when provisioning a brand-new React site.
5
5
 
6
- The `appContainer: true` and `appSpace` fields in `content.json` are what make this a React site rather than a standard LWR site. The `appSpace` value follows the format `{namespace}__{developerName}` and must match a deployed `WebApplication` metadata record.
6
+ The `appContainer: true` and `appSpace` fields in `content.json` are what make this a React site rather than a standard LWR site. The `appSpace` value follows the format `{namespace}__{developerName}` and must match a deployed `UIBundle` metadata record.
7
7
 
8
8
  ## File Location
9
9
  The DigitalExperience directory contains only `_meta.json` and `content.json`. Do not create any directories other than `sfdc_cms__site` inside the bundle.
@@ -43,20 +43,36 @@ sf template generate flexipage \
43
43
  --output-dir force-app/main/default/flexipages
44
44
  ```
45
45
 
46
- **Note:** If the `sf template generate flexipage` command fails, recommend users upgrade to the latest version of the Salesforce CLI:
47
- ```bash
48
- npm install -g @salesforce/cli@latest
49
- ```
46
+ **CRITICAL:** If the `sf template generate flexipage` command fails, **STOP**.
50
47
 
51
- #### **Field Selection Guidelines**
52
- - **Validate fields exist**: Use MCP tools or describe commands to discover available fields for the object before specifying them in the command
53
- - **Prefer compound fields**: Use `Name` (not `FirstName`/`LastName`), `BillingAddress` (not `BillingStreet`/`BillingCity`/`BillingState`), `MailingAddress`, etc. when available
48
+ 1. Install the templates plugin:
49
+ ```bash
50
+ sf plugins install templates
51
+ ```
52
+ 2. Retry the `sf template generate flexipage` command
53
+ 3. Verify the FlexiPage XML file was created
54
+
55
+ Do NOT continue to Step 2 until the template command succeeds. The generated XML is required for the entire workflow.
54
56
 
55
57
  #### **Template-specific requirements**
56
- - **RecordPage**: Requires `--sobject` (e.g., Account, Custom_Object__c)
57
- - **RecordPage**: Requires `--primary-field` and `--secondary-fields` for dynamic highlights, `--detail-fields` for full record details. Use the most important identifying field as primary, e.g. Name. Use the secondary fields (max 12, recommended 4-6) to show a summary of the record. Use detail fields to show the full details of the record.
58
- - **AppPage**: No additional requirements
59
- - **HomePage**: No additional requirements
58
+
59
+ **RecordPage:**
60
+ - Requires `--sobject` (e.g., Account, Custom_Object__c)
61
+ - Requires field parameters:
62
+ - `--primary-field`: Most important identifying field (e.g., Name)
63
+ - `--secondary-fields`: Record summary (recommended 4-6, max 12)
64
+ - `--detail-fields`: Full record details, including required fields (e.g., Name)
65
+
66
+ **AppPage:**
67
+ - No additional requirements
68
+
69
+ **HomePage:**
70
+ - No additional requirements
71
+
72
+ #### **Field Selection Rules**
73
+ - **Validate fields exist**: Use MCP tools or describe commands to discover available fields for the object before specifying them in the command
74
+ - **Prefer compound fields**: Use `Name` (not `FirstName`/`LastName`), `BillingAddress` (not `BillingStreet`/`BillingCity`/`BillingState`), `MailingAddress`, etc. when available
75
+ - **Include required fields in detail-fields**: Always include object required fields (like `Name`) in the `--detail-fields` parameter, even if they're also used in `--primary-field` or `--secondary-fields`
60
76
 
61
77
  #### **What you get**
62
78
  - Valid FlexiPage XML with correct structure
@@ -238,7 +254,7 @@ Every fieldInstance requires:
238
254
 
239
255
  ### "We couldn't retrieve or load the information on the field"
240
256
  **Cause:** Invalid field API name - field doesn't exist on the object or has incorrect spelling
241
- **Fix:** Use MCP tools or describe commands to discover valid fields, then update the field reference (see Field Selection Guidelines)
257
+ **Fix:** Use MCP tools or describe commands to discover valid fields, then update the field reference (see Field Selection Rules)
242
258
 
243
259
  ### "Invalid field reference"
244
260
  **Cause:** Used `ObjectName.Field` instead of `Record.Field`
@@ -0,0 +1,45 @@
1
+ ---
2
+ name: generating-ui-bundle-features
3
+ description: "Search and install pre-built features into Salesforce React UI bundles — authentication, shadcn, search, navigation, GraphQL, Agentforce AI, and more. Use whenever searching for or installing features. Always check for an existing feature before building from scratch. Triggers on: install feature, add authentication, add shadcn, add feature, search features, list features."
4
+ ---
5
+
6
+ # UI Bundle Features
7
+
8
+ ## Installing Pre-built Features
9
+
10
+ Always check for an existing feature before building something from scratch. The features CLI installs pre-built, tested packages into Salesforce UI bundles — from foundational UI libraries (shadcn/ui) to full-stack capabilities (authentication, search, navigation, GraphQL, Agentforce AI).
11
+
12
+ ### Workflow
13
+
14
+ 1. **Search project code first** — check `src/` for existing implementations before installing anything. Scope searches to `src/` to avoid matching `node_modules/` or `dist/`.
15
+
16
+ 2. **Search available features** — use `npx @salesforce/ui-bundle-features list` with `--search <query>` to filter by keyword. Use `--verbose` for full descriptions.
17
+
18
+ 3. **Describe a feature** — use `npx @salesforce/ui-bundle-features describe <feature>` to see components, dependencies, copy operations, and example files.
19
+
20
+ 4. **Install** — use `npx @salesforce/ui-bundle-features install <feature> --ui-bundle-dir <name>`. Key options:
21
+ - `--dry-run` to preview changes
22
+ - `--yes` for non-interactive mode (skips conflicts)
23
+ - `--on-conflict error` to detect conflicts, then `--conflict-resolution <file>` to resolve them
24
+
25
+ If no matching feature is found, ask the user before building a custom implementation — a relevant feature may exist under a different name.
26
+
27
+ ### Conflict Handling
28
+
29
+ In non-interactive environments, use the two-pass approach: first run with `--on-conflict error` to detect conflicts, then create a resolution JSON file (`{ "path": "skip" | "overwrite" }`) and re-run with `--conflict-resolution`.
30
+
31
+ ### Post-install: Integrating Example Files
32
+
33
+ Features may include `__example__` files showing integration patterns. For each:
34
+
35
+ 1. Read the example file to understand the pattern
36
+ 2. Read the target file (shown in `describe` output)
37
+ 3. Apply the pattern from the example into the target
38
+ 4. Delete the example file after successful integration
39
+
40
+ ### Hint Placeholders
41
+
42
+ Some copy paths use `<descriptive-name>` placeholders (e.g., `<desired-page-with-search-input>`) that the CLI does not resolve. After installation, rename or relocate these files to the intended target, or integrate their patterns into an existing file.
43
+
44
+
45
+