salesforce-metadata-mcp 2.1.3 → 2.2.0
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/AGENTFORCE.md +125 -1
- package/CHANGELOG.md +125 -0
- package/README.md +106 -19
- package/dist/index.js +2 -2
- package/dist/schemas/index.js +424 -3
- package/dist/services/deployment.js +29 -3
- package/dist/services/salesforce.js +1127 -1
- package/dist/tools/agentforce.js +197 -38
- package/dist/tools/apex.js +71 -3
- package/dist/tools/automation.js +25 -2
- package/dist/tools/data.js +86 -0
- package/dist/tools/deployment.js +40 -4
- package/dist/tools/experience.js +52 -2
- package/dist/tools/index.js +4 -0
- package/dist/tools/integrations.js +22 -2
- package/dist/tools/objects.js +42 -2
- package/dist/tools/reports.js +46 -0
- package/dist/tools/security.js +82 -2
- package/dist/tools/ui.js +72 -2
- package/package.json +14 -3
|
@@ -222,6 +222,24 @@ export function createClient(auth) {
|
|
|
222
222
|
body: bodyStr,
|
|
223
223
|
});
|
|
224
224
|
},
|
|
225
|
+
patch(path, body) {
|
|
226
|
+
return doFetch(path, {
|
|
227
|
+
method: "PATCH",
|
|
228
|
+
headers: { Authorization: authHeader, "Content-Type": "application/json" },
|
|
229
|
+
body: JSON.stringify(body),
|
|
230
|
+
});
|
|
231
|
+
},
|
|
232
|
+
async del(path) {
|
|
233
|
+
const response = await fetchWithTimeout(`${baseURL}${path}`, {
|
|
234
|
+
method: "DELETE",
|
|
235
|
+
headers: { Authorization: authHeader },
|
|
236
|
+
}, 60_000);
|
|
237
|
+
if (!response.ok) {
|
|
238
|
+
const text = await response.text().catch(() => "");
|
|
239
|
+
throw new Error(sanitizeError(`Salesforce API error ${response.status}: ${text.slice(0, 300)}`));
|
|
240
|
+
}
|
|
241
|
+
return { data: {} };
|
|
242
|
+
},
|
|
225
243
|
};
|
|
226
244
|
}
|
|
227
245
|
/**
|
|
@@ -1219,6 +1237,8 @@ function buildBotXml(params) {
|
|
|
1219
1237
|
<met:richContentEnabled>true</met:richContentEnabled>
|
|
1220
1238
|
<met:sessionTimeout>0</met:sessionTimeout>
|
|
1221
1239
|
<met:type>${x(params.type ?? "EinsteinCopilot")}</met:type>
|
|
1240
|
+
${params.systemPrompt ? `<met:systemPrompt>${x(params.systemPrompt)}</met:systemPrompt>` : ""}
|
|
1241
|
+
${params.openingMessage ? `<met:welcomeMessage>${x(params.openingMessage)}</met:welcomeMessage>` : ""}
|
|
1222
1242
|
</met:metadata>`;
|
|
1223
1243
|
}
|
|
1224
1244
|
function buildGenAiPluginXml(params) {
|
|
@@ -1239,8 +1259,9 @@ function buildGenAiPluginXml(params) {
|
|
|
1239
1259
|
<met:description>${x(params.description)}</met:description>
|
|
1240
1260
|
<met:scope>${x(params.scope)}</met:scope>
|
|
1241
1261
|
<met:pluginType>Topic</met:pluginType>
|
|
1242
|
-
<met:canEscalate
|
|
1262
|
+
<met:canEscalate>${params.escalationEnabled === true ? "true" : "false"}</met:canEscalate>
|
|
1243
1263
|
<met:language>en_US</met:language>
|
|
1264
|
+
${params.fallbackTopic ? `<met:fallbackPlugin>${x(params.fallbackTopic)}</met:fallbackPlugin>` : ""}
|
|
1244
1265
|
${instructionsXml}
|
|
1245
1266
|
${functionsXml}
|
|
1246
1267
|
</met:metadata>`;
|
|
@@ -1250,11 +1271,16 @@ function buildGenAiPlannerBundleXml(params) {
|
|
|
1250
1271
|
<met:genAiPlugins>
|
|
1251
1272
|
<met:genAiPluginName>${x(t)}</met:genAiPluginName>
|
|
1252
1273
|
</met:genAiPlugins>`).join("\n");
|
|
1274
|
+
const dataLibXml = params.dataLibraryName ? `
|
|
1275
|
+
<met:dataLibraries>
|
|
1276
|
+
<met:dataLibraryName>${x(params.dataLibraryName)}</met:dataLibraryName>
|
|
1277
|
+
</met:dataLibraries>` : "";
|
|
1253
1278
|
return `<met:metadata xsi:type="met:GenAiPlannerBundle" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
1254
1279
|
<met:fullName>${x(params.plannerName)}</met:fullName>
|
|
1255
1280
|
<met:masterLabel>${x(params.label)}</met:masterLabel>
|
|
1256
1281
|
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
1257
1282
|
<met:plannerType>AiCopilot__ReAct</met:plannerType>
|
|
1283
|
+
${dataLibXml}
|
|
1258
1284
|
${pluginsXml}
|
|
1259
1285
|
</met:metadata>`;
|
|
1260
1286
|
}
|
|
@@ -1527,4 +1553,1104 @@ export async function deactivateAgent(auth, agentApiName) {
|
|
|
1527
1553
|
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
1528
1554
|
}
|
|
1529
1555
|
}
|
|
1556
|
+
// ─── Metadata read/list/delete helpers ───────────────────────────────────────
|
|
1557
|
+
export async function readMetadataItem(auth, type, fullName) {
|
|
1558
|
+
const body = `<met:readMetadata>
|
|
1559
|
+
<met:type>${type}</met:type>
|
|
1560
|
+
<met:fullNames>${x(fullName)}</met:fullNames>
|
|
1561
|
+
</met:readMetadata>`;
|
|
1562
|
+
try {
|
|
1563
|
+
const xml = await callMetadataSoap(auth, "readMetadata", body);
|
|
1564
|
+
const error = extractSoapError(xml);
|
|
1565
|
+
if (error)
|
|
1566
|
+
return { success: false, message: error };
|
|
1567
|
+
return { success: true, rawXml: xml };
|
|
1568
|
+
}
|
|
1569
|
+
catch (err) {
|
|
1570
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
export async function listMetadataType(auth, type) {
|
|
1574
|
+
const body = `<met:listMetadata>
|
|
1575
|
+
<met:queries><met:type>${x(type)}</met:type></met:queries>
|
|
1576
|
+
<met:asOfVersion>${API_VERSION}</met:asOfVersion>
|
|
1577
|
+
</met:listMetadata>`;
|
|
1578
|
+
try {
|
|
1579
|
+
const xml = await callMetadataSoap(auth, "listMetadata", body);
|
|
1580
|
+
const error = extractSoapError(xml);
|
|
1581
|
+
if (error)
|
|
1582
|
+
return { success: false, message: error, items: [] };
|
|
1583
|
+
const items = [];
|
|
1584
|
+
for (const block of [...xml.matchAll(/<result>([\s\S]*?)<\/result>/gi)]) {
|
|
1585
|
+
const inner = block[1];
|
|
1586
|
+
const fullNameMatch = inner.match(/<fullName[^>]*>([\s\S]*?)<\/fullName>/i);
|
|
1587
|
+
if (fullNameMatch) {
|
|
1588
|
+
const labelMatch = inner.match(/<label[^>]*>([\s\S]*?)<\/label>/i);
|
|
1589
|
+
const lastModifiedMatch = inner.match(/<lastModifiedDate[^>]*>([\s\S]*?)<\/lastModifiedDate>/i);
|
|
1590
|
+
items.push({
|
|
1591
|
+
fullName: fullNameMatch[1].trim(),
|
|
1592
|
+
label: labelMatch?.[1].trim() ?? "",
|
|
1593
|
+
lastModifiedDate: lastModifiedMatch?.[1].trim() ?? "",
|
|
1594
|
+
type,
|
|
1595
|
+
});
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
return { success: true, items };
|
|
1599
|
+
}
|
|
1600
|
+
catch (err) {
|
|
1601
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)), items: [] };
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
export async function deleteMetadataItems(auth, type, fullNames) {
|
|
1605
|
+
if (fullNames.length === 0)
|
|
1606
|
+
return { success: true, deleted: [], message: "Nothing to delete" };
|
|
1607
|
+
const fullNamesXml = fullNames.map(n => `<met:fullNames>${x(n)}</met:fullNames>`).join("\n");
|
|
1608
|
+
const body = `<met:deleteMetadata>
|
|
1609
|
+
<met:type>${x(type)}</met:type>
|
|
1610
|
+
${fullNamesXml}
|
|
1611
|
+
</met:deleteMetadata>`;
|
|
1612
|
+
try {
|
|
1613
|
+
const xml = await callMetadataSoap(auth, "deleteMetadata", body);
|
|
1614
|
+
const error = extractSoapError(xml);
|
|
1615
|
+
if (error)
|
|
1616
|
+
return { success: false, message: error, deleted: [] };
|
|
1617
|
+
const deleted = [];
|
|
1618
|
+
const errors = [];
|
|
1619
|
+
for (const block of [...xml.matchAll(/<result>([\s\S]*?)<\/result>/gi)]) {
|
|
1620
|
+
const inner = block[1];
|
|
1621
|
+
const fullNameMatch = inner.match(/<fullName[^>]*>([\s\S]*?)<\/fullName>/i);
|
|
1622
|
+
const successMatch = inner.match(/<success>(true|false)<\/success>/i);
|
|
1623
|
+
const fnName = fullNameMatch?.[1].trim() ?? "";
|
|
1624
|
+
if (successMatch?.[1] === "true") {
|
|
1625
|
+
deleted.push(fnName);
|
|
1626
|
+
}
|
|
1627
|
+
else {
|
|
1628
|
+
const msgMatch = inner.match(/<message[^>]*>([\s\S]*?)<\/message>/i);
|
|
1629
|
+
errors.push(`${fnName}: ${msgMatch?.[1].trim() ?? "Unknown error"}`);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
return {
|
|
1633
|
+
success: errors.length === 0,
|
|
1634
|
+
deleted,
|
|
1635
|
+
message: deleted.length > 0 ? `Deleted: ${deleted.join(", ")}` : "Nothing deleted",
|
|
1636
|
+
...(errors.length > 0 ? { errors } : {}),
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
catch (err) {
|
|
1640
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)), deleted: [] };
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
// ─── Agentforce query/management functions ────────────────────────────────────
|
|
1644
|
+
export async function getAgent(auth, agentApiName) {
|
|
1645
|
+
try {
|
|
1646
|
+
let status = "Unknown";
|
|
1647
|
+
const statusResp = await fetchWithTimeout(`${auth.instanceUrl}/services/data/v${API_VERSION}/connect/einstein/copilot/${agentApiName}`, { method: "GET", headers: { "Authorization": `Bearer ${auth.accessToken}`, "Content-Type": "application/json" } }, 15_000).catch(() => null);
|
|
1648
|
+
if (statusResp?.ok) {
|
|
1649
|
+
const data = await statusResp.json().catch(() => ({}));
|
|
1650
|
+
status = data.status ?? data.botStatus ?? "Unknown";
|
|
1651
|
+
}
|
|
1652
|
+
const [botResult, pluginList, funcList, plannerList] = await Promise.all([
|
|
1653
|
+
readMetadataItem(auth, "Bot", agentApiName),
|
|
1654
|
+
listMetadataType(auth, "GenAiPlugin"),
|
|
1655
|
+
listMetadataType(auth, "GenAiFunction"),
|
|
1656
|
+
listMetadataType(auth, "GenAiPlannerBundle"),
|
|
1657
|
+
]);
|
|
1658
|
+
return {
|
|
1659
|
+
success: true,
|
|
1660
|
+
agent: { apiName: agentApiName, status, metadataFound: botResult.success },
|
|
1661
|
+
topics: pluginList.items,
|
|
1662
|
+
actions: funcList.items,
|
|
1663
|
+
planners: plannerList.items,
|
|
1664
|
+
message: `Agent '${agentApiName}' — status: ${status}, ${pluginList.items.length} topic(s), ${funcList.items.length} action(s), ${plannerList.items.length} planner(s).`,
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
catch (err) {
|
|
1668
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
export async function listAgents(auth) {
|
|
1672
|
+
try {
|
|
1673
|
+
const botList = await listMetadataType(auth, "Bot");
|
|
1674
|
+
if (!botList.success)
|
|
1675
|
+
return { success: false, message: botList.message ?? "Failed to list agents", agents: [] };
|
|
1676
|
+
const agents = [];
|
|
1677
|
+
for (const bot of botList.items) {
|
|
1678
|
+
let status = "Unknown";
|
|
1679
|
+
const resp = await fetchWithTimeout(`${auth.instanceUrl}/services/data/v${API_VERSION}/connect/einstein/copilot/${bot.fullName}`, { method: "GET", headers: { "Authorization": `Bearer ${auth.accessToken}` } }, 10_000).catch(() => null);
|
|
1680
|
+
if (resp?.ok) {
|
|
1681
|
+
const data = await resp.json().catch(() => ({}));
|
|
1682
|
+
status = data.status ?? data.botStatus ?? "Unknown";
|
|
1683
|
+
}
|
|
1684
|
+
agents.push({ apiName: bot.fullName, label: bot.label, status, lastModifiedDate: bot.lastModifiedDate });
|
|
1685
|
+
}
|
|
1686
|
+
return { success: true, agents, message: `Found ${agents.length} agent(s).` };
|
|
1687
|
+
}
|
|
1688
|
+
catch (err) {
|
|
1689
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)), agents: [] };
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
export async function updateAgentTopic(auth, params, agentApiName) {
|
|
1693
|
+
try {
|
|
1694
|
+
if (agentApiName) {
|
|
1695
|
+
const deactResult = await deactivateAgent(auth, agentApiName);
|
|
1696
|
+
if (!deactResult.success)
|
|
1697
|
+
console.error(`Warning: deactivation returned: ${deactResult.message}`);
|
|
1698
|
+
}
|
|
1699
|
+
const result = await upsertMetadata(auth, buildGenAiPluginXml(params));
|
|
1700
|
+
if (agentApiName) {
|
|
1701
|
+
const actResult = await activateAgent(auth, agentApiName);
|
|
1702
|
+
if (!actResult.success)
|
|
1703
|
+
return { ...result, warning: `Topic updated but agent reactivation failed: ${actResult.message}` };
|
|
1704
|
+
}
|
|
1705
|
+
return result;
|
|
1706
|
+
}
|
|
1707
|
+
catch (err) {
|
|
1708
|
+
if (agentApiName)
|
|
1709
|
+
await activateAgent(auth, agentApiName).catch(() => { });
|
|
1710
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
export async function deleteAgent(auth, params) {
|
|
1714
|
+
const deleted = [];
|
|
1715
|
+
const errors = [];
|
|
1716
|
+
try {
|
|
1717
|
+
await deactivateAgent(auth, params.agentApiName).catch(() => { });
|
|
1718
|
+
const botRead = await readMetadataItem(auth, "Bot", params.agentApiName);
|
|
1719
|
+
let topicsToDelete = [];
|
|
1720
|
+
let actionsToDelete = [];
|
|
1721
|
+
let plannerToDelete = null;
|
|
1722
|
+
if (botRead.success && botRead.rawXml) {
|
|
1723
|
+
const plannerMatch = botRead.rawXml.match(/<plannerBundle[^>]*>([\s\S]*?)<\/plannerBundle>/i)
|
|
1724
|
+
|| botRead.rawXml.match(/<plannerBundleName[^>]*>([\s\S]*?)<\/plannerBundleName>/i);
|
|
1725
|
+
if (plannerMatch)
|
|
1726
|
+
plannerToDelete = plannerMatch[1].trim();
|
|
1727
|
+
}
|
|
1728
|
+
if ((params.deleteTopics || params.deleteActions) && plannerToDelete) {
|
|
1729
|
+
const plannerRead = await readMetadataItem(auth, "GenAiPlannerBundle", plannerToDelete);
|
|
1730
|
+
if (plannerRead.success && plannerRead.rawXml) {
|
|
1731
|
+
topicsToDelete = [...plannerRead.rawXml.matchAll(/<genAiPluginName[^>]*>([\s\S]*?)<\/genAiPluginName>/gi)].map(m => m[1].trim());
|
|
1732
|
+
if (params.deleteActions) {
|
|
1733
|
+
for (const topicName of topicsToDelete) {
|
|
1734
|
+
const topicRead = await readMetadataItem(auth, "GenAiPlugin", topicName);
|
|
1735
|
+
if (topicRead.success && topicRead.rawXml) {
|
|
1736
|
+
const actionNames = [...topicRead.rawXml.matchAll(/<functionName[^>]*>([\s\S]*?)<\/functionName>/gi)].map(m => m[1].trim());
|
|
1737
|
+
actionsToDelete.push(...actionNames);
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
actionsToDelete = [...new Set(actionsToDelete)];
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
if (actionsToDelete.length > 0) {
|
|
1745
|
+
const res = await deleteMetadataItems(auth, "GenAiFunction", actionsToDelete);
|
|
1746
|
+
deleted.push(...(res.deleted ?? []));
|
|
1747
|
+
if (res.errors)
|
|
1748
|
+
errors.push(...res.errors);
|
|
1749
|
+
}
|
|
1750
|
+
if (topicsToDelete.length > 0) {
|
|
1751
|
+
const res = await deleteMetadataItems(auth, "GenAiPlugin", topicsToDelete);
|
|
1752
|
+
deleted.push(...(res.deleted ?? []));
|
|
1753
|
+
if (res.errors)
|
|
1754
|
+
errors.push(...res.errors);
|
|
1755
|
+
}
|
|
1756
|
+
if (plannerToDelete) {
|
|
1757
|
+
const res = await deleteMetadataItems(auth, "GenAiPlannerBundle", [plannerToDelete]);
|
|
1758
|
+
deleted.push(...(res.deleted ?? []));
|
|
1759
|
+
if (res.errors)
|
|
1760
|
+
errors.push(...res.errors);
|
|
1761
|
+
}
|
|
1762
|
+
const botRes = await deleteMetadataItems(auth, "Bot", [params.agentApiName]);
|
|
1763
|
+
deleted.push(...(botRes.deleted ?? []));
|
|
1764
|
+
if (botRes.errors)
|
|
1765
|
+
errors.push(...botRes.errors);
|
|
1766
|
+
return {
|
|
1767
|
+
success: errors.length === 0,
|
|
1768
|
+
deleted,
|
|
1769
|
+
message: `Deleted ${deleted.length} component(s)${deleted.length > 0 ? ": " + deleted.join(", ") : ""}.`,
|
|
1770
|
+
...(errors.length > 0 ? { errors } : {}),
|
|
1771
|
+
};
|
|
1772
|
+
}
|
|
1773
|
+
catch (err) {
|
|
1774
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)), deleted };
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
export async function testAgent(auth, params) {
|
|
1778
|
+
try {
|
|
1779
|
+
const orgUrl = (params.orgUrl ?? auth.instanceUrl).replace(/\/$/, "");
|
|
1780
|
+
const externalSessionKey = `mcp_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
1781
|
+
const bootstrapResp = await fetchWithTimeout(`${orgUrl}/agentforce/bootstrap/nameduser`, {
|
|
1782
|
+
method: "POST",
|
|
1783
|
+
headers: { "Authorization": `Bearer ${auth.accessToken}`, "Content-Type": "application/json" },
|
|
1784
|
+
body: JSON.stringify({ botApiName: params.agentApiName, externalSessionKey, forceConfig: { endpoint: orgUrl } }),
|
|
1785
|
+
}, 30_000);
|
|
1786
|
+
if (!bootstrapResp.ok) {
|
|
1787
|
+
const body = await bootstrapResp.text().catch(() => "");
|
|
1788
|
+
return { success: false, message: `Bootstrap failed (HTTP ${bootstrapResp.status}): ${body.slice(0, 400)}` };
|
|
1789
|
+
}
|
|
1790
|
+
const bootstrap = await bootstrapResp.json();
|
|
1791
|
+
const agentToken = bootstrap.token;
|
|
1792
|
+
const agentUrl = (bootstrap.agentUrl ?? "").replace(/\/$/, "");
|
|
1793
|
+
if (!agentToken || !agentUrl)
|
|
1794
|
+
return { success: false, message: `Bootstrap missing token or agentUrl. Response: ${JSON.stringify(bootstrap).slice(0, 300)}` };
|
|
1795
|
+
const sessionResp = await fetchWithTimeout(`${agentUrl}/sessions`, {
|
|
1796
|
+
method: "POST",
|
|
1797
|
+
headers: { "Authorization": `Bearer ${agentToken}`, "Content-Type": "application/json", "x-sfdc-app-context": "EinsteinGPT", "x-client-feature-id": "ai-platform-einstein-gpt" },
|
|
1798
|
+
body: JSON.stringify({ externalSessionKey, instanceConfig: { endpoint: orgUrl } }),
|
|
1799
|
+
}, 30_000);
|
|
1800
|
+
if (!sessionResp.ok) {
|
|
1801
|
+
const body = await sessionResp.text().catch(() => "");
|
|
1802
|
+
return { success: false, message: `Session creation failed (HTTP ${sessionResp.status}): ${body.slice(0, 400)}` };
|
|
1803
|
+
}
|
|
1804
|
+
const sessionData = await sessionResp.json();
|
|
1805
|
+
const sessionId = sessionData.sessionId ?? sessionData.id;
|
|
1806
|
+
if (!sessionId)
|
|
1807
|
+
return { success: false, message: `No sessionId in response: ${JSON.stringify(sessionData).slice(0, 300)}` };
|
|
1808
|
+
const msgResp = await fetchWithTimeout(`${agentUrl}/sessions/${sessionId}/messages`, {
|
|
1809
|
+
method: "POST",
|
|
1810
|
+
headers: { "Authorization": `Bearer ${agentToken}`, "Content-Type": "application/json", "x-sfdc-app-context": "EinsteinGPT", "x-client-feature-id": "ai-platform-einstein-gpt" },
|
|
1811
|
+
body: JSON.stringify({ message: { role: "user", content: [{ type: "text", text: params.message }] }, variables: [] }),
|
|
1812
|
+
}, 90_000);
|
|
1813
|
+
if (!msgResp.ok) {
|
|
1814
|
+
const body = await msgResp.text().catch(() => "");
|
|
1815
|
+
return { success: false, sessionId, message: `Message failed (HTTP ${msgResp.status}): ${body.slice(0, 400)}` };
|
|
1816
|
+
}
|
|
1817
|
+
const msgData = await msgResp.json();
|
|
1818
|
+
const messages = msgData.messages ?? msgData.result?.messages ?? [];
|
|
1819
|
+
const responseText = messages
|
|
1820
|
+
.filter(m => m.type === "Reply" || m.role === "agent")
|
|
1821
|
+
.map(m => {
|
|
1822
|
+
if (typeof m.message === "string")
|
|
1823
|
+
return m.message;
|
|
1824
|
+
if (Array.isArray(m.content))
|
|
1825
|
+
return m.content.map(c => c.text ?? "").join("");
|
|
1826
|
+
return m.text ?? "";
|
|
1827
|
+
})
|
|
1828
|
+
.filter(Boolean)
|
|
1829
|
+
.join("\n")
|
|
1830
|
+
.trim();
|
|
1831
|
+
return {
|
|
1832
|
+
success: true,
|
|
1833
|
+
sessionId,
|
|
1834
|
+
response: responseText || JSON.stringify(msgData).slice(0, 1000),
|
|
1835
|
+
message: responseText ? "Agent responded successfully." : "Message sent successfully.",
|
|
1836
|
+
};
|
|
1837
|
+
}
|
|
1838
|
+
catch (err) {
|
|
1839
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
export async function getAgentLogs(auth, params) {
|
|
1843
|
+
try {
|
|
1844
|
+
const client = createClient(auth);
|
|
1845
|
+
const limit = params.limit ?? 20;
|
|
1846
|
+
let whereClause = "LogDate = TODAY";
|
|
1847
|
+
if (params.agentApiName) {
|
|
1848
|
+
const safe = params.agentApiName.replace(/'/g, "\\'");
|
|
1849
|
+
whereClause += ` AND (EventTarget LIKE '%${safe}%' OR EventDetails LIKE '%${safe}%')`;
|
|
1850
|
+
}
|
|
1851
|
+
const query = `SELECT EventLabel, EventTarget, EventDetails, EventDate FROM ConversationDefinitionEventLog WHERE ${whereClause} ORDER BY EventDate DESC LIMIT ${limit}`;
|
|
1852
|
+
const resp = await client.get(`/query?q=${encodeURIComponent(query)}`);
|
|
1853
|
+
const records = resp.data.records ?? [];
|
|
1854
|
+
const topicEvents = records.filter(r => (r.EventLabel ?? "").includes("TopicClassification")).length;
|
|
1855
|
+
const actionEvents = records.filter(r => (r.EventLabel ?? "").includes("ActionExecuted")).length;
|
|
1856
|
+
const errorEvents = records.filter(r => (r.EventLabel ?? "").toLowerCase().includes("error") || (r.EventLabel ?? "").toLowerCase().includes("fail")).length;
|
|
1857
|
+
const logs = records.map(r => {
|
|
1858
|
+
let details = r.EventDetails;
|
|
1859
|
+
try {
|
|
1860
|
+
details = JSON.parse(r.EventDetails ?? "{}");
|
|
1861
|
+
}
|
|
1862
|
+
catch { /* keep raw */ }
|
|
1863
|
+
return { time: r.EventDate, event: r.EventLabel, target: r.EventTarget, details };
|
|
1864
|
+
});
|
|
1865
|
+
return {
|
|
1866
|
+
success: true,
|
|
1867
|
+
totalRecords: records.length,
|
|
1868
|
+
summary: { topicClassifications: topicEvents, actionsExecuted: actionEvents, errors: errorEvents },
|
|
1869
|
+
logs,
|
|
1870
|
+
message: `${records.length} entries: ${topicEvents} topic classification(s), ${actionEvents} action execution(s), ${errorEvents} error(s).`,
|
|
1871
|
+
};
|
|
1872
|
+
}
|
|
1873
|
+
catch (err) {
|
|
1874
|
+
const msg = sanitizeError(err instanceof Error ? err.message : String(err));
|
|
1875
|
+
if (msg.includes("INVALID_TYPE") || msg.includes("ConversationDefinition"))
|
|
1876
|
+
return { success: false, message: "ConversationDefinitionEventLog is not available in this org. Requires Event Monitoring or Agentforce debugging enabled.", logs: [] };
|
|
1877
|
+
return { success: false, message: msg, logs: [] };
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
// ─── v2.2.0 Service Functions ─────────────────────────────────────────────────
|
|
1881
|
+
// Objects & Fields
|
|
1882
|
+
export async function updateCustomObject(auth, params) {
|
|
1883
|
+
try {
|
|
1884
|
+
const fields = [];
|
|
1885
|
+
if (params.label) fields.push(`<met:label>${x(params.label)}</met:label>`);
|
|
1886
|
+
if (params.pluralLabel) fields.push(`<met:pluralLabel>${x(params.pluralLabel)}</met:pluralLabel>`);
|
|
1887
|
+
if (params.description !== undefined) fields.push(`<met:description>${x(params.description)}</met:description>`);
|
|
1888
|
+
if (params.enableHistory !== undefined) fields.push(`<met:enableHistory>${params.enableHistory}</met:enableHistory>`);
|
|
1889
|
+
if (params.enableReports !== undefined) fields.push(`<met:enableReports>${params.enableReports}</met:enableReports>`);
|
|
1890
|
+
if (params.enableSearch !== undefined) fields.push(`<met:enableSearch>${params.enableSearch}</met:enableSearch>`);
|
|
1891
|
+
if (params.enableActivities !== undefined) fields.push(`<met:enableActivities>${params.enableActivities}</met:enableActivities>`);
|
|
1892
|
+
const xml = `<met:metadata xsi:type="met:CustomObject" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
1893
|
+
<met:fullName>${x(params.objectApiName)}</met:fullName>
|
|
1894
|
+
${fields.join("\n ")}
|
|
1895
|
+
</met:metadata>`;
|
|
1896
|
+
return await upsertMetadata(auth, xml);
|
|
1897
|
+
} catch (err) {
|
|
1898
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
export async function updateCustomField(auth, params) {
|
|
1902
|
+
try {
|
|
1903
|
+
const fields = [];
|
|
1904
|
+
if (params.label) fields.push(`<met:label>${x(params.label)}</met:label>`);
|
|
1905
|
+
if (params.description !== undefined) fields.push(`<met:description>${x(params.description)}</met:description>`);
|
|
1906
|
+
if (params.helpText !== undefined) fields.push(`<met:inlineHelpText>${x(params.helpText)}</met:inlineHelpText>`);
|
|
1907
|
+
if (params.required !== undefined) fields.push(`<met:required>${params.required}</met:required>`);
|
|
1908
|
+
if (params.unique !== undefined) fields.push(`<met:unique>${params.unique}</met:unique>`);
|
|
1909
|
+
if (params.defaultValue !== undefined) fields.push(`<met:defaultValue>${x(params.defaultValue)}</met:defaultValue>`);
|
|
1910
|
+
const xml = `<met:metadata xsi:type="met:CustomField" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
1911
|
+
<met:fullName>${x(params.objectApiName)}.${x(params.fieldApiName)}</met:fullName>
|
|
1912
|
+
${fields.join("\n ")}
|
|
1913
|
+
</met:metadata>`;
|
|
1914
|
+
return await upsertMetadata(auth, xml);
|
|
1915
|
+
} catch (err) {
|
|
1916
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
export async function createRelationshipField(auth, params) {
|
|
1920
|
+
try {
|
|
1921
|
+
const isMD = params.relationshipType === "MasterDetail";
|
|
1922
|
+
const deleteConstraint = params.onDelete === "Restrict" ? "Restrict" : "SetNull";
|
|
1923
|
+
const xml = `<met:metadata xsi:type="met:CustomField" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
1924
|
+
<met:fullName>${x(params.objectApiName)}.${x(params.fieldName)}__c</met:fullName>
|
|
1925
|
+
<met:label>${x(params.label)}</met:label>
|
|
1926
|
+
<met:type>${isMD ? "MasterDetail" : "Lookup"}</met:type>
|
|
1927
|
+
<met:referenceTo>${x(params.relatedObject)}</met:referenceTo>
|
|
1928
|
+
<met:relationshipName>${x(params.relationshipName)}</met:relationshipName>
|
|
1929
|
+
${!isMD ? `<met:deleteConstraint>${deleteConstraint}</met:deleteConstraint>` : ""}
|
|
1930
|
+
${params.required ? `<met:required>true</met:required>` : ""}
|
|
1931
|
+
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
1932
|
+
</met:metadata>`;
|
|
1933
|
+
return await upsertMetadata(auth, xml);
|
|
1934
|
+
} catch (err) {
|
|
1935
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
export async function createFormulaField(auth, params) {
|
|
1939
|
+
try {
|
|
1940
|
+
const typeMap = { Text: "Text", Number: "Number", Currency: "Currency", Date: "Date", DateTime: "DateTime", Checkbox: "Checkbox", Percent: "Percent" };
|
|
1941
|
+
const sfType = typeMap[params.returnType] ?? "Text";
|
|
1942
|
+
const xml = `<met:metadata xsi:type="met:CustomField" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
1943
|
+
<met:fullName>${x(params.objectApiName)}.${x(params.fieldName)}__c</met:fullName>
|
|
1944
|
+
<met:label>${x(params.label)}</met:label>
|
|
1945
|
+
<met:type>${sfType}</met:type>
|
|
1946
|
+
<met:formula>${x(params.formula)}</met:formula>
|
|
1947
|
+
${params.scale !== undefined ? `<met:scale>${params.scale}</met:scale>` : ""}
|
|
1948
|
+
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
1949
|
+
</met:metadata>`;
|
|
1950
|
+
return await upsertMetadata(auth, xml);
|
|
1951
|
+
} catch (err) {
|
|
1952
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
// Security
|
|
1956
|
+
export async function assignPermissionSet(auth, params) {
|
|
1957
|
+
try {
|
|
1958
|
+
const client = createClient(auth);
|
|
1959
|
+
// Resolve permission set ID
|
|
1960
|
+
const psQuery = await client.get(`/query?q=${encodeURIComponent(`SELECT Id FROM PermissionSet WHERE Name = '${params.permissionSetName.replace(/'/g, "\\'")}'`)}`);
|
|
1961
|
+
const psId = psQuery.data.records?.[0]?.Id;
|
|
1962
|
+
if (!psId) return { success: false, message: `Permission Set '${params.permissionSetName}' not found.` };
|
|
1963
|
+
// Resolve user ID
|
|
1964
|
+
let userId = params.userId;
|
|
1965
|
+
if (!userId && params.username) {
|
|
1966
|
+
const uQuery = await client.get(`/query?q=${encodeURIComponent(`SELECT Id FROM User WHERE Username = '${params.username.replace(/'/g, "\\'")}'`)}`);
|
|
1967
|
+
userId = uQuery.data.records?.[0]?.Id;
|
|
1968
|
+
if (!userId) return { success: false, message: `User '${params.username}' not found.` };
|
|
1969
|
+
}
|
|
1970
|
+
if (!userId) return { success: false, message: "Provide username or userId." };
|
|
1971
|
+
const resp = await client.post("/sobjects/PermissionSetAssignment", { PermissionSetId: psId, AssigneeId: userId });
|
|
1972
|
+
return { success: true, fullName: resp.data.id, created: true, message: `Permission Set '${params.permissionSetName}' assigned successfully.` };
|
|
1973
|
+
} catch (err) {
|
|
1974
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
export async function createPermissionSetGroup(auth, params) {
|
|
1978
|
+
try {
|
|
1979
|
+
const psXml = params.permissionSets.map(ps => `<met:permissionSets>${x(ps)}</met:permissionSets>`).join("\n ");
|
|
1980
|
+
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>
|
|
1982
|
+
<met:label>${x(params.label)}</met:label>
|
|
1983
|
+
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
1984
|
+
${psXml}
|
|
1985
|
+
</met:metadata>`;
|
|
1986
|
+
return await upsertMetadata(auth, xml);
|
|
1987
|
+
} catch (err) {
|
|
1988
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
export async function updatePermissionSet(auth, params) {
|
|
1992
|
+
try {
|
|
1993
|
+
const objPerms = (params.objectPermissions ?? []).map(op => `
|
|
1994
|
+
<met:objectPermissions>
|
|
1995
|
+
<met:object>${x(op.object)}</met:object>
|
|
1996
|
+
${op.allowCreate ? "<met:allowCreate>true</met:allowCreate>" : ""}
|
|
1997
|
+
${op.allowRead ? "<met:allowRead>true</met:allowRead>" : ""}
|
|
1998
|
+
${op.allowEdit ? "<met:allowEdit>true</met:allowEdit>" : ""}
|
|
1999
|
+
${op.allowDelete ? "<met:allowDelete>true</met:allowDelete>" : ""}
|
|
2000
|
+
${op.viewAllRecords ? "<met:viewAllRecords>true</met:viewAllRecords>" : ""}
|
|
2001
|
+
${op.modifyAllRecords ? "<met:modifyAllRecords>true</met:modifyAllRecords>" : ""}
|
|
2002
|
+
</met:objectPermissions>`).join("");
|
|
2003
|
+
const fldPerms = (params.fieldPermissions ?? []).map(fp => `
|
|
2004
|
+
<met:fieldPermissions>
|
|
2005
|
+
<met:field>${x(fp.field)}</met:field>
|
|
2006
|
+
<met:readable>${fp.readable ? "true" : "false"}</met:readable>
|
|
2007
|
+
<met:editable>${fp.editable ? "true" : "false"}</met:editable>
|
|
2008
|
+
</met:fieldPermissions>`).join("");
|
|
2009
|
+
const xml = `<met:metadata xsi:type="met:PermissionSet" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2010
|
+
<met:fullName>${x(params.permissionSetName)}</met:fullName>
|
|
2011
|
+
${objPerms}
|
|
2012
|
+
${fldPerms}
|
|
2013
|
+
</met:metadata>`;
|
|
2014
|
+
return await upsertMetadata(auth, xml);
|
|
2015
|
+
} catch (err) {
|
|
2016
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
export async function createMutingPermissionSet(auth, params) {
|
|
2020
|
+
try {
|
|
2021
|
+
const objPerms = (params.objectPermissions ?? []).map(op => `
|
|
2022
|
+
<met:objectPermissions>
|
|
2023
|
+
<met:object>${x(op.object)}</met:object>
|
|
2024
|
+
${op.allowCreate ? "<met:allowCreate>true</met:allowCreate>" : ""}
|
|
2025
|
+
${op.allowRead ? "<met:allowRead>true</met:allowRead>" : ""}
|
|
2026
|
+
${op.allowEdit ? "<met:allowEdit>true</met:allowEdit>" : ""}
|
|
2027
|
+
${op.allowDelete ? "<met:allowDelete>true</met:allowDelete>" : ""}
|
|
2028
|
+
</met:objectPermissions>`).join("");
|
|
2029
|
+
const fldPerms = (params.fieldPermissions ?? []).map(fp => `
|
|
2030
|
+
<met:fieldPermissions>
|
|
2031
|
+
<met:field>${x(fp.field)}</met:field>
|
|
2032
|
+
<met:readable>${fp.readable ? "true" : "false"}</met:readable>
|
|
2033
|
+
<met:editable>${fp.editable ? "true" : "false"}</met:editable>
|
|
2034
|
+
</met:fieldPermissions>`).join("");
|
|
2035
|
+
const xml = `<met:metadata xsi:type="met:MutingPermissionSet" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2036
|
+
<met:fullName>${x(params.mutingPermSetName)}</met:fullName>
|
|
2037
|
+
<met:label>${x(params.label)}</met:label>
|
|
2038
|
+
${params.permissionSetGroup ? `<met:permissionSetGroup>${x(params.permissionSetGroup)}</met:permissionSetGroup>` : ""}
|
|
2039
|
+
${objPerms}
|
|
2040
|
+
${fldPerms}
|
|
2041
|
+
</met:metadata>`;
|
|
2042
|
+
return await upsertMetadata(auth, xml);
|
|
2043
|
+
} catch (err) {
|
|
2044
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
export async function setFieldLevelSecurity(auth, params) {
|
|
2048
|
+
try {
|
|
2049
|
+
const results = [];
|
|
2050
|
+
const profileXmls = (params.profiles ?? []).map(profileName => `<met:metadata xsi:type="met:Profile" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2051
|
+
<met:fullName>${x(profileName)}</met:fullName>
|
|
2052
|
+
<met:fieldPermissions>
|
|
2053
|
+
<met:field>${x(params.fieldApiName)}</met:field>
|
|
2054
|
+
<met:readable>${params.readable ? "true" : "false"}</met:readable>
|
|
2055
|
+
<met:editable>${params.editable ? "true" : "false"}</met:editable>
|
|
2056
|
+
</met:fieldPermissions>
|
|
2057
|
+
</met:metadata>`);
|
|
2058
|
+
const psXmls = (params.permissionSets ?? []).map(psName => `<met:metadata xsi:type="met:PermissionSet" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2059
|
+
<met:fullName>${x(psName)}</met:fullName>
|
|
2060
|
+
<met:fieldPermissions>
|
|
2061
|
+
<met:field>${x(params.fieldApiName)}</met:field>
|
|
2062
|
+
<met:readable>${params.readable ? "true" : "false"}</met:readable>
|
|
2063
|
+
<met:editable>${params.editable ? "true" : "false"}</met:editable>
|
|
2064
|
+
</met:fieldPermissions>
|
|
2065
|
+
</met:metadata>`);
|
|
2066
|
+
for (const xml of [...profileXmls, ...psXmls]) {
|
|
2067
|
+
const result = await upsertMetadata(auth, xml);
|
|
2068
|
+
results.push(result);
|
|
2069
|
+
}
|
|
2070
|
+
const allOk = results.every(r => r.success);
|
|
2071
|
+
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("; ")}` };
|
|
2072
|
+
} catch (err) {
|
|
2073
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
export async function setOrgWideDefaults(auth, params) {
|
|
2077
|
+
try {
|
|
2078
|
+
const xml = `<met:metadata xsi:type="met:CustomObject" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2079
|
+
<met:fullName>${x(params.objectApiName)}</met:fullName>
|
|
2080
|
+
<met:sharingModel>${x(params.defaultInternal)}</met:sharingModel>
|
|
2081
|
+
${params.defaultExternal ? `<met:externalSharingModel>${x(params.defaultExternal)}</met:externalSharingModel>` : ""}
|
|
2082
|
+
</met:metadata>`;
|
|
2083
|
+
return await upsertMetadata(auth, xml);
|
|
2084
|
+
} catch (err) {
|
|
2085
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
export async function cloneProfile(auth, params) {
|
|
2089
|
+
try {
|
|
2090
|
+
const readXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>Profile</met:type><met:fullNames>${x(params.cloneFrom)}</met:fullNames></met:readMetadata>`);
|
|
2091
|
+
const recordMatch = readXml.match(/<records>([\s\S]*?)<\/records>/i);
|
|
2092
|
+
if (!recordMatch) return { success: false, message: `Profile '${params.cloneFrom}' not found.` };
|
|
2093
|
+
let profileXml = recordMatch[1]
|
|
2094
|
+
.replace(/<fullName>[^<]*<\/fullName>/i, `<fullName>${x(params.profileName)}</fullName>`)
|
|
2095
|
+
.replace(/<userLicense>[^<]*<\/userLicense>/i, "");
|
|
2096
|
+
const xml = `<met:metadata xsi:type="met:Profile" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2097
|
+
<met:fullName>${x(params.profileName)}</met:fullName>
|
|
2098
|
+
${profileXml}
|
|
2099
|
+
</met:metadata>`;
|
|
2100
|
+
return await upsertMetadata(auth, xml);
|
|
2101
|
+
} catch (err) {
|
|
2102
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
export async function updateProfile(auth, params) {
|
|
2106
|
+
try {
|
|
2107
|
+
const objPerms = (params.objectPermissions ?? []).map(op => `
|
|
2108
|
+
<met:objectPermissions>
|
|
2109
|
+
<met:object>${x(op.object)}</met:object>
|
|
2110
|
+
${op.allowCreate ? "<met:allowCreate>true</met:allowCreate>" : ""}
|
|
2111
|
+
${op.allowRead ? "<met:allowRead>true</met:allowRead>" : ""}
|
|
2112
|
+
${op.allowEdit ? "<met:allowEdit>true</met:allowEdit>" : ""}
|
|
2113
|
+
${op.allowDelete ? "<met:allowDelete>true</met:allowDelete>" : ""}
|
|
2114
|
+
${op.viewAllRecords ? "<met:viewAllRecords>true</met:viewAllRecords>" : ""}
|
|
2115
|
+
${op.modifyAllRecords ? "<met:modifyAllRecords>true</met:modifyAllRecords>" : ""}
|
|
2116
|
+
</met:objectPermissions>`).join("");
|
|
2117
|
+
const fldPerms = (params.fieldPermissions ?? []).map(fp => `
|
|
2118
|
+
<met:fieldPermissions>
|
|
2119
|
+
<met:field>${x(fp.field)}</met:field>
|
|
2120
|
+
<met:readable>${fp.readable ? "true" : "false"}</met:readable>
|
|
2121
|
+
<met:editable>${fp.editable ? "true" : "false"}</met:editable>
|
|
2122
|
+
</met:fieldPermissions>`).join("");
|
|
2123
|
+
const tabVis = (params.tabVisibilities ?? []).map(tv => `
|
|
2124
|
+
<met:tabVisibilities>
|
|
2125
|
+
<met:tab>${x(tv.tab)}</met:tab>
|
|
2126
|
+
<met:visibility>${x(tv.visibility)}</met:visibility>
|
|
2127
|
+
</met:tabVisibilities>`).join("");
|
|
2128
|
+
const appVis = (params.applicationVisibilities ?? []).map(av => `
|
|
2129
|
+
<met:applicationVisibilities>
|
|
2130
|
+
<met:application>${x(av.application)}</met:application>
|
|
2131
|
+
<met:default>${av.default ? "true" : "false"}</met:default>
|
|
2132
|
+
<met:visible>${av.visible ? "true" : "false"}</met:visible>
|
|
2133
|
+
</met:applicationVisibilities>`).join("");
|
|
2134
|
+
const xml = `<met:metadata xsi:type="met:Profile" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2135
|
+
<met:fullName>${x(params.profileName)}</met:fullName>
|
|
2136
|
+
${objPerms}
|
|
2137
|
+
${fldPerms}
|
|
2138
|
+
${tabVis}
|
|
2139
|
+
${appVis}
|
|
2140
|
+
</met:metadata>`;
|
|
2141
|
+
return await upsertMetadata(auth, xml);
|
|
2142
|
+
} catch (err) {
|
|
2143
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
// UI
|
|
2147
|
+
export async function updatePageLayout(auth, params) {
|
|
2148
|
+
try {
|
|
2149
|
+
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>([\s\S]*?)<\/records>/i);
|
|
2151
|
+
if (!recordMatch) return { success: false, message: `Layout '${params.layoutName}' not found.` };
|
|
2152
|
+
let inner = recordMatch[1];
|
|
2153
|
+
if (params.label) inner = inner.replace(/<fullName>[^<]*<\/fullName>/i, `<fullName>${x(params.objectApiName)}-${x(params.label)}</fullName>`);
|
|
2154
|
+
const xml = `<met:metadata xsi:type="met:Layout" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2155
|
+
${inner}
|
|
2156
|
+
</met:metadata>`;
|
|
2157
|
+
return await upsertMetadata(auth, xml);
|
|
2158
|
+
} catch (err) {
|
|
2159
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
export async function assignPageLayout(auth, params) {
|
|
2163
|
+
try {
|
|
2164
|
+
const layoutAssignmentsXml = params.profiles.map(profile => `
|
|
2165
|
+
<met:layoutAssignments>
|
|
2166
|
+
<met:layout>${x(params.objectApiName)}-${x(params.layoutName)}</met:layout>
|
|
2167
|
+
<met:profile>${x(profile)}</met:profile>
|
|
2168
|
+
</met:layoutAssignments>`).join("");
|
|
2169
|
+
const xml = `<met:metadata xsi:type="met:Profile" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2170
|
+
<met:fullName>${x(params.profiles[0])}</met:fullName>
|
|
2171
|
+
${layoutAssignmentsXml}
|
|
2172
|
+
</met:metadata>`;
|
|
2173
|
+
return await upsertMetadata(auth, xml);
|
|
2174
|
+
} catch (err) {
|
|
2175
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
export async function createFlexiPage(auth, params) {
|
|
2179
|
+
try {
|
|
2180
|
+
const regionXml = (params.regions ?? []).map(r => `
|
|
2181
|
+
<met:flexiPageRegions>
|
|
2182
|
+
<met:name>${x(r.name)}</met:name>
|
|
2183
|
+
<met:type>${x(r.type)}</met:type>
|
|
2184
|
+
${(r.components ?? []).map(c => `
|
|
2185
|
+
<met:itemInstances>
|
|
2186
|
+
<met:componentName>${x(c.name)}</met:componentName>
|
|
2187
|
+
${(c.properties ?? []).map(p => `
|
|
2188
|
+
<met:componentInstanceProperties>
|
|
2189
|
+
<met:name>${x(p.name)}</met:name>
|
|
2190
|
+
<met:value>${x(p.value)}</met:value>
|
|
2191
|
+
</met:componentInstanceProperties>`).join("")}
|
|
2192
|
+
</met:itemInstances>`).join("")}
|
|
2193
|
+
</met:flexiPageRegions>`).join("");
|
|
2194
|
+
const xml = `<met:metadata xsi:type="met:FlexiPage" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2195
|
+
<met:fullName>${x(params.pageName)}</met:fullName>
|
|
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>` : ""}
|
|
2199
|
+
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
2200
|
+
${regionXml}
|
|
2201
|
+
</met:metadata>`;
|
|
2202
|
+
return await upsertMetadata(auth, xml);
|
|
2203
|
+
} catch (err) {
|
|
2204
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
export async function updateFlexiPage(auth, params) {
|
|
2208
|
+
try {
|
|
2209
|
+
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>([\s\S]*?)<\/records>/i);
|
|
2211
|
+
if (!recordMatch) return { success: false, message: `FlexiPage '${params.pageName}' not found.` };
|
|
2212
|
+
let inner = recordMatch[1];
|
|
2213
|
+
if (params.label) inner = inner.replace(/<masterLabel>[^<]*<\/masterLabel>/i, `<masterLabel>${x(params.label)}</masterLabel>`);
|
|
2214
|
+
const xml = `<met:metadata xsi:type="met:FlexiPage" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2215
|
+
${inner}
|
|
2216
|
+
</met:metadata>`;
|
|
2217
|
+
return await upsertMetadata(auth, xml);
|
|
2218
|
+
} catch (err) {
|
|
2219
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
export async function assignFlexiPage(auth, params) {
|
|
2223
|
+
try {
|
|
2224
|
+
const client = createClient(auth);
|
|
2225
|
+
const resp = await client.post(`/connect/communities/${x(params.siteName)}/flexipages/${x(params.pageName)}/assignments`, { profiles: params.profiles ?? [], profileIds: [] });
|
|
2226
|
+
return { success: true, message: `FlexiPage '${params.pageName}' assigned successfully.`, fullName: resp.data?.id ?? params.pageName, created: false };
|
|
2227
|
+
} catch (err) {
|
|
2228
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
export async function assignCompactLayout(auth, params) {
|
|
2232
|
+
try {
|
|
2233
|
+
const xml = `<met:metadata xsi:type="met:CustomObject" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2234
|
+
<met:fullName>${x(params.objectApiName)}</met:fullName>
|
|
2235
|
+
<met:compactLayoutAssignment>${x(params.compactLayoutName)}</met:compactLayoutAssignment>
|
|
2236
|
+
</met:metadata>`;
|
|
2237
|
+
return await upsertMetadata(auth, xml);
|
|
2238
|
+
} catch (err) {
|
|
2239
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
// Automation
|
|
2243
|
+
export async function activateFlow(auth, params) {
|
|
2244
|
+
try {
|
|
2245
|
+
const toolingBase = `${auth.instanceUrl}/services/data/v${API_VERSION}/tooling`;
|
|
2246
|
+
const headers = { Authorization: `Bearer ${auth.accessToken}`, "Content-Type": "application/json" };
|
|
2247
|
+
const defResp = await fetchWithTimeout(`${toolingBase}/query?q=${encodeURIComponent(`SELECT Id, ActiveVersionId FROM FlowDefinition WHERE DeveloperName = '${params.flowApiName.replace(/'/g, "\\'")}'`)}`, { method: "GET", headers }, 30_000);
|
|
2248
|
+
if (!defResp.ok) return { success: false, message: `Could not query FlowDefinition: HTTP ${defResp.status}` };
|
|
2249
|
+
const defData = await defResp.json();
|
|
2250
|
+
const defId = defData.records?.[0]?.Id;
|
|
2251
|
+
if (!defId) return { success: false, message: `Flow '${params.flowApiName}' not found.` };
|
|
2252
|
+
// Find latest version
|
|
2253
|
+
const verResp = await fetchWithTimeout(`${toolingBase}/query?q=${encodeURIComponent(`SELECT Id, VersionNumber FROM Flow WHERE Definition.Id = '${defId}' ORDER BY VersionNumber DESC LIMIT 1`)}`, { method: "GET", headers }, 30_000);
|
|
2254
|
+
if (!verResp.ok) return { success: false, message: `Could not query Flow versions: HTTP ${verResp.status}` };
|
|
2255
|
+
const verData = await verResp.json();
|
|
2256
|
+
const verId = verData.records?.[0]?.Id;
|
|
2257
|
+
if (!verId) return { success: false, message: `No versions found for flow '${params.flowApiName}'.` };
|
|
2258
|
+
const patchResp = await fetchWithTimeout(`${toolingBase}/sobjects/FlowDefinition/${defId}`, { method: "PATCH", headers, body: JSON.stringify({ Metadata: { activeVersionNumber: verData.records[0].VersionNumber } }) }, 30_000);
|
|
2259
|
+
if (!patchResp.ok) {
|
|
2260
|
+
const errText = await patchResp.text().catch(() => "");
|
|
2261
|
+
return { success: false, message: `Flow activation failed: ${errText.slice(0, 200)}` };
|
|
2262
|
+
}
|
|
2263
|
+
return { success: true, fullName: params.flowApiName, created: false, message: `Flow '${params.flowApiName}' activated successfully.` };
|
|
2264
|
+
} catch (err) {
|
|
2265
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
export async function createQuickAction(auth, params) {
|
|
2269
|
+
try {
|
|
2270
|
+
const xml = `<met:metadata xsi:type="met:QuickAction" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2271
|
+
<met:fullName>${x(params.objectApiName ? `${params.objectApiName}.${params.actionName}` : params.actionName)}</met:fullName>
|
|
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>` : ""}
|
|
2275
|
+
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
2276
|
+
${params.flowDefinition ? `<met:flowDefinition>${x(params.flowDefinition)}</met:flowDefinition>` : ""}
|
|
2277
|
+
${params.optionsCreateFeedItem !== undefined ? `<met:optionsCreateFeedItem>${params.optionsCreateFeedItem}</met:optionsCreateFeedItem>` : ""}
|
|
2278
|
+
</met:metadata>`;
|
|
2279
|
+
return await upsertMetadata(auth, xml);
|
|
2280
|
+
} catch (err) {
|
|
2281
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
// Apex & LWC
|
|
2285
|
+
export async function getApexClass(auth, params) {
|
|
2286
|
+
try {
|
|
2287
|
+
const toolingBase = `${auth.instanceUrl}/services/data/v${API_VERSION}/tooling`;
|
|
2288
|
+
const headers = { Authorization: `Bearer ${auth.accessToken}`, "Content-Type": "application/json" };
|
|
2289
|
+
const safeName = params.className.replace(/'/g, "\\'");
|
|
2290
|
+
const resp = await fetchWithTimeout(`${toolingBase}/query?q=${encodeURIComponent(`SELECT Id, Name, Body, ApiVersion, Status, LengthWithoutComments FROM ApexClass WHERE Name = '${safeName}'`)}`, { method: "GET", headers }, 30_000);
|
|
2291
|
+
if (!resp.ok) return { success: false, message: `Tooling API error: HTTP ${resp.status}` };
|
|
2292
|
+
const data = await resp.json();
|
|
2293
|
+
if (!data.records?.length) return { success: false, message: `Apex class '${params.className}' not found.` };
|
|
2294
|
+
const cls = data.records[0];
|
|
2295
|
+
return { success: true, message: `Found Apex class '${params.className}'.`, id: cls.Id, name: cls.Name, body: cls.Body, apiVersion: cls.ApiVersion, status: cls.Status, length: cls.LengthWithoutComments };
|
|
2296
|
+
} catch (err) {
|
|
2297
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
export async function getCodeCoverage(auth, params) {
|
|
2301
|
+
try {
|
|
2302
|
+
const toolingBase = `${auth.instanceUrl}/services/data/v${API_VERSION}/tooling`;
|
|
2303
|
+
const headers = { Authorization: `Bearer ${auth.accessToken}`, "Content-Type": "application/json" };
|
|
2304
|
+
const safeName = (params.className ?? "").replace(/'/g, "\\'");
|
|
2305
|
+
const whereClause = safeName ? ` WHERE ApexClassOrTrigger.Name = '${safeName}'` : "";
|
|
2306
|
+
const resp = await fetchWithTimeout(`${toolingBase}/query?q=${encodeURIComponent(`SELECT ApexClassOrTriggerId, ApexClassOrTrigger.Name, NumLinesCovered, NumLinesUncovered FROM ApexCodeCoverageAggregate${whereClause} ORDER BY NumLinesCovered DESC LIMIT ${params.limit ?? 20}`)}`, { method: "GET", headers }, 30_000);
|
|
2307
|
+
if (!resp.ok) return { success: false, message: `Tooling API error: HTTP ${resp.status}` };
|
|
2308
|
+
const data = await resp.json();
|
|
2309
|
+
const records = (data.records ?? []).map(r => {
|
|
2310
|
+
const total = (r.NumLinesCovered ?? 0) + (r.NumLinesUncovered ?? 0);
|
|
2311
|
+
const pct = total > 0 ? Math.round((r.NumLinesCovered / total) * 100) : 0;
|
|
2312
|
+
return { name: r.ApexClassOrTrigger?.Name ?? r.ApexClassOrTriggerId, covered: r.NumLinesCovered, uncovered: r.NumLinesUncovered, total, percentage: pct };
|
|
2313
|
+
});
|
|
2314
|
+
return { success: true, records, message: `Code coverage for ${records.length} class(es).` };
|
|
2315
|
+
} catch (err) {
|
|
2316
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
// Reports
|
|
2320
|
+
export async function createReport(auth, params) {
|
|
2321
|
+
try {
|
|
2322
|
+
const filtersXml = (params.filters ?? []).map(f => `
|
|
2323
|
+
<met:reportFilters>
|
|
2324
|
+
<met:column>${x(f.column)}</met:column>
|
|
2325
|
+
<met:operator>${x(f.operator)}</met:operator>
|
|
2326
|
+
<met:value>${x(f.value)}</met:value>
|
|
2327
|
+
</met:reportFilters>`).join("");
|
|
2328
|
+
const columnsXml = (params.columns ?? []).map(c => `<met:reportColumns><met:field>${x(c)}</met:field><met:aggregateTypes>Sum</met:aggregateTypes></met:reportColumns>`).join("\n ");
|
|
2329
|
+
const xml = `<met:metadata xsi:type="met:Report" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2330
|
+
<met:fullName>${x(params.reportName)}</met:fullName>
|
|
2331
|
+
<met:name>${x(params.label)}</met:name>
|
|
2332
|
+
<met:reportType>${x(params.reportType)}</met:reportType>
|
|
2333
|
+
<met:format>${x(params.format ?? "Tabular")}</met:format>
|
|
2334
|
+
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
2335
|
+
${params.folderId ? `<met:folder>${x(params.folderId)}</met:folder>` : ""}
|
|
2336
|
+
${columnsXml}
|
|
2337
|
+
${filtersXml}
|
|
2338
|
+
<met:showDetails>true</met:showDetails>
|
|
2339
|
+
</met:metadata>`;
|
|
2340
|
+
return await upsertMetadata(auth, xml);
|
|
2341
|
+
} catch (err) {
|
|
2342
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
export async function updateDashboard(auth, params) {
|
|
2346
|
+
try {
|
|
2347
|
+
const readXml = await callMetadataSoap(auth, "readMetadata", `<met:readMetadata><met:type>Dashboard</met:type><met:fullNames>${x(params.dashboardName)}</met:fullNames></met:readMetadata>`);
|
|
2348
|
+
const recordMatch = readXml.match(/<records>([\s\S]*?)<\/records>/i);
|
|
2349
|
+
if (!recordMatch) return { success: false, message: `Dashboard '${params.dashboardName}' not found.` };
|
|
2350
|
+
let inner = recordMatch[1];
|
|
2351
|
+
if (params.title) inner = inner.replace(/<title>[^<]*<\/title>/i, `<title>${x(params.title)}</title>`);
|
|
2352
|
+
if (params.description) inner = inner.replace(/<description>[^<]*<\/description>/i, `<description>${x(params.description)}</description>`);
|
|
2353
|
+
const xml = `<met:metadata xsi:type="met:Dashboard" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2354
|
+
${inner}
|
|
2355
|
+
</met:metadata>`;
|
|
2356
|
+
return await upsertMetadata(auth, xml);
|
|
2357
|
+
} catch (err) {
|
|
2358
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
export async function createReportFolder(auth, params) {
|
|
2362
|
+
try {
|
|
2363
|
+
const xml = `<met:metadata xsi:type="met:ReportFolder" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2364
|
+
<met:fullName>${x(params.folderName)}</met:fullName>
|
|
2365
|
+
<met:name>${x(params.label)}</met:name>
|
|
2366
|
+
<met:accessType>${x(params.accessType ?? "Hidden")}</met:accessType>
|
|
2367
|
+
${params.description ? `<met:description>${x(params.description)}</met:description>` : ""}
|
|
2368
|
+
</met:metadata>`;
|
|
2369
|
+
return await upsertMetadata(auth, xml);
|
|
2370
|
+
} catch (err) {
|
|
2371
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
export async function shareReportFolder(auth, params) {
|
|
2375
|
+
try {
|
|
2376
|
+
const sharesXml = params.shares.map(s => `
|
|
2377
|
+
<met:folderShares>
|
|
2378
|
+
<met:accessLevel>${x(s.accessLevel)}</met:accessLevel>
|
|
2379
|
+
<met:sharedTo>${x(s.sharedTo)}</met:sharedTo>
|
|
2380
|
+
<met:sharedToType>${x(s.sharedToType)}</met:sharedToType>
|
|
2381
|
+
</met:folderShares>`).join("");
|
|
2382
|
+
const folderType = params.folderType ?? "ReportFolder";
|
|
2383
|
+
const xml = `<met:metadata xsi:type="met:${folderType}" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2384
|
+
<met:fullName>${x(params.folderName)}</met:fullName>
|
|
2385
|
+
${sharesXml}
|
|
2386
|
+
</met:metadata>`;
|
|
2387
|
+
return await upsertMetadata(auth, xml);
|
|
2388
|
+
} catch (err) {
|
|
2389
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
// Users & Data
|
|
2393
|
+
export async function createUser(auth, params) {
|
|
2394
|
+
try {
|
|
2395
|
+
const client = createClient(auth);
|
|
2396
|
+
// Resolve profile ID
|
|
2397
|
+
const safeProfName = params.profileName.replace(/'/g, "\\'");
|
|
2398
|
+
const profResp = await client.get(`/query?q=${encodeURIComponent(`SELECT Id FROM Profile WHERE Name = '${safeProfName}'`)}`);
|
|
2399
|
+
const profileId = profResp.data.records?.[0]?.Id;
|
|
2400
|
+
if (!profileId) return { success: false, message: `Profile '${params.profileName}' not found.` };
|
|
2401
|
+
const userObj = { Username: params.username, LastName: params.lastName, FirstName: params.firstName ?? "", Email: params.email, Alias: params.alias ?? params.lastName.slice(0, 8), ProfileId: profileId, TimeZoneSidKey: params.timeZone ?? "America/Los_Angeles", LocaleSidKey: params.locale ?? "en_US", EmailEncodingKey: params.emailEncoding ?? "UTF-8", LanguageLocaleKey: params.language ?? "en_US" };
|
|
2402
|
+
if (params.roleApiName) {
|
|
2403
|
+
const safeRole = params.roleApiName.replace(/'/g, "\\'");
|
|
2404
|
+
const roleResp = await client.get(`/query?q=${encodeURIComponent(`SELECT Id FROM UserRole WHERE DeveloperName = '${safeRole}'`)}`);
|
|
2405
|
+
const roleId = roleResp.data.records?.[0]?.Id;
|
|
2406
|
+
if (roleId) userObj["UserRoleId"] = roleId;
|
|
2407
|
+
}
|
|
2408
|
+
const resp = await client.post("/sobjects/User", userObj);
|
|
2409
|
+
return { success: true, fullName: resp.data.id, created: true, message: `User '${params.username}' created with ID ${resp.data.id}.` };
|
|
2410
|
+
} catch (err) {
|
|
2411
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
export async function updateUser(auth, params) {
|
|
2415
|
+
try {
|
|
2416
|
+
const client = createClient(auth);
|
|
2417
|
+
const safeUsername = params.username.replace(/'/g, "\\'");
|
|
2418
|
+
const uResp = await client.get(`/query?q=${encodeURIComponent(`SELECT Id FROM User WHERE Username = '${safeUsername}'`)}`);
|
|
2419
|
+
const userId = uResp.data.records?.[0]?.Id;
|
|
2420
|
+
if (!userId) return { success: false, message: `User '${params.username}' not found.` };
|
|
2421
|
+
const updates = {};
|
|
2422
|
+
if (params.firstName !== undefined) updates["FirstName"] = params.firstName;
|
|
2423
|
+
if (params.lastName !== undefined) updates["LastName"] = params.lastName;
|
|
2424
|
+
if (params.email !== undefined) updates["Email"] = params.email;
|
|
2425
|
+
if (params.isActive !== undefined) updates["IsActive"] = params.isActive;
|
|
2426
|
+
if (params.title !== undefined) updates["Title"] = params.title;
|
|
2427
|
+
if (params.department !== undefined) updates["Department"] = params.department;
|
|
2428
|
+
if (params.phone !== undefined) updates["Phone"] = params.phone;
|
|
2429
|
+
await client.patch(`/sobjects/User/${userId}`, updates);
|
|
2430
|
+
return { success: true, fullName: userId, created: false, message: `User '${params.username}' updated successfully.` };
|
|
2431
|
+
} catch (err) {
|
|
2432
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
export async function assignQueueMember(auth, params) {
|
|
2436
|
+
try {
|
|
2437
|
+
const client = createClient(auth);
|
|
2438
|
+
const safeQueue = params.queueApiName.replace(/'/g, "\\'");
|
|
2439
|
+
const qResp = await client.get(`/query?q=${encodeURIComponent(`SELECT Id FROM Group WHERE DeveloperName = '${safeQueue}' AND Type = 'Queue'`)}`);
|
|
2440
|
+
const queueId = qResp.data.records?.[0]?.Id;
|
|
2441
|
+
if (!queueId) return { success: false, message: `Queue '${params.queueApiName}' not found.` };
|
|
2442
|
+
const safeUsername = params.username.replace(/'/g, "\\'");
|
|
2443
|
+
const uResp = await client.get(`/query?q=${encodeURIComponent(`SELECT Id FROM User WHERE Username = '${safeUsername}'`)}`);
|
|
2444
|
+
const userId = uResp.data.records?.[0]?.Id;
|
|
2445
|
+
if (!userId) return { success: false, message: `User '${params.username}' not found.` };
|
|
2446
|
+
const resp = await client.post("/sobjects/GroupMember", { GroupId: queueId, UserOrGroupId: userId });
|
|
2447
|
+
return { success: true, fullName: resp.data.id, created: true, message: `User '${params.username}' added to queue '${params.queueApiName}'.` };
|
|
2448
|
+
} catch (err) {
|
|
2449
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
export async function createPublicGroup(auth, params) {
|
|
2453
|
+
try {
|
|
2454
|
+
const client = createClient(auth);
|
|
2455
|
+
const resp = await client.post("/sobjects/Group", { Name: params.groupName, DeveloperName: params.developerName ?? params.groupName.replace(/\s+/g, "_"), Type: "Regular", DoesIncludeBosses: params.includeManagers ?? false });
|
|
2456
|
+
return { success: true, fullName: resp.data.id, created: true, message: `Public group '${params.groupName}' created with ID ${resp.data.id}.` };
|
|
2457
|
+
} catch (err) {
|
|
2458
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
export async function queryRecords(auth, params) {
|
|
2462
|
+
try {
|
|
2463
|
+
const client = createClient(auth);
|
|
2464
|
+
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}`;
|
|
2465
|
+
const resp = await client.get(`/query?q=${encodeURIComponent(soql)}`);
|
|
2466
|
+
return { success: true, totalSize: resp.data.totalSize, records: resp.data.records, message: `${resp.data.totalSize ?? 0} record(s) returned.` };
|
|
2467
|
+
} catch (err) {
|
|
2468
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
export async function createRecord(auth, params) {
|
|
2472
|
+
try {
|
|
2473
|
+
const client = createClient(auth);
|
|
2474
|
+
const resp = await client.post(`/sobjects/${params.objectApiName}`, params.fields);
|
|
2475
|
+
return { success: true, fullName: resp.data.id, created: true, message: `${params.objectApiName} record created with ID ${resp.data.id}.` };
|
|
2476
|
+
} catch (err) {
|
|
2477
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
export async function updateRecord(auth, params) {
|
|
2481
|
+
try {
|
|
2482
|
+
const client = createClient(auth);
|
|
2483
|
+
await client.patch(`/sobjects/${params.objectApiName}/${params.recordId}`, params.fields);
|
|
2484
|
+
return { success: true, fullName: params.recordId, created: false, message: `${params.objectApiName} record ${params.recordId} updated successfully.` };
|
|
2485
|
+
} catch (err) {
|
|
2486
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
export async function bulkImportRecords(auth, params) {
|
|
2490
|
+
try {
|
|
2491
|
+
const baseUrl = `${auth.instanceUrl}/services/data/v${API_VERSION}`;
|
|
2492
|
+
const headers = { Authorization: `Bearer ${auth.accessToken}`, "Content-Type": "application/json" };
|
|
2493
|
+
// Create Bulk API 2.0 job
|
|
2494
|
+
const jobResp = await fetchWithTimeout(`${baseUrl}/jobs/ingest`, { method: "POST", headers, body: JSON.stringify({ object: params.objectApiName, operation: params.operation ?? "insert", contentType: "CSV", lineEnding: "LF" }) }, 30_000);
|
|
2495
|
+
if (!jobResp.ok) return { success: false, message: `Failed to create bulk job: HTTP ${jobResp.status}` };
|
|
2496
|
+
const jobData = await jobResp.json();
|
|
2497
|
+
const jobId = jobData.id;
|
|
2498
|
+
// Upload CSV data
|
|
2499
|
+
const csvHeaders = { Authorization: `Bearer ${auth.accessToken}`, "Content-Type": "text/csv" };
|
|
2500
|
+
const uploadResp = await fetchWithTimeout(`${baseUrl}/jobs/ingest/${jobId}/batches`, { method: "PUT", headers: csvHeaders, body: params.csvData }, 60_000);
|
|
2501
|
+
if (!uploadResp.ok) return { success: false, message: `Failed to upload CSV: HTTP ${uploadResp.status}` };
|
|
2502
|
+
// Close job
|
|
2503
|
+
const closeResp = await fetchWithTimeout(`${baseUrl}/jobs/ingest/${jobId}`, { method: "PATCH", headers, body: JSON.stringify({ state: "UploadComplete" }) }, 30_000);
|
|
2504
|
+
if (!closeResp.ok) return { success: false, message: `Failed to close job: HTTP ${closeResp.status}` };
|
|
2505
|
+
// Poll for completion
|
|
2506
|
+
const maxWait = 120_000;
|
|
2507
|
+
const start = Date.now();
|
|
2508
|
+
while (Date.now() - start < maxWait) {
|
|
2509
|
+
await new Promise(r => setTimeout(r, 5_000));
|
|
2510
|
+
const statusResp = await fetchWithTimeout(`${baseUrl}/jobs/ingest/${jobId}`, { method: "GET", headers }, 30_000);
|
|
2511
|
+
if (!statusResp.ok) continue;
|
|
2512
|
+
const statusData = await statusResp.json();
|
|
2513
|
+
if (["JobComplete", "Failed", "Aborted"].includes(statusData.state)) {
|
|
2514
|
+
return { success: statusData.state === "JobComplete", fullName: jobId, created: true, message: `Bulk job ${jobId}: ${statusData.state}. Records processed: ${statusData.numberRecordsProcessed ?? 0}, failed: ${statusData.numberRecordsFailed ?? 0}.` };
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
return { success: false, message: `Bulk job ${jobId} timed out. Check job status manually.` };
|
|
2518
|
+
} catch (err) {
|
|
2519
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
// Integrations
|
|
2523
|
+
export async function createOutboundMessage(auth, params) {
|
|
2524
|
+
try {
|
|
2525
|
+
const fieldsXml = (params.fields ?? []).map(f => `<met:fields>${x(f)}</met:fields>`).join("\n ");
|
|
2526
|
+
const xml = `<met:metadata xsi:type="met:WorkflowOutboundMessage" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2527
|
+
<met:fullName>${x(params.objectApiName)}.${x(params.messageName)}</met:fullName>
|
|
2528
|
+
<met:apiVersion>${API_VERSION}</met:apiVersion>
|
|
2529
|
+
<met:description>${x(params.description ?? "")}</met:description>
|
|
2530
|
+
<met:endpointUrl>${x(params.endpointUrl)}</met:endpointUrl>
|
|
2531
|
+
<met:fields>Id</met:fields>
|
|
2532
|
+
${fieldsXml}
|
|
2533
|
+
<met:includeSessionId>${params.includeSessionId ? "true" : "false"}</met:includeSessionId>
|
|
2534
|
+
<met:integrationUser>${x(params.integrationUser ?? "admin@example.com")}</met:integrationUser>
|
|
2535
|
+
<met:name>${x(params.label)}</met:name>
|
|
2536
|
+
<met:object>${x(params.objectApiName)}</met:object>
|
|
2537
|
+
<met:useDeadLetterQueue>false</met:useDeadLetterQueue>
|
|
2538
|
+
</met:metadata>`;
|
|
2539
|
+
return await upsertMetadata(auth, xml);
|
|
2540
|
+
} catch (err) {
|
|
2541
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
export async function createAuthProvider(auth, params) {
|
|
2545
|
+
try {
|
|
2546
|
+
const xml = `<met:metadata xsi:type="met:AuthProvider" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2547
|
+
<met:fullName>${x(params.providerName)}</met:fullName>
|
|
2548
|
+
<met:friendlyName>${x(params.label)}</met:friendlyName>
|
|
2549
|
+
<met:providerType>${x(params.providerType)}</met:providerType>
|
|
2550
|
+
${params.consumerKey ? `<met:consumerKey>${x(params.consumerKey)}</met:consumerKey>` : ""}
|
|
2551
|
+
${params.consumerSecret ? `<met:consumerSecret>${x(params.consumerSecret)}</met:consumerSecret>` : ""}
|
|
2552
|
+
${params.authorizeUrl ? `<met:authorizeUrl>${x(params.authorizeUrl)}</met:authorizeUrl>` : ""}
|
|
2553
|
+
${params.tokenUrl ? `<met:tokenUrl>${x(params.tokenUrl)}</met:tokenUrl>` : ""}
|
|
2554
|
+
${params.defaultScopes ? `<met:defaultScopes>${x(params.defaultScopes)}</met:defaultScopes>` : ""}
|
|
2555
|
+
${params.sendAccessTokenInHeader !== undefined ? `<met:sendAccessTokenInHeader>${params.sendAccessTokenInHeader}</met:sendAccessTokenInHeader>` : ""}
|
|
2556
|
+
${params.sendClientCredentialsInHeader !== undefined ? `<met:sendClientCredentialsInHeader>${params.sendClientCredentialsInHeader}</met:sendClientCredentialsInHeader>` : ""}
|
|
2557
|
+
<met:pluginId>${x(params.pluginClass ?? "")}</met:pluginId>
|
|
2558
|
+
</met:metadata>`;
|
|
2559
|
+
return await upsertMetadata(auth, xml);
|
|
2560
|
+
} catch (err) {
|
|
2561
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
// Deployment - listMetadataType already defined above (v2.1.4 SOAP helpers), re-export with message wrapper
|
|
2565
|
+
export async function listMetadataForTool(auth, metadataType) {
|
|
2566
|
+
const result = await listMetadataType(auth, metadataType);
|
|
2567
|
+
if (!result.success) return result;
|
|
2568
|
+
return { success: true, type: metadataType, totalSize: result.items.length, items: result.items, message: `${result.items.length} ${metadataType} component(s) found.` };
|
|
2569
|
+
}
|
|
2570
|
+
// Experience Cloud
|
|
2571
|
+
export async function publishExperienceSite(auth, params) {
|
|
2572
|
+
try {
|
|
2573
|
+
const client = createClient(auth);
|
|
2574
|
+
// Get communities list
|
|
2575
|
+
const commResp = await fetchWithTimeout(`${auth.instanceUrl}/services/data/v${API_VERSION}/connect/communities`, { method: "GET", headers: { Authorization: `Bearer ${auth.accessToken}`, "Content-Type": "application/json" } }, 30_000);
|
|
2576
|
+
if (!commResp.ok) return { success: false, message: `Could not retrieve communities: HTTP ${commResp.status}` };
|
|
2577
|
+
const commData = await commResp.json();
|
|
2578
|
+
const site = (commData.communities ?? []).find(c => c.name === params.siteName || c.urlPathPrefix === params.siteName);
|
|
2579
|
+
if (!site) return { success: false, message: `Experience site '${params.siteName}' not found.` };
|
|
2580
|
+
const pubResp = await fetchWithTimeout(`${auth.instanceUrl}/services/data/v${API_VERSION}/connect/communities/${site.id}/publish`, { method: "POST", headers: { Authorization: `Bearer ${auth.accessToken}`, "Content-Type": "application/json" }, body: JSON.stringify({}) }, 60_000);
|
|
2581
|
+
if (!pubResp.ok) {
|
|
2582
|
+
const errText = await pubResp.text().catch(() => "");
|
|
2583
|
+
return { success: false, message: `Publish failed: ${errText.slice(0, 200)}` };
|
|
2584
|
+
}
|
|
2585
|
+
return { success: true, fullName: site.id, created: false, message: `Experience site '${params.siteName}' published successfully.` };
|
|
2586
|
+
} catch (err) {
|
|
2587
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
export async function updateExperienceSite(auth, params) {
|
|
2591
|
+
try {
|
|
2592
|
+
const fields = [];
|
|
2593
|
+
if (params.description) fields.push(`<met:description>${x(params.description)}</met:description>`);
|
|
2594
|
+
if (params.guestProfile) fields.push(`<met:guestProfile>${x(params.guestProfile)}</met:guestProfile>`);
|
|
2595
|
+
if (params.allowGuestAccess !== undefined) fields.push(`<met:allowGuestApiAccess>${params.allowGuestAccess}</met:allowGuestApiAccess>`);
|
|
2596
|
+
const xml = `<met:metadata xsi:type="met:Network" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2597
|
+
<met:fullName>${x(params.siteName)}</met:fullName>
|
|
2598
|
+
${fields.join("\n ")}
|
|
2599
|
+
</met:metadata>`;
|
|
2600
|
+
return await upsertMetadata(auth, xml);
|
|
2601
|
+
} catch (err) {
|
|
2602
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
export async function createNavigationMenu(auth, params) {
|
|
2606
|
+
try {
|
|
2607
|
+
const itemsXml = (params.items ?? []).map((item, i) => `
|
|
2608
|
+
<met:navigationMenuItems>
|
|
2609
|
+
<met:label>${x(item.label)}</met:label>
|
|
2610
|
+
<met:position>${i + 1}</met:position>
|
|
2611
|
+
<met:publiclyAvailable>${item.publiclyAvailable !== false}</met:publiclyAvailable>
|
|
2612
|
+
<met:target>${x(item.target)}</met:target>
|
|
2613
|
+
<met:targetType>${x(item.targetType ?? "ExternalLink")}</met:targetType>
|
|
2614
|
+
<met:type>${x(item.type ?? "Url")}</met:type>
|
|
2615
|
+
</met:navigationMenuItems>`).join("");
|
|
2616
|
+
const xml = `<met:metadata xsi:type="met:NavigationMenu" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2617
|
+
<met:fullName>${x(params.menuName)}</met:fullName>
|
|
2618
|
+
<met:masterLabel>${x(params.label)}</met:masterLabel>
|
|
2619
|
+
<met:networkId>${x(params.siteName)}</met:networkId>
|
|
2620
|
+
${itemsXml}
|
|
2621
|
+
</met:metadata>`;
|
|
2622
|
+
return await upsertMetadata(auth, xml);
|
|
2623
|
+
} catch (err) {
|
|
2624
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
export async function createExperienceSiteMember(auth, params) {
|
|
2628
|
+
try {
|
|
2629
|
+
const profilesXml = params.profiles.map(p => `<met:networkMemberGroups><met:permissionSet>${x(p)}</met:permissionSet></met:networkMemberGroups>`).join("\n ");
|
|
2630
|
+
const xml = `<met:metadata xsi:type="met:Network" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2631
|
+
<met:fullName>${x(params.siteName)}</met:fullName>
|
|
2632
|
+
${profilesXml}
|
|
2633
|
+
</met:metadata>`;
|
|
2634
|
+
return await upsertMetadata(auth, xml);
|
|
2635
|
+
} catch (err) {
|
|
2636
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
export async function setExperienceSiteBranding(auth, params) {
|
|
2640
|
+
try {
|
|
2641
|
+
const propertiesXml = (params.properties ?? []).map(p => `
|
|
2642
|
+
<met:brandingProperties>
|
|
2643
|
+
<met:name>${x(p.name)}</met:name>
|
|
2644
|
+
<met:value>${x(p.value)}</met:value>
|
|
2645
|
+
</met:brandingProperties>`).join("");
|
|
2646
|
+
const xml = `<met:metadata xsi:type="met:BrandingSet" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
2647
|
+
<met:fullName>${x(params.brandingSetName)}</met:fullName>
|
|
2648
|
+
<met:masterLabel>${x(params.label ?? params.brandingSetName)}</met:masterLabel>
|
|
2649
|
+
${propertiesXml}
|
|
2650
|
+
</met:metadata>`;
|
|
2651
|
+
return await upsertMetadata(auth, xml);
|
|
2652
|
+
} catch (err) {
|
|
2653
|
+
return { success: false, message: sanitizeError(err instanceof Error ? err.message : String(err)) };
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
1530
2656
|
//# sourceMappingURL=salesforce.js.map
|