salesforce-metadata-mcp 2.5.1 → 2.5.2
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 +39 -0
- package/README.md +15 -1
- package/TOOLS.md +1 -1
- package/dist/index.js +4 -4
- package/dist/services/salesforce.js +349 -143
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.5.2] - 2026-05-29
|
|
4
|
+
|
|
5
|
+
### Bug Fixes — 9 SOAP API Compatibility Corrections
|
|
6
|
+
|
|
7
|
+
All fixes address HTTP 500 errors or incorrect metadata caused by element ordering or invalid elements in SOAP upsertMetadata calls against the Salesforce Metadata API v66.
|
|
8
|
+
|
|
9
|
+
#### `sf_create_list_view`
|
|
10
|
+
- **Fix:** Replaced invalid `<allUsers>` element with `<allInternalUsers/>` in the `SharedTo` type. The previous element caused HTTP 500 "Element allUsers invalid at this location."
|
|
11
|
+
|
|
12
|
+
#### `sf_create_email_template`
|
|
13
|
+
- **Fix:** Removed invalid `<htmlValue>` element from SOAP Metadata API v66; email templates now always use `type: text`. HTML content is preserved in `textOnly` field.
|
|
14
|
+
|
|
15
|
+
#### `sf_create_page_layout`
|
|
16
|
+
- **Fix:** The `Name` field in a layout section now gets `behavior: Required` instead of `behavior: Edit`. Salesforce enforces "Field:Name must be Required" for standard name fields.
|
|
17
|
+
|
|
18
|
+
#### `sf_clone_profile`
|
|
19
|
+
- **Fix:** Strips all `<tabVisibilities>` blocks from the cloned profile XML. Re-upserting profile tab settings for system-managed tabs (e.g., AINaturalLangProcessRslt) causes "You can't edit tab settings for..." errors.
|
|
20
|
+
|
|
21
|
+
#### `sf_add_picklist_values`
|
|
22
|
+
- **Fix:** Replaced Tooling API field lookup with `readMetadata` on the parent `CustomObject`. The Tooling API does not index fields created in the same session via the Metadata API, causing "Field not found" errors on freshly-created fields.
|
|
23
|
+
|
|
24
|
+
#### `sf_create_lightning_record_page` (`createFlexiPage`)
|
|
25
|
+
- **Fix:** Corrected element name from `<pageType>` to `<type>` (WSDL name). Added required `<template>` and `<flexiPageRegions>` elements. Corrected WSDL element ordering. Added support for `apiName` parameter alias.
|
|
26
|
+
|
|
27
|
+
#### `sf_create_letterhead`
|
|
28
|
+
- **Fix:** Rewrote XML structure to match WSDL sequence order (bottomLine → description → footer → header → middleLine → name → topLine). Fixed `LetterheadHeaderFooter` to use `<backgroundColor>` (not `<color>`, which is invalid in that type). Removed invalid `<fontFace>` and `<fontSize>` elements. Added required `<middleLine>` element. Removed invalid `<style>` element from `LetterheadLine`.
|
|
29
|
+
|
|
30
|
+
#### `sf_update_dashboard`
|
|
31
|
+
- **Fix:** When the target dashboard does not exist, creates it with all required fields: `backgroundEndColor`, `backgroundFadeDirection`, `backgroundStartColor`, `dashboardType`, `leftSection`, `rightSection`, `runningUser`, `textColor`, `title`, `titleColor`, `titleSize`.
|
|
32
|
+
|
|
33
|
+
#### `sf_create_quick_action`
|
|
34
|
+
- **Fix:** Corrected WSDL element ordering (alphabetical). Added `<quickActionLayout>` support when `fields` parameter is provided. Fields named `Subject`, `Name`, or `LastName` automatically receive `uiBehavior: Required`.
|
|
35
|
+
|
|
36
|
+
### Test Coverage
|
|
37
|
+
- **Before:** 65/83 (78%) → **After:** 76/83 (92%) — 11 additional tool passes
|
|
38
|
+
- All failures remaining are org-level limitations: empty-flow activation, OmniStudio (package not installed), Experience Cloud (feature not enabled), FlowInterview debug logging disabled
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
3
42
|
## [2.5.1] - 2026-05-28
|
|
4
43
|
|
|
5
44
|
### Documentation
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# salesforce-metadata-mcp
|
|
2
2
|
|
|
3
|
-
[](https://npmjs.com/package/salesforce-metadata-mcp)
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
[](https://modelcontextprotocol.io)
|
|
6
6
|
|
|
@@ -46,6 +46,20 @@ See [SETUP.md](SETUP.md) for all authentication methods and detailed setup instr
|
|
|
46
46
|
|
|
47
47
|
---
|
|
48
48
|
|
|
49
|
+
## What's New in v2.5.2
|
|
50
|
+
|
|
51
|
+
Bug-fix release: 9 SOAP API compatibility corrections that resolve HTTP 500 errors and incorrect metadata against Salesforce Metadata API v66. Test coverage improved from 78% → 92% (76/83 tools passing).
|
|
52
|
+
|
|
53
|
+
- **`sf_create_list_view`** — Fixed `<allUsers>` → `<allInternalUsers/>` in SharedTo type
|
|
54
|
+
- **`sf_create_email_template`** — Removed invalid `<htmlValue>` element (API v66 incompatible)
|
|
55
|
+
- **`sf_create_page_layout`** — Name field now correctly gets `behavior: Required`
|
|
56
|
+
- **`sf_clone_profile`** — Strips `<tabVisibilities>` for system-managed tabs before re-upsert
|
|
57
|
+
- **`sf_add_picklist_values`** — Switched from Tooling API to `readMetadata` for reliable field lookup
|
|
58
|
+
- **`sf_create_lightning_record_page`** — Fixed element name (`type` not `pageType`), added required `template` and `flexiPageRegions`
|
|
59
|
+
- **`sf_create_letterhead`** — Full WSDL-compliant rewrite: correct element order, `backgroundColor` in header/footer, added `middleLine`
|
|
60
|
+
- **`sf_update_dashboard`** — Creates dashboard when not found, with all required fields including `leftSection`/`rightSection`
|
|
61
|
+
- **`sf_create_quick_action`** — Correct WSDL element order, added `quickActionLayout` support
|
|
62
|
+
|
|
49
63
|
## What's New in v2.5.0
|
|
50
64
|
|
|
51
65
|
45 new tools across 11 categories:
|
package/TOOLS.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -6,14 +6,14 @@ import { createServer } from "http";
|
|
|
6
6
|
import { registerTools } from "./tools/index.js";
|
|
7
7
|
const server = new McpServer({
|
|
8
8
|
name: "salesforce-metadata-mcp",
|
|
9
|
-
version: "2.5.
|
|
9
|
+
version: "2.5.2",
|
|
10
10
|
});
|
|
11
11
|
registerTools(server);
|
|
12
12
|
// ─── Transport: stdio ─────────────────────────────────────────────────────────
|
|
13
13
|
async function runStdio() {
|
|
14
14
|
const transport = new StdioServerTransport();
|
|
15
15
|
await server.connect(transport);
|
|
16
|
-
console.error("Salesforce Metadata MCP server v2.5.
|
|
16
|
+
console.error("Salesforce Metadata MCP server v2.5.2 running on stdio");
|
|
17
17
|
}
|
|
18
18
|
// ─── Transport: HTTP ──────────────────────────────────────────────────────────
|
|
19
19
|
function readBody(req) {
|
|
@@ -38,7 +38,7 @@ async function runHTTP() {
|
|
|
38
38
|
const url = req.url ?? "/";
|
|
39
39
|
if (url === "/health" && req.method === "GET") {
|
|
40
40
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
41
|
-
res.end(JSON.stringify({ status: "ok", server: "salesforce-metadata-mcp", version: "2.5.
|
|
41
|
+
res.end(JSON.stringify({ status: "ok", server: "salesforce-metadata-mcp", version: "2.5.2"}));
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
44
|
if (url === "/mcp" && req.method === "POST") {
|
|
@@ -64,7 +64,7 @@ async function runHTTP() {
|
|
|
64
64
|
res.end(JSON.stringify({ error: "Not found" }));
|
|
65
65
|
});
|
|
66
66
|
httpServer.listen(port, () => {
|
|
67
|
-
console.error(`Salesforce Metadata MCP server v2.5.
|
|
67
|
+
console.error(`Salesforce Metadata MCP server v2.5.2 running on http://localhost:${port}/mcp`);
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
70
|
// ─── Entry point ──────────────────────────────────────────────────────────────
|
|
@@ -299,7 +299,8 @@ export function parseUpsertResult(xml) {
|
|
|
299
299
|
return results;
|
|
300
300
|
}
|
|
301
301
|
export function x(str) {
|
|
302
|
-
|
|
302
|
+
if (str == null) return "";
|
|
303
|
+
return String(str).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
303
304
|
}
|
|
304
305
|
// ─── XML Builders ────────────────────────────────────────────────────────────
|
|
305
306
|
function buildCustomObjectXml(meta) {
|
|
@@ -331,18 +332,25 @@ function buildPicklistValuesXml(values) {
|
|
|
331
332
|
</met:value>`).join("\n");
|
|
332
333
|
}
|
|
333
334
|
function buildCustomFieldXml(meta) {
|
|
334
|
-
|
|
335
|
+
// Normalize picklistValues format: { values: [...], restricted, sorted } → valueSet
|
|
336
|
+
let valueSet = meta.valueSet;
|
|
337
|
+
if (!valueSet && meta.picklistValues) {
|
|
338
|
+
const pv = meta.picklistValues;
|
|
339
|
+
const vals = Array.isArray(pv) ? pv : (pv.values ?? []);
|
|
340
|
+
valueSet = { restricted: pv.restricted ?? false, valueSetDefinition: { sorted: pv.sorted ?? false, value: vals.map(v => typeof v === "string" ? { fullName: v, label: v, default: false } : { fullName: v.fullName ?? v.label, label: v.label ?? v.fullName, default: v.default ?? false }) } };
|
|
341
|
+
}
|
|
342
|
+
const picklistXml = valueSet
|
|
335
343
|
? `<met:valueSet>
|
|
336
|
-
${
|
|
344
|
+
${valueSet.restricted !== undefined ? `<met:restricted>${valueSet.restricted}</met:restricted>` : ""}
|
|
337
345
|
<met:valueSetDefinition>
|
|
338
|
-
<met:sorted>${
|
|
339
|
-
${buildPicklistValuesXml(
|
|
346
|
+
<met:sorted>${valueSet.valueSetDefinition.sorted}</met:sorted>
|
|
347
|
+
${buildPicklistValuesXml(valueSet.valueSetDefinition.value)}
|
|
340
348
|
</met:valueSetDefinition>
|
|
341
349
|
</met:valueSet>` : "";
|
|
342
350
|
const lookupXml = meta.referenceTo
|
|
343
351
|
? `<met:referenceTo>${x(meta.referenceTo)}</met:referenceTo>
|
|
344
352
|
<met:relationshipLabel>${x(meta.relationshipLabel ?? meta.referenceTo)}</met:relationshipLabel>
|
|
345
|
-
<met:relationshipName>${x(meta.relationshipName ?? meta.referenceTo.replace(/__c$/i, ""))}</met:relationshipName>
|
|
353
|
+
<met:relationshipName>${x(meta.relationshipName ?? String(meta.referenceTo).replace(/__c$/i, ""))}</met:relationshipName>
|
|
346
354
|
${meta.deleteConstraint ? `<met:deleteConstraint>${meta.deleteConstraint}</met:deleteConstraint>` : ""}` : "";
|
|
347
355
|
return `<met:metadata xsi:type="met:CustomField" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
348
356
|
<met:fullName>${x(meta.fullName)}</met:fullName>
|
|
@@ -356,6 +364,7 @@ function buildCustomFieldXml(meta) {
|
|
|
356
364
|
${meta.visibleLines !== undefined ? `<met:visibleLines>${meta.visibleLines}</met:visibleLines>` : ""}
|
|
357
365
|
${meta.precision !== undefined ? `<met:precision>${meta.precision}</met:precision>` : ""}
|
|
358
366
|
${meta.scale !== undefined ? `<met:scale>${meta.scale}</met:scale>` : ""}
|
|
367
|
+
${meta.defaultValue !== undefined ? `<met:defaultValue>${meta.defaultValue}</met:defaultValue>` : ""}
|
|
359
368
|
${picklistXml}
|
|
360
369
|
${lookupXml}
|
|
361
370
|
</met:metadata>`;
|
|
@@ -601,7 +610,7 @@ function buildFlowXml(params) {
|
|
|
601
610
|
<met:label>${x(params.label)}</met:label>
|
|
602
611
|
<met:apiVersion>${API_VERSION}</met:apiVersion>
|
|
603
612
|
<met:status>${x(params.status)}</met:status>
|
|
604
|
-
<met:processType>${x(params.flowType)}</met:processType>
|
|
613
|
+
<met:processType>${params.flowType === "Screen" ? "Flow" : params.flowType === "RecordTriggeredFlow" ? "AutoLaunchedFlow" : x(params.flowType)}</met:processType>
|
|
605
614
|
<met:environments>Default</met:environments>
|
|
606
615
|
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
607
616
|
${vars}
|
|
@@ -872,11 +881,10 @@ function buildRoleXml(params) {
|
|
|
872
881
|
<met:name>${x(params.name)}</met:name>
|
|
873
882
|
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
874
883
|
${params.parentRole ? `<met:parentRole>${x(params.parentRole)}</met:parentRole>` : ""}
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
<met:mayForecastManagerShare>${params.mayForecastManagerShare}</met:mayForecastManagerShare>
|
|
884
|
+
${params.caseAccessLevel ? `<met:caseAccessLevel>${x(params.caseAccessLevel)}</met:caseAccessLevel>` : ""}
|
|
885
|
+
${params.contactAccessLevel ? `<met:contactAccessLevel>${x(params.contactAccessLevel)}</met:contactAccessLevel>` : ""}
|
|
886
|
+
${params.opportunityAccessLevel ? `<met:opportunityAccessLevel>${x(params.opportunityAccessLevel)}</met:opportunityAccessLevel>` : ""}
|
|
887
|
+
${params.mayForecastManagerShare !== undefined ? `<met:mayForecastManagerShare>${params.mayForecastManagerShare}</met:mayForecastManagerShare>` : ""}
|
|
880
888
|
</met:metadata>`;
|
|
881
889
|
}
|
|
882
890
|
function buildQueueXml(params) {
|
|
@@ -972,7 +980,7 @@ function buildListViewXml(params) {
|
|
|
972
980
|
const sharedToXml = params.sharedTo ? `
|
|
973
981
|
<met:sharedTo>
|
|
974
982
|
<met:${params.sharedTo.type}>${params.sharedTo.name ? x(params.sharedTo.name) : ""}</met:${params.sharedTo.type}>
|
|
975
|
-
</met:sharedTo>` : `<met:sharedTo><met:
|
|
983
|
+
</met:sharedTo>` : `<met:sharedTo><met:allInternalUsers/></met:sharedTo>`;
|
|
976
984
|
return `<met:metadata xsi:type="met:CustomObject" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
977
985
|
<met:fullName>${x(params.objectName)}</met:fullName>
|
|
978
986
|
<met:listViews>
|
|
@@ -989,15 +997,13 @@ function buildListViewXml(params) {
|
|
|
989
997
|
function buildEmailTemplateXml(params) {
|
|
990
998
|
return `<met:metadata xsi:type="met:EmailTemplate" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
991
999
|
<met:fullName>${x(params.fullName)}</met:fullName>
|
|
992
|
-
<met:name>${x(params.name)}</met:name>
|
|
993
|
-
<met:available>${params.available}</met:available>
|
|
994
|
-
<met:
|
|
995
|
-
<met:label>${x(params.label)}</met:label>
|
|
1000
|
+
<met:name>${x(params.name ?? params.label)}</met:name>
|
|
1001
|
+
<met:available>${params.available ?? true}</met:available>
|
|
1002
|
+
<met:encodingKey>${x(params.encoding ?? params.encodingKey ?? "UTF-8")}</met:encodingKey>
|
|
996
1003
|
<met:style>none</met:style>
|
|
997
1004
|
<met:subject>${x(params.subject)}</met:subject>
|
|
998
|
-
<met:textOnly>${x(params.body)}</met:textOnly>
|
|
999
|
-
|
|
1000
|
-
<met:type>${x(params.type)}</met:type>
|
|
1005
|
+
<met:textOnly>${x(params.body ?? params.textBody ?? params.htmlValue ?? "")}</met:textOnly>
|
|
1006
|
+
<met:type>text</met:type>
|
|
1001
1007
|
${params.relatedEntityType ? `<met:relatedEntityType>${x(params.relatedEntityType)}</met:relatedEntityType>` : ""}
|
|
1002
1008
|
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
1003
1009
|
${params.replyTo ? `<met:replyTo>${x(params.replyTo)}</met:replyTo>` : ""}
|
|
@@ -1005,12 +1011,14 @@ function buildEmailTemplateXml(params) {
|
|
|
1005
1011
|
</met:metadata>`;
|
|
1006
1012
|
}
|
|
1007
1013
|
function buildCustomNotificationTypeXml(params) {
|
|
1014
|
+
const label = params.masterLabel ?? params.customNotifTypeName ?? params.label ?? params.fullName;
|
|
1008
1015
|
return `<met:metadata xsi:type="met:CustomNotificationType" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
1009
1016
|
<met:fullName>${x(params.fullName)}</met:fullName>
|
|
1010
|
-
<met:
|
|
1017
|
+
<met:masterLabel>${x(label)}</met:masterLabel>
|
|
1018
|
+
<met:customNotifTypeName>${x(params.customNotifTypeName ?? params.fullName)}</met:customNotifTypeName>
|
|
1011
1019
|
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
1012
|
-
<met:desktop>${params.desktop}</met:desktop>
|
|
1013
|
-
<met:mobile>${params.mobile}</met:mobile>
|
|
1020
|
+
<met:desktop>${params.desktop ?? false}</met:desktop>
|
|
1021
|
+
<met:mobile>${params.mobile ?? false}</met:mobile>
|
|
1014
1022
|
</met:metadata>`;
|
|
1015
1023
|
}
|
|
1016
1024
|
function buildReportTypeXml(params) {
|
|
@@ -1160,7 +1168,7 @@ function buildPageLayoutXml(params) {
|
|
|
1160
1168
|
const columns = s.style === "OneColumn" ? 1 : 2;
|
|
1161
1169
|
const fieldItems = s.fields.map(f => `
|
|
1162
1170
|
<met:layoutItems>
|
|
1163
|
-
<met:behavior
|
|
1171
|
+
<met:behavior>${f === "Name" ? "Required" : "Edit"}</met:behavior>
|
|
1164
1172
|
<met:field>${x(f)}</met:field>
|
|
1165
1173
|
</met:layoutItems>`).join("\n");
|
|
1166
1174
|
return `<met:layoutSections>
|
|
@@ -1389,20 +1397,45 @@ export async function addPicklistValues(auth, objectFieldFullName, newValues) {
|
|
|
1389
1397
|
if (!objectName || !fieldName)
|
|
1390
1398
|
return { success: false, message: "fullName must be in format 'ObjectName__c.FieldName__c'" };
|
|
1391
1399
|
try {
|
|
1392
|
-
|
|
1393
|
-
const
|
|
1394
|
-
const
|
|
1395
|
-
if (!
|
|
1396
|
-
return { success: false, message: `
|
|
1397
|
-
|
|
1398
|
-
const
|
|
1399
|
-
const
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1400
|
+
// Read the full CustomObject to find the field and its current picklist values
|
|
1401
|
+
const readXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>CustomObject</met:type><met:fullNames>${x(objectName)}</met:fullNames></met:readMetadata>`);
|
|
1402
|
+
const recordMatch = readXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
1403
|
+
if (!recordMatch)
|
|
1404
|
+
return { success: false, message: `Object '${objectName}' not found.` };
|
|
1405
|
+
// Find the target field block within the object XML
|
|
1406
|
+
const fieldBlockRegex = new RegExp(`<fields[^>]*>[\\s\\S]*?<fullName>${fieldName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}<\\/fullName>[\\s\\S]*?<\\/fields>`, "i");
|
|
1407
|
+
const fieldBlock = recordMatch[1].match(fieldBlockRegex);
|
|
1408
|
+
if (!fieldBlock)
|
|
1409
|
+
return { success: false, message: `Field '${objectFieldFullName}' not found in object.` };
|
|
1410
|
+
// Parse existing picklist values
|
|
1411
|
+
const existingValues = [];
|
|
1412
|
+
const valueBlockRegex = /<value[^>]*>([\s\S]*?)<\/value>/gi;
|
|
1413
|
+
let valueBlockMatch;
|
|
1414
|
+
while ((valueBlockMatch = valueBlockRegex.exec(fieldBlock[0])) !== null) {
|
|
1415
|
+
const block = valueBlockMatch[1];
|
|
1416
|
+
const fnMatch = block.match(/<fullName>([^<]*)<\/fullName>/i);
|
|
1417
|
+
const lbMatch = block.match(/<label>([^<]*)<\/label>/i);
|
|
1418
|
+
const dfMatch = block.match(/<default>([^<]*)<\/default>/i);
|
|
1419
|
+
if (fnMatch)
|
|
1420
|
+
existingValues.push({ fullName: fnMatch[1], label: lbMatch?.[1] ?? fnMatch[1], default: dfMatch?.[1] === "true" });
|
|
1421
|
+
}
|
|
1422
|
+
const existingNames = new Set(existingValues.map((v) => v.fullName));
|
|
1423
|
+
const merged = [...existingValues, ...newValues.filter((v) => !existingNames.has(v.fullName))];
|
|
1424
|
+
// Build updated field XML with new values
|
|
1425
|
+
const newValueXml = merged.map(v => `<value><fullName>${x(v.fullName)}</fullName><default>${v.default ?? false}</default><label>${x(v.label ?? v.fullName)}</label></value>`).join("");
|
|
1426
|
+
let updatedField = fieldBlock[0];
|
|
1427
|
+
if (updatedField.match(/<valueSetDefinition>/i)) {
|
|
1428
|
+
updatedField = updatedField.replace(/<valueSetDefinition>([\s\S]*?)<\/valueSetDefinition>/i, `<valueSetDefinition><sorted>false</sorted>${newValueXml}</valueSetDefinition>`);
|
|
1429
|
+
}
|
|
1430
|
+
else {
|
|
1431
|
+
updatedField = updatedField.replace(/<\/fields>/i, `<valueSet><restricted>false</restricted><valueSetDefinition><sorted>false</sorted>${newValueXml}</valueSetDefinition></valueSet></fields>`);
|
|
1432
|
+
}
|
|
1433
|
+
// Replace the field in the object XML and upsert the whole object
|
|
1434
|
+
const updatedObjectXml = recordMatch[1].replace(fieldBlock[0], updatedField);
|
|
1435
|
+
const xml = `<met:metadata xsi:type="met:CustomObject" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
1436
|
+
${updatedObjectXml}
|
|
1437
|
+
</met:metadata>`;
|
|
1438
|
+
return await upsertMetadata(auth, xml);
|
|
1406
1439
|
}
|
|
1407
1440
|
catch (err) {
|
|
1408
1441
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -1416,7 +1449,29 @@ export async function createApprovalProcess(auth, params) {
|
|
|
1416
1449
|
return upsertMetadata(auth, buildApprovalProcessXml(params));
|
|
1417
1450
|
}
|
|
1418
1451
|
export async function createValidationRule(auth, params) {
|
|
1419
|
-
|
|
1452
|
+
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
|
+
return await upsertMetadata(auth, buildValidationRuleXml(params));
|
|
1472
|
+
} catch (err) {
|
|
1473
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
1474
|
+
}
|
|
1420
1475
|
}
|
|
1421
1476
|
export async function createWorkflowFieldUpdate(auth, params) {
|
|
1422
1477
|
return upsertMetadata(auth, buildWorkflowFieldUpdateXml(params));
|
|
@@ -1473,10 +1528,55 @@ export async function createTab(auth, params) {
|
|
|
1473
1528
|
return upsertMetadata(auth, buildTabXml(params));
|
|
1474
1529
|
}
|
|
1475
1530
|
export async function createCompactLayout(auth, params) {
|
|
1476
|
-
|
|
1531
|
+
try {
|
|
1532
|
+
const objectName = params.objectName ?? params.objectApiName;
|
|
1533
|
+
const rawName = params.fullName ?? params.name ?? "";
|
|
1534
|
+
const layoutName = rawName.includes(".") ? rawName.split(".").slice(1).join(".") : rawName;
|
|
1535
|
+
const readXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>CustomObject</met:type><met:fullNames>${x(objectName)}</met:fullNames></met:readMetadata>`);
|
|
1536
|
+
const recordMatch = readXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
1537
|
+
if (!recordMatch) return { success: false, message: `Object '${objectName}' not found.` };
|
|
1538
|
+
const fieldsXml = (params.fields ?? []).map(f => `<met:fields>${x(f)}</met:fields>`).join("\n ");
|
|
1539
|
+
const compactXml = `<met:compactLayouts>
|
|
1540
|
+
<met:fullName>${x(layoutName)}</met:fullName>
|
|
1541
|
+
<met:label>${x(params.label)}</met:label>
|
|
1542
|
+
${fieldsXml}
|
|
1543
|
+
</met:compactLayouts>`;
|
|
1544
|
+
const xml = `<met:metadata xsi:type="met:CustomObject" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
1545
|
+
${recordMatch[1]}
|
|
1546
|
+
${compactXml}
|
|
1547
|
+
</met:metadata>`;
|
|
1548
|
+
return await upsertMetadata(auth, xml);
|
|
1549
|
+
} catch (err) {
|
|
1550
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
1551
|
+
}
|
|
1477
1552
|
}
|
|
1478
1553
|
export async function createListView(auth, params) {
|
|
1479
|
-
|
|
1554
|
+
try {
|
|
1555
|
+
const objectName = params.objectName ?? params.objectApiName;
|
|
1556
|
+
const rawName = params.fullName ?? params.name ?? "";
|
|
1557
|
+
const viewName = rawName.includes(".") ? rawName.split(".").slice(1).join(".") : rawName;
|
|
1558
|
+
const readXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>CustomObject</met:type><met:fullNames>${x(objectName)}</met:fullNames></met:readMetadata>`);
|
|
1559
|
+
const recordMatch = readXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
1560
|
+
if (!recordMatch) return { success: false, message: `Object '${objectName}' not found.` };
|
|
1561
|
+
const colsXml = (params.columns ?? []).map(c => `<met:columns>${x(c)}</met:columns>`).join("\n ");
|
|
1562
|
+
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
|
+
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
|
+
const listViewXml = `<met:listViews>
|
|
1565
|
+
<met:fullName>${x(viewName)}</met:fullName>
|
|
1566
|
+
<met:label>${x(params.label)}</met:label>
|
|
1567
|
+
<met:filterScope>${x(params.filterScope ?? "Everything")}</met:filterScope>
|
|
1568
|
+
${colsXml}
|
|
1569
|
+
${filtersXml}
|
|
1570
|
+
${sharedToXml}
|
|
1571
|
+
</met:listViews>`;
|
|
1572
|
+
const xml = `<met:metadata xsi:type="met:CustomObject" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
1573
|
+
${recordMatch[1]}
|
|
1574
|
+
${listViewXml}
|
|
1575
|
+
</met:metadata>`;
|
|
1576
|
+
return await upsertMetadata(auth, xml);
|
|
1577
|
+
} catch (err) {
|
|
1578
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
1579
|
+
}
|
|
1480
1580
|
}
|
|
1481
1581
|
export async function createEmailTemplate(auth, params) {
|
|
1482
1582
|
return upsertMetadata(auth, buildEmailTemplateXml(params));
|
|
@@ -1881,17 +1981,23 @@ export async function getAgentLogs(auth, params) {
|
|
|
1881
1981
|
// Objects & Fields
|
|
1882
1982
|
export async function updateCustomObject(auth, params) {
|
|
1883
1983
|
try {
|
|
1884
|
-
const
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
if (
|
|
1888
|
-
|
|
1889
|
-
if (params.
|
|
1890
|
-
if (params.
|
|
1891
|
-
if (params.
|
|
1984
|
+
const objectName = params.objectApiName ?? params.fullName;
|
|
1985
|
+
const readXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>CustomObject</met:type><met:fullNames>${x(objectName)}</met:fullNames></met:readMetadata>`);
|
|
1986
|
+
const recordMatch = readXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
1987
|
+
if (!recordMatch) return { success: false, message: `Object '${objectName}' not found.` };
|
|
1988
|
+
let inner = recordMatch[1];
|
|
1989
|
+
if (params.label) inner = inner.replace(/<label>[^<]*<\/label>/i, `<label>${x(params.label)}</label>`);
|
|
1990
|
+
if (params.pluralLabel) inner = inner.replace(/<pluralLabel>[^<]*<\/pluralLabel>/i, `<pluralLabel>${x(params.pluralLabel)}</pluralLabel>`);
|
|
1991
|
+
if (params.description !== undefined) {
|
|
1992
|
+
if (inner.includes("<description>")) inner = inner.replace(/<description>[^<]*<\/description>/i, `<description>${x(params.description)}</description>`);
|
|
1993
|
+
else inner += `\n <met:description>${x(params.description)}</met:description>`;
|
|
1994
|
+
}
|
|
1995
|
+
if (params.enableHistory !== undefined) inner = inner.includes("<enableHistory>") ? inner.replace(/<enableHistory>[^<]*<\/enableHistory>/i, `<enableHistory>${params.enableHistory}</enableHistory>`) : inner + `\n <met:enableHistory>${params.enableHistory}</met:enableHistory>`;
|
|
1996
|
+
if (params.enableReports !== undefined) inner = inner.includes("<enableReports>") ? inner.replace(/<enableReports>[^<]*<\/enableReports>/i, `<enableReports>${params.enableReports}</enableReports>`) : inner + `\n <met:enableReports>${params.enableReports}</met:enableReports>`;
|
|
1997
|
+
if (params.enableActivities !== undefined) inner = inner.includes("<enableActivities>") ? inner.replace(/<enableActivities>[^<]*<\/enableActivities>/i, `<enableActivities>${params.enableActivities}</enableActivities>`) : inner + `\n <met:enableActivities>${params.enableActivities}</met:enableActivities>`;
|
|
1998
|
+
if (params.enableSearch !== undefined) inner = inner.includes("<enableSearch>") ? inner.replace(/<enableSearch>[^<]*<\/enableSearch>/i, `<enableSearch>${params.enableSearch}</enableSearch>`) : inner + `\n <met:enableSearch>${params.enableSearch}</met:enableSearch>`;
|
|
1892
1999
|
const xml = `<met:metadata xsi:type="met:CustomObject" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
1893
|
-
|
|
1894
|
-
${fields.join("\n ")}
|
|
2000
|
+
${inner}
|
|
1895
2001
|
</met:metadata>`;
|
|
1896
2002
|
return await upsertMetadata(auth, xml);
|
|
1897
2003
|
} catch (err) {
|
|
@@ -1939,8 +2045,11 @@ export async function createFormulaField(auth, params) {
|
|
|
1939
2045
|
try {
|
|
1940
2046
|
const typeMap = { Text: "Text", Number: "Number", Currency: "Currency", Date: "Date", DateTime: "DateTime", Checkbox: "Checkbox", Percent: "Percent" };
|
|
1941
2047
|
const sfType = typeMap[params.returnType] ?? "Text";
|
|
2048
|
+
const objectName = params.objectApiName ?? params.objectName;
|
|
2049
|
+
const rawField = params.fieldApiName ?? params.fieldName ?? "";
|
|
2050
|
+
const fieldName = rawField.replace(/__c$/i, "");
|
|
1942
2051
|
const xml = `<met:metadata xsi:type="met:CustomField" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
1943
|
-
<met:fullName>${x(
|
|
2052
|
+
<met:fullName>${x(objectName)}.${x(fieldName)}__c</met:fullName>
|
|
1944
2053
|
<met:label>${x(params.label)}</met:label>
|
|
1945
2054
|
<met:type>${sfType}</met:type>
|
|
1946
2055
|
<met:formula>${x(params.formula)}</met:formula>
|
|
@@ -1976,9 +2085,9 @@ export async function assignPermissionSet(auth, params) {
|
|
|
1976
2085
|
}
|
|
1977
2086
|
export async function createPermissionSetGroup(auth, params) {
|
|
1978
2087
|
try {
|
|
1979
|
-
const psXml = params.permissionSets.map(ps => `<met:permissionSets>${x(ps)}</met:permissionSets>`).join("\n ");
|
|
2088
|
+
const psXml = (params.permissionSets ?? []).map(ps => `<met:permissionSets>${x(ps)}</met:permissionSets>`).join("\n ");
|
|
1980
2089
|
const xml = `<met:metadata xsi:type="met:PermissionSetGroup" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
1981
|
-
<met:fullName>${x(params.groupName)}</met:fullName>
|
|
2090
|
+
<met:fullName>${x(params.fullName ?? params.groupName)}</met:fullName>
|
|
1982
2091
|
<met:label>${x(params.label)}</met:label>
|
|
1983
2092
|
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
1984
2093
|
${psXml}
|
|
@@ -2047,25 +2156,32 @@ export async function createMutingPermissionSet(auth, params) {
|
|
|
2047
2156
|
export async function setFieldLevelSecurity(auth, params) {
|
|
2048
2157
|
try {
|
|
2049
2158
|
const results = [];
|
|
2050
|
-
const
|
|
2159
|
+
for (const profileName of (params.profiles ?? [])) {
|
|
2160
|
+
const xml = `<met:metadata xsi:type="met:Profile" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2051
2161
|
<met:fullName>${x(profileName)}</met:fullName>
|
|
2052
2162
|
<met:fieldPermissions>
|
|
2053
2163
|
<met:field>${x(params.fieldApiName)}</met:field>
|
|
2054
2164
|
<met:readable>${params.readable ? "true" : "false"}</met:readable>
|
|
2055
2165
|
<met:editable>${params.editable ? "true" : "false"}</met:editable>
|
|
2056
2166
|
</met:fieldPermissions>
|
|
2057
|
-
</met:metadata
|
|
2058
|
-
|
|
2167
|
+
</met:metadata>`;
|
|
2168
|
+
results.push(await upsertMetadata(auth, xml));
|
|
2169
|
+
}
|
|
2170
|
+
const psXmls = (params.permissionSets ?? []).map(psName => {
|
|
2171
|
+
// Read label from existing PS if possible, otherwise use fullName as label
|
|
2172
|
+
const psLabel = x(psName);
|
|
2173
|
+
return `<met:metadata xsi:type="met:PermissionSet" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2059
2174
|
<met:fullName>${x(psName)}</met:fullName>
|
|
2175
|
+
<met:label>${psLabel}</met:label>
|
|
2060
2176
|
<met:fieldPermissions>
|
|
2061
2177
|
<met:field>${x(params.fieldApiName)}</met:field>
|
|
2062
2178
|
<met:readable>${params.readable ? "true" : "false"}</met:readable>
|
|
2063
2179
|
<met:editable>${params.editable ? "true" : "false"}</met:editable>
|
|
2064
2180
|
</met:fieldPermissions>
|
|
2065
|
-
</met:metadata
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
results.push(
|
|
2181
|
+
</met:metadata>`;
|
|
2182
|
+
});
|
|
2183
|
+
for (const xml of psXmls) {
|
|
2184
|
+
results.push(await upsertMetadata(auth, xml));
|
|
2069
2185
|
}
|
|
2070
2186
|
const allOk = results.every(r => r.success);
|
|
2071
2187
|
return { success: allOk, message: allOk ? `Field security set for ${params.fieldApiName} on ${results.length} profile(s)/permission set(s).` : `Some updates failed: ${results.filter(r => !r.success).map(r => r.message).join("; ")}` };
|
|
@@ -2087,14 +2203,17 @@ export async function setOrgWideDefaults(auth, params) {
|
|
|
2087
2203
|
}
|
|
2088
2204
|
export async function cloneProfile(auth, params) {
|
|
2089
2205
|
try {
|
|
2090
|
-
const
|
|
2091
|
-
const
|
|
2092
|
-
|
|
2206
|
+
const sourceProfile = params.cloneFrom ?? params.sourceProfileName;
|
|
2207
|
+
const newProfileName = params.profileName ?? params.newProfileName;
|
|
2208
|
+
const readXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>Profile</met:type><met:fullNames>${x(sourceProfile)}</met:fullNames></met:readMetadata>`);
|
|
2209
|
+
const recordMatch = readXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
2210
|
+
if (!recordMatch) return { success: false, message: `Profile '${sourceProfile}' not found.` };
|
|
2093
2211
|
let profileXml = recordMatch[1]
|
|
2094
|
-
.replace(/<fullName>[^<]*<\/fullName>/i, `<fullName>${x(
|
|
2095
|
-
.replace(/<userLicense>[^<]*<\/userLicense>/i, "")
|
|
2212
|
+
.replace(/<fullName>[^<]*<\/fullName>/i, `<fullName>${x(newProfileName)}</fullName>`)
|
|
2213
|
+
.replace(/<userLicense>[^<]*<\/userLicense>/i, "")
|
|
2214
|
+
.replace(/<tabVisibilities>[\s\S]*?<\/tabVisibilities>/gi, "");
|
|
2096
2215
|
const xml = `<met:metadata xsi:type="met:Profile" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2097
|
-
<met:fullName>${x(
|
|
2216
|
+
<met:fullName>${x(newProfileName)}</met:fullName>
|
|
2098
2217
|
${profileXml}
|
|
2099
2218
|
</met:metadata>`;
|
|
2100
2219
|
return await upsertMetadata(auth, xml);
|
|
@@ -2147,7 +2266,7 @@ export async function updateProfile(auth, params) {
|
|
|
2147
2266
|
export async function updatePageLayout(auth, params) {
|
|
2148
2267
|
try {
|
|
2149
2268
|
const layoutXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>Layout</met:type><met:fullNames>${x(params.objectApiName)}-${x(params.layoutName)}</met:fullNames></met:readMetadata>`);
|
|
2150
|
-
const recordMatch = layoutXml.match(/<records
|
|
2269
|
+
const recordMatch = layoutXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
2151
2270
|
if (!recordMatch) return { success: false, message: `Layout '${params.layoutName}' not found.` };
|
|
2152
2271
|
let inner = recordMatch[1];
|
|
2153
2272
|
if (params.label) inner = inner.replace(/<fullName>[^<]*<\/fullName>/i, `<fullName>${x(params.objectApiName)}-${x(params.label)}</fullName>`);
|
|
@@ -2177,6 +2296,9 @@ export async function assignPageLayout(auth, params) {
|
|
|
2177
2296
|
}
|
|
2178
2297
|
export async function createFlexiPage(auth, params) {
|
|
2179
2298
|
try {
|
|
2299
|
+
const fullName = params.pageName ?? params.apiName ?? params.fullName;
|
|
2300
|
+
const pageType = params.pageType ?? params.type ?? "UtilityBar";
|
|
2301
|
+
const template = params.template ?? "one:utilityBarTemplateDesktop";
|
|
2180
2302
|
const regionXml = (params.regions ?? []).map(r => `
|
|
2181
2303
|
<met:flexiPageRegions>
|
|
2182
2304
|
<met:name>${x(r.name)}</met:name>
|
|
@@ -2190,14 +2312,20 @@ export async function createFlexiPage(auth, params) {
|
|
|
2190
2312
|
<met:value>${x(p.value)}</met:value>
|
|
2191
2313
|
</met:componentInstanceProperties>`).join("")}
|
|
2192
2314
|
</met:itemInstances>`).join("")}
|
|
2193
|
-
</met:flexiPageRegions>`).join("")
|
|
2315
|
+
</met:flexiPageRegions>`).join("") || `
|
|
2316
|
+
<met:flexiPageRegions>
|
|
2317
|
+
<met:name>main</met:name>
|
|
2318
|
+
<met:type>Region</met:type>
|
|
2319
|
+
</met:flexiPageRegions>`;
|
|
2320
|
+
const sobjectType = params.objectApiName ?? params.sobjectType;
|
|
2194
2321
|
const xml = `<met:metadata xsi:type="met:FlexiPage" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2195
|
-
<met:fullName>${x(
|
|
2196
|
-
<met:masterLabel>${x(params.label)}</met:masterLabel>
|
|
2197
|
-
<met:pageType>${x(params.pageType)}</met:pageType>
|
|
2198
|
-
${params.objectApiName ? `<met:sobjectType>${x(params.objectApiName)}</met:sobjectType>` : ""}
|
|
2322
|
+
<met:fullName>${x(fullName)}</met:fullName>
|
|
2199
2323
|
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
2200
2324
|
${regionXml}
|
|
2325
|
+
<met:masterLabel>${x(params.label)}</met:masterLabel>
|
|
2326
|
+
${sobjectType ? `<met:sobjectType>${x(sobjectType)}</met:sobjectType>` : ""}
|
|
2327
|
+
<met:template><met:name>${x(template)}</met:name></met:template>
|
|
2328
|
+
<met:type>${x(pageType)}</met:type>
|
|
2201
2329
|
</met:metadata>`;
|
|
2202
2330
|
return await upsertMetadata(auth, xml);
|
|
2203
2331
|
} catch (err) {
|
|
@@ -2207,7 +2335,7 @@ export async function createFlexiPage(auth, params) {
|
|
|
2207
2335
|
export async function updateFlexiPage(auth, params) {
|
|
2208
2336
|
try {
|
|
2209
2337
|
const readXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>FlexiPage</met:type><met:fullNames>${x(params.pageName)}</met:fullNames></met:readMetadata>`);
|
|
2210
|
-
const recordMatch = readXml.match(/<records
|
|
2338
|
+
const recordMatch = readXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
2211
2339
|
if (!recordMatch) return { success: false, message: `FlexiPage '${params.pageName}' not found.` };
|
|
2212
2340
|
let inner = recordMatch[1];
|
|
2213
2341
|
if (params.label) inner = inner.replace(/<masterLabel>[^<]*<\/masterLabel>/i, `<masterLabel>${x(params.label)}</masterLabel>`);
|
|
@@ -2267,14 +2395,33 @@ export async function activateFlow(auth, params) {
|
|
|
2267
2395
|
}
|
|
2268
2396
|
export async function createQuickAction(auth, params) {
|
|
2269
2397
|
try {
|
|
2398
|
+
const actionType = params.type ?? params.actionType;
|
|
2399
|
+
const actionName = params.actionName ?? params.fullName ?? "";
|
|
2400
|
+
const fullName = actionName.includes(".") ? actionName : (params.objectApiName ? `${params.objectApiName}.${actionName}` : actionName);
|
|
2401
|
+
const requiredQAFields = new Set(["Subject", "Name", "LastName"]);
|
|
2402
|
+
const fieldsXml = (params.fields ?? []).map(f => `
|
|
2403
|
+
<met:quickActionLayoutItems>
|
|
2404
|
+
<met:emptySpace>false</met:emptySpace>
|
|
2405
|
+
<met:field>${x(f)}</met:field>
|
|
2406
|
+
<met:uiBehavior>${requiredQAFields.has(f) ? "Required" : "Edit"}</met:uiBehavior>
|
|
2407
|
+
</met:quickActionLayoutItems>`).join("");
|
|
2408
|
+
const layoutXml = fieldsXml ? `
|
|
2409
|
+
<met:quickActionLayout>
|
|
2410
|
+
<met:layoutSectionStyle>TwoColumnsTopToBottom</met:layoutSectionStyle>
|
|
2411
|
+
<met:quickActionLayoutColumns>
|
|
2412
|
+
${fieldsXml}
|
|
2413
|
+
</met:quickActionLayoutColumns>
|
|
2414
|
+
<met:quickActionLayoutColumns></met:quickActionLayoutColumns>
|
|
2415
|
+
</met:quickActionLayout>` : "";
|
|
2270
2416
|
const xml = `<met:metadata xsi:type="met:QuickAction" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2271
|
-
<met:fullName>${x(
|
|
2272
|
-
<met:label>${x(params.label)}</met:label>
|
|
2273
|
-
<met:type>${x(params.type)}</met:type>
|
|
2274
|
-
${params.targetObject ? `<met:targetObject>${x(params.targetObject)}</met:targetObject>` : ""}
|
|
2417
|
+
<met:fullName>${x(fullName)}</met:fullName>
|
|
2275
2418
|
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
2276
2419
|
${params.flowDefinition ? `<met:flowDefinition>${x(params.flowDefinition)}</met:flowDefinition>` : ""}
|
|
2277
|
-
|
|
2420
|
+
<met:label>${x(params.label)}</met:label>
|
|
2421
|
+
<met:optionsCreateFeedItem>${params.optionsCreateFeedItem ?? false}</met:optionsCreateFeedItem>
|
|
2422
|
+
${layoutXml}
|
|
2423
|
+
${params.targetObject ? `<met:targetObject>${x(params.targetObject)}</met:targetObject>` : ""}
|
|
2424
|
+
<met:type>${x(actionType)}</met:type>
|
|
2278
2425
|
</met:metadata>`;
|
|
2279
2426
|
return await upsertMetadata(auth, xml);
|
|
2280
2427
|
} catch (err) {
|
|
@@ -2319,21 +2466,21 @@ export async function getCodeCoverage(auth, params) {
|
|
|
2319
2466
|
// Reports
|
|
2320
2467
|
export async function createReport(auth, params) {
|
|
2321
2468
|
try {
|
|
2469
|
+
const reportName = params.reportName ?? params.fullName;
|
|
2470
|
+
const label = params.label ?? params.name ?? reportName;
|
|
2322
2471
|
const filtersXml = (params.filters ?? []).map(f => `
|
|
2323
2472
|
<met:reportFilters>
|
|
2324
2473
|
<met:column>${x(f.column)}</met:column>
|
|
2325
2474
|
<met:operator>${x(f.operator)}</met:operator>
|
|
2326
2475
|
<met:value>${x(f.value)}</met:value>
|
|
2327
2476
|
</met:reportFilters>`).join("");
|
|
2328
|
-
const columnsXml = (params.columns ?? []).map(c => `<met:
|
|
2477
|
+
const columnsXml = (params.columns ?? []).map(c => `<met:columns><met:field>${x(c)}</met:field></met:columns>`).join("\n ");
|
|
2329
2478
|
const xml = `<met:metadata xsi:type="met:Report" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2330
|
-
<met:fullName>${x(
|
|
2331
|
-
<met:name>${x(
|
|
2479
|
+
<met:fullName>${x(reportName)}</met:fullName>
|
|
2480
|
+
<met:name>${x(label)}</met:name>
|
|
2332
2481
|
<met:reportType>${x(params.reportType)}</met:reportType>
|
|
2333
2482
|
<met:format>${x(params.format ?? "Tabular")}</met:format>
|
|
2334
2483
|
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
2335
|
-
${params.folderId ? `<met:folder>${x(params.folderId)}</met:folder>` : ""}
|
|
2336
|
-
${columnsXml}
|
|
2337
2484
|
${filtersXml}
|
|
2338
2485
|
<met:showDetails>true</met:showDetails>
|
|
2339
2486
|
</met:metadata>`;
|
|
@@ -2344,14 +2491,37 @@ export async function createReport(auth, params) {
|
|
|
2344
2491
|
}
|
|
2345
2492
|
export async function updateDashboard(auth, params) {
|
|
2346
2493
|
try {
|
|
2347
|
-
const
|
|
2348
|
-
const
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2494
|
+
const dashboardName = params.dashboardName ?? params.fullName;
|
|
2495
|
+
const readXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>Dashboard</met:type><met:fullNames>${x(dashboardName)}</met:fullNames></met:readMetadata>`);
|
|
2496
|
+
const recordMatch = readXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
2497
|
+
if (recordMatch) {
|
|
2498
|
+
let inner = recordMatch[1];
|
|
2499
|
+
if (params.title) inner = inner.replace(/<title>[^<]*<\/title>/i, `<title>${x(params.title)}</title>`);
|
|
2500
|
+
if (params.description) inner = inner.replace(/<description>[^<]*<\/description>/i, `<description>${x(params.description)}</description>`);
|
|
2501
|
+
const xml = `<met:metadata xsi:type="met:Dashboard" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2354
2502
|
${inner}
|
|
2503
|
+
</met:metadata>`;
|
|
2504
|
+
return await upsertMetadata(auth, xml);
|
|
2505
|
+
}
|
|
2506
|
+
// Dashboard not found — create it with all required fields
|
|
2507
|
+
const client = createClient(auth);
|
|
2508
|
+
const userResp = await client.get(`/query?q=${encodeURIComponent("SELECT Username FROM User WHERE IsActive=true AND UserType='Standard' ORDER BY CreatedDate ASC LIMIT 1")}`);
|
|
2509
|
+
const runningUser = userResp.data?.records?.[0]?.Username ?? "";
|
|
2510
|
+
const title = params.title ?? (dashboardName.includes("/") ? dashboardName.split("/").pop() : dashboardName);
|
|
2511
|
+
const xml = `<met:metadata xsi:type="met:Dashboard" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2512
|
+
<met:fullName>${x(dashboardName)}</met:fullName>
|
|
2513
|
+
<met:backgroundEndColor>#FFFFFF</met:backgroundEndColor>
|
|
2514
|
+
<met:backgroundFadeDirection>Diagonal</met:backgroundFadeDirection>
|
|
2515
|
+
<met:backgroundStartColor>#FFFFFF</met:backgroundStartColor>
|
|
2516
|
+
<met:dashboardType>SpecifiedUser</met:dashboardType>
|
|
2517
|
+
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
2518
|
+
<met:leftSection><met:columnSize>Medium</met:columnSize></met:leftSection>
|
|
2519
|
+
<met:rightSection><met:columnSize>Medium</met:columnSize></met:rightSection>
|
|
2520
|
+
<met:runningUser>${x(runningUser)}</met:runningUser>
|
|
2521
|
+
<met:textColor>#000000</met:textColor>
|
|
2522
|
+
<met:title>${x(title)}</met:title>
|
|
2523
|
+
<met:titleColor>#000000</met:titleColor>
|
|
2524
|
+
<met:titleSize>12</met:titleSize>
|
|
2355
2525
|
</met:metadata>`;
|
|
2356
2526
|
return await upsertMetadata(auth, xml);
|
|
2357
2527
|
} catch (err) {
|
|
@@ -2360,10 +2530,13 @@ export async function updateDashboard(auth, params) {
|
|
|
2360
2530
|
}
|
|
2361
2531
|
export async function createReportFolder(auth, params) {
|
|
2362
2532
|
try {
|
|
2363
|
-
const
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2533
|
+
const folderName = params.folderName ?? params.name ?? params.fullName;
|
|
2534
|
+
const label = params.label ?? params.name ?? folderName;
|
|
2535
|
+
const folderType = params.folderType === "Dashboard" ? "DashboardFolder" : "ReportFolder";
|
|
2536
|
+
const xml = `<met:metadata xsi:type="met:${folderType}" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2537
|
+
<met:fullName>${x(folderName)}</met:fullName>
|
|
2538
|
+
<met:name>${x(label)}</met:name>
|
|
2539
|
+
<met:accessType>${x(params.accessType ?? "Shared")}</met:accessType>
|
|
2367
2540
|
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
2368
2541
|
</met:metadata>`;
|
|
2369
2542
|
return await upsertMetadata(auth, xml);
|
|
@@ -2373,15 +2546,27 @@ export async function createReportFolder(auth, params) {
|
|
|
2373
2546
|
}
|
|
2374
2547
|
export async function shareReportFolder(auth, params) {
|
|
2375
2548
|
try {
|
|
2376
|
-
const
|
|
2549
|
+
const folderName = params.folderName ?? params.name ?? params.fullName;
|
|
2550
|
+
const folderType = params.folderType === "Dashboard" ? "DashboardFolder" : (params.folderType ?? "ReportFolder");
|
|
2551
|
+
// Read existing folder to get required 'name' field
|
|
2552
|
+
const readXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>${folderType}</met:type><met:fullNames>${x(folderName)}</met:fullNames></met:readMetadata>`);
|
|
2553
|
+
const recordMatch = readXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
2554
|
+
const sharesXml = (params.shares ?? []).map(s => `
|
|
2377
2555
|
<met:folderShares>
|
|
2378
2556
|
<met:accessLevel>${x(s.accessLevel)}</met:accessLevel>
|
|
2379
2557
|
<met:sharedTo>${x(s.sharedTo)}</met:sharedTo>
|
|
2380
2558
|
<met:sharedToType>${x(s.sharedToType)}</met:sharedToType>
|
|
2381
2559
|
</met:folderShares>`).join("");
|
|
2382
|
-
|
|
2560
|
+
if (recordMatch) {
|
|
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
|
+
}
|
|
2383
2567
|
const xml = `<met:metadata xsi:type="met:${folderType}" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2384
|
-
<met:fullName>${x(
|
|
2568
|
+
<met:fullName>${x(folderName)}</met:fullName>
|
|
2569
|
+
<met:name>${x(params.folderLabel ?? folderName)}</met:name>
|
|
2385
2570
|
${sharesXml}
|
|
2386
2571
|
</met:metadata>`;
|
|
2387
2572
|
return await upsertMetadata(auth, xml);
|
|
@@ -3336,9 +3521,13 @@ export async function deleteRecord(auth, params) {
|
|
|
3336
3521
|
export async function getSetupAuditTrail(auth, params) {
|
|
3337
3522
|
try {
|
|
3338
3523
|
const client = createClient(auth);
|
|
3524
|
+
const now = new Date();
|
|
3525
|
+
const defaultDays = params.lookbackDays ?? 30;
|
|
3526
|
+
const startDate = params.startDate ?? new Date(now.getTime() - defaultDays * 86400000).toISOString().slice(0, 10);
|
|
3527
|
+
const endDate = params.endDate ?? now.toISOString().slice(0, 10);
|
|
3339
3528
|
const conditions = [];
|
|
3340
|
-
conditions.push(`CreatedDate >= ${
|
|
3341
|
-
conditions.push(`CreatedDate <= ${
|
|
3529
|
+
conditions.push(`CreatedDate >= ${startDate}T00:00:00Z`);
|
|
3530
|
+
conditions.push(`CreatedDate <= ${endDate}T23:59:59Z`);
|
|
3342
3531
|
if (params.createdByUsername) conditions.push(`CreatedBy.Username = '${params.createdByUsername.replace(/'/g, "\\'")}'`);
|
|
3343
3532
|
if (params.section) conditions.push(`Section = '${params.section.replace(/'/g, "\\'")}'`);
|
|
3344
3533
|
const soql = `SELECT Id, CreatedDate, CreatedBy.Username, Section, Action, Display FROM SetupAuditTrail WHERE ${conditions.join(" AND ")} ORDER BY CreatedDate DESC LIMIT ${params.limit ?? 100}`;
|
|
@@ -3358,16 +3547,20 @@ export async function getSetupAuditTrail(auth, params) {
|
|
|
3358
3547
|
export async function getLoginHistory(auth, params) {
|
|
3359
3548
|
try {
|
|
3360
3549
|
const client = createClient(auth);
|
|
3550
|
+
const now = new Date();
|
|
3551
|
+
const defaultHours = params.lookbackHours ?? (params.lookbackDays ? params.lookbackDays * 24 : 168);
|
|
3552
|
+
const startDate = params.startDate ?? new Date(now.getTime() - defaultHours * 3600000).toISOString().slice(0, 10);
|
|
3553
|
+
const endDate = params.endDate ?? now.toISOString().slice(0, 10);
|
|
3361
3554
|
const conditions = [];
|
|
3362
|
-
conditions.push(`LoginTime >= ${
|
|
3363
|
-
conditions.push(`LoginTime <= ${
|
|
3555
|
+
conditions.push(`LoginTime >= ${startDate}T00:00:00Z`);
|
|
3556
|
+
conditions.push(`LoginTime <= ${endDate}T23:59:59Z`);
|
|
3364
3557
|
if (params.username) conditions.push(`Username = '${params.username.replace(/'/g, "\\'")}'`);
|
|
3365
3558
|
if (params.status) conditions.push(`Status = '${params.status.replace(/'/g, "\\'")}'`);
|
|
3366
|
-
const soql = `SELECT Id, LoginTime,
|
|
3559
|
+
const soql = `SELECT Id, LoginTime, UserId, SourceIp, Browser, Platform, Status, LoginType FROM LoginHistory WHERE ${conditions.join(" AND ")} ORDER BY LoginTime DESC LIMIT ${params.limit ?? 100}`;
|
|
3367
3560
|
const resp = await client.get(`/query?q=${encodeURIComponent(soql)}`);
|
|
3368
3561
|
const records = (resp.data.records ?? []).map((r) => ({
|
|
3369
3562
|
loginTime: r.LoginTime,
|
|
3370
|
-
|
|
3563
|
+
userId: r.UserId,
|
|
3371
3564
|
sourceIp: r.SourceIp,
|
|
3372
3565
|
browser: r.Browser,
|
|
3373
3566
|
platform: r.Platform,
|
|
@@ -3382,7 +3575,12 @@ export async function getLoginHistory(auth, params) {
|
|
|
3382
3575
|
export async function getEventLogs(auth, params) {
|
|
3383
3576
|
try {
|
|
3384
3577
|
const client = createClient(auth);
|
|
3385
|
-
const
|
|
3578
|
+
const now = new Date();
|
|
3579
|
+
const defaultHours = params.lookbackHours ?? (params.lookbackDays ? params.lookbackDays * 24 : 24);
|
|
3580
|
+
const startDate = params.startDate ?? new Date(now.getTime() - defaultHours * 3600000).toISOString().slice(0, 10);
|
|
3581
|
+
const endDate = params.endDate ?? now.toISOString().slice(0, 10);
|
|
3582
|
+
const eventType = params.eventType ?? "Login";
|
|
3583
|
+
const soql = `SELECT Id, EventType, LogFile, LogDate, LogFileLength FROM EventLogFile WHERE EventType = '${eventType.replace(/'/g, "\\'")}' AND LogDate >= ${startDate}T00:00:00Z AND LogDate <= ${endDate}T23:59:59Z ORDER BY LogDate DESC LIMIT ${params.limit ?? 10}`;
|
|
3386
3584
|
const resp = await client.get(`/query?q=${encodeURIComponent(soql)}`);
|
|
3387
3585
|
const logFiles = resp.data.records ?? [];
|
|
3388
3586
|
if (logFiles.length === 0) {
|
|
@@ -3524,40 +3722,36 @@ export async function createEventRelay(auth, params) {
|
|
|
3524
3722
|
export async function createLetterhead(auth, params) {
|
|
3525
3723
|
try {
|
|
3526
3724
|
const xml = `<met:metadata xsi:type="met:Letterhead" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
3527
|
-
<met:fullName>${x(params.letterheadName)}</met:fullName>
|
|
3725
|
+
<met:fullName>${x(params.fullName ?? params.letterheadName)}</met:fullName>
|
|
3528
3726
|
<met:available>true</met:available>
|
|
3529
3727
|
<met:backgroundColor>${x(params.bodyColor ?? "#FFFFFF")}</met:backgroundColor>
|
|
3530
3728
|
<met:bodyColor>${x(params.bodyColor ?? "#FFFFFF")}</met:bodyColor>
|
|
3531
|
-
<met:
|
|
3729
|
+
<met:bottomLine>
|
|
3730
|
+
<met:color>${x(params.bottomLineColor ?? "#0070D2")}</met:color>
|
|
3731
|
+
<met:height>3</met:height>
|
|
3732
|
+
</met:bottomLine>
|
|
3733
|
+
<met:description>${x(params.description ?? params.footerText ?? "")}</met:description>
|
|
3532
3734
|
<met:footer>
|
|
3533
3735
|
<met:backgroundColor>${x(params.footerColor ?? "#404040")}</met:backgroundColor>
|
|
3534
|
-
<met:color>${x(params.footerFontColor ?? "#FFFFFF")}</met:color>
|
|
3535
|
-
<met:fontFace>${x(params.fontName ?? "Arial")}</met:fontFace>
|
|
3536
|
-
<met:fontSize>${params.fontSize ?? 12}</met:fontSize>
|
|
3537
3736
|
<met:height>60</met:height>
|
|
3538
3737
|
<met:horizontalAlignment>Center</met:horizontalAlignment>
|
|
3539
3738
|
<met:verticalAlignment>Bottom</met:verticalAlignment>
|
|
3540
3739
|
</met:footer>
|
|
3541
3740
|
<met:header>
|
|
3542
3741
|
<met:backgroundColor>${x(params.headerColor ?? "#0070D2")}</met:backgroundColor>
|
|
3543
|
-
<met:color>${x(params.headerFontColor ?? "#FFFFFF")}</met:color>
|
|
3544
|
-
<met:fontFace>${x(params.fontName ?? "Arial")}</met:fontFace>
|
|
3545
|
-
<met:fontSize>${params.fontSize ?? 12}</met:fontSize>
|
|
3546
3742
|
<met:height>60</met:height>
|
|
3547
3743
|
<met:horizontalAlignment>Center</met:horizontalAlignment>
|
|
3548
3744
|
<met:verticalAlignment>Middle</met:verticalAlignment>
|
|
3549
3745
|
</met:header>
|
|
3550
|
-
<met:
|
|
3746
|
+
<met:middleLine>
|
|
3747
|
+
<met:color>${x(params.middleLineColor ?? "#0070D2")}</met:color>
|
|
3748
|
+
<met:height>1</met:height>
|
|
3749
|
+
</met:middleLine>
|
|
3750
|
+
<met:name>${x(params.name ?? params.label ?? params.fullName ?? params.letterheadName)}</met:name>
|
|
3551
3751
|
<met:topLine>
|
|
3552
3752
|
<met:color>${x(params.topLineColor ?? "#0070D2")}</met:color>
|
|
3553
3753
|
<met:height>3</met:height>
|
|
3554
|
-
<met:style>Solid</met:style>
|
|
3555
3754
|
</met:topLine>
|
|
3556
|
-
<met:bottomLine>
|
|
3557
|
-
<met:color>${x(params.bottomLineColor ?? "#0070D2")}</met:color>
|
|
3558
|
-
<met:height>3</met:height>
|
|
3559
|
-
<met:style>Solid</met:style>
|
|
3560
|
-
</met:bottomLine>
|
|
3561
3755
|
</met:metadata>`;
|
|
3562
3756
|
return await upsertMetadata(auth, xml);
|
|
3563
3757
|
} catch (err) {
|
|
@@ -3792,14 +3986,18 @@ export async function createRollupSummaryField(auth, params) {
|
|
|
3792
3986
|
}
|
|
3793
3987
|
export async function createExternalIdField(auth, params) {
|
|
3794
3988
|
try {
|
|
3989
|
+
const objectName = params.objectApiName ?? params.objectName;
|
|
3990
|
+
const rawField = params.fieldApiName ?? params.fieldName ?? "";
|
|
3991
|
+
const fieldName = rawField.replace(/__c$/i, "");
|
|
3992
|
+
const fieldType = params.fieldType ?? params.type ?? "Text";
|
|
3795
3993
|
const xml = `<met:metadata xsi:type="met:CustomField" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
3796
|
-
<met:fullName>${x(
|
|
3797
|
-
<met:label>${x(params.label)}</met:label>
|
|
3798
|
-
<met:type>${x(
|
|
3994
|
+
<met:fullName>${x(objectName)}.${x(fieldName)}__c</met:fullName>
|
|
3995
|
+
<met:label>${x(params.label ?? fieldName)}</met:label>
|
|
3996
|
+
<met:type>${x(fieldType)}</met:type>
|
|
3799
3997
|
<met:externalId>true</met:externalId>
|
|
3800
|
-
<met:unique>${params.isUnique ? "true" : "false"}</met:unique>
|
|
3801
|
-
${
|
|
3802
|
-
${params.isCaseSensitive
|
|
3998
|
+
<met:unique>${(params.isUnique ?? params.unique) ? "true" : "false"}</met:unique>
|
|
3999
|
+
${fieldType === "Text" ? `<met:length>${params.length ?? 80}</met:length>` : ""}
|
|
4000
|
+
${(params.isCaseSensitive ?? params.caseInsensitive === false) && fieldType === "Text" ? "<met:caseSensitive>true</met:caseSensitive>" : ""}
|
|
3803
4001
|
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
3804
4002
|
</met:metadata>`;
|
|
3805
4003
|
return await upsertMetadata(auth, xml);
|
|
@@ -3809,17 +4007,22 @@ export async function createExternalIdField(auth, params) {
|
|
|
3809
4007
|
}
|
|
3810
4008
|
export async function enableObjectFeatures(auth, params) {
|
|
3811
4009
|
try {
|
|
3812
|
-
const
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
if (
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
4010
|
+
const objectName = params.objectApiName ?? params.fullName;
|
|
4011
|
+
const readXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>CustomObject</met:type><met:fullNames>${x(objectName)}</met:fullNames></met:readMetadata>`);
|
|
4012
|
+
const recordMatch = readXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
4013
|
+
if (!recordMatch) return { success: false, message: `Object '${objectName}' not found.` };
|
|
4014
|
+
let inner = recordMatch[1];
|
|
4015
|
+
const setFlag = (flag, val) => {
|
|
4016
|
+
if (inner.includes(`<${flag}>`)) inner = inner.replace(new RegExp(`<${flag}>[^<]*</${flag}>`, "i"), `<${flag}>${val}</${flag}>`);
|
|
4017
|
+
else inner += `\n <met:${flag}>${val}</met:${flag}>`;
|
|
4018
|
+
};
|
|
4019
|
+
if (params.enableHistory !== undefined) setFlag("enableHistory", params.enableHistory);
|
|
4020
|
+
if (params.enableFeeds !== undefined) setFlag("enableFeeds", params.enableFeeds);
|
|
4021
|
+
if (params.enableSearch !== undefined) setFlag("enableSearch", params.enableSearch);
|
|
4022
|
+
if (params.enableReports !== undefined) setFlag("enableReports", params.enableReports);
|
|
4023
|
+
if (params.enableActivities !== undefined) setFlag("enableActivities", params.enableActivities);
|
|
3820
4024
|
const xml = `<met:metadata xsi:type="met:CustomObject" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
3821
|
-
|
|
3822
|
-
${parts.join("\n ")}
|
|
4025
|
+
${inner}
|
|
3823
4026
|
</met:metadata>`;
|
|
3824
4027
|
return await upsertMetadata(auth, xml);
|
|
3825
4028
|
} catch (err) {
|
|
@@ -3829,7 +4032,7 @@ export async function enableObjectFeatures(auth, params) {
|
|
|
3829
4032
|
export async function updateFlow(auth, params) {
|
|
3830
4033
|
try {
|
|
3831
4034
|
const readXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>Flow</met:type><met:fullNames>${x(params.flowApiName)}</met:fullNames></met:readMetadata>`);
|
|
3832
|
-
const recordMatch = readXml.match(/<records
|
|
4035
|
+
const recordMatch = readXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
3833
4036
|
if (!recordMatch) return { success: false, message: `Flow '${params.flowApiName}' not found.` };
|
|
3834
4037
|
let inner = recordMatch[1];
|
|
3835
4038
|
if (params.newLabel) inner = inner.replace(/<label>[^<]*<\/label>/i, `<label>${x(params.newLabel)}</label>`);
|
|
@@ -3858,7 +4061,7 @@ export async function updateFlow(auth, params) {
|
|
|
3858
4061
|
export async function cloneFlow(auth, params) {
|
|
3859
4062
|
try {
|
|
3860
4063
|
const readXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>Flow</met:type><met:fullNames>${x(params.sourceFlowApiName)}</met:fullNames></met:readMetadata>`);
|
|
3861
|
-
const recordMatch = readXml.match(/<records
|
|
4064
|
+
const recordMatch = readXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
3862
4065
|
if (!recordMatch) return { success: false, message: `Flow '${params.sourceFlowApiName}' not found.` };
|
|
3863
4066
|
let inner = recordMatch[1];
|
|
3864
4067
|
inner = inner.replace(/<fullName>[^<]*<\/fullName>/i, `<fullName>${x(params.newFlowApiName)}</fullName>`);
|
|
@@ -4083,7 +4286,7 @@ export async function createExperienceContainer(auth, params) {
|
|
|
4083
4286
|
<met:value>${x(p.value)}</met:value>
|
|
4084
4287
|
</met:componentInstanceProperties>`).join("");
|
|
4085
4288
|
const readXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>FlexiPage</met:type><met:fullNames>${x(params.pageName)}</met:fullNames></met:readMetadata>`);
|
|
4086
|
-
const recordMatch = readXml.match(/<records
|
|
4289
|
+
const recordMatch = readXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
4087
4290
|
if (!recordMatch) return { success: false, message: `FlexiPage '${params.pageName}' not found.` };
|
|
4088
4291
|
const inner = recordMatch[1];
|
|
4089
4292
|
const regionPatch = `\n <met:flexiPageRegions>\n <met:name>${x(params.region ?? "main")}</met:name>\n <met:type>Region</met:type>\n <met:itemInstances>\n <met:componentName>${x(params.containerName)}</met:componentName>\n ${propsXml}\n </met:itemInstances>\n </met:flexiPageRegions>`;
|
|
@@ -4129,13 +4332,16 @@ export async function createCmsContent(auth, params) {
|
|
|
4129
4332
|
export async function exportRecords(auth, params) {
|
|
4130
4333
|
try {
|
|
4131
4334
|
const client = createClient(auth);
|
|
4132
|
-
const
|
|
4335
|
+
const query = params.query ?? params.soql ?? "";
|
|
4336
|
+
const maxRecords = params.limit ?? params.maxRecords ?? 2000;
|
|
4337
|
+
const includeHeaders = params.includeHeaders ?? params.includeHeader ?? true;
|
|
4338
|
+
const limitedQuery = query.replace(/\bLIMIT\s+\d+/i, "").trim() + ` LIMIT ${maxRecords}`;
|
|
4133
4339
|
const resp = await client.get(`/query?q=${encodeURIComponent(limitedQuery)}`);
|
|
4134
4340
|
const records = resp.data.records ?? [];
|
|
4135
|
-
if (records.length === 0) return { success: true, csv:
|
|
4341
|
+
if (records.length === 0) return { success: true, csv: includeHeaders ? "" : "", totalSize: 0, message: "No records found." };
|
|
4136
4342
|
const headers = Object.keys(records[0]).filter(k => k !== "attributes");
|
|
4137
4343
|
const rows = records.map(r => headers.map(h => { const v = r[h]; return v == null ? "" : typeof v === "object" ? JSON.stringify(v) : String(v).replace(/"/g, '""'); }).map(v => `"${v}"`).join(","));
|
|
4138
|
-
const csv = (
|
|
4344
|
+
const csv = (includeHeaders ? [headers.join(",")] : []).concat(rows).join("\n");
|
|
4139
4345
|
return { success: true, totalSize: resp.data.totalSize, recordCount: records.length, csv, message: `${records.length} record(s) exported.` };
|
|
4140
4346
|
} catch (err) {
|
|
4141
4347
|
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
@@ -4283,7 +4489,7 @@ export async function createAgentChannel(auth, params) {
|
|
|
4283
4489
|
try {
|
|
4284
4490
|
if (params.channelType === "Messaging") {
|
|
4285
4491
|
const readXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>MessagingChannel</met:type><met:fullNames>${x(params.channelName)}</met:fullNames></met:readMetadata>`);
|
|
4286
|
-
const recordMatch = readXml.match(/<records
|
|
4492
|
+
const recordMatch = readXml.match(/<records[^>]*>([\s\S]*?)<\/records>/i);
|
|
4287
4493
|
if (!recordMatch) return { success: false, message: `MessagingChannel '${params.channelName}' not found.` };
|
|
4288
4494
|
let inner = recordMatch[1];
|
|
4289
4495
|
if (inner.includes("<routingName>")) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "salesforce-metadata-mcp",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.2",
|
|
4
4
|
"description": "The most comprehensive Salesforce MCP server: 200+ tools covering custom objects, flows, Apex, LWC, page layouts, permissions, profiles, users, reports, dashboards, Experience Cloud, OmniStudio, OmniChannel, Agentforce, Einstein AI, audit trail, territories, sandboxes, CDC, REST resources, document generation, and more — everything a Salesforce admin, advanced admin and developer needs.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|