@salesforce/templates 66.13.5 → 66.13.6
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/lib/templates/project/angularextapp/CHANGELOG.md +258 -0
- package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/angular.json +1 -1
- package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/eslint-plugin-angular-graphql.js +148 -0
- package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/eslint.config.js +9 -1
- package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/package-lock.json +299 -279
- package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/package.json +2 -2
- package/lib/templates/project/angularextapp/package.json +1 -1
- package/lib/templates/project/angularextapp/scripts/org-setup-config-schema.mjs +17 -0
- package/lib/templates/project/angularextapp/scripts/org-setup-utils.mjs +33 -3
- package/lib/templates/project/angularextapp/scripts/org-setup.mjs +535 -3
- package/lib/templates/project/angularintapp/CHANGELOG.md +266 -0
- package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/angular.json +1 -1
- package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/eslint-plugin-angular-graphql.js +148 -0
- package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/eslint.config.js +9 -1
- package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/package-lock.json +309 -289
- package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/package.json +2 -2
- package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/features/agentforce/agentforce-embed.service.ts +6 -1
- package/lib/templates/project/angularintapp/package.json +1 -1
- package/lib/templates/project/angularintapp/scripts/org-setup-config-schema.mjs +17 -0
- package/lib/templates/project/angularintapp/scripts/org-setup-utils.mjs +33 -3
- package/lib/templates/project/angularintapp/scripts/org-setup.mjs +535 -3
- package/lib/templates/project/reactexternalapp/CHANGELOG.md +1313 -0
- package/lib/templates/project/reactexternalapp/PRD.md +38 -0
- package/lib/templates/project/reactexternalapp/README.md +14 -10
- package/lib/templates/project/reactexternalapp/_p_/_m_/_ps_/reactexternalapp_Guest_User_Api_Access.permissionset-meta.xml +25 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/package-lock.json +587 -626
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/package.json +9 -7
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/scripts/get-graphql-schema.mjs +8 -5
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/components/SocialLogin.tsx +141 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/pages/Login.tsx +2 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/api/distinctValuesService.ts +84 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/api/searchService.ts +71 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/MergedSearchResults.tsx +277 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/Search.tsx +185 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SearchResults.tsx +77 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SourceSection.tsx +182 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/PaginationControls.tsx +185 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/ScopeSelector.tsx +55 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/SearchBar.tsx +55 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/SortControl.tsx +62 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/ActiveFilters.tsx +61 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/DefaultFilterPanel.tsx +125 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/{_os_/components → search/components/filters}/FilterContext.tsx +7 -20
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/BooleanFilter.tsx +50 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/DateRangeFilter.tsx +160 -0
- package/lib/templates/project/{reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters → reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs}/FilterFieldWrapper.tsx +11 -6
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/MultiSelectFilter.tsx +89 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/NumericRangeFilter.tsx +108 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/SelectFilter.tsx +46 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/TextFilter.tsx +52 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/results/DefaultResultRow.tsx +109 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/config.json +83 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/{_os_ → search}/hooks/useAsyncData.ts +3 -10
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/hooks/useDistinctValues.ts +59 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/hooks/useSearch.ts +703 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/index.ts +90 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/loadConfig.ts +14 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/queryBuilder.ts +156 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/types.ts +307 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/debounce.ts +17 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/fieldUtils.ts +14 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/filterUtils.ts +285 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/sortUtils.ts +34 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/graphqlClient.ts +24 -8
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/appLayout.tsx +7 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/CustomerWebClientChat.tsx +74 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/{_f_/_os_/components → components}/ObjectBreadcrumb.tsx +7 -2
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/ui/button.tsx +1 -1
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/ui/datePicker.tsx +2 -2
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/ui/table.tsx +9 -2
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/index.ts +6 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/AccountObjectDetailPage.tsx +49 -16
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/AccountSearch.tsx +21 -293
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/Home.tsx +22 -7
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/TestChatPage.tsx +26 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/routes.tsx +11 -5
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/types/chat.ts +36 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/{_f_/_os_/utils/fieldUtils.ts → utils/accountFields.ts} +9 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/tsconfig.json +2 -2
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/tsconfig.tsbuildinfo +1 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleSocialLoginConfig.cls +133 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleSocialLoginConfig.cls-meta.xml +5 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleSocialLoginConfig_Test.cls +101 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleSocialLoginConfig_Test.cls-meta.xml +5 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/package.xml +4 -0
- package/lib/templates/project/reactexternalapp/package.json +8 -2
- package/lib/templates/project/reactexternalapp/scripts/org-setup-config-schema.mjs +120 -0
- package/lib/templates/project/reactexternalapp/scripts/org-setup-dev.mjs +87 -0
- package/lib/templates/project/reactexternalapp/scripts/org-setup-utils.mjs +569 -0
- package/lib/templates/project/reactexternalapp/scripts/org-setup-xml.mjs +182 -0
- package/lib/templates/project/reactexternalapp/scripts/org-setup.config.json +6 -2
- package/lib/templates/project/reactexternalapp/scripts/org-setup.mjs +1509 -328
- package/lib/templates/project/reactexternalapp/scripts/validate-org-setup-config.mjs +38 -0
- package/lib/templates/project/reactinternalapp/CHANGELOG.md +1127 -0
- package/lib/templates/project/reactinternalapp/PRD.md +35 -0
- package/lib/templates/project/reactinternalapp/README.md +14 -10
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/package-lock.json +436 -439
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/package.json +9 -8
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/api/distinctValuesService.ts +84 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/api/searchService.ts +71 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/MergedSearchResults.tsx +277 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/Search.tsx +185 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SearchResults.tsx +77 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SourceSection.tsx +182 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/PaginationControls.tsx +185 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/ScopeSelector.tsx +55 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/SearchBar.tsx +55 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/SortControl.tsx +62 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/ActiveFilters.tsx +61 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/DefaultFilterPanel.tsx +125 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/{_os_/components → search/components/filters}/FilterContext.tsx +7 -20
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/BooleanFilter.tsx +50 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/DateRangeFilter.tsx +160 -0
- package/lib/templates/project/{reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters → reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs}/FilterFieldWrapper.tsx +11 -6
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/MultiSelectFilter.tsx +89 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/NumericRangeFilter.tsx +108 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/SelectFilter.tsx +46 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/TextFilter.tsx +52 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/results/DefaultResultRow.tsx +109 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/config.json +83 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/{_os_ → search}/hooks/useAsyncData.ts +3 -10
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/hooks/useDistinctValues.ts +59 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/hooks/useSearch.ts +703 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/index.ts +90 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/loadConfig.ts +14 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/queryBuilder.ts +156 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/types.ts +307 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/debounce.ts +17 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/fieldUtils.ts +14 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/filterUtils.ts +285 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/sortUtils.ts +34 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/AgentforceConversationClient.tsx +16 -132
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/{_f_/_os_/components → components}/ObjectBreadcrumb.tsx +7 -2
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/ui/button.tsx +1 -1
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/ui/datePicker.tsx +2 -2
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/ui/table.tsx +9 -2
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/pages/AccountObjectDetailPage.tsx +49 -16
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/pages/AccountSearch.tsx +21 -293
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/pages/Home.tsx +21 -6
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/routes.tsx +4 -4
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/{_f_/_os_/utils/fieldUtils.ts → utils/accountFields.ts} +9 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/tsconfig.json +2 -2
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/tsconfig.tsbuildinfo +1 -0
- package/lib/templates/project/reactinternalapp/package.json +4 -2
- package/lib/templates/project/reactinternalapp/scripts/org-setup-config-schema.mjs +24 -0
- package/lib/templates/project/reactinternalapp/scripts/org-setup-dev.mjs +87 -0
- package/lib/templates/project/reactinternalapp/scripts/org-setup-utils.mjs +569 -0
- package/lib/templates/project/reactinternalapp/scripts/org-setup-xml.mjs +182 -0
- package/lib/templates/project/reactinternalapp/scripts/org-setup.mjs +1162 -214
- package/lib/templates/uiBundles/angularbasic/angular.json +1 -1
- package/lib/templates/uiBundles/angularbasic/eslint-plugin-angular-graphql.js +148 -0
- package/lib/templates/uiBundles/angularbasic/eslint.config.js +9 -1
- package/lib/templates/uiBundles/angularbasic/package-lock.json +299 -279
- package/lib/templates/uiBundles/angularbasic/package.json +2 -2
- package/lib/templates/uiBundles/reactbasic/package-lock.json +372 -352
- package/lib/utils/template-placeholders.js +7 -0
- package/lib/utils/template-placeholders.js.map +1 -1
- package/lib/utils/uiBundleTemplateUtils.d.ts +3 -1
- package/lib/utils/uiBundleTemplateUtils.js +4 -1
- package/lib/utils/uiBundleTemplateUtils.js.map +1 -1
- package/package.json +7 -7
- package/lib/templates/project/reactexternalapp/AGENT.md +0 -252
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/accountSearchService.ts +0 -46
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/distinctAccountIndustries.graphql +0 -19
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/distinctAccountTypes.graphql +0 -19
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/getAccountDetail.graphql +0 -121
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/searchAccounts.graphql +0 -51
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountObjectDetailPage.tsx +0 -355
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountSearch.tsx +0 -304
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/Home.tsx +0 -34
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/api/objectSearchService.ts +0 -82
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/ActiveFilters.tsx +0 -89
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/PaginationControls.tsx +0 -109
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/SearchBar.tsx +0 -41
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/SortControl.tsx +0 -143
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/BooleanFilter.tsx +0 -78
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/DateFilter.tsx +0 -128
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/DateRangeFilter.tsx +0 -70
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/MultiSelectFilter.tsx +0 -97
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/NumericRangeFilter.tsx +0 -167
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/SearchFilter.tsx +0 -52
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/SelectFilter.tsx +0 -97
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/TextFilter.tsx +0 -93
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useDebouncedCallback.ts +0 -34
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useObjectSearchParams.ts +0 -254
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/debounce.ts +0 -25
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/filterUtils.ts +0 -404
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/sortUtils.ts +0 -38
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/account/accountSearchService.ts +0 -46
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/account/query/distinctAccountIndustries.graphql +0 -19
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/account/query/distinctAccountTypes.graphql +0 -19
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/account/query/searchAccounts.graphql +0 -51
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/graphql-operations-types.ts +0 -11260
- package/lib/templates/project/reactexternalapp/scripts/prepare-import-unique-fields.js +0 -122
- package/lib/templates/project/reactinternalapp/AGENT.md +0 -252
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/accountSearchService.ts +0 -46
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/distinctAccountIndustries.graphql +0 -19
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/distinctAccountTypes.graphql +0 -19
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/getAccountDetail.graphql +0 -121
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/searchAccounts.graphql +0 -51
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountObjectDetailPage.tsx +0 -355
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountSearch.tsx +0 -304
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/Home.tsx +0 -34
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/api/objectSearchService.ts +0 -82
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/ActiveFilters.tsx +0 -89
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/PaginationControls.tsx +0 -109
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/SearchBar.tsx +0 -41
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/SortControl.tsx +0 -143
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/BooleanFilter.tsx +0 -78
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/DateFilter.tsx +0 -128
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/DateRangeFilter.tsx +0 -70
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/MultiSelectFilter.tsx +0 -97
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/NumericRangeFilter.tsx +0 -167
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/SearchFilter.tsx +0 -52
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/SelectFilter.tsx +0 -97
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/TextFilter.tsx +0 -93
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useDebouncedCallback.ts +0 -34
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useObjectSearchParams.ts +0 -254
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/debounce.ts +0 -25
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/filterUtils.ts +0 -404
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/sortUtils.ts +0 -38
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/account/accountSearchService.ts +0 -46
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/account/query/distinctAccountIndustries.graphql +0 -19
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/account/query/distinctAccountTypes.graphql +0 -19
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/account/query/searchAccounts.graphql +0 -51
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/graphql-operations-types.ts +0 -11260
- package/lib/templates/project/reactinternalapp/scripts/prepare-import-unique-fields.js +0 -122
- /package/lib/templates/project/angularextapp/_p_/_m_/{permissionsets → _ps_}/angularextapp_Guest_User_Api_Access.permissionset-meta.xml +0 -0
- /package/lib/templates/project/angularintapp/_p_/_m_/{permissionsets → _ps_}/angularintapp_Access.permissionset-meta.xml +0 -0
- /package/lib/templates/project/reactinternalapp/_p_/_m_/{permissionsets → _ps_}/reactinternalapp_Access.permissionset-meta.xml +0 -0
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
existsSync,
|
|
47
47
|
readFileSync,
|
|
48
48
|
writeFileSync,
|
|
49
|
+
mkdirSync,
|
|
49
50
|
mkdtempSync,
|
|
50
51
|
rmSync,
|
|
51
52
|
openSync,
|
|
@@ -286,6 +287,7 @@ function parseArgs() {
|
|
|
286
287
|
skipGraphql: false,
|
|
287
288
|
skipUIBundleBuild: false,
|
|
288
289
|
skipSelfReg: false,
|
|
290
|
+
skipSocialLogin: false,
|
|
289
291
|
};
|
|
290
292
|
for (let i = 0; i < args.length; i++) {
|
|
291
293
|
if (args[i] === '--target-org' && args[i + 1]) {
|
|
@@ -299,6 +301,7 @@ function parseArgs() {
|
|
|
299
301
|
else if (args[i] === '--skip-role') flags.skipRole = true;
|
|
300
302
|
else if (args[i] === '--skip-data') flags.skipData = true;
|
|
301
303
|
else if (args[i] === '--skip-self-reg') flags.skipSelfReg = true;
|
|
304
|
+
else if (args[i] === '--skip-social-login') flags.skipSocialLogin = true;
|
|
302
305
|
else if (args[i] === '--skip-graphql') flags.skipGraphql = true;
|
|
303
306
|
else if (args[i] === '--skip-ui-bundle-build') flags.skipUIBundleBuild = true;
|
|
304
307
|
// --skip-login and --skip-dev are retired: login is now an unconditional
|
|
@@ -322,6 +325,7 @@ Options:
|
|
|
322
325
|
--permset-name <name> Assign only this permission set (repeatable). Default: all sets under permissionsets/
|
|
323
326
|
--skip-deploy Do not deploy metadata
|
|
324
327
|
--skip-permset Do not assign permission set
|
|
328
|
+
--skip-social-login Do not enable social login (auth providers + community profile)
|
|
325
329
|
--skip-data Do not prepare data or run data import
|
|
326
330
|
--skip-graphql Do not fetch schema or run GraphQL codegen
|
|
327
331
|
--skip-ui-bundle-build Do not npm install / build the UI bundle
|
|
@@ -521,6 +525,435 @@ function loadSelfRegConfig(config) {
|
|
|
521
525
|
};
|
|
522
526
|
}
|
|
523
527
|
|
|
528
|
+
/**
|
|
529
|
+
* Social login config, read from the already-validated config.
|
|
530
|
+
*
|
|
531
|
+
* Config shape:
|
|
532
|
+
* {
|
|
533
|
+
* "socialLogin": {
|
|
534
|
+
* "communityMemberProfile": "Customer Community User",
|
|
535
|
+
* "authProviderNames": ["Google", "Facebook", "My_SAML_Provider"],
|
|
536
|
+
* "communityUserPermset": "myapp_Guest_User_Api_Access" // optional
|
|
537
|
+
* }
|
|
538
|
+
* }
|
|
539
|
+
*
|
|
540
|
+
* - communityMemberProfile: profile added to NetworkMemberGroup (required for access)
|
|
541
|
+
* - authProviderNames: DeveloperNames of AuthProvider (OAuth) or SamlSsoConfig (SAML) records to link to site
|
|
542
|
+
* - communityUserPermset: permset assigned to community users so getCurrentUser()
|
|
543
|
+
* works (requires PermissionsApiEnabled for /chatter/users/me via /sf/api/)
|
|
544
|
+
*
|
|
545
|
+
* Returns null if no "socialLogin" section exists in config (the step is hidden).
|
|
546
|
+
*/
|
|
547
|
+
function loadSocialLoginConfig(config) {
|
|
548
|
+
const section = config.socialLogin;
|
|
549
|
+
if (!section) return null;
|
|
550
|
+
return {
|
|
551
|
+
communityMemberProfile: section.communityMemberProfile,
|
|
552
|
+
authProviderNames: section.authProviderNames,
|
|
553
|
+
communityUserPermset: section.communityUserPermset || null,
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Enable the "Allow using standard external profiles for self-registration,
|
|
559
|
+
* user creation, and login" org setting via Metadata API deploy.
|
|
560
|
+
*
|
|
561
|
+
* This setting is required for SSO registration handlers to create users with
|
|
562
|
+
* standard community profiles (e.g. "Customer Community User"). Without it,
|
|
563
|
+
* auth providers return FIELD_INTEGRITY_EXCEPTION on user insert.
|
|
564
|
+
*
|
|
565
|
+
* The correct metadata field is `enableOotbProfExtUserOpsEnable` in
|
|
566
|
+
* CommunitiesSettings. The REST/Tooling API approaches also fail on many
|
|
567
|
+
* org types. Deploying via Metadata API is the most reliable method.
|
|
568
|
+
*/
|
|
569
|
+
function enableExternalProfiles(targetOrg) {
|
|
570
|
+
const dir = mkdtempSync(join(tmpdir(), APEX_TMP_PREFIX));
|
|
571
|
+
try {
|
|
572
|
+
// Create a minimal sfdx project with just the Communities setting
|
|
573
|
+
const settingsDir = join(dir, 'force-app', 'main', 'default', 'settings');
|
|
574
|
+
mkdirSync(settingsDir, { recursive: true });
|
|
575
|
+
|
|
576
|
+
writeFileSync(join(dir, 'sfdx-project.json'), JSON.stringify({
|
|
577
|
+
packageDirectories: [{ path: 'force-app', default: true }],
|
|
578
|
+
sourceApiVersion: '68.0',
|
|
579
|
+
}));
|
|
580
|
+
|
|
581
|
+
writeFileSync(join(settingsDir, 'Communities.settings-meta.xml'), [
|
|
582
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
583
|
+
'<CommunitiesSettings xmlns="http://soap.sforce.com/2006/04/metadata">',
|
|
584
|
+
' <enableOotbProfExtUserOpsEnable>true</enableOotbProfExtUserOpsEnable>',
|
|
585
|
+
'</CommunitiesSettings>',
|
|
586
|
+
].join('\n'));
|
|
587
|
+
|
|
588
|
+
const result = spawnSync('sf', [
|
|
589
|
+
'project', 'deploy', 'start',
|
|
590
|
+
'--target-org', targetOrg,
|
|
591
|
+
'--source-dir', join(dir, 'force-app'),
|
|
592
|
+
'--json',
|
|
593
|
+
], { cwd: dir, stdio: 'pipe', shell: true, timeout: 120000 });
|
|
594
|
+
|
|
595
|
+
const out = result.stdout?.toString() || '';
|
|
596
|
+
let deployResult;
|
|
597
|
+
try { deployResult = JSON.parse(out); } catch { deployResult = null; }
|
|
598
|
+
|
|
599
|
+
if (deployResult?.status === 0 || deployResult?.result?.status === 'Succeeded') {
|
|
600
|
+
console.log(' Enabled "Allow using standard external profiles" org setting.');
|
|
601
|
+
} else {
|
|
602
|
+
// Check if it failed because the setting is already enabled or not deployable
|
|
603
|
+
const errMsg = deployResult?.message || deployResult?.result?.details?.componentFailures?.[0]?.problem || '';
|
|
604
|
+
if (errMsg.includes('already') || errMsg.includes('enableOotbProfExtUserOpsEnable')) {
|
|
605
|
+
console.log(' Warning: could not deploy "Allow standard external profiles" setting.');
|
|
606
|
+
console.log(` Detail: ${errMsg}`);
|
|
607
|
+
console.log(' The setting may already be active. Enable manually via Setup > Digital Experiences > Settings if needed.');
|
|
608
|
+
} else {
|
|
609
|
+
console.log(' Warning: could not enable "Allow standard external profiles".');
|
|
610
|
+
console.log(` Deploy result: ${errMsg || 'unknown error'}`);
|
|
611
|
+
console.log(' Enable manually via Setup > Digital Experiences > Settings.');
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
} finally {
|
|
615
|
+
try { rmSync(dir, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Enable Auth Providers for a community by creating AuthConfigProviders junction
|
|
621
|
+
* records via Anonymous Apex + REST API callouts.
|
|
622
|
+
*
|
|
623
|
+
* React (Site Container) sites intentionally hide SSO configuration in the Admin
|
|
624
|
+
* UI, so this must be done programmatically. The approach:
|
|
625
|
+
* 1. Query the AuthConfig record for the site (one per community).
|
|
626
|
+
* 2. Query AuthProvider records by DeveloperName.
|
|
627
|
+
* 3. Query SamlSsoConfig records by DeveloperName (for SAML providers).
|
|
628
|
+
* 4. Check existing AuthConfigProviders to avoid duplicates.
|
|
629
|
+
* 5. Create missing AuthConfigProviders via REST API (DML is not allowed on this object).
|
|
630
|
+
*
|
|
631
|
+
* AuthConfig.AuthOptionsAuthProvider auto-flips to true when AuthConfigProviders
|
|
632
|
+
* are inserted — no separate update needed.
|
|
633
|
+
*
|
|
634
|
+
* @param {string[]} authProviderNames - DeveloperNames of AuthProvider or SamlSsoConfig records
|
|
635
|
+
* @param {string} siteName - Name of the Network/community
|
|
636
|
+
* @param {string} targetOrg - Target org alias
|
|
637
|
+
*/
|
|
638
|
+
function enableAuthProvidersForSite(authProviderNames, siteName, targetOrg) {
|
|
639
|
+
// DML is not allowed on AuthConfigProviders, so we use Anonymous Apex with
|
|
640
|
+
// HTTP callouts to the REST API to create the junction records.
|
|
641
|
+
// Whitelist each DeveloperName before it is interpolated into the generated
|
|
642
|
+
// Apex `DeveloperName IN (…)` literal — same call-site guard the data-import
|
|
643
|
+
// path applies with validateApiName, and defense-in-depth alongside the
|
|
644
|
+
// up-front socialLogin validation in main(). loadSocialLoginConfig already
|
|
645
|
+
// rejects an empty list, so the resulting literal is never empty here.
|
|
646
|
+
const providerNamesLiteral = authProviderNames
|
|
647
|
+
.map((n) => `'${validateSoqlName(n, 'socialLogin.authProviderNames entry')}'`)
|
|
648
|
+
.join(', ');
|
|
649
|
+
|
|
650
|
+
const apex = `
|
|
651
|
+
// Enable Auth Providers (OAuth + SAML) for community "${siteName}"
|
|
652
|
+
Http http = new Http();
|
|
653
|
+
String baseUrl = Url.getOrgDomainUrl().toExternalForm();
|
|
654
|
+
String token = UserInfo.getSessionId();
|
|
655
|
+
|
|
656
|
+
// Step 1: Find the AuthConfig for this site
|
|
657
|
+
List<AuthConfig> authConfigs = [
|
|
658
|
+
SELECT Id, Url
|
|
659
|
+
FROM AuthConfig
|
|
660
|
+
WHERE Url LIKE '%${siteName}%'
|
|
661
|
+
LIMIT 1
|
|
662
|
+
];
|
|
663
|
+
if (authConfigs.isEmpty()) {
|
|
664
|
+
System.debug('ERROR_NO_AUTHCONFIG');
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
AuthConfig ac = authConfigs[0];
|
|
668
|
+
System.debug('AUTHCONFIG_FOUND:' + ac.Id + ':' + ac.Url);
|
|
669
|
+
|
|
670
|
+
// Step 2: Query AuthProvider records by DeveloperName (OAuth providers)
|
|
671
|
+
List<AuthProvider> oauthProviders = [
|
|
672
|
+
SELECT Id, DeveloperName, FriendlyName
|
|
673
|
+
FROM AuthProvider
|
|
674
|
+
WHERE DeveloperName IN (${providerNamesLiteral})
|
|
675
|
+
];
|
|
676
|
+
System.debug('OAUTH_PROVIDERS_FOUND:' + oauthProviders.size());
|
|
677
|
+
|
|
678
|
+
// Step 3: Query SamlSsoConfig records by DeveloperName (SAML providers)
|
|
679
|
+
List<SamlSsoConfig> samlProviders = [
|
|
680
|
+
SELECT Id, DeveloperName
|
|
681
|
+
FROM SamlSsoConfig
|
|
682
|
+
WHERE DeveloperName IN (${providerNamesLiteral})
|
|
683
|
+
];
|
|
684
|
+
System.debug('SAML_PROVIDERS_FOUND:' + samlProviders.size());
|
|
685
|
+
|
|
686
|
+
// Combine both into a unified list with (Id, DeveloperName, Type)
|
|
687
|
+
List<Map<String, String>> allProviders = new List<Map<String, String>>();
|
|
688
|
+
for (AuthProvider ap : oauthProviders) {
|
|
689
|
+
allProviders.add(new Map<String, String>{
|
|
690
|
+
'Id' => ap.Id,
|
|
691
|
+
'DeveloperName' => ap.DeveloperName,
|
|
692
|
+
'Type' => 'OAuth'
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
for (SamlSsoConfig sp : samlProviders) {
|
|
696
|
+
allProviders.add(new Map<String, String>{
|
|
697
|
+
'Id' => sp.Id,
|
|
698
|
+
'DeveloperName' => sp.DeveloperName,
|
|
699
|
+
'Type' => 'SAML'
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
if (allProviders.isEmpty()) {
|
|
704
|
+
System.debug('ERROR_NO_PROVIDERS');
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
System.debug('TOTAL_PROVIDERS_FOUND:' + allProviders.size());
|
|
708
|
+
|
|
709
|
+
// Step 4: Check existing AuthConfigProviders to avoid duplicates
|
|
710
|
+
Set<Id> existingProviderIds = new Set<Id>();
|
|
711
|
+
for (AuthConfigProviders acp : [
|
|
712
|
+
SELECT AuthProviderId FROM AuthConfigProviders WHERE AuthConfigId = :ac.Id
|
|
713
|
+
]) {
|
|
714
|
+
existingProviderIds.add(acp.AuthProviderId);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Step 5: Create missing junction records via REST API
|
|
718
|
+
Integer inserted = 0;
|
|
719
|
+
for (Map<String, String> provider : allProviders) {
|
|
720
|
+
String providerId = provider.get('Id');
|
|
721
|
+
String providerName = provider.get('DeveloperName');
|
|
722
|
+
String providerType = provider.get('Type');
|
|
723
|
+
|
|
724
|
+
if (existingProviderIds.contains(providerId)) {
|
|
725
|
+
System.debug('ALREADY_LINKED:' + providerName + ':' + providerType);
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
728
|
+
HttpRequest req = new HttpRequest();
|
|
729
|
+
req.setEndpoint(baseUrl + '/services/data/v62.0/sobjects/AuthConfigProviders');
|
|
730
|
+
req.setMethod('POST');
|
|
731
|
+
req.setHeader('Authorization', 'Bearer ' + token);
|
|
732
|
+
req.setHeader('Content-Type', 'application/json');
|
|
733
|
+
req.setBody('{"AuthConfigId":"' + ac.Id + '","AuthProviderId":"' + providerId + '"}');
|
|
734
|
+
HttpResponse res = http.send(req);
|
|
735
|
+
if (res.getStatusCode() == 201 || res.getStatusCode() == 200) {
|
|
736
|
+
System.debug('INSERTED:' + providerName + ':' + providerType);
|
|
737
|
+
inserted++;
|
|
738
|
+
} else {
|
|
739
|
+
System.debug('INSERT_FAILED:' + providerName + ':' + providerType + ':' + res.getStatusCode() + ':' + res.getBody());
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
if (inserted == 0 && existingProviderIds.size() >= allProviders.size()) {
|
|
743
|
+
System.debug('ALL_ALREADY_LINKED');
|
|
744
|
+
} else {
|
|
745
|
+
System.debug('TOTAL_INSERTED:' + inserted);
|
|
746
|
+
}
|
|
747
|
+
`;
|
|
748
|
+
|
|
749
|
+
const apexOut = withApexTempDir((writeApex) => {
|
|
750
|
+
const tmpApex = writeApex('auth-providers.apex', apex);
|
|
751
|
+
const result = spawnSync('sf', [
|
|
752
|
+
'apex', 'run', '--target-org', targetOrg, '--file', tmpApex,
|
|
753
|
+
], { cwd: ROOT, stdio: 'pipe', shell: true, timeout: 60000 });
|
|
754
|
+
const out = result.stdout?.toString() || '';
|
|
755
|
+
const err = result.stderr?.toString() || '';
|
|
756
|
+
if (result.status !== 0 && !out.includes('Compiled successfully')) {
|
|
757
|
+
process.stderr.write(err || out);
|
|
758
|
+
throw new StepError('failed to execute AuthConfigProviders apex');
|
|
759
|
+
}
|
|
760
|
+
return out;
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
// Parse output for status — match only |DEBUG| lines to avoid false positives
|
|
764
|
+
// from the "Execute Anonymous:" source echo that sf apex run always prints.
|
|
765
|
+
if (apexOut.match(/\|DEBUG\|ERROR_NO_AUTHCONFIG/)) {
|
|
766
|
+
throw new StepError(`no AuthConfig found for site "${siteName}" — ensure the site is published/active`);
|
|
767
|
+
}
|
|
768
|
+
if (apexOut.match(/\|DEBUG\|ERROR_NO_PROVIDERS/)) {
|
|
769
|
+
throw new StepError(`no AuthProvider or SamlSsoConfig records found for names: ${authProviderNames.join(', ')} — create them in Setup first`);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// Check for insert failures
|
|
773
|
+
const insertFails = [...apexOut.matchAll(/\|DEBUG\|INSERT_FAILED:([^:]+):(OAuth|SAML):(\d+):([^\n]+)/g)];
|
|
774
|
+
if (insertFails.length > 0) {
|
|
775
|
+
for (const m of insertFails) {
|
|
776
|
+
console.error(` ✖ ${m[1]} (${m[2]}) — HTTP ${m[3]}: ${m[4].trim()}`);
|
|
777
|
+
}
|
|
778
|
+
throw new StepError(`failed to link ${insertFails.length} Auth Provider(s) to site AuthConfig`);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const totalMatch = apexOut.match(/\|DEBUG\|TOTAL_INSERTED:(\d+)/);
|
|
782
|
+
if (totalMatch) {
|
|
783
|
+
console.log(` Linked ${totalMatch[1]} Auth Provider(s) to site "${siteName}".`);
|
|
784
|
+
} else if (apexOut.match(/\|DEBUG\|ALL_ALREADY_LINKED/)) {
|
|
785
|
+
console.log(` All Auth Provider(s) already linked to site "${siteName}"; no changes needed.`);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// Log individual provider statuses
|
|
789
|
+
const alreadyLinked = [...apexOut.matchAll(/\|DEBUG\|ALREADY_LINKED:([^:]+):(OAuth|SAML)/g)];
|
|
790
|
+
for (const m of alreadyLinked) {
|
|
791
|
+
console.log(` ✔ ${m[1]} (${m[2]}, already linked)`);
|
|
792
|
+
}
|
|
793
|
+
const inserted = [...apexOut.matchAll(/\|DEBUG\|INSERTED:([^:]+):(OAuth|SAML)/g)];
|
|
794
|
+
for (const m of inserted) {
|
|
795
|
+
console.log(` + ${m[1]} (${m[2]}, newly linked)`);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Add a community user profile to the NetworkMemberGroup so that SSO-registered
|
|
801
|
+
* users (who get assigned this profile by the registration handler) can access
|
|
802
|
+
* the community.
|
|
803
|
+
*
|
|
804
|
+
* Without this, users created by the SSO registration handler get
|
|
805
|
+
* "NO_ACCESS: User was not authorized for the community".
|
|
806
|
+
*
|
|
807
|
+
* @param {string} profileName - Profile name to add (e.g. "Customer Community User")
|
|
808
|
+
* @param {string} siteName - Name of the Network/community
|
|
809
|
+
* @param {string} targetOrg - Target org alias
|
|
810
|
+
*/
|
|
811
|
+
function addCommunityMemberProfile(profileName, siteName, targetOrg) {
|
|
812
|
+
// Query Network Id
|
|
813
|
+
const netQuery = `SELECT Id FROM Network WHERE Name = '${siteName}'`;
|
|
814
|
+
const netResult = spawnSync('sf', [
|
|
815
|
+
'data', 'query', '--query', netQuery, '--target-org', targetOrg, '--json',
|
|
816
|
+
], { cwd: ROOT, encoding: 'utf8' });
|
|
817
|
+
let networkId = null;
|
|
818
|
+
if (netResult.status === 0) {
|
|
819
|
+
try {
|
|
820
|
+
const json = JSON.parse(netResult.stdout);
|
|
821
|
+
networkId = json.result?.records?.[0]?.Id || null;
|
|
822
|
+
} catch { /* fall through */ }
|
|
823
|
+
}
|
|
824
|
+
if (!networkId) {
|
|
825
|
+
throw new StepError(`could not find Network "${siteName}" in org`);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// Query Profile Id
|
|
829
|
+
const profQuery = `SELECT Id FROM Profile WHERE Name = '${profileName}'`;
|
|
830
|
+
const profResult = spawnSync('sf', [
|
|
831
|
+
'data', 'query', '--query', profQuery, '--target-org', targetOrg, '--json',
|
|
832
|
+
], { cwd: ROOT, encoding: 'utf8' });
|
|
833
|
+
let profileId = null;
|
|
834
|
+
if (profResult.status === 0) {
|
|
835
|
+
try {
|
|
836
|
+
const json = JSON.parse(profResult.stdout);
|
|
837
|
+
profileId = json.result?.records?.[0]?.Id || null;
|
|
838
|
+
} catch { /* fall through */ }
|
|
839
|
+
}
|
|
840
|
+
if (!profileId) {
|
|
841
|
+
throw new StepError(`could not find Profile "${profileName}" in org`);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// Check if already a member
|
|
845
|
+
const memberQuery = `SELECT Id FROM NetworkMemberGroup WHERE NetworkId = '${networkId}' AND ParentId = '${profileId}'`;
|
|
846
|
+
const memberResult = spawnSync('sf', [
|
|
847
|
+
'data', 'query', '--query', memberQuery, '--target-org', targetOrg, '--json',
|
|
848
|
+
], { cwd: ROOT, encoding: 'utf8' });
|
|
849
|
+
let alreadyExists = false;
|
|
850
|
+
if (memberResult.status === 0) {
|
|
851
|
+
try {
|
|
852
|
+
const json = JSON.parse(memberResult.stdout);
|
|
853
|
+
alreadyExists = (json.result?.records?.length || 0) > 0;
|
|
854
|
+
} catch { /* proceed to create */ }
|
|
855
|
+
}
|
|
856
|
+
if (alreadyExists) {
|
|
857
|
+
console.log(` Profile "${profileName}" is already a member of community "${siteName}"; skipping.`);
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// Create NetworkMemberGroup record
|
|
862
|
+
const createResult = spawnSync('sf', [
|
|
863
|
+
'data', 'create', 'record',
|
|
864
|
+
'--sobject', 'NetworkMemberGroup',
|
|
865
|
+
'--values', `NetworkId='${networkId}' ParentId='${profileId}'`,
|
|
866
|
+
'--target-org', targetOrg,
|
|
867
|
+
'--json',
|
|
868
|
+
], { cwd: ROOT, encoding: 'utf8' });
|
|
869
|
+
if (createResult.status !== 0) {
|
|
870
|
+
const out = (createResult.stderr || '') + (createResult.stdout || '');
|
|
871
|
+
if (out) process.stderr.write(out);
|
|
872
|
+
throw new StepError(`failed to add profile "${profileName}" to NetworkMemberGroup for "${siteName}"`);
|
|
873
|
+
}
|
|
874
|
+
try {
|
|
875
|
+
const json = JSON.parse(createResult.stdout);
|
|
876
|
+
const id = json.result?.id;
|
|
877
|
+
console.log(` Added profile "${profileName}" to community "${siteName}" (NetworkMemberGroup: ${id}).`);
|
|
878
|
+
} catch {
|
|
879
|
+
console.log(` Added profile "${profileName}" to community "${siteName}".`);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* Assign a permission set to all existing community users with a given profile.
|
|
885
|
+
*
|
|
886
|
+
* After SSO login, community users need the app's API-enabled permset so that
|
|
887
|
+
* getCurrentUser() (which calls /chatter/users/me via the /sf/api/ proxy) works.
|
|
888
|
+
* Without PermissionsApiEnabled the proxy returns API_DISABLED_FOR_ORG and the
|
|
889
|
+
* React app treats the user as unauthenticated.
|
|
890
|
+
*
|
|
891
|
+
* This is idempotent: already-assigned users are skipped.
|
|
892
|
+
*/
|
|
893
|
+
function assignPermsetToCommunityUsers(permsetName, profileName, targetOrg) {
|
|
894
|
+
// Resolve PermissionSet Id
|
|
895
|
+
const psQuery = `SELECT Id FROM PermissionSet WHERE Name = '${permsetName}'`;
|
|
896
|
+
const psResult = spawnSync('sf', [
|
|
897
|
+
'data', 'query', '--query', psQuery, '--target-org', targetOrg, '--json',
|
|
898
|
+
], { cwd: ROOT, encoding: 'utf8' });
|
|
899
|
+
let permsetId = null;
|
|
900
|
+
if (psResult.status === 0) {
|
|
901
|
+
try {
|
|
902
|
+
const json = JSON.parse(psResult.stdout);
|
|
903
|
+
permsetId = json.result?.records?.[0]?.Id || null;
|
|
904
|
+
} catch { /* fall through */ }
|
|
905
|
+
}
|
|
906
|
+
if (!permsetId) {
|
|
907
|
+
throw new StepError(`could not find PermissionSet "${permsetName}" in org`);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// Find community users on the given profile who do NOT already have the permset
|
|
911
|
+
const usersQuery = [
|
|
912
|
+
`SELECT Id, Username FROM User`,
|
|
913
|
+
`WHERE Profile.Name = '${profileName}'`,
|
|
914
|
+
`AND IsActive = true`,
|
|
915
|
+
`AND UserType IN ('CspLitePortal', 'CustomerSuccess', 'PowerCustomerSuccess')`,
|
|
916
|
+
`AND Id NOT IN (SELECT AssigneeId FROM PermissionSetAssignment WHERE PermissionSetId = '${permsetId}')`,
|
|
917
|
+
].join(' ');
|
|
918
|
+
const usersResult = spawnSync('sf', [
|
|
919
|
+
'data', 'query', '--query', usersQuery, '--target-org', targetOrg, '--json',
|
|
920
|
+
], { cwd: ROOT, encoding: 'utf8' });
|
|
921
|
+
let users = [];
|
|
922
|
+
if (usersResult.status === 0) {
|
|
923
|
+
try {
|
|
924
|
+
const json = JSON.parse(usersResult.stdout);
|
|
925
|
+
users = json.result?.records || [];
|
|
926
|
+
} catch { /* empty list */ }
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
if (users.length === 0) {
|
|
930
|
+
console.log(` No community users on profile "${profileName}" need permset "${permsetName}"; skipping.`);
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// Assign the permset to each user
|
|
935
|
+
let assigned = 0;
|
|
936
|
+
for (const user of users) {
|
|
937
|
+
const createResult = spawnSync('sf', [
|
|
938
|
+
'data', 'create', 'record',
|
|
939
|
+
'--sobject', 'PermissionSetAssignment',
|
|
940
|
+
'--values', `PermissionSetId='${permsetId}' AssigneeId='${user.Id}'`,
|
|
941
|
+
'--target-org', targetOrg,
|
|
942
|
+
'--json',
|
|
943
|
+
], { cwd: ROOT, encoding: 'utf8' });
|
|
944
|
+
if (createResult.status === 0) {
|
|
945
|
+
assigned++;
|
|
946
|
+
} else {
|
|
947
|
+
// Log but don't fail — user might already have it via race condition
|
|
948
|
+
const out = (createResult.stdout || '') + (createResult.stderr || '');
|
|
949
|
+
if (!out.includes('DUPLICATE_VALUE')) {
|
|
950
|
+
console.warn(` Warning: could not assign "${permsetName}" to ${user.Username}: ${out.slice(0, 120)}`);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
console.log(` Assigned permset "${permsetName}" to ${assigned}/${users.length} community user(s).`);
|
|
955
|
+
}
|
|
956
|
+
|
|
524
957
|
/**
|
|
525
958
|
* Ensure the self-registration profile is listed in networkMemberGroups.
|
|
526
959
|
* This must happen BEFORE the initial deploy so that the profile is a recognised
|
|
@@ -1056,7 +1489,10 @@ function buildApexInsert(sobject, records, refIds) {
|
|
|
1056
1489
|
lines.push(' else { m.put(\'err\', results[i].getErrors()[0].getMessage()); }');
|
|
1057
1490
|
lines.push(' setupOut.add(m);');
|
|
1058
1491
|
lines.push('}');
|
|
1059
|
-
|
|
1492
|
+
// Use LoggingLevel.ERROR to ensure this critical line appears near the end of
|
|
1493
|
+
// the debug log and isn't truncated by excessive DEBUG-level noise from DML
|
|
1494
|
+
// operations, workflow rules, or process builders that fire during insert.
|
|
1495
|
+
lines.push("System.debug(LoggingLevel.ERROR, 'SETUP_RESULT_JSON:' + JSON.serialize(setupOut));");
|
|
1060
1496
|
return lines.join('\n');
|
|
1061
1497
|
}
|
|
1062
1498
|
|
|
@@ -1316,6 +1752,7 @@ async function main() {
|
|
|
1316
1752
|
skipPermset: argSkipPermset,
|
|
1317
1753
|
skipRole: argSkipRole,
|
|
1318
1754
|
skipSelfReg: argSkipSelfReg,
|
|
1755
|
+
skipSocialLogin: argSkipSocialLogin,
|
|
1319
1756
|
skipData: argSkipData,
|
|
1320
1757
|
skipGraphql: argSkipGraphql,
|
|
1321
1758
|
skipUIBundleBuild: argSkipUIBundleBuild,
|
|
@@ -1349,6 +1786,8 @@ async function main() {
|
|
|
1349
1786
|
const hasRoleConfig = roleConfig !== null;
|
|
1350
1787
|
const selfRegConfig = loadSelfRegConfig(config);
|
|
1351
1788
|
const hasSelfRegConfig = selfRegConfig !== null;
|
|
1789
|
+
const socialLoginConfig = loadSocialLoginConfig(config);
|
|
1790
|
+
const hasSocialLoginConfig = socialLoginConfig !== null;
|
|
1352
1791
|
|
|
1353
1792
|
// Validate the selfRegProfile name for SOQL-safety up front, alongside the
|
|
1354
1793
|
// config validation and BEFORE any org mutation (login/deploy). A quote /
|
|
@@ -1364,6 +1803,30 @@ async function main() {
|
|
|
1364
1803
|
}
|
|
1365
1804
|
}
|
|
1366
1805
|
|
|
1806
|
+
// Same fail-fast treatment for the socialLogin identifiers, alongside the
|
|
1807
|
+
// self-reg guard above and BEFORE any org mutation. communityMemberProfile,
|
|
1808
|
+
// every authProviderNames entry, and the optional communityUserPermset are all
|
|
1809
|
+
// interpolated into generated SOQL / CLI value strings — the Profile and
|
|
1810
|
+
// PermissionSet `WHERE Name = '…'` lookups, the AuthProvider/SamlSsoConfig
|
|
1811
|
+
// `DeveloperName IN (…)` list, and the NetworkMemberGroup/User queries. These
|
|
1812
|
+
// are developer-authored identifiers the app ships, so a quote / backslash /
|
|
1813
|
+
// control char is a config mistake to surface loudly here rather than a value
|
|
1814
|
+
// to silently escape (same rationale as validateSoqlName's self-reg use).
|
|
1815
|
+
if (hasSocialLoginConfig) {
|
|
1816
|
+
try {
|
|
1817
|
+
validateSoqlName(socialLoginConfig.communityMemberProfile, 'socialLogin.communityMemberProfile');
|
|
1818
|
+
for (const providerName of socialLoginConfig.authProviderNames ?? []) {
|
|
1819
|
+
validateSoqlName(providerName, 'socialLogin.authProviderNames entry');
|
|
1820
|
+
}
|
|
1821
|
+
if (socialLoginConfig.communityUserPermset) {
|
|
1822
|
+
validateSoqlName(socialLoginConfig.communityUserPermset, 'socialLogin.communityUserPermset');
|
|
1823
|
+
}
|
|
1824
|
+
} catch (err) {
|
|
1825
|
+
console.error(`Invalid org-setup.config.json:\n - ${err.message}`);
|
|
1826
|
+
process.exit(1);
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1367
1830
|
// failFast is a fixed, implementer-owned classification — NOT
|
|
1368
1831
|
// user-configurable. A fail-fast failure aborts the run immediately; a
|
|
1369
1832
|
// skippable failure is recorded and the run continues. The exit code is
|
|
@@ -1378,6 +1841,7 @@ async function main() {
|
|
|
1378
1841
|
{ key: 'permset', label: permsetStepLabel, enabled: !argSkipPermset, available: true, failFast: false },
|
|
1379
1842
|
{ key: 'role', label: `Role — assign "${roleConfig?.roleName ?? '?'}" to current user`, enabled: !argSkipRole && hasRoleConfig, available: hasRoleConfig, failFast: false },
|
|
1380
1843
|
{ key: 'selfReg', label: 'Self-Registration — enable for site', enabled: !argSkipSelfReg && hasSelfRegConfig, available: hasSelfRegConfig, failFast: false },
|
|
1844
|
+
{ key: 'socialLogin', label: 'Social Login — enable auth providers + community profile', enabled: !argSkipSocialLogin && hasSocialLoginConfig, available: hasSocialLoginConfig, failFast: false },
|
|
1381
1845
|
{ key: 'data', label: 'Data — delete + import records via Apex', enabled: !argSkipData && hasDataPlan, available: hasDataPlan, failFast: true },
|
|
1382
1846
|
{ key: 'graphql', label: 'GraphQL — schema introspect + codegen', enabled: !argSkipGraphql, available: true, failFast: true },
|
|
1383
1847
|
];
|
|
@@ -1393,6 +1857,7 @@ async function main() {
|
|
|
1393
1857
|
const skipPermset = !on.permset;
|
|
1394
1858
|
const skipRole = !on.role;
|
|
1395
1859
|
const skipSelfReg = !on.selfReg;
|
|
1860
|
+
const skipSocialLogin = !on.socialLogin;
|
|
1396
1861
|
const skipData = !on.data;
|
|
1397
1862
|
const skipGraphql = !on.graphql;
|
|
1398
1863
|
|
|
@@ -1402,11 +1867,12 @@ async function main() {
|
|
|
1402
1867
|
|
|
1403
1868
|
console.log('Setup — target org:', targetOrg, '| UI bundle:', uiBundleDir ?? '(none)');
|
|
1404
1869
|
console.log(
|
|
1405
|
-
'Steps: login=always deploy=%s permset=%s role=%s selfReg=%s data=%s graphql=%s uiBundle=%s',
|
|
1870
|
+
'Steps: login=always deploy=%s permset=%s role=%s selfReg=%s socialLogin=%s data=%s graphql=%s uiBundle=%s',
|
|
1406
1871
|
!skipDeploy,
|
|
1407
1872
|
!skipPermset,
|
|
1408
1873
|
!skipRole,
|
|
1409
1874
|
!skipSelfReg,
|
|
1875
|
+
!skipSocialLogin,
|
|
1410
1876
|
doData,
|
|
1411
1877
|
!skipGraphql,
|
|
1412
1878
|
!skipUIBundleBuild
|
|
@@ -1619,6 +2085,53 @@ async function main() {
|
|
|
1619
2085
|
recordSkipped(selfRegStep, selfRegStep.available ? 'not selected' : 'no config');
|
|
1620
2086
|
}
|
|
1621
2087
|
|
|
2088
|
+
const socialLoginStep = stepDefs.find((s) => s.key === 'socialLogin');
|
|
2089
|
+
if (!skipSocialLogin) {
|
|
2090
|
+
console.log('\n--- Social Login — enable auth providers + community profile ---');
|
|
2091
|
+
await runStep(socialLoginStep, targetOrg, () => {
|
|
2092
|
+
const siteName = deriveSiteName();
|
|
2093
|
+
if (!siteName) {
|
|
2094
|
+
throw new StepError(
|
|
2095
|
+
'socialLogin is configured but no networks/<siteName>.network-meta.xml was found to derive the site',
|
|
2096
|
+
);
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
const totalSubSteps = socialLoginConfig.communityUserPermset ? 4 : 3;
|
|
2100
|
+
|
|
2101
|
+
// Sub-step 1: Enable "Allow using standard external profiles" org setting.
|
|
2102
|
+
// This is required so the SSO registration handler can create users with
|
|
2103
|
+
// standard community profiles (e.g. "Customer Community User").
|
|
2104
|
+
console.log(` [1/${totalSubSteps}] Enabling "Allow standard external profiles" setting...`);
|
|
2105
|
+
enableExternalProfiles(targetOrg);
|
|
2106
|
+
|
|
2107
|
+
// Sub-step 2: Link Auth Providers to the site's AuthConfig.
|
|
2108
|
+
// React (Site Container) sites hide SSO config in Admin UI, so this must
|
|
2109
|
+
// be done programmatically via AuthConfig/AuthConfigProviders records.
|
|
2110
|
+
console.log(` [2/${totalSubSteps}] Linking Auth Providers to site AuthConfig...`);
|
|
2111
|
+
enableAuthProvidersForSite(socialLoginConfig.authProviderNames, siteName, targetOrg);
|
|
2112
|
+
|
|
2113
|
+
// Sub-step 3: Add the community member profile to NetworkMemberGroup.
|
|
2114
|
+
// Without this, users created by the registration handler get
|
|
2115
|
+
// "NO_ACCESS: User was not authorized for the community".
|
|
2116
|
+
console.log(` [3/${totalSubSteps}] Adding community member profile to NetworkMemberGroup...`);
|
|
2117
|
+
addCommunityMemberProfile(socialLoginConfig.communityMemberProfile, siteName, targetOrg);
|
|
2118
|
+
|
|
2119
|
+
// Sub-step 4 (optional): Assign API-enabled permset to community users.
|
|
2120
|
+
// Without this, getCurrentUser() fails because /chatter/users/me requires
|
|
2121
|
+
// PermissionsApiEnabled which the standard community profile lacks.
|
|
2122
|
+
if (socialLoginConfig.communityUserPermset) {
|
|
2123
|
+
console.log(` [4/${totalSubSteps}] Assigning API permset to community users...`);
|
|
2124
|
+
assignPermsetToCommunityUsers(
|
|
2125
|
+
socialLoginConfig.communityUserPermset,
|
|
2126
|
+
socialLoginConfig.communityMemberProfile,
|
|
2127
|
+
targetOrg,
|
|
2128
|
+
);
|
|
2129
|
+
}
|
|
2130
|
+
});
|
|
2131
|
+
} else {
|
|
2132
|
+
recordSkipped(socialLoginStep, socialLoginStep.available ? 'not selected' : 'no config');
|
|
2133
|
+
}
|
|
2134
|
+
|
|
1622
2135
|
const dataStep = stepDefs.find((s) => s.key === 'data');
|
|
1623
2136
|
if (doData) {
|
|
1624
2137
|
await runStep(dataStep, targetOrg, () => {
|
|
@@ -1672,8 +2185,14 @@ async function main() {
|
|
|
1672
2185
|
// API headers (Sforce-Duplicate-Rule-Action) cannot override.
|
|
1673
2186
|
console.log('\n--- Data import (Apex) ---');
|
|
1674
2187
|
const refMap = new Map();
|
|
2188
|
+
// Apex anonymous code is capped at ~1M chars total, but each batch is much
|
|
2189
|
+
// smaller in practice. The real constraint is the SETUP_RESULT_JSON debug log
|
|
2190
|
+
// line: Salesforce truncates debug lines at ~5KB, and large batches produce
|
|
2191
|
+
// JSON arrays that exceed that limit. With RESULT_JSON_PER_RECORD = 150 and
|
|
2192
|
+
// MAX_BATCH = 100, the worst-case output is ~15KB of JSON, which fits
|
|
2193
|
+
// comfortably after the debug-line prefix and the marker.
|
|
1675
2194
|
const APEX_CHAR_LIMIT = 25000;
|
|
1676
|
-
const APEX_MAX_BATCH =
|
|
2195
|
+
const APEX_MAX_BATCH = 100;
|
|
1677
2196
|
|
|
1678
2197
|
for (const entry of planEntries) {
|
|
1679
2198
|
for (const file of entry.files) {
|
|
@@ -1705,7 +2224,16 @@ async function main() {
|
|
|
1705
2224
|
// rendered refId literal is counted here too (mirroring the same
|
|
1706
2225
|
// referenceId/_idx fallback the batch emit uses) — otherwise sizing
|
|
1707
2226
|
// under-counts and a packed batch could edge past APEX_CHAR_LIMIT.
|
|
2227
|
+
//
|
|
2228
|
+
// CRITICAL: also account for the SETUP_RESULT_JSON output size. The
|
|
2229
|
+
// System.debug line can exceed the Salesforce debug log line limit when
|
|
2230
|
+
// the serialized JSON array is large. Estimate ~85 chars per success
|
|
2231
|
+
// result ({"ref":"_idxNNN","id":"001XXXXXXXXXXXXXXX"}) and ~150 chars per
|
|
2232
|
+
// potential error ({"ref":"_idxNNN","err":"typical error message"}), taking
|
|
2233
|
+
// the higher value to be conservative. This ensures the batching prevents
|
|
2234
|
+
// log truncation that would fail parseApexInsertResults.
|
|
1708
2235
|
const overhead = buildApexInsert(entry.sobject, [], []).length;
|
|
2236
|
+
const RESULT_JSON_PER_RECORD = 150;
|
|
1709
2237
|
const recordSizes = records.map((rec, i) => {
|
|
1710
2238
|
let size = `{ ${entry.sobject} r = new ${entry.sobject}();\n`.length + 'recs.add(r); }\n'.length;
|
|
1711
2239
|
for (const [key, val] of Object.entries(rec)) {
|
|
@@ -1714,6 +2242,10 @@ async function main() {
|
|
|
1714
2242
|
}
|
|
1715
2243
|
const refId = rec.attributes?.referenceId || `_idx${i}`;
|
|
1716
2244
|
size += `'${escapeSoqlString(refId)}',`.length;
|
|
2245
|
+
// Add the estimated size of this record's JSON result entry in the
|
|
2246
|
+
// SETUP_RESULT_JSON output. Without this, large batches can produce
|
|
2247
|
+
// debug log lines that exceed Salesforce's limit and get truncated.
|
|
2248
|
+
size += RESULT_JSON_PER_RECORD;
|
|
1717
2249
|
return size;
|
|
1718
2250
|
});
|
|
1719
2251
|
const batches = planApexBatches(recordSizes, {
|