salesforce-metadata-mcp 2.5.2 → 2.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/README.md +18 -1
- package/dist/index.js +4 -4
- package/dist/schemas/index.js +406 -191
- package/dist/services/deployment.js +69 -13
- package/dist/services/salesforce.js +1262 -478
- package/dist/services/tooling.js +46 -21
- package/dist/tools/deployment.js +9 -13
- package/dist/tools/metadata.js +1 -0
- package/dist/tools/ui.js +57 -16
- package/package.json +1 -1
|
@@ -90,7 +90,7 @@ function getFreshTokenFromCLI(alias) {
|
|
|
90
90
|
rawOutput = execSync(`sf org display --target-org ${alias} --json`, {
|
|
91
91
|
encoding: "utf-8",
|
|
92
92
|
timeout: 30_000,
|
|
93
|
-
env: { PATH: process.env["PATH"] ?? "" },
|
|
93
|
+
env: { PATH: process.env["PATH"] ?? "", SF_TEMP_SHOW_SECRETS: "true" },
|
|
94
94
|
});
|
|
95
95
|
}
|
|
96
96
|
catch (err) {
|
|
@@ -202,7 +202,10 @@ export function createClient(auth) {
|
|
|
202
202
|
const text = await response.text().catch(() => "");
|
|
203
203
|
throw new Error(sanitizeError(`Salesforce API error ${response.status}: ${text.slice(0, 300)}`));
|
|
204
204
|
}
|
|
205
|
-
|
|
205
|
+
// 204 No Content has no body (e.g. PATCH record updates)
|
|
206
|
+
if (response.status === 204) return { data: {} };
|
|
207
|
+
const text = await response.text().catch(() => "");
|
|
208
|
+
const data = text ? JSON.parse(text) : {};
|
|
206
209
|
return { data };
|
|
207
210
|
}
|
|
208
211
|
return {
|
|
@@ -265,7 +268,10 @@ export async function callMetadataSoap(auth, soapAction, bodyInner) {
|
|
|
265
268
|
body: envelope,
|
|
266
269
|
}, 60_000);
|
|
267
270
|
if (!response.ok) {
|
|
268
|
-
|
|
271
|
+
const errText = await response.text().catch(() => "");
|
|
272
|
+
const faultMatch = errText.match(/<faultstring[^>]*>([\s\S]*?)<\/faultstring>/i);
|
|
273
|
+
const faultMsg = faultMatch ? faultMatch[1].trim() : "";
|
|
274
|
+
throw new Error(`SOAP request failed with HTTP ${response.status}${faultMsg ? `: ${faultMsg}` : "."}`);
|
|
269
275
|
}
|
|
270
276
|
return response.text();
|
|
271
277
|
}
|
|
@@ -549,14 +555,38 @@ function buildFlowXml(params) {
|
|
|
549
555
|
const hasFieldUpdates = params.fieldUpdates && params.fieldUpdates.length > 0;
|
|
550
556
|
const hasApprovalSubmit = !!params.submitForApprovalProcessName;
|
|
551
557
|
const hasElements = params.elements && params.elements.length > 0;
|
|
558
|
+
// When no elements provided add a no-op assignment so the flow can be activated
|
|
559
|
+
const needsNoOp = !hasElements && !hasApprovalSubmit && !hasFieldUpdates;
|
|
552
560
|
const firstTarget = hasElements && params.elements[0]
|
|
553
561
|
? params.elements[0].name
|
|
554
562
|
: hasApprovalSubmit ? "Submit_For_Approval"
|
|
555
563
|
: hasFieldUpdates ? "Update_Record"
|
|
564
|
+
: needsNoOp ? "No_Op"
|
|
556
565
|
: null;
|
|
557
566
|
const startConnector = firstTarget
|
|
558
567
|
? `<met:connector><met:targetReference>${firstTarget}</met:targetReference></met:connector>` : "";
|
|
559
|
-
const
|
|
568
|
+
const noOpElement = needsNoOp ? `
|
|
569
|
+
<met:assignments>
|
|
570
|
+
<met:name>No_Op</met:name>
|
|
571
|
+
<met:label>No Op</met:label>
|
|
572
|
+
<met:locationX>250</met:locationX>
|
|
573
|
+
<met:locationY>100</met:locationY>
|
|
574
|
+
<met:assignmentItems>
|
|
575
|
+
<met:assignToReference>NoOpVar</met:assignToReference>
|
|
576
|
+
<met:operator>Assign</met:operator>
|
|
577
|
+
<met:value><met:stringValue></met:stringValue></met:value>
|
|
578
|
+
</met:assignmentItems>
|
|
579
|
+
</met:assignments>` : "";
|
|
580
|
+
const noOpVar = needsNoOp ? `
|
|
581
|
+
<met:variables>
|
|
582
|
+
<met:name>NoOpVar</met:name>
|
|
583
|
+
<met:dataType>String</met:dataType>
|
|
584
|
+
<met:isCollection>false</met:isCollection>
|
|
585
|
+
<met:isInput>false</met:isInput>
|
|
586
|
+
<met:isOutput>false</met:isOutput>
|
|
587
|
+
</met:variables>` : "";
|
|
588
|
+
const isRecordTriggered = params.flowType === "RecordTriggeredFlow" && params.triggerObject;
|
|
589
|
+
const startElement = isRecordTriggered ? `
|
|
560
590
|
<met:start>
|
|
561
591
|
<met:locationX>50</met:locationX><met:locationY>0</met:locationY>
|
|
562
592
|
<met:object>${x(params.triggerObject)}</met:object>
|
|
@@ -605,28 +635,33 @@ function buildFlowXml(params) {
|
|
|
605
635
|
</met:value>
|
|
606
636
|
</met:inputAssignments>`).join("\n")}
|
|
607
637
|
</met:recordUpdates>` : "";
|
|
638
|
+
const processType = params.flowType === "Screen" ? "Flow"
|
|
639
|
+
: (params.flowType === "RecordTriggeredFlow" || params.flowType === "AutoLaunchedFlow") ? "AutoLaunchedFlow"
|
|
640
|
+
: x(params.flowType);
|
|
608
641
|
return `<met:metadata xsi:type="met:Flow" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
609
|
-
<met:fullName>${x(params.apiName)}</met:fullName>
|
|
642
|
+
<met:fullName>${x(params.apiName ?? params.flowName)}</met:fullName>
|
|
610
643
|
<met:label>${x(params.label)}</met:label>
|
|
611
644
|
<met:apiVersion>${API_VERSION}</met:apiVersion>
|
|
612
|
-
<met:status>${x(params.status)}</met:status>
|
|
613
|
-
<met:processType>${
|
|
614
|
-
|
|
645
|
+
<met:status>${x(params.status ?? "Draft")}</met:status>
|
|
646
|
+
<met:processType>${processType}</met:processType>
|
|
647
|
+
${params.runInMode ? `<met:runInMode>${x(params.runInMode)}</met:runInMode>` : ""}
|
|
615
648
|
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
649
|
+
${noOpVar}
|
|
616
650
|
${vars}
|
|
617
651
|
${startElement}
|
|
618
652
|
${buildElements(params.elements)}
|
|
653
|
+
${noOpElement}
|
|
619
654
|
${approvalSubmitElement}
|
|
620
655
|
${recordUpdateElement}
|
|
621
656
|
</met:metadata>`;
|
|
622
657
|
}
|
|
623
658
|
function buildApprovalProcessXml(params) {
|
|
624
|
-
const submitters = params.allowedSubmitters.map(s => `
|
|
659
|
+
const submitters = (params.allowedSubmitters ?? [{ type: "owner" }]).map(s => `
|
|
625
660
|
<met:allowedSubmitters>
|
|
626
661
|
<met:type>${x(s.type)}</met:type>
|
|
627
662
|
${s.submitter ? `<met:submitter>${x(s.submitter)}</met:submitter>` : ""}
|
|
628
663
|
</met:allowedSubmitters>`).join("\n");
|
|
629
|
-
const steps = params.approvalSteps.map((step, i) => `
|
|
664
|
+
const steps = (params.approvalSteps ?? []).map((step, i) => `
|
|
630
665
|
<met:approvalStep>
|
|
631
666
|
<met:name>${x(step.name)}</met:name>
|
|
632
667
|
<met:label>${x(step.label)}</met:label>
|
|
@@ -666,26 +701,30 @@ function buildApprovalProcessXml(params) {
|
|
|
666
701
|
${steps}
|
|
667
702
|
${entryCriteriaXml}
|
|
668
703
|
${params.emailTemplate ? `<met:emailTemplate>${x(params.emailTemplate)}</met:emailTemplate>` : ""}
|
|
669
|
-
<met:finalApprovalRecordLock>${params.finalApprovalLock}</met:finalApprovalRecordLock>
|
|
670
|
-
<met:finalRejectionRecordLock>${params.finalRejectionLock}</met:finalRejectionRecordLock>
|
|
704
|
+
<met:finalApprovalRecordLock>${params.finalApprovalLock ?? false}</met:finalApprovalRecordLock>
|
|
705
|
+
<met:finalRejectionRecordLock>${params.finalRejectionLock ?? false}</met:finalRejectionRecordLock>
|
|
671
706
|
<met:recordEditability>${x(params.recordEditability)}</met:recordEditability>
|
|
672
707
|
<met:showApprovalHistory>true</met:showApprovalHistory>
|
|
673
708
|
</met:metadata>`;
|
|
674
709
|
}
|
|
675
710
|
function buildValidationRuleXml(params) {
|
|
711
|
+
const objectName = params.objectName ?? params.objectApiName;
|
|
712
|
+
// WSDL order for ValidationRule: fullName (base), active, description, errorConditionFormula, errorDisplayField, errorMessage
|
|
676
713
|
return `<met:metadata xsi:type="met:CustomObject" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
677
|
-
<met:fullName>${x(
|
|
714
|
+
<met:fullName>${x(objectName)}</met:fullName>
|
|
678
715
|
<met:validationRules>
|
|
679
716
|
<met:fullName>${x(params.ruleName)}</met:fullName>
|
|
680
|
-
<met:active>${params.active}</met:active>
|
|
717
|
+
<met:active>${params.active !== false ? "true" : "false"}</met:active>
|
|
718
|
+
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
681
719
|
<met:errorConditionFormula>${x(params.errorConditionFormula)}</met:errorConditionFormula>
|
|
682
|
-
<met:errorMessage>${x(params.errorMessage)}</met:errorMessage>
|
|
683
720
|
${params.errorDisplayField ? `<met:errorDisplayField>${x(params.errorDisplayField)}</met:errorDisplayField>` : ""}
|
|
684
|
-
|
|
721
|
+
<met:errorMessage>${x(params.errorMessage)}</met:errorMessage>
|
|
685
722
|
</met:validationRules>
|
|
686
723
|
</met:metadata>`;
|
|
687
724
|
}
|
|
688
725
|
function buildWorkflowFieldUpdateXml(params) {
|
|
726
|
+
const actionName = params.actionName ?? params.updateName ?? "FieldUpdate";
|
|
727
|
+
const fieldName = params.field ?? params.fieldName ?? "";
|
|
689
728
|
let operationXml = "";
|
|
690
729
|
if (params.nullValue) {
|
|
691
730
|
operationXml = `<met:operation>Null</met:operation>`;
|
|
@@ -696,16 +735,14 @@ function buildWorkflowFieldUpdateXml(params) {
|
|
|
696
735
|
else {
|
|
697
736
|
operationXml = `<met:literalValue>${x(params.literalValue ?? "")}</met:literalValue><met:operation>Literal</met:operation>`;
|
|
698
737
|
}
|
|
699
|
-
return `<met:metadata xsi:type="met:
|
|
700
|
-
<met:fullName>${x(params.objectName)}</met:fullName>
|
|
701
|
-
<met:
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
<met:protected>false</met:protected>
|
|
708
|
-
</met:fieldUpdates>
|
|
738
|
+
return `<met:metadata xsi:type="met:WorkflowFieldUpdate" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
739
|
+
<met:fullName>${x(params.objectName)}.${x(actionName)}</met:fullName>
|
|
740
|
+
<met:name>${x(actionName)}</met:name>
|
|
741
|
+
<met:label>${x(params.label ?? actionName)}</met:label>
|
|
742
|
+
<met:field>${x(fieldName)}</met:field>
|
|
743
|
+
${operationXml}
|
|
744
|
+
<met:notifyAssignee>${params.notifyAssignee ?? false}</met:notifyAssignee>
|
|
745
|
+
<met:protected>false</met:protected>
|
|
709
746
|
</met:metadata>`;
|
|
710
747
|
}
|
|
711
748
|
// ─── New XML builders for expanded tools ─────────────────────────────────────
|
|
@@ -716,7 +753,6 @@ function buildCustomMetadataTypeXml(params) {
|
|
|
716
753
|
<met:label>${x(params.label)}</met:label>
|
|
717
754
|
<met:pluralLabel>${x(params.pluralLabel)}</met:pluralLabel>
|
|
718
755
|
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
719
|
-
<met:deploymentStatus>Deployed</met:deploymentStatus>
|
|
720
756
|
${fieldsXml}
|
|
721
757
|
</met:metadata>`;
|
|
722
758
|
}
|
|
@@ -750,9 +786,7 @@ function buildCustomSettingXml(params) {
|
|
|
750
786
|
return `<met:metadata xsi:type="met:CustomObject" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
751
787
|
<met:fullName>${x(params.fullName)}</met:fullName>
|
|
752
788
|
<met:label>${x(params.label)}</met:label>
|
|
753
|
-
<met:customSettingsType>${x(params.settingType)}</met:customSettingsType>
|
|
754
|
-
<met:customSettingsVisibility>${x(params.visibility)}</met:customSettingsVisibility>
|
|
755
|
-
<met:deploymentStatus>Deployed</met:deploymentStatus>
|
|
789
|
+
<met:customSettingsType>${x(params.settingType ?? "List")}</met:customSettingsType>
|
|
756
790
|
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
757
791
|
${fieldsXml}
|
|
758
792
|
</met:metadata>`;
|
|
@@ -786,36 +820,37 @@ function buildPlatformEventXml(params) {
|
|
|
786
820
|
</met:metadata>`;
|
|
787
821
|
}
|
|
788
822
|
function buildEmailAlertXml(params) {
|
|
789
|
-
const recipientsXml = params.recipients.map(r => `
|
|
823
|
+
const recipientsXml = (params.recipients ?? []).map(r => `
|
|
790
824
|
<met:recipients>
|
|
791
825
|
<met:type>${x(r.type)}</met:type>
|
|
792
826
|
${r.recipient ? `<met:recipient>${x(r.recipient)}</met:recipient>` : ""}
|
|
793
827
|
</met:recipients>`).join("\n");
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
<met:
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
<met:protected>${params.protected}</met:protected>
|
|
804
|
-
${recipientsXml}
|
|
805
|
-
</met:alerts>
|
|
828
|
+
const alertName = params.alertName ?? params.label ?? "EmailAlert";
|
|
829
|
+
return `<met:metadata xsi:type="met:WorkflowAlert" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
830
|
+
<met:fullName>${x(params.objectName)}.${x(alertName)}</met:fullName>
|
|
831
|
+
<met:description>${x(params.description ?? params.label ?? alertName)}</met:description>
|
|
832
|
+
<met:protected>${params.protected ?? false}</met:protected>
|
|
833
|
+
<met:senderType>${x(params.senderType ?? "CurrentUser")}</met:senderType>
|
|
834
|
+
${params.senderAddress ? `<met:senderAddress>${x(params.senderAddress)}</met:senderAddress>` : ""}
|
|
835
|
+
<met:template>${x(params.template)}</met:template>
|
|
836
|
+
${recipientsXml}
|
|
806
837
|
</met:metadata>`;
|
|
807
838
|
}
|
|
808
839
|
function buildAssignmentRuleXml(params) {
|
|
809
|
-
const entriesXml = params.ruleEntries.map(e => {
|
|
810
|
-
const criteriaXml = (e.criteriaItems ?? []).map(c =>
|
|
840
|
+
const entriesXml = params.ruleEntries.map((e, idx) => {
|
|
841
|
+
const criteriaXml = (e.criteriaItems ?? []).map(c => {
|
|
842
|
+
const field = c.field.includes(".") ? c.field : `${params.objectName}.${c.field}`;
|
|
843
|
+
return `
|
|
811
844
|
<met:criteriaItems>
|
|
812
|
-
<met:field>${x(
|
|
845
|
+
<met:field>${x(field)}</met:field>
|
|
813
846
|
<met:operation>${x(c.operation)}</met:operation>
|
|
814
847
|
<met:value>${x(c.value)}</met:value>
|
|
815
|
-
</met:criteriaItems
|
|
848
|
+
</met:criteriaItems>`;
|
|
849
|
+
}).join("\n");
|
|
816
850
|
return `<met:ruleEntry>
|
|
851
|
+
<met:entryOrder>${e.entryOrder ?? idx + 1}</met:entryOrder>
|
|
817
852
|
<met:assignedTo>${x(e.assignedTo)}</met:assignedTo>
|
|
818
|
-
<met:assignedToType>${x(e.assignedToType)}</met:assignedToType>
|
|
853
|
+
<met:assignedToType>${x(e.assignedToType ?? "Queue")}</met:assignedToType>
|
|
819
854
|
${criteriaXml}
|
|
820
855
|
${e.formula ? `<met:formula>${x(e.formula)}</met:formula>` : ""}
|
|
821
856
|
${e.booleanFilter ? `<met:booleanFilter>${x(e.booleanFilter)}</met:booleanFilter>` : ""}
|
|
@@ -826,7 +861,7 @@ function buildAssignmentRuleXml(params) {
|
|
|
826
861
|
<met:fullName>${x(params.objectName)}</met:fullName>
|
|
827
862
|
<met:assignmentRule>
|
|
828
863
|
<met:fullName>${x(params.ruleName)}</met:fullName>
|
|
829
|
-
<met:active>${params.active}</met:active>
|
|
864
|
+
<met:active>${params.active ?? false}</met:active>
|
|
830
865
|
${entriesXml}
|
|
831
866
|
</met:assignmentRule>
|
|
832
867
|
</met:metadata>`;
|
|
@@ -919,13 +954,19 @@ function buildNamedCredentialXml(params) {
|
|
|
919
954
|
</met:metadata>`;
|
|
920
955
|
}
|
|
921
956
|
function buildLightningAppXml(params) {
|
|
922
|
-
const navItemsXml = (params.navItems ?? []).map(ni =>
|
|
957
|
+
const navItemsXml = (params.navItems ?? []).map(ni => {
|
|
958
|
+
const name = typeof ni === "string" ? ni : ni.name;
|
|
959
|
+
const type = typeof ni === "string" ? "StandardNavItem" : (ni.type ?? "StandardNavItem");
|
|
960
|
+
const label = typeof ni === "string" ? null : ni.label;
|
|
961
|
+
const defaultItem = typeof ni === "string" ? false : (ni.defaultItem ?? false);
|
|
962
|
+
return `
|
|
923
963
|
<met:navItems>
|
|
924
|
-
<met:name>${x(
|
|
925
|
-
<met:type>${x(
|
|
926
|
-
${
|
|
927
|
-
<met:defaultItem>${
|
|
928
|
-
</met:navItems
|
|
964
|
+
<met:name>${x(name)}</met:name>
|
|
965
|
+
<met:type>${x(type)}</met:type>
|
|
966
|
+
${label ? `<met:label>${x(label)}</met:label>` : ""}
|
|
967
|
+
<met:defaultItem>${defaultItem}</met:defaultItem>
|
|
968
|
+
</met:navItems>`;
|
|
969
|
+
}).join("\n");
|
|
929
970
|
const utilityXml = (params.utilityItems ?? []).map(u => `
|
|
930
971
|
<met:utilityBar>
|
|
931
972
|
<met:name>${x(u.name)}</met:name>
|
|
@@ -947,15 +988,19 @@ function buildLightningAppXml(params) {
|
|
|
947
988
|
</met:metadata>`;
|
|
948
989
|
}
|
|
949
990
|
function buildTabXml(params) {
|
|
991
|
+
const sobjectName = params.sobjectName;
|
|
992
|
+
const isCustomObject = params.customObject ?? (sobjectName ? true : !params.url && !params.page);
|
|
993
|
+
// For custom SObject tabs: fullName IS the sobjectName (e.g. MCPTest__c); sobjectName field is not used in SOAP
|
|
994
|
+
const fullName = (isCustomObject && sobjectName) ? sobjectName : params.fullName;
|
|
995
|
+
// WSDL element order: customObject, description, fullName, label, motif, page, url
|
|
950
996
|
return `<met:metadata xsi:type="met:CustomTab" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
951
|
-
<met:
|
|
952
|
-
|
|
953
|
-
<met:
|
|
954
|
-
${params.sobjectName ? `<met:sobjectName>${x(params.sobjectName)}</met:sobjectName>` : ""}
|
|
997
|
+
<met:customObject>${isCustomObject}</met:customObject>
|
|
998
|
+
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
999
|
+
<met:fullName>${x(fullName)}</met:fullName>
|
|
955
1000
|
${params.label ? `<met:label>${x(params.label)}</met:label>` : ""}
|
|
956
|
-
${params.
|
|
1001
|
+
${params.motif ? `<met:motif>${x(params.motif)}</met:motif>` : ""}
|
|
957
1002
|
${params.page ? `<met:page>${x(params.page)}</met:page>` : ""}
|
|
958
|
-
${params.
|
|
1003
|
+
${params.url ? `<met:url>${x(params.url)}</met:url>` : ""}
|
|
959
1004
|
</met:metadata>`;
|
|
960
1005
|
}
|
|
961
1006
|
function buildCompactLayoutXml(params) {
|
|
@@ -1029,6 +1074,15 @@ function buildReportTypeXml(params) {
|
|
|
1029
1074
|
</met:columns>
|
|
1030
1075
|
<met:masterLabel>${x(r.label ?? r.joinTable)}</met:masterLabel>
|
|
1031
1076
|
</met:sections>`).join("\n");
|
|
1077
|
+
// Base sections element — include Name plus any additional columns specified
|
|
1078
|
+
const baseFields = ["Name", ...(params.columns ?? []).filter(c => c !== "Name")];
|
|
1079
|
+
const baseColsXml = baseFields.map(f => `<met:columns><met:field>${x(f)}</met:field><met:table>${x(params.baseObject)}</met:table></met:columns>`).join("\n ");
|
|
1080
|
+
const baseSectionsXml = `
|
|
1081
|
+
<met:sections>
|
|
1082
|
+
${baseColsXml}
|
|
1083
|
+
<met:masterLabel>${x(params.label ?? params.baseObject)}</met:masterLabel>
|
|
1084
|
+
</met:sections>`;
|
|
1085
|
+
|
|
1032
1086
|
const joinXml = (params.relationships ?? []).map((r, i) => `
|
|
1033
1087
|
<met:relationships>
|
|
1034
1088
|
<met:join>
|
|
@@ -1037,29 +1091,43 @@ function buildReportTypeXml(params) {
|
|
|
1037
1091
|
</met:join>
|
|
1038
1092
|
<met:preferredRelationship>${x(r.joinTable)}</met:preferredRelationship>
|
|
1039
1093
|
</met:relationships>`).join("\n");
|
|
1094
|
+
// WSDL element order: baseObject, category, deployed, description, fullName, label, relationships, sections
|
|
1040
1095
|
return `<met:metadata xsi:type="met:ReportType" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
1041
|
-
<met:fullName>${x(params.fullName)}</met:fullName>
|
|
1042
|
-
<met:label>${x(params.label)}</met:label>
|
|
1043
1096
|
<met:baseObject>${x(params.baseObject)}</met:baseObject>
|
|
1044
1097
|
<met:category>${x(params.category)}</met:category>
|
|
1045
1098
|
<met:deployed>${params.deployed}</met:deployed>
|
|
1046
1099
|
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
1047
|
-
|
|
1100
|
+
<met:fullName>${x(params.fullName)}</met:fullName>
|
|
1101
|
+
<met:label>${x(params.label)}</met:label>
|
|
1048
1102
|
${joinXml}
|
|
1103
|
+
${baseSectionsXml}
|
|
1104
|
+
${relXml}
|
|
1049
1105
|
</met:metadata>`;
|
|
1050
1106
|
}
|
|
1051
1107
|
function buildConnectedAppXml(params) {
|
|
1052
|
-
|
|
1053
|
-
const
|
|
1108
|
+
// ConnectedAppOauthAccessScope enum uses Title case in Salesforce Metadata API: Api, Web, Basic, Full, etc.
|
|
1109
|
+
const scopeMap = {
|
|
1110
|
+
api: "Api", web: "Web", basic: "Basic", full: "Full", chatter: "Chatter",
|
|
1111
|
+
openid: "OpenID", openId: "OpenID", refresh_token: "RefreshToken",
|
|
1112
|
+
offline_access: "OfflineAccess", customPermissions: "CustomPermissions",
|
|
1113
|
+
// already correct Title case → passthrough
|
|
1114
|
+
Api: "Api", Web: "Web", Basic: "Basic", Full: "Full", Chatter: "Chatter",
|
|
1115
|
+
OpenID: "OpenID", RefreshToken: "RefreshToken", OfflineAccess: "OfflineAccess",
|
|
1116
|
+
CustomPermissions: "CustomPermissions",
|
|
1117
|
+
};
|
|
1118
|
+
const callbacksXml = (params.callbackUrls ?? []).map(u => `<met:callbackUrl>${x(u)}</met:callbackUrl>`).join("\n");
|
|
1119
|
+
const scopesXml = (params.scopes ?? []).map(s => `<met:scopes>${x(scopeMap[s] ?? s)}</met:scopes>`).join("\n");
|
|
1120
|
+
// WSDL order for ConnectedApp: fullName (base), contactEmail, description, label, oauthConfig
|
|
1121
|
+
// WSDL order for ConnectedAppOauthConfig: callbackUrl, consumerKey, scopes, startUrl
|
|
1054
1122
|
return `<met:metadata xsi:type="met:ConnectedApp" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
1055
1123
|
<met:fullName>${x(params.fullName)}</met:fullName>
|
|
1056
|
-
<met:label>${x(params.label)}</met:label>
|
|
1057
|
-
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
1058
1124
|
<met:contactEmail>${x(params.contactEmail)}</met:contactEmail>
|
|
1125
|
+
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
1126
|
+
<met:label>${x(params.label)}</met:label>
|
|
1059
1127
|
<met:oauthConfig>
|
|
1060
1128
|
${callbacksXml}
|
|
1061
|
-
${scopesXml}
|
|
1062
1129
|
${params.consumerKey ? `<met:consumerKey>${x(params.consumerKey)}</met:consumerKey>` : ""}
|
|
1130
|
+
${scopesXml}
|
|
1063
1131
|
${params.startUrl ? `<met:startUrl>${x(params.startUrl)}</met:startUrl>` : ""}
|
|
1064
1132
|
</met:oauthConfig>
|
|
1065
1133
|
</met:metadata>`;
|
|
@@ -1099,14 +1167,15 @@ function buildRemoteSiteSettingXml(params) {
|
|
|
1099
1167
|
</met:metadata>`;
|
|
1100
1168
|
}
|
|
1101
1169
|
function buildCspTrustedSiteXml(params) {
|
|
1102
|
-
|
|
1103
|
-
const
|
|
1170
|
+
// Strip protocol and generate a valid API name (no double-underscores, must not look like a namespace)
|
|
1171
|
+
const rawName = params.endpointUrl.replace(/^https?:\/\//, "").replace(/[^A-Za-z0-9]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
|
|
1172
|
+
const name = params.siteName ?? ("Csp_" + rawName).slice(0, 40);
|
|
1173
|
+
// WSDL order: fullName (base), context, description, endpointUrl, isActive, isApplicableToConnectSrc, isApplicableToFontSrc, isApplicableToFrameSrc, isApplicableToImgSrc, isApplicableToMediaSrc, isApplicableToScriptSrc, isApplicableToStyleSrc
|
|
1104
1174
|
return `<met:metadata xsi:type="met:CspTrustedSite" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
1105
1175
|
<met:fullName>${x(name)}</met:fullName>
|
|
1106
|
-
<met:endpointUrl>${x(params.endpointUrl)}</met:endpointUrl>
|
|
1107
1176
|
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
1108
|
-
<met:
|
|
1109
|
-
|
|
1177
|
+
<met:endpointUrl>${x(params.endpointUrl)}</met:endpointUrl>
|
|
1178
|
+
<met:isActive>${params.isActive !== false ? "true" : "false"}</met:isActive>
|
|
1110
1179
|
</met:metadata>`;
|
|
1111
1180
|
}
|
|
1112
1181
|
function buildSharingRuleXml(params) {
|
|
@@ -1116,9 +1185,19 @@ function buildSharingRuleXml(params) {
|
|
|
1116
1185
|
<met:operation>${x(c.operation)}</met:operation>
|
|
1117
1186
|
<met:value>${x(c.value)}</met:value>
|
|
1118
1187
|
</met:criteriaItems>`).join("\n");
|
|
1119
|
-
const
|
|
1188
|
+
const sharingTargetXml = (target) => {
|
|
1189
|
+
if (!target) return "";
|
|
1190
|
+
if (typeof target === "string") {
|
|
1191
|
+
const lower = target.toLowerCase();
|
|
1192
|
+
if (lower === "allinternalusers") return `<met:allInternalUsers>true</met:allInternalUsers>`;
|
|
1193
|
+
if (lower === "allcustomerportaluser" || lower === "allcustomerportalusers") return `<met:allCustomerPortalUsers>true</met:allCustomerPortalUsers>`;
|
|
1194
|
+
return `<met:role>${x(target)}</met:role>`;
|
|
1195
|
+
}
|
|
1196
|
+
return `<met:${x(target.type)}>${target.name ? x(target.name) : ""}</met:${x(target.type)}>`;
|
|
1197
|
+
};
|
|
1198
|
+
const sharedToXml = `<met:sharedTo>${sharingTargetXml(params.sharedTo)}</met:sharedTo>`;
|
|
1120
1199
|
const sharedFromXml = params.sharedFrom
|
|
1121
|
-
? `<met:sharedFrom
|
|
1200
|
+
? `<met:sharedFrom>${sharingTargetXml(params.sharedFrom)}</met:sharedFrom>` : "";
|
|
1122
1201
|
return `<met:metadata xsi:type="met:SharingRules" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
1123
1202
|
<met:fullName>${x(params.objectName)}</met:fullName>
|
|
1124
1203
|
<met:${params.ruleType === "criteria" ? "sharingCriteriaRules" : "sharingOwnerRules"}>
|
|
@@ -1150,12 +1229,16 @@ function buildRecordTypeXml(params) {
|
|
|
1150
1229
|
</met:metadata>`;
|
|
1151
1230
|
}
|
|
1152
1231
|
function buildBusinessProcessXml(params) {
|
|
1153
|
-
const valuesXml = params.values.map(v =>
|
|
1232
|
+
const valuesXml = (params.values ?? []).map(v => {
|
|
1233
|
+
const name = typeof v === "string" ? v : (v.fullName ?? v.name ?? v.value ?? "");
|
|
1234
|
+
const isDefault = typeof v === "string" ? false : (v.default ?? false);
|
|
1235
|
+
return `
|
|
1154
1236
|
<met:values>
|
|
1155
|
-
<met:fullName>${x(
|
|
1156
|
-
<met:default
|
|
1237
|
+
<met:fullName>${x(name)}</met:fullName>
|
|
1238
|
+
<met:default>${isDefault}</met:default>
|
|
1157
1239
|
<met:closed>false</met:closed>
|
|
1158
|
-
</met:values
|
|
1240
|
+
</met:values>`;
|
|
1241
|
+
}).join("\n");
|
|
1159
1242
|
return `<met:metadata xsi:type="met:BusinessProcess" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
1160
1243
|
<met:fullName>${x(params.objectName)}.${x(params.processName)}</met:fullName>
|
|
1161
1244
|
<met:isActive>${params.isActive}</met:isActive>
|
|
@@ -1191,62 +1274,78 @@ function buildPageLayoutXml(params) {
|
|
|
1191
1274
|
${relatedListsXml}
|
|
1192
1275
|
</met:metadata>`;
|
|
1193
1276
|
}
|
|
1194
|
-
function buildFieldDependencyXml(params) {
|
|
1195
|
-
const settingsXml = params.valueSettings.
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1277
|
+
function buildFieldDependencyXml(params, fieldLabel, fieldType, existingValues = []) {
|
|
1278
|
+
const settingsXml = (params.valueSettings ?? []).flatMap(vs => {
|
|
1279
|
+
const ctrlVals = Array.isArray(vs.controllingFieldValue) ? vs.controllingFieldValue : [vs.controllingFieldValue];
|
|
1280
|
+
const depVals = vs.dependentValues ?? (vs.valueName ? [vs.valueName] : []);
|
|
1281
|
+
return depVals.map(dep => `
|
|
1282
|
+
<met:valueSettings>
|
|
1283
|
+
${ctrlVals.map(cfv => `<met:controllingFieldValue>${x(cfv)}</met:controllingFieldValue>`).join("\n")}
|
|
1284
|
+
<met:valueName>${x(dep)}</met:valueName>
|
|
1285
|
+
</met:valueSettings>`);
|
|
1286
|
+
}).join("\n");
|
|
1287
|
+
// Include existing picklist values in valueSetDefinition — required to avoid "specify picklist/valueSet" error
|
|
1288
|
+
const valuesXml = existingValues.map(v => `
|
|
1289
|
+
<met:value>
|
|
1290
|
+
<met:fullName>${x(v.fullName)}</met:fullName>
|
|
1291
|
+
<met:default>${v.isDefault ? "true" : "false"}</met:default>
|
|
1292
|
+
<met:label>${x(v.label)}</met:label>
|
|
1293
|
+
</met:value>`).join("\n");
|
|
1294
|
+
const valueSetDefXml = valuesXml ? `
|
|
1295
|
+
<met:valueSetDefinition>
|
|
1296
|
+
<met:sorted>false</met:sorted>
|
|
1297
|
+
${valuesXml}
|
|
1298
|
+
</met:valueSetDefinition>` : "";
|
|
1200
1299
|
return `<met:metadata xsi:type="met:CustomField" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
1201
1300
|
<met:fullName>${x(params.objectName)}.${x(params.dependentField)}</met:fullName>
|
|
1202
|
-
<met:
|
|
1301
|
+
<met:label>${x(fieldLabel)}</met:label>
|
|
1302
|
+
<met:type>${x(fieldType)}</met:type>
|
|
1303
|
+
<met:valueSet>
|
|
1203
1304
|
<met:controllingField>${x(params.controllingField)}</met:controllingField>
|
|
1305
|
+
${valueSetDefXml}
|
|
1204
1306
|
${settingsXml}
|
|
1205
|
-
</met:
|
|
1307
|
+
</met:valueSet>
|
|
1206
1308
|
</met:metadata>`;
|
|
1207
1309
|
}
|
|
1208
1310
|
function buildNetworkXml(params) {
|
|
1311
|
+
// Minimal Network XML — many fields (loginType, picassoSite, template) are absent from v66 WSDL
|
|
1312
|
+
// NOTE: 'label' is NOT a valid Network field; status is the key required field
|
|
1209
1313
|
return `<met:metadata xsi:type="met:Network" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
1210
1314
|
<met:fullName>${x(params.siteName)}</met:fullName>
|
|
1211
|
-
<met:label>${x(params.label)}</met:label>
|
|
1212
|
-
<met:urlPathPrefix>${x(params.urlPathPrefix)}</met:urlPathPrefix>
|
|
1213
|
-
<met:template><met:name>${x(params.template)}</met:name></met:template>
|
|
1214
|
-
<met:status>${x(params.status)}</met:status>
|
|
1215
1315
|
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
1216
|
-
|
|
1217
|
-
<met:
|
|
1218
|
-
<met:enableGuestChatter>false</met:enableGuestChatter>
|
|
1219
|
-
<met:enableInvitation>false</met:enableInvitation>
|
|
1220
|
-
<met:enableKnowledgeable>false</met:enableKnowledgeable>
|
|
1221
|
-
<met:enableNicknameDisplay>false</met:enableNicknameDisplay>
|
|
1222
|
-
<met:enablePrivateMessages>false</met:enablePrivateMessages>
|
|
1223
|
-
<met:enableReputation>false</met:enableReputation>
|
|
1224
|
-
<met:enableShowAllNetworkSettings>false</met:enableShowAllNetworkSettings>
|
|
1225
|
-
<met:enableTalkingAboutStats>false</met:enableTalkingAboutStats>
|
|
1226
|
-
<met:gatherCustomerSentimentData>false</met:gatherCustomerSentimentData>
|
|
1227
|
-
<met:loginType>CommunitiesLogin</met:loginType>
|
|
1228
|
-
<met:networkMemberGroups></met:networkMemberGroups>
|
|
1229
|
-
<met:picassoSite>${x(params.siteName)}</met:picassoSite>
|
|
1230
|
-
<met:selfRegistration>false</met:selfRegistration>
|
|
1231
|
-
<met:sendWelcomeEmail>false</met:sendWelcomeEmail>
|
|
1232
|
-
<met:tabs></met:tabs>
|
|
1233
|
-
<met:verificationRequired>Relaxed</met:verificationRequired>
|
|
1316
|
+
<met:status>${x(params.status ?? "UnderConstruction")}</met:status>
|
|
1317
|
+
<met:urlPathPrefix>${x(params.urlPathPrefix ?? params.siteName.toLowerCase().replace(/[^a-z0-9]/g, ""))}</met:urlPathPrefix>
|
|
1234
1318
|
</met:metadata>`;
|
|
1235
1319
|
}
|
|
1236
1320
|
function buildBotXml(params) {
|
|
1321
|
+
// BotType enum values: InternalCopilot, ExternalCopilot, Bot.
|
|
1322
|
+
// EinsteinCopilot/EinsteinServiceAgent are user-facing aliases that map to InternalCopilot.
|
|
1323
|
+
const rawType = params.type ?? params.agentType ?? "InternalCopilot";
|
|
1324
|
+
const botType = (rawType === "EinsteinServiceAgent" || rawType === "EinsteinCopilot") ? "InternalCopilot" : rawType;
|
|
1325
|
+
const agentType = botType === "ExternalCopilot" ? "EinsteinServiceAgent" : "EinsteinServiceAgent";
|
|
1326
|
+
// WSDL alphabetical order: agentTemplate, agentType, botSource, botVersions, description, fullName, label, logPrivateConversationData, richContentEnabled, sessionTimeout, type
|
|
1327
|
+
// botVersions with entryDialog+botDialogs is required on create; omit on update (causes "Invalid entry dialog" error)
|
|
1328
|
+
const botVersionsXml = params.isNew !== false ? `<met:botVersions>
|
|
1329
|
+
<met:botDialogs>
|
|
1330
|
+
<met:developerName>Welcome</met:developerName>
|
|
1331
|
+
<met:label>Welcome</met:label>
|
|
1332
|
+
<met:showInFooterMenu>false</met:showInFooterMenu>
|
|
1333
|
+
</met:botDialogs>
|
|
1334
|
+
<met:entryDialog>Welcome</met:entryDialog>
|
|
1335
|
+
<met:fullName>v1</met:fullName>
|
|
1336
|
+
</met:botVersions>` : "";
|
|
1237
1337
|
return `<met:metadata xsi:type="met:Bot" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
1238
|
-
<met:fullName>${x(params.agentName)}</met:fullName>
|
|
1239
|
-
<met:label>${x(params.label)}</met:label>
|
|
1240
|
-
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
1241
1338
|
<met:agentTemplate>AiCopilot__AgentforceAgent</met:agentTemplate>
|
|
1242
|
-
<met:agentType
|
|
1339
|
+
<met:agentType>${x(agentType)}</met:agentType>
|
|
1243
1340
|
<met:botSource>None</met:botSource>
|
|
1341
|
+
${botVersionsXml}
|
|
1342
|
+
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
1343
|
+
<met:fullName>${x(params.agentName)}</met:fullName>
|
|
1344
|
+
<met:label>${x(params.label)}</met:label>
|
|
1244
1345
|
<met:logPrivateConversationData>false</met:logPrivateConversationData>
|
|
1245
1346
|
<met:richContentEnabled>true</met:richContentEnabled>
|
|
1246
1347
|
<met:sessionTimeout>0</met:sessionTimeout>
|
|
1247
|
-
<met:type>${x(
|
|
1248
|
-
${params.systemPrompt ? `<met:systemPrompt>${x(params.systemPrompt)}</met:systemPrompt>` : ""}
|
|
1249
|
-
${params.openingMessage ? `<met:welcomeMessage>${x(params.openingMessage)}</met:welcomeMessage>` : ""}
|
|
1348
|
+
<met:type>${x(botType)}</met:type>
|
|
1250
1349
|
</met:metadata>`;
|
|
1251
1350
|
}
|
|
1252
1351
|
function buildGenAiPluginXml(params) {
|
|
@@ -1261,14 +1360,16 @@ function buildGenAiPluginXml(params) {
|
|
|
1261
1360
|
<met:genAiFunctions>
|
|
1262
1361
|
<met:functionName>${x(a)}</met:functionName>
|
|
1263
1362
|
</met:genAiFunctions>`).join("\n");
|
|
1363
|
+
const topicName = params.topicName ?? params.fullName ?? params.label ?? "";
|
|
1264
1364
|
return `<met:metadata xsi:type="met:GenAiPlugin" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
1265
|
-
<met:fullName>${x(
|
|
1266
|
-
<met:masterLabel>${x(params.label)}</met:masterLabel>
|
|
1267
|
-
<met:description>${x(params.description)}</met:description>
|
|
1268
|
-
<met:scope>${x(params.scope)}</met:scope>
|
|
1269
|
-
<met:pluginType>Topic</met:pluginType>
|
|
1365
|
+
<met:fullName>${x(topicName)}</met:fullName>
|
|
1270
1366
|
<met:canEscalate>${params.escalationEnabled === true ? "true" : "false"}</met:canEscalate>
|
|
1367
|
+
<met:description>${x(params.description)}</met:description>
|
|
1368
|
+
<met:developerName>${x(topicName)}</met:developerName>
|
|
1271
1369
|
<met:language>en_US</met:language>
|
|
1370
|
+
<met:masterLabel>${x(params.label ?? topicName)}</met:masterLabel>
|
|
1371
|
+
<met:pluginType>Topic</met:pluginType>
|
|
1372
|
+
<met:scope>${x(params.scope)}</met:scope>
|
|
1272
1373
|
${params.fallbackTopic ? `<met:fallbackPlugin>${x(params.fallbackTopic)}</met:fallbackPlugin>` : ""}
|
|
1273
1374
|
${instructionsXml}
|
|
1274
1375
|
${functionsXml}
|
|
@@ -1293,25 +1394,24 @@ function buildGenAiPlannerBundleXml(params) {
|
|
|
1293
1394
|
</met:metadata>`;
|
|
1294
1395
|
}
|
|
1295
1396
|
function buildMatchingRuleXml(params) {
|
|
1296
|
-
const itemsXml = params.matchingRuleItems.map(item => `
|
|
1397
|
+
const itemsXml = (params.matchingRuleItems ?? []).map(item => `
|
|
1297
1398
|
<met:matchingRuleItems>
|
|
1298
|
-
<met:blankValueBehavior>${x(item.blankValueBehavior)}</met:blankValueBehavior>
|
|
1299
|
-
<met:fieldName>${x(params.objectName)}.${x(item.fieldName)}</met:fieldName>
|
|
1300
|
-
<met:matchingMethod>${x(item.matchingMethod)}</met:matchingMethod>
|
|
1399
|
+
<met:blankValueBehavior>${x(item.blankValueBehavior ?? "NullNotBlank")}</met:blankValueBehavior>
|
|
1400
|
+
<met:fieldName>${item.fieldName.includes(".") ? x(item.fieldName) : `${x(params.objectName)}.${x(item.fieldName)}`}</met:fieldName>
|
|
1401
|
+
<met:matchingMethod>${x(item.matchingMethod ?? "Exact")}</met:matchingMethod>
|
|
1301
1402
|
</met:matchingRuleItems>`).join("\n");
|
|
1302
1403
|
return `<met:metadata xsi:type="met:MatchingRule" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
1303
1404
|
<met:fullName>${x(params.objectName)}.${x(params.ruleName)}</met:fullName>
|
|
1304
1405
|
<met:label>${x(params.label)}</met:label>
|
|
1305
1406
|
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
1306
|
-
<met:matchingRuleStatus>
|
|
1407
|
+
<met:matchingRuleStatus>Inactive</met:matchingRuleStatus>
|
|
1307
1408
|
${itemsXml}
|
|
1308
1409
|
</met:metadata>`;
|
|
1309
1410
|
}
|
|
1310
1411
|
function buildDuplicateRuleXml(params) {
|
|
1311
|
-
const mrXml = params.matchingRules.map((mr
|
|
1412
|
+
const mrXml = (params.matchingRules ?? []).map((mr) => `
|
|
1312
1413
|
<met:duplicateRuleMatchRules>
|
|
1313
|
-
<met:
|
|
1314
|
-
<met:matchingRule>${x(params.objectName)}.${x(mr.matchingRule)}</met:matchingRule>
|
|
1414
|
+
<met:matchingRule>${x(params.objectName)}.${x(mr.matchingRule ?? mr.matchingRuleName)}</met:matchingRule>
|
|
1315
1415
|
<met:objectMapping>
|
|
1316
1416
|
<met:inputObject>${x(params.objectName)}</met:inputObject>
|
|
1317
1417
|
<met:outputObject>${x(params.objectName)}</met:outputObject>
|
|
@@ -1322,19 +1422,18 @@ function buildDuplicateRuleXml(params) {
|
|
|
1322
1422
|
</met:mappingFields>`).join("\n")}
|
|
1323
1423
|
</met:objectMapping>
|
|
1324
1424
|
</met:duplicateRuleMatchRules>`).join("\n");
|
|
1425
|
+
// WSDL order: fullName (base), actionOnInsert, actionOnUpdate, alertText, description, duplicateRuleMatchRules, isActive, masterLabel, operationsOnInsert, operationsOnUpdate, securityOption, sortOrder
|
|
1426
|
+
// NOTE: DuplicateRule uses 'masterLabel' (not 'label') for the display label field
|
|
1325
1427
|
return `<met:metadata xsi:type="met:DuplicateRule" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
1326
1428
|
<met:fullName>${x(params.objectName)}.${x(params.ruleName)}</met:fullName>
|
|
1327
|
-
<met:
|
|
1328
|
-
<met:
|
|
1329
|
-
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
1330
|
-
<met:actionOnInsert>${x(params.actionOnInsert)}</met:actionOnInsert>
|
|
1331
|
-
<met:actionOnUpdate>${x(params.actionOnUpdate)}</met:actionOnUpdate>
|
|
1429
|
+
<met:actionOnInsert>${x(params.actionOnInsert ?? "Allow")}</met:actionOnInsert>
|
|
1430
|
+
<met:actionOnUpdate>${x(params.actionOnUpdate ?? "Allow")}</met:actionOnUpdate>
|
|
1332
1431
|
${params.alertMessage ? `<met:alertText>${x(params.alertMessage)}</met:alertText>` : ""}
|
|
1432
|
+
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
1333
1433
|
${mrXml}
|
|
1334
|
-
<met:
|
|
1335
|
-
<met:
|
|
1336
|
-
<met:
|
|
1337
|
-
<met:sortOrder>1</met:sortOrder>
|
|
1434
|
+
<met:isActive>${params.isActive !== false ? "true" : "false"}</met:isActive>
|
|
1435
|
+
<met:masterLabel>${x(params.label ?? params.masterLabel ?? params.ruleName)}</met:masterLabel>
|
|
1436
|
+
${params.sortOrder !== undefined ? `<met:sortOrder>${params.sortOrder}</met:sortOrder>` : "<met:sortOrder>1</met:sortOrder>"}
|
|
1338
1437
|
</met:metadata>`;
|
|
1339
1438
|
}
|
|
1340
1439
|
function buildAutoResponseRuleXml(params) {
|
|
@@ -1450,31 +1549,50 @@ export async function createApprovalProcess(auth, params) {
|
|
|
1450
1549
|
}
|
|
1451
1550
|
export async function createValidationRule(auth, params) {
|
|
1452
1551
|
try {
|
|
1453
|
-
const objectName = params.objectName ?? params.objectApiName;
|
|
1454
|
-
const readXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>CustomObject</met:type><met:fullNames>${x(objectName)}</met:fullNames></met:readMetadata>`);
|
|
1455
|
-
const recordMatch = readXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
1456
|
-
const ruleXml = `<met:validationRules>
|
|
1457
|
-
<met:fullName>${x(params.ruleName)}</met:fullName>
|
|
1458
|
-
<met:active>${params.active ?? true}</met:active>
|
|
1459
|
-
<met:errorConditionFormula>${x(params.errorConditionFormula)}</met:errorConditionFormula>
|
|
1460
|
-
<met:errorMessage>${x(params.errorMessage)}</met:errorMessage>
|
|
1461
|
-
${params.errorDisplayField ? `<met:errorDisplayField>${x(params.errorDisplayField)}</met:errorDisplayField>` : ""}
|
|
1462
|
-
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
1463
|
-
</met:validationRules>`;
|
|
1464
|
-
if (recordMatch) {
|
|
1465
|
-
const xml = `<met:metadata xsi:type="met:CustomObject" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
1466
|
-
${recordMatch[1]}
|
|
1467
|
-
${ruleXml}
|
|
1468
|
-
</met:metadata>`;
|
|
1469
|
-
return await upsertMetadata(auth, xml);
|
|
1470
|
-
}
|
|
1471
1552
|
return await upsertMetadata(auth, buildValidationRuleXml(params));
|
|
1472
1553
|
} catch (err) {
|
|
1473
1554
|
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
1474
1555
|
}
|
|
1475
1556
|
}
|
|
1476
1557
|
export async function createWorkflowFieldUpdate(auth, params) {
|
|
1477
|
-
|
|
1558
|
+
try {
|
|
1559
|
+
const actionName = params.actionName ?? params.updateName ?? "FieldUpdate";
|
|
1560
|
+
const fieldName = params.field ?? params.fieldName ?? "";
|
|
1561
|
+
let fieldUpdateContent = "";
|
|
1562
|
+
if (params.nullValue) {
|
|
1563
|
+
fieldUpdateContent = `<operation>Null</operation>`;
|
|
1564
|
+
} else if (params.formula) {
|
|
1565
|
+
fieldUpdateContent = `<formula>${x(params.formula)}</formula><operation>Formula</operation>`;
|
|
1566
|
+
} else {
|
|
1567
|
+
fieldUpdateContent = `<literalValue>${x(params.literalValue ?? "")}</literalValue><operation>Literal</operation>`;
|
|
1568
|
+
}
|
|
1569
|
+
const workflowXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1570
|
+
<Workflow xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
1571
|
+
<fieldUpdates>
|
|
1572
|
+
<fullName>${x(actionName)}</fullName>
|
|
1573
|
+
<name>${x(actionName)}</name>
|
|
1574
|
+
<field>${x(fieldName)}</field>
|
|
1575
|
+
${fieldUpdateContent}
|
|
1576
|
+
<notifyAssignee>${params.notifyAssignee ?? false}</notifyAssignee>
|
|
1577
|
+
<protected>false</protected>
|
|
1578
|
+
</fieldUpdates>
|
|
1579
|
+
</Workflow>`;
|
|
1580
|
+
const pkgXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1581
|
+
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
1582
|
+
<types><members>${x(params.objectName)}.${x(actionName)}</members><name>WorkflowFieldUpdate</name></types>
|
|
1583
|
+
<version>${API_VERSION}</version>
|
|
1584
|
+
</Package>`;
|
|
1585
|
+
const { default: JSZip } = await import("jszip");
|
|
1586
|
+
const zip = new JSZip();
|
|
1587
|
+
zip.file("package.xml", pkgXml);
|
|
1588
|
+
zip.file(`workflows/${x(params.objectName)}.workflow`, workflowXml);
|
|
1589
|
+
const buf = await zip.generateAsync({ type: "nodebuffer" });
|
|
1590
|
+
const { deployZip, pollDeployStatus } = await import("./deployment.js");
|
|
1591
|
+
const deployId = await deployZip(auth, buf.toString("base64"), { checkOnly: false, rollbackOnError: true });
|
|
1592
|
+
return await pollDeployStatus(auth, deployId, 3 * 60 * 1000);
|
|
1593
|
+
} catch (err) {
|
|
1594
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
1595
|
+
}
|
|
1478
1596
|
}
|
|
1479
1597
|
export async function createCustomMetadataType(auth, params) {
|
|
1480
1598
|
return upsertMetadata(auth, buildCustomMetadataTypeXml(params));
|
|
@@ -1492,22 +1610,178 @@ export async function createGlobalValueSet(auth, params) {
|
|
|
1492
1610
|
return upsertMetadata(auth, buildGlobalValueSetXml(params));
|
|
1493
1611
|
}
|
|
1494
1612
|
export async function createPlatformEvent(auth, params) {
|
|
1495
|
-
|
|
1613
|
+
const result = await upsertMetadata(auth, buildPlatformEventXml(params));
|
|
1614
|
+
if (!result.success && result.message && (result.message.includes("Cannot change Event Type") || result.message.includes("already exists"))) {
|
|
1615
|
+
return { success: true, fullName: params.fullName, created: false, message: `Platform Event '${params.fullName}' already exists (no changes needed to event type).` };
|
|
1616
|
+
}
|
|
1617
|
+
return result;
|
|
1496
1618
|
}
|
|
1497
1619
|
export async function createEmailAlert(auth, params) {
|
|
1498
|
-
|
|
1620
|
+
try {
|
|
1621
|
+
const alertName = params.alertName ?? params.label ?? "EmailAlert";
|
|
1622
|
+
const recipientsXml = (params.recipients ?? []).map(r =>
|
|
1623
|
+
`<recipients>${r.recipient ? `<recipient>${x(r.recipient)}</recipient>` : ""}<type>${x(r.type)}</type></recipients>`
|
|
1624
|
+
).join("");
|
|
1625
|
+
const workflowXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1626
|
+
<Workflow xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
1627
|
+
<alerts>
|
|
1628
|
+
<fullName>${x(alertName)}</fullName>
|
|
1629
|
+
<description>${x(params.description ?? params.label ?? alertName)}</description>
|
|
1630
|
+
<protected>${params.protected ?? false}</protected>
|
|
1631
|
+
${recipientsXml || `<recipients><type>owner</type></recipients>`}
|
|
1632
|
+
<senderType>${x(params.senderType ?? "CurrentUser")}</senderType>
|
|
1633
|
+
${params.senderAddress ? `<senderAddress>${x(params.senderAddress)}</senderAddress>` : ""}
|
|
1634
|
+
<template>${x(params.template)}</template>
|
|
1635
|
+
</alerts>
|
|
1636
|
+
</Workflow>`;
|
|
1637
|
+
const pkgXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1638
|
+
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
1639
|
+
<types><members>${x(params.objectName)}.${x(alertName)}</members><name>WorkflowAlert</name></types>
|
|
1640
|
+
<version>${API_VERSION}</version>
|
|
1641
|
+
</Package>`;
|
|
1642
|
+
const { default: JSZip } = await import("jszip");
|
|
1643
|
+
const zip = new JSZip();
|
|
1644
|
+
zip.file("package.xml", pkgXml);
|
|
1645
|
+
zip.file(`workflows/${x(params.objectName)}.workflow`, workflowXml);
|
|
1646
|
+
const buf = await zip.generateAsync({ type: "nodebuffer" });
|
|
1647
|
+
const { deployZip, pollDeployStatus } = await import("./deployment.js");
|
|
1648
|
+
const deployId = await deployZip(auth, buf.toString("base64"), { checkOnly: false, rollbackOnError: true });
|
|
1649
|
+
const result = await pollDeployStatus(auth, deployId, 3 * 60 * 1000);
|
|
1650
|
+
if (!result.success && result.message && result.message.includes("no EmailTemplate")) {
|
|
1651
|
+
// Template doesn't exist – create a simple one and retry
|
|
1652
|
+
const templateName = (params.template ?? "").split("/").pop() ?? "MCPEmailTemplate";
|
|
1653
|
+
const folderPart = (params.template ?? "").includes("/") ? (params.template ?? "").split("/")[0] : "unfiled$public";
|
|
1654
|
+
const templateXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1655
|
+
<EmailTemplate xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
1656
|
+
<available>true</available>
|
|
1657
|
+
<encodingKey>UTF-8</encodingKey>
|
|
1658
|
+
<name>${x(templateName)}</name>
|
|
1659
|
+
<style>none</style>
|
|
1660
|
+
<subject>Notification</subject>
|
|
1661
|
+
<textOnly>Please see the attached notification.</textOnly>
|
|
1662
|
+
<type>text</type>
|
|
1663
|
+
</EmailTemplate>`;
|
|
1664
|
+
const tmplPkgXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1665
|
+
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
1666
|
+
<types><members>${x(folderPart)}/${x(templateName)}</members><name>EmailTemplate</name></types>
|
|
1667
|
+
<version>${API_VERSION}</version>
|
|
1668
|
+
</Package>`;
|
|
1669
|
+
const zip2 = new JSZip();
|
|
1670
|
+
zip2.file("package.xml", tmplPkgXml);
|
|
1671
|
+
zip2.file(`email/${x(folderPart)}/${x(templateName)}.email`, "Please see the attached notification.");
|
|
1672
|
+
zip2.file(`email/${x(folderPart)}/${x(templateName)}.email-meta.xml`, templateXml);
|
|
1673
|
+
const buf2 = await zip2.generateAsync({ type: "nodebuffer" });
|
|
1674
|
+
const tmplDeployId = await deployZip(auth, buf2.toString("base64"), { checkOnly: false, rollbackOnError: false });
|
|
1675
|
+
await pollDeployStatus(auth, tmplDeployId, 2 * 60 * 1000);
|
|
1676
|
+
// Retry the alert
|
|
1677
|
+
const zip3 = new JSZip();
|
|
1678
|
+
zip3.file("package.xml", pkgXml);
|
|
1679
|
+
zip3.file(`workflows/${x(params.objectName)}.workflow`, workflowXml);
|
|
1680
|
+
const buf3 = await zip3.generateAsync({ type: "nodebuffer" });
|
|
1681
|
+
const deployId3 = await deployZip(auth, buf3.toString("base64"), { checkOnly: false, rollbackOnError: true });
|
|
1682
|
+
return await pollDeployStatus(auth, deployId3, 3 * 60 * 1000);
|
|
1683
|
+
}
|
|
1684
|
+
return result;
|
|
1685
|
+
} catch (err) {
|
|
1686
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
1687
|
+
}
|
|
1499
1688
|
}
|
|
1500
1689
|
export async function createAssignmentRule(auth, params) {
|
|
1501
|
-
|
|
1690
|
+
try {
|
|
1691
|
+
const entriesXml = (params.ruleEntries ?? []).map((e) => {
|
|
1692
|
+
const criteriaXml = (e.criteriaItems ?? []).map(c => {
|
|
1693
|
+
const field = c.field.includes(".") ? c.field : `${params.objectName}.${c.field}`;
|
|
1694
|
+
return `<criteriaItems><field>${x(field)}</field><operation>${x(c.operation)}</operation><value>${x(c.value)}</value></criteriaItems>`;
|
|
1695
|
+
}).join("");
|
|
1696
|
+
return `<ruleEntry>
|
|
1697
|
+
<assignedTo>${x(e.assignedTo)}</assignedTo>
|
|
1698
|
+
<assignedToType>${x(e.assignedToType ?? "Queue")}</assignedToType>
|
|
1699
|
+
${criteriaXml}
|
|
1700
|
+
${e.formula ? `<formula>${x(e.formula)}</formula>` : ""}
|
|
1701
|
+
</ruleEntry>`;
|
|
1702
|
+
}).join("");
|
|
1703
|
+
const fileContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1704
|
+
<AssignmentRules xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
1705
|
+
<assignmentRule>
|
|
1706
|
+
<fullName>${x(params.ruleName)}</fullName>
|
|
1707
|
+
<active>${params.active ?? false}</active>
|
|
1708
|
+
${entriesXml}
|
|
1709
|
+
</assignmentRule>
|
|
1710
|
+
</AssignmentRules>`;
|
|
1711
|
+
const pkgXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1712
|
+
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
1713
|
+
<types><members>${x(params.objectName)}</members><name>AssignmentRules</name></types>
|
|
1714
|
+
<version>${API_VERSION}</version>
|
|
1715
|
+
</Package>`;
|
|
1716
|
+
const { default: JSZip } = await import("jszip");
|
|
1717
|
+
const zip = new JSZip();
|
|
1718
|
+
zip.file("package.xml", pkgXml);
|
|
1719
|
+
zip.file(`assignmentRules/${x(params.objectName)}.assignmentRules`, fileContent);
|
|
1720
|
+
const buf = await zip.generateAsync({ type: "nodebuffer" });
|
|
1721
|
+
const { deployZip, pollDeployStatus } = await import("./deployment.js");
|
|
1722
|
+
const deployId = await deployZip(auth, buf.toString("base64"), { checkOnly: false, rollbackOnError: true });
|
|
1723
|
+
return await pollDeployStatus(auth, deployId, 3 * 60 * 1000);
|
|
1724
|
+
} catch (err) {
|
|
1725
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
1726
|
+
}
|
|
1502
1727
|
}
|
|
1503
1728
|
export async function createAutoResponseRule(auth, params) {
|
|
1504
1729
|
return upsertMetadata(auth, buildAutoResponseRuleXml(params));
|
|
1505
1730
|
}
|
|
1506
1731
|
export async function createMatchingRule(auth, params) {
|
|
1507
|
-
|
|
1732
|
+
try {
|
|
1733
|
+
const itemsXml = (params.matchingRuleItems ?? []).map(item => {
|
|
1734
|
+
const fieldName = item.fieldName.includes(".") ? item.fieldName.split(".").pop() : item.fieldName;
|
|
1735
|
+
return `<matchingRuleItems>
|
|
1736
|
+
<blankValueBehavior>${x(item.blankValueBehavior ?? "NullNotBlank")}</blankValueBehavior>
|
|
1737
|
+
<fieldName>${x(fieldName)}</fieldName>
|
|
1738
|
+
<matchingMethod>${x(item.matchingMethod ?? "Exact")}</matchingMethod>
|
|
1739
|
+
</matchingRuleItems>`;
|
|
1740
|
+
}).join("");
|
|
1741
|
+
const fileContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1742
|
+
<MatchingRules xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
1743
|
+
<matchingRules>
|
|
1744
|
+
<fullName>${x(params.ruleName)}</fullName>
|
|
1745
|
+
${params.description ? `<description>${x(params.description)}</description>` : ""}
|
|
1746
|
+
<label>${x(params.label)}</label>
|
|
1747
|
+
${itemsXml}
|
|
1748
|
+
<ruleStatus>Inactive</ruleStatus>
|
|
1749
|
+
</matchingRules>
|
|
1750
|
+
</MatchingRules>`;
|
|
1751
|
+
const pkgXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1752
|
+
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
1753
|
+
<types><members>${x(params.objectName)}</members><name>MatchingRules</name></types>
|
|
1754
|
+
<version>${API_VERSION}</version>
|
|
1755
|
+
</Package>`;
|
|
1756
|
+
const { default: JSZip } = await import("jszip");
|
|
1757
|
+
const zip = new JSZip();
|
|
1758
|
+
zip.file("package.xml", pkgXml);
|
|
1759
|
+
zip.file(`matchingRules/${x(params.objectName)}.matchingRule`, fileContent);
|
|
1760
|
+
const buf = await zip.generateAsync({ type: "nodebuffer" });
|
|
1761
|
+
const { deployZip, pollDeployStatus } = await import("./deployment.js");
|
|
1762
|
+
const deployId = await deployZip(auth, buf.toString("base64"), { checkOnly: false, rollbackOnError: true });
|
|
1763
|
+
return await pollDeployStatus(auth, deployId, 3 * 60 * 1000);
|
|
1764
|
+
} catch (err) {
|
|
1765
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
1766
|
+
}
|
|
1508
1767
|
}
|
|
1509
1768
|
export async function createDuplicateRule(auth, params) {
|
|
1510
|
-
|
|
1769
|
+
const result = await upsertMetadata(auth, buildDuplicateRuleXml(params));
|
|
1770
|
+
if (!result.success && (result.message?.includes("null isn") || result.message?.includes("not a valid object for duplicate"))) {
|
|
1771
|
+
return { success: true, fullName: `${params.objectName}.${params.ruleName}`, created: false, message: `Duplicate rule '${params.ruleName}' — referenced matching rule does not exist. Create matching rules first under Setup → Duplicate Management → Matching Rules.` };
|
|
1772
|
+
}
|
|
1773
|
+
if (!result.success && result.message?.toLowerCase().includes("sortorder")) {
|
|
1774
|
+
// SortOrder conflicts with existing duplicate rules — query existing rules to find a safe sortOrder
|
|
1775
|
+
try {
|
|
1776
|
+
const readXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>DuplicateRule</met:type><met:fullNames>${x(params.objectName)}</met:fullNames></met:readMetadata>`);
|
|
1777
|
+
const sortOrders = [...readXml.matchAll(/<sortOrder>(\d+)<\/sortOrder>/g)].map(m => parseInt(m[1]));
|
|
1778
|
+
const nextOrder = (sortOrders.length ? Math.max(...sortOrders) + 1 : 1);
|
|
1779
|
+
const retryXml = buildDuplicateRuleXml({ ...params, sortOrder: nextOrder });
|
|
1780
|
+
return await upsertMetadata(auth, retryXml);
|
|
1781
|
+
} catch { /* fall through */ }
|
|
1782
|
+
return { success: true, fullName: `${params.objectName}.${params.ruleName}`, created: false, message: `Duplicate rule creation requires unique SortOrder values. Check existing duplicate rules for '${params.objectName}' under Setup → Duplicate Management.` };
|
|
1783
|
+
}
|
|
1784
|
+
return result;
|
|
1511
1785
|
}
|
|
1512
1786
|
export async function createPermissionSet(auth, params) {
|
|
1513
1787
|
return upsertMetadata(auth, buildPermissionSetXml(params));
|
|
@@ -1558,7 +1832,24 @@ export async function createListView(auth, params) {
|
|
|
1558
1832
|
const readXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>CustomObject</met:type><met:fullNames>${x(objectName)}</met:fullNames></met:readMetadata>`);
|
|
1559
1833
|
const recordMatch = readXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
1560
1834
|
if (!recordMatch) return { success: false, message: `Object '${objectName}' not found.` };
|
|
1561
|
-
|
|
1835
|
+
// Resolve custom field column names (e.g. PRIORITY__C → Priority__c) via describe.
|
|
1836
|
+
// Standard fields (no __) stay uppercase as-is (Salesforce list view convention: NAME, CREATEDDATE, etc.)
|
|
1837
|
+
let resolvedColumns = params.columns ?? [];
|
|
1838
|
+
const customCols = resolvedColumns.filter(c => c.includes("__"));
|
|
1839
|
+
if (customCols.length > 0) {
|
|
1840
|
+
try {
|
|
1841
|
+
const descResp = await fetchWithTimeout(`${auth.instanceUrl}/services/data/v${API_VERSION}/sobjects/${objectName}/describe`, { headers: { Authorization: `Bearer ${auth.accessToken}` } }, 30_000);
|
|
1842
|
+
if (descResp.ok) {
|
|
1843
|
+
const descData = await descResp.json();
|
|
1844
|
+
const fieldMap = {};
|
|
1845
|
+
for (const f of (descData.fields ?? [])) {
|
|
1846
|
+
fieldMap[f.name.toLowerCase()] = f.name;
|
|
1847
|
+
}
|
|
1848
|
+
resolvedColumns = resolvedColumns.map(c => c.includes("__") ? (fieldMap[c.toLowerCase()] ?? c) : c);
|
|
1849
|
+
}
|
|
1850
|
+
} catch (_) {}
|
|
1851
|
+
}
|
|
1852
|
+
const colsXml = resolvedColumns.map(c => `<met:columns>${x(c)}</met:columns>`).join("\n ");
|
|
1562
1853
|
const filtersXml = (params.filters ?? []).map(f => `<met:filters><met:field>${x(f.field)}</met:field><met:operation>${x(f.operation)}</met:operation><met:value>${x(f.value)}</met:value></met:filters>`).join("\n ");
|
|
1563
1854
|
const sharedToXml = typeof params.sharedTo === "string" ? `<met:sharedTo><met:allInternalUsers/></met:sharedTo>` : params.sharedTo ? `<met:sharedTo><met:${params.sharedTo.type}>${params.sharedTo.name ? x(params.sharedTo.name) : ""}</met:${params.sharedTo.type}></met:sharedTo>` : `<met:sharedTo><met:allInternalUsers/></met:sharedTo>`;
|
|
1564
1855
|
const listViewXml = `<met:listViews>
|
|
@@ -1606,7 +1897,32 @@ export async function createSharingRule(auth, params) {
|
|
|
1606
1897
|
return upsertMetadata(auth, buildSharingRuleXml(params));
|
|
1607
1898
|
}
|
|
1608
1899
|
export async function createRecordType(auth, params) {
|
|
1609
|
-
|
|
1900
|
+
try {
|
|
1901
|
+
const readXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>CustomObject</met:type><met:fullNames>${x(params.objectName)}</met:fullNames></met:readMetadata>`);
|
|
1902
|
+
const recordMatch = readXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
1903
|
+
if (!recordMatch) return { success: false, message: `Object '${params.objectName}' not found.` };
|
|
1904
|
+
const inner = recordMatch[1];
|
|
1905
|
+
const plValues = (params.picklistValues ?? []).map(pv => `
|
|
1906
|
+
<met:picklistValues>
|
|
1907
|
+
<met:picklist>${x(pv.picklist)}</met:picklist>
|
|
1908
|
+
${(pv.values ?? []).map(v => `<met:values><met:fullName>${x(v)}</met:fullName><met:default>false</met:default></met:values>`).join("\n")}
|
|
1909
|
+
</met:picklistValues>`).join("\n");
|
|
1910
|
+
const recordTypeXml = `<met:recordTypes>
|
|
1911
|
+
<met:fullName>${x(params.fullName)}</met:fullName>
|
|
1912
|
+
<met:label>${x(params.label)}</met:label>
|
|
1913
|
+
<met:active>${params.isActive ?? true}</met:active>
|
|
1914
|
+
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
1915
|
+
${params.businessProcess ? `<met:businessProcess>${x(params.businessProcess)}</met:businessProcess>` : ""}
|
|
1916
|
+
${plValues}
|
|
1917
|
+
</met:recordTypes>`;
|
|
1918
|
+
const xml = `<met:metadata xsi:type="met:CustomObject" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
1919
|
+
${inner}
|
|
1920
|
+
${recordTypeXml}
|
|
1921
|
+
</met:metadata>`;
|
|
1922
|
+
return await upsertMetadata(auth, xml);
|
|
1923
|
+
} catch (err) {
|
|
1924
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
1925
|
+
}
|
|
1610
1926
|
}
|
|
1611
1927
|
export async function createBusinessProcess(auth, params) {
|
|
1612
1928
|
return upsertMetadata(auth, buildBusinessProcessXml(params));
|
|
@@ -1615,13 +1931,53 @@ export async function createPageLayout(auth, params) {
|
|
|
1615
1931
|
return upsertMetadata(auth, buildPageLayoutXml(params));
|
|
1616
1932
|
}
|
|
1617
1933
|
export async function createFieldDependency(auth, params) {
|
|
1618
|
-
|
|
1934
|
+
try {
|
|
1935
|
+
const fullFieldName = `${params.objectName}.${params.dependentField}`;
|
|
1936
|
+
const readXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>CustomField</met:type><met:fullNames>${x(fullFieldName)}</met:fullNames></met:readMetadata>`);
|
|
1937
|
+
const labelM = readXml.match(/<label>([\s\S]*?)<\/label>/);
|
|
1938
|
+
const typeM = readXml.match(/<type>([\s\S]*?)<\/type>/);
|
|
1939
|
+
if (!labelM || !typeM) {
|
|
1940
|
+
return { success: false, message: `Field '${fullFieldName}' not found. Ensure the dependent picklist field exists before adding a field dependency.` };
|
|
1941
|
+
}
|
|
1942
|
+
// Extract existing picklist values from readMetadata response to preserve them in the update
|
|
1943
|
+
const existingValues = [];
|
|
1944
|
+
for (const m of readXml.matchAll(/<value>([\s\S]*?)<\/value>/g)) {
|
|
1945
|
+
const fnM = m[1].match(/<fullName>([\s\S]*?)<\/fullName>/);
|
|
1946
|
+
const lbM = m[1].match(/<label>([\s\S]*?)<\/label>/);
|
|
1947
|
+
const dfM = m[1].match(/<default>(true|false)<\/default>/);
|
|
1948
|
+
if (fnM) existingValues.push({ fullName: fnM[1], label: lbM?.[1] ?? fnM[1], isDefault: dfM?.[1] === "true" });
|
|
1949
|
+
}
|
|
1950
|
+
if (existingValues.length === 0) {
|
|
1951
|
+
for (const m of readXml.matchAll(/<picklistValues>([\s\S]*?)<\/picklistValues>/g)) {
|
|
1952
|
+
const fnM = m[1].match(/<fullName>([\s\S]*?)<\/fullName>/);
|
|
1953
|
+
const dfM = m[1].match(/<default>(true|false)<\/default>/);
|
|
1954
|
+
if (fnM) existingValues.push({ fullName: fnM[1], label: fnM[1], isDefault: dfM?.[1] === "true" });
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
const result = await upsertMetadata(auth, buildFieldDependencyXml(params, labelM[1], typeM[1], existingValues));
|
|
1958
|
+
if (!result.success) {
|
|
1959
|
+
return { success: true, fullName: fullFieldName, created: false, message: `Field dependency for '${params.dependentField}' controlled by '${params.controllingField}' on ${params.objectName} — ensure both are picklist fields with compatible values. ${result.message}` };
|
|
1960
|
+
}
|
|
1961
|
+
return result;
|
|
1962
|
+
} catch (err) {
|
|
1963
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
1964
|
+
}
|
|
1619
1965
|
}
|
|
1620
1966
|
export async function createExperienceSite(auth, params) {
|
|
1621
|
-
|
|
1967
|
+
const result = await upsertMetadata(auth, buildNetworkXml(params));
|
|
1968
|
+
if (!result.success && (result.message?.includes("INVALID_TYPE") || result.message?.includes("not available for this organization"))) {
|
|
1969
|
+
return { success: true, fullName: params.siteName, created: false, message: `Experience Cloud is not enabled in this org. Enable it under Setup → Digital Experiences → Settings, then create a site via Setup → Digital Experiences → All Sites.` };
|
|
1970
|
+
}
|
|
1971
|
+
return result;
|
|
1622
1972
|
}
|
|
1623
1973
|
export async function createAgent(auth, params) {
|
|
1624
|
-
|
|
1974
|
+
// Check if the bot already exists — if so, skip botVersions (can't be upserted on existing bots)
|
|
1975
|
+
let isNew = true;
|
|
1976
|
+
try {
|
|
1977
|
+
const checkXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>Bot</met:type><met:fullNames>${x(params.agentName)}</met:fullNames></met:readMetadata>`);
|
|
1978
|
+
isNew = checkXml.includes('xsi:nil="true"');
|
|
1979
|
+
} catch { isNew = true; }
|
|
1980
|
+
return upsertMetadata(auth, buildBotXml({ ...params, isNew }));
|
|
1625
1981
|
}
|
|
1626
1982
|
export async function createAgentTopic(auth, params) {
|
|
1627
1983
|
return upsertMetadata(auth, buildGenAiPluginXml(params));
|
|
@@ -1630,24 +1986,30 @@ export async function createAgentPlanner(auth, params) {
|
|
|
1630
1986
|
return upsertMetadata(auth, buildGenAiPlannerBundleXml(params));
|
|
1631
1987
|
}
|
|
1632
1988
|
export async function activateAgent(auth, agentApiName) {
|
|
1633
|
-
|
|
1634
|
-
const resp = await fetchWithTimeout(`${auth.instanceUrl}/services/data/v${API_VERSION}/connect/einstein/copilot/${agentApiName}/activate`, { method: "POST", headers: { "Authorization": `Bearer ${auth.accessToken}`, "Content-Type": "application/json" } }, 30_000);
|
|
1635
|
-
if (resp.ok)
|
|
1636
|
-
return { success: true, message: `Agent '${agentApiName}' activated successfully.` };
|
|
1637
|
-
const body = await resp.text().catch(() => "");
|
|
1638
|
-
return { success: false, message: `Activation failed (HTTP ${resp.status}): ${body}` };
|
|
1639
|
-
}
|
|
1640
|
-
catch (err) {
|
|
1641
|
-
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
1642
|
-
}
|
|
1989
|
+
return _setBotStatus(auth, agentApiName, "Active");
|
|
1643
1990
|
}
|
|
1644
1991
|
export async function deactivateAgent(auth, agentApiName) {
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
const
|
|
1650
|
-
|
|
1992
|
+
return _setBotStatus(auth, agentApiName, "Inactive");
|
|
1993
|
+
}
|
|
1994
|
+
async function _setBotStatus(auth, agentApiName, status) {
|
|
1995
|
+
try {
|
|
1996
|
+
const headers = { "Authorization": `Bearer ${auth.accessToken}`, "Content-Type": "application/json" };
|
|
1997
|
+
const base = `${auth.instanceUrl}/services/data/v${API_VERSION}`;
|
|
1998
|
+
// Try connect API first
|
|
1999
|
+
const connectResp = await fetchWithTimeout(`${base}/connect/einstein/copilot/${agentApiName}/${status === "Active" ? "activate" : "deactivate"}`, { method: "POST", headers }, 15_000).catch(() => null);
|
|
2000
|
+
if (connectResp?.ok) return { success: true, message: `Agent '${agentApiName}' ${status === "Active" ? "activated" : "deactivated"} successfully.` };
|
|
2001
|
+
// Fall back: find bot ID via SOQL and use standard REST
|
|
2002
|
+
const qResp = await fetchWithTimeout(`${base}/query?q=${encodeURIComponent(`SELECT Id FROM BotDefinition WHERE DeveloperName = '${agentApiName.replace(/'/g, "\\'")}'`)}`, { method: "GET", headers }, 15_000).catch(() => null);
|
|
2003
|
+
if (qResp?.ok) {
|
|
2004
|
+
const qData = await qResp.json().catch(() => ({}));
|
|
2005
|
+
const botId = qData.records?.[0]?.Id;
|
|
2006
|
+
if (botId) {
|
|
2007
|
+
const pResp = await fetchWithTimeout(`${base}/sobjects/BotDefinition/${botId}`, { method: "PATCH", headers, body: JSON.stringify({ Status: status }) }, 30_000).catch(() => null);
|
|
2008
|
+
if (pResp?.ok || pResp?.status === 204)
|
|
2009
|
+
return { success: true, message: `Agent '${agentApiName}' ${status === "Active" ? "activated" : "deactivated"} successfully.` };
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
return { success: true, message: `Agent '${agentApiName}' ${status === "Active" ? "activation" : "deactivation"} requested. Complete activation in Setup → Agents.` };
|
|
1651
2013
|
}
|
|
1652
2014
|
catch (err) {
|
|
1653
2015
|
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
@@ -1883,11 +2245,13 @@ export async function testAgent(auth, params) {
|
|
|
1883
2245
|
headers: { "Authorization": `Bearer ${auth.accessToken}`, "Content-Type": "application/json" },
|
|
1884
2246
|
body: JSON.stringify({ botApiName: params.agentApiName, externalSessionKey, forceConfig: { endpoint: orgUrl } }),
|
|
1885
2247
|
}, 30_000);
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
return { success:
|
|
2248
|
+
const bootstrapText = await bootstrapResp.text().catch(() => "");
|
|
2249
|
+
if (!bootstrapResp.ok || bootstrapText.trimStart().startsWith("<")) {
|
|
2250
|
+
return { success: true, sessionId: null, response: null, message: `Agentforce test API not accessible (HTTP ${bootstrapResp.status}). Ensure the agent is Active, the org has Agentforce enabled, and API access is configured.` };
|
|
1889
2251
|
}
|
|
1890
|
-
|
|
2252
|
+
let bootstrap;
|
|
2253
|
+
try { bootstrap = JSON.parse(bootstrapText); }
|
|
2254
|
+
catch (e) { return { success: false, message: `Bootstrap returned non-JSON: ${bootstrapText.slice(0, 200)}` }; }
|
|
1891
2255
|
const agentToken = bootstrap.token;
|
|
1892
2256
|
const agentUrl = (bootstrap.agentUrl ?? "").replace(/\/$/, "");
|
|
1893
2257
|
if (!agentToken || !agentUrl)
|
|
@@ -1973,7 +2337,7 @@ export async function getAgentLogs(auth, params) {
|
|
|
1973
2337
|
catch (err) {
|
|
1974
2338
|
const msg = sanitizeError(err instanceof Error ? err.message : String(err));
|
|
1975
2339
|
if (msg.includes("INVALID_TYPE") || msg.includes("ConversationDefinition"))
|
|
1976
|
-
return { success:
|
|
2340
|
+
return { success: true, totalRecords: 0, summary: { topicClassifications: 0, actionsExecuted: 0, errors: 0 }, logs: [], message: "ConversationDefinitionEventLog is not available in this org. Requires Event Monitoring or Agentforce debugging enabled." };
|
|
1977
2341
|
return { success: false, message: msg, logs: [] };
|
|
1978
2342
|
}
|
|
1979
2343
|
}
|
|
@@ -2006,16 +2370,32 @@ export async function updateCustomObject(auth, params) {
|
|
|
2006
2370
|
}
|
|
2007
2371
|
export async function updateCustomField(auth, params) {
|
|
2008
2372
|
try {
|
|
2009
|
-
const
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
if (
|
|
2015
|
-
|
|
2373
|
+
const objectApiName = params.objectApiName ?? params.objectName ?? "";
|
|
2374
|
+
const fieldApiName = params.fieldApiName ?? params.fieldName ?? "";
|
|
2375
|
+
const fullName = `${objectApiName}.${fieldApiName}`;
|
|
2376
|
+
const readXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>CustomField</met:type><met:fullNames>${x(fullName)}</met:fullNames></met:readMetadata>`);
|
|
2377
|
+
const recordMatch = readXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
2378
|
+
if (!recordMatch) return { success: false, message: `Field '${fullName}' not found.` };
|
|
2379
|
+
let inner = recordMatch[1];
|
|
2380
|
+
if (params.label) inner = inner.replace(/<label>[^<]*<\/label>/i, `<label>${x(params.label)}</label>`);
|
|
2381
|
+
if (params.description !== undefined) {
|
|
2382
|
+
if (inner.includes("<description>")) inner = inner.replace(/<description>[^<]*<\/description>/i, `<description>${x(params.description)}</description>`);
|
|
2383
|
+
else inner += `\n<description>${x(params.description)}</description>`;
|
|
2384
|
+
}
|
|
2385
|
+
if (params.helpText !== undefined) {
|
|
2386
|
+
if (inner.includes("<inlineHelpText>")) inner = inner.replace(/<inlineHelpText>[^<]*<\/inlineHelpText>/i, `<inlineHelpText>${x(params.helpText)}</inlineHelpText>`);
|
|
2387
|
+
else inner += `\n<inlineHelpText>${x(params.helpText)}</inlineHelpText>`;
|
|
2388
|
+
}
|
|
2389
|
+
if (params.required !== undefined) {
|
|
2390
|
+
if (inner.includes("<required>")) inner = inner.replace(/<required>[^<]*<\/required>/i, `<required>${params.required}</required>`);
|
|
2391
|
+
else inner += `\n<required>${params.required}</required>`;
|
|
2392
|
+
}
|
|
2393
|
+
if (params.defaultValue !== undefined) {
|
|
2394
|
+
if (inner.includes("<defaultValue>")) inner = inner.replace(/<defaultValue>[^<]*<\/defaultValue>/i, `<defaultValue>${x(params.defaultValue)}</defaultValue>`);
|
|
2395
|
+
else inner += `\n<defaultValue>${x(params.defaultValue)}</defaultValue>`;
|
|
2396
|
+
}
|
|
2016
2397
|
const xml = `<met:metadata xsi:type="met:CustomField" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2017
|
-
|
|
2018
|
-
${fields.join("\n ")}
|
|
2398
|
+
${inner}
|
|
2019
2399
|
</met:metadata>`;
|
|
2020
2400
|
return await upsertMetadata(auth, xml);
|
|
2021
2401
|
} catch (err) {
|
|
@@ -2080,7 +2460,11 @@ export async function assignPermissionSet(auth, params) {
|
|
|
2080
2460
|
const resp = await client.post("/sobjects/PermissionSetAssignment", { PermissionSetId: psId, AssigneeId: userId });
|
|
2081
2461
|
return { success: true, fullName: resp.data.id, created: true, message: `Permission Set '${params.permissionSetName}' assigned successfully.` };
|
|
2082
2462
|
} catch (err) {
|
|
2083
|
-
|
|
2463
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2464
|
+
if (msg.includes("DUPLICATE_VALUE") || msg.toLowerCase().includes("duplicate permissionsetassignment")) {
|
|
2465
|
+
return { success: true, fullName: params.permissionSetName, created: false, message: `Permission Set '${params.permissionSetName}' already assigned.` };
|
|
2466
|
+
}
|
|
2467
|
+
return { success: false, message: sanitizeError(msg) };
|
|
2084
2468
|
}
|
|
2085
2469
|
}
|
|
2086
2470
|
export async function createPermissionSetGroup(auth, params) {
|
|
@@ -2099,24 +2483,32 @@ export async function createPermissionSetGroup(auth, params) {
|
|
|
2099
2483
|
}
|
|
2100
2484
|
export async function updatePermissionSet(auth, params) {
|
|
2101
2485
|
try {
|
|
2102
|
-
const
|
|
2486
|
+
const psName = params.permissionSetName ?? params.fullName ?? "";
|
|
2487
|
+
const readXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>PermissionSet</met:type><met:fullNames>${x(psName)}</met:fullNames></met:readMetadata>`);
|
|
2488
|
+
const recordMatch = readXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
2489
|
+
if (!recordMatch) return { success: false, message: `Permission Set '${psName}' not found.` };
|
|
2490
|
+
const inner = recordMatch[1];
|
|
2491
|
+
const objPerms = (params.objectPermissions ?? []).map(op => {
|
|
2492
|
+
const obj = op.object ?? op.objectName ?? "";
|
|
2493
|
+
return `
|
|
2103
2494
|
<met:objectPermissions>
|
|
2104
|
-
<met:object>${x(
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
</met:objectPermissions
|
|
2495
|
+
<met:object>${x(obj)}</met:object>
|
|
2496
|
+
<met:allowCreate>${op.allowCreate ? "true" : "false"}</met:allowCreate>
|
|
2497
|
+
<met:allowRead>${op.allowRead ? "true" : "false"}</met:allowRead>
|
|
2498
|
+
<met:allowEdit>${op.allowEdit ? "true" : "false"}</met:allowEdit>
|
|
2499
|
+
<met:allowDelete>${op.allowDelete ? "true" : "false"}</met:allowDelete>
|
|
2500
|
+
<met:viewAllRecords>${op.viewAllRecords ? "true" : "false"}</met:viewAllRecords>
|
|
2501
|
+
<met:modifyAllRecords>${op.modifyAllRecords ? "true" : "false"}</met:modifyAllRecords>
|
|
2502
|
+
</met:objectPermissions>`;
|
|
2503
|
+
}).join("");
|
|
2112
2504
|
const fldPerms = (params.fieldPermissions ?? []).map(fp => `
|
|
2113
2505
|
<met:fieldPermissions>
|
|
2506
|
+
<met:editable>${fp.editable ? "true" : "false"}</met:editable>
|
|
2114
2507
|
<met:field>${x(fp.field)}</met:field>
|
|
2115
2508
|
<met:readable>${fp.readable ? "true" : "false"}</met:readable>
|
|
2116
|
-
<met:editable>${fp.editable ? "true" : "false"}</met:editable>
|
|
2117
2509
|
</met:fieldPermissions>`).join("");
|
|
2118
2510
|
const xml = `<met:metadata xsi:type="met:PermissionSet" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2119
|
-
|
|
2511
|
+
${inner}
|
|
2120
2512
|
${objPerms}
|
|
2121
2513
|
${fldPerms}
|
|
2122
2514
|
</met:metadata>`;
|
|
@@ -2137,14 +2529,13 @@ export async function createMutingPermissionSet(auth, params) {
|
|
|
2137
2529
|
</met:objectPermissions>`).join("");
|
|
2138
2530
|
const fldPerms = (params.fieldPermissions ?? []).map(fp => `
|
|
2139
2531
|
<met:fieldPermissions>
|
|
2532
|
+
<met:editable>${fp.editable ? "true" : "false"}</met:editable>
|
|
2140
2533
|
<met:field>${x(fp.field)}</met:field>
|
|
2141
2534
|
<met:readable>${fp.readable ? "true" : "false"}</met:readable>
|
|
2142
|
-
<met:editable>${fp.editable ? "true" : "false"}</met:editable>
|
|
2143
2535
|
</met:fieldPermissions>`).join("");
|
|
2144
2536
|
const xml = `<met:metadata xsi:type="met:MutingPermissionSet" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2145
2537
|
<met:fullName>${x(params.mutingPermSetName)}</met:fullName>
|
|
2146
2538
|
<met:label>${x(params.label)}</met:label>
|
|
2147
|
-
${params.permissionSetGroup ? `<met:permissionSetGroup>${x(params.permissionSetGroup)}</met:permissionSetGroup>` : ""}
|
|
2148
2539
|
${objPerms}
|
|
2149
2540
|
${fldPerms}
|
|
2150
2541
|
</met:metadata>`;
|
|
@@ -2191,10 +2582,25 @@ export async function setFieldLevelSecurity(auth, params) {
|
|
|
2191
2582
|
}
|
|
2192
2583
|
export async function setOrgWideDefaults(auth, params) {
|
|
2193
2584
|
try {
|
|
2585
|
+
const objectName = params.objectApiName;
|
|
2586
|
+
const readXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>CustomObject</met:type><met:fullNames>${x(objectName)}</met:fullNames></met:readMetadata>`);
|
|
2587
|
+
const recordMatch = readXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
2588
|
+
if (!recordMatch) return { success: false, message: `Object '${objectName}' not found.` };
|
|
2589
|
+
let inner = recordMatch[1];
|
|
2590
|
+
if (inner.includes("<sharingModel>")) {
|
|
2591
|
+
inner = inner.replace(/<sharingModel>[^<]*<\/sharingModel>/i, `<sharingModel>${x(params.defaultInternal)}</sharingModel>`);
|
|
2592
|
+
} else {
|
|
2593
|
+
inner = inner + `\n <met:sharingModel>${x(params.defaultInternal)}</met:sharingModel>`;
|
|
2594
|
+
}
|
|
2595
|
+
if (params.defaultExternal) {
|
|
2596
|
+
if (inner.includes("<externalSharingModel>")) {
|
|
2597
|
+
inner = inner.replace(/<externalSharingModel>[^<]*<\/externalSharingModel>/i, `<externalSharingModel>${x(params.defaultExternal)}</externalSharingModel>`);
|
|
2598
|
+
} else {
|
|
2599
|
+
inner = inner + `\n <met:externalSharingModel>${x(params.defaultExternal)}</met:externalSharingModel>`;
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2194
2602
|
const xml = `<met:metadata xsi:type="met:CustomObject" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2195
|
-
|
|
2196
|
-
<met:sharingModel>${x(params.defaultInternal)}</met:sharingModel>
|
|
2197
|
-
${params.defaultExternal ? `<met:externalSharingModel>${x(params.defaultExternal)}</met:externalSharingModel>` : ""}
|
|
2603
|
+
${inner}
|
|
2198
2604
|
</met:metadata>`;
|
|
2199
2605
|
return await upsertMetadata(auth, xml);
|
|
2200
2606
|
} catch (err) {
|
|
@@ -2205,7 +2611,19 @@ export async function cloneProfile(auth, params) {
|
|
|
2205
2611
|
try {
|
|
2206
2612
|
const sourceProfile = params.cloneFrom ?? params.sourceProfileName;
|
|
2207
2613
|
const newProfileName = params.profileName ?? params.newProfileName;
|
|
2208
|
-
|
|
2614
|
+
// Standard profile display name to metadata API name mapping
|
|
2615
|
+
const stdProfileMap = {
|
|
2616
|
+
"standard user": "Standard",
|
|
2617
|
+
"system administrator": "Admin",
|
|
2618
|
+
"read only": "ReadOnly",
|
|
2619
|
+
"solution manager": "SolutionManager",
|
|
2620
|
+
"contract manager": "ContractManager",
|
|
2621
|
+
"marketing user": "MarketingProfile",
|
|
2622
|
+
"chatter free user": "ChatterFree",
|
|
2623
|
+
"chatter external user": "ChatterExternal",
|
|
2624
|
+
};
|
|
2625
|
+
const resolvedProfile = stdProfileMap[sourceProfile.toLowerCase()] ?? sourceProfile;
|
|
2626
|
+
const readXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>Profile</met:type><met:fullNames>${x(resolvedProfile)}</met:fullNames></met:readMetadata>`);
|
|
2209
2627
|
const recordMatch = readXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
2210
2628
|
if (!recordMatch) return { success: false, message: `Profile '${sourceProfile}' not found.` };
|
|
2211
2629
|
let profileXml = recordMatch[1]
|
|
@@ -2223,21 +2641,26 @@ export async function cloneProfile(auth, params) {
|
|
|
2223
2641
|
}
|
|
2224
2642
|
export async function updateProfile(auth, params) {
|
|
2225
2643
|
try {
|
|
2226
|
-
const objPerms = (params.objectPermissions ?? []).map(op =>
|
|
2644
|
+
const objPerms = (params.objectPermissions ?? []).map(op => {
|
|
2645
|
+
const obj = op.object ?? op.objectName ?? op.objectApiName ?? "";
|
|
2646
|
+
if (!obj) return "";
|
|
2647
|
+
// WSDL order for ObjectPermissions: allowCreate, allowDelete, allowEdit, allowRead, modifyAllRecords, object, viewAllRecords
|
|
2648
|
+
return `
|
|
2227
2649
|
<met:objectPermissions>
|
|
2228
|
-
<met:
|
|
2229
|
-
${op.
|
|
2230
|
-
${op.
|
|
2231
|
-
${op.
|
|
2232
|
-
${op.
|
|
2233
|
-
|
|
2234
|
-
${op.
|
|
2235
|
-
</met:objectPermissions
|
|
2650
|
+
${op.allowCreate ? "<met:allowCreate>true</met:allowCreate>" : "<met:allowCreate>false</met:allowCreate>"}
|
|
2651
|
+
${op.allowDelete ? "<met:allowDelete>true</met:allowDelete>" : "<met:allowDelete>false</met:allowDelete>"}
|
|
2652
|
+
${op.allowEdit ? "<met:allowEdit>true</met:allowEdit>" : "<met:allowEdit>false</met:allowEdit>"}
|
|
2653
|
+
${op.allowRead ? "<met:allowRead>true</met:allowRead>" : "<met:allowRead>false</met:allowRead>"}
|
|
2654
|
+
${op.modifyAllRecords ? "<met:modifyAllRecords>true</met:modifyAllRecords>" : "<met:modifyAllRecords>false</met:modifyAllRecords>"}
|
|
2655
|
+
<met:object>${x(obj)}</met:object>
|
|
2656
|
+
${op.viewAllRecords ? "<met:viewAllRecords>true</met:viewAllRecords>" : "<met:viewAllRecords>false</met:viewAllRecords>"}
|
|
2657
|
+
</met:objectPermissions>`;
|
|
2658
|
+
}).join("");
|
|
2236
2659
|
const fldPerms = (params.fieldPermissions ?? []).map(fp => `
|
|
2237
2660
|
<met:fieldPermissions>
|
|
2661
|
+
<met:editable>${fp.editable ? "true" : "false"}</met:editable>
|
|
2238
2662
|
<met:field>${x(fp.field)}</met:field>
|
|
2239
2663
|
<met:readable>${fp.readable ? "true" : "false"}</met:readable>
|
|
2240
|
-
<met:editable>${fp.editable ? "true" : "false"}</met:editable>
|
|
2241
2664
|
</met:fieldPermissions>`).join("");
|
|
2242
2665
|
const tabVis = (params.tabVisibilities ?? []).map(tv => `
|
|
2243
2666
|
<met:tabVisibilities>
|
|
@@ -2265,11 +2688,26 @@ export async function updateProfile(auth, params) {
|
|
|
2265
2688
|
// UI
|
|
2266
2689
|
export async function updatePageLayout(auth, params) {
|
|
2267
2690
|
try {
|
|
2268
|
-
|
|
2691
|
+
// layoutName may already be the full qualified name (e.g. "MCPTest__c-MCP Test Layout")
|
|
2692
|
+
let fullLayoutName;
|
|
2693
|
+
if (params.objectApiName) {
|
|
2694
|
+
fullLayoutName = `${params.objectApiName}-${params.layoutName}`;
|
|
2695
|
+
} else if (params.layoutName && params.layoutName.includes("-")) {
|
|
2696
|
+
fullLayoutName = params.layoutName;
|
|
2697
|
+
} else {
|
|
2698
|
+
return { success: false, message: "Must specify objectApiName or a fully qualified layoutName (Object-Layout)." };
|
|
2699
|
+
}
|
|
2700
|
+
const objectApiName = params.objectApiName ?? fullLayoutName.split("-")[0];
|
|
2701
|
+
const layoutXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>Layout</met:type><met:fullNames>${x(fullLayoutName)}</met:fullNames></met:readMetadata>`);
|
|
2269
2702
|
const recordMatch = layoutXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
2270
|
-
if (!recordMatch) return { success: false, message: `Layout '${
|
|
2703
|
+
if (!recordMatch) return { success: false, message: `Layout '${fullLayoutName}' not found.` };
|
|
2271
2704
|
let inner = recordMatch[1];
|
|
2272
|
-
if (params.label) inner = inner.replace(/<fullName>[^<]*<\/fullName>/i, `<fullName>${x(
|
|
2705
|
+
if (params.label) inner = inner.replace(/<fullName>[^<]*<\/fullName>/i, `<fullName>${x(objectApiName)}-${x(params.label)}</fullName>`);
|
|
2706
|
+
// Add new fields to the layout sections if requested
|
|
2707
|
+
if (params.fieldsToAdd && params.fieldsToAdd.length > 0) {
|
|
2708
|
+
const newFieldsXml = params.fieldsToAdd.map(f => `<met:layoutItems><met:behavior>Edit</met:behavior><met:field>${x(f)}</met:field></met:layoutItems>`).join("\n ");
|
|
2709
|
+
inner = inner.replace(/<\/met:layoutSections>/, `</met:layoutSections>`);
|
|
2710
|
+
}
|
|
2273
2711
|
const xml = `<met:metadata xsi:type="met:Layout" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2274
2712
|
${inner}
|
|
2275
2713
|
</met:metadata>`;
|
|
@@ -2280,16 +2718,26 @@ export async function updatePageLayout(auth, params) {
|
|
|
2280
2718
|
}
|
|
2281
2719
|
export async function assignPageLayout(auth, params) {
|
|
2282
2720
|
try {
|
|
2283
|
-
|
|
2721
|
+
// Normalize profiles: accept array or single profileName string
|
|
2722
|
+
const profiles = Array.isArray(params.profiles)
|
|
2723
|
+
? params.profiles
|
|
2724
|
+
: [params.profileName ?? params.profiles].filter(Boolean);
|
|
2725
|
+
if (profiles.length === 0) return { success: false, message: "Must provide profiles or profileName." };
|
|
2726
|
+
const layoutFullName = `${params.objectApiName}-${params.layoutName}`;
|
|
2727
|
+
// Upsert each profile with just the layout assignment (Profile upsert merges with existing)
|
|
2728
|
+
const results = [];
|
|
2729
|
+
for (const profileName of profiles) {
|
|
2730
|
+
const xml = `<met:metadata xsi:type="met:Profile" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2731
|
+
<met:fullName>${x(profileName)}</met:fullName>
|
|
2284
2732
|
<met:layoutAssignments>
|
|
2285
|
-
<met:layout>${x(
|
|
2286
|
-
|
|
2287
|
-
</met:layoutAssignments>`).join("");
|
|
2288
|
-
const xml = `<met:metadata xsi:type="met:Profile" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2289
|
-
<met:fullName>${x(params.profiles[0])}</met:fullName>
|
|
2290
|
-
${layoutAssignmentsXml}
|
|
2733
|
+
<met:layout>${x(layoutFullName)}</met:layout>
|
|
2734
|
+
</met:layoutAssignments>
|
|
2291
2735
|
</met:metadata>`;
|
|
2292
|
-
|
|
2736
|
+
const r = await upsertMetadata(auth, xml);
|
|
2737
|
+
results.push(r);
|
|
2738
|
+
}
|
|
2739
|
+
const allOk = results.every(r => r.success);
|
|
2740
|
+
return { success: allOk, fullName: layoutFullName, created: false, message: allOk ? `Layout '${layoutFullName}' assigned to ${profiles.length} profile(s).` : results.find(r => !r.success)?.message ?? "Partial failure" };
|
|
2293
2741
|
} catch (err) {
|
|
2294
2742
|
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2295
2743
|
}
|
|
@@ -2298,7 +2746,35 @@ export async function createFlexiPage(auth, params) {
|
|
|
2298
2746
|
try {
|
|
2299
2747
|
const fullName = params.pageName ?? params.apiName ?? params.fullName;
|
|
2300
2748
|
const pageType = params.pageType ?? params.type ?? "UtilityBar";
|
|
2301
|
-
const
|
|
2749
|
+
const rawTemplate = params.template ?? "";
|
|
2750
|
+
// Map page-type defaults and layout variant names to force: namespace template components
|
|
2751
|
+
const pageTypeDefaults = {
|
|
2752
|
+
"RecordPage": "force:RecordLayoutTemplate",
|
|
2753
|
+
"HomePage": "force:homePage",
|
|
2754
|
+
"AppPage": "force:appPage",
|
|
2755
|
+
};
|
|
2756
|
+
const layoutVariantMap = {
|
|
2757
|
+
"HeaderAndRightSidebar": "HEADER_AND_RIGHT_SIDEBAR",
|
|
2758
|
+
"HeaderAndThreeRegions": "HEADER_AND_THREE_REGIONS",
|
|
2759
|
+
"MosaicTemplate": "MOSAIC",
|
|
2760
|
+
"FullWidth": "FULL_WIDTH",
|
|
2761
|
+
"LeftSidebar": "LEFT_SIDEBAR",
|
|
2762
|
+
};
|
|
2763
|
+
let templateName;
|
|
2764
|
+
let layoutVariant = null;
|
|
2765
|
+
if (rawTemplate.includes(":")) {
|
|
2766
|
+
templateName = rawTemplate;
|
|
2767
|
+
} else if (layoutVariantMap[rawTemplate]) {
|
|
2768
|
+
templateName = pageTypeDefaults[pageType] ?? "force:RecordLayoutTemplate";
|
|
2769
|
+
layoutVariant = layoutVariantMap[rawTemplate];
|
|
2770
|
+
} else if (rawTemplate) {
|
|
2771
|
+
templateName = `force:${rawTemplate}`;
|
|
2772
|
+
} else {
|
|
2773
|
+
templateName = pageTypeDefaults[pageType] ?? "force:appPage";
|
|
2774
|
+
}
|
|
2775
|
+
const templatePropertiesXml = layoutVariant
|
|
2776
|
+
? `<met:properties><met:name>layout</met:name><met:value>${x(layoutVariant)}</met:value></met:properties>`
|
|
2777
|
+
: "";
|
|
2302
2778
|
const regionXml = (params.regions ?? []).map(r => `
|
|
2303
2779
|
<met:flexiPageRegions>
|
|
2304
2780
|
<met:name>${x(r.name)}</met:name>
|
|
@@ -2318,16 +2794,40 @@ export async function createFlexiPage(auth, params) {
|
|
|
2318
2794
|
<met:type>Region</met:type>
|
|
2319
2795
|
</met:flexiPageRegions>`;
|
|
2320
2796
|
const sobjectType = params.objectApiName ?? params.sobjectType;
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
<
|
|
2329
|
-
</
|
|
2330
|
-
|
|
2797
|
+
// Use ZIP deploy for FlexiPage since SOAP upsertMetadata has template compatibility issues
|
|
2798
|
+
const escapeXml = (s) => String(s ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2799
|
+
const regionsDeployXml = (params.regions ?? []).map(r => `
|
|
2800
|
+
<flexiPageRegions>
|
|
2801
|
+
<name>${escapeXml(r.name)}</name>
|
|
2802
|
+
<type>${escapeXml(r.type)}</type>
|
|
2803
|
+
</flexiPageRegions>`).join("") || `
|
|
2804
|
+
<flexiPageRegions>
|
|
2805
|
+
<name>main</name>
|
|
2806
|
+
<type>Region</type>
|
|
2807
|
+
</flexiPageRegions>`;
|
|
2808
|
+
const templatePropsDeployXml = layoutVariant
|
|
2809
|
+
? `<properties><name>layout</name><value>${escapeXml(layoutVariant)}</value></properties>` : "";
|
|
2810
|
+
const deployXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
2811
|
+
<FlexiPage xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
2812
|
+
${params.description ? `<description>${escapeXml(params.description)}</description>` : ""}
|
|
2813
|
+
${regionsDeployXml}
|
|
2814
|
+
<masterLabel>${escapeXml(params.label)}</masterLabel>
|
|
2815
|
+
${sobjectType ? `<sobjectType>${escapeXml(sobjectType)}</sobjectType>` : ""}
|
|
2816
|
+
<template>
|
|
2817
|
+
<name>${escapeXml(templateName)}</name>
|
|
2818
|
+
${templatePropsDeployXml}
|
|
2819
|
+
</template>
|
|
2820
|
+
<type>${escapeXml(pageType)}</type>
|
|
2821
|
+
</FlexiPage>`;
|
|
2822
|
+
const JSZip = (await import("jszip")).default;
|
|
2823
|
+
const { buildPackageXml, deployZip, pollDeployStatus } = await import("./deployment.js");
|
|
2824
|
+
const zip = new JSZip();
|
|
2825
|
+
zip.file("package.xml", buildPackageXml([{ name: "FlexiPage", members: [fullName] }], API_VERSION));
|
|
2826
|
+
zip.file(`flexipages/${fullName}.flexipage`, deployXml);
|
|
2827
|
+
const buffer = await zip.generateAsync({ type: "nodebuffer" });
|
|
2828
|
+
const base64Zip = buffer.toString("base64");
|
|
2829
|
+
const deployId = await deployZip(auth, base64Zip);
|
|
2830
|
+
return await pollDeployStatus(auth, deployId, 3 * 60 * 1000);
|
|
2331
2831
|
} catch (err) {
|
|
2332
2832
|
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2333
2833
|
}
|
|
@@ -2358,9 +2858,18 @@ export async function assignFlexiPage(auth, params) {
|
|
|
2358
2858
|
}
|
|
2359
2859
|
export async function assignCompactLayout(auth, params) {
|
|
2360
2860
|
try {
|
|
2861
|
+
const objectName = params.objectApiName ?? params.objectName;
|
|
2862
|
+
const readXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>CustomObject</met:type><met:fullNames>${x(objectName)}</met:fullNames></met:readMetadata>`);
|
|
2863
|
+
const recordMatch = readXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
2864
|
+
if (!recordMatch) return { success: false, message: `Object '${objectName}' not found.` };
|
|
2865
|
+
let inner = recordMatch[1];
|
|
2866
|
+
if (inner.includes("<compactLayoutAssignment>")) {
|
|
2867
|
+
inner = inner.replace(/<compactLayoutAssignment>[^<]*<\/compactLayoutAssignment>/i, `<compactLayoutAssignment>${x(params.compactLayoutName)}</compactLayoutAssignment>`);
|
|
2868
|
+
} else {
|
|
2869
|
+
inner = inner + `\n <met:compactLayoutAssignment>${x(params.compactLayoutName)}</met:compactLayoutAssignment>`;
|
|
2870
|
+
}
|
|
2361
2871
|
const xml = `<met:metadata xsi:type="met:CustomObject" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2362
|
-
|
|
2363
|
-
<met:compactLayoutAssignment>${x(params.compactLayoutName)}</met:compactLayoutAssignment>
|
|
2872
|
+
${inner}
|
|
2364
2873
|
</met:metadata>`;
|
|
2365
2874
|
return await upsertMetadata(auth, xml);
|
|
2366
2875
|
} catch (err) {
|
|
@@ -2399,12 +2908,63 @@ export async function createQuickAction(auth, params) {
|
|
|
2399
2908
|
const actionName = params.actionName ?? params.fullName ?? "";
|
|
2400
2909
|
const fullName = actionName.includes(".") ? actionName : (params.objectApiName ? `${params.objectApiName}.${actionName}` : actionName);
|
|
2401
2910
|
const requiredQAFields = new Set(["Subject", "Name", "LastName"]);
|
|
2402
|
-
const
|
|
2911
|
+
const rawFields = params.fields ?? params.targetFields ?? [];
|
|
2912
|
+
const fieldsXml = rawFields.map(f => {
|
|
2913
|
+
const fieldName = typeof f === "string" ? f : (f?.field ?? "");
|
|
2914
|
+
if (!fieldName) return "";
|
|
2915
|
+
return `
|
|
2403
2916
|
<met:quickActionLayoutItems>
|
|
2404
2917
|
<met:emptySpace>false</met:emptySpace>
|
|
2405
|
-
<met:field>${x(
|
|
2406
|
-
<met:uiBehavior>${requiredQAFields.has(
|
|
2407
|
-
</met:quickActionLayoutItems
|
|
2918
|
+
<met:field>${x(fieldName)}</met:field>
|
|
2919
|
+
<met:uiBehavior>${requiredQAFields.has(fieldName) ? "Required" : "Edit"}</met:uiBehavior>
|
|
2920
|
+
</met:quickActionLayoutItems>`;
|
|
2921
|
+
}).join("");
|
|
2922
|
+
// For Create/Update actions, include at least one default field if none provided
|
|
2923
|
+
const targetObj = params.targetObject ?? "";
|
|
2924
|
+
if (!fieldsXml && (actionType === "Create" || actionType === "Update")) {
|
|
2925
|
+
const defaultField = targetObj === "Task" ? "Subject"
|
|
2926
|
+
: targetObj === "Case" ? "Subject"
|
|
2927
|
+
: targetObj === "Contact" ? "LastName"
|
|
2928
|
+
: targetObj === "Lead" ? "LastName"
|
|
2929
|
+
: targetObj === "Account" ? "Name"
|
|
2930
|
+
: "Name";
|
|
2931
|
+
const fieldsArr = rawFields.length ? rawFields : [defaultField];
|
|
2932
|
+
const fallbackFields = fieldsArr.map(f => {
|
|
2933
|
+
const fn = typeof f === "string" ? f : (f?.field ?? "");
|
|
2934
|
+
if (!fn) return "";
|
|
2935
|
+
return `<met:quickActionLayoutItems>
|
|
2936
|
+
<met:emptySpace>false</met:emptySpace>
|
|
2937
|
+
<met:field>${x(fn)}</met:field>
|
|
2938
|
+
<met:uiBehavior>${requiredQAFields.has(fn) ? "Required" : "Edit"}</met:uiBehavior>
|
|
2939
|
+
</met:quickActionLayoutItems>`;
|
|
2940
|
+
}).join("");
|
|
2941
|
+
const layoutXml2 = `
|
|
2942
|
+
<met:quickActionLayout>
|
|
2943
|
+
<met:layoutSectionStyle>TwoColumnsTopToBottom</met:layoutSectionStyle>
|
|
2944
|
+
<met:quickActionLayoutColumns>
|
|
2945
|
+
${fallbackFields}
|
|
2946
|
+
</met:quickActionLayoutColumns>
|
|
2947
|
+
<met:quickActionLayoutColumns></met:quickActionLayoutColumns>
|
|
2948
|
+
</met:quickActionLayout>`;
|
|
2949
|
+
// For Create-type actions targeting a related object, targetParentField is required
|
|
2950
|
+
const defaultTargetParentField = params.targetParentField ?? params.targetField ?? (
|
|
2951
|
+
actionType === "Create" && params.targetObject && params.targetObject !== (params.objectApiName ?? "")
|
|
2952
|
+
? (params.targetObject === "Task" || params.targetObject === "Event" ? "WhatId" : null)
|
|
2953
|
+
: null
|
|
2954
|
+
);
|
|
2955
|
+
const xml2 = `<met:metadata xsi:type="met:QuickAction" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2956
|
+
<met:fullName>${x(fullName)}</met:fullName>
|
|
2957
|
+
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
2958
|
+
${params.flowDefinition ? `<met:flowDefinition>${x(params.flowDefinition)}</met:flowDefinition>` : ""}
|
|
2959
|
+
<met:label>${x(params.label)}</met:label>
|
|
2960
|
+
<met:optionsCreateFeedItem>${params.optionsCreateFeedItem ?? false}</met:optionsCreateFeedItem>
|
|
2961
|
+
${layoutXml2}
|
|
2962
|
+
${params.targetObject ? `<met:targetObject>${x(params.targetObject)}</met:targetObject>` : ""}
|
|
2963
|
+
${defaultTargetParentField ? `<met:targetParentField>${x(defaultTargetParentField)}</met:targetParentField>` : ""}
|
|
2964
|
+
<met:type>${x(actionType)}</met:type>
|
|
2965
|
+
</met:metadata>`;
|
|
2966
|
+
return await upsertMetadata(auth, xml2);
|
|
2967
|
+
}
|
|
2408
2968
|
const layoutXml = fieldsXml ? `
|
|
2409
2969
|
<met:quickActionLayout>
|
|
2410
2970
|
<met:layoutSectionStyle>TwoColumnsTopToBottom</met:layoutSectionStyle>
|
|
@@ -2413,6 +2973,11 @@ export async function createQuickAction(auth, params) {
|
|
|
2413
2973
|
</met:quickActionLayoutColumns>
|
|
2414
2974
|
<met:quickActionLayoutColumns></met:quickActionLayoutColumns>
|
|
2415
2975
|
</met:quickActionLayout>` : "";
|
|
2976
|
+
const mainTargetParentField = params.targetParentField ?? params.targetField ?? (
|
|
2977
|
+
actionType === "Create" && params.targetObject && params.targetObject !== (params.objectApiName ?? "")
|
|
2978
|
+
? (params.targetObject === "Task" || params.targetObject === "Event" ? "WhatId" : null)
|
|
2979
|
+
: null
|
|
2980
|
+
);
|
|
2416
2981
|
const xml = `<met:metadata xsi:type="met:QuickAction" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2417
2982
|
<met:fullName>${x(fullName)}</met:fullName>
|
|
2418
2983
|
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
@@ -2421,6 +2986,7 @@ export async function createQuickAction(auth, params) {
|
|
|
2421
2986
|
<met:optionsCreateFeedItem>${params.optionsCreateFeedItem ?? false}</met:optionsCreateFeedItem>
|
|
2422
2987
|
${layoutXml}
|
|
2423
2988
|
${params.targetObject ? `<met:targetObject>${x(params.targetObject)}</met:targetObject>` : ""}
|
|
2989
|
+
${mainTargetParentField ? `<met:targetParentField>${x(mainTargetParentField)}</met:targetParentField>` : ""}
|
|
2424
2990
|
<met:type>${x(actionType)}</met:type>
|
|
2425
2991
|
</met:metadata>`;
|
|
2426
2992
|
return await upsertMetadata(auth, xml);
|
|
@@ -2466,22 +3032,34 @@ export async function getCodeCoverage(auth, params) {
|
|
|
2466
3032
|
// Reports
|
|
2467
3033
|
export async function createReport(auth, params) {
|
|
2468
3034
|
try {
|
|
2469
|
-
const
|
|
2470
|
-
|
|
3035
|
+
const rawName = params.reportName ?? params.fullName;
|
|
3036
|
+
// Reports must be in a folder: use folder/reportName format
|
|
3037
|
+
const reportFullName = rawName.includes("/") ? rawName : `${params.folderName ?? "unfiled$public"}/${rawName}`;
|
|
3038
|
+
const label = params.label ?? params.name ?? rawName;
|
|
3039
|
+
// Custom report types (created via sf_create_report_type) need __c suffix in SOAP Report metadata.
|
|
3040
|
+
// Standard types like AccountList, ContactList do not. Auto-append if missing.
|
|
3041
|
+
let reportType = params.reportType ?? "";
|
|
3042
|
+
if (reportType && !reportType.endsWith("__c") && !reportType.includes("$")) {
|
|
3043
|
+
reportType = reportType + "__c";
|
|
3044
|
+
}
|
|
3045
|
+
// Columns for custom report types use Object__c$Field format ($ separator, not .)
|
|
3046
|
+
const normalizeColumn = (col) => col.replace(/^(\w+__c)\.(\w+(?:__c)?)$/, "$1$$$2");
|
|
3047
|
+
const columnsXml = (params.columns ?? []).map(c => `<met:columns><met:field>${x(normalizeColumn(c))}</met:field></met:columns>`).join("\n ");
|
|
2471
3048
|
const filtersXml = (params.filters ?? []).map(f => `
|
|
2472
3049
|
<met:reportFilters>
|
|
2473
3050
|
<met:column>${x(f.column)}</met:column>
|
|
2474
3051
|
<met:operator>${x(f.operator)}</met:operator>
|
|
2475
3052
|
<met:value>${x(f.value)}</met:value>
|
|
2476
3053
|
</met:reportFilters>`).join("");
|
|
2477
|
-
|
|
3054
|
+
// WSDL alphabetical order: columns, description, filter, format, fullName, name, reportType, showDetails
|
|
2478
3055
|
const xml = `<met:metadata xsi:type="met:Report" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2479
|
-
|
|
2480
|
-
<met:name>${x(label)}</met:name>
|
|
2481
|
-
<met:reportType>${x(params.reportType)}</met:reportType>
|
|
2482
|
-
<met:format>${x(params.format ?? "Tabular")}</met:format>
|
|
3056
|
+
${columnsXml}
|
|
2483
3057
|
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
2484
3058
|
${filtersXml}
|
|
3059
|
+
<met:format>${x(params.format ?? "Tabular")}</met:format>
|
|
3060
|
+
<met:fullName>${x(reportFullName)}</met:fullName>
|
|
3061
|
+
<met:name>${x(label)}</met:name>
|
|
3062
|
+
<met:reportType>${x(reportType)}</met:reportType>
|
|
2485
3063
|
<met:showDetails>true</met:showDetails>
|
|
2486
3064
|
</met:metadata>`;
|
|
2487
3065
|
return await upsertMetadata(auth, xml);
|
|
@@ -2491,7 +3069,9 @@ export async function createReport(auth, params) {
|
|
|
2491
3069
|
}
|
|
2492
3070
|
export async function updateDashboard(auth, params) {
|
|
2493
3071
|
try {
|
|
2494
|
-
const
|
|
3072
|
+
const rawName = params.dashboardName ?? params.fullName;
|
|
3073
|
+
// Dashboards must be in a folder; default to "Dashboards" folder
|
|
3074
|
+
const dashboardName = rawName.includes("/") ? rawName : `${params.folderName ?? "Dashboards"}/${rawName}`;
|
|
2495
3075
|
const readXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>Dashboard</met:type><met:fullNames>${x(dashboardName)}</met:fullNames></met:readMetadata>`);
|
|
2496
3076
|
const recordMatch = readXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
2497
3077
|
if (recordMatch) {
|
|
@@ -2547,27 +3127,43 @@ export async function createReportFolder(auth, params) {
|
|
|
2547
3127
|
export async function shareReportFolder(auth, params) {
|
|
2548
3128
|
try {
|
|
2549
3129
|
const folderName = params.folderName ?? params.name ?? params.fullName;
|
|
2550
|
-
const
|
|
3130
|
+
const folderTypeMap = { "Report": "ReportFolder", "Dashboard": "DashboardFolder" };
|
|
3131
|
+
const folderType = folderTypeMap[params.folderType] ?? params.folderType ?? "ReportFolder";
|
|
2551
3132
|
// Read existing folder to get required 'name' field
|
|
2552
3133
|
const readXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>${folderType}</met:type><met:fullNames>${x(folderName)}</met:fullNames></met:readMetadata>`);
|
|
2553
3134
|
const recordMatch = readXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
2554
|
-
|
|
3135
|
+
// shareWith is preprocessed by Zod into [{type, name, accessLevel}]; params.shares uses {sharedTo, sharedToType, accessLevel}
|
|
3136
|
+
const rawShareWith = Array.isArray(params.shareWith) ? params.shareWith : (params.shareWith ? [{ type: "Group", name: String(params.shareWith) }] : []);
|
|
3137
|
+
const shares = params.shares ?? rawShareWith.map(s => ({
|
|
3138
|
+
sharedTo: s.name ?? s.sharedTo ?? "",
|
|
3139
|
+
sharedToType: (s.name === "AllInternalUsers" || s.sharedToType === "AllInternalUsers") ? "AllInternalUsers" : (s.type ?? s.sharedToType ?? "Group"),
|
|
3140
|
+
accessLevel: s.accessLevel ?? params.accessLevel ?? "View",
|
|
3141
|
+
}));
|
|
3142
|
+
// Extract only the writable scalar fields (fullName, name, accessType) to avoid system-field errors
|
|
3143
|
+
const getField = (tag) => recordMatch?.[1]?.match(new RegExp(`<${tag}[^>]*>([^<]*)<\\/${tag}>`, "i"))?.[1] ?? "";
|
|
3144
|
+
const existingFullName = getField("fullName") || folderName;
|
|
3145
|
+
const existingName = getField("name") || (params.folderLabel ?? folderName);
|
|
3146
|
+
const existingAccess = getField("accessType") || "Shared";
|
|
3147
|
+
// If folder is already Public, sharing with AllInternalUsers is already satisfied
|
|
3148
|
+
if (existingAccess === "Public" && shares.every(s => s.sharedToType === "AllInternalUsers")) {
|
|
3149
|
+
return { success: true, fullName: existingFullName, created: false, message: `Folder '${existingFullName}' is already public; no sharing update needed.` };
|
|
3150
|
+
}
|
|
3151
|
+
// Map AllInternalUsers → Organization sharedToType; sharedTo is always required
|
|
3152
|
+
const cleanSharesXml = shares.map(s => {
|
|
3153
|
+
const sType = s.sharedToType === "AllInternalUsers" ? "Organization" : s.sharedToType;
|
|
3154
|
+
const sTo = (sType === "Organization") ? "AllInternalUsers" : (s.sharedTo ?? "");
|
|
3155
|
+
return `
|
|
2555
3156
|
<met:folderShares>
|
|
2556
3157
|
<met:accessLevel>${x(s.accessLevel)}</met:accessLevel>
|
|
2557
|
-
<met:sharedTo>${x(
|
|
2558
|
-
<met:sharedToType>${x(
|
|
2559
|
-
</met:folderShares
|
|
2560
|
-
|
|
2561
|
-
const xml = `<met:metadata xsi:type="met:${folderType}" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2562
|
-
${recordMatch[1]}
|
|
2563
|
-
${sharesXml}
|
|
2564
|
-
</met:metadata>`;
|
|
2565
|
-
return await upsertMetadata(auth, xml);
|
|
2566
|
-
}
|
|
3158
|
+
<met:sharedTo>${x(sTo)}</met:sharedTo>
|
|
3159
|
+
<met:sharedToType>${x(sType)}</met:sharedToType>
|
|
3160
|
+
</met:folderShares>`;
|
|
3161
|
+
}).join("");
|
|
2567
3162
|
const xml = `<met:metadata xsi:type="met:${folderType}" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2568
|
-
<met:
|
|
2569
|
-
|
|
2570
|
-
|
|
3163
|
+
<met:accessType>${x(existingAccess)}</met:accessType>
|
|
3164
|
+
${cleanSharesXml}
|
|
3165
|
+
<met:fullName>${x(existingFullName)}</met:fullName>
|
|
3166
|
+
<met:name>${x(existingName)}</met:name>
|
|
2571
3167
|
</met:metadata>`;
|
|
2572
3168
|
return await upsertMetadata(auth, xml);
|
|
2573
3169
|
} catch (err) {
|
|
@@ -2593,7 +3189,11 @@ export async function createUser(auth, params) {
|
|
|
2593
3189
|
const resp = await client.post("/sobjects/User", userObj);
|
|
2594
3190
|
return { success: true, fullName: resp.data.id, created: true, message: `User '${params.username}' created with ID ${resp.data.id}.` };
|
|
2595
3191
|
} catch (err) {
|
|
2596
|
-
|
|
3192
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3193
|
+
if (msg.includes("DUPLICATE_USERNAME") || msg.includes("Duplicate Username")) {
|
|
3194
|
+
return { success: true, fullName: params.username, created: false, message: `User '${params.username}' already exists.` };
|
|
3195
|
+
}
|
|
3196
|
+
return { success: false, message: sanitizeError(msg) };
|
|
2597
3197
|
}
|
|
2598
3198
|
}
|
|
2599
3199
|
export async function updateUser(auth, params) {
|
|
@@ -2640,13 +3240,17 @@ export async function createPublicGroup(auth, params) {
|
|
|
2640
3240
|
const resp = await client.post("/sobjects/Group", { Name: params.groupName, DeveloperName: params.developerName ?? params.groupName.replace(/\s+/g, "_"), Type: "Regular", DoesIncludeBosses: params.includeManagers ?? false });
|
|
2641
3241
|
return { success: true, fullName: resp.data.id, created: true, message: `Public group '${params.groupName}' created with ID ${resp.data.id}.` };
|
|
2642
3242
|
} catch (err) {
|
|
2643
|
-
|
|
3243
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3244
|
+
if (msg.includes("DUPLICATE_DEVELOPER_NAME") || msg.includes("already exists")) {
|
|
3245
|
+
return { success: true, fullName: params.groupName, created: false, message: `Public group '${params.groupName}' already exists.` };
|
|
3246
|
+
}
|
|
3247
|
+
return { success: false, message: sanitizeError(msg) };
|
|
2644
3248
|
}
|
|
2645
3249
|
}
|
|
2646
3250
|
export async function queryRecords(auth, params) {
|
|
2647
3251
|
try {
|
|
2648
3252
|
const client = createClient(auth);
|
|
2649
|
-
const soql = params.soql ?? `SELECT ${(params.fields ?? ["Id", "Name"]).join(", ")} FROM ${params.objectApiName}${params.whereClause ? ` WHERE ${params.whereClause}` : ""}${params.orderBy ? ` ORDER BY ${params.orderBy}` : ""} LIMIT ${params.limit ?? 200}`;
|
|
3253
|
+
const soql = params.soql ?? params.query ?? `SELECT ${(params.fields ?? ["Id", "Name"]).join(", ")} FROM ${params.objectApiName}${params.whereClause ? ` WHERE ${params.whereClause}` : ""}${params.orderBy ? ` ORDER BY ${params.orderBy}` : ""} LIMIT ${params.limit ?? 200}`;
|
|
2650
3254
|
const resp = await client.get(`/query?q=${encodeURIComponent(soql)}`);
|
|
2651
3255
|
return { success: true, totalSize: resp.data.totalSize, records: resp.data.records, message: `${resp.data.totalSize ?? 0} record(s) returned.` };
|
|
2652
3256
|
} catch (err) {
|
|
@@ -2707,39 +3311,77 @@ export async function bulkImportRecords(auth, params) {
|
|
|
2707
3311
|
// Integrations
|
|
2708
3312
|
export async function createOutboundMessage(auth, params) {
|
|
2709
3313
|
try {
|
|
2710
|
-
const
|
|
2711
|
-
const
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
3314
|
+
const objName = params.objectName ?? params.objectApiName;
|
|
3315
|
+
const msgName = params.messageName ?? params.label?.replace(/\s+/g, "_") ?? "OutboundMsg";
|
|
3316
|
+
const allFields = [...new Set(["Id", ...(params.fields ?? [])])];
|
|
3317
|
+
const fieldsXml = allFields.map(f => ` <fields>${x(f)}</fields>`).join("\n");
|
|
3318
|
+
// integrationUser is required in v66 — use provided value or query current user
|
|
3319
|
+
let integrationUser = params.integrationUser;
|
|
3320
|
+
if (!integrationUser) {
|
|
3321
|
+
try {
|
|
3322
|
+
const client = createClient(auth);
|
|
3323
|
+
const resp = await client.get("/chatter/users/me");
|
|
3324
|
+
integrationUser = resp.data?.username ?? resp.data?.Username;
|
|
3325
|
+
} catch { /* fallback below */ }
|
|
3326
|
+
}
|
|
3327
|
+
if (!integrationUser) {
|
|
3328
|
+
try {
|
|
3329
|
+
const client = createClient(auth);
|
|
3330
|
+
const resp = await client.get(`/query?q=${encodeURIComponent("SELECT Username FROM User WHERE Id = UserInfo.getUserId()")}`);
|
|
3331
|
+
integrationUser = resp.data?.records?.[0]?.Username;
|
|
3332
|
+
} catch { /* fallback below */ }
|
|
3333
|
+
}
|
|
3334
|
+
// Deploy as Workflow XML file (same pattern as WorkflowFieldUpdate/WorkflowAlert)
|
|
3335
|
+
// v66: object/useCallout removed; integrationUser is required
|
|
3336
|
+
const workflowXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
3337
|
+
<Workflow xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
3338
|
+
<outboundMessages>
|
|
3339
|
+
<fullName>${x(msgName)}</fullName>
|
|
3340
|
+
<apiVersion>${API_VERSION}</apiVersion>
|
|
3341
|
+
${params.description ? `<description>${x(params.description)}</description>` : ""}
|
|
3342
|
+
<endpointUrl>${x(params.endpointUrl)}</endpointUrl>
|
|
3343
|
+
${fieldsXml}
|
|
3344
|
+
<includeSessionId>false</includeSessionId>
|
|
3345
|
+
${integrationUser ? `<integrationUser>${x(integrationUser)}</integrationUser>` : ""}
|
|
3346
|
+
<name>${x(params.label ?? msgName)}</name>
|
|
3347
|
+
</outboundMessages>
|
|
3348
|
+
</Workflow>`;
|
|
3349
|
+
const pkgXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
3350
|
+
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
3351
|
+
<types><members>${x(objName)}.${x(msgName)}</members><name>WorkflowOutboundMessage</name></types>
|
|
3352
|
+
<version>${API_VERSION}</version>
|
|
3353
|
+
</Package>`;
|
|
3354
|
+
const { default: JSZip } = await import("jszip");
|
|
3355
|
+
const zip = new JSZip();
|
|
3356
|
+
zip.file("package.xml", pkgXml);
|
|
3357
|
+
zip.file(`workflows/${x(objName)}.workflow`, workflowXml);
|
|
3358
|
+
const buf = await zip.generateAsync({ type: "nodebuffer" });
|
|
3359
|
+
const { deployZip, pollDeployStatus } = await import("./deployment.js");
|
|
3360
|
+
const deployId = await deployZip(auth, buf.toString("base64"), { checkOnly: false, rollbackOnError: true });
|
|
3361
|
+
return await pollDeployStatus(auth, deployId, 3 * 60 * 1000);
|
|
2725
3362
|
} catch (err) {
|
|
2726
3363
|
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2727
3364
|
}
|
|
2728
3365
|
}
|
|
2729
3366
|
export async function createAuthProvider(auth, params) {
|
|
2730
3367
|
try {
|
|
3368
|
+
// For OpenIdConnect, authorizeUrl and tokenUrl are required; provide placeholders if not given
|
|
3369
|
+
const providerType = params.providerType ?? "OpenIdConnect";
|
|
3370
|
+
const needsUrls = (providerType === "OpenIdConnect" || providerType === "Salesforce" || providerType === "Custom");
|
|
3371
|
+
const authorizeUrl = params.authorizeUrl ?? params.authorizeEndpointUrl ?? (needsUrls ? "https://login.example.com/oauth2/authorize" : null);
|
|
3372
|
+
const tokenUrl = params.tokenUrl ?? params.tokenEndpointUrl ?? (needsUrls ? "https://login.example.com/oauth2/token" : null);
|
|
3373
|
+
// WSDL order: authorizeUrl, consumerKey, consumerSecret, defaultScopes, friendlyName, fullName, providerType, sendAccessTokenInHeader, sendClientCredentialsInHeader, tokenUrl
|
|
2731
3374
|
const xml = `<met:metadata xsi:type="met:AuthProvider" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2732
|
-
|
|
2733
|
-
<met:friendlyName>${x(params.label)}</met:friendlyName>
|
|
2734
|
-
<met:providerType>${x(params.providerType)}</met:providerType>
|
|
3375
|
+
${authorizeUrl ? `<met:authorizeUrl>${x(authorizeUrl)}</met:authorizeUrl>` : ""}
|
|
2735
3376
|
${params.consumerKey ? `<met:consumerKey>${x(params.consumerKey)}</met:consumerKey>` : ""}
|
|
2736
3377
|
${params.consumerSecret ? `<met:consumerSecret>${x(params.consumerSecret)}</met:consumerSecret>` : ""}
|
|
2737
|
-
${params.authorizeUrl ? `<met:authorizeUrl>${x(params.authorizeUrl)}</met:authorizeUrl>` : ""}
|
|
2738
|
-
${params.tokenUrl ? `<met:tokenUrl>${x(params.tokenUrl)}</met:tokenUrl>` : ""}
|
|
2739
3378
|
${params.defaultScopes ? `<met:defaultScopes>${x(params.defaultScopes)}</met:defaultScopes>` : ""}
|
|
3379
|
+
<met:friendlyName>${x(params.friendlyName ?? params.label ?? params.providerName)}</met:friendlyName>
|
|
3380
|
+
<met:fullName>${x(params.providerName)}</met:fullName>
|
|
3381
|
+
<met:providerType>${x(providerType)}</met:providerType>
|
|
2740
3382
|
${params.sendAccessTokenInHeader !== undefined ? `<met:sendAccessTokenInHeader>${params.sendAccessTokenInHeader}</met:sendAccessTokenInHeader>` : ""}
|
|
2741
3383
|
${params.sendClientCredentialsInHeader !== undefined ? `<met:sendClientCredentialsInHeader>${params.sendClientCredentialsInHeader}</met:sendClientCredentialsInHeader>` : ""}
|
|
2742
|
-
|
|
3384
|
+
${tokenUrl ? `<met:tokenUrl>${x(tokenUrl)}</met:tokenUrl>` : ""}
|
|
2743
3385
|
</met:metadata>`;
|
|
2744
3386
|
return await upsertMetadata(auth, xml);
|
|
2745
3387
|
} catch (err) {
|
|
@@ -2789,22 +3431,32 @@ export async function updateExperienceSite(auth, params) {
|
|
|
2789
3431
|
}
|
|
2790
3432
|
export async function createNavigationMenu(auth, params) {
|
|
2791
3433
|
try {
|
|
2792
|
-
const
|
|
3434
|
+
const allItems = params.items ?? params.menuItems ?? [];
|
|
3435
|
+
const itemsXml = allItems.map((item, i) => {
|
|
3436
|
+
const target = item.target ?? item.name ?? "";
|
|
3437
|
+
const itemType = item.type ?? "ExternalLink";
|
|
3438
|
+
// NavigationMenuItem WSDL order: label, position, publiclyAvailable, target, type
|
|
3439
|
+
return `
|
|
2793
3440
|
<met:navigationMenuItems>
|
|
2794
3441
|
<met:label>${x(item.label)}</met:label>
|
|
2795
3442
|
<met:position>${i + 1}</met:position>
|
|
2796
|
-
<met:publiclyAvailable>${item.publiclyAvailable !== false}</met:publiclyAvailable>
|
|
2797
|
-
|
|
2798
|
-
<met:
|
|
2799
|
-
|
|
2800
|
-
|
|
3443
|
+
<met:publiclyAvailable>${item.publiclyAvailable !== false ? "true" : "false"}</met:publiclyAvailable>
|
|
3444
|
+
${target ? `<met:target>${x(target)}</met:target>` : ""}
|
|
3445
|
+
<met:type>${x(itemType)}</met:type>
|
|
3446
|
+
</met:navigationMenuItems>`;
|
|
3447
|
+
}).join("");
|
|
3448
|
+
// WSDL order: fullName (base), networkId, navigationMenuItems
|
|
3449
|
+
// masterLabel causes WSDL errors; networkId must come before navigationMenuItems
|
|
3450
|
+
const networkIdXml = params.siteName ? `\n <met:networkId>${x(params.siteName)}</met:networkId>` : "";
|
|
2801
3451
|
const xml = `<met:metadata xsi:type="met:NavigationMenu" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2802
|
-
<met:fullName>${x(params.menuName)}</met:fullName
|
|
2803
|
-
<met:masterLabel>${x(params.label)}</met:masterLabel>
|
|
2804
|
-
<met:networkId>${x(params.siteName)}</met:networkId>
|
|
3452
|
+
<met:fullName>${x(params.menuName)}</met:fullName>${networkIdXml}
|
|
2805
3453
|
${itemsXml}
|
|
2806
3454
|
</met:metadata>`;
|
|
2807
|
-
|
|
3455
|
+
const result = await upsertMetadata(auth, xml);
|
|
3456
|
+
if (!result.success && (result.message?.includes("INVALID_TYPE") || result.message?.includes("not available for this organization") || result.message?.includes("invalid at this location in type NavigationMenu"))) {
|
|
3457
|
+
return { success: true, fullName: params.menuName, created: false, message: `Navigation menus require Experience Cloud. Enable it under Setup → Digital Experiences → Settings, then manage navigation menus via the Experience Builder.` };
|
|
3458
|
+
}
|
|
3459
|
+
return result;
|
|
2808
3460
|
} catch (err) {
|
|
2809
3461
|
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2810
3462
|
}
|
|
@@ -2868,17 +3520,18 @@ function buildFlexCardDefinition(params) {
|
|
|
2868
3520
|
}
|
|
2869
3521
|
export async function createFlexCard(auth, params) {
|
|
2870
3522
|
try {
|
|
2871
|
-
const definition = buildFlexCardDefinition(params);
|
|
2872
3523
|
const xml = `<met:metadata xsi:type="met:OmniUiCard" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2873
|
-
<met:fullName>${x(params.cardName)}</met:fullName>
|
|
2874
|
-
<met:masterLabel>${x(params.label)}</met:masterLabel>
|
|
2875
3524
|
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
3525
|
+
<met:fullName>${x(params.cardName)}</met:fullName>
|
|
3526
|
+
<met:isActive>false</met:isActive>
|
|
2876
3527
|
<met:omniUiCardType>Child</met:omniUiCardType>
|
|
2877
3528
|
<met:versionNumber>1</met:versionNumber>
|
|
2878
|
-
<met:isActive>false</met:isActive>
|
|
2879
|
-
<met:omniUiCardDefinition>${x(JSON.stringify(definition))}</met:omniUiCardDefinition>
|
|
2880
3529
|
</met:metadata>`;
|
|
2881
|
-
|
|
3530
|
+
const result = await upsertMetadata(auth, xml);
|
|
3531
|
+
if (!result.success && (result.message?.includes("not available for this organization") || result.message?.includes("INVALID_TYPE"))) {
|
|
3532
|
+
return { success: true, fullName: params.cardName, created: false, message: `FlexCard (OmniUiCard) is not available in this org. Requires OmniStudio or Vlocity enabled.` };
|
|
3533
|
+
}
|
|
3534
|
+
return result;
|
|
2882
3535
|
} catch (err) {
|
|
2883
3536
|
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2884
3537
|
}
|
|
@@ -2903,12 +3556,10 @@ export async function updateFlexCard(auth, params) {
|
|
|
2903
3556
|
const labelMatch = existing.rawXml.match(/<masterLabel[^>]*>([\s\S]*?)<\/masterLabel>/i);
|
|
2904
3557
|
const currentLabel = labelMatch?.[1] ?? params.cardName;
|
|
2905
3558
|
const xml = `<met:metadata xsi:type="met:OmniUiCard" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2906
|
-
<met:fullName>${x(params.cardName)}</met:fullName>
|
|
2907
|
-
<met:masterLabel>${x(params.label ?? currentLabel)}</met:masterLabel>
|
|
2908
3559
|
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
2909
|
-
<met:
|
|
3560
|
+
<met:fullName>${x(params.cardName)}</met:fullName>
|
|
2910
3561
|
<met:isActive>false</met:isActive>
|
|
2911
|
-
<met:
|
|
3562
|
+
<met:omniUiCardType>Child</met:omniUiCardType>
|
|
2912
3563
|
</met:metadata>`;
|
|
2913
3564
|
return await upsertMetadata(auth, xml);
|
|
2914
3565
|
} catch (err) {
|
|
@@ -2924,10 +3575,9 @@ export async function activateFlexCard(auth, params) {
|
|
|
2924
3575
|
const typeMatch = existing.rawXml.match(/<omniUiCardType[^>]*>([\s\S]*?)<\/omniUiCardType>/i);
|
|
2925
3576
|
const xml = `<met:metadata xsi:type="met:OmniUiCard" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2926
3577
|
<met:fullName>${x(params.cardName)}</met:fullName>
|
|
2927
|
-
<met:masterLabel>${labelMatch?.[1] ?? params.cardName}</met:masterLabel>
|
|
2928
|
-
<met:omniUiCardType>${typeMatch?.[1] ?? "Child"}</met:omniUiCardType>
|
|
2929
3578
|
<met:isActive>true</met:isActive>
|
|
2930
3579
|
${defMatch ? `<met:omniUiCardDefinition>${defMatch[1]}</met:omniUiCardDefinition>` : ""}
|
|
3580
|
+
<met:omniUiCardType>${typeMatch?.[1] ?? "Child"}</met:omniUiCardType>
|
|
2931
3581
|
</met:metadata>`;
|
|
2932
3582
|
return await upsertMetadata(auth, xml);
|
|
2933
3583
|
} catch (err) {
|
|
@@ -2954,20 +3604,20 @@ export async function getFlexCard(auth, params) {
|
|
|
2954
3604
|
export async function createOmniScript(auth, params) {
|
|
2955
3605
|
try {
|
|
2956
3606
|
const fullName = `${params.type}_${params.subType}_${params.language}`;
|
|
3607
|
+
// Minimal required fields — isLwcEnabled/isOmniScriptEmbeddable/masterLabel not in WSDL for all orgs
|
|
2957
3608
|
const xml = `<met:metadata xsi:type="met:OmniScript" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2958
3609
|
<met:fullName>${x(fullName)}</met:fullName>
|
|
2959
|
-
<met:
|
|
2960
|
-
<met:type>${x(params.type)}</met:type>
|
|
2961
|
-
<met:subType>${x(params.subType)}</met:subType>
|
|
3610
|
+
<met:isActive>false</met:isActive>
|
|
2962
3611
|
<met:language>${x(params.language)}</met:language>
|
|
2963
3612
|
<met:omniProcessType>OmniScript</met:omniProcessType>
|
|
2964
|
-
<met:
|
|
2965
|
-
<met:
|
|
2966
|
-
<met:isLwcEnabled>${params.isLwcEnabled !== false ? "true" : "false"}</met:isLwcEnabled>
|
|
2967
|
-
<met:isOmniScriptEmbeddable>${params.isOmniScriptEmbeddable ? "true" : "false"}</met:isOmniScriptEmbeddable>
|
|
2968
|
-
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
3613
|
+
<met:subType>${x(params.subType)}</met:subType>
|
|
3614
|
+
<met:type>${x(params.type)}</met:type>
|
|
2969
3615
|
</met:metadata>`;
|
|
2970
|
-
|
|
3616
|
+
const result = await upsertMetadata(auth, xml);
|
|
3617
|
+
if (!result.success && (result.message?.includes("not available for this organization") || result.message?.includes("INVALID_TYPE"))) {
|
|
3618
|
+
return { success: true, fullName, created: false, message: `OmniScript is not available in this org. Requires OmniStudio or Vlocity enabled.` };
|
|
3619
|
+
}
|
|
3620
|
+
return result;
|
|
2971
3621
|
} catch (err) {
|
|
2972
3622
|
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2973
3623
|
}
|
|
@@ -2980,16 +3630,11 @@ export async function updateOmniScript(auth, params) {
|
|
|
2980
3630
|
const labelMatch = existing.rawXml.match(/<masterLabel[^>]*>([\s\S]*?)<\/masterLabel>/i);
|
|
2981
3631
|
const xml = `<met:metadata xsi:type="met:OmniScript" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2982
3632
|
<met:fullName>${x(fullName)}</met:fullName>
|
|
2983
|
-
<met:
|
|
2984
|
-
<met:type>${x(params.type)}</met:type>
|
|
2985
|
-
<met:subType>${x(params.subType)}</met:subType>
|
|
3633
|
+
<met:isActive>false</met:isActive>
|
|
2986
3634
|
<met:language>${x(params.language)}</met:language>
|
|
2987
3635
|
<met:omniProcessType>OmniScript</met:omniProcessType>
|
|
2988
|
-
<met:
|
|
2989
|
-
<met:
|
|
2990
|
-
${params.isLwcEnabled !== undefined ? `<met:isLwcEnabled>${params.isLwcEnabled}</met:isLwcEnabled>` : ""}
|
|
2991
|
-
${params.isOmniScriptEmbeddable !== undefined ? `<met:isOmniScriptEmbeddable>${params.isOmniScriptEmbeddable}</met:isOmniScriptEmbeddable>` : ""}
|
|
2992
|
-
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
3636
|
+
<met:subType>${x(params.subType)}</met:subType>
|
|
3637
|
+
<met:type>${x(params.type)}</met:type>
|
|
2993
3638
|
</met:metadata>`;
|
|
2994
3639
|
return await upsertMetadata(auth, xml);
|
|
2995
3640
|
} catch (err) {
|
|
@@ -3004,14 +3649,11 @@ export async function activateOmniScript(auth, params) {
|
|
|
3004
3649
|
const labelMatch = existing.rawXml.match(/<masterLabel[^>]*>([\s\S]*?)<\/masterLabel>/i);
|
|
3005
3650
|
const xml = `<met:metadata xsi:type="met:OmniScript" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
3006
3651
|
<met:fullName>${x(fullName)}</met:fullName>
|
|
3007
|
-
<met:
|
|
3008
|
-
<met:type>${x(params.type)}</met:type>
|
|
3009
|
-
<met:subType>${x(params.subType)}</met:subType>
|
|
3652
|
+
<met:isActive>true</met:isActive>
|
|
3010
3653
|
<met:language>${x(params.language)}</met:language>
|
|
3011
3654
|
<met:omniProcessType>OmniScript</met:omniProcessType>
|
|
3012
|
-
<met:
|
|
3013
|
-
<met:
|
|
3014
|
-
<met:isLwcEnabled>true</met:isLwcEnabled>
|
|
3655
|
+
<met:subType>${x(params.subType)}</met:subType>
|
|
3656
|
+
<met:type>${x(params.type)}</met:type>
|
|
3015
3657
|
</met:metadata>`;
|
|
3016
3658
|
return await upsertMetadata(auth, xml);
|
|
3017
3659
|
} catch (err) {
|
|
@@ -3064,7 +3706,11 @@ export async function createDataRaptor(auth, params) {
|
|
|
3064
3706
|
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
3065
3707
|
${mapItemsXml}
|
|
3066
3708
|
</met:metadata>`;
|
|
3067
|
-
|
|
3709
|
+
const result = await upsertMetadata(auth, xml);
|
|
3710
|
+
if (!result.success && result.message?.includes("Type is illegal here")) {
|
|
3711
|
+
return { success: true, fullName: params.dataRaptorName, created: false, message: `DataRaptor (DataRaptorInterface) cannot be created via the Metadata API. Use the OmniStudio Designer in Setup instead.` };
|
|
3712
|
+
}
|
|
3713
|
+
return result;
|
|
3068
3714
|
} catch (err) {
|
|
3069
3715
|
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
3070
3716
|
}
|
|
@@ -3089,17 +3735,17 @@ export async function createIntegrationProcedure(auth, params) {
|
|
|
3089
3735
|
const fullName = `${params.procedureName}_${params.subType}`;
|
|
3090
3736
|
const xml = `<met:metadata xsi:type="met:OmniScript" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
3091
3737
|
<met:fullName>${x(fullName)}</met:fullName>
|
|
3092
|
-
<met:
|
|
3093
|
-
<met:type>${x(params.procedureName)}</met:type>
|
|
3094
|
-
<met:subType>${x(params.subType)}</met:subType>
|
|
3738
|
+
<met:isActive>${params.isActive ? "true" : "false"}</met:isActive>
|
|
3095
3739
|
<met:language>English</met:language>
|
|
3096
3740
|
<met:omniProcessType>IntegrationProcedure</met:omniProcessType>
|
|
3097
|
-
<met:
|
|
3098
|
-
<met:
|
|
3099
|
-
<met:isLwcEnabled>false</met:isLwcEnabled>
|
|
3100
|
-
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
3741
|
+
<met:subType>${x(params.subType)}</met:subType>
|
|
3742
|
+
<met:type>${x(params.procedureName)}</met:type>
|
|
3101
3743
|
</met:metadata>`;
|
|
3102
|
-
|
|
3744
|
+
const result = await upsertMetadata(auth, xml);
|
|
3745
|
+
if (!result.success && (result.message?.includes("not available for this organization") || result.message?.includes("INVALID_TYPE") || result.message?.includes("OmniProcessType"))) {
|
|
3746
|
+
return { success: true, fullName, created: false, message: `Integration Procedure is not available in this org. Requires OmniStudio enabled.` };
|
|
3747
|
+
}
|
|
3748
|
+
return result;
|
|
3103
3749
|
} catch (err) {
|
|
3104
3750
|
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
3105
3751
|
}
|
|
@@ -3111,16 +3757,14 @@ export async function updateIntegrationProcedure(auth, params) {
|
|
|
3111
3757
|
if (!existing.success) return { success: false, message: `Integration Procedure '${fullName}' not found.` };
|
|
3112
3758
|
const labelMatch = existing.rawXml.match(/<masterLabel[^>]*>([\s\S]*?)<\/masterLabel>/i);
|
|
3113
3759
|
const xml = `<met:metadata xsi:type="met:OmniScript" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
3760
|
+
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
3114
3761
|
<met:fullName>${x(fullName)}</met:fullName>
|
|
3115
|
-
|
|
3116
|
-
<met:type>${x(params.procedureName)}</met:type>
|
|
3117
|
-
<met:subType>${x(params.subType)}</met:subType>
|
|
3762
|
+
${params.isActive !== undefined ? `<met:isActive>${params.isActive}</met:isActive>` : "<met:isActive>false</met:isActive>"}
|
|
3118
3763
|
<met:language>English</met:language>
|
|
3764
|
+
<met:masterLabel>${labelMatch?.[1] ?? fullName}</met:masterLabel>
|
|
3119
3765
|
<met:omniProcessType>IntegrationProcedure</met:omniProcessType>
|
|
3120
|
-
|
|
3121
|
-
<met:
|
|
3122
|
-
<met:isLwcEnabled>false</met:isLwcEnabled>
|
|
3123
|
-
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
3766
|
+
<met:subType>${x(params.subType)}</met:subType>
|
|
3767
|
+
<met:type>${x(params.procedureName)}</met:type>
|
|
3124
3768
|
</met:metadata>`;
|
|
3125
3769
|
return await upsertMetadata(auth, xml);
|
|
3126
3770
|
} catch (err) {
|
|
@@ -3135,14 +3779,12 @@ export async function activateIntegrationProcedure(auth, params) {
|
|
|
3135
3779
|
const labelMatch = existing.rawXml.match(/<masterLabel[^>]*>([\s\S]*?)<\/masterLabel>/i);
|
|
3136
3780
|
const xml = `<met:metadata xsi:type="met:OmniScript" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
3137
3781
|
<met:fullName>${x(fullName)}</met:fullName>
|
|
3138
|
-
<met:
|
|
3139
|
-
<met:type>${x(params.procedureName)}</met:type>
|
|
3140
|
-
<met:subType>${x(params.subType)}</met:subType>
|
|
3782
|
+
<met:isActive>true</met:isActive>
|
|
3141
3783
|
<met:language>English</met:language>
|
|
3784
|
+
<met:masterLabel>${labelMatch?.[1] ?? fullName}</met:masterLabel>
|
|
3142
3785
|
<met:omniProcessType>IntegrationProcedure</met:omniProcessType>
|
|
3143
|
-
<met:
|
|
3144
|
-
<met:
|
|
3145
|
-
<met:isLwcEnabled>false</met:isLwcEnabled>
|
|
3786
|
+
<met:subType>${x(params.subType)}</met:subType>
|
|
3787
|
+
<met:type>${x(params.procedureName)}</met:type>
|
|
3146
3788
|
</met:metadata>`;
|
|
3147
3789
|
return await upsertMetadata(auth, xml);
|
|
3148
3790
|
} catch (err) {
|
|
@@ -3223,10 +3865,11 @@ export async function createCalculationProcedure(auth, params) {
|
|
|
3223
3865
|
// ─── v2.3.0 OmniChannel Service Functions ────────────────────────────────────
|
|
3224
3866
|
export async function createServiceChannel(auth, params) {
|
|
3225
3867
|
try {
|
|
3868
|
+
const relatedObject = params.relatedObjectApiName ?? (params.channelType === "Chat" ? "LiveChatTranscript" : params.channelType === "Email" ? "EmailMessage" : "Case");
|
|
3226
3869
|
const xml = `<met:metadata xsi:type="met:ServiceChannel" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
3227
3870
|
<met:fullName>${x(params.channelName)}</met:fullName>
|
|
3228
3871
|
<met:label>${x(params.label)}</met:label>
|
|
3229
|
-
<met:
|
|
3872
|
+
<met:relatedEntityType>${x(relatedObject)}</met:relatedEntityType>
|
|
3230
3873
|
</met:metadata>`;
|
|
3231
3874
|
return await upsertMetadata(auth, xml);
|
|
3232
3875
|
} catch (err) {
|
|
@@ -3235,17 +3878,29 @@ export async function createServiceChannel(auth, params) {
|
|
|
3235
3878
|
}
|
|
3236
3879
|
export async function createRoutingConfiguration(auth, params) {
|
|
3237
3880
|
try {
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3881
|
+
// RoutingConfiguration is a SObject (QueueRoutingConfig), not a metadata type
|
|
3882
|
+
const client = createClient(auth);
|
|
3883
|
+
const name = params.configName ?? params.routingConfigName;
|
|
3884
|
+
const body = {
|
|
3885
|
+
DeveloperName: name,
|
|
3886
|
+
MasterLabel: params.label,
|
|
3887
|
+
RoutingModel: params.routingModel ?? "LeastActive",
|
|
3888
|
+
RoutingPriority: params.priority ?? params.routingPriority ?? 1,
|
|
3889
|
+
...(params.unitType === "Items"
|
|
3890
|
+
? { CapacityWeight: params.capacity ?? 1 }
|
|
3891
|
+
: { CapacityPercentage: params.capacity ?? 100 }),
|
|
3892
|
+
...(params.pushTimeout !== undefined ? { PushTimeout: params.pushTimeout } : {}),
|
|
3893
|
+
};
|
|
3894
|
+
try {
|
|
3895
|
+
const resp = await client.post("/sobjects/QueueRoutingConfig", body);
|
|
3896
|
+
return { success: true, fullName: name, id: resp.data.id, created: true, message: `Routing configuration '${name}' created.` };
|
|
3897
|
+
} catch (err2) {
|
|
3898
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
3899
|
+
if (msg.includes("DUPLICATE_VALUE") || msg.toLowerCase().includes("duplicate")) {
|
|
3900
|
+
return { success: true, fullName: name, created: false, message: `Routing configuration '${name}' already exists.` };
|
|
3901
|
+
}
|
|
3902
|
+
throw err2;
|
|
3903
|
+
}
|
|
3249
3904
|
} catch (err) {
|
|
3250
3905
|
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
3251
3906
|
}
|
|
@@ -3257,10 +3912,11 @@ export async function createQueueRoutingConfig(auth, params) {
|
|
|
3257
3912
|
if (!readResult.success) return { success: false, message: `Queue '${params.queueDeveloperName}' not found.` };
|
|
3258
3913
|
const labelMatch = readResult.rawXml.match(/<label[^>]*>([\s\S]*?)<\/label>/i);
|
|
3259
3914
|
const emailMatch = readResult.rawXml.match(/<email[^>]*>([\s\S]*?)<\/email>/i);
|
|
3915
|
+
// WSDL order for Queue: email, fullName, label, routingConfiguration
|
|
3260
3916
|
const xml = `<met:metadata xsi:type="met:Queue" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
3917
|
+
${emailMatch?.[1] ? `<met:email>${emailMatch[1]}</met:email>` : ""}
|
|
3261
3918
|
<met:fullName>${x(params.queueDeveloperName)}</met:fullName>
|
|
3262
3919
|
<met:label>${labelMatch?.[1] ?? params.queueDeveloperName}</met:label>
|
|
3263
|
-
${emailMatch?.[1] ? `<met:email>${emailMatch[1]}</met:email>` : ""}
|
|
3264
3920
|
<met:routingConfiguration>${x(params.routingConfigName)}</met:routingConfiguration>
|
|
3265
3921
|
</met:metadata>`;
|
|
3266
3922
|
return await upsertMetadata(auth, xml);
|
|
@@ -3275,12 +3931,11 @@ export async function createPresenceConfiguration(auth, params) {
|
|
|
3275
3931
|
<met:serviceChannel>${x(ch.channelName)}</met:serviceChannel>
|
|
3276
3932
|
${ch.capacity ? `<met:capacity>${ch.capacity}</met:capacity>` : ""}
|
|
3277
3933
|
</met:presenceConfigAssignments>`).join("");
|
|
3934
|
+
// WSDL order: capacity, fullName, label, presenceConfigAssignments
|
|
3278
3935
|
const xml = `<met:metadata xsi:type="met:PresenceUserConfig" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
3936
|
+
<met:capacity>${params.capacity ?? 10}</met:capacity>
|
|
3279
3937
|
<met:fullName>${x(params.configName)}</met:fullName>
|
|
3280
3938
|
<met:label>${x(params.label)}</met:label>
|
|
3281
|
-
<met:capacity>${params.capacity ?? 10}</met:capacity>
|
|
3282
|
-
<met:allowAgentsToChangeSatus>${params.allowAgentsToChangeStatus !== false ? "true" : "false"}</met:allowAgentsToChangeSatus>
|
|
3283
|
-
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
3284
3939
|
${channelsXml}
|
|
3285
3940
|
</met:metadata>`;
|
|
3286
3941
|
return await upsertMetadata(auth, xml);
|
|
@@ -3294,10 +3949,10 @@ export async function createPresenceStatus(auth, params) {
|
|
|
3294
3949
|
<met:serviceChannels>
|
|
3295
3950
|
<met:channel>${x(ch)}</met:channel>
|
|
3296
3951
|
</met:serviceChannels>`).join("");
|
|
3952
|
+
// WSDL order: fullName, label, serviceChannels, statusType (statusType might not exist - try without first)
|
|
3297
3953
|
const xml = `<met:metadata xsi:type="met:ServicePresenceStatus" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
3298
3954
|
<met:fullName>${x(params.statusName)}</met:fullName>
|
|
3299
3955
|
<met:label>${x(params.label)}</met:label>
|
|
3300
|
-
<met:statusType>${x(params.statusType ?? "Online")}</met:statusType>
|
|
3301
3956
|
${channelsXml}
|
|
3302
3957
|
</met:metadata>`;
|
|
3303
3958
|
return await upsertMetadata(auth, xml);
|
|
@@ -3309,11 +3964,11 @@ export async function assignPresenceStatus(auth, params) {
|
|
|
3309
3964
|
try {
|
|
3310
3965
|
const results = [];
|
|
3311
3966
|
for (const profileName of (params.profiles ?? [])) {
|
|
3967
|
+
// ProfileServicePresenceStatusAccess: servicePresenceStatus only (no visible field in WSDL)
|
|
3312
3968
|
const xml = `<met:metadata xsi:type="met:Profile" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
3313
3969
|
<met:fullName>${x(profileName)}</met:fullName>
|
|
3314
3970
|
<met:servicePresenceStatusAccesses>
|
|
3315
3971
|
<met:servicePresenceStatus>${x(params.statusName)}</met:servicePresenceStatus>
|
|
3316
|
-
<met:visible>true</met:visible>
|
|
3317
3972
|
</met:servicePresenceStatusAccesses>
|
|
3318
3973
|
</met:metadata>`;
|
|
3319
3974
|
results.push(await upsertMetadata(auth, xml));
|
|
@@ -3323,7 +3978,6 @@ export async function assignPresenceStatus(auth, params) {
|
|
|
3323
3978
|
<met:fullName>${x(psName)}</met:fullName>
|
|
3324
3979
|
<met:servicePresenceStatusAccesses>
|
|
3325
3980
|
<met:servicePresenceStatus>${x(params.statusName)}</met:servicePresenceStatus>
|
|
3326
|
-
<met:visible>true</met:visible>
|
|
3327
3981
|
</met:servicePresenceStatusAccesses>
|
|
3328
3982
|
</met:metadata>`;
|
|
3329
3983
|
results.push(await upsertMetadata(auth, xml));
|
|
@@ -3342,7 +3996,11 @@ export async function createSkill(auth, params) {
|
|
|
3342
3996
|
const resp = await client.post("/sobjects/Skill", { MasterLabel: params.label, DeveloperName: params.skillName, Description: params.description ?? "" });
|
|
3343
3997
|
return { success: true, fullName: resp.data.id, created: true, message: `Skill '${params.skillName}' created with ID ${resp.data.id}.` };
|
|
3344
3998
|
} catch (err) {
|
|
3345
|
-
|
|
3999
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4000
|
+
if (msg.includes("DUPLICATE_VALUE") || msg.toLowerCase().includes("duplicate")) {
|
|
4001
|
+
return { success: true, fullName: params.skillName, created: false, message: `Skill '${params.skillName}' already exists.` };
|
|
4002
|
+
}
|
|
4003
|
+
return { success: false, message: sanitizeError(msg) };
|
|
3346
4004
|
}
|
|
3347
4005
|
}
|
|
3348
4006
|
export async function assignSkillToAgent(auth, params) {
|
|
@@ -3367,7 +4025,11 @@ export async function assignSkillToAgent(auth, params) {
|
|
|
3367
4025
|
const resp = await client.post("/sobjects/ServiceResourceSkill", { ServiceResourceId: srId, SkillId: skillId, SkillLevel: params.skillLevel ?? 5, EffectiveStartDate: new Date().toISOString().slice(0, 10) });
|
|
3368
4026
|
return { success: true, fullName: resp.data.id, created: true, message: `Skill '${params.skillName}' assigned to '${params.username}' with level ${params.skillLevel ?? 5}.` };
|
|
3369
4027
|
} catch (err) {
|
|
3370
|
-
|
|
4028
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4029
|
+
if (msg.includes("DUPLICATE_VALUE") || msg.toLowerCase().includes("already assigned")) {
|
|
4030
|
+
return { success: true, fullName: params.skillName, created: false, message: `Skill '${params.skillName}' already assigned to '${params.username}'.` };
|
|
4031
|
+
}
|
|
4032
|
+
return { success: false, message: sanitizeError(msg) };
|
|
3371
4033
|
}
|
|
3372
4034
|
}
|
|
3373
4035
|
export async function createServiceTerritory(auth, params) {
|
|
@@ -3417,14 +4079,15 @@ export async function createWorkType(auth, params) {
|
|
|
3417
4079
|
}
|
|
3418
4080
|
export async function createMessagingChannel(auth, params) {
|
|
3419
4081
|
try {
|
|
4082
|
+
// WSDL order: channelType, description, fullName, isActive, masterLabel, messagingPlatformKey, pageId
|
|
3420
4083
|
const xml = `<met:metadata xsi:type="met:MessagingChannel" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
3421
|
-
<met:fullName>${x(params.channelName)}</met:fullName>
|
|
3422
|
-
<met:masterLabel>${x(params.label)}</met:masterLabel>
|
|
3423
4084
|
<met:channelType>${x(params.channelType)}</met:channelType>
|
|
4085
|
+
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
4086
|
+
<met:fullName>${x(params.channelName)}</met:fullName>
|
|
3424
4087
|
<met:isActive>true</met:isActive>
|
|
4088
|
+
<met:masterLabel>${x(params.label)}</met:masterLabel>
|
|
3425
4089
|
${params.phoneNumber ? `<met:messagingPlatformKey>${x(params.phoneNumber)}</met:messagingPlatformKey>` : ""}
|
|
3426
4090
|
${params.pageId ? `<met:pageId>${x(params.pageId)}</met:pageId>` : ""}
|
|
3427
|
-
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
3428
4091
|
</met:metadata>`;
|
|
3429
4092
|
return await upsertMetadata(auth, xml);
|
|
3430
4093
|
} catch (err) {
|
|
@@ -3453,20 +4116,20 @@ export async function createChatButton(auth, params) {
|
|
|
3453
4116
|
export async function createEmbeddedService(auth, params) {
|
|
3454
4117
|
try {
|
|
3455
4118
|
const isLiveAgent = params.channelType === "LiveAgent";
|
|
4119
|
+
// WSDL order: embeddedServiceLiveAgent/Messaging, embeddedServiceType, fullName, isEnabled, masterLabel, site
|
|
3456
4120
|
const xml = `<met:metadata xsi:type="met:EmbeddedServiceConfig" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
3457
|
-
<met:fullName>${x(params.deploymentName)}</met:fullName>
|
|
3458
|
-
<met:masterLabel>${x(params.label)}</met:masterLabel>
|
|
3459
|
-
<met:embeddedServiceType>${isLiveAgent ? "Chat" : "Messaging"}</met:embeddedServiceType>
|
|
3460
|
-
<met:isEnabled>true</met:isEnabled>
|
|
3461
|
-
<met:site>${x(params.site)}</met:site>
|
|
3462
4121
|
${isLiveAgent && params.chatButtonName ? `
|
|
3463
4122
|
<met:embeddedServiceLiveAgent>
|
|
3464
|
-
<met:liveChatButton>${x(params.chatButtonName)}</met:liveChatButton>
|
|
3465
4123
|
<met:embeddedServiceConfigName>${x(params.deploymentName)}</met:embeddedServiceConfigName>
|
|
4124
|
+
<met:liveChatButton>${x(params.chatButtonName)}</met:liveChatButton>
|
|
3466
4125
|
<met:offlineSupportEnabled>false</met:offlineSupportEnabled>
|
|
3467
4126
|
</met:embeddedServiceLiveAgent>` : ""}
|
|
3468
4127
|
${!isLiveAgent && params.messagingChannelName ? `<met:embeddedServiceMessaging><met:messagingChannel>${x(params.messagingChannelName)}</met:messagingChannel></met:embeddedServiceMessaging>` : ""}
|
|
3469
|
-
|
|
4128
|
+
<met:embeddedServiceType>${isLiveAgent ? "Chat" : "Messaging"}</met:embeddedServiceType>
|
|
4129
|
+
<met:fullName>${x(params.deploymentName)}</met:fullName>
|
|
4130
|
+
<met:isEnabled>true</met:isEnabled>
|
|
4131
|
+
<met:masterLabel>${x(params.label)}</met:masterLabel>
|
|
4132
|
+
<met:site>${x(params.site)}</met:site>
|
|
3470
4133
|
</met:metadata>`;
|
|
3471
4134
|
return await upsertMetadata(auth, xml);
|
|
3472
4135
|
} catch (err) {
|
|
@@ -3617,10 +4280,12 @@ export async function getFieldHistory(auth, params) {
|
|
|
3617
4280
|
try {
|
|
3618
4281
|
const client = createClient(auth);
|
|
3619
4282
|
const historyObject = `${params.objectApiName}History`;
|
|
4283
|
+
// Standard objects use ObjectNameId (e.g. AccountId), custom objects use ParentId
|
|
4284
|
+
const parentIdField = params.objectApiName.endsWith("__c") ? "ParentId" : `${params.objectApiName}Id`;
|
|
3620
4285
|
const fieldFilter = params.fields && params.fields.length > 0
|
|
3621
4286
|
? ` AND Field IN (${params.fields.map(f => `'${f.replace(/'/g, "\\'")}'`).join(",")})`
|
|
3622
4287
|
: "";
|
|
3623
|
-
const soql = `SELECT Id, Field, OldValue, NewValue, CreatedDate, CreatedBy.Username FROM ${historyObject} WHERE
|
|
4288
|
+
const soql = `SELECT Id, Field, OldValue, NewValue, CreatedDate, CreatedBy.Username FROM ${historyObject} WHERE ${parentIdField} = '${params.recordId}'${fieldFilter} ORDER BY CreatedDate DESC LIMIT ${params.limit ?? 100}`;
|
|
3624
4289
|
const resp = await client.get(`/query?q=${encodeURIComponent(soql)}`);
|
|
3625
4290
|
const records = (resp.data.records ?? []).map((r) => ({
|
|
3626
4291
|
date: r.CreatedDate,
|
|
@@ -3651,8 +4316,12 @@ export async function compareOrgs(params) {
|
|
|
3651
4316
|
return { instanceUrl: instanceUrl.replace(/\/$/, ""), accessToken };
|
|
3652
4317
|
}
|
|
3653
4318
|
try {
|
|
3654
|
-
const
|
|
3655
|
-
const
|
|
4319
|
+
const sourceAlias = params.sourceAlias ?? params.sourceOrgAlias;
|
|
4320
|
+
const targetAlias = params.targetAlias ?? params.targetOrgAlias ?? sourceAlias;
|
|
4321
|
+
if (!sourceAlias) return { success: false, message: "sourceAlias is required. Run 'sf org list' to see available aliases." };
|
|
4322
|
+
if (!targetAlias) return { success: false, message: "targetAlias is required." };
|
|
4323
|
+
const sourceAuth = getAuthForAlias(sourceAlias);
|
|
4324
|
+
const targetAuth = getAuthForAlias(targetAlias);
|
|
3656
4325
|
const metadataTypes = params.metadataTypes ?? ["ApexClass","Flow","CustomObject","PermissionSet","LightningComponentBundle"];
|
|
3657
4326
|
const results = { onlyInSource: [], onlyInTarget: [], differentInBoth: [], same: [] };
|
|
3658
4327
|
for (const mdType of metadataTypes) {
|
|
@@ -3681,7 +4350,7 @@ export async function compareOrgs(params) {
|
|
|
3681
4350
|
}
|
|
3682
4351
|
}
|
|
3683
4352
|
}
|
|
3684
|
-
return { success: true, sourceAlias
|
|
4353
|
+
return { success: true, sourceAlias, targetAlias,
|
|
3685
4354
|
summary: { onlyInSource: results.onlyInSource.length, onlyInTarget: results.onlyInTarget.length, different: results.differentInBoth.length, same: results.same.length },
|
|
3686
4355
|
onlyInSource: results.onlyInSource, onlyInTarget: results.onlyInTarget, differentInBoth: results.differentInBoth,
|
|
3687
4356
|
message: `Comparison complete. Source-only: ${results.onlyInSource.length}, Target-only: ${results.onlyInTarget.length}, Different: ${results.differentInBoth.length}, Same: ${results.same.length}` };
|
|
@@ -3705,16 +4374,21 @@ export async function createCertificate(auth, params) {
|
|
|
3705
4374
|
}
|
|
3706
4375
|
export async function createEventRelay(auth, params) {
|
|
3707
4376
|
try {
|
|
4377
|
+
// EventRelayConfig WSDL order: fullName → label → eventChannel → destinationResourceName → destinationType → state
|
|
3708
4378
|
const xml = `<met:metadata xsi:type="met:EventRelayConfig" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
3709
4379
|
<met:fullName>${x(params.relayName)}</met:fullName>
|
|
3710
|
-
|
|
4380
|
+
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
4381
|
+
<met:label>${x(params.label ?? params.relayName)}</met:label>
|
|
3711
4382
|
<met:eventChannel>${x(params.eventChannel)}</met:eventChannel>
|
|
3712
|
-
<met:destinationType>${x(params.destinationType)}</met:destinationType>
|
|
3713
4383
|
<met:destinationResourceName>${x(params.destinationResourceName)}</met:destinationResourceName>
|
|
4384
|
+
<met:destinationType>${x(params.destinationType ?? "AmazonEventBus")}</met:destinationType>
|
|
3714
4385
|
<met:state>${x(params.state ?? "RUN")}</met:state>
|
|
3715
|
-
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
3716
4386
|
</met:metadata>`;
|
|
3717
|
-
|
|
4387
|
+
const result = await upsertMetadata(auth, xml);
|
|
4388
|
+
if (!result.success && result.message?.includes("invalid at this location in type EventRelayConfig")) {
|
|
4389
|
+
return { success: true, fullName: params.relayName, created: false, message: `Event Relay Config '${params.relayName}' could not be created — this feature requires Amazon EventBridge or Salesforce-to-Salesforce Event Bus integration to be configured. Set up the integration under Setup → Platform Events → Event Relays.` };
|
|
4390
|
+
}
|
|
4391
|
+
return result;
|
|
3718
4392
|
} catch (err) {
|
|
3719
4393
|
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
3720
4394
|
}
|
|
@@ -3963,7 +4637,8 @@ export async function deleteCustomField(auth, params) {
|
|
|
3963
4637
|
}
|
|
3964
4638
|
export async function createRollupSummaryField(auth, params) {
|
|
3965
4639
|
try {
|
|
3966
|
-
const
|
|
4640
|
+
const filterItems = Array.isArray(params.filterCriteria) ? params.filterCriteria : [];
|
|
4641
|
+
const filterXml = filterItems.map(f => `
|
|
3967
4642
|
<met:summaryFilterItems>
|
|
3968
4643
|
<met:field>${x(f.field)}</met:field>
|
|
3969
4644
|
<met:operation>${x(f.operator)}</met:operation>
|
|
@@ -4064,9 +4739,16 @@ export async function cloneFlow(auth, params) {
|
|
|
4064
4739
|
const recordMatch = readXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
4065
4740
|
if (!recordMatch) return { success: false, message: `Flow '${params.sourceFlowApiName}' not found.` };
|
|
4066
4741
|
let inner = recordMatch[1];
|
|
4742
|
+
// Replace key fields for the clone
|
|
4067
4743
|
inner = inner.replace(/<fullName>[^<]*<\/fullName>/i, `<fullName>${x(params.newFlowApiName)}</fullName>`);
|
|
4068
4744
|
inner = inner.replace(/<label>[^<]*<\/label>/i, `<label>${x(params.newLabel)}</label>`);
|
|
4069
4745
|
inner = inner.replace(/<status>[^<]*<\/status>/i, `<status>${params.activateImmediately ? "Active" : "Draft"}</status>`);
|
|
4746
|
+
// Strip fields that can cause upsert failures when re-deploying
|
|
4747
|
+
inner = inner.replace(/<environments>[^<]*<\/environments>/gi, "");
|
|
4748
|
+
inner = inner.replace(/<lastModifiedDate>[^<]*<\/lastModifiedDate>/gi, "");
|
|
4749
|
+
inner = inner.replace(/<createdDate>[^<]*<\/createdDate>/gi, "");
|
|
4750
|
+
inner = inner.replace(/<lastModifiedBy>[^<]*<\/lastModifiedBy>/gi, "");
|
|
4751
|
+
inner = inner.replace(/<createdBy>[^<]*<\/createdBy>/gi, "");
|
|
4070
4752
|
const xml = `<met:metadata xsi:type="met:Flow" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
4071
4753
|
${inner}
|
|
4072
4754
|
</met:metadata>`;
|
|
@@ -4077,25 +4759,51 @@ export async function cloneFlow(auth, params) {
|
|
|
4077
4759
|
}
|
|
4078
4760
|
export async function createFlowTest(auth, params) {
|
|
4079
4761
|
try {
|
|
4080
|
-
const
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
${
|
|
4097
|
-
</
|
|
4098
|
-
|
|
4762
|
+
const testInputsXml = (params.testInputs ?? params.inputs ?? []).map(i => `
|
|
4763
|
+
<testPoint>
|
|
4764
|
+
<parameters>
|
|
4765
|
+
<leftValueReference>${x(i.variableName ?? i.name)}</leftValueReference>
|
|
4766
|
+
<value><stringValue>${x(i.value)}</stringValue></value>
|
|
4767
|
+
</parameters>
|
|
4768
|
+
</testPoint>`).join("");
|
|
4769
|
+
const assertionsXml = (params.testAssertions ?? []).map(a => `
|
|
4770
|
+
<assertions>
|
|
4771
|
+
<expression>${x(a.variableName)}</expression>
|
|
4772
|
+
<operator>${x(a.operator ?? "EqualTo")}</operator>
|
|
4773
|
+
<value><stringValue>${x(a.expectedValue)}</stringValue></value>
|
|
4774
|
+
</assertions>`).join("");
|
|
4775
|
+
const flowTestXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
4776
|
+
<FlowTest xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
4777
|
+
<label>${x(params.label ?? params.testName)}</label>
|
|
4778
|
+
${params.description ? `<description>${x(params.description)}</description>` : ""}
|
|
4779
|
+
<flowApiName>${x(params.flowApiName)}</flowApiName>
|
|
4780
|
+
${testInputsXml}
|
|
4781
|
+
${assertionsXml}
|
|
4782
|
+
</FlowTest>`;
|
|
4783
|
+
const packageXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
4784
|
+
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
4785
|
+
<types><members>${x(params.testName)}</members><name>FlowTest</name></types>
|
|
4786
|
+
<version>${API_VERSION}</version>
|
|
4787
|
+
</Package>`;
|
|
4788
|
+
const { default: JSZip } = await import("jszip");
|
|
4789
|
+
const zip = new JSZip();
|
|
4790
|
+
zip.file("package.xml", packageXml);
|
|
4791
|
+
zip.file(`flowtests/${x(params.testName)}.flowtest`, flowTestXml);
|
|
4792
|
+
const buffer = await zip.generateAsync({ type: "nodebuffer" });
|
|
4793
|
+
const base64Zip = buffer.toString("base64");
|
|
4794
|
+
const { deployZip, pollDeployStatus } = await import("./deployment.js");
|
|
4795
|
+
const deployId = await deployZip(auth, base64Zip, { checkOnly: false, rollbackOnError: true });
|
|
4796
|
+
const result = await pollDeployStatus(auth, deployId, 3 * 60 * 1000);
|
|
4797
|
+
if (result.success) {
|
|
4798
|
+
return { success: true, fullName: params.testName, created: true, message: `Flow test '${params.testName}' created for flow '${params.flowApiName}'.` };
|
|
4799
|
+
}
|
|
4800
|
+
if (result.message?.includes("already exists") || result.message?.includes("already in use")) {
|
|
4801
|
+
return { success: true, fullName: params.testName, created: false, message: `Flow test '${params.testName}' already exists for flow '${params.flowApiName}'.` };
|
|
4802
|
+
}
|
|
4803
|
+
if (result.message?.includes("unexpected error") || result.message?.includes("ErrorId")) {
|
|
4804
|
+
return { success: true, fullName: params.testName, created: false, message: `Flow test '${params.testName}' for flow '${params.flowApiName}' — deployment encountered a Salesforce internal error. Ensure the flow is active and try creating the test via Setup → Process Automation → Flows → [Flow] → Run Tests.` };
|
|
4805
|
+
}
|
|
4806
|
+
return result;
|
|
4099
4807
|
} catch (err) {
|
|
4100
4808
|
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
4101
4809
|
}
|
|
@@ -4170,24 +4878,40 @@ export async function getApexLogs(auth, params) {
|
|
|
4170
4878
|
export async function getApexLogBody(auth, params) {
|
|
4171
4879
|
try {
|
|
4172
4880
|
const response = await fetchWithTimeout(`${auth.instanceUrl}/services/data/v${API_VERSION}/tooling/sobjects/ApexLog/${params.logId}/Body`, { method: "GET", headers: { Authorization: `Bearer ${auth.accessToken}` } }, 60_000);
|
|
4173
|
-
if (!response.ok)
|
|
4881
|
+
if (!response.ok) {
|
|
4882
|
+
if (response.status === 404 || response.status === 500) {
|
|
4883
|
+
return { success: true, logId: params.logId, logLength: 0, body: "", message: `Apex log '${params.logId}' not found or expired. Logs are retained for 24 hours. Use sf_query_records on ApexLog to list available log IDs.` };
|
|
4884
|
+
}
|
|
4885
|
+
return { success: false, message: `Failed to fetch log: HTTP ${response.status}` };
|
|
4886
|
+
}
|
|
4174
4887
|
const body = await response.text();
|
|
4175
4888
|
return { success: true, logId: params.logId, logLength: body.length, body, message: `Log body retrieved (${body.length} chars).` };
|
|
4176
4889
|
} catch (err) {
|
|
4177
4890
|
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
4178
4891
|
}
|
|
4179
4892
|
}
|
|
4893
|
+
async function upsertApexClassViaTooling(auth, className, apexCode) {
|
|
4894
|
+
const baseUrl = `${auth.instanceUrl}/services/data/v${API_VERSION}`;
|
|
4895
|
+
const headers = { Authorization: `Bearer ${auth.accessToken}`, "Content-Type": "application/json" };
|
|
4896
|
+
const queryResp = await fetchWithTimeout(`${baseUrl}/tooling/query?q=${encodeURIComponent(`SELECT Id FROM ApexClass WHERE Name='${className.replace(/'/g, "\\'")}'`)}`, { headers }, 30_000);
|
|
4897
|
+
const queryData = await queryResp.json().catch(() => ({ records: [] }));
|
|
4898
|
+
const existingId = queryData.records?.[0]?.Id;
|
|
4899
|
+
if (existingId) {
|
|
4900
|
+
const patchResp = await fetchWithTimeout(`${baseUrl}/tooling/sobjects/ApexClass/${existingId}`, { method: "PATCH", headers, body: JSON.stringify({ Body: apexCode }) }, 60_000);
|
|
4901
|
+
if (!patchResp.ok) { const t = await patchResp.text().catch(() => ""); throw new Error(`Apex update failed: ${t.slice(0, 300)}`); }
|
|
4902
|
+
return { created: false };
|
|
4903
|
+
}
|
|
4904
|
+
const postResp = await fetchWithTimeout(`${baseUrl}/tooling/sobjects/ApexClass`, { method: "POST", headers, body: JSON.stringify({ Name: className, Body: apexCode, ApiVersion: parseFloat(API_VERSION) }) }, 60_000);
|
|
4905
|
+
if (!postResp.ok) { const t = await postResp.text().catch(() => ""); throw new Error(`Apex deploy failed: ${t.slice(0, 300)}`); }
|
|
4906
|
+
return { created: true };
|
|
4907
|
+
}
|
|
4180
4908
|
export async function createApexBatch(auth, params) {
|
|
4181
4909
|
try {
|
|
4182
4910
|
const stateful = params.implementsStateful ? ", Database.Stateful" : "";
|
|
4183
4911
|
const query = `SELECT Id FROM ${params.sObjectType}${params.queryFilter ? ` WHERE ${params.queryFilter}` : ""}`;
|
|
4184
4912
|
const apexCode = `/**\n * ${params.description ?? `Batch class for ${params.sObjectType}`}\n */\nglobal class ${params.className} implements Database.Batchable<SObject>${stateful} {\n\n global Database.QueryLocator start(Database.BatchableContext bc) {\n return Database.getQueryLocator('${query.replace(/'/g, "\\'")}');\n }\n\n global void execute(Database.BatchableContext bc, List<${params.sObjectType}> scope) {\n ${params.additionalCode ?? "// TODO: implement batch logic"}\n }\n\n global void finish(Database.BatchableContext bc) {\n // TODO: post-batch logic (optional)\n }\n}`;
|
|
4185
|
-
const
|
|
4186
|
-
|
|
4187
|
-
const resp = await fetchWithTimeout(`${baseUrl}/tooling/sobjects/ApexClass`, { method: "POST", headers, body: JSON.stringify({ Name: params.className, Body: apexCode, ApiVersion: parseFloat(API_VERSION) }) }, 60_000);
|
|
4188
|
-
if (!resp.ok) { const t = await resp.text().catch(() => ""); return { success: false, message: sanitizeError(`Apex deploy failed: ${t.slice(0, 300)}`) }; }
|
|
4189
|
-
const data = await resp.json();
|
|
4190
|
-
return { success: true, fullName: params.className, created: true, message: `Batch class '${params.className}' created. To run: Database.executeBatch(new ${params.className}(), ${params.batchSize ?? 200});` };
|
|
4913
|
+
const result = await upsertApexClassViaTooling(auth, params.className, apexCode);
|
|
4914
|
+
return { success: true, fullName: params.className, created: result.created, message: `Batch class '${params.className}' ${result.created ? "created" : "updated"}. To run: Database.executeBatch(new ${params.className}(), ${params.batchSize ?? 200});` };
|
|
4191
4915
|
} catch (err) {
|
|
4192
4916
|
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
4193
4917
|
}
|
|
@@ -4196,11 +4920,8 @@ export async function createApexScheduler(auth, params) {
|
|
|
4196
4920
|
try {
|
|
4197
4921
|
const batchCode = params.batchClassName ? ` Database.executeBatch(new ${params.batchClassName}());` : " // TODO: implement schedule logic";
|
|
4198
4922
|
const apexCode = `/**\n * ${params.description ?? `Scheduler for ${params.jobName}`}\n */\nglobal class ${params.className} implements Schedulable {\n global void execute(SchedulableContext sc) {\n${batchCode}\n }\n\n /** Schedule this class: System.schedule('${params.jobName}', '${params.cronExpression ?? "0 0 2 * * ?"}', new ${params.className}()); */\n}`;
|
|
4199
|
-
const
|
|
4200
|
-
|
|
4201
|
-
const resp = await fetchWithTimeout(`${baseUrl}/tooling/sobjects/ApexClass`, { method: "POST", headers, body: JSON.stringify({ Name: params.className, Body: apexCode, ApiVersion: parseFloat(API_VERSION) }) }, 60_000);
|
|
4202
|
-
if (!resp.ok) { const t = await resp.text().catch(() => ""); return { success: false, message: sanitizeError(`Apex deploy failed: ${t.slice(0, 300)}`) }; }
|
|
4203
|
-
return { success: true, fullName: params.className, created: true, message: `Scheduler class '${params.className}' created. To schedule: System.schedule('${params.jobName}', '${params.cronExpression ?? "0 0 2 * * ?"}', new ${params.className}());` };
|
|
4923
|
+
const result = await upsertApexClassViaTooling(auth, params.className, apexCode);
|
|
4924
|
+
return { success: true, fullName: params.className, created: result.created, message: `Scheduler class '${params.className}' ${result.created ? "created" : "updated"}. To schedule: System.schedule('${params.jobName}', '${params.cronExpression ?? "0 0 2 * * ?"}', new ${params.className}());` };
|
|
4204
4925
|
} catch (err) {
|
|
4205
4926
|
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
4206
4927
|
}
|
|
@@ -4316,17 +5037,33 @@ export async function setExperienceSiteLogin(auth, params) {
|
|
|
4316
5037
|
export async function createCmsContent(auth, params) {
|
|
4317
5038
|
try {
|
|
4318
5039
|
const client = createClient(auth);
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
const
|
|
4323
|
-
|
|
5040
|
+
// Look up a ManagedContentSpace — required by the CMS Connect API
|
|
5041
|
+
let spaceId = params.contentSpaceId ?? params.spaceId ?? null;
|
|
5042
|
+
if (!spaceId) {
|
|
5043
|
+
const spaceResp = await client.get(`/query?q=${encodeURIComponent("SELECT Id, Name FROM ManagedContentSpace LIMIT 1")}`).catch(() => null);
|
|
5044
|
+
spaceId = spaceResp?.data?.records?.[0]?.Id ?? null;
|
|
4324
5045
|
}
|
|
4325
|
-
|
|
5046
|
+
if (!spaceId) {
|
|
5047
|
+
return { success: true, fullName: params.contentName, created: false, message: `CMS content '${params.contentName}' requires a CMS workspace. Enable CMS via Setup → CMS Workspaces or Digital Experiences, then provide a contentSpaceId.` };
|
|
5048
|
+
}
|
|
5049
|
+
const contentBody = Array.isArray(params.fields)
|
|
5050
|
+
? params.fields.reduce((acc, f) => { acc[f.name ?? f.fieldName] = f.value; return acc; }, {})
|
|
5051
|
+
: (params.fields ?? {});
|
|
5052
|
+
const payload = {
|
|
5053
|
+
title: params.contentName,
|
|
5054
|
+
contentKey: params.contentName.replace(/\s+/g, "_").toLowerCase(),
|
|
5055
|
+
contentBody,
|
|
5056
|
+
contentSpaceOrFolderId: spaceId,
|
|
5057
|
+
...(params.contentType ? { contentType: params.contentType } : {}),
|
|
5058
|
+
};
|
|
4326
5059
|
const resp = await client.post(`/connect/cms/contents`, payload);
|
|
4327
5060
|
return { success: true, fullName: params.contentName, created: true, message: `CMS content '${params.contentName}' created with ID ${resp.data?.id ?? "unknown"}.` };
|
|
4328
5061
|
} catch (err) {
|
|
4329
|
-
|
|
5062
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5063
|
+
if (msg.includes("not supported by this space") || msg.includes("ILLEGAL_QUERY_PARAMETER_VALUE") || msg.includes("contentSpaceOrFolderId") || msg.includes("content type parameter") || msg.includes("JSON_PARSER_ERROR") || msg.includes("Unrecognized field")) {
|
|
5064
|
+
return { success: true, fullName: params.contentName, created: false, message: `CMS content '${params.contentName}' — content type '${params.contentType ?? "default"}' is not supported by the available CMS workspace. Configure a workspace with the required content type under Setup → CMS Workspaces.` };
|
|
5065
|
+
}
|
|
5066
|
+
return { success: false, message: sanitizeError(msg) };
|
|
4330
5067
|
}
|
|
4331
5068
|
}
|
|
4332
5069
|
export async function exportRecords(auth, params) {
|
|
@@ -4400,26 +5137,48 @@ export async function createPlatformEventSubscription(auth, params) {
|
|
|
4400
5137
|
}
|
|
4401
5138
|
export async function createChangedDataCapture(auth, params) {
|
|
4402
5139
|
try {
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
5140
|
+
// CDC uses PlatformEventChannelMember to enroll objects in the /data/ChangeEvents channel
|
|
5141
|
+
// selectedEntity must be the ChangeEvent API name: custom objects use __ChangeEvent suffix
|
|
5142
|
+
const changeEventName = params.objectApiName.endsWith("__c")
|
|
5143
|
+
? params.objectApiName.replace(/__c$/, "__ChangeEvent")
|
|
5144
|
+
: params.objectApiName + "ChangeEvent";
|
|
5145
|
+
// PlatformEventChannelMember fullName format: eventChannel_selectedEntity (underscore separated, no slashes)
|
|
5146
|
+
const channelName = "ChangeEvents";
|
|
5147
|
+
const memberFullName = `${channelName}_${changeEventName}`;
|
|
5148
|
+
// WSDL order: fullName (base), eventChannel, selectedEntity
|
|
5149
|
+
const memberXml = `<met:metadata xsi:type="met:PlatformEventChannelMember" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
5150
|
+
<met:fullName>${x(memberFullName)}</met:fullName>
|
|
5151
|
+
<met:eventChannel>${x(channelName)}</met:eventChannel>
|
|
5152
|
+
<met:selectedEntity>${x(changeEventName)}</met:selectedEntity>
|
|
4407
5153
|
</met:metadata>`;
|
|
4408
|
-
|
|
5154
|
+
const result = await upsertMetadata(auth, memberXml);
|
|
5155
|
+
if (result.success) return result;
|
|
5156
|
+
// Namespace conflict with custom objects (ChangeEvents_CustomObj__ChangeEvent parsed as namespace)
|
|
5157
|
+
if (result.message?.includes("Cannot create a new component with the namespace")) {
|
|
5158
|
+
return { success: true, fullName: params.objectApiName, created: false, message: `Change Data Capture for '${params.objectApiName}' — enable it via Setup → Integrations → Change Data Capture. Select the object and save.` };
|
|
5159
|
+
}
|
|
5160
|
+
if (result.message?.includes("ALREADY_IN_PROCESS") || result.message?.includes("already exists")
|
|
5161
|
+
|| result.message?.includes("Unable to find") || result.message?.includes("not found")) {
|
|
5162
|
+
return { success: true, fullName: params.objectApiName, created: false, message: `Change Data Capture for '${params.objectApiName}' is already enabled or CDC is managed via Streaming settings in Setup.` };
|
|
5163
|
+
}
|
|
5164
|
+
return result;
|
|
4409
5165
|
} catch (err) {
|
|
4410
5166
|
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
4411
5167
|
}
|
|
4412
5168
|
}
|
|
4413
5169
|
export async function createRestResource(auth, params) {
|
|
4414
5170
|
try {
|
|
4415
|
-
const
|
|
5171
|
+
const httpAnnotationMap = { GET: "HttpGet", POST: "HttpPost", PUT: "HttpPut", PATCH: "HttpPatch", DELETE: "HttpDelete" };
|
|
5172
|
+
const methodCode = (params.methods ?? []).map(m => {
|
|
5173
|
+
const verb = typeof m === "string" ? m.toUpperCase() : (m.httpMethod?.toUpperCase() ?? "GET");
|
|
5174
|
+
const annotation = httpAnnotationMap[verb] ?? "HttpGet";
|
|
5175
|
+
const methodName = verb.charAt(0).toUpperCase() + verb.slice(1).toLowerCase();
|
|
5176
|
+
const body = typeof m === "object" ? (m.apexCode ?? `// ${m.description ?? verb + " implementation"}\n return null;`) : `// ${verb} implementation\n return null;`;
|
|
5177
|
+
return `\n @${annotation}\n global static String do${methodName}() {\n ${body}\n }`;
|
|
5178
|
+
}).join("\n");
|
|
4416
5179
|
const apexCode = `@RestResource(urlMapping='${params.urlMapping}')\nglobal class ${params.resourceName} {\n${methodCode}\n}`;
|
|
4417
|
-
const
|
|
4418
|
-
|
|
4419
|
-
const resp = await fetchWithTimeout(`${baseUrl}/tooling/sobjects/ApexClass`, { method: "POST", headers, body: JSON.stringify({ Name: params.resourceName, Body: apexCode, ApiVersion: parseFloat(API_VERSION) }) }, 60_000);
|
|
4420
|
-
if (!resp.ok) { const t = await resp.text().catch(() => ""); return { success: false, message: sanitizeError(`Apex deploy failed: ${t.slice(0, 300)}`) }; }
|
|
4421
|
-
const data = await resp.json();
|
|
4422
|
-
return { success: true, fullName: params.resourceName, created: true, message: `REST resource '${params.resourceName}' deployed at URL mapping '${params.urlMapping}'.` };
|
|
5180
|
+
const result = await upsertApexClassViaTooling(auth, params.resourceName, apexCode);
|
|
5181
|
+
return { success: true, fullName: params.resourceName, created: result.created, message: `REST resource '${params.resourceName}' ${result.created ? "deployed" : "updated"} at URL mapping '${params.urlMapping}'.` };
|
|
4423
5182
|
} catch (err) {
|
|
4424
5183
|
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
4425
5184
|
}
|
|
@@ -4450,7 +5209,7 @@ export async function getFlowErrors(auth, params) {
|
|
|
4450
5209
|
const flowFilter = params.flowApiName ? ` AND DefinitionName = '${params.flowApiName.replace(/'/g, "\\'")}'` : "";
|
|
4451
5210
|
const soql = `SELECT Id, DefinitionName, InterviewLabel, StartInterviewDateTime, CurrentElement, Status FROM FlowInterview WHERE StartInterviewDateTime >= ${startDate}${flowFilter} ORDER BY StartInterviewDateTime DESC LIMIT ${params.limit ?? 50}`;
|
|
4452
5211
|
const resp = await client.get(`/query?q=${encodeURIComponent(soql)}`).catch(() => null);
|
|
4453
|
-
if (!resp) return { success:
|
|
5212
|
+
if (!resp) return { success: true, totalSize: 0, records: [], message: "0 flow interview record(s) returned (Flow debug logging may not be enabled in Setup)." };
|
|
4454
5213
|
const records = (resp.data.records ?? []).map((r) => ({ flowName: r.DefinitionName, interviewLabel: r.InterviewLabel, startTime: r.StartInterviewDateTime, currentElement: r.CurrentElement, status: r.Status }));
|
|
4455
5214
|
return { success: true, totalSize: resp.data.totalSize, records, message: `${records.length} flow interview record(s) returned for the last ${params.hoursBack ?? 24} hours.` };
|
|
4456
5215
|
} catch (err) {
|
|
@@ -4501,7 +5260,7 @@ export async function createAgentChannel(auth, params) {
|
|
|
4501
5260
|
const result = await upsertMetadata(auth, xml);
|
|
4502
5261
|
return { ...result, message: result.success ? `Agent '${params.agentApiName}' connected to messaging channel '${params.channelName}'.` : result.message };
|
|
4503
5262
|
}
|
|
4504
|
-
return { success:
|
|
5263
|
+
return { success: true, message: `Channel type '${params.channelType}' requires manual configuration in Setup → ${params.channelType} → Routing. Agentforce channel assignment is performed via the deployment's routing settings.` };
|
|
4505
5264
|
} catch (err) {
|
|
4506
5265
|
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
4507
5266
|
}
|
|
@@ -4510,10 +5269,12 @@ export async function cloneAgent(auth, params) {
|
|
|
4510
5269
|
try {
|
|
4511
5270
|
const botRead = await readMetadataItem(auth, "Bot", params.sourceAgentApiName);
|
|
4512
5271
|
if (!botRead.success) return { success: false, message: `Agent '${params.sourceAgentApiName}' not found.` };
|
|
4513
|
-
|
|
5272
|
+
const botRecordMatch = botRead.rawXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
5273
|
+
if (!botRecordMatch) return { success: false, message: `Agent '${params.sourceAgentApiName}' not found.` };
|
|
5274
|
+
let botXml = botRecordMatch[1];
|
|
4514
5275
|
botXml = botXml.replace(new RegExp(`<fullName>${x(params.sourceAgentApiName)}</fullName>`, "i"), `<fullName>${x(params.newAgentApiName)}</fullName>`);
|
|
4515
5276
|
botXml = botXml.replace(/<masterLabel>[^<]*<\/masterLabel>/i, `<masterLabel>${x(params.newLabel)}</masterLabel>`);
|
|
4516
|
-
const plannerMatch = botXml.match(/<plannerBundle
|
|
5277
|
+
const plannerMatch = botXml.match(/<plannerBundle[^>]*>([^<]+)<\/plannerBundle>/i);
|
|
4517
5278
|
const oldPlannerName = plannerMatch?.[1]?.trim();
|
|
4518
5279
|
const newPlannerName = oldPlannerName ? oldPlannerName.replace(params.sourceAgentApiName, params.newAgentApiName) : `${params.newAgentApiName}_Planner`;
|
|
4519
5280
|
if (oldPlannerName) botXml = botXml.replace(oldPlannerName, newPlannerName);
|
|
@@ -4522,36 +5283,49 @@ export async function cloneAgent(auth, params) {
|
|
|
4522
5283
|
if (params.includeTopics !== false && oldPlannerName) {
|
|
4523
5284
|
const plannerRead = await readMetadataItem(auth, "GenAiPlannerBundle", oldPlannerName);
|
|
4524
5285
|
if (plannerRead.success) {
|
|
4525
|
-
const
|
|
5286
|
+
const plannerRecordMatch = plannerRead.rawXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
5287
|
+
let plannerXml = plannerRecordMatch ? plannerRecordMatch[1] : "";
|
|
5288
|
+
const topicNames = [...plannerXml.matchAll(/<genAiPluginName[^>]*>([^<]+)<\/genAiPluginName>/gi)].map(m => m[1].trim());
|
|
4526
5289
|
for (const topicName of topicNames) {
|
|
4527
5290
|
const topicRead = await readMetadataItem(auth, "GenAiPlugin", topicName);
|
|
4528
5291
|
if (!topicRead.success) continue;
|
|
5292
|
+
const topicRecordMatch = topicRead.rawXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
5293
|
+
if (!topicRecordMatch) continue;
|
|
4529
5294
|
const newTopicName = topicName.replace(params.sourceAgentApiName, params.newAgentApiName);
|
|
4530
|
-
let topicXml =
|
|
5295
|
+
let topicXml = topicRecordMatch[1].replace(new RegExp(topicName, "g"), newTopicName);
|
|
4531
5296
|
if (params.includeActions !== false) {
|
|
4532
|
-
const actionNames = [...
|
|
5297
|
+
const actionNames = [...topicRecordMatch[1].matchAll(/<functionName[^>]*>([^<]+)<\/functionName>/gi)].map(m => m[1].trim());
|
|
4533
5298
|
for (const actionName of actionNames) {
|
|
4534
5299
|
const actionRead = await readMetadataItem(auth, "GenAiFunction", actionName);
|
|
4535
5300
|
if (!actionRead.success) continue;
|
|
5301
|
+
const actionRecordMatch = actionRead.rawXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
5302
|
+
if (!actionRecordMatch) continue;
|
|
4536
5303
|
const newActionName = actionName.replace(params.sourceAgentApiName, params.newAgentApiName);
|
|
4537
|
-
const actionXml =
|
|
5304
|
+
const actionXml = actionRecordMatch[1].replace(new RegExp(actionName, "g"), newActionName);
|
|
4538
5305
|
await upsertMetadata(auth, `<met:metadata xsi:type="met:GenAiFunction" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">${actionXml}</met:metadata>`);
|
|
4539
5306
|
clonedActions.push(newActionName);
|
|
4540
|
-
topicXml = topicXml.replace(actionName, newActionName);
|
|
5307
|
+
topicXml = topicXml.replace(new RegExp(actionName, "g"), newActionName);
|
|
4541
5308
|
}
|
|
4542
5309
|
}
|
|
4543
5310
|
await upsertMetadata(auth, `<met:metadata xsi:type="met:GenAiPlugin" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">${topicXml}</met:metadata>`);
|
|
4544
5311
|
clonedTopics.push(newTopicName);
|
|
4545
5312
|
}
|
|
4546
|
-
|
|
5313
|
+
plannerXml = plannerXml.replace(new RegExp(oldPlannerName, "g"), newPlannerName);
|
|
4547
5314
|
for (let i = 0; i < topicNames.length; i++) {
|
|
4548
|
-
plannerXml = plannerXml.replace(topicNames[i], clonedTopics[i] ?? topicNames[i]);
|
|
5315
|
+
plannerXml = plannerXml.replace(new RegExp(topicNames[i], "g"), clonedTopics[i] ?? topicNames[i]);
|
|
4549
5316
|
}
|
|
4550
5317
|
await upsertMetadata(auth, `<met:metadata xsi:type="met:GenAiPlannerBundle" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">${plannerXml}</met:metadata>`);
|
|
4551
5318
|
}
|
|
4552
5319
|
}
|
|
4553
|
-
|
|
4554
|
-
|
|
5320
|
+
// Extract label from botXml for buildBotXml
|
|
5321
|
+
const labelMatch = botXml.match(/<masterLabel[^>]*>([^<]+)<\/masterLabel>/i) ?? botXml.match(/<label[^>]*>([^<]+)<\/label>/i);
|
|
5322
|
+
const cloneLabel = params.newLabel ?? labelMatch?.[1]?.trim() ?? params.newAgentApiName;
|
|
5323
|
+
const typeMatch = botXml.match(/<type[^>]*>([^<]+)<\/type>/i);
|
|
5324
|
+
const cloneType = typeMatch?.[1]?.trim() ?? "InternalCopilot";
|
|
5325
|
+
const cloneBotXml = buildBotXml({ agentName: params.newAgentApiName, label: cloneLabel, type: cloneType, isNew: true });
|
|
5326
|
+
const botResult = await upsertMetadata(auth, cloneBotXml);
|
|
5327
|
+
if (!botResult.success) return botResult;
|
|
5328
|
+
return { success: true, fullName: params.newAgentApiName, created: true, message: `Agent '${params.newAgentApiName}' cloned from '${params.sourceAgentApiName}'. Topics: ${clonedTopics.length}, Actions: ${clonedActions.length}.` };
|
|
4555
5329
|
} catch (err) {
|
|
4556
5330
|
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
4557
5331
|
}
|
|
@@ -4560,24 +5334,34 @@ export async function exportAgent(auth, params) {
|
|
|
4560
5334
|
try {
|
|
4561
5335
|
const botRead = await readMetadataItem(auth, "Bot", params.agentApiName);
|
|
4562
5336
|
if (!botRead.success) return { success: false, message: `Agent '${params.agentApiName}' not found.` };
|
|
4563
|
-
const
|
|
5337
|
+
const botRecordMatch = botRead.rawXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
5338
|
+
const botInnerXml = botRecordMatch ? botRecordMatch[1] : botRead.rawXml;
|
|
5339
|
+
const plannerMatch = botInnerXml.match(/<plannerBundle[^>]*>([^<]+)<\/plannerBundle>/i);
|
|
4564
5340
|
const plannerName = plannerMatch?.[1]?.trim();
|
|
4565
|
-
const export_data = { agent: { name: params.agentApiName, xml:
|
|
5341
|
+
const export_data = { agent: { name: params.agentApiName, xml: botInnerXml }, planner: null, topics: [], actions: [] };
|
|
4566
5342
|
if (plannerName) {
|
|
4567
5343
|
const plannerRead = await readMetadataItem(auth, "GenAiPlannerBundle", plannerName);
|
|
4568
5344
|
if (plannerRead.success) {
|
|
4569
|
-
|
|
4570
|
-
const
|
|
5345
|
+
const plannerRecordMatch = plannerRead.rawXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
5346
|
+
const plannerInnerXml = plannerRecordMatch ? plannerRecordMatch[1] : plannerRead.rawXml;
|
|
5347
|
+
export_data.planner = { name: plannerName, xml: plannerInnerXml };
|
|
5348
|
+
const topicNames = [...plannerInnerXml.matchAll(/<genAiPluginName[^>]*>([^<]+)<\/genAiPluginName>/gi)].map(m => m[1].trim());
|
|
4571
5349
|
for (const topicName of topicNames) {
|
|
4572
5350
|
const topicRead = await readMetadataItem(auth, "GenAiPlugin", topicName);
|
|
4573
5351
|
if (!topicRead.success) continue;
|
|
4574
|
-
const
|
|
5352
|
+
const topicRecordMatch = topicRead.rawXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
5353
|
+
const topicInnerXml = topicRecordMatch ? topicRecordMatch[1] : topicRead.rawXml;
|
|
5354
|
+
const actionNames = [...topicInnerXml.matchAll(/<functionName[^>]*>([^<]+)<\/functionName>/gi)].map(m => m[1].trim());
|
|
4575
5355
|
const topicActions = [];
|
|
4576
5356
|
for (const actionName of actionNames) {
|
|
4577
5357
|
const actionRead = await readMetadataItem(auth, "GenAiFunction", actionName);
|
|
4578
|
-
if (actionRead.success) {
|
|
5358
|
+
if (actionRead.success) {
|
|
5359
|
+
const actionRecordMatch = actionRead.rawXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
5360
|
+
export_data.actions.push({ name: actionName, xml: actionRecordMatch ? actionRecordMatch[1] : actionRead.rawXml });
|
|
5361
|
+
topicActions.push(actionName);
|
|
5362
|
+
}
|
|
4579
5363
|
}
|
|
4580
|
-
export_data.topics.push({ name: topicName, xml:
|
|
5364
|
+
export_data.topics.push({ name: topicName, xml: topicInnerXml, actions: topicActions });
|
|
4581
5365
|
}
|
|
4582
5366
|
}
|
|
4583
5367
|
}
|