@lead-routing/cli 0.1.2 → 0.1.3

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 +71 -15
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -744,18 +744,41 @@ async function checkRemoteDockerCompose(ssh) {
744
744
  return { ok: true, label: `Docker Compose \u2014 ${stdout.trim()}` };
745
745
  }
746
746
  async function checkRemotePort(ssh, port) {
747
- const { stdout } = await ssh.execSilent(
748
- `ss -tlnp 2>/dev/null | grep ':${port} ' || netstat -tlnp 2>/dev/null | grep ':${port} ' || echo "free"`
749
- );
750
- const isBound = stdout.trim() !== "free" && stdout.trim() !== "";
751
- if (isBound) {
752
- return {
753
- ok: false,
754
- warn: true,
755
- label: `Port ${port} \u2014 already in use on server (Caddy needs it for HTTPS \u2014 ensure nothing else is binding it)`
756
- };
747
+ const portCheckCmd = `ss -tlnp 2>/dev/null | grep ':${port} ' || netstat -tlnp 2>/dev/null | grep ':${port} ' || echo "free"`;
748
+ const { stdout: initial } = await ssh.execSilent(portCheckCmd);
749
+ const isBound = initial.trim() !== "free" && initial.trim() !== "";
750
+ if (!isBound) {
751
+ return { ok: true, label: `Port ${port} \u2014 available` };
752
+ }
753
+ const knownServices = ["nginx", "apache2", "httpd", "lighttpd", "caddy"];
754
+ for (const svc of knownServices) {
755
+ const { code: activeCode } = await ssh.execSilent(
756
+ `systemctl is-active --quiet ${svc} 2>/dev/null`
757
+ );
758
+ if (activeCode === 0) {
759
+ await ssh.execSilent(
760
+ `systemctl stop ${svc} 2>/dev/null; systemctl disable ${svc} 2>/dev/null`
761
+ );
762
+ const { stdout: recheck } = await ssh.execSilent(portCheckCmd);
763
+ if (recheck.trim() === "free" || !recheck.trim()) {
764
+ return {
765
+ ok: true,
766
+ label: `Port ${port} \u2014 freed (stopped and disabled system ${svc} service)`
767
+ };
768
+ }
769
+ }
757
770
  }
758
- return { ok: true, label: `Port ${port} \u2014 available` };
771
+ const { stdout: occupant } = await ssh.execSilent(
772
+ `ss -tlnp 2>/dev/null | grep ':${port} ' | head -1 || echo "unknown process"`
773
+ );
774
+ return {
775
+ ok: false,
776
+ // Hard error — Caddy cannot get TLS certs without these ports
777
+ label: `Port ${port} is occupied and could not be freed automatically.
778
+ Occupant: ${occupant.trim()}
779
+ Stop the conflicting process on the server, then re-run:
780
+ lead-routing init`
781
+ };
759
782
  }
760
783
 
761
784
  // src/steps/upload-files.ts
@@ -986,7 +1009,7 @@ ON CONFLICT ("orgId", email) DO NOTHING;
986
1009
 
987
1010
  // src/steps/verify-health.ts
988
1011
  import { spinner as spinner5, log as log5 } from "@clack/prompts";
989
- async function verifyHealth(appUrl, engineUrl) {
1012
+ async function verifyHealth(appUrl, engineUrl, ssh, remoteDir) {
990
1013
  const checks = [
991
1014
  { service: "Web app", url: `${appUrl}/api/health` },
992
1015
  { service: "Routing engine", url: `${engineUrl}/health` }
@@ -996,9 +1019,42 @@ async function verifyHealth(appUrl, engineUrl) {
996
1019
  if (r.ok) {
997
1020
  log5.success(`${r.service} \u2014 ${r.url}`);
998
1021
  } else {
999
- log5.warn(`${r.service} not responding yet \u2014 ${r.detail}`);
1022
+ log5.warn(`${r.service} \u2014 did not respond after ${r.detail}`);
1000
1023
  }
1001
1024
  }
1025
+ const failed = results.filter((r) => !r.ok);
1026
+ if (failed.length === 0) return;
1027
+ log5.info("Fetching remote diagnostics\u2026");
1028
+ try {
1029
+ const { stdout: ps } = await ssh.execSilent("docker compose ps --format table", remoteDir);
1030
+ if (ps.trim()) log5.info(`Container status:
1031
+ ${ps.trim()}`);
1032
+ } catch {
1033
+ }
1034
+ try {
1035
+ const { stdout: caddyLogs } = await ssh.execSilent(
1036
+ "docker compose logs caddy --tail 30 --no-color 2>&1",
1037
+ remoteDir
1038
+ );
1039
+ if (caddyLogs.trim()) log5.info(`Caddy logs (last 30 lines):
1040
+ ${caddyLogs.trim()}`);
1041
+ } catch {
1042
+ }
1043
+ const failedNames = failed.map((r) => r.service).join(" and ");
1044
+ throw new Error(
1045
+ `${failedNames} did not respond after 2 minutes.
1046
+
1047
+ Common causes (check Caddy logs above):
1048
+ \u2022 Let's Encrypt rate limit \u2014 wait until tomorrow and re-run
1049
+ \u2022 Port 80/443 still blocked by another process
1050
+ \u2022 Container crashed \u2014 check container status above
1051
+
1052
+ To resume once fixed:
1053
+ 1. SSH into your server:
1054
+ cd ${remoteDir} && docker compose restart caddy
1055
+ 2. Then re-run Salesforce setup:
1056
+ lead-routing sfdc deploy`
1057
+ );
1002
1058
  }
1003
1059
  async function pollHealth(service, url, maxAttempts = 24, intervalMs = 5e3) {
1004
1060
  const s = spinner5();
@@ -1022,7 +1078,7 @@ async function pollHealth(service, url, maxAttempts = 24, intervalMs = 5e3) {
1022
1078
  service,
1023
1079
  url,
1024
1080
  ok: false,
1025
- detail: `timed out after ${maxAttempts * intervalMs / 1e3}s`
1081
+ detail: `${maxAttempts * intervalMs / 1e3}s`
1026
1082
  };
1027
1083
  }
1028
1084
  function sleep2(ms) {
@@ -1479,7 +1535,7 @@ Files created: docker-compose.yml, Caddyfile, .env.web, .env.engine, lead-routin
1479
1535
  log8.step("Step 7/9 Database migrations");
1480
1536
  await runMigrations(ssh, dir, cfg.adminEmail, cfg.adminPassword);
1481
1537
  log8.step("Step 8/9 Verifying health");
1482
- await verifyHealth(cfg.appUrl, cfg.engineUrl);
1538
+ await verifyHealth(cfg.appUrl, cfg.engineUrl, ssh, remoteDir);
1483
1539
  log8.step("Step 9/9 Deploying Salesforce package");
1484
1540
  await sfdcDeployInline({
1485
1541
  appUrl: cfg.appUrl,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lead-routing/cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
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"],