@lead-routing/cli 0.1.9 → 0.1.11
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 +111 -113
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -69,16 +69,7 @@ async function checkSalesforceCLI() {
|
|
|
69
69
|
// src/steps/collect-ssh-config.ts
|
|
70
70
|
import { existsSync } from "fs";
|
|
71
71
|
import { homedir } from "os";
|
|
72
|
-
import { join } from "path";
|
|
73
72
|
import { text, password, note, log as log2, cancel, isCancel } from "@clack/prompts";
|
|
74
|
-
var DEFAULT_KEYS = [
|
|
75
|
-
join(homedir(), ".ssh", "id_ed25519"),
|
|
76
|
-
join(homedir(), ".ssh", "id_rsa"),
|
|
77
|
-
join(homedir(), ".ssh", "id_ecdsa")
|
|
78
|
-
];
|
|
79
|
-
function detectDefaultKey() {
|
|
80
|
-
return DEFAULT_KEYS.find(existsSync);
|
|
81
|
-
}
|
|
82
73
|
function bail(value) {
|
|
83
74
|
if (isCancel(value)) {
|
|
84
75
|
cancel("Setup cancelled.");
|
|
@@ -88,7 +79,7 @@ function bail(value) {
|
|
|
88
79
|
}
|
|
89
80
|
async function collectSshConfig(opts = {}) {
|
|
90
81
|
note(
|
|
91
|
-
"The CLI will SSH into your server to deploy the full stack.\nYou will need:\n \u2022 Server hostname or IP address\n \u2022 SSH
|
|
82
|
+
"The CLI will SSH into your server to deploy the full stack.\nYou will need:\n \u2022 Server hostname or IP address\n \u2022 SSH password for your server (root access)\n \u2022 A fresh Linux VPS (Ubuntu/Debian/CentOS) \u2014 Docker installed automatically",
|
|
92
83
|
"Server connection"
|
|
93
84
|
);
|
|
94
85
|
const host = await text({
|
|
@@ -108,19 +99,12 @@ async function collectSshConfig(opts = {}) {
|
|
|
108
99
|
privateKeyPath = resolved;
|
|
109
100
|
log2.info(`Using SSH key: ${opts.sshKey}`);
|
|
110
101
|
} else {
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const p = await password({
|
|
118
|
-
message: `SSH password for ${opts.sshUser ?? "root"}@${host}`,
|
|
119
|
-
validate: (v) => !v ? "Required" : void 0
|
|
120
|
-
});
|
|
121
|
-
if (isCancel(p)) bail(p);
|
|
122
|
-
pwd = p;
|
|
123
|
-
}
|
|
102
|
+
const p = await password({
|
|
103
|
+
message: `SSH password for ${opts.sshUser ?? "root"}@${host}`,
|
|
104
|
+
validate: (v) => !v ? "Required" : void 0
|
|
105
|
+
});
|
|
106
|
+
if (isCancel(p)) bail(p);
|
|
107
|
+
pwd = p;
|
|
124
108
|
}
|
|
125
109
|
return {
|
|
126
110
|
host,
|
|
@@ -258,7 +242,7 @@ async function collectConfig(opts = {}) {
|
|
|
258
242
|
|
|
259
243
|
// src/steps/generate-files.ts
|
|
260
244
|
import { mkdirSync, writeFileSync as writeFileSync2, readFileSync as readFileSync2 } from "fs";
|
|
261
|
-
import { join as
|
|
245
|
+
import { join as join2, dirname } from "path";
|
|
262
246
|
import { fileURLToPath } from "url";
|
|
263
247
|
import { log as log3 } from "@clack/prompts";
|
|
264
248
|
|
|
@@ -489,9 +473,9 @@ function renderCaddyfile(appUrl, engineUrl) {
|
|
|
489
473
|
|
|
490
474
|
// src/utils/config.ts
|
|
491
475
|
import { readFileSync, writeFileSync, existsSync as existsSync2 } from "fs";
|
|
492
|
-
import { join
|
|
476
|
+
import { join } from "path";
|
|
493
477
|
function getConfigPath(dir) {
|
|
494
|
-
return
|
|
478
|
+
return join(dir, "lead-routing.json");
|
|
495
479
|
}
|
|
496
480
|
function readConfig(dir) {
|
|
497
481
|
const path2 = getConfigPath(dir);
|
|
@@ -506,10 +490,10 @@ function writeConfig(dir, config2) {
|
|
|
506
490
|
writeFileSync(getConfigPath(dir), JSON.stringify(config2, null, 2), "utf8");
|
|
507
491
|
}
|
|
508
492
|
function findInstallDir(startDir = process.cwd()) {
|
|
509
|
-
const candidate =
|
|
493
|
+
const candidate = join(startDir, "lead-routing.json");
|
|
510
494
|
if (existsSync2(candidate)) return startDir;
|
|
511
|
-
const nested =
|
|
512
|
-
if (existsSync2(nested)) return
|
|
495
|
+
const nested = join(startDir, "lead-routing", "lead-routing.json");
|
|
496
|
+
if (existsSync2(nested)) return join(startDir, "lead-routing");
|
|
513
497
|
return null;
|
|
514
498
|
}
|
|
515
499
|
|
|
@@ -517,14 +501,14 @@ function findInstallDir(startDir = process.cwd()) {
|
|
|
517
501
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
518
502
|
function getCliVersion() {
|
|
519
503
|
try {
|
|
520
|
-
const pkg = JSON.parse(readFileSync2(
|
|
504
|
+
const pkg = JSON.parse(readFileSync2(join2(__dirname, "../../package.json"), "utf8"));
|
|
521
505
|
return pkg.version ?? "0.1.0";
|
|
522
506
|
} catch {
|
|
523
507
|
return "0.1.0";
|
|
524
508
|
}
|
|
525
509
|
}
|
|
526
510
|
function generateFiles(cfg, sshCfg) {
|
|
527
|
-
const dir =
|
|
511
|
+
const dir = join2(process.cwd(), "lead-routing");
|
|
528
512
|
mkdirSync(dir, { recursive: true });
|
|
529
513
|
const dockerEngineUrl = `http://engine:3001`;
|
|
530
514
|
const composeContent = renderDockerCompose({
|
|
@@ -532,11 +516,11 @@ function generateFiles(cfg, sshCfg) {
|
|
|
532
516
|
managedRedis: cfg.managedRedis,
|
|
533
517
|
dbPassword: cfg.dbPassword
|
|
534
518
|
});
|
|
535
|
-
const composeFile =
|
|
519
|
+
const composeFile = join2(dir, "docker-compose.yml");
|
|
536
520
|
writeFileSync2(composeFile, composeContent, "utf8");
|
|
537
521
|
log3.success("Generated docker-compose.yml");
|
|
538
522
|
const caddyfileContent = renderCaddyfile(cfg.appUrl, cfg.engineUrl);
|
|
539
|
-
writeFileSync2(
|
|
523
|
+
writeFileSync2(join2(dir, "Caddyfile"), caddyfileContent, "utf8");
|
|
540
524
|
log3.success("Generated Caddyfile");
|
|
541
525
|
const envWebContent = renderEnvWeb({
|
|
542
526
|
appUrl: cfg.appUrl,
|
|
@@ -552,7 +536,7 @@ function generateFiles(cfg, sshCfg) {
|
|
|
552
536
|
resendApiKey: cfg.resendApiKey || void 0,
|
|
553
537
|
feedbackToEmail: cfg.feedbackToEmail || void 0
|
|
554
538
|
});
|
|
555
|
-
const envWeb =
|
|
539
|
+
const envWeb = join2(dir, ".env.web");
|
|
556
540
|
writeFileSync2(envWeb, envWebContent, "utf8");
|
|
557
541
|
log3.success("Generated .env.web");
|
|
558
542
|
const envEngineContent = renderEnvEngine({
|
|
@@ -563,7 +547,7 @@ function generateFiles(cfg, sshCfg) {
|
|
|
563
547
|
sfdcLoginUrl: cfg.sfdcLoginUrl,
|
|
564
548
|
engineWebhookSecret: cfg.engineWebhookSecret
|
|
565
549
|
});
|
|
566
|
-
const envEngine =
|
|
550
|
+
const envEngine = join2(dir, ".env.engine");
|
|
567
551
|
writeFileSync2(envEngine, envEngineContent, "utf8");
|
|
568
552
|
log3.success("Generated .env.engine");
|
|
569
553
|
writeConfig(dir, {
|
|
@@ -593,14 +577,15 @@ function generateFiles(cfg, sshCfg) {
|
|
|
593
577
|
}
|
|
594
578
|
|
|
595
579
|
// src/steps/check-remote-prerequisites.ts
|
|
596
|
-
import { log as log4 } from "@clack/prompts";
|
|
580
|
+
import { log as log4, spinner as spinner2 } from "@clack/prompts";
|
|
597
581
|
async function checkRemotePrerequisites(ssh) {
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
|
|
582
|
+
const dockerResult = await checkOrInstallDocker(ssh);
|
|
583
|
+
const composeResult = await checkRemoteDockerCompose(ssh);
|
|
584
|
+
const portResults = await Promise.all([
|
|
601
585
|
checkRemotePort(ssh, 80),
|
|
602
586
|
checkRemotePort(ssh, 443)
|
|
603
587
|
]);
|
|
588
|
+
const results = [dockerResult, composeResult, ...portResults];
|
|
604
589
|
const failed = results.filter((r) => !r.ok && !r.warn);
|
|
605
590
|
const warnings = results.filter((r) => !r.ok && r.warn);
|
|
606
591
|
for (const r of results) {
|
|
@@ -620,30 +605,65 @@ async function checkRemotePrerequisites(ssh) {
|
|
|
620
605
|
`Remote server is missing required software:
|
|
621
606
|
` + failed.map((r) => ` \u2022 ${r.label}`).join("\n") + `
|
|
622
607
|
|
|
623
|
-
|
|
608
|
+
Fix the issues above and re-run lead-routing init.`
|
|
624
609
|
);
|
|
625
610
|
}
|
|
626
611
|
}
|
|
627
|
-
async function
|
|
628
|
-
const { stdout, code } = await ssh.execSilent("docker --version");
|
|
629
|
-
if (code
|
|
612
|
+
async function checkOrInstallDocker(ssh) {
|
|
613
|
+
const { stdout, code } = await ssh.execSilent("docker --version 2>/dev/null");
|
|
614
|
+
if (code === 0 && stdout) {
|
|
615
|
+
const match = stdout.match(/Docker version (\d+)/);
|
|
616
|
+
if (match && parseInt(match[1], 10) < 24) {
|
|
617
|
+
return { ok: false, label: `Docker ${stdout.trim()} \u2014 version 24+ required` };
|
|
618
|
+
}
|
|
619
|
+
return { ok: true, label: `Docker \u2014 ${stdout.trim()}` };
|
|
620
|
+
}
|
|
621
|
+
const s = spinner2();
|
|
622
|
+
s.start("Docker not found \u2014 installing via get.docker.com (~2 min)\u2026");
|
|
623
|
+
try {
|
|
624
|
+
const { code: curlCode } = await ssh.execSilent("command -v curl 2>/dev/null");
|
|
625
|
+
if (curlCode !== 0) {
|
|
626
|
+
await ssh.execSilent(
|
|
627
|
+
"apt-get install -y curl 2>/dev/null || yum install -y curl 2>/dev/null"
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
const { code: installCode } = await ssh.execSilent(
|
|
631
|
+
"curl -fsSL https://get.docker.com | sh 2>&1"
|
|
632
|
+
);
|
|
633
|
+
if (installCode !== 0) {
|
|
634
|
+
s.stop("Docker auto-install failed");
|
|
635
|
+
return {
|
|
636
|
+
ok: false,
|
|
637
|
+
label: "Docker \u2014 auto-install failed.\n SSH in and run manually: curl -fsSL https://get.docker.com | sh"
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
await ssh.execSilent(
|
|
641
|
+
"systemctl start docker 2>/dev/null; systemctl enable docker 2>/dev/null"
|
|
642
|
+
);
|
|
643
|
+
const { stdout: ver, code: verCode } = await ssh.execSilent("docker --version 2>/dev/null");
|
|
644
|
+
if (verCode !== 0 || !ver) {
|
|
645
|
+
s.stop("Docker installed but not responding");
|
|
646
|
+
return {
|
|
647
|
+
ok: false,
|
|
648
|
+
label: "Docker \u2014 installed but daemon not responding. Try rebooting the server."
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
s.stop(`Docker installed \u2014 ${ver.trim()}`);
|
|
652
|
+
return { ok: true, label: `Docker \u2014 installed ${ver.trim()}` };
|
|
653
|
+
} catch (err) {
|
|
654
|
+
s.stop("Docker auto-install failed");
|
|
630
655
|
return {
|
|
631
656
|
ok: false,
|
|
632
|
-
label:
|
|
657
|
+
label: `Docker \u2014 auto-install failed: ${err instanceof Error ? err.message : String(err)}`
|
|
633
658
|
};
|
|
634
659
|
}
|
|
635
|
-
const match = stdout.match(/Docker version (\d+)/);
|
|
636
|
-
if (match && parseInt(match[1], 10) < 24) {
|
|
637
|
-
return { ok: false, label: `Docker ${stdout.trim()} \u2014 version 24+ required` };
|
|
638
|
-
}
|
|
639
|
-
return { ok: true, label: `Docker \u2014 ${stdout.trim()}` };
|
|
640
660
|
}
|
|
641
661
|
async function checkRemoteDockerCompose(ssh) {
|
|
642
|
-
const { stdout, code } = await ssh.execSilent("docker compose version");
|
|
662
|
+
const { stdout, code } = await ssh.execSilent("docker compose version 2>/dev/null");
|
|
643
663
|
if (code !== 0 || !stdout) {
|
|
644
664
|
return {
|
|
645
665
|
ok: false,
|
|
646
|
-
label: "Docker Compose \u2014 not found
|
|
666
|
+
label: "Docker Compose \u2014 not found (update Docker to include Compose v2)"
|
|
647
667
|
};
|
|
648
668
|
}
|
|
649
669
|
return { ok: true, label: `Docker Compose \u2014 ${stdout.trim()}` };
|
|
@@ -696,10 +716,10 @@ async function checkRemotePort(ssh, port) {
|
|
|
696
716
|
}
|
|
697
717
|
|
|
698
718
|
// src/steps/upload-files.ts
|
|
699
|
-
import { join as
|
|
700
|
-
import { spinner as
|
|
719
|
+
import { join as join3 } from "path";
|
|
720
|
+
import { spinner as spinner3 } from "@clack/prompts";
|
|
701
721
|
async function uploadFiles(ssh, localDir, remoteDir) {
|
|
702
|
-
const s =
|
|
722
|
+
const s = spinner3();
|
|
703
723
|
s.start("Uploading config files to server");
|
|
704
724
|
try {
|
|
705
725
|
await ssh.mkdir(remoteDir);
|
|
@@ -712,7 +732,7 @@ async function uploadFiles(ssh, localDir, remoteDir) {
|
|
|
712
732
|
];
|
|
713
733
|
await ssh.upload(
|
|
714
734
|
filenames.map((f) => ({
|
|
715
|
-
local:
|
|
735
|
+
local: join3(localDir, f),
|
|
716
736
|
remote: `${remoteDir}/${f}`
|
|
717
737
|
}))
|
|
718
738
|
);
|
|
@@ -724,7 +744,7 @@ async function uploadFiles(ssh, localDir, remoteDir) {
|
|
|
724
744
|
}
|
|
725
745
|
|
|
726
746
|
// src/steps/start-services.ts
|
|
727
|
-
import { spinner as
|
|
747
|
+
import { spinner as spinner4, log as log5 } from "@clack/prompts";
|
|
728
748
|
async function startServices(ssh, remoteDir) {
|
|
729
749
|
await wipeStalePostgresVolume(ssh, remoteDir);
|
|
730
750
|
await pullImages(ssh, remoteDir);
|
|
@@ -738,7 +758,7 @@ async function wipeStalePostgresVolume(ssh, remoteDir) {
|
|
|
738
758
|
if (code !== 0) {
|
|
739
759
|
return;
|
|
740
760
|
}
|
|
741
|
-
const s =
|
|
761
|
+
const s = spinner4();
|
|
742
762
|
s.start("Removing existing database volume for clean install");
|
|
743
763
|
try {
|
|
744
764
|
await ssh.exec("docker compose down -v --remove-orphans", remoteDir);
|
|
@@ -752,7 +772,7 @@ async function wipeStalePostgresVolume(ssh, remoteDir) {
|
|
|
752
772
|
}
|
|
753
773
|
}
|
|
754
774
|
async function pullImages(ssh, remoteDir) {
|
|
755
|
-
const s =
|
|
775
|
+
const s = spinner4();
|
|
756
776
|
s.start("Pulling Docker images on server (this may take a few minutes)");
|
|
757
777
|
try {
|
|
758
778
|
await ssh.exec("docker compose pull", remoteDir);
|
|
@@ -765,7 +785,7 @@ async function pullImages(ssh, remoteDir) {
|
|
|
765
785
|
}
|
|
766
786
|
}
|
|
767
787
|
async function startContainers(ssh, remoteDir) {
|
|
768
|
-
const s =
|
|
788
|
+
const s = spinner4();
|
|
769
789
|
s.start("Starting services");
|
|
770
790
|
try {
|
|
771
791
|
await ssh.exec("docker compose up -d --remove-orphans", remoteDir);
|
|
@@ -776,7 +796,7 @@ async function startContainers(ssh, remoteDir) {
|
|
|
776
796
|
}
|
|
777
797
|
}
|
|
778
798
|
async function waitForPostgres(ssh, remoteDir) {
|
|
779
|
-
const s =
|
|
799
|
+
const s = spinner4();
|
|
780
800
|
s.start("Waiting for PostgreSQL to be ready");
|
|
781
801
|
const maxAttempts = 24;
|
|
782
802
|
let containerReady = false;
|
|
@@ -821,7 +841,7 @@ import * as path from "path";
|
|
|
821
841
|
import * as crypto from "crypto";
|
|
822
842
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
823
843
|
import { execa as execa2 } from "execa";
|
|
824
|
-
import { spinner as
|
|
844
|
+
import { spinner as spinner5 } from "@clack/prompts";
|
|
825
845
|
var __filename = fileURLToPath2(import.meta.url);
|
|
826
846
|
var __dirname2 = path.dirname(__filename);
|
|
827
847
|
function readEnvVar(envFile, key) {
|
|
@@ -857,7 +877,7 @@ function findPrismaBin() {
|
|
|
857
877
|
return found;
|
|
858
878
|
}
|
|
859
879
|
async function runMigrations(ssh, localDir, adminEmail, adminPassword) {
|
|
860
|
-
const s =
|
|
880
|
+
const s = spinner5();
|
|
861
881
|
s.start("Opening secure tunnel to database");
|
|
862
882
|
let tunnelClose;
|
|
863
883
|
try {
|
|
@@ -873,7 +893,7 @@ async function runMigrations(ssh, localDir, adminEmail, adminPassword) {
|
|
|
873
893
|
}
|
|
874
894
|
}
|
|
875
895
|
async function applyMigrations(localDir, localPort) {
|
|
876
|
-
const s =
|
|
896
|
+
const s = spinner5();
|
|
877
897
|
s.start("Running database migrations");
|
|
878
898
|
try {
|
|
879
899
|
const DATABASE_URL = getTunneledDbUrl(localDir, localPort);
|
|
@@ -891,7 +911,7 @@ async function applyMigrations(localDir, localPort) {
|
|
|
891
911
|
}
|
|
892
912
|
}
|
|
893
913
|
async function seedAdminUser(localDir, localPort, adminEmail, adminPassword) {
|
|
894
|
-
const s =
|
|
914
|
+
const s = spinner5();
|
|
895
915
|
s.start("Creating admin user");
|
|
896
916
|
try {
|
|
897
917
|
const DATABASE_URL = getTunneledDbUrl(localDir, localPort);
|
|
@@ -924,7 +944,7 @@ ON CONFLICT ("orgId", email) DO NOTHING;
|
|
|
924
944
|
}
|
|
925
945
|
|
|
926
946
|
// src/steps/verify-health.ts
|
|
927
|
-
import { spinner as
|
|
947
|
+
import { spinner as spinner6, log as log6 } from "@clack/prompts";
|
|
928
948
|
async function verifyHealth(appUrl, engineUrl, ssh, remoteDir) {
|
|
929
949
|
const checks = [
|
|
930
950
|
{ service: "Web app", url: `${appUrl}/api/health` },
|
|
@@ -973,7 +993,7 @@ To resume once fixed:
|
|
|
973
993
|
);
|
|
974
994
|
}
|
|
975
995
|
async function pollHealth(service, url, maxAttempts = 24, intervalMs = 5e3) {
|
|
976
|
-
const s =
|
|
996
|
+
const s = spinner6();
|
|
977
997
|
s.start(`Waiting for ${service}`);
|
|
978
998
|
for (let i = 0; i < maxAttempts; i++) {
|
|
979
999
|
try {
|
|
@@ -1003,10 +1023,10 @@ function sleep2(ms) {
|
|
|
1003
1023
|
|
|
1004
1024
|
// src/steps/sfdc-deploy-inline.ts
|
|
1005
1025
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, cpSync, rmSync } from "fs";
|
|
1006
|
-
import { join as
|
|
1026
|
+
import { join as join5, dirname as dirname3 } from "path";
|
|
1007
1027
|
import { tmpdir } from "os";
|
|
1008
1028
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1009
|
-
import { spinner as
|
|
1029
|
+
import { spinner as spinner7, log as log7 } from "@clack/prompts";
|
|
1010
1030
|
import { execa as execa3 } from "execa";
|
|
1011
1031
|
var __dirname3 = dirname3(fileURLToPath3(import.meta.url));
|
|
1012
1032
|
function patchXml(content, tag, value) {
|
|
@@ -1015,7 +1035,7 @@ function patchXml(content, tag, value) {
|
|
|
1015
1035
|
}
|
|
1016
1036
|
async function sfdcDeployInline(params) {
|
|
1017
1037
|
const { appUrl, engineUrl, orgAlias, installDir } = params;
|
|
1018
|
-
const s =
|
|
1038
|
+
const s = spinner7();
|
|
1019
1039
|
const { code: authCheck } = await execa3(
|
|
1020
1040
|
"sf",
|
|
1021
1041
|
["org", "display", "--target-org", orgAlias, "--json"],
|
|
@@ -1034,10 +1054,10 @@ async function sfdcDeployInline(params) {
|
|
|
1034
1054
|
}
|
|
1035
1055
|
}
|
|
1036
1056
|
s.start("Copying Salesforce package\u2026");
|
|
1037
|
-
const inDist =
|
|
1038
|
-
const nextToDist =
|
|
1057
|
+
const inDist = join5(__dirname3, "sfdc-package");
|
|
1058
|
+
const nextToDist = join5(__dirname3, "..", "sfdc-package");
|
|
1039
1059
|
const bundledPkg = existsSync4(inDist) ? inDist : nextToDist;
|
|
1040
|
-
const destPkg =
|
|
1060
|
+
const destPkg = join5(installDir ?? tmpdir(), "lead-routing-sfdc-package");
|
|
1041
1061
|
if (!existsSync4(bundledPkg)) {
|
|
1042
1062
|
s.stop("sfdc-package not found in CLI bundle");
|
|
1043
1063
|
throw new Error(
|
|
@@ -1048,7 +1068,7 @@ The CLI may need to be reinstalled: npm i -g @lead-routing/cli`
|
|
|
1048
1068
|
if (existsSync4(destPkg)) rmSync(destPkg, { recursive: true, force: true });
|
|
1049
1069
|
cpSync(bundledPkg, destPkg, { recursive: true });
|
|
1050
1070
|
s.stop("Package copied");
|
|
1051
|
-
const ncPath =
|
|
1071
|
+
const ncPath = join5(
|
|
1052
1072
|
destPkg,
|
|
1053
1073
|
"force-app",
|
|
1054
1074
|
"main",
|
|
@@ -1060,7 +1080,7 @@ The CLI may need to be reinstalled: npm i -g @lead-routing/cli`
|
|
|
1060
1080
|
const nc = patchXml(readFileSync4(ncPath, "utf8"), "endpoint", engineUrl);
|
|
1061
1081
|
writeFileSync3(ncPath, nc, "utf8");
|
|
1062
1082
|
}
|
|
1063
|
-
const rssEnginePath =
|
|
1083
|
+
const rssEnginePath = join5(
|
|
1064
1084
|
destPkg,
|
|
1065
1085
|
"force-app",
|
|
1066
1086
|
"main",
|
|
@@ -1073,7 +1093,7 @@ The CLI may need to be reinstalled: npm i -g @lead-routing/cli`
|
|
|
1073
1093
|
rss = patchXml(rss, "description", "Lead Router Engine endpoint");
|
|
1074
1094
|
writeFileSync3(rssEnginePath, rss, "utf8");
|
|
1075
1095
|
}
|
|
1076
|
-
const rssAppPath =
|
|
1096
|
+
const rssAppPath = join5(
|
|
1077
1097
|
destPkg,
|
|
1078
1098
|
"force-app",
|
|
1079
1099
|
"main",
|
|
@@ -1175,7 +1195,7 @@ The CLI may need to be reinstalled: npm i -g @lead-routing/cli`
|
|
|
1175
1195
|
}
|
|
1176
1196
|
async function loginViaAppBridge(rawAppUrl, orgAlias) {
|
|
1177
1197
|
const appUrl = rawAppUrl.replace(/\/+$/, "");
|
|
1178
|
-
const s =
|
|
1198
|
+
const s = spinner7();
|
|
1179
1199
|
s.start("Starting Salesforce authentication via your Lead Router app\u2026");
|
|
1180
1200
|
let sessionId;
|
|
1181
1201
|
let authUrl;
|
|
@@ -1505,7 +1525,7 @@ async function runInit(options = {}) {
|
|
|
1505
1525
|
log9.step("Step 1/9 Checking local prerequisites");
|
|
1506
1526
|
await checkPrerequisites();
|
|
1507
1527
|
log9.step("Step 2/9 SSH connection");
|
|
1508
|
-
|
|
1528
|
+
const sshCfg = await collectSshConfig({
|
|
1509
1529
|
sshPort: options.sshPort,
|
|
1510
1530
|
sshUser: options.sshUser,
|
|
1511
1531
|
sshKey: options.sshKey,
|
|
@@ -1516,31 +1536,9 @@ async function runInit(options = {}) {
|
|
|
1516
1536
|
await ssh.connect(sshCfg);
|
|
1517
1537
|
log9.success(`Connected to ${sshCfg.host}`);
|
|
1518
1538
|
} catch (err) {
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
const pw = await promptPassword({
|
|
1523
|
-
message: `SSH password for ${sshCfg.username}@${sshCfg.host}`,
|
|
1524
|
-
validate: (v) => !v ? "Required" : void 0
|
|
1525
|
-
});
|
|
1526
|
-
if (isCancel4(pw)) {
|
|
1527
|
-
cancel3("Setup cancelled.");
|
|
1528
|
-
process.exit(0);
|
|
1529
|
-
}
|
|
1530
|
-
sshCfg = { ...sshCfg, privateKeyPath: void 0, password: pw };
|
|
1531
|
-
try {
|
|
1532
|
-
await ssh.connect(sshCfg);
|
|
1533
|
-
log9.success(`Connected to ${sshCfg.host}`);
|
|
1534
|
-
} catch (err2) {
|
|
1535
|
-
log9.error(`SSH connection failed: ${String(err2)}`);
|
|
1536
|
-
log9.info("Fix your SSH credentials and re-run `lead-routing init`.");
|
|
1537
|
-
process.exit(1);
|
|
1538
|
-
}
|
|
1539
|
-
} else {
|
|
1540
|
-
log9.error(`SSH connection failed: ${String(err)}`);
|
|
1541
|
-
log9.info("Fix your SSH credentials and re-run `lead-routing init`.");
|
|
1542
|
-
process.exit(1);
|
|
1543
|
-
}
|
|
1539
|
+
log9.error(`SSH connection failed: ${String(err)}`);
|
|
1540
|
+
log9.info("Check your password and re-run `lead-routing init`.");
|
|
1541
|
+
process.exit(1);
|
|
1544
1542
|
}
|
|
1545
1543
|
}
|
|
1546
1544
|
log9.step("Step 3/9 Configuration");
|
|
@@ -1614,7 +1612,7 @@ Files created: docker-compose.yml, Caddyfile, .env.web, .env.engine, lead-routin
|
|
|
1614
1612
|
|
|
1615
1613
|
// src/commands/deploy.ts
|
|
1616
1614
|
import { writeFileSync as writeFileSync4, unlinkSync } from "fs";
|
|
1617
|
-
import { join as
|
|
1615
|
+
import { join as join6 } from "path";
|
|
1618
1616
|
import { tmpdir as tmpdir2 } from "os";
|
|
1619
1617
|
import { intro as intro2, outro as outro2, log as log10, password as promptPassword2 } from "@clack/prompts";
|
|
1620
1618
|
import chalk3 from "chalk";
|
|
@@ -1656,7 +1654,7 @@ async function runDeploy() {
|
|
|
1656
1654
|
const remoteDir = await ssh.resolveHome(cfg.remoteDir);
|
|
1657
1655
|
log10.step("Syncing Caddyfile");
|
|
1658
1656
|
const caddyContent = renderCaddyfile(cfg.appUrl, cfg.engineUrl);
|
|
1659
|
-
const tmpCaddy =
|
|
1657
|
+
const tmpCaddy = join6(tmpdir2(), "lead-routing-Caddyfile");
|
|
1660
1658
|
writeFileSync4(tmpCaddy, caddyContent, "utf8");
|
|
1661
1659
|
await ssh.upload([{ local: tmpCaddy, remote: `${remoteDir}/Caddyfile` }]);
|
|
1662
1660
|
unlinkSync(tmpCaddy);
|
|
@@ -1822,8 +1820,8 @@ async function runStatus() {
|
|
|
1822
1820
|
|
|
1823
1821
|
// src/commands/config.ts
|
|
1824
1822
|
import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync5 } from "fs";
|
|
1825
|
-
import { join as
|
|
1826
|
-
import { intro as intro4, outro as outro4, text as text3, password as password3, spinner as
|
|
1823
|
+
import { join as join7 } from "path";
|
|
1824
|
+
import { intro as intro4, outro as outro4, text as text3, password as password3, spinner as spinner8, log as log14 } from "@clack/prompts";
|
|
1827
1825
|
import chalk5 from "chalk";
|
|
1828
1826
|
import { execa as execa7 } from "execa";
|
|
1829
1827
|
function parseEnv(filePath) {
|
|
@@ -1866,8 +1864,8 @@ async function runConfigSfdc() {
|
|
|
1866
1864
|
log14.info("Run `lead-routing init` first, or cd into your installation directory.");
|
|
1867
1865
|
process.exit(1);
|
|
1868
1866
|
}
|
|
1869
|
-
const envWeb =
|
|
1870
|
-
const envEngine =
|
|
1867
|
+
const envWeb = join7(dir, ".env.web");
|
|
1868
|
+
const envEngine = join7(dir, ".env.engine");
|
|
1871
1869
|
const currentWeb = parseEnv(envWeb);
|
|
1872
1870
|
const currentClientId = currentWeb.get("SFDC_CLIENT_ID") ?? "";
|
|
1873
1871
|
const currentLoginUrl = currentWeb.get("SFDC_LOGIN_URL") ?? "https://login.salesforce.com";
|
|
@@ -1899,7 +1897,7 @@ Callback URL for your Connected App: ${callbackUrl}`
|
|
|
1899
1897
|
writeEnv(envWeb, updates);
|
|
1900
1898
|
writeEnv(envEngine, updates);
|
|
1901
1899
|
log14.success("Updated .env.web and .env.engine");
|
|
1902
|
-
const s =
|
|
1900
|
+
const s = spinner8();
|
|
1903
1901
|
s.start("Restarting web and engine containers\u2026");
|
|
1904
1902
|
try {
|
|
1905
1903
|
await execa7("docker", ["compose", "up", "-d", "--force-recreate", "web", "engine"], {
|
|
@@ -1920,7 +1918,7 @@ function runConfigShow() {
|
|
|
1920
1918
|
console.error("No lead-routing installation found in the current directory.");
|
|
1921
1919
|
process.exit(1);
|
|
1922
1920
|
}
|
|
1923
|
-
const envWeb =
|
|
1921
|
+
const envWeb = join7(dir, ".env.web");
|
|
1924
1922
|
const cfg = parseEnv(envWeb);
|
|
1925
1923
|
const adminSecret = cfg.get("ADMIN_SECRET") ?? "(not found)";
|
|
1926
1924
|
const appUrl = cfg.get("APP_URL") ?? "(not found)";
|
|
@@ -1936,7 +1934,7 @@ function runConfigShow() {
|
|
|
1936
1934
|
}
|
|
1937
1935
|
|
|
1938
1936
|
// src/commands/sfdc.ts
|
|
1939
|
-
import { intro as intro5, outro as outro5, text as text4, spinner as
|
|
1937
|
+
import { intro as intro5, outro as outro5, text as text4, spinner as spinner9, log as log15 } from "@clack/prompts";
|
|
1940
1938
|
import chalk6 from "chalk";
|
|
1941
1939
|
import { execa as execa8 } from "execa";
|
|
1942
1940
|
async function runSfdcDeploy() {
|
|
@@ -1964,7 +1962,7 @@ async function runSfdcDeploy() {
|
|
|
1964
1962
|
if (typeof rawEngine === "symbol") process.exit(0);
|
|
1965
1963
|
engineUrl = rawEngine.trim();
|
|
1966
1964
|
}
|
|
1967
|
-
const s =
|
|
1965
|
+
const s = spinner9();
|
|
1968
1966
|
s.start("Checking Salesforce CLI\u2026");
|
|
1969
1967
|
try {
|
|
1970
1968
|
await execa8("sf", ["--version"], { all: true });
|
|
@@ -2007,7 +2005,7 @@ async function runSfdcDeploy() {
|
|
|
2007
2005
|
|
|
2008
2006
|
// src/index.ts
|
|
2009
2007
|
var program = new Command();
|
|
2010
|
-
program.name("lead-routing").description("Self-hosted Lead Routing \u2014 scaffold, deploy, and manage your installation").version("0.1.
|
|
2008
|
+
program.name("lead-routing").description("Self-hosted Lead Routing \u2014 scaffold, deploy, and manage your installation").version("0.1.11");
|
|
2011
2009
|
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("--sandbox", "Use Salesforce sandbox (test.salesforce.com) instead of production").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({
|
|
2012
2010
|
dryRun: opts.dryRun,
|
|
2013
2011
|
resume: opts.resume,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lead-routing/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
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"],
|