@lead-routing/cli 0.2.0 → 0.3.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/dist/index.js
CHANGED
|
@@ -5,6 +5,8 @@ import { Command } from "commander";
|
|
|
5
5
|
|
|
6
6
|
// src/commands/init.ts
|
|
7
7
|
import { promises as dns } from "dns";
|
|
8
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
9
|
+
import { join as join6 } from "path";
|
|
8
10
|
import { intro, outro, note as note4, log as log9, confirm as confirm2, cancel as cancel3, isCancel as isCancel4, password as promptPassword } from "@clack/prompts";
|
|
9
11
|
import chalk2 from "chalk";
|
|
10
12
|
|
|
@@ -170,8 +172,9 @@ async function collectConfig(opts = {}) {
|
|
|
170
172
|
const dbPassword = generateSecret(16);
|
|
171
173
|
const managedDb = !opts.externalDb;
|
|
172
174
|
const databaseUrl = opts.externalDb ?? `postgresql://leadrouting:${dbPassword}@postgres:5432/leadrouting`;
|
|
175
|
+
const redisPassword = generateSecret(16);
|
|
173
176
|
const managedRedis = !opts.externalRedis;
|
|
174
|
-
const redisUrl = opts.externalRedis ??
|
|
177
|
+
const redisUrl = opts.externalRedis ?? `redis://:${redisPassword}@redis:6379`;
|
|
175
178
|
note2("This creates the first admin user for the web app.", "Admin Account");
|
|
176
179
|
const adminEmail = await text2({
|
|
177
180
|
message: "Admin email address",
|
|
@@ -193,6 +196,7 @@ async function collectConfig(opts = {}) {
|
|
|
193
196
|
const sessionSecret = generateSecret(32);
|
|
194
197
|
const engineWebhookSecret = generateSecret(32);
|
|
195
198
|
const adminSecret = generateSecret(16);
|
|
199
|
+
const internalApiKey = generateSecret(32);
|
|
196
200
|
return {
|
|
197
201
|
appUrl: appUrl.trim().replace(/\/+$/, ""),
|
|
198
202
|
engineUrl: engineUrl.trim().replace(/\/+$/, ""),
|
|
@@ -204,13 +208,15 @@ async function collectConfig(opts = {}) {
|
|
|
204
208
|
dbPassword: managedDb ? dbPassword : "",
|
|
205
209
|
managedRedis,
|
|
206
210
|
redisUrl,
|
|
211
|
+
redisPassword: managedRedis ? redisPassword : "",
|
|
207
212
|
adminEmail,
|
|
208
213
|
adminPassword,
|
|
209
214
|
resendApiKey: "",
|
|
210
215
|
feedbackToEmail: "",
|
|
211
216
|
sessionSecret,
|
|
212
217
|
engineWebhookSecret,
|
|
213
|
-
adminSecret
|
|
218
|
+
adminSecret,
|
|
219
|
+
internalApiKey
|
|
214
220
|
};
|
|
215
221
|
}
|
|
216
222
|
|
|
@@ -243,14 +249,16 @@ function renderDockerCompose(c) {
|
|
|
243
249
|
timeout: 5s
|
|
244
250
|
retries: 10
|
|
245
251
|
` : "";
|
|
252
|
+
const redisPassword = c.redisPassword ?? "";
|
|
246
253
|
const redisService = c.managedRedis ? `
|
|
247
254
|
redis:
|
|
248
255
|
image: redis:7-alpine
|
|
249
|
-
restart: unless-stopped
|
|
256
|
+
restart: unless-stopped${redisPassword ? `
|
|
257
|
+
command: redis-server --requirepass ${redisPassword}` : ""}
|
|
250
258
|
volumes:
|
|
251
259
|
- redis_data:/data
|
|
252
260
|
healthcheck:
|
|
253
|
-
test: ["CMD", "redis-cli"
|
|
261
|
+
test: ["CMD-SHELL", "redis-cli${redisPassword ? ` -a ${redisPassword}` : ""} ping | grep PONG"]
|
|
254
262
|
interval: 5s
|
|
255
263
|
timeout: 3s
|
|
256
264
|
retries: 10
|
|
@@ -366,6 +374,9 @@ function renderEnvWeb(c) {
|
|
|
366
374
|
`ADMIN_EMAIL=${c.adminEmail}`,
|
|
367
375
|
`ADMIN_PASSWORD=${c.adminPassword}`,
|
|
368
376
|
``,
|
|
377
|
+
`# Internal API key (shared with engine for analytics)`,
|
|
378
|
+
`INTERNAL_API_KEY=${c.internalApiKey}`,
|
|
379
|
+
``,
|
|
369
380
|
`# Email (optional)`,
|
|
370
381
|
`RESEND_API_KEY=${c.resendApiKey ?? ""}`,
|
|
371
382
|
`FEEDBACK_TO_EMAIL=${c.feedbackToEmail ?? ""}`
|
|
@@ -395,7 +406,10 @@ function renderEnvEngine(c) {
|
|
|
395
406
|
`SFDC_LOGIN_URL=${c.sfdcLoginUrl}`,
|
|
396
407
|
``,
|
|
397
408
|
`# Webhook`,
|
|
398
|
-
`ENGINE_WEBHOOK_SECRET=${c.engineWebhookSecret}
|
|
409
|
+
`ENGINE_WEBHOOK_SECRET=${c.engineWebhookSecret}`,
|
|
410
|
+
``,
|
|
411
|
+
`# Internal API key (Bearer token for analytics endpoints)`,
|
|
412
|
+
`INTERNAL_API_KEY=${c.internalApiKey}`
|
|
399
413
|
].join("\n");
|
|
400
414
|
}
|
|
401
415
|
|
|
@@ -412,7 +426,18 @@ function renderCaddyfile(appUrl, engineUrl) {
|
|
|
412
426
|
`# Generated by lead-routing CLI`,
|
|
413
427
|
`# Caddy auto-provisions SSL certificates via Let's Encrypt`,
|
|
414
428
|
``,
|
|
429
|
+
`(security_headers) {`,
|
|
430
|
+
` header {`,
|
|
431
|
+
` X-Content-Type-Options nosniff`,
|
|
432
|
+
` X-Frame-Options DENY`,
|
|
433
|
+
` Referrer-Policy strict-origin-when-cross-origin`,
|
|
434
|
+
` Permissions-Policy interest-cohort=()`,
|
|
435
|
+
` Strict-Transport-Security "max-age=31536000; includeSubDomains"`,
|
|
436
|
+
` }`,
|
|
437
|
+
`}`,
|
|
438
|
+
``,
|
|
415
439
|
`${appHost} {`,
|
|
440
|
+
` import security_headers`,
|
|
416
441
|
` reverse_proxy web:3000 {`,
|
|
417
442
|
` health_uri /api/health`,
|
|
418
443
|
` health_interval 15s`,
|
|
@@ -420,6 +445,7 @@ function renderCaddyfile(appUrl, engineUrl) {
|
|
|
420
445
|
`}`,
|
|
421
446
|
``,
|
|
422
447
|
`${appHost}:${enginePort} {`,
|
|
448
|
+
` import security_headers`,
|
|
423
449
|
` reverse_proxy engine:3001 {`,
|
|
424
450
|
` health_uri /health`,
|
|
425
451
|
` health_interval 15s`,
|
|
@@ -432,7 +458,18 @@ function renderCaddyfile(appUrl, engineUrl) {
|
|
|
432
458
|
`# Generated by lead-routing CLI`,
|
|
433
459
|
`# Caddy auto-provisions SSL certificates via Let's Encrypt`,
|
|
434
460
|
``,
|
|
461
|
+
`(security_headers) {`,
|
|
462
|
+
` header {`,
|
|
463
|
+
` X-Content-Type-Options nosniff`,
|
|
464
|
+
` X-Frame-Options DENY`,
|
|
465
|
+
` Referrer-Policy strict-origin-when-cross-origin`,
|
|
466
|
+
` Permissions-Policy interest-cohort=()`,
|
|
467
|
+
` Strict-Transport-Security "max-age=31536000; includeSubDomains"`,
|
|
468
|
+
` }`,
|
|
469
|
+
`}`,
|
|
470
|
+
``,
|
|
435
471
|
`${appHost} {`,
|
|
472
|
+
` import security_headers`,
|
|
436
473
|
` reverse_proxy web:3000 {`,
|
|
437
474
|
` health_uri /api/health`,
|
|
438
475
|
` health_interval 15s`,
|
|
@@ -440,6 +477,7 @@ function renderCaddyfile(appUrl, engineUrl) {
|
|
|
440
477
|
`}`,
|
|
441
478
|
``,
|
|
442
479
|
`${engineHost} {`,
|
|
480
|
+
` import security_headers`,
|
|
443
481
|
` reverse_proxy engine:3001 {`,
|
|
444
482
|
` health_uri /health`,
|
|
445
483
|
` health_interval 15s`,
|
|
@@ -491,7 +529,8 @@ function generateFiles(cfg, sshCfg) {
|
|
|
491
529
|
const composeContent = renderDockerCompose({
|
|
492
530
|
managedDb: cfg.managedDb,
|
|
493
531
|
managedRedis: cfg.managedRedis,
|
|
494
|
-
dbPassword: cfg.dbPassword
|
|
532
|
+
dbPassword: cfg.dbPassword,
|
|
533
|
+
redisPassword: cfg.redisPassword
|
|
495
534
|
});
|
|
496
535
|
const composeFile = join2(dir, "docker-compose.yml");
|
|
497
536
|
writeFileSync2(composeFile, composeContent, "utf8");
|
|
@@ -513,6 +552,7 @@ function generateFiles(cfg, sshCfg) {
|
|
|
513
552
|
adminSecret: cfg.adminSecret,
|
|
514
553
|
adminEmail: cfg.adminEmail,
|
|
515
554
|
adminPassword: cfg.adminPassword,
|
|
555
|
+
internalApiKey: cfg.internalApiKey,
|
|
516
556
|
resendApiKey: cfg.resendApiKey || void 0,
|
|
517
557
|
feedbackToEmail: cfg.feedbackToEmail || void 0
|
|
518
558
|
});
|
|
@@ -525,7 +565,8 @@ function generateFiles(cfg, sshCfg) {
|
|
|
525
565
|
sfdcClientId: cfg.sfdcClientId,
|
|
526
566
|
sfdcClientSecret: cfg.sfdcClientSecret,
|
|
527
567
|
sfdcLoginUrl: cfg.sfdcLoginUrl,
|
|
528
|
-
engineWebhookSecret: cfg.engineWebhookSecret
|
|
568
|
+
engineWebhookSecret: cfg.engineWebhookSecret,
|
|
569
|
+
internalApiKey: cfg.internalApiKey
|
|
529
570
|
});
|
|
530
571
|
const envEngine = join2(dir, ".env.engine");
|
|
531
572
|
writeFileSync2(envEngine, envEngineContent, "utf8");
|
|
@@ -549,6 +590,7 @@ function generateFiles(cfg, sshCfg) {
|
|
|
549
590
|
// Stored so `lead-routing sfdc deploy` can re-authenticate without re-prompting
|
|
550
591
|
sfdcClientId: cfg.sfdcClientId,
|
|
551
592
|
sfdcLoginUrl: cfg.sfdcLoginUrl,
|
|
593
|
+
engineWebhookSecret: cfg.engineWebhookSecret,
|
|
552
594
|
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
553
595
|
version: getCliVersion()
|
|
554
596
|
});
|
|
@@ -894,7 +936,7 @@ function sleep2(ms) {
|
|
|
894
936
|
}
|
|
895
937
|
|
|
896
938
|
// src/steps/sfdc-deploy-inline.ts
|
|
897
|
-
import { readFileSync as
|
|
939
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, cpSync, rmSync } from "fs";
|
|
898
940
|
import { join as join5, dirname as dirname2 } from "path";
|
|
899
941
|
import { tmpdir } from "os";
|
|
900
942
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -983,8 +1025,7 @@ var SalesforceApi = class {
|
|
|
983
1025
|
const deployOptions = JSON.stringify({
|
|
984
1026
|
deployOptions: {
|
|
985
1027
|
rollbackOnError: true,
|
|
986
|
-
singlePackage: true
|
|
987
|
-
rest: true
|
|
1028
|
+
singlePackage: true
|
|
988
1029
|
}
|
|
989
1030
|
});
|
|
990
1031
|
const parts = [];
|
|
@@ -1064,21 +1105,116 @@ var DuplicateError = class extends Error {
|
|
|
1064
1105
|
|
|
1065
1106
|
// src/utils/zip-source.ts
|
|
1066
1107
|
import { join as join4 } from "path";
|
|
1108
|
+
import { readdirSync, readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
|
|
1067
1109
|
import archiver from "archiver";
|
|
1110
|
+
var META_TYPE_MAP = {
|
|
1111
|
+
applications: "CustomApplication",
|
|
1112
|
+
classes: "ApexClass",
|
|
1113
|
+
triggers: "ApexTrigger",
|
|
1114
|
+
lwc: "LightningComponentBundle",
|
|
1115
|
+
permissionsets: "PermissionSet",
|
|
1116
|
+
namedCredentials: "NamedCredential",
|
|
1117
|
+
remoteSiteSettings: "RemoteSiteSetting",
|
|
1118
|
+
tabs: "CustomTab"
|
|
1119
|
+
};
|
|
1068
1120
|
async function zipSourcePackage(packageDir) {
|
|
1121
|
+
const forceAppDefault = join4(packageDir, "force-app", "main", "default");
|
|
1122
|
+
let apiVersion = "59.0";
|
|
1123
|
+
try {
|
|
1124
|
+
const proj = JSON.parse(readFileSync3(join4(packageDir, "sfdx-project.json"), "utf8"));
|
|
1125
|
+
if (proj.sourceApiVersion) apiVersion = proj.sourceApiVersion;
|
|
1126
|
+
} catch {
|
|
1127
|
+
}
|
|
1128
|
+
const members = /* @__PURE__ */ new Map();
|
|
1129
|
+
const addMember = (type, name) => {
|
|
1130
|
+
if (!members.has(type)) members.set(type, /* @__PURE__ */ new Set());
|
|
1131
|
+
members.get(type).add(name);
|
|
1132
|
+
};
|
|
1069
1133
|
return new Promise((resolve, reject) => {
|
|
1070
1134
|
const chunks = [];
|
|
1071
1135
|
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
1072
1136
|
archive.on("data", (chunk) => chunks.push(chunk));
|
|
1073
1137
|
archive.on("end", () => resolve(Buffer.concat(chunks)));
|
|
1074
1138
|
archive.on("error", reject);
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1139
|
+
for (const [dirName, metaType] of Object.entries(META_TYPE_MAP)) {
|
|
1140
|
+
const srcDir = join4(forceAppDefault, dirName);
|
|
1141
|
+
if (!existsSync3(srcDir)) continue;
|
|
1142
|
+
const entries = readdirSync(srcDir, { withFileTypes: true });
|
|
1143
|
+
for (const entry of entries) {
|
|
1144
|
+
if (dirName === "lwc" && entry.isDirectory()) {
|
|
1145
|
+
addMember(metaType, entry.name);
|
|
1146
|
+
archive.directory(join4(srcDir, entry.name), `${dirName}/${entry.name}`);
|
|
1147
|
+
} else if (entry.isFile()) {
|
|
1148
|
+
archive.file(join4(srcDir, entry.name), { name: `${dirName}/${entry.name}` });
|
|
1149
|
+
if (!entry.name.endsWith("-meta.xml")) {
|
|
1150
|
+
const memberName = entry.name.replace(/\.[^.]+$/, "");
|
|
1151
|
+
addMember(metaType, memberName);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
const objectsDir = join4(forceAppDefault, "objects");
|
|
1157
|
+
if (existsSync3(objectsDir)) {
|
|
1158
|
+
for (const objEntry of readdirSync(objectsDir, { withFileTypes: true })) {
|
|
1159
|
+
if (!objEntry.isDirectory()) continue;
|
|
1160
|
+
const objName = objEntry.name;
|
|
1161
|
+
addMember("CustomObject", objName);
|
|
1162
|
+
const objDir = join4(objectsDir, objName);
|
|
1163
|
+
const objectXml = mergeObjectXml(objDir, objName, apiVersion);
|
|
1164
|
+
archive.append(Buffer.from(objectXml, "utf8"), {
|
|
1165
|
+
name: `objects/${objName}.object`
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
const packageXml = generatePackageXml(members, apiVersion);
|
|
1170
|
+
archive.append(Buffer.from(packageXml, "utf8"), { name: "package.xml" });
|
|
1079
1171
|
archive.finalize();
|
|
1080
1172
|
});
|
|
1081
1173
|
}
|
|
1174
|
+
function mergeObjectXml(objDir, objName, apiVersion) {
|
|
1175
|
+
const lines = [
|
|
1176
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
1177
|
+
'<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">'
|
|
1178
|
+
];
|
|
1179
|
+
const objMetaPath = join4(objDir, `${objName}.object-meta.xml`);
|
|
1180
|
+
if (existsSync3(objMetaPath)) {
|
|
1181
|
+
const content = readFileSync3(objMetaPath, "utf8");
|
|
1182
|
+
const inner = content.replace(/<\?xml[^?]*\?>\s*/g, "").replace(/<CustomObject[^>]*>/g, "").replace(/<\/CustomObject>/g, "").trim();
|
|
1183
|
+
if (inner) lines.push(inner);
|
|
1184
|
+
}
|
|
1185
|
+
const fieldsDir = join4(objDir, "fields");
|
|
1186
|
+
if (existsSync3(fieldsDir)) {
|
|
1187
|
+
for (const fieldFile of readdirSync(fieldsDir).sort()) {
|
|
1188
|
+
if (!fieldFile.endsWith(".field-meta.xml")) continue;
|
|
1189
|
+
const content = readFileSync3(join4(fieldsDir, fieldFile), "utf8");
|
|
1190
|
+
const inner = content.replace(/<\?xml[^?]*\?>\s*/g, "").replace(/<CustomField[^>]*>/g, "").replace(/<\/CustomField>/g, "").trim();
|
|
1191
|
+
if (inner) {
|
|
1192
|
+
lines.push(" <fields>");
|
|
1193
|
+
lines.push(` ${inner}`);
|
|
1194
|
+
lines.push(" </fields>");
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
lines.push("</CustomObject>");
|
|
1199
|
+
return lines.join("\n");
|
|
1200
|
+
}
|
|
1201
|
+
function generatePackageXml(members, apiVersion) {
|
|
1202
|
+
const lines = [
|
|
1203
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
1204
|
+
'<Package xmlns="http://soap.sforce.com/2006/04/metadata">'
|
|
1205
|
+
];
|
|
1206
|
+
for (const [metaType, names] of [...members.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
1207
|
+
lines.push(" <types>");
|
|
1208
|
+
for (const name of [...names].sort()) {
|
|
1209
|
+
lines.push(` <members>${name}</members>`);
|
|
1210
|
+
}
|
|
1211
|
+
lines.push(` <name>${metaType}</name>`);
|
|
1212
|
+
lines.push(" </types>");
|
|
1213
|
+
}
|
|
1214
|
+
lines.push(` <version>${apiVersion}</version>`);
|
|
1215
|
+
lines.push("</Package>");
|
|
1216
|
+
return lines.join("\n");
|
|
1217
|
+
}
|
|
1082
1218
|
|
|
1083
1219
|
// src/steps/sfdc-deploy-inline.ts
|
|
1084
1220
|
var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
|
|
@@ -1094,16 +1230,16 @@ async function sfdcDeployInline(params) {
|
|
|
1094
1230
|
s.start("Copying Salesforce package\u2026");
|
|
1095
1231
|
const inDist = join5(__dirname2, "sfdc-package");
|
|
1096
1232
|
const nextToDist = join5(__dirname2, "..", "sfdc-package");
|
|
1097
|
-
const bundledPkg =
|
|
1233
|
+
const bundledPkg = existsSync4(inDist) ? inDist : nextToDist;
|
|
1098
1234
|
const destPkg = join5(installDir ?? tmpdir(), "lead-routing-sfdc-package");
|
|
1099
|
-
if (!
|
|
1235
|
+
if (!existsSync4(bundledPkg)) {
|
|
1100
1236
|
s.stop("sfdc-package not found in CLI bundle");
|
|
1101
1237
|
throw new Error(
|
|
1102
1238
|
`Expected bundle at: ${inDist}
|
|
1103
1239
|
The CLI may need to be reinstalled: npm i -g @lead-routing/cli`
|
|
1104
1240
|
);
|
|
1105
1241
|
}
|
|
1106
|
-
if (
|
|
1242
|
+
if (existsSync4(destPkg)) rmSync(destPkg, { recursive: true, force: true });
|
|
1107
1243
|
cpSync(bundledPkg, destPkg, { recursive: true });
|
|
1108
1244
|
s.stop("Package copied");
|
|
1109
1245
|
const ncPath = join5(
|
|
@@ -1114,8 +1250,8 @@ The CLI may need to be reinstalled: npm i -g @lead-routing/cli`
|
|
|
1114
1250
|
"namedCredentials",
|
|
1115
1251
|
"RoutingEngine.namedCredential-meta.xml"
|
|
1116
1252
|
);
|
|
1117
|
-
if (
|
|
1118
|
-
const nc = patchXml(
|
|
1253
|
+
if (existsSync4(ncPath)) {
|
|
1254
|
+
const nc = patchXml(readFileSync4(ncPath, "utf8"), "endpoint", engineUrl);
|
|
1119
1255
|
writeFileSync3(ncPath, nc, "utf8");
|
|
1120
1256
|
}
|
|
1121
1257
|
const rssEnginePath = join5(
|
|
@@ -1126,8 +1262,8 @@ The CLI may need to be reinstalled: npm i -g @lead-routing/cli`
|
|
|
1126
1262
|
"remoteSiteSettings",
|
|
1127
1263
|
"LeadRouterEngine.remoteSite-meta.xml"
|
|
1128
1264
|
);
|
|
1129
|
-
if (
|
|
1130
|
-
let rss = patchXml(
|
|
1265
|
+
if (existsSync4(rssEnginePath)) {
|
|
1266
|
+
let rss = patchXml(readFileSync4(rssEnginePath, "utf8"), "url", engineUrl);
|
|
1131
1267
|
rss = patchXml(rss, "description", "Lead Router Engine endpoint");
|
|
1132
1268
|
writeFileSync3(rssEnginePath, rss, "utf8");
|
|
1133
1269
|
}
|
|
@@ -1139,8 +1275,8 @@ The CLI may need to be reinstalled: npm i -g @lead-routing/cli`
|
|
|
1139
1275
|
"remoteSiteSettings",
|
|
1140
1276
|
"LeadRouterApp.remoteSite-meta.xml"
|
|
1141
1277
|
);
|
|
1142
|
-
if (
|
|
1143
|
-
let rss = patchXml(
|
|
1278
|
+
if (existsSync4(rssAppPath)) {
|
|
1279
|
+
let rss = patchXml(readFileSync4(rssAppPath, "utf8"), "url", appUrl);
|
|
1144
1280
|
rss = patchXml(rss, "description", "Lead Router App URL");
|
|
1145
1281
|
writeFileSync3(rssAppPath, rss, "utf8");
|
|
1146
1282
|
}
|
|
@@ -1210,16 +1346,17 @@ ${failureMsg || result.errorMessage || "Unknown error"}`
|
|
|
1210
1346
|
const existing = await sf.query(
|
|
1211
1347
|
"SELECT Id FROM Routing_Settings__c LIMIT 1"
|
|
1212
1348
|
);
|
|
1349
|
+
const settingsData = {
|
|
1350
|
+
App_Url__c: appUrl,
|
|
1351
|
+
Engine_Endpoint__c: engineUrl
|
|
1352
|
+
};
|
|
1353
|
+
if (params.webhookSecret) {
|
|
1354
|
+
settingsData.Webhook_Secret__c = params.webhookSecret;
|
|
1355
|
+
}
|
|
1213
1356
|
if (existing.length > 0) {
|
|
1214
|
-
await sf.update("Routing_Settings__c", existing[0].Id,
|
|
1215
|
-
App_Url__c: appUrl,
|
|
1216
|
-
Engine_Endpoint__c: engineUrl
|
|
1217
|
-
});
|
|
1357
|
+
await sf.update("Routing_Settings__c", existing[0].Id, settingsData);
|
|
1218
1358
|
} else {
|
|
1219
|
-
await sf.create("Routing_Settings__c",
|
|
1220
|
-
App_Url__c: appUrl,
|
|
1221
|
-
Engine_Endpoint__c: engineUrl
|
|
1222
|
-
});
|
|
1359
|
+
await sf.create("Routing_Settings__c", settingsData);
|
|
1223
1360
|
}
|
|
1224
1361
|
s.stop("Org settings written");
|
|
1225
1362
|
} catch (err) {
|
|
@@ -1521,7 +1658,8 @@ async function runInit(options = {}) {
|
|
|
1521
1658
|
orgAlias: "lead-routing",
|
|
1522
1659
|
sfdcClientId: saved.sfdcClientId ?? "",
|
|
1523
1660
|
sfdcLoginUrl: saved.sfdcLoginUrl ?? "https://login.salesforce.com",
|
|
1524
|
-
installDir: dir
|
|
1661
|
+
installDir: dir,
|
|
1662
|
+
webhookSecret: saved.engineWebhookSecret
|
|
1525
1663
|
});
|
|
1526
1664
|
await guideAppLauncherSetup(saved.appUrl);
|
|
1527
1665
|
outro(
|
|
@@ -1597,6 +1735,14 @@ Files created: docker-compose.yml, Caddyfile, .env.web, .env.engine, lead-routin
|
|
|
1597
1735
|
await startServices(ssh, remoteDir);
|
|
1598
1736
|
log9.step("Step 7/8 Verifying health");
|
|
1599
1737
|
await verifyHealth(cfg.appUrl, cfg.engineUrl, ssh, remoteDir);
|
|
1738
|
+
try {
|
|
1739
|
+
const envWebPath = join6(dir, ".env.web");
|
|
1740
|
+
const envContent = readFileSync5(envWebPath, "utf-8");
|
|
1741
|
+
const cleaned = envContent.split("\n").filter((line) => !line.startsWith("ADMIN_PASSWORD=")).join("\n");
|
|
1742
|
+
writeFileSync4(envWebPath, cleaned, "utf-8");
|
|
1743
|
+
log9.success("Removed ADMIN_PASSWORD from .env.web (no longer needed after seed)");
|
|
1744
|
+
} catch {
|
|
1745
|
+
}
|
|
1600
1746
|
log9.step("Step 8/8 Deploying Salesforce package");
|
|
1601
1747
|
await sfdcDeployInline({
|
|
1602
1748
|
appUrl: cfg.appUrl,
|
|
@@ -1604,7 +1750,8 @@ Files created: docker-compose.yml, Caddyfile, .env.web, .env.engine, lead-routin
|
|
|
1604
1750
|
orgAlias: "lead-routing",
|
|
1605
1751
|
sfdcClientId: cfg.sfdcClientId,
|
|
1606
1752
|
sfdcLoginUrl: cfg.sfdcLoginUrl,
|
|
1607
|
-
installDir: dir
|
|
1753
|
+
installDir: dir,
|
|
1754
|
+
webhookSecret: cfg.engineWebhookSecret
|
|
1608
1755
|
});
|
|
1609
1756
|
await guideAppLauncherSetup(cfg.appUrl);
|
|
1610
1757
|
outro(
|
|
@@ -1633,8 +1780,8 @@ Files created: docker-compose.yml, Caddyfile, .env.web, .env.engine, lead-routin
|
|
|
1633
1780
|
}
|
|
1634
1781
|
|
|
1635
1782
|
// src/commands/deploy.ts
|
|
1636
|
-
import { writeFileSync as
|
|
1637
|
-
import { join as
|
|
1783
|
+
import { writeFileSync as writeFileSync5, unlinkSync } from "fs";
|
|
1784
|
+
import { join as join7 } from "path";
|
|
1638
1785
|
import { tmpdir as tmpdir2 } from "os";
|
|
1639
1786
|
import { intro as intro2, outro as outro2, log as log10, password as promptPassword2 } from "@clack/prompts";
|
|
1640
1787
|
import chalk3 from "chalk";
|
|
@@ -1676,8 +1823,8 @@ async function runDeploy() {
|
|
|
1676
1823
|
const remoteDir = await ssh.resolveHome(cfg.remoteDir);
|
|
1677
1824
|
log10.step("Syncing Caddyfile");
|
|
1678
1825
|
const caddyContent = renderCaddyfile(cfg.appUrl, cfg.engineUrl);
|
|
1679
|
-
const tmpCaddy =
|
|
1680
|
-
|
|
1826
|
+
const tmpCaddy = join7(tmpdir2(), "lead-routing-Caddyfile");
|
|
1827
|
+
writeFileSync5(tmpCaddy, caddyContent, "utf8");
|
|
1681
1828
|
await ssh.upload([{ local: tmpCaddy, remote: `${remoteDir}/Caddyfile` }]);
|
|
1682
1829
|
unlinkSync(tmpCaddy);
|
|
1683
1830
|
await ssh.exec("docker compose restart caddy", remoteDir);
|
|
@@ -1839,15 +1986,15 @@ async function runStatus() {
|
|
|
1839
1986
|
}
|
|
1840
1987
|
|
|
1841
1988
|
// src/commands/config.ts
|
|
1842
|
-
import { readFileSync as
|
|
1843
|
-
import { join as
|
|
1989
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync5 } from "fs";
|
|
1990
|
+
import { join as join8 } from "path";
|
|
1844
1991
|
import { intro as intro4, outro as outro4, text as text3, password as password3, spinner as spinner6, log as log14 } from "@clack/prompts";
|
|
1845
1992
|
import chalk5 from "chalk";
|
|
1846
1993
|
import { execa as execa4 } from "execa";
|
|
1847
1994
|
function parseEnv(filePath) {
|
|
1848
1995
|
const map = /* @__PURE__ */ new Map();
|
|
1849
|
-
if (!
|
|
1850
|
-
for (const line of
|
|
1996
|
+
if (!existsSync5(filePath)) return map;
|
|
1997
|
+
for (const line of readFileSync6(filePath, "utf8").split("\n")) {
|
|
1851
1998
|
const trimmed = line.trim();
|
|
1852
1999
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1853
2000
|
const eq = trimmed.indexOf("=");
|
|
@@ -1857,7 +2004,7 @@ function parseEnv(filePath) {
|
|
|
1857
2004
|
return map;
|
|
1858
2005
|
}
|
|
1859
2006
|
function writeEnv(filePath, updates) {
|
|
1860
|
-
const lines =
|
|
2007
|
+
const lines = existsSync5(filePath) ? readFileSync6(filePath, "utf8").split("\n") : [];
|
|
1861
2008
|
const updated = /* @__PURE__ */ new Set();
|
|
1862
2009
|
const result = lines.map((line) => {
|
|
1863
2010
|
const trimmed = line.trim();
|
|
@@ -1874,7 +2021,7 @@ function writeEnv(filePath, updates) {
|
|
|
1874
2021
|
for (const [key, val] of Object.entries(updates)) {
|
|
1875
2022
|
if (!updated.has(key)) result.push(`${key}=${val}`);
|
|
1876
2023
|
}
|
|
1877
|
-
|
|
2024
|
+
writeFileSync6(filePath, result.join("\n"), "utf8");
|
|
1878
2025
|
}
|
|
1879
2026
|
async function runConfigSfdc() {
|
|
1880
2027
|
intro4("Lead Routing \u2014 Update Salesforce Credentials");
|
|
@@ -1884,8 +2031,8 @@ async function runConfigSfdc() {
|
|
|
1884
2031
|
log14.info("Run `lead-routing init` first, or cd into your installation directory.");
|
|
1885
2032
|
process.exit(1);
|
|
1886
2033
|
}
|
|
1887
|
-
const envWeb =
|
|
1888
|
-
const envEngine =
|
|
2034
|
+
const envWeb = join8(dir, ".env.web");
|
|
2035
|
+
const envEngine = join8(dir, ".env.engine");
|
|
1889
2036
|
const currentWeb = parseEnv(envWeb);
|
|
1890
2037
|
const currentClientId = currentWeb.get("SFDC_CLIENT_ID") ?? "";
|
|
1891
2038
|
const currentLoginUrl = currentWeb.get("SFDC_LOGIN_URL") ?? "https://login.salesforce.com";
|
|
@@ -1938,7 +2085,7 @@ function runConfigShow() {
|
|
|
1938
2085
|
console.error("No lead-routing installation found in the current directory.");
|
|
1939
2086
|
process.exit(1);
|
|
1940
2087
|
}
|
|
1941
|
-
const envWeb =
|
|
2088
|
+
const envWeb = join8(dir, ".env.web");
|
|
1942
2089
|
const cfg = parseEnv(envWeb);
|
|
1943
2090
|
const adminSecret = cfg.get("ADMIN_SECRET") ?? "(not found)";
|
|
1944
2091
|
const appUrl = cfg.get("APP_URL") ?? "(not found)";
|
|
@@ -1996,7 +2143,8 @@ async function runSfdcDeploy() {
|
|
|
1996
2143
|
// Read from config if available
|
|
1997
2144
|
sfdcClientId: config2?.sfdcClientId ?? "",
|
|
1998
2145
|
sfdcLoginUrl: config2?.sfdcLoginUrl ?? "https://login.salesforce.com",
|
|
1999
|
-
installDir: dir ?? void 0
|
|
2146
|
+
installDir: dir ?? void 0,
|
|
2147
|
+
webhookSecret: config2?.engineWebhookSecret
|
|
2000
2148
|
});
|
|
2001
2149
|
} catch (err) {
|
|
2002
2150
|
log15.error(err instanceof Error ? err.message : String(err));
|
|
@@ -2011,7 +2159,7 @@ async function runSfdcDeploy() {
|
|
|
2011
2159
|
}
|
|
2012
2160
|
|
|
2013
2161
|
// src/commands/uninstall.ts
|
|
2014
|
-
import { rmSync as rmSync2, existsSync as
|
|
2162
|
+
import { rmSync as rmSync2, existsSync as existsSync6 } from "fs";
|
|
2015
2163
|
import { intro as intro6, outro as outro6, log as log16, confirm as confirm3, password as promptPassword3, isCancel as isCancel5 } from "@clack/prompts";
|
|
2016
2164
|
import chalk7 from "chalk";
|
|
2017
2165
|
async function runUninstall() {
|
|
@@ -2087,7 +2235,7 @@ async function runUninstall() {
|
|
|
2087
2235
|
await ssh.disconnect();
|
|
2088
2236
|
}
|
|
2089
2237
|
log16.step("Removing local config directory");
|
|
2090
|
-
if (
|
|
2238
|
+
if (existsSync6(dir)) {
|
|
2091
2239
|
rmSync2(dir, { recursive: true, force: true });
|
|
2092
2240
|
log16.success(`Removed ${dir}`);
|
|
2093
2241
|
}
|
|
@@ -230,7 +230,7 @@ model RoutingBranch {
|
|
|
230
230
|
rule RoutingRule @relation(fields: [ruleId], references: [id], onDelete: Cascade)
|
|
231
231
|
label String?
|
|
232
232
|
priority Int @default(0)
|
|
233
|
-
assignmentType AssignmentType
|
|
233
|
+
assignmentType AssignmentType?
|
|
234
234
|
assigneeUserId String?
|
|
235
235
|
assigneeTeamId String?
|
|
236
236
|
assigneeQueueId String?
|