@lead-routing/cli 0.4.0 → 0.4.1
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 +17 -139
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -111,7 +111,7 @@ function bail2(value) {
|
|
|
111
111
|
}
|
|
112
112
|
async function collectConfig(opts = {}) {
|
|
113
113
|
note2(
|
|
114
|
-
"You will need:\n \u2022
|
|
114
|
+
"You will need:\n \u2022 Public HTTPS URLs for the web app and routing engine",
|
|
115
115
|
"Before you begin"
|
|
116
116
|
);
|
|
117
117
|
const appUrl = await text2({
|
|
@@ -142,33 +142,6 @@ async function collectConfig(opts = {}) {
|
|
|
142
142
|
}
|
|
143
143
|
});
|
|
144
144
|
if (isCancel2(engineUrl)) bail2(engineUrl);
|
|
145
|
-
const callbackUrl = `${appUrl.trim().replace(/\/+$/, "")}/api/auth/sfdc/callback`;
|
|
146
|
-
note2(
|
|
147
|
-
`You need a Salesforce Connected App. If you haven't created one yet:
|
|
148
|
-
|
|
149
|
-
1. Go to Salesforce Setup \u2192 App Manager \u2192 New Connected App
|
|
150
|
-
2. Connected App Name: Lead Routing
|
|
151
|
-
3. Check "Enable OAuth Settings"
|
|
152
|
-
4. Callback URL (copy exactly \u2014 must match):
|
|
153
|
-
${callbackUrl}
|
|
154
|
-
5. Selected Scopes: api \u2022 refresh_token, offline_access
|
|
155
|
-
6. Check "Require Secret for Web Server Flow"
|
|
156
|
-
7. Save \u2014 wait ~2 min, then click "Manage Consumer Details"
|
|
157
|
-
8. Copy the Consumer Key (Client ID) and Consumer Secret below`,
|
|
158
|
-
"Salesforce Connected App setup"
|
|
159
|
-
);
|
|
160
|
-
const sfdcClientId = await text2({
|
|
161
|
-
message: 'Consumer Key (labelled "Client ID" in newer orgs)',
|
|
162
|
-
placeholder: "3MVG9...",
|
|
163
|
-
validate: (v) => !v ? "Required" : void 0
|
|
164
|
-
});
|
|
165
|
-
if (isCancel2(sfdcClientId)) bail2(sfdcClientId);
|
|
166
|
-
const sfdcClientSecret = await password2({
|
|
167
|
-
message: 'Consumer Secret (labelled "Client Secret" in newer orgs)',
|
|
168
|
-
validate: (v) => !v ? "Required" : void 0
|
|
169
|
-
});
|
|
170
|
-
if (isCancel2(sfdcClientSecret)) bail2(sfdcClientSecret);
|
|
171
|
-
const sfdcLoginUrl = opts.sandbox ? "https://test.salesforce.com" : "https://login.salesforce.com";
|
|
172
145
|
const dbPassword = generateSecret(16);
|
|
173
146
|
const managedDb = !opts.externalDb;
|
|
174
147
|
const databaseUrl = opts.externalDb ?? `postgresql://leadrouting:${dbPassword}@postgres:5432/leadrouting`;
|
|
@@ -200,9 +173,6 @@ async function collectConfig(opts = {}) {
|
|
|
200
173
|
return {
|
|
201
174
|
appUrl: appUrl.trim().replace(/\/+$/, ""),
|
|
202
175
|
engineUrl: engineUrl.trim().replace(/\/+$/, ""),
|
|
203
|
-
sfdcClientId: sfdcClientId.trim(),
|
|
204
|
-
sfdcClientSecret: sfdcClientSecret.trim(),
|
|
205
|
-
sfdcLoginUrl,
|
|
206
176
|
managedDb,
|
|
207
177
|
databaseUrl,
|
|
208
178
|
dbPassword: managedDb ? dbPassword : "",
|
|
@@ -360,12 +330,6 @@ function renderEnvWeb(c) {
|
|
|
360
330
|
`# Redis`,
|
|
361
331
|
`REDIS_URL=${c.redisUrl}`,
|
|
362
332
|
``,
|
|
363
|
-
`# Salesforce OAuth`,
|
|
364
|
-
`SFDC_CLIENT_ID=${c.sfdcClientId}`,
|
|
365
|
-
`SFDC_CLIENT_SECRET=${c.sfdcClientSecret}`,
|
|
366
|
-
`SFDC_LOGIN_URL=${c.sfdcLoginUrl}`,
|
|
367
|
-
`SFDC_REDIRECT_URI=${c.appUrl.replace(/\/+$/, "")}/api/auth/sfdc/callback`,
|
|
368
|
-
``,
|
|
369
333
|
`# Session`,
|
|
370
334
|
`SESSION_SECRET=${c.sessionSecret}`,
|
|
371
335
|
``,
|
|
@@ -400,11 +364,6 @@ function renderEnvEngine(c) {
|
|
|
400
364
|
`# Redis`,
|
|
401
365
|
`REDIS_URL=${c.redisUrl}`,
|
|
402
366
|
``,
|
|
403
|
-
`# Salesforce OAuth`,
|
|
404
|
-
`SFDC_CLIENT_ID=${c.sfdcClientId}`,
|
|
405
|
-
`SFDC_CLIENT_SECRET=${c.sfdcClientSecret}`,
|
|
406
|
-
`SFDC_LOGIN_URL=${c.sfdcLoginUrl}`,
|
|
407
|
-
``,
|
|
408
367
|
`# Webhook`,
|
|
409
368
|
`ENGINE_WEBHOOK_SECRET=${c.engineWebhookSecret}`,
|
|
410
369
|
``,
|
|
@@ -544,9 +503,6 @@ function generateFiles(cfg, sshCfg) {
|
|
|
544
503
|
publicEngineUrl: cfg.engineUrl,
|
|
545
504
|
databaseUrl: cfg.databaseUrl,
|
|
546
505
|
redisUrl: cfg.redisUrl,
|
|
547
|
-
sfdcClientId: cfg.sfdcClientId,
|
|
548
|
-
sfdcClientSecret: cfg.sfdcClientSecret,
|
|
549
|
-
sfdcLoginUrl: cfg.sfdcLoginUrl,
|
|
550
506
|
sessionSecret: cfg.sessionSecret,
|
|
551
507
|
engineWebhookSecret: cfg.engineWebhookSecret,
|
|
552
508
|
adminSecret: cfg.adminSecret,
|
|
@@ -562,9 +518,6 @@ function generateFiles(cfg, sshCfg) {
|
|
|
562
518
|
const envEngineContent = renderEnvEngine({
|
|
563
519
|
databaseUrl: cfg.databaseUrl,
|
|
564
520
|
redisUrl: cfg.redisUrl,
|
|
565
|
-
sfdcClientId: cfg.sfdcClientId,
|
|
566
|
-
sfdcClientSecret: cfg.sfdcClientSecret,
|
|
567
|
-
sfdcLoginUrl: cfg.sfdcLoginUrl,
|
|
568
521
|
engineWebhookSecret: cfg.engineWebhookSecret,
|
|
569
522
|
internalApiKey: cfg.internalApiKey
|
|
570
523
|
});
|
|
@@ -587,9 +540,6 @@ function generateFiles(cfg, sshCfg) {
|
|
|
587
540
|
db: cfg.managedDb,
|
|
588
541
|
redis: cfg.managedRedis
|
|
589
542
|
},
|
|
590
|
-
// Stored so `lead-routing sfdc deploy` can re-authenticate without re-prompting
|
|
591
|
-
sfdcClientId: cfg.sfdcClientId,
|
|
592
|
-
sfdcLoginUrl: cfg.sfdcLoginUrl,
|
|
593
543
|
engineWebhookSecret: cfg.engineWebhookSecret,
|
|
594
544
|
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
595
545
|
version: getCliVersion()
|
|
@@ -1159,7 +1109,6 @@ async function runInit(options = {}) {
|
|
|
1159
1109
|
}
|
|
1160
1110
|
log7.step("Step 3/7 Configuration");
|
|
1161
1111
|
const cfg = await collectConfig({
|
|
1162
|
-
sandbox: options.sandbox,
|
|
1163
1112
|
externalDb: options.externalDb,
|
|
1164
1113
|
externalRedis: options.externalRedis
|
|
1165
1114
|
});
|
|
@@ -1437,9 +1386,8 @@ async function runStatus() {
|
|
|
1437
1386
|
// src/commands/config.ts
|
|
1438
1387
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync5, existsSync as existsSync3 } from "fs";
|
|
1439
1388
|
import { join as join6 } from "path";
|
|
1440
|
-
import { intro as intro4, outro as outro4,
|
|
1389
|
+
import { intro as intro4, outro as outro4, log as log12 } from "@clack/prompts";
|
|
1441
1390
|
import chalk4 from "chalk";
|
|
1442
|
-
import { execa as execa4 } from "execa";
|
|
1443
1391
|
function parseEnv(filePath) {
|
|
1444
1392
|
const map = /* @__PURE__ */ new Map();
|
|
1445
1393
|
if (!existsSync3(filePath)) return map;
|
|
@@ -1452,81 +1400,18 @@ function parseEnv(filePath) {
|
|
|
1452
1400
|
}
|
|
1453
1401
|
return map;
|
|
1454
1402
|
}
|
|
1455
|
-
function writeEnv(filePath, updates) {
|
|
1456
|
-
const lines = existsSync3(filePath) ? readFileSync4(filePath, "utf8").split("\n") : [];
|
|
1457
|
-
const updated = /* @__PURE__ */ new Set();
|
|
1458
|
-
const result = lines.map((line) => {
|
|
1459
|
-
const trimmed = line.trim();
|
|
1460
|
-
if (!trimmed || trimmed.startsWith("#")) return line;
|
|
1461
|
-
const eq = trimmed.indexOf("=");
|
|
1462
|
-
if (eq === -1) return line;
|
|
1463
|
-
const key = trimmed.slice(0, eq);
|
|
1464
|
-
if (key in updates) {
|
|
1465
|
-
updated.add(key);
|
|
1466
|
-
return `${key}=${updates[key]}`;
|
|
1467
|
-
}
|
|
1468
|
-
return line;
|
|
1469
|
-
});
|
|
1470
|
-
for (const [key, val] of Object.entries(updates)) {
|
|
1471
|
-
if (!updated.has(key)) result.push(`${key}=${val}`);
|
|
1472
|
-
}
|
|
1473
|
-
writeFileSync5(filePath, result.join("\n"), "utf8");
|
|
1474
|
-
}
|
|
1475
1403
|
async function runConfigSfdc() {
|
|
1476
|
-
intro4("Lead Routing \u2014
|
|
1404
|
+
intro4("Lead Routing \u2014 Salesforce Configuration");
|
|
1477
1405
|
const dir = findInstallDir();
|
|
1478
1406
|
if (!dir) {
|
|
1479
1407
|
log12.error("No lead-routing installation found in the current directory.");
|
|
1480
1408
|
log12.info("Run `lead-routing init` first, or cd into your installation directory.");
|
|
1481
1409
|
process.exit(1);
|
|
1482
1410
|
}
|
|
1483
|
-
const envWeb = join6(dir, ".env.web");
|
|
1484
|
-
const envEngine = join6(dir, ".env.engine");
|
|
1485
|
-
const currentWeb = parseEnv(envWeb);
|
|
1486
|
-
const currentClientId = currentWeb.get("SFDC_CLIENT_ID") ?? "";
|
|
1487
|
-
const currentLoginUrl = currentWeb.get("SFDC_LOGIN_URL") ?? "https://login.salesforce.com";
|
|
1488
|
-
const currentAppUrl = currentWeb.get("APP_URL") ?? "";
|
|
1489
|
-
const callbackUrl = `${currentAppUrl}/api/auth/callback`;
|
|
1490
1411
|
log12.info(
|
|
1491
|
-
|
|
1492
|
-
Callback URL for your Connected App: ${callbackUrl}`
|
|
1493
|
-
);
|
|
1494
|
-
const clientId = await text3({
|
|
1495
|
-
message: "Consumer Key (Client ID)",
|
|
1496
|
-
initialValue: currentClientId,
|
|
1497
|
-
validate: (v) => !v ? "Required" : void 0
|
|
1498
|
-
});
|
|
1499
|
-
if (clientId === null || typeof clientId === "symbol") {
|
|
1500
|
-
process.exit(0);
|
|
1501
|
-
}
|
|
1502
|
-
const clientSecret = await password3({
|
|
1503
|
-
message: "Consumer Secret (Client Secret)",
|
|
1504
|
-
validate: (v) => !v ? "Required" : void 0
|
|
1505
|
-
});
|
|
1506
|
-
if (clientSecret === null || typeof clientSecret === "symbol") {
|
|
1507
|
-
process.exit(0);
|
|
1508
|
-
}
|
|
1509
|
-
const updates = {
|
|
1510
|
-
SFDC_CLIENT_ID: clientId,
|
|
1511
|
-
SFDC_CLIENT_SECRET: clientSecret
|
|
1512
|
-
};
|
|
1513
|
-
writeEnv(envWeb, updates);
|
|
1514
|
-
writeEnv(envEngine, updates);
|
|
1515
|
-
log12.success("Updated .env.web and .env.engine");
|
|
1516
|
-
const s = spinner5();
|
|
1517
|
-
s.start("Restarting web and engine containers\u2026");
|
|
1518
|
-
try {
|
|
1519
|
-
await execa4("docker", ["compose", "up", "-d", "--force-recreate", "web", "engine"], {
|
|
1520
|
-
cwd: dir
|
|
1521
|
-
});
|
|
1522
|
-
s.stop("Containers restarted");
|
|
1523
|
-
} catch (err) {
|
|
1524
|
-
s.stop("Restart failed \u2014 run `docker compose up -d --force-recreate web engine` manually");
|
|
1525
|
-
log12.warn(String(err));
|
|
1526
|
-
}
|
|
1527
|
-
outro4(
|
|
1528
|
-
"Salesforce credentials updated!\n\nNext: go to the web app \u2192 Settings \u2192 Connect Salesforce to refresh your OAuth tokens."
|
|
1412
|
+
"Salesforce credentials are now managed automatically via the managed package.\nNo manual configuration is needed.\n\nIf you need to reconnect Salesforce, go to the web app \u2192 Settings \u2192 Connect Salesforce."
|
|
1529
1413
|
);
|
|
1414
|
+
outro4("No changes needed.");
|
|
1530
1415
|
}
|
|
1531
1416
|
function runConfigShow() {
|
|
1532
1417
|
const dir = findInstallDir();
|
|
@@ -1538,19 +1423,16 @@ function runConfigShow() {
|
|
|
1538
1423
|
const cfg = parseEnv(envWeb);
|
|
1539
1424
|
const adminSecret = cfg.get("ADMIN_SECRET") ?? "(not found)";
|
|
1540
1425
|
const appUrl = cfg.get("APP_URL") ?? "(not found)";
|
|
1541
|
-
const sfdcClientId = cfg.get("SFDC_CLIENT_ID") ?? "(not found)";
|
|
1542
1426
|
console.log();
|
|
1543
1427
|
console.log(chalk4.bold("Lead Routing \u2014 Installation Config"));
|
|
1544
1428
|
console.log();
|
|
1545
1429
|
console.log(` Admin panel: ${chalk4.cyan(appUrl + "/admin")}`);
|
|
1546
1430
|
console.log(` Admin secret: ${chalk4.yellow(adminSecret)}`);
|
|
1547
1431
|
console.log();
|
|
1548
|
-
console.log(` SFDC Client ID: ${chalk4.white(sfdcClientId)}`);
|
|
1549
|
-
console.log();
|
|
1550
1432
|
}
|
|
1551
1433
|
|
|
1552
1434
|
// src/commands/sfdc.ts
|
|
1553
|
-
import { intro as intro5, outro as outro5, text as
|
|
1435
|
+
import { intro as intro5, outro as outro5, text as text3, log as log15 } from "@clack/prompts";
|
|
1554
1436
|
import chalk6 from "chalk";
|
|
1555
1437
|
|
|
1556
1438
|
// src/steps/sfdc-deploy-inline.ts
|
|
@@ -1559,7 +1441,7 @@ import { join as join8, dirname as dirname2 } from "path";
|
|
|
1559
1441
|
import { tmpdir as tmpdir2 } from "os";
|
|
1560
1442
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1561
1443
|
import { execSync } from "child_process";
|
|
1562
|
-
import { spinner as
|
|
1444
|
+
import { spinner as spinner5, log as log13 } from "@clack/prompts";
|
|
1563
1445
|
|
|
1564
1446
|
// src/utils/sfdc-api.ts
|
|
1565
1447
|
var API_VERSION = "v59.0";
|
|
@@ -1680,9 +1562,9 @@ Content-Type: application/zip\r
|
|
|
1680
1562
|
body
|
|
1681
1563
|
});
|
|
1682
1564
|
if (!res.ok) {
|
|
1683
|
-
const
|
|
1565
|
+
const text4 = await res.text();
|
|
1684
1566
|
throw new Error(
|
|
1685
|
-
`Metadata deploy request failed (${res.status}): ${
|
|
1567
|
+
`Metadata deploy request failed (${res.status}): ${text4}`
|
|
1686
1568
|
);
|
|
1687
1569
|
}
|
|
1688
1570
|
const result = await res.json();
|
|
@@ -1699,9 +1581,9 @@ Content-Type: application/zip\r
|
|
|
1699
1581
|
const url = `${this.baseUrl}/metadata/deployRequest/${deployId}?includeDetails=true`;
|
|
1700
1582
|
const res = await fetch(url, { headers: this.headers() });
|
|
1701
1583
|
if (!res.ok) {
|
|
1702
|
-
const
|
|
1584
|
+
const text4 = await res.text();
|
|
1703
1585
|
throw new Error(
|
|
1704
|
-
`Deploy status check failed (${res.status}): ${
|
|
1586
|
+
`Deploy status check failed (${res.status}): ${text4}`
|
|
1705
1587
|
);
|
|
1706
1588
|
}
|
|
1707
1589
|
const data = await res.json();
|
|
@@ -1842,7 +1724,7 @@ function patchXml(content, tag, value) {
|
|
|
1842
1724
|
}
|
|
1843
1725
|
async function sfdcDeployInline(params) {
|
|
1844
1726
|
const { appUrl, engineUrl, installDir } = params;
|
|
1845
|
-
const s =
|
|
1727
|
+
const s = spinner5();
|
|
1846
1728
|
const { accessToken, instanceUrl } = await loginViaAppBridge(appUrl);
|
|
1847
1729
|
const sf = new SalesforceApi(instanceUrl, accessToken);
|
|
1848
1730
|
s.start("Copying Salesforce package\u2026");
|
|
@@ -1985,7 +1867,7 @@ ${failureMsg || result.errorMessage || "Unknown error"}`
|
|
|
1985
1867
|
}
|
|
1986
1868
|
async function loginViaAppBridge(rawAppUrl) {
|
|
1987
1869
|
const appUrl = rawAppUrl.replace(/\/+$/, "");
|
|
1988
|
-
const s =
|
|
1870
|
+
const s = spinner5();
|
|
1989
1871
|
s.start("Starting Salesforce authentication via your Lead Router app\u2026");
|
|
1990
1872
|
let sessionId;
|
|
1991
1873
|
let authUrl;
|
|
@@ -2107,20 +1989,20 @@ async function runSfdcDeploy() {
|
|
|
2107
1989
|
log15.info(`Using config from ${dir}/lead-routing.json`);
|
|
2108
1990
|
} else {
|
|
2109
1991
|
log15.warn("No lead-routing.json found \u2014 enter the URLs from your installation.");
|
|
2110
|
-
const rawApp = await
|
|
1992
|
+
const rawApp = await text3({
|
|
2111
1993
|
message: "App URL (e.g. https://leads.acme.com)",
|
|
2112
1994
|
validate: (v) => !v ? "Required" : void 0
|
|
2113
1995
|
});
|
|
2114
1996
|
if (typeof rawApp === "symbol") process.exit(0);
|
|
2115
1997
|
appUrl = rawApp.trim();
|
|
2116
|
-
const rawEngine = await
|
|
1998
|
+
const rawEngine = await text3({
|
|
2117
1999
|
message: "Engine URL (e.g. https://engine.acme.com or https://acme.com:3001)",
|
|
2118
2000
|
validate: (v) => !v ? "Required" : void 0
|
|
2119
2001
|
});
|
|
2120
2002
|
if (typeof rawEngine === "symbol") process.exit(0);
|
|
2121
2003
|
engineUrl = rawEngine.trim();
|
|
2122
2004
|
}
|
|
2123
|
-
const alias = await
|
|
2005
|
+
const alias = await text3({
|
|
2124
2006
|
message: "Salesforce org alias (used to log in)",
|
|
2125
2007
|
placeholder: "lead-routing",
|
|
2126
2008
|
initialValue: "lead-routing",
|
|
@@ -2132,9 +2014,6 @@ async function runSfdcDeploy() {
|
|
|
2132
2014
|
appUrl,
|
|
2133
2015
|
engineUrl,
|
|
2134
2016
|
orgAlias: alias,
|
|
2135
|
-
// Read from config if available
|
|
2136
|
-
sfdcClientId: config2?.sfdcClientId ?? "",
|
|
2137
|
-
sfdcLoginUrl: config2?.sfdcLoginUrl ?? "https://login.salesforce.com",
|
|
2138
2017
|
installDir: dir ?? void 0,
|
|
2139
2018
|
webhookSecret: config2?.engineWebhookSecret
|
|
2140
2019
|
});
|
|
@@ -2241,10 +2120,9 @@ async function runUninstall() {
|
|
|
2241
2120
|
// src/index.ts
|
|
2242
2121
|
var program = new Command();
|
|
2243
2122
|
program.name("lead-routing").description("Self-hosted Lead Routing \u2014 scaffold, deploy, and manage your installation").version("0.1.13");
|
|
2244
|
-
program.command("init").description("Interactive setup wizard \u2014 configure and deploy the full Lead Routing stack").option("--dry-run", "Generate config files without connecting or deploying").option("--resume", "Skip to health check using existing lead-routing.json (post-timeout recovery)").option("--
|
|
2123
|
+
program.command("init").description("Interactive setup wizard \u2014 configure and deploy the full Lead Routing stack").option("--dry-run", "Generate config files without connecting or deploying").option("--resume", "Skip to health check using existing lead-routing.json (post-timeout recovery)").option("--ssh-port <port>", "SSH port (default: 22)", parseInt).option("--ssh-user <user>", "SSH username (default: root)").option("--ssh-key <path>", "Path to SSH private key (overrides auto-detection)").option("--remote-dir <path>", "Remote install directory (default: ~/lead-routing)").option("--external-db <url>", "Use external PostgreSQL URL instead of managed Docker container").option("--external-redis <url>", "Use external Redis URL instead of managed Docker container").action((opts) => runInit({
|
|
2245
2124
|
dryRun: opts.dryRun,
|
|
2246
2125
|
resume: opts.resume,
|
|
2247
|
-
sandbox: opts.sandbox,
|
|
2248
2126
|
sshPort: opts.sshPort,
|
|
2249
2127
|
sshUser: opts.sshUser,
|
|
2250
2128
|
sshKey: opts.sshKey,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lead-routing/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Self-hosted deployment CLI for Lead Routing",
|
|
5
5
|
"homepage": "https://github.com/lead-routing/lead-routing",
|
|
6
6
|
"keywords": [
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
],
|
|
13
13
|
"type": "module",
|
|
14
14
|
"bin": {
|
|
15
|
-
"lead-routing": "
|
|
15
|
+
"lead-routing": "dist/index.js"
|
|
16
16
|
},
|
|
17
17
|
"engines": {
|
|
18
18
|
"node": ">=20"
|