chore: sync React B2E & B2X (1.120.6) with uiBundle lockfiles and gitignore @W-21940169@ (#179)

chore: sync React B2E (1.120.6) & B2X (1.120.6) samples with lockfiles and gitignore

Add package-lock.json generation for uiBundle directories and .gitignore
writing to the sync scripts so they persist across syncs.

Co-authored-by: gbockus-sf <76090802+gbockus-sf@users.noreply.github.com>
This commit is contained in:
k-j-kim 2026-04-08 10:00:47 -07:00 committed by GitHub
parent eaf5acdb7f
commit 574d8dcca2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 35428 additions and 85 deletions

44
package-lock.json generated
View File

@ -1,16 +1,16 @@
{
"name": "@salesforce/afv-skills",
"version": "1.6.7",
"version": "1.6.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@salesforce/afv-skills",
"version": "1.6.7",
"version": "1.6.8",
"license": "CC-BY-NC-4.0",
"devDependencies": {
"@salesforce/ui-bundle-template-app-react-sample-b2e": "^1.120.3",
"@salesforce/ui-bundle-template-app-react-sample-b2x": "^1.120.3",
"@salesforce/ui-bundle-template-app-react-sample-b2e": "^1.120.6",
"@salesforce/ui-bundle-template-app-react-sample-b2x": "^1.120.6",
"@salesforce/webapp-template-app-react-sample-b2e-experimental": "^1.117.1",
"@salesforce/webapp-template-app-react-sample-b2x-experimental": "^1.117.1",
"@types/js-yaml": "^4.0.9",
@ -690,22 +690,22 @@
}
},
"node_modules/@salesforce/sdk-core": {
"version": "1.120.3",
"resolved": "https://registry.npmjs.org/@salesforce/sdk-core/-/sdk-core-1.120.3.tgz",
"integrity": "sha512-fzzQwWvN8GCK5peEf37sedsb1gBIHwiAOWXKrohPcwRjUd1wfhMoe45LvV8bOMgO4CBjKrNA21sf0D3FOLtzdQ==",
"version": "1.120.6",
"resolved": "https://registry.npmjs.org/@salesforce/sdk-core/-/sdk-core-1.120.6.tgz",
"integrity": "sha512-yqd3Cf5hPzMJTy9GEsy9N41oFFIrcAOU/QuDDZ+nAmmXNmprM3kt93JCdhHr2L1P6JP2SHMhNMJQ1lKmSPl/uw==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt"
},
"node_modules/@salesforce/sdk-data": {
"version": "1.120.3",
"resolved": "https://registry.npmjs.org/@salesforce/sdk-data/-/sdk-data-1.120.3.tgz",
"integrity": "sha512-pw7Bx7Zo0K+apB+waxgbGpGigIsuoPmqDxbMIEdTepW/2/oxFx7RH7edG2qjjt96oHQcTCyPbNdzY/6HjC6TLw==",
"version": "1.120.6",
"resolved": "https://registry.npmjs.org/@salesforce/sdk-data/-/sdk-data-1.120.6.tgz",
"integrity": "sha512-ejVCIU3EJTt2uv1kKx/3Jg98ESHbuG5zh+sn1Z4rfxtU/jVzeLZQyS3dIjw1krhSFIl9He+JU5EW8/s5W6aQSA==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@conduit-client/service-fetch-network": "3.17.0",
"@conduit-client/utils": "3.17.0",
"@salesforce/sdk-core": "^1.120.3"
"@salesforce/sdk-core": "^1.120.6"
}
},
"node_modules/@salesforce/ts-types": {
@ -719,14 +719,14 @@
}
},
"node_modules/@salesforce/ui-bundle": {
"version": "1.120.3",
"resolved": "https://registry.npmjs.org/@salesforce/ui-bundle/-/ui-bundle-1.120.3.tgz",
"integrity": "sha512-rZAWPKRxTWwFbwudESFqromtF9OFIH7LcqrkJMkq0Wfdt6mRh3t+jVNg8z3Fj4aQf/BqpY4efLofmSnAhjABAQ==",
"version": "1.120.6",
"resolved": "https://registry.npmjs.org/@salesforce/ui-bundle/-/ui-bundle-1.120.6.tgz",
"integrity": "sha512-sIHzbSMkMwvhh7IzAO/TzTMjIdphf8dTu+A4+LHobQ0XLp9Y0dii1ckMhLTb/i97qdCDq+aeIIKVQpRedgjsTw==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@salesforce/core": "^8.23.4",
"@salesforce/sdk-data": "^1.120.3",
"@salesforce/sdk-data": "^1.120.6",
"micromatch": "^4.0.8",
"path-to-regexp": "^8.3.0"
},
@ -735,20 +735,20 @@
}
},
"node_modules/@salesforce/ui-bundle-template-app-react-sample-b2e": {
"version": "1.120.3",
"resolved": "https://registry.npmjs.org/@salesforce/ui-bundle-template-app-react-sample-b2e/-/ui-bundle-template-app-react-sample-b2e-1.120.3.tgz",
"integrity": "sha512-1Fn0gPRe4p91+nUptRgN1AcHCuYLe+ooQBSjJPdq/hVmU6E2oISIpntgDj7Z2FIc6O+F3DZDkGXyhcDs0B8j7w==",
"version": "1.120.6",
"resolved": "https://registry.npmjs.org/@salesforce/ui-bundle-template-app-react-sample-b2e/-/ui-bundle-template-app-react-sample-b2e-1.120.6.tgz",
"integrity": "sha512-/A31Oon0gRvs+kshC55T2VoaEqDNfnwsSfJNfCi8Y4Y2UeGuMNtd4tvRrahQ6+QBup4NWUdOQNIwJWaXUWDC4A==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@salesforce/ui-bundle": "^1.120.3",
"@salesforce/ui-bundle": "^1.120.6",
"sonner": "^1.7.0"
}
},
"node_modules/@salesforce/ui-bundle-template-app-react-sample-b2x": {
"version": "1.120.3",
"resolved": "https://registry.npmjs.org/@salesforce/ui-bundle-template-app-react-sample-b2x/-/ui-bundle-template-app-react-sample-b2x-1.120.3.tgz",
"integrity": "sha512-waF/yPAjnsM5y7RhcypNaj+WPiy1fD5FdYXPSQyZqngfc/SyVEqtuDr8J7l+pLjkyLoAAIBCRAA0I/uEQfxArQ==",
"version": "1.120.6",
"resolved": "https://registry.npmjs.org/@salesforce/ui-bundle-template-app-react-sample-b2x/-/ui-bundle-template-app-react-sample-b2x-1.120.6.tgz",
"integrity": "sha512-j0joo4RYXM9OnVpZX4vb8nI/AMw9Uxz6xzm9o8KQ8WkU79P20xBdRTKBS1H379rpWV0KcxaMFBx6qGV82jgLNA==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.txt"
},

View File

@ -11,8 +11,8 @@
"registry": "https://registry.npmjs.org"
},
"devDependencies": {
"@salesforce/ui-bundle-template-app-react-sample-b2e": "^1.120.3",
"@salesforce/ui-bundle-template-app-react-sample-b2x": "^1.120.3",
"@salesforce/ui-bundle-template-app-react-sample-b2e": "^1.120.6",
"@salesforce/ui-bundle-template-app-react-sample-b2x": "^1.120.6",
"@salesforce/webapp-template-app-react-sample-b2e-experimental": "^1.117.1",
"@salesforce/webapp-template-app-react-sample-b2x-experimental": "^1.117.1",
"@types/js-yaml": "^4.0.9",

View File

@ -0,0 +1,45 @@
# This file is used for Git repositories to specify intentionally untracked files that Git should ignore.
# If you are not using git, you can delete this file. For more information see: https://git-scm.com/docs/gitignore
# For useful gitignore templates see: https://github.com/github/gitignore
# Salesforce cache
.sf/
.sfdx/
.localdevserver/
deploy-options.json
# LWC VSCode autocomplete
**/lwc/jsconfig.json
# LWC Jest coverage reports
coverage/
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Dependency directories
node_modules/
# Eslint cache
.eslintcache
# MacOS system files
.DS_Store
# Windows system files
Thumbs.db
ehthumbs.db
[Dd]esktop.ini
$RECYCLE.BIN/
# Local environment variables
.env
# Python Salesforce Functions
**/__pycache__/
**/.venv/
**/venv/

View File

@ -1 +1 @@
1.120.3
1.120.6

View File

@ -3,6 +3,33 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.120.6](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.120.5...v1.120.6) (2026-04-07)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.120.5](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.120.4...v1.120.5) (2026-04-07)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.120.4](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.120.3...v1.120.4) (2026-04-07)
### Bug Fixes
* make tenant owner of own record ([#414](https://github.com/salesforce-experience-platform-emu/webapps/issues/414)) ([bf1f289](https://github.com/salesforce-experience-platform-emu/webapps/commit/bf1f289a8d56a381adde51e642e67db38099f193))
## [1.120.3](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.120.2...v1.120.3) (2026-04-02)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project

View File

@ -16,6 +16,7 @@ A property management sample React UI Bundle for the Salesforce platform. Demons
- [6. Generate GraphQL Types](#6-generate-graphql-types)
- [7. Rebuild the UI Bundle](#7-rebuild-the-ui-bundle)
- [8. Deploy the UI Bundle](#8-deploy-the-ui-bundle)
- [9. Agentforce Conversation Client](#9-agentforce-conversation-client)
5. [Local Development](#local-development)
6. [Resources](#resources)
@ -23,13 +24,14 @@ A property management sample React UI Bundle for the Salesforce platform. Demons
## What's Included
| Path | Description |
| --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `force-app/main/default/uiBundles/propertymanagementapp/` | React UI Bundle (source, config, tests) |
| `force-app/main/default/objects/` | 17 custom objects — `Agent__c`, `Application__c`, `KPI_Snapshot__c`, `Lease__c`, `Maintenance_Request__c`, `Maintenance_Worker__c`, `Notification__c`, `Payment__c`, `Property__c`, `Property_Cost__c`, `Property_Feature__c`, `Property_Image__c`, `Property_Listing__c`, `Property_Management_Company__c`, `Property_Owner__c`, `Property_Sale__c`, `Tenant__c` |
| `force-app/main/default/layouts/` | Page layouts for each custom object |
| `force-app/main/default/permissionsets/` | `Property_Management_Access` permission set — full CRUD access to all custom objects |
| `force-app/main/default/data/` | Sample data (JSON) for all objects, importable via `sf data import tree` |
```
force-app/main/default/
├── uiBundles/ # React UI Bundle (source, config, tests)
├── objects/ # 17 Custom Objects (Property, Tenant, Lease, etc.)
├── layouts/ # Page layouts for all custom objects
├── permissionsets/ # Property_Management_Access (Full CRUD)
└── data/ # Sample JSON data (use: sf data import tree)
```
---
@ -224,6 +226,13 @@ Once the build is complete, deploy the UI Bundle to your org:
sf project deploy start --source-dir force-app/main/default/uiBundles --target-org <alias>
```
### 9. Agentforce Conversation Client
The sample app includes an Agentforce Conversation Client (ACC) placeholder component in `applayout.tsx`. The ACC component requires a valid `agentId` for an agent that exists in the org. The options to create an agent specific to this use-case are:
- **Agentforce Vibes:** Generate agent metadata with the extension, then deploy it to the org.
- **Agentforce Builder:** Create the agent in the UI. See [Set Up Your Agent](https://help.salesforce.com/s/articleView?id=ai.agent_parent_setup.htm&type=5).
---
## Local Development

View File

@ -0,0 +1,9 @@
{
"permsetAssignments": {
"defaultAssignee": "skip",
"assignments": {
"Property_Management_Access": { "assignee": "currentUser" },
"Tenant_Maintenance_Access": { "assignee": "currentUser" }
}
}
}

View File

@ -18,8 +18,8 @@
"graphql:schema": "node scripts/get-graphql-schema.mjs"
},
"dependencies": {
"@salesforce/sdk-data": "^1.120.3",
"@salesforce/ui-bundle": "^1.120.3",
"@salesforce/sdk-data": "^1.120.6",
"@salesforce/ui-bundle": "^1.120.6",
"@tailwindcss/vite": "^4.1.17",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@ -46,7 +46,7 @@
"@graphql-eslint/eslint-plugin": "^4.1.0",
"@graphql-tools/utils": "^11.0.0",
"@playwright/test": "^1.49.0",
"@salesforce/vite-plugin-ui-bundle": "^1.120.3",
"@salesforce/vite-plugin-ui-bundle": "^1.120.6",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.1.0",
"@testing-library/user-event": "^14.5.2",
@ -69,5 +69,8 @@
"vite": "^7.2.4",
"vite-plugin-graphql-codegen": "^3.6.3",
"vitest": "^4.0.17"
},
"overrides": {
"lodash": "^4.18.1"
}
}

View File

@ -113,6 +113,7 @@ async function ensureTenantForApprovedApplication(applicationId: string): Promis
Property__c: propertyId,
User_Status__c: "Tenant",
Status__c: "Active",
OwnerId: userId,
};
if (startDate) {
tenantFields.Start_Date__c = String(startDate);

View File

@ -1,12 +1,12 @@
{
"name": "@salesforce/webapp-template-base-sfdx-project-experimental",
"version": "1.120.3",
"version": "1.120.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@salesforce/webapp-template-base-sfdx-project-experimental",
"version": "1.120.3",
"version": "1.120.6",
"license": "SEE LICENSE IN LICENSE.txt",
"devDependencies": {
"@lwc/eslint-plugin-lwc": "^3.3.0",

View File

@ -1,6 +1,6 @@
{
"name": "@salesforce/ui-bundle-template-base-sfdx-project",
"version": "1.120.3",
"version": "1.120.6",
"description": "Base SFDX project template",
"license": "SEE LICENSE IN LICENSE.txt",
"publishConfig": {

View File

@ -0,0 +1,6 @@
{
"permsetAssignments": {
"defaultAssignee": "currentUser",
"assignments": {}
}
}

View File

@ -14,10 +14,22 @@
* 1. login sf org login web only if org not already connected (skip with --skip-login)
* 2. uiBundle (all UI bundles) npm install && npm run build so dist exists for deploy (skip with --skip-ui-bundle-build)
* 3. deploy sf project deploy start --target-org <alias> (requires dist for entity deployment)
* 4. permset sf org assign permset for each *.permissionset-meta.xml (skip with --skip-permset; override via --permset-name)
* 4. permset assign permsets per org-setup.config.json (skip with --skip-permset; override via --permset-name)
* 5. data prepare unique fields + sf data import tree (skipped if no data dir/plan)
* 6. graphql (in UI bundle) npm run graphql:schema then npm run graphql:codegen
* 7. dev (in UI bundle) npm run dev launch dev server (skip with --skip-dev)
*
* Permset assignment config (scripts/org-setup.config.json):
* {
* "permsetAssignments": {
* "defaultAssignee": "currentUser",
* "assignments": {
* "Guest_Permset": { "assignee": "guest@mysite.example.com" },
* "Internal_Only": { "assignee": "skip" }
* }
* }
* }
* Assignee values: "currentUser" (default), "skip", or a specific username.
*/
import { spawnSync } from 'node:child_process';
@ -127,6 +139,20 @@ Options:
--skip-dev Do not launch the dev server at the end
-y, --yes Skip interactive step picker; run all enabled steps immediately
-h, --help Show this help
Permset config (scripts/org-setup.config.json):
Control per-permset assignment via a config file. Example:
{
"permsetAssignments": {
"defaultAssignee": "currentUser",
"assignments": {
"Guest_Permset": { "assignee": "guest@mysite.example.com" },
"Internal_Only": { "assignee": "skip" }
}
}
}
Assignee values: "currentUser" (default), "skip", or a specific username.
Without this file, all discovered permsets are assigned to the current user.
`);
process.exit(0);
}
@ -181,6 +207,51 @@ function discoverPermissionSetNames() {
return names.sort();
}
/**
* Load permset assignment configuration from org-setup.config.json.
*
* Config shape:
* {
* "permsetAssignments": {
* "defaultAssignee": "currentUser", // "currentUser" (default) or "skip"
* "assignments": {
* "My_Guest_Permset": { "assignee": "guest@mysite.example.com" },
* "Internal_Only": { "assignee": "skip" }
* }
* }
* }
*
* Assignee values:
* "currentUser" assign to the user running the script (default)
* "skip" do not assign this permset
* "<username>" assign to a specific user via --on-behalf-of
*
* Returns { defaultAssignee: string, assignments: Record<string, { assignee: string }> }
*/
function loadPermsetConfig() {
const configPath = resolve(__dirname, 'org-setup.config.json');
const defaults = { defaultAssignee: 'currentUser', assignments: {} };
if (!existsSync(configPath)) return defaults;
try {
const raw = JSON.parse(readFileSync(configPath, 'utf8'));
const section = raw?.permsetAssignments;
if (!section) return defaults;
return {
defaultAssignee: section.defaultAssignee || 'currentUser',
assignments: section.assignments || {},
};
} catch (err) {
console.warn(`Warning: failed to parse org-setup.config.json: ${err.message}; using defaults.`);
return defaults;
}
}
/** Resolve the effective assignee for a given permset name. */
function resolveAssignee(permsetName, permsetConfig) {
const override = permsetConfig.assignments[permsetName];
return override?.assignee || permsetConfig.defaultAssignee;
}
function isOrgConnected(targetOrg) {
const result = spawnSync('sf', ['org', 'display', '--target-org', targetOrg, '--json'], {
cwd: ROOT,
@ -422,28 +493,35 @@ async function main() {
}
if (!skipPermset) {
const permsetConfig = loadPermsetConfig();
if (permsetNames.length === 0) {
console.log('\n--- Assign permission sets ---');
console.log('No permission sets found under permissionsets/ and none passed via --permset-name; skipping.');
} else {
console.log('\n--- Assign permission sets ---');
for (const permsetName of permsetNames) {
const permsetResult = spawnSync(
'sf',
['org', 'assign', 'permset', '--name', permsetName, '--target-org', targetOrg],
{
cwd: ROOT,
stdio: 'pipe',
shell: true,
}
);
const assignee = resolveAssignee(permsetName, permsetConfig);
if (assignee === 'skip') {
console.log(`Permission set "${permsetName}" — skipped (config).`);
continue;
}
const sfArgs = ['org', 'assign', 'permset', '--name', permsetName, '--target-org', targetOrg];
if (assignee !== 'currentUser') {
sfArgs.push('--on-behalf-of', assignee);
}
const assigneeLabel = assignee === 'currentUser' ? 'current user' : assignee;
const permsetResult = spawnSync('sf', sfArgs, {
cwd: ROOT,
stdio: 'pipe',
shell: true,
});
if (permsetResult.status === 0) {
console.log(`Permission set "${permsetName}" assigned.`);
console.log(`Permission set "${permsetName}" assigned to ${assigneeLabel}.`);
} else {
const out =
(permsetResult.stderr?.toString() || '') + (permsetResult.stdout?.toString() || '');
if (out.includes('Duplicate') && out.includes('PermissionSet')) {
console.log(`Permission set "${permsetName}" already assigned; skipping.`);
console.log(`Permission set "${permsetName}" already assigned to ${assigneeLabel}; skipping.`);
} else if (out.includes('not found') && out.includes('target org')) {
console.log(`Permission set "${permsetName}" not in org; skipping.`);
} else {

View File

@ -0,0 +1,45 @@
# This file is used for Git repositories to specify intentionally untracked files that Git should ignore.
# If you are not using git, you can delete this file. For more information see: https://git-scm.com/docs/gitignore
# For useful gitignore templates see: https://github.com/github/gitignore
# Salesforce cache
.sf/
.sfdx/
.localdevserver/
deploy-options.json
# LWC VSCode autocomplete
**/lwc/jsconfig.json
# LWC Jest coverage reports
coverage/
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Dependency directories
node_modules/
# Eslint cache
.eslintcache
# MacOS system files
.DS_Store
# Windows system files
Thumbs.db
ehthumbs.db
[Dd]esktop.ini
$RECYCLE.BIN/
# Local environment variables
.env
# Python Salesforce Functions
**/__pycache__/
**/.venv/
**/venv/

View File

@ -1 +1 @@
1.120.3
1.120.6

View File

@ -3,6 +3,33 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.120.6](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.120.5...v1.120.6) (2026-04-07)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.120.5](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.120.4...v1.120.5) (2026-04-07)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
## [1.120.4](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.120.3...v1.120.4) (2026-04-07)
### Bug Fixes
* make tenant owner of own record ([#414](https://github.com/salesforce-experience-platform-emu/webapps/issues/414)) ([bf1f289](https://github.com/salesforce-experience-platform-emu/webapps/commit/bf1f289a8d56a381adde51e642e67db38099f193))
## [1.120.3](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.120.2...v1.120.3) (2026-04-02)
**Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project

View File

@ -29,20 +29,25 @@ A property rental sample React UI Bundle for Salesforce Experience Cloud. Demons
## What's Included
| Path | Description |
| ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `force-app/main/default/uiBundles/propertyrentalapp/` | React UI Bundle (source, config, tests) |
| `force-app/main/default/objects/` | 17 custom objects — `Agent__c`, `Application__c`, `KPI_Snapshot__c`, `Lease__c`, `Maintenance_Request__c`, `Maintenance_Worker__c`, `Notification__c`, `Payment__c`, `Property__c`, `Property_Cost__c`, `Property_Feature__c`, `Property_Image__c`, `Property_Listing__c`, `Property_Management_Company__c`, `Property_Owner__c`, `Property_Sale__c`, `Tenant__c` |
| `force-app/main/default/layouts/` | Page layouts for each custom object |
| `force-app/main/default/permissionsets/` | `Property_Management_Access` (full admin access) and `Tenant_Maintenance_Access` (scoped tenant access) |
| `force-app/main/default/classes/` | Apex classes — `MaintenanceRequestTriggerHandler`, `TenantTriggerHandler`, `UIBundleAuthUtils`, `UIBundleChangePassword`, `UIBundleForgotPassword`, `UIBundleLogin`, `UIBundleAppRegistration` |
| `force-app/main/default/triggers/` | Apex triggers — `MaintenanceRequestTrigger`, `TenantTrigger` |
| `force-app/main/default/cspTrustedSites/` | CSP trusted sites for external resources (Google Fonts, Pexels, Unsplash, GitHub Avatars, OpenStreetMap, Open-Meteo) |
| `force-app/main/default/data/` | Sample data (JSON) for all objects, importable via `sf data import tree` |
| `force-app/main/default/digitalExperienceConfigs/` | Experience Cloud site configuration |
| `force-app/main/default/digitalExperiences/` | Experience Cloud site definition |
| `force-app/main/default/networks/` | Experience Cloud network metadata |
| `force-app/main/default/sites/` | Salesforce Sites configuration |
```
force-app/main/default/
├── classes/ # Apex Logic: Triggers, Auth, and Registration
│ ├── MaintenanceRequestTriggerHandler.cls
│ ├── TenantTriggerHandler.cls
│ └── UIBundle (Auth, Login, Registration, etc.)
├── cspTrustedSites/ # External resource security (Maps, Fonts, Avatars)
├── data/ # Sample JSON data for "sf data import tree"
├── digitalExperienceConfigs/ # Experience Cloud site configurations
├── digitalExperiences/ # Experience Cloud site definitions
├── layouts/ # Page layouts for all 17 custom objects
├── networks/ # Experience Cloud network metadata
├── objects/ # 17 Custom Objects (Property, Tenant, Lease, etc.)
├── permissionsets/ # Access: Property_Management (Admin) & Tenant (Scoped)
├── sites/ # Salesforce Sites configuration
├── triggers/ # Apex Triggers: MaintenanceRequest & Tenant
└── uiBundles/
└── propertyrentalapp/ # React UI Bundle (source, config, tests)
```
---
@ -462,7 +467,7 @@ By default, Salesforce does not expose any records to guest users. You must crea
2. Scroll to the **Property Sharing Rules** section and click **New**.
3. Configure the rule:
- **Label**: `Available Properties for Guest Users`
- **Rule Type**: Select **Based on criteria**
- **Rule Type**: Select **Guest user access, based on criteria**
- **Criteria**: `Status Equals Available` (adjust the field and value to match your data model)
- **Share with**: Select **Guests of the Property Rental App site** (the guest user group for your Experience Cloud site)
- **Access Level**: `Read Only`
@ -473,7 +478,7 @@ By default, Salesforce does not expose any records to guest users. You must crea
Repeat the same steps in the **Property Listing Sharing Rules** section:
- **Label**: `Available Property Listings for Guest Users`
- **Rule Type**: `Based on criteria`
- **Rule Type**: `Guest user access, based on criteria`
- **Criteria**: Match listings you want to expose (e.g., `Status Equals Active`)
- **Share with**: `Guests of the Property Rental App site`
- **Access Level**: `Read Only`

View File

@ -0,0 +1,9 @@
{
"permsetAssignments": {
"defaultAssignee": "skip",
"assignments": {
"Property_Management_Access": { "assignee": "currentUser" },
"Tenant_Maintenance_Access": { "assignee": "currentUser" }
}
}
}

View File

@ -18,8 +18,8 @@
"graphql:schema": "node scripts/get-graphql-schema.mjs"
},
"dependencies": {
"@salesforce/sdk-data": "^1.120.3",
"@salesforce/ui-bundle": "^1.120.3",
"@salesforce/sdk-data": "^1.120.6",
"@salesforce/ui-bundle": "^1.120.6",
"@tailwindcss/vite": "^4.1.17",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@ -50,7 +50,7 @@
"@graphql-eslint/eslint-plugin": "^4.1.0",
"@graphql-tools/utils": "^11.0.0",
"@playwright/test": "^1.49.0",
"@salesforce/vite-plugin-ui-bundle": "^1.120.3",
"@salesforce/vite-plugin-ui-bundle": "^1.120.6",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.1.0",
"@testing-library/user-event": "^14.5.2",
@ -73,5 +73,8 @@
"vite": "^7.2.4",
"vite-plugin-graphql-codegen": "^3.6.3",
"vitest": "^4.0.17"
},
"overrides": {
"lodash": "^4.18.1"
}
}

View File

@ -1,12 +1,12 @@
{
"name": "@salesforce/webapp-template-base-sfdx-project-experimental",
"version": "1.120.3",
"version": "1.120.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@salesforce/webapp-template-base-sfdx-project-experimental",
"version": "1.120.3",
"version": "1.120.6",
"license": "SEE LICENSE IN LICENSE.txt",
"devDependencies": {
"@lwc/eslint-plugin-lwc": "^3.3.0",

View File

@ -1,6 +1,6 @@
{
"name": "@salesforce/ui-bundle-template-base-sfdx-project",
"version": "1.120.3",
"version": "1.120.6",
"description": "Base SFDX project template",
"license": "SEE LICENSE IN LICENSE.txt",
"publishConfig": {

View File

@ -0,0 +1,6 @@
{
"permsetAssignments": {
"defaultAssignee": "currentUser",
"assignments": {}
}
}

View File

@ -14,10 +14,22 @@
* 1. login sf org login web only if org not already connected (skip with --skip-login)
* 2. uiBundle (all UI bundles) npm install && npm run build so dist exists for deploy (skip with --skip-ui-bundle-build)
* 3. deploy sf project deploy start --target-org <alias> (requires dist for entity deployment)
* 4. permset sf org assign permset for each *.permissionset-meta.xml (skip with --skip-permset; override via --permset-name)
* 4. permset assign permsets per org-setup.config.json (skip with --skip-permset; override via --permset-name)
* 5. data prepare unique fields + sf data import tree (skipped if no data dir/plan)
* 6. graphql (in UI bundle) npm run graphql:schema then npm run graphql:codegen
* 7. dev (in UI bundle) npm run dev launch dev server (skip with --skip-dev)
*
* Permset assignment config (scripts/org-setup.config.json):
* {
* "permsetAssignments": {
* "defaultAssignee": "currentUser",
* "assignments": {
* "Guest_Permset": { "assignee": "guest@mysite.example.com" },
* "Internal_Only": { "assignee": "skip" }
* }
* }
* }
* Assignee values: "currentUser" (default), "skip", or a specific username.
*/
import { spawnSync } from 'node:child_process';
@ -127,6 +139,20 @@ Options:
--skip-dev Do not launch the dev server at the end
-y, --yes Skip interactive step picker; run all enabled steps immediately
-h, --help Show this help
Permset config (scripts/org-setup.config.json):
Control per-permset assignment via a config file. Example:
{
"permsetAssignments": {
"defaultAssignee": "currentUser",
"assignments": {
"Guest_Permset": { "assignee": "guest@mysite.example.com" },
"Internal_Only": { "assignee": "skip" }
}
}
}
Assignee values: "currentUser" (default), "skip", or a specific username.
Without this file, all discovered permsets are assigned to the current user.
`);
process.exit(0);
}
@ -181,6 +207,51 @@ function discoverPermissionSetNames() {
return names.sort();
}
/**
* Load permset assignment configuration from org-setup.config.json.
*
* Config shape:
* {
* "permsetAssignments": {
* "defaultAssignee": "currentUser", // "currentUser" (default) or "skip"
* "assignments": {
* "My_Guest_Permset": { "assignee": "guest@mysite.example.com" },
* "Internal_Only": { "assignee": "skip" }
* }
* }
* }
*
* Assignee values:
* "currentUser" assign to the user running the script (default)
* "skip" do not assign this permset
* "<username>" assign to a specific user via --on-behalf-of
*
* Returns { defaultAssignee: string, assignments: Record<string, { assignee: string }> }
*/
function loadPermsetConfig() {
const configPath = resolve(__dirname, 'org-setup.config.json');
const defaults = { defaultAssignee: 'currentUser', assignments: {} };
if (!existsSync(configPath)) return defaults;
try {
const raw = JSON.parse(readFileSync(configPath, 'utf8'));
const section = raw?.permsetAssignments;
if (!section) return defaults;
return {
defaultAssignee: section.defaultAssignee || 'currentUser',
assignments: section.assignments || {},
};
} catch (err) {
console.warn(`Warning: failed to parse org-setup.config.json: ${err.message}; using defaults.`);
return defaults;
}
}
/** Resolve the effective assignee for a given permset name. */
function resolveAssignee(permsetName, permsetConfig) {
const override = permsetConfig.assignments[permsetName];
return override?.assignee || permsetConfig.defaultAssignee;
}
function isOrgConnected(targetOrg) {
const result = spawnSync('sf', ['org', 'display', '--target-org', targetOrg, '--json'], {
cwd: ROOT,
@ -422,28 +493,35 @@ async function main() {
}
if (!skipPermset) {
const permsetConfig = loadPermsetConfig();
if (permsetNames.length === 0) {
console.log('\n--- Assign permission sets ---');
console.log('No permission sets found under permissionsets/ and none passed via --permset-name; skipping.');
} else {
console.log('\n--- Assign permission sets ---');
for (const permsetName of permsetNames) {
const permsetResult = spawnSync(
'sf',
['org', 'assign', 'permset', '--name', permsetName, '--target-org', targetOrg],
{
cwd: ROOT,
stdio: 'pipe',
shell: true,
}
);
const assignee = resolveAssignee(permsetName, permsetConfig);
if (assignee === 'skip') {
console.log(`Permission set "${permsetName}" — skipped (config).`);
continue;
}
const sfArgs = ['org', 'assign', 'permset', '--name', permsetName, '--target-org', targetOrg];
if (assignee !== 'currentUser') {
sfArgs.push('--on-behalf-of', assignee);
}
const assigneeLabel = assignee === 'currentUser' ? 'current user' : assignee;
const permsetResult = spawnSync('sf', sfArgs, {
cwd: ROOT,
stdio: 'pipe',
shell: true,
});
if (permsetResult.status === 0) {
console.log(`Permission set "${permsetName}" assigned.`);
console.log(`Permission set "${permsetName}" assigned to ${assigneeLabel}.`);
} else {
const out =
(permsetResult.stderr?.toString() || '') + (permsetResult.stdout?.toString() || '');
if (out.includes('Duplicate') && out.includes('PermissionSet')) {
console.log(`Permission set "${permsetName}" already assigned; skipping.`);
console.log(`Permission set "${permsetName}" already assigned to ${assigneeLabel}; skipping.`);
} else if (out.includes('not found') && out.includes('target org')) {
console.log(`Permission set "${permsetName}" not in org; skipping.`);
} else {

View File

@ -68,6 +68,61 @@ if (fs.existsSync(licensePath)) {
fs.copyFileSync(licensePath, path.join(SAMPLE_DIR, 'LICENSE.txt'));
}
// Write .gitignore for the sample project
const gitignoreContent = `# This file is used for Git repositories to specify intentionally untracked files that Git should ignore.
# If you are not using git, you can delete this file. For more information see: https://git-scm.com/docs/gitignore
# For useful gitignore templates see: https://github.com/github/gitignore
# Salesforce cache
.sf/
.sfdx/
.localdevserver/
deploy-options.json
# LWC VSCode autocomplete
**/lwc/jsconfig.json
# LWC Jest coverage reports
coverage/
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Dependency directories
node_modules/
# Eslint cache
.eslintcache
# MacOS system files
.DS_Store
# Windows system files
Thumbs.db
ehthumbs.db
[Dd]esktop.ini
$RECYCLE.BIN/
# Local environment variables
.env
# Python Salesforce Functions
**/__pycache__/
**/.venv/
**/venv/
`;
fs.writeFileSync(path.join(SAMPLE_DIR, '.gitignore'), gitignoreContent, 'utf8');
// Generate package-lock.json for the uiBundle
const { execSync } = require('child_process');
const uiBundleDir = path.join(SAMPLE_DIR, 'force-app', 'main', 'default', 'uiBundles', 'propertymanagementapp');
console.log(`Generating package-lock.json for uiBundles/propertymanagementapp`);
execSync('npm install --package-lock-only --ignore-scripts', { cwd: uiBundleDir, stdio: 'inherit' });
// Write .version for version tracking (used by workflow and local)
fs.writeFileSync(path.join(SAMPLE_DIR, '.version'), version + '\n', 'utf8');

View File

@ -68,6 +68,61 @@ if (fs.existsSync(licensePath)) {
fs.copyFileSync(licensePath, path.join(SAMPLE_DIR, 'LICENSE.txt'));
}
// Write .gitignore for the sample project
const gitignoreContent = `# This file is used for Git repositories to specify intentionally untracked files that Git should ignore.
# If you are not using git, you can delete this file. For more information see: https://git-scm.com/docs/gitignore
# For useful gitignore templates see: https://github.com/github/gitignore
# Salesforce cache
.sf/
.sfdx/
.localdevserver/
deploy-options.json
# LWC VSCode autocomplete
**/lwc/jsconfig.json
# LWC Jest coverage reports
coverage/
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Dependency directories
node_modules/
# Eslint cache
.eslintcache
# MacOS system files
.DS_Store
# Windows system files
Thumbs.db
ehthumbs.db
[Dd]esktop.ini
$RECYCLE.BIN/
# Local environment variables
.env
# Python Salesforce Functions
**/__pycache__/
**/.venv/
**/venv/
`;
fs.writeFileSync(path.join(SAMPLE_DIR, '.gitignore'), gitignoreContent, 'utf8');
// Generate package-lock.json for the uiBundle
const { execSync } = require('child_process');
const uiBundleDir = path.join(SAMPLE_DIR, 'force-app', 'main', 'default', 'uiBundles', 'propertyrentalapp');
console.log(`Generating package-lock.json for uiBundles/propertyrentalapp`);
execSync('npm install --package-lock-only --ignore-scripts', { cwd: uiBundleDir, stdio: 'inherit' });
// Write .version for version tracking (used by workflow and local)
fs.writeFileSync(path.join(SAMPLE_DIR, '.version'), version + '\n', 'utf8');