@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.
Files changed (2) hide show
  1. package/dist/index.js +111 -113
  2. 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 access (key auto-detected, or password)\n \u2022 Docker 24+ already installed on the server",
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 detected = detectDefaultKey();
112
- if (detected) {
113
- privateKeyPath = detected;
114
- log2.info(`Using SSH key: ${detected.replace(homedir(), "~")}`);
115
- } else {
116
- log2.warn("No SSH key found at ~/.ssh/id_ed25519, ~/.ssh/id_rsa, or ~/.ssh/id_ecdsa");
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 join3, dirname } from "path";
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 as join2 } from "path";
476
+ import { join } from "path";
493
477
  function getConfigPath(dir) {
494
- return join2(dir, "lead-routing.json");
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 = join2(startDir, "lead-routing.json");
493
+ const candidate = join(startDir, "lead-routing.json");
510
494
  if (existsSync2(candidate)) return startDir;
511
- const nested = join2(startDir, "lead-routing", "lead-routing.json");
512
- if (existsSync2(nested)) return join2(startDir, "lead-routing");
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(join3(__dirname, "../../package.json"), "utf8"));
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 = join3(process.cwd(), "lead-routing");
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 = join3(dir, "docker-compose.yml");
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(join3(dir, "Caddyfile"), caddyfileContent, "utf8");
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 = join3(dir, ".env.web");
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 = join3(dir, ".env.engine");
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 results = await Promise.all([
599
- checkRemoteDocker(ssh),
600
- checkRemoteDockerCompose(ssh),
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
- Install the missing software on your server and re-run lead-routing init.`
608
+ Fix the issues above and re-run lead-routing init.`
624
609
  );
625
610
  }
626
611
  }
627
- async function checkRemoteDocker(ssh) {
628
- const { stdout, code } = await ssh.execSilent("docker --version");
629
- if (code !== 0 || !stdout) {
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: "Docker \u2014 not found on server (install Docker Engine 24+)"
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 on server (update Docker to include Compose v2)"
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 join4 } from "path";
700
- import { spinner as spinner2 } from "@clack/prompts";
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 = spinner2();
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: join4(localDir, f),
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 spinner3, log as log5 } from "@clack/prompts";
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 = spinner3();
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 = spinner3();
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 = spinner3();
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 = spinner3();
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 spinner4 } from "@clack/prompts";
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 = spinner4();
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 = spinner4();
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 = spinner4();
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 spinner5, log as log6 } from "@clack/prompts";
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 = spinner5();
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 join6, dirname as dirname3 } from "path";
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 spinner6, log as log7 } from "@clack/prompts";
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 = spinner6();
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 = join6(__dirname3, "sfdc-package");
1038
- const nextToDist = join6(__dirname3, "..", "sfdc-package");
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 = join6(installDir ?? tmpdir(), "lead-routing-sfdc-package");
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 = join6(
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 = join6(
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 = join6(
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 = spinner6();
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
- let sshCfg = await collectSshConfig({
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
- if (sshCfg.privateKeyPath && !sshCfg.password) {
1520
- log9.warn(`Key auth failed \u2014 the server rejected the SSH key`);
1521
- log9.info("Falling back to password auth\u2026");
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 join7 } from "path";
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 = join7(tmpdir2(), "lead-routing-Caddyfile");
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 join8 } from "path";
1826
- import { intro as intro4, outro as outro4, text as text3, password as password3, spinner as spinner7, log as log14 } from "@clack/prompts";
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 = join8(dir, ".env.web");
1870
- const envEngine = join8(dir, ".env.engine");
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 = spinner7();
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 = join8(dir, ".env.web");
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 spinner8, log as log15 } from "@clack/prompts";
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 = spinner8();
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.9");
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.9",
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"],