@lead-routing/cli 0.1.3 → 0.1.5
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 +136 -41
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/init.ts
|
|
7
|
-
import {
|
|
7
|
+
import { promises as dns } from "dns";
|
|
8
|
+
import { intro, outro, note as note4, log as log8, confirm as confirm3, cancel as cancel3, isCancel as isCancel4, password as promptPassword } from "@clack/prompts";
|
|
8
9
|
import chalk2 from "chalk";
|
|
9
10
|
|
|
10
11
|
// src/steps/prerequisites.ts
|
|
@@ -189,7 +190,8 @@ async function collectConfig() {
|
|
|
189
190
|
validate: (v) => {
|
|
190
191
|
if (!v) return "Required";
|
|
191
192
|
try {
|
|
192
|
-
new URL(v);
|
|
193
|
+
const u = new URL(v);
|
|
194
|
+
if (u.protocol !== "https:") return "Must be an HTTPS URL (required for Salesforce OAuth)";
|
|
193
195
|
} catch {
|
|
194
196
|
return "Must be a valid URL (e.g. https://routing.acme.com)";
|
|
195
197
|
}
|
|
@@ -211,16 +213,16 @@ async function collectConfig() {
|
|
|
211
213
|
}
|
|
212
214
|
});
|
|
213
215
|
if (isCancel2(engineUrl)) bail2(engineUrl);
|
|
214
|
-
const callbackUrl = `${appUrl}/api/auth/callback`;
|
|
216
|
+
const callbackUrl = `${appUrl.trim().replace(/\/+$/, "")}/api/auth/sfdc/callback`;
|
|
215
217
|
note2(
|
|
216
218
|
`You need a Salesforce Connected App. If you haven't created one yet:
|
|
217
219
|
|
|
218
220
|
1. Go to Salesforce Setup \u2192 App Manager \u2192 New Connected App
|
|
219
221
|
2. Connected App Name: Lead Routing
|
|
220
222
|
3. Check "Enable OAuth Settings"
|
|
221
|
-
4. Callback URL:
|
|
223
|
+
4. Callback URL (copy exactly \u2014 must match):
|
|
222
224
|
${callbackUrl}
|
|
223
|
-
5. Selected Scopes: api \u2022 refresh_token, offline_access
|
|
225
|
+
5. Selected Scopes: api \u2022 refresh_token, offline_access
|
|
224
226
|
6. Check "Require Secret for Web Server Flow"
|
|
225
227
|
7. Save \u2014 wait ~2 min, then click "Manage Consumer Details"
|
|
226
228
|
8. Copy the Consumer Key (Client ID) and Consumer Secret below`,
|
|
@@ -342,8 +344,8 @@ async function collectConfig() {
|
|
|
342
344
|
return {
|
|
343
345
|
appUrl: appUrl.trim().replace(/\/+$/, ""),
|
|
344
346
|
engineUrl: engineUrl.trim().replace(/\/+$/, ""),
|
|
345
|
-
sfdcClientId,
|
|
346
|
-
sfdcClientSecret,
|
|
347
|
+
sfdcClientId: sfdcClientId.trim(),
|
|
348
|
+
sfdcClientSecret: sfdcClientSecret.trim(),
|
|
347
349
|
sfdcLoginUrl,
|
|
348
350
|
orgAlias,
|
|
349
351
|
managedDb,
|
|
@@ -362,8 +364,9 @@ async function collectConfig() {
|
|
|
362
364
|
}
|
|
363
365
|
|
|
364
366
|
// src/steps/generate-files.ts
|
|
365
|
-
import { mkdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
366
|
-
import { join as join2 } from "path";
|
|
367
|
+
import { mkdirSync, writeFileSync as writeFileSync2, readFileSync as readFileSync2 } from "fs";
|
|
368
|
+
import { join as join2, dirname } from "path";
|
|
369
|
+
import { fileURLToPath } from "url";
|
|
367
370
|
import { log as log2 } from "@clack/prompts";
|
|
368
371
|
|
|
369
372
|
// src/templates/docker-compose.ts
|
|
@@ -618,6 +621,15 @@ function findInstallDir(startDir = process.cwd()) {
|
|
|
618
621
|
}
|
|
619
622
|
|
|
620
623
|
// src/steps/generate-files.ts
|
|
624
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
625
|
+
function getCliVersion() {
|
|
626
|
+
try {
|
|
627
|
+
const pkg = JSON.parse(readFileSync2(join2(__dirname, "../../package.json"), "utf8"));
|
|
628
|
+
return pkg.version ?? "0.1.0";
|
|
629
|
+
} catch {
|
|
630
|
+
return "0.1.0";
|
|
631
|
+
}
|
|
632
|
+
}
|
|
621
633
|
function generateFiles(cfg, sshCfg) {
|
|
622
634
|
const dir = join2(process.cwd(), "lead-routing");
|
|
623
635
|
mkdirSync(dir, { recursive: true });
|
|
@@ -681,7 +693,7 @@ function generateFiles(cfg, sshCfg) {
|
|
|
681
693
|
sfdcClientId: cfg.sfdcClientId,
|
|
682
694
|
sfdcLoginUrl: cfg.sfdcLoginUrl,
|
|
683
695
|
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
684
|
-
version:
|
|
696
|
+
version: getCliVersion()
|
|
685
697
|
});
|
|
686
698
|
log2.success("Generated lead-routing.json");
|
|
687
699
|
return { dir, composeFile, envWeb, envEngine, adminSecret: cfg.adminSecret };
|
|
@@ -905,11 +917,11 @@ function sleep(ms) {
|
|
|
905
917
|
import * as fs from "fs";
|
|
906
918
|
import * as path from "path";
|
|
907
919
|
import * as crypto from "crypto";
|
|
908
|
-
import { fileURLToPath } from "url";
|
|
920
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
909
921
|
import { execa as execa2 } from "execa";
|
|
910
922
|
import { spinner as spinner4 } from "@clack/prompts";
|
|
911
|
-
var __filename =
|
|
912
|
-
var
|
|
923
|
+
var __filename = fileURLToPath2(import.meta.url);
|
|
924
|
+
var __dirname2 = path.dirname(__filename);
|
|
913
925
|
function readEnvVar(envFile, key) {
|
|
914
926
|
const content = fs.readFileSync(envFile, "utf8");
|
|
915
927
|
const match = content.match(new RegExp(`^${key}=(.+)$`, "m"));
|
|
@@ -928,11 +940,11 @@ function findPrismaBin() {
|
|
|
928
940
|
// npx / npm global install: @lead-routing/cli is nested under the scope dir,
|
|
929
941
|
// so prisma lands 3 levels above dist/ in node_modules/.bin/
|
|
930
942
|
// e.g. ~/.npm/_npx/HASH/node_modules/.bin/prisma
|
|
931
|
-
path.join(
|
|
932
|
-
path.join(
|
|
943
|
+
path.join(__dirname2, "../../../.bin/prisma"),
|
|
944
|
+
path.join(__dirname2, "../../../prisma/bin/prisma.js"),
|
|
933
945
|
// Fallback: prisma nested inside the package's own node_modules (hoisted install)
|
|
934
|
-
path.join(
|
|
935
|
-
path.join(
|
|
946
|
+
path.join(__dirname2, "../node_modules/.bin/prisma"),
|
|
947
|
+
path.join(__dirname2, "../node_modules/prisma/bin/prisma.js"),
|
|
936
948
|
// Monorepo dev paths
|
|
937
949
|
path.resolve("packages/db/node_modules/.bin/prisma"),
|
|
938
950
|
path.resolve("node_modules/.bin/prisma"),
|
|
@@ -951,7 +963,9 @@ async function runMigrations(ssh, localDir, adminEmail, adminPassword) {
|
|
|
951
963
|
tunnelClose = close;
|
|
952
964
|
s.stop(`Database tunnel open (local port ${localPort})`);
|
|
953
965
|
await applyMigrations(localDir, localPort);
|
|
954
|
-
|
|
966
|
+
if (adminEmail && adminPassword) {
|
|
967
|
+
await seedAdminUser(localDir, localPort, adminEmail, adminPassword);
|
|
968
|
+
}
|
|
955
969
|
} finally {
|
|
956
970
|
tunnelClose?.();
|
|
957
971
|
}
|
|
@@ -962,7 +976,7 @@ async function applyMigrations(localDir, localPort) {
|
|
|
962
976
|
try {
|
|
963
977
|
const DATABASE_URL = getTunneledDbUrl(localDir, localPort);
|
|
964
978
|
const prismaBin = findPrismaBin();
|
|
965
|
-
const bundledSchema = path.join(
|
|
979
|
+
const bundledSchema = path.join(__dirname2, "prisma/schema.prisma");
|
|
966
980
|
const monoSchema = path.resolve("packages/db/prisma/schema.prisma");
|
|
967
981
|
const schemaPath = fs.existsSync(bundledSchema) ? bundledSchema : monoSchema;
|
|
968
982
|
await execa2(prismaBin, ["migrate", "deploy", "--schema", schemaPath], {
|
|
@@ -1086,13 +1100,13 @@ function sleep2(ms) {
|
|
|
1086
1100
|
}
|
|
1087
1101
|
|
|
1088
1102
|
// src/steps/sfdc-deploy-inline.ts
|
|
1089
|
-
import { readFileSync as
|
|
1090
|
-
import { join as join5, dirname as
|
|
1103
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, cpSync, rmSync } from "fs";
|
|
1104
|
+
import { join as join5, dirname as dirname3 } from "path";
|
|
1091
1105
|
import { tmpdir } from "os";
|
|
1092
|
-
import { fileURLToPath as
|
|
1106
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1093
1107
|
import { spinner as spinner6, log as log6 } from "@clack/prompts";
|
|
1094
1108
|
import { execa as execa3 } from "execa";
|
|
1095
|
-
var
|
|
1109
|
+
var __dirname3 = dirname3(fileURLToPath3(import.meta.url));
|
|
1096
1110
|
function patchXml(content, tag, value) {
|
|
1097
1111
|
const re = new RegExp(`(<${tag}>)[^<]*(</\\s*${tag}>)`, "g");
|
|
1098
1112
|
return content.replace(re, `$1${value}$2`);
|
|
@@ -1118,8 +1132,8 @@ async function sfdcDeployInline(params) {
|
|
|
1118
1132
|
}
|
|
1119
1133
|
}
|
|
1120
1134
|
s.start("Copying Salesforce package\u2026");
|
|
1121
|
-
const inDist = join5(
|
|
1122
|
-
const nextToDist = join5(
|
|
1135
|
+
const inDist = join5(__dirname3, "sfdc-package");
|
|
1136
|
+
const nextToDist = join5(__dirname3, "..", "sfdc-package");
|
|
1123
1137
|
const bundledPkg = existsSync4(inDist) ? inDist : nextToDist;
|
|
1124
1138
|
const destPkg = join5(installDir ?? tmpdir(), "lead-routing-sfdc-package");
|
|
1125
1139
|
if (!existsSync4(bundledPkg)) {
|
|
@@ -1141,7 +1155,7 @@ The CLI may need to be reinstalled: npm i -g @lead-routing/cli`
|
|
|
1141
1155
|
"RoutingEngine.namedCredential-meta.xml"
|
|
1142
1156
|
);
|
|
1143
1157
|
if (existsSync4(ncPath)) {
|
|
1144
|
-
const nc = patchXml(
|
|
1158
|
+
const nc = patchXml(readFileSync4(ncPath, "utf8"), "endpoint", engineUrl);
|
|
1145
1159
|
writeFileSync3(ncPath, nc, "utf8");
|
|
1146
1160
|
}
|
|
1147
1161
|
const rssEnginePath = join5(
|
|
@@ -1153,7 +1167,7 @@ The CLI may need to be reinstalled: npm i -g @lead-routing/cli`
|
|
|
1153
1167
|
"LeadRouterEngine.remoteSite-meta.xml"
|
|
1154
1168
|
);
|
|
1155
1169
|
if (existsSync4(rssEnginePath)) {
|
|
1156
|
-
let rss = patchXml(
|
|
1170
|
+
let rss = patchXml(readFileSync4(rssEnginePath, "utf8"), "url", engineUrl);
|
|
1157
1171
|
rss = patchXml(rss, "description", "Lead Router Engine endpoint");
|
|
1158
1172
|
writeFileSync3(rssEnginePath, rss, "utf8");
|
|
1159
1173
|
}
|
|
@@ -1166,7 +1180,7 @@ The CLI may need to be reinstalled: npm i -g @lead-routing/cli`
|
|
|
1166
1180
|
"LeadRouterApp.remoteSite-meta.xml"
|
|
1167
1181
|
);
|
|
1168
1182
|
if (existsSync4(rssAppPath)) {
|
|
1169
|
-
let rss = patchXml(
|
|
1183
|
+
let rss = patchXml(readFileSync4(rssAppPath, "utf8"), "url", appUrl);
|
|
1170
1184
|
rss = patchXml(rss, "description", "Lead Router App URL");
|
|
1171
1185
|
writeFileSync3(rssAppPath, rss, "utf8");
|
|
1172
1186
|
}
|
|
@@ -1494,13 +1508,97 @@ ${result.stderr || result.stdout}`
|
|
|
1494
1508
|
};
|
|
1495
1509
|
|
|
1496
1510
|
// src/commands/init.ts
|
|
1511
|
+
async function checkDnsResolvable(appUrl, engineUrl) {
|
|
1512
|
+
let hosts;
|
|
1513
|
+
try {
|
|
1514
|
+
hosts = [.../* @__PURE__ */ new Set([new URL(appUrl).hostname, new URL(engineUrl).hostname])];
|
|
1515
|
+
} catch {
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
for (const host of hosts) {
|
|
1519
|
+
try {
|
|
1520
|
+
await dns.lookup(host);
|
|
1521
|
+
} catch {
|
|
1522
|
+
log8.warn(
|
|
1523
|
+
`${chalk2.yellow(host)} does not resolve in DNS yet.
|
|
1524
|
+
Check for typos \u2014 a bad domain will cause a 2-minute timeout at step 8.`
|
|
1525
|
+
);
|
|
1526
|
+
const go = await confirm3({ message: "Continue anyway?", initialValue: true });
|
|
1527
|
+
if (isCancel4(go) || !go) {
|
|
1528
|
+
cancel3("Setup cancelled.");
|
|
1529
|
+
process.exit(0);
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1497
1534
|
async function runInit(options = {}) {
|
|
1498
1535
|
const dryRun = options.dryRun ?? false;
|
|
1536
|
+
const resume = options.resume ?? false;
|
|
1499
1537
|
console.log();
|
|
1500
1538
|
intro(
|
|
1501
|
-
chalk2.bold.cyan("Lead Routing \u2014 Self-Hosted Setup") + (dryRun ? chalk2.yellow(" [dry run]") : "")
|
|
1539
|
+
chalk2.bold.cyan("Lead Routing \u2014 Self-Hosted Setup") + (dryRun ? chalk2.yellow(" [dry run]") : "") + (resume ? chalk2.yellow(" [resume]") : "")
|
|
1502
1540
|
);
|
|
1503
1541
|
const ssh = new SshConnection();
|
|
1542
|
+
if (resume) {
|
|
1543
|
+
try {
|
|
1544
|
+
const dir = findInstallDir();
|
|
1545
|
+
if (!dir) {
|
|
1546
|
+
log8.error("No lead-routing.json found \u2014 run `lead-routing init` first.");
|
|
1547
|
+
process.exit(1);
|
|
1548
|
+
}
|
|
1549
|
+
const saved = readConfig(dir);
|
|
1550
|
+
let sshPassword;
|
|
1551
|
+
if (!saved.ssh.privateKeyPath) {
|
|
1552
|
+
const pw = await promptPassword({
|
|
1553
|
+
message: `SSH password for ${saved.ssh.username}@${saved.ssh.host}`
|
|
1554
|
+
});
|
|
1555
|
+
if (typeof pw === "symbol") process.exit(0);
|
|
1556
|
+
sshPassword = pw;
|
|
1557
|
+
}
|
|
1558
|
+
log8.step("Connecting to server");
|
|
1559
|
+
await ssh.connect({
|
|
1560
|
+
host: saved.ssh.host,
|
|
1561
|
+
port: saved.ssh.port,
|
|
1562
|
+
username: saved.ssh.username,
|
|
1563
|
+
privateKeyPath: saved.ssh.privateKeyPath,
|
|
1564
|
+
password: sshPassword,
|
|
1565
|
+
remoteDir: saved.remoteDir
|
|
1566
|
+
});
|
|
1567
|
+
log8.success(`Connected to ${saved.ssh.host}`);
|
|
1568
|
+
const remoteDir = await ssh.resolveHome(saved.remoteDir);
|
|
1569
|
+
log8.step("Step 8/9 Verifying health");
|
|
1570
|
+
await verifyHealth(saved.appUrl, saved.engineUrl, ssh, remoteDir);
|
|
1571
|
+
log8.step("Step 9/9 Deploying Salesforce package");
|
|
1572
|
+
await sfdcDeployInline({
|
|
1573
|
+
appUrl: saved.appUrl,
|
|
1574
|
+
engineUrl: saved.engineUrl,
|
|
1575
|
+
orgAlias: "lead-routing",
|
|
1576
|
+
sfdcClientId: saved.sfdcClientId ?? "",
|
|
1577
|
+
sfdcLoginUrl: saved.sfdcLoginUrl ?? "https://login.salesforce.com",
|
|
1578
|
+
installDir: dir
|
|
1579
|
+
});
|
|
1580
|
+
await guideAppLauncherSetup(saved.appUrl);
|
|
1581
|
+
outro(
|
|
1582
|
+
chalk2.green("\u2714 You're live!") + `
|
|
1583
|
+
|
|
1584
|
+
Dashboard: ${chalk2.cyan(saved.appUrl)}
|
|
1585
|
+
Routing engine: ${chalk2.cyan(saved.engineUrl)}
|
|
1586
|
+
|
|
1587
|
+
` + chalk2.bold(" Next steps:\n") + ` ${chalk2.cyan("1.")} Open ${chalk2.cyan(saved.appUrl)} and log in
|
|
1588
|
+
${chalk2.cyan("2.")} Create your first routing rule to start routing leads
|
|
1589
|
+
|
|
1590
|
+
Run ${chalk2.cyan("lead-routing doctor")} to check service health at any time.
|
|
1591
|
+
Run ${chalk2.cyan("lead-routing deploy")} to update to a new version.`
|
|
1592
|
+
);
|
|
1593
|
+
} catch (err) {
|
|
1594
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1595
|
+
log8.error(`Resume failed: ${message}`);
|
|
1596
|
+
process.exit(1);
|
|
1597
|
+
} finally {
|
|
1598
|
+
await ssh.disconnect();
|
|
1599
|
+
}
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1504
1602
|
try {
|
|
1505
1603
|
log8.step("Step 1/9 Checking local prerequisites");
|
|
1506
1604
|
await checkPrerequisites();
|
|
@@ -1508,6 +1606,7 @@ async function runInit(options = {}) {
|
|
|
1508
1606
|
const sshCfg = await collectSshConfig();
|
|
1509
1607
|
log8.step("Step 3/9 Configuration");
|
|
1510
1608
|
const cfg = await collectConfig();
|
|
1609
|
+
await checkDnsResolvable(cfg.appUrl, cfg.engineUrl);
|
|
1511
1610
|
log8.step("Step 4/9 Generating config files");
|
|
1512
1611
|
const { dir, adminSecret } = generateFiles(cfg, sshCfg);
|
|
1513
1612
|
note4(
|
|
@@ -1575,7 +1674,7 @@ Files created: docker-compose.yml, Caddyfile, .env.web, .env.engine, lead-routin
|
|
|
1575
1674
|
import { writeFileSync as writeFileSync4, unlinkSync } from "fs";
|
|
1576
1675
|
import { join as join6 } from "path";
|
|
1577
1676
|
import { tmpdir as tmpdir2 } from "os";
|
|
1578
|
-
import { intro as intro2, outro as outro2, log as log9, password as
|
|
1677
|
+
import { intro as intro2, outro as outro2, log as log9, password as promptPassword2 } from "@clack/prompts";
|
|
1579
1678
|
import chalk3 from "chalk";
|
|
1580
1679
|
async function runDeploy() {
|
|
1581
1680
|
console.log();
|
|
@@ -1591,7 +1690,7 @@ async function runDeploy() {
|
|
|
1591
1690
|
const ssh = new SshConnection();
|
|
1592
1691
|
let sshPassword;
|
|
1593
1692
|
if (!cfg.ssh.privateKeyPath) {
|
|
1594
|
-
const pw = await
|
|
1693
|
+
const pw = await promptPassword2({
|
|
1595
1694
|
message: `SSH password for ${cfg.ssh.username}@${cfg.ssh.host}`
|
|
1596
1695
|
});
|
|
1597
1696
|
if (typeof pw === "symbol") process.exit(0);
|
|
@@ -1628,7 +1727,7 @@ async function runDeploy() {
|
|
|
1628
1727
|
await ssh.exec("docker compose up -d --remove-orphans", remoteDir);
|
|
1629
1728
|
log9.success("Services restarted");
|
|
1630
1729
|
log9.step("Running database migrations");
|
|
1631
|
-
await runMigrations(ssh, dir
|
|
1730
|
+
await runMigrations(ssh, dir);
|
|
1632
1731
|
outro2(
|
|
1633
1732
|
chalk3.green("\u2714 Deployment complete!") + `
|
|
1634
1733
|
|
|
@@ -1780,7 +1879,7 @@ async function runStatus() {
|
|
|
1780
1879
|
}
|
|
1781
1880
|
|
|
1782
1881
|
// src/commands/config.ts
|
|
1783
|
-
import { readFileSync as
|
|
1882
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync5 } from "fs";
|
|
1784
1883
|
import { join as join7 } from "path";
|
|
1785
1884
|
import { intro as intro4, outro as outro4, text as text3, password as password3, spinner as spinner7, log as log13 } from "@clack/prompts";
|
|
1786
1885
|
import chalk5 from "chalk";
|
|
@@ -1788,7 +1887,7 @@ import { execa as execa7 } from "execa";
|
|
|
1788
1887
|
function parseEnv(filePath) {
|
|
1789
1888
|
const map = /* @__PURE__ */ new Map();
|
|
1790
1889
|
if (!existsSync5(filePath)) return map;
|
|
1791
|
-
for (const line of
|
|
1890
|
+
for (const line of readFileSync5(filePath, "utf8").split("\n")) {
|
|
1792
1891
|
const trimmed = line.trim();
|
|
1793
1892
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1794
1893
|
const eq = trimmed.indexOf("=");
|
|
@@ -1798,7 +1897,7 @@ function parseEnv(filePath) {
|
|
|
1798
1897
|
return map;
|
|
1799
1898
|
}
|
|
1800
1899
|
function writeEnv(filePath, updates) {
|
|
1801
|
-
const lines = existsSync5(filePath) ?
|
|
1900
|
+
const lines = existsSync5(filePath) ? readFileSync5(filePath, "utf8").split("\n") : [];
|
|
1802
1901
|
const updated = /* @__PURE__ */ new Set();
|
|
1803
1902
|
const result = lines.map((line) => {
|
|
1804
1903
|
const trimmed = line.trim();
|
|
@@ -1956,14 +2055,10 @@ async function runSfdcDeploy() {
|
|
|
1956
2055
|
log14.error(err instanceof Error ? err.message : String(err));
|
|
1957
2056
|
process.exit(1);
|
|
1958
2057
|
}
|
|
2058
|
+
await guideAppLauncherSetup(appUrl);
|
|
1959
2059
|
outro5(
|
|
1960
2060
|
chalk6.green("\u2714 Salesforce package deployed!") + `
|
|
1961
2061
|
|
|
1962
|
-
Next steps:
|
|
1963
|
-
1. In Salesforce, open App Launcher \u2192 search "Lead Router Setup"
|
|
1964
|
-
2. Click "Connect to Lead Router" to authorise the OAuth connection
|
|
1965
|
-
3. Follow the 4-step wizard to activate triggers and sync field schema
|
|
1966
|
-
|
|
1967
2062
|
Your Lead Router dashboard: ${chalk6.cyan(appUrl)}`
|
|
1968
2063
|
);
|
|
1969
2064
|
}
|
|
@@ -1971,7 +2066,7 @@ async function runSfdcDeploy() {
|
|
|
1971
2066
|
// src/index.ts
|
|
1972
2067
|
var program = new Command();
|
|
1973
2068
|
program.name("lead-routing").description("Self-hosted Lead Routing \u2014 scaffold, deploy, and manage your installation").version("0.1.0");
|
|
1974
|
-
program.command("init").description("Interactive setup wizard \u2014 configure and deploy the full Lead Routing stack").option("--dry-run", "Run the wizard and generate config files without starting Docker services").action((opts) => runInit({ dryRun: opts.dryRun }));
|
|
2069
|
+
program.command("init").description("Interactive setup wizard \u2014 configure and deploy the full Lead Routing stack").option("--dry-run", "Run the wizard and generate config files without starting Docker services").option("--resume", "Skip steps 1-7 and resume from health check using existing lead-routing.json").action((opts) => runInit({ dryRun: opts.dryRun, resume: opts.resume }));
|
|
1975
2070
|
program.command("deploy").description("Pull latest images, restart services, and run any pending migrations").action(runDeploy);
|
|
1976
2071
|
program.command("doctor").description("Check the health of all services in your installation").action(runDoctor);
|
|
1977
2072
|
program.command("logs [service]").description("Stream logs from a service (web, engine, postgres, redis). Defaults to engine.").action((service) => runLogs(service));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lead-routing/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Self-hosted deployment CLI for Lead Routing",
|
|
5
5
|
"homepage": "https://github.com/lead-routing/lead-routing",
|
|
6
6
|
"keywords": ["salesforce", "lead-routing", "self-hosted", "deployment", "cli"],
|