@lead-routing/cli 0.1.13 → 0.2.0

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 CHANGED
@@ -10,24 +10,9 @@ import chalk2 from "chalk";
10
10
 
11
11
  // src/steps/prerequisites.ts
12
12
  import { log } from "@clack/prompts";
13
-
14
- // src/utils/exec.ts
15
- import { execa } from "execa";
16
- import { spinner } from "@clack/prompts";
17
- async function runSilent(cmd, args, opts = {}) {
18
- try {
19
- const result = await execa(cmd, args, { cwd: opts.cwd, reject: false });
20
- return result.stdout;
21
- } catch {
22
- return "";
23
- }
24
- }
25
-
26
- // src/steps/prerequisites.ts
27
13
  async function checkPrerequisites() {
28
14
  const results = await Promise.all([
29
- checkNodeVersion(),
30
- checkSalesforceCLI()
15
+ checkNodeVersion()
31
16
  ]);
32
17
  const failed = results.filter((r) => !r.ok);
33
18
  for (const r of results) {
@@ -54,17 +39,6 @@ async function checkNodeVersion() {
54
39
  }
55
40
  return { ok: true, label: `Node.js ${version}` };
56
41
  }
57
- async function checkSalesforceCLI() {
58
- const out = await runSilent("sf", ["--version"]);
59
- if (!out) {
60
- return {
61
- ok: false,
62
- label: "Salesforce CLI (sf) \u2014 not found",
63
- detail: "install from https://developer.salesforce.com/tools/salesforcecli"
64
- };
65
- }
66
- return { ok: true, label: `Salesforce CLI \u2014 ${out.trim()}` };
67
- }
68
42
 
69
43
  // src/steps/collect-ssh-config.ts
70
44
  import { existsSync } from "fs";
@@ -389,6 +363,8 @@ function renderEnvWeb(c) {
389
363
  ``,
390
364
  `# Admin`,
391
365
  `ADMIN_SECRET=${c.adminSecret}`,
366
+ `ADMIN_EMAIL=${c.adminEmail}`,
367
+ `ADMIN_PASSWORD=${c.adminPassword}`,
392
368
  ``,
393
369
  `# Email (optional)`,
394
370
  `RESEND_API_KEY=${c.resendApiKey ?? ""}`,
@@ -479,10 +455,10 @@ function getConfigPath(dir) {
479
455
  return join(dir, "lead-routing.json");
480
456
  }
481
457
  function readConfig(dir) {
482
- const path2 = getConfigPath(dir);
483
- if (!existsSync2(path2)) return null;
458
+ const path = getConfigPath(dir);
459
+ if (!existsSync2(path)) return null;
484
460
  try {
485
- return JSON.parse(readFileSync(path2, "utf8"));
461
+ return JSON.parse(readFileSync(path, "utf8"));
486
462
  } catch {
487
463
  return null;
488
464
  }
@@ -535,6 +511,8 @@ function generateFiles(cfg, sshCfg) {
535
511
  sessionSecret: cfg.sessionSecret,
536
512
  engineWebhookSecret: cfg.engineWebhookSecret,
537
513
  adminSecret: cfg.adminSecret,
514
+ adminEmail: cfg.adminEmail,
515
+ adminPassword: cfg.adminPassword,
538
516
  resendApiKey: cfg.resendApiKey || void 0,
539
517
  feedbackToEmail: cfg.feedbackToEmail || void 0
540
518
  });
@@ -579,7 +557,7 @@ function generateFiles(cfg, sshCfg) {
579
557
  }
580
558
 
581
559
  // src/steps/check-remote-prerequisites.ts
582
- import { log as log4, spinner as spinner2 } from "@clack/prompts";
560
+ import { log as log4, spinner } from "@clack/prompts";
583
561
  async function checkRemotePrerequisites(ssh) {
584
562
  const dockerResult = await checkOrInstallDocker(ssh);
585
563
  const composeResult = await checkRemoteDockerCompose(ssh);
@@ -620,7 +598,7 @@ async function checkOrInstallDocker(ssh) {
620
598
  }
621
599
  return { ok: true, label: `Docker \u2014 ${stdout.trim()}` };
622
600
  }
623
- const s = spinner2();
601
+ const s = spinner();
624
602
  s.start("Docker not found \u2014 installing via get.docker.com (~2 min)\u2026");
625
603
  try {
626
604
  const { code: curlCode } = await ssh.execSilent("command -v curl 2>/dev/null");
@@ -719,9 +697,9 @@ async function checkRemotePort(ssh, port) {
719
697
 
720
698
  // src/steps/upload-files.ts
721
699
  import { join as join3 } from "path";
722
- import { spinner as spinner3 } from "@clack/prompts";
700
+ import { spinner as spinner2 } from "@clack/prompts";
723
701
  async function uploadFiles(ssh, localDir, remoteDir) {
724
- const s = spinner3();
702
+ const s = spinner2();
725
703
  s.start("Uploading config files to server");
726
704
  try {
727
705
  await ssh.mkdir(remoteDir);
@@ -746,7 +724,7 @@ async function uploadFiles(ssh, localDir, remoteDir) {
746
724
  }
747
725
 
748
726
  // src/steps/start-services.ts
749
- import { spinner as spinner4, log as log5 } from "@clack/prompts";
727
+ import { spinner as spinner3, log as log5 } from "@clack/prompts";
750
728
  async function startServices(ssh, remoteDir) {
751
729
  await wipeStalePostgresVolume(ssh, remoteDir);
752
730
  await pullImages(ssh, remoteDir);
@@ -760,7 +738,7 @@ async function wipeStalePostgresVolume(ssh, remoteDir) {
760
738
  if (code !== 0) {
761
739
  return;
762
740
  }
763
- const s = spinner4();
741
+ const s = spinner3();
764
742
  s.start("Removing existing database volume for clean install");
765
743
  try {
766
744
  await ssh.exec("docker compose down -v --remove-orphans", remoteDir);
@@ -774,7 +752,7 @@ async function wipeStalePostgresVolume(ssh, remoteDir) {
774
752
  }
775
753
  }
776
754
  async function pullImages(ssh, remoteDir) {
777
- const s = spinner4();
755
+ const s = spinner3();
778
756
  s.start("Pulling Docker images on server (this may take a few minutes)");
779
757
  try {
780
758
  await ssh.exec("docker compose pull", remoteDir);
@@ -787,7 +765,7 @@ async function pullImages(ssh, remoteDir) {
787
765
  }
788
766
  }
789
767
  async function startContainers(ssh, remoteDir) {
790
- const s = spinner4();
768
+ const s = spinner3();
791
769
  s.start("Starting services");
792
770
  try {
793
771
  await ssh.exec("docker compose up -d --remove-orphans", remoteDir);
@@ -798,7 +776,7 @@ async function startContainers(ssh, remoteDir) {
798
776
  }
799
777
  }
800
778
  async function waitForPostgres(ssh, remoteDir) {
801
- const s = spinner4();
779
+ const s = spinner3();
802
780
  s.start("Waiting for PostgreSQL to be ready");
803
781
  const maxAttempts = 24;
804
782
  let containerReady = false;
@@ -834,119 +812,11 @@ async function waitForPostgres(ssh, remoteDir) {
834
812
  log5.warn("Host TCP port check timed out \u2014 tunnel may have issues. Proceeding anyway.");
835
813
  }
836
814
  function sleep(ms) {
837
- return new Promise((resolve2) => setTimeout(resolve2, ms));
838
- }
839
-
840
- // src/steps/run-migrations.ts
841
- import * as fs from "fs";
842
- import * as path from "path";
843
- import * as crypto from "crypto";
844
- import { fileURLToPath as fileURLToPath2 } from "url";
845
- import { execa as execa2 } from "execa";
846
- import { spinner as spinner5 } from "@clack/prompts";
847
- var __filename = fileURLToPath2(import.meta.url);
848
- var __dirname2 = path.dirname(__filename);
849
- function readEnvVar(envFile, key) {
850
- const content = fs.readFileSync(envFile, "utf8");
851
- const match = content.match(new RegExp(`^${key}=(.+)$`, "m"));
852
- if (!match) throw new Error(`${key} not found in ${envFile}`);
853
- return match[1].trim().replace(/^["']|["']$/g, "");
854
- }
855
- function getTunneledDbUrl(localDir, localPort) {
856
- const rawUrl = readEnvVar(path.join(localDir, ".env.web"), "DATABASE_URL");
857
- const parsed = new URL(rawUrl);
858
- parsed.hostname = "localhost";
859
- parsed.port = String(localPort);
860
- return parsed.toString();
861
- }
862
- function findPrismaBin() {
863
- const candidates = [
864
- // npx / npm global install: @lead-routing/cli is nested under the scope dir,
865
- // so prisma lands 3 levels above dist/ in node_modules/.bin/
866
- // e.g. ~/.npm/_npx/HASH/node_modules/.bin/prisma
867
- path.join(__dirname2, "../../../.bin/prisma"),
868
- path.join(__dirname2, "../../../prisma/bin/prisma.js"),
869
- // Fallback: prisma nested inside the package's own node_modules (hoisted install)
870
- path.join(__dirname2, "../node_modules/.bin/prisma"),
871
- path.join(__dirname2, "../node_modules/prisma/bin/prisma.js"),
872
- // Monorepo dev paths
873
- path.resolve("packages/db/node_modules/.bin/prisma"),
874
- path.resolve("node_modules/.bin/prisma"),
875
- path.resolve("node_modules/.pnpm/node_modules/.bin/prisma")
876
- ];
877
- const found = candidates.find(fs.existsSync);
878
- if (!found) throw new Error("Prisma binary not found \u2014 CLI may need to be reinstalled.");
879
- return found;
880
- }
881
- async function runMigrations(ssh, localDir, adminEmail, adminPassword) {
882
- const s = spinner5();
883
- s.start("Opening secure tunnel to database");
884
- let tunnelClose;
885
- try {
886
- const { localPort, close } = await ssh.tunnel(5432);
887
- tunnelClose = close;
888
- s.stop(`Database tunnel open (local port ${localPort})`);
889
- await applyMigrations(localDir, localPort);
890
- if (adminEmail && adminPassword) {
891
- await seedAdminUser(localDir, localPort, adminEmail, adminPassword);
892
- }
893
- } finally {
894
- tunnelClose?.();
895
- }
896
- }
897
- async function applyMigrations(localDir, localPort) {
898
- const s = spinner5();
899
- s.start("Running database migrations");
900
- try {
901
- const DATABASE_URL = getTunneledDbUrl(localDir, localPort);
902
- const prismaBin = findPrismaBin();
903
- const bundledSchema = path.join(__dirname2, "prisma/schema.prisma");
904
- const monoSchema = path.resolve("packages/db/prisma/schema.prisma");
905
- const schemaPath = fs.existsSync(bundledSchema) ? bundledSchema : monoSchema;
906
- await execa2(prismaBin, ["migrate", "deploy", "--schema", schemaPath], {
907
- env: { ...process.env, DATABASE_URL }
908
- });
909
- s.stop("Database migrations applied");
910
- } catch (err) {
911
- s.stop("Migrations failed");
912
- throw err;
913
- }
914
- }
915
- async function seedAdminUser(localDir, localPort, adminEmail, adminPassword) {
916
- const s = spinner5();
917
- s.start("Creating admin user");
918
- try {
919
- const DATABASE_URL = getTunneledDbUrl(localDir, localPort);
920
- const webhookSecret = readEnvVar(path.join(localDir, ".env.engine"), "ENGINE_WEBHOOK_SECRET");
921
- const salt = crypto.randomBytes(16).toString("hex");
922
- const pbkdf2Hash = crypto.pbkdf2Sync(adminPassword, salt, 31e4, 32, "sha256").toString("hex");
923
- const passwordHash = `${salt}:${pbkdf2Hash}`;
924
- const safeEmail = adminEmail.replace(/'/g, "''");
925
- const safeWebhookSecret = webhookSecret.replace(/'/g, "''");
926
- const sql = `
927
- -- Create initial organisation if none exists (self-hosted defaults: PAID plan, unlimited seats)
928
- INSERT INTO organizations (id, "webhookSecret", plan, "seatsPurchased", "isActive", "createdAt", "updatedAt")
929
- SELECT gen_random_uuid(), '${safeWebhookSecret}', 'PAID', 9999, true, NOW(), NOW()
930
- WHERE NOT EXISTS (SELECT 1 FROM organizations);
931
-
932
- -- Create admin AppUser under the first org (idempotent)
933
- INSERT INTO app_users (id, "orgId", email, name, "passwordHash", role, "isActive", "createdAt", "updatedAt")
934
- SELECT gen_random_uuid(), o.id, '${safeEmail}', 'Admin', '${passwordHash}', 'ADMIN', true, NOW(), NOW()
935
- FROM organizations o
936
- LIMIT 1
937
- ON CONFLICT ("orgId", email) DO NOTHING;
938
- `;
939
- const prismaBin = findPrismaBin();
940
- await execa2(prismaBin, ["db", "execute", "--stdin", "--url", DATABASE_URL], { input: sql });
941
- s.stop("Admin user ready");
942
- } catch (err) {
943
- s.stop("Seed failed");
944
- throw err;
945
- }
815
+ return new Promise((resolve) => setTimeout(resolve, ms));
946
816
  }
947
817
 
948
818
  // src/steps/verify-health.ts
949
- import { spinner as spinner6, log as log6 } from "@clack/prompts";
819
+ import { spinner as spinner4, log as log6 } from "@clack/prompts";
950
820
  async function verifyHealth(appUrl, engineUrl, ssh, remoteDir) {
951
821
  const checks = [
952
822
  { service: "Web app", url: `${appUrl}/api/health` },
@@ -995,7 +865,7 @@ To resume once fixed:
995
865
  );
996
866
  }
997
867
  async function pollHealth(service, url, maxAttempts = 24, intervalMs = 5e3) {
998
- const s = spinner6();
868
+ const s = spinner4();
999
869
  s.start(`Waiting for ${service}`);
1000
870
  for (let i = 0; i < maxAttempts; i++) {
1001
871
  try {
@@ -1020,54 +890,220 @@ async function pollHealth(service, url, maxAttempts = 24, intervalMs = 5e3) {
1020
890
  };
1021
891
  }
1022
892
  function sleep2(ms) {
1023
- return new Promise((resolve2) => setTimeout(resolve2, ms));
893
+ return new Promise((resolve) => setTimeout(resolve, ms));
1024
894
  }
1025
895
 
1026
896
  // src/steps/sfdc-deploy-inline.ts
1027
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, cpSync, rmSync } from "fs";
1028
- import { join as join5, dirname as dirname3 } from "path";
897
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync3, cpSync, rmSync } from "fs";
898
+ import { join as join5, dirname as dirname2 } from "path";
1029
899
  import { tmpdir } from "os";
1030
- import { fileURLToPath as fileURLToPath3 } from "url";
1031
- import { spinner as spinner7, log as log7 } from "@clack/prompts";
1032
- import { execa as execa3 } from "execa";
1033
- var __dirname3 = dirname3(fileURLToPath3(import.meta.url));
900
+ import { fileURLToPath as fileURLToPath2 } from "url";
901
+ import { execSync } from "child_process";
902
+ import { spinner as spinner5, log as log7 } from "@clack/prompts";
903
+
904
+ // src/utils/sfdc-api.ts
905
+ var API_VERSION = "v59.0";
906
+ var SalesforceApi = class {
907
+ constructor(instanceUrl, accessToken) {
908
+ this.instanceUrl = instanceUrl;
909
+ this.accessToken = accessToken;
910
+ this.baseUrl = `${instanceUrl.replace(/\/+$/, "")}/services/data/${API_VERSION}`;
911
+ }
912
+ baseUrl;
913
+ headers(extra) {
914
+ return {
915
+ Authorization: `Bearer ${this.accessToken}`,
916
+ "Content-Type": "application/json",
917
+ ...extra
918
+ };
919
+ }
920
+ /** Execute a SOQL query and return records */
921
+ async query(soql) {
922
+ const url = `${this.baseUrl}/query?q=${encodeURIComponent(soql)}`;
923
+ const res = await fetch(url, { headers: this.headers() });
924
+ if (!res.ok) {
925
+ const body = await res.text();
926
+ throw new Error(`SOQL query failed (${res.status}): ${body}`);
927
+ }
928
+ const data = await res.json();
929
+ return data.records;
930
+ }
931
+ /** Create an sObject record, returns the new record ID */
932
+ async create(sobject, data) {
933
+ const url = `${this.baseUrl}/sobjects/${sobject}`;
934
+ const res = await fetch(url, {
935
+ method: "POST",
936
+ headers: this.headers(),
937
+ body: JSON.stringify(data)
938
+ });
939
+ if (!res.ok) {
940
+ const body = await res.text();
941
+ if (res.status === 400 && body.includes("Duplicate")) {
942
+ throw new DuplicateError(body);
943
+ }
944
+ throw new Error(`Create ${sobject} failed (${res.status}): ${body}`);
945
+ }
946
+ const result = await res.json();
947
+ return result.id;
948
+ }
949
+ /** Update an sObject record */
950
+ async update(sobject, id, data) {
951
+ const url = `${this.baseUrl}/sobjects/${sobject}/${id}`;
952
+ const res = await fetch(url, {
953
+ method: "PATCH",
954
+ headers: this.headers(),
955
+ body: JSON.stringify(data)
956
+ });
957
+ if (!res.ok) {
958
+ const body = await res.text();
959
+ throw new Error(`Update ${sobject}/${id} failed (${res.status}): ${body}`);
960
+ }
961
+ }
962
+ /** Get current user info (for permission set assignment) */
963
+ async getCurrentUserId() {
964
+ const url = `${this.instanceUrl.replace(/\/+$/, "")}/services/oauth2/userinfo`;
965
+ const res = await fetch(url, {
966
+ headers: { Authorization: `Bearer ${this.accessToken}` }
967
+ });
968
+ if (!res.ok) {
969
+ const body = await res.text();
970
+ throw new Error(`Get current user failed (${res.status}): ${body}`);
971
+ }
972
+ const data = await res.json();
973
+ return data.user_id;
974
+ }
975
+ /**
976
+ * Deploy metadata using the Source Deploy API (same API that `sf project deploy start` uses).
977
+ * Accepts a ZIP buffer containing the source-format package.
978
+ * Returns the deploy request ID for polling.
979
+ */
980
+ async deployMetadata(zipBuffer) {
981
+ const url = `${this.baseUrl}/metadata/deployRequest`;
982
+ const boundary = `----FormBoundary${Date.now()}`;
983
+ const deployOptions = JSON.stringify({
984
+ deployOptions: {
985
+ rollbackOnError: true,
986
+ singlePackage: true,
987
+ rest: true
988
+ }
989
+ });
990
+ const parts = [];
991
+ parts.push(
992
+ Buffer.from(
993
+ `--${boundary}\r
994
+ Content-Disposition: form-data; name="json"\r
995
+ Content-Type: application/json\r
996
+ \r
997
+ ${deployOptions}\r
998
+ `
999
+ )
1000
+ );
1001
+ parts.push(
1002
+ Buffer.from(
1003
+ `--${boundary}\r
1004
+ Content-Disposition: form-data; name="file"; filename="deploy.zip"\r
1005
+ Content-Type: application/zip\r
1006
+ \r
1007
+ `
1008
+ )
1009
+ );
1010
+ parts.push(zipBuffer);
1011
+ parts.push(Buffer.from(`\r
1012
+ --${boundary}--\r
1013
+ `));
1014
+ const body = Buffer.concat(parts);
1015
+ const res = await fetch(url, {
1016
+ method: "POST",
1017
+ headers: {
1018
+ Authorization: `Bearer ${this.accessToken}`,
1019
+ "Content-Type": `multipart/form-data; boundary=${boundary}`
1020
+ },
1021
+ body
1022
+ });
1023
+ if (!res.ok) {
1024
+ const text5 = await res.text();
1025
+ throw new Error(
1026
+ `Metadata deploy request failed (${res.status}): ${text5}`
1027
+ );
1028
+ }
1029
+ const result = await res.json();
1030
+ return result.id;
1031
+ }
1032
+ /**
1033
+ * Poll deploy status until complete.
1034
+ * Returns deploy result with success/failure info.
1035
+ */
1036
+ async waitForDeploy(deployId, timeoutMs = 3e5) {
1037
+ const startTime = Date.now();
1038
+ const pollInterval = 3e3;
1039
+ while (Date.now() - startTime < timeoutMs) {
1040
+ const url = `${this.baseUrl}/metadata/deployRequest/${deployId}?includeDetails=true`;
1041
+ const res = await fetch(url, { headers: this.headers() });
1042
+ if (!res.ok) {
1043
+ const text5 = await res.text();
1044
+ throw new Error(
1045
+ `Deploy status check failed (${res.status}): ${text5}`
1046
+ );
1047
+ }
1048
+ const data = await res.json();
1049
+ const result = data.deployResult;
1050
+ if (result.done) {
1051
+ return result;
1052
+ }
1053
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
1054
+ }
1055
+ throw new Error(`Deploy timed out after ${timeoutMs / 1e3}s`);
1056
+ }
1057
+ };
1058
+ var DuplicateError = class extends Error {
1059
+ constructor(message) {
1060
+ super(message);
1061
+ this.name = "DuplicateError";
1062
+ }
1063
+ };
1064
+
1065
+ // src/utils/zip-source.ts
1066
+ import { join as join4 } from "path";
1067
+ import archiver from "archiver";
1068
+ async function zipSourcePackage(packageDir) {
1069
+ return new Promise((resolve, reject) => {
1070
+ const chunks = [];
1071
+ const archive = archiver("zip", { zlib: { level: 9 } });
1072
+ archive.on("data", (chunk) => chunks.push(chunk));
1073
+ archive.on("end", () => resolve(Buffer.concat(chunks)));
1074
+ archive.on("error", reject);
1075
+ archive.directory(join4(packageDir, "force-app"), "force-app");
1076
+ archive.file(join4(packageDir, "sfdx-project.json"), {
1077
+ name: "sfdx-project.json"
1078
+ });
1079
+ archive.finalize();
1080
+ });
1081
+ }
1082
+
1083
+ // src/steps/sfdc-deploy-inline.ts
1084
+ var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
1034
1085
  function patchXml(content, tag, value) {
1035
1086
  const re = new RegExp(`(<${tag}>)[^<]*(</\\s*${tag}>)`, "g");
1036
1087
  return content.replace(re, `$1${value}$2`);
1037
1088
  }
1038
1089
  async function sfdcDeployInline(params) {
1039
- const { appUrl, engineUrl, orgAlias, installDir } = params;
1040
- const s = spinner7();
1041
- const { exitCode: authCheck } = await execa3(
1042
- "sf",
1043
- ["org", "display", "--target-org", orgAlias, "--json"],
1044
- { reject: false }
1045
- );
1046
- const alreadyAuthed = authCheck === 0;
1047
- let sfCredEnv = {};
1048
- let targetOrgArgs = ["--target-org", orgAlias];
1049
- if (alreadyAuthed) {
1050
- log7.success("Using existing Salesforce authentication");
1051
- } else {
1052
- const { accessToken, instanceUrl, aliasStored } = await loginViaAppBridge(appUrl, orgAlias);
1053
- sfCredEnv = { SF_ACCESS_TOKEN: accessToken, SF_ORG_INSTANCE_URL: instanceUrl };
1054
- if (!aliasStored) {
1055
- targetOrgArgs = [];
1056
- }
1057
- }
1090
+ const { appUrl, engineUrl, installDir } = params;
1091
+ const s = spinner5();
1092
+ const { accessToken, instanceUrl } = await loginViaAppBridge(appUrl);
1093
+ const sf = new SalesforceApi(instanceUrl, accessToken);
1058
1094
  s.start("Copying Salesforce package\u2026");
1059
- const inDist = join5(__dirname3, "sfdc-package");
1060
- const nextToDist = join5(__dirname3, "..", "sfdc-package");
1061
- const bundledPkg = existsSync4(inDist) ? inDist : nextToDist;
1095
+ const inDist = join5(__dirname2, "sfdc-package");
1096
+ const nextToDist = join5(__dirname2, "..", "sfdc-package");
1097
+ const bundledPkg = existsSync3(inDist) ? inDist : nextToDist;
1062
1098
  const destPkg = join5(installDir ?? tmpdir(), "lead-routing-sfdc-package");
1063
- if (!existsSync4(bundledPkg)) {
1099
+ if (!existsSync3(bundledPkg)) {
1064
1100
  s.stop("sfdc-package not found in CLI bundle");
1065
1101
  throw new Error(
1066
1102
  `Expected bundle at: ${inDist}
1067
1103
  The CLI may need to be reinstalled: npm i -g @lead-routing/cli`
1068
1104
  );
1069
1105
  }
1070
- if (existsSync4(destPkg)) rmSync(destPkg, { recursive: true, force: true });
1106
+ if (existsSync3(destPkg)) rmSync(destPkg, { recursive: true, force: true });
1071
1107
  cpSync(bundledPkg, destPkg, { recursive: true });
1072
1108
  s.stop("Package copied");
1073
1109
  const ncPath = join5(
@@ -1078,8 +1114,8 @@ The CLI may need to be reinstalled: npm i -g @lead-routing/cli`
1078
1114
  "namedCredentials",
1079
1115
  "RoutingEngine.namedCredential-meta.xml"
1080
1116
  );
1081
- if (existsSync4(ncPath)) {
1082
- const nc = patchXml(readFileSync4(ncPath, "utf8"), "endpoint", engineUrl);
1117
+ if (existsSync3(ncPath)) {
1118
+ const nc = patchXml(readFileSync3(ncPath, "utf8"), "endpoint", engineUrl);
1083
1119
  writeFileSync3(ncPath, nc, "utf8");
1084
1120
  }
1085
1121
  const rssEnginePath = join5(
@@ -1090,8 +1126,8 @@ The CLI may need to be reinstalled: npm i -g @lead-routing/cli`
1090
1126
  "remoteSiteSettings",
1091
1127
  "LeadRouterEngine.remoteSite-meta.xml"
1092
1128
  );
1093
- if (existsSync4(rssEnginePath)) {
1094
- let rss = patchXml(readFileSync4(rssEnginePath, "utf8"), "url", engineUrl);
1129
+ if (existsSync3(rssEnginePath)) {
1130
+ let rss = patchXml(readFileSync3(rssEnginePath, "utf8"), "url", engineUrl);
1095
1131
  rss = patchXml(rss, "description", "Lead Router Engine endpoint");
1096
1132
  writeFileSync3(rssEnginePath, rss, "utf8");
1097
1133
  }
@@ -1103,45 +1139,67 @@ The CLI may need to be reinstalled: npm i -g @lead-routing/cli`
1103
1139
  "remoteSiteSettings",
1104
1140
  "LeadRouterApp.remoteSite-meta.xml"
1105
1141
  );
1106
- if (existsSync4(rssAppPath)) {
1107
- let rss = patchXml(readFileSync4(rssAppPath, "utf8"), "url", appUrl);
1142
+ if (existsSync3(rssAppPath)) {
1143
+ let rss = patchXml(readFileSync3(rssAppPath, "utf8"), "url", appUrl);
1108
1144
  rss = patchXml(rss, "description", "Lead Router App URL");
1109
1145
  writeFileSync3(rssAppPath, rss, "utf8");
1110
1146
  }
1111
1147
  log7.success("Remote Site Settings patched");
1112
1148
  s.start("Deploying Salesforce package (this may take ~2 min)\u2026");
1113
1149
  try {
1114
- await execa3(
1115
- "sf",
1116
- ["project", "deploy", "start", ...targetOrgArgs, "--source-dir", "force-app"],
1117
- { cwd: destPkg, stdio: "inherit", env: { ...process.env, ...sfCredEnv } }
1118
- );
1119
- s.stop("Package deployed");
1150
+ const zipBuffer = await zipSourcePackage(destPkg);
1151
+ const deployId = await sf.deployMetadata(zipBuffer);
1152
+ const result = await sf.waitForDeploy(deployId);
1153
+ if (!result.success) {
1154
+ const failures = result.details?.componentFailures ?? [];
1155
+ const failureMsg = failures.map((f) => ` ${f.componentType}/${f.fullName}: ${f.problem}`).join("\n");
1156
+ s.stop("Deployment failed");
1157
+ throw new Error(
1158
+ `Metadata deploy failed (${result.numberComponentErrors} error(s)):
1159
+ ${failureMsg || result.errorMessage || "Unknown error"}`
1160
+ );
1161
+ }
1162
+ s.stop(`Package deployed (${result.numberComponentsDeployed} components)`);
1120
1163
  } catch (err) {
1164
+ if (err instanceof Error && err.message.startsWith("Metadata deploy failed")) {
1165
+ throw err;
1166
+ }
1121
1167
  s.stop("Deployment failed");
1122
1168
  throw new Error(
1123
- `sf project deploy failed: ${String(err)}
1169
+ `Metadata deploy failed: ${String(err)}
1124
1170
 
1125
- Retry manually:
1126
- cd ${destPkg}
1127
- sf project deploy start --target-org ${orgAlias} --source-dir force-app`
1171
+ The patched package is at: ${destPkg}
1172
+ You can retry with: sf project deploy start --source-dir force-app`
1128
1173
  );
1129
1174
  }
1130
1175
  s.start("Assigning LeadRouterAdmin permission set\u2026");
1131
1176
  try {
1132
- await execa3(
1133
- "sf",
1134
- ["org", "assign", "permset", "--name", "LeadRouterAdmin", ...targetOrgArgs],
1135
- { stdio: "inherit", env: { ...process.env, ...sfCredEnv } }
1177
+ const permSets = await sf.query(
1178
+ "SELECT Id FROM PermissionSet WHERE Name = 'LeadRouterAdmin' LIMIT 1"
1136
1179
  );
1137
- s.stop("Permission set assigned \u2014 Lead Router Setup will appear in the App Launcher");
1138
- } catch (err) {
1139
- const msg = String(err);
1140
- if (msg.includes("Duplicate PermissionSetAssignment")) {
1141
- s.stop("Permission set already assigned");
1180
+ if (permSets.length === 0) {
1181
+ s.stop("LeadRouterAdmin permission set not found (non-fatal)");
1182
+ log7.warn("The permission set may not have been included in the deploy.");
1142
1183
  } else {
1184
+ const userId = await sf.getCurrentUserId();
1185
+ try {
1186
+ await sf.create("PermissionSetAssignment", {
1187
+ AssigneeId: userId,
1188
+ PermissionSetId: permSets[0].Id
1189
+ });
1190
+ s.stop("Permission set assigned \u2014 Lead Router Setup will appear in the App Launcher");
1191
+ } catch (err) {
1192
+ if (err instanceof DuplicateError) {
1193
+ s.stop("Permission set already assigned");
1194
+ } else {
1195
+ throw err;
1196
+ }
1197
+ }
1198
+ }
1199
+ } catch (err) {
1200
+ if (!(err instanceof DuplicateError)) {
1143
1201
  s.stop("Permission set assignment failed (non-fatal)");
1144
- log7.warn(msg);
1202
+ log7.warn(String(err));
1145
1203
  log7.info(
1146
1204
  "Grant access manually:\n Salesforce Setup \u2192 Users \u2192 Permission Sets \u2192 Lead Router Admin \u2192 Manage Assignments"
1147
1205
  );
@@ -1149,44 +1207,19 @@ The CLI may need to be reinstalled: npm i -g @lead-routing/cli`
1149
1207
  }
1150
1208
  s.start("Writing org settings to Routing_Settings__c\u2026");
1151
1209
  try {
1152
- let existingId;
1153
- try {
1154
- const qr = await execa3("sf", [
1155
- "data",
1156
- "query",
1157
- ...targetOrgArgs,
1158
- "--query",
1159
- "SELECT Id FROM Routing_Settings__c LIMIT 1",
1160
- "--json"
1161
- ], { env: { ...process.env, ...sfCredEnv } });
1162
- const parsed = JSON.parse(qr.stdout);
1163
- existingId = parsed?.result?.records?.[0]?.Id;
1164
- } catch {
1165
- }
1166
- if (existingId) {
1167
- await execa3("sf", [
1168
- "data",
1169
- "update",
1170
- "record",
1171
- ...targetOrgArgs,
1172
- "--sobject",
1173
- "Routing_Settings__c",
1174
- "--record-id",
1175
- existingId,
1176
- "--values",
1177
- `App_Url__c='${appUrl}' Engine_Endpoint__c='${engineUrl}'`
1178
- ], { stdio: "inherit", env: { ...process.env, ...sfCredEnv } });
1210
+ const existing = await sf.query(
1211
+ "SELECT Id FROM Routing_Settings__c LIMIT 1"
1212
+ );
1213
+ if (existing.length > 0) {
1214
+ await sf.update("Routing_Settings__c", existing[0].Id, {
1215
+ App_Url__c: appUrl,
1216
+ Engine_Endpoint__c: engineUrl
1217
+ });
1179
1218
  } else {
1180
- await execa3("sf", [
1181
- "data",
1182
- "create",
1183
- "record",
1184
- ...targetOrgArgs,
1185
- "--sobject",
1186
- "Routing_Settings__c",
1187
- "--values",
1188
- `App_Url__c='${appUrl}' Engine_Endpoint__c='${engineUrl}'`
1189
- ], { stdio: "inherit", env: { ...process.env, ...sfCredEnv } });
1219
+ await sf.create("Routing_Settings__c", {
1220
+ App_Url__c: appUrl,
1221
+ Engine_Endpoint__c: engineUrl
1222
+ });
1190
1223
  }
1191
1224
  s.stop("Org settings written");
1192
1225
  } catch (err) {
@@ -1195,9 +1228,9 @@ The CLI may need to be reinstalled: npm i -g @lead-routing/cli`
1195
1228
  log7.info("Set manually: Salesforce \u2192 Custom Settings \u2192 Routing Settings \u2192 Manage");
1196
1229
  }
1197
1230
  }
1198
- async function loginViaAppBridge(rawAppUrl, orgAlias) {
1231
+ async function loginViaAppBridge(rawAppUrl) {
1199
1232
  const appUrl = rawAppUrl.replace(/\/+$/, "");
1200
- const s = spinner7();
1233
+ const s = spinner5();
1201
1234
  s.start("Starting Salesforce authentication via your Lead Router app\u2026");
1202
1235
  let sessionId;
1203
1236
  let authUrl;
@@ -1227,8 +1260,10 @@ Ensure the app is running and the URL is correct.`
1227
1260
  `);
1228
1261
  log7.info('If Chrome shows a "Dangerous site" warning with no proceed option, paste the URL into Safari or Firefox.');
1229
1262
  const opener = process.platform === "win32" ? "start" : process.platform === "darwin" ? "open" : "xdg-open";
1230
- await execa3(opener, [authUrl], { reject: false }).catch(() => {
1231
- });
1263
+ try {
1264
+ execSync(`${opener} "${authUrl}"`, { stdio: "ignore" });
1265
+ } catch {
1266
+ }
1232
1267
  s.start("Waiting for Salesforce authentication in browser\u2026");
1233
1268
  const maxPolls = 150;
1234
1269
  let accessToken;
@@ -1258,20 +1293,7 @@ Ensure the app is running and the URL is correct.`
1258
1293
  );
1259
1294
  }
1260
1295
  s.stop("Authenticated with Salesforce");
1261
- let aliasStored = false;
1262
- try {
1263
- await execa3(
1264
- "sf",
1265
- ["org", "login", "access-token", "--instance-url", instanceUrl, "--alias", orgAlias, "--no-prompt"],
1266
- { env: { ...process.env, SFDX_ACCESS_TOKEN: accessToken } }
1267
- );
1268
- log7.success(`Salesforce org saved as "${orgAlias}"`);
1269
- aliasStored = true;
1270
- } catch (err) {
1271
- log7.warn(`Could not persist sf CLI credentials: ${String(err)}`);
1272
- log7.info("Continuing with direct token auth for this session.");
1273
- }
1274
- return { accessToken, instanceUrl, aliasStored };
1296
+ return { accessToken, instanceUrl };
1275
1297
  }
1276
1298
 
1277
1299
  // src/steps/app-launcher-guide.ts
@@ -1412,10 +1434,10 @@ ${result.stderr || result.stdout}`
1412
1434
  }
1413
1435
  );
1414
1436
  });
1415
- return new Promise((resolve2, reject) => {
1437
+ return new Promise((resolve, reject) => {
1416
1438
  server.listen(0, "127.0.0.1", () => {
1417
1439
  const { port } = server.address();
1418
- resolve2({
1440
+ resolve({
1419
1441
  localPort: port,
1420
1442
  close: () => server.close()
1421
1443
  });
@@ -1490,9 +1512,9 @@ async function runInit(options = {}) {
1490
1512
  });
1491
1513
  log9.success(`Connected to ${saved.ssh.host}`);
1492
1514
  const remoteDir = await ssh.resolveHome(saved.remoteDir);
1493
- log9.step("Step 8/9 Verifying health");
1515
+ log9.step("Step 7/8 Verifying health");
1494
1516
  await verifyHealth(saved.appUrl, saved.engineUrl, ssh, remoteDir);
1495
- log9.step("Step 9/9 Deploying Salesforce package");
1517
+ log9.step("Step 8/8 Deploying Salesforce package");
1496
1518
  await sfdcDeployInline({
1497
1519
  appUrl: saved.appUrl,
1498
1520
  engineUrl: saved.engineUrl,
@@ -1524,9 +1546,9 @@ async function runInit(options = {}) {
1524
1546
  return;
1525
1547
  }
1526
1548
  try {
1527
- log9.step("Step 1/9 Checking local prerequisites");
1549
+ log9.step("Step 1/8 Checking local prerequisites");
1528
1550
  await checkPrerequisites();
1529
- log9.step("Step 2/9 SSH connection");
1551
+ log9.step("Step 2/8 SSH connection");
1530
1552
  const sshCfg = await collectSshConfig({
1531
1553
  sshPort: options.sshPort,
1532
1554
  sshUser: options.sshUser,
@@ -1543,14 +1565,14 @@ async function runInit(options = {}) {
1543
1565
  process.exit(1);
1544
1566
  }
1545
1567
  }
1546
- log9.step("Step 3/9 Configuration");
1568
+ log9.step("Step 3/8 Configuration");
1547
1569
  const cfg = await collectConfig({
1548
1570
  sandbox: options.sandbox,
1549
1571
  externalDb: options.externalDb,
1550
1572
  externalRedis: options.externalRedis
1551
1573
  });
1552
1574
  await checkDnsResolvable(cfg.appUrl, cfg.engineUrl);
1553
- log9.step("Step 4/9 Generating config files");
1575
+ log9.step("Step 4/8 Generating config files");
1554
1576
  const { dir, adminSecret } = generateFiles(cfg, sshCfg);
1555
1577
  note4(
1556
1578
  `Local config directory: ${chalk2.cyan(dir)}
@@ -1567,17 +1589,15 @@ Files created: docker-compose.yml, Caddyfile, .env.web, .env.engine, lead-routin
1567
1589
  );
1568
1590
  return;
1569
1591
  }
1570
- log9.step("Step 5/9 Remote setup");
1592
+ log9.step("Step 5/8 Remote setup");
1571
1593
  const remoteDir = await ssh.resolveHome(sshCfg.remoteDir);
1572
1594
  await checkRemotePrerequisites(ssh);
1573
1595
  await uploadFiles(ssh, dir, remoteDir);
1574
- log9.step("Step 6/9 Starting services");
1596
+ log9.step("Step 6/8 Starting services");
1575
1597
  await startServices(ssh, remoteDir);
1576
- log9.step("Step 7/9 Database migrations");
1577
- await runMigrations(ssh, dir, cfg.adminEmail, cfg.adminPassword);
1578
- log9.step("Step 8/9 Verifying health");
1598
+ log9.step("Step 7/8 Verifying health");
1579
1599
  await verifyHealth(cfg.appUrl, cfg.engineUrl, ssh, remoteDir);
1580
- log9.step("Step 9/9 Deploying Salesforce package");
1600
+ log9.step("Step 8/8 Deploying Salesforce package");
1581
1601
  await sfdcDeployInline({
1582
1602
  appUrl: cfg.appUrl,
1583
1603
  engineUrl: cfg.engineUrl,
@@ -1668,8 +1688,6 @@ async function runDeploy() {
1668
1688
  log10.step("Restarting services");
1669
1689
  await ssh.exec("docker compose up -d --remove-orphans", remoteDir);
1670
1690
  log10.success("Services restarted");
1671
- log10.step("Running database migrations");
1672
- await runMigrations(ssh, dir);
1673
1691
  outro2(
1674
1692
  chalk3.green("\u2714 Deployment complete!") + `
1675
1693
 
@@ -1687,7 +1705,7 @@ async function runDeploy() {
1687
1705
  // src/commands/doctor.ts
1688
1706
  import { intro as intro3, outro as outro3, log as log11 } from "@clack/prompts";
1689
1707
  import chalk4 from "chalk";
1690
- import { execa as execa4 } from "execa";
1708
+ import { execa } from "execa";
1691
1709
  async function runDoctor() {
1692
1710
  console.log();
1693
1711
  intro3(chalk4.bold.cyan("Lead Routing \u2014 Health Check"));
@@ -1725,7 +1743,7 @@ async function runDoctor() {
1725
1743
  }
1726
1744
  async function checkDockerDaemon() {
1727
1745
  try {
1728
- await execa4("docker", ["info"], { reject: true });
1746
+ await execa("docker", ["info"], { reject: true });
1729
1747
  return { label: "Docker daemon", pass: true };
1730
1748
  } catch {
1731
1749
  return { label: "Docker daemon", pass: false, detail: "not running" };
@@ -1733,7 +1751,7 @@ async function checkDockerDaemon() {
1733
1751
  }
1734
1752
  async function checkContainer(name, dir) {
1735
1753
  try {
1736
- const result = await execa4(
1754
+ const result = await execa(
1737
1755
  "docker",
1738
1756
  ["compose", "ps", "--format", "json", name],
1739
1757
  { cwd: dir, reject: false }
@@ -1777,7 +1795,7 @@ async function checkEndpoint(label, url) {
1777
1795
 
1778
1796
  // src/commands/logs.ts
1779
1797
  import { log as log12 } from "@clack/prompts";
1780
- import { execa as execa5 } from "execa";
1798
+ import { execa as execa2 } from "execa";
1781
1799
  var VALID_SERVICES = ["web", "engine", "postgres", "redis"];
1782
1800
  async function runLogs(service = "engine") {
1783
1801
  if (!VALID_SERVICES.includes(service)) {
@@ -1792,7 +1810,7 @@ async function runLogs(service = "engine") {
1792
1810
  console.log(`
1793
1811
  Streaming logs for ${service} (Ctrl+C to stop)...
1794
1812
  `);
1795
- const child = execa5("docker", ["compose", "logs", "-f", "--tail=100", service], {
1813
+ const child = execa2("docker", ["compose", "logs", "-f", "--tail=100", service], {
1796
1814
  cwd: dir,
1797
1815
  stdio: "inherit",
1798
1816
  reject: false
@@ -1802,14 +1820,14 @@ Streaming logs for ${service} (Ctrl+C to stop)...
1802
1820
 
1803
1821
  // src/commands/status.ts
1804
1822
  import { log as log13 } from "@clack/prompts";
1805
- import { execa as execa6 } from "execa";
1823
+ import { execa as execa3 } from "execa";
1806
1824
  async function runStatus() {
1807
1825
  const dir = findInstallDir();
1808
1826
  if (!dir) {
1809
1827
  log13.error("No lead-routing.json found. Run `lead-routing init` first.");
1810
1828
  process.exit(1);
1811
1829
  }
1812
- const result = await execa6("docker", ["compose", "ps"], {
1830
+ const result = await execa3("docker", ["compose", "ps"], {
1813
1831
  cwd: dir,
1814
1832
  stdio: "inherit",
1815
1833
  reject: false
@@ -1821,15 +1839,15 @@ async function runStatus() {
1821
1839
  }
1822
1840
 
1823
1841
  // src/commands/config.ts
1824
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync5 } from "fs";
1842
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync5, existsSync as existsSync4 } from "fs";
1825
1843
  import { join as join7 } from "path";
1826
- import { intro as intro4, outro as outro4, text as text3, password as password3, spinner as spinner8, log as log14 } from "@clack/prompts";
1844
+ import { intro as intro4, outro as outro4, text as text3, password as password3, spinner as spinner6, log as log14 } from "@clack/prompts";
1827
1845
  import chalk5 from "chalk";
1828
- import { execa as execa7 } from "execa";
1846
+ import { execa as execa4 } from "execa";
1829
1847
  function parseEnv(filePath) {
1830
1848
  const map = /* @__PURE__ */ new Map();
1831
- if (!existsSync5(filePath)) return map;
1832
- for (const line of readFileSync5(filePath, "utf8").split("\n")) {
1849
+ if (!existsSync4(filePath)) return map;
1850
+ for (const line of readFileSync4(filePath, "utf8").split("\n")) {
1833
1851
  const trimmed = line.trim();
1834
1852
  if (!trimmed || trimmed.startsWith("#")) continue;
1835
1853
  const eq = trimmed.indexOf("=");
@@ -1839,7 +1857,7 @@ function parseEnv(filePath) {
1839
1857
  return map;
1840
1858
  }
1841
1859
  function writeEnv(filePath, updates) {
1842
- const lines = existsSync5(filePath) ? readFileSync5(filePath, "utf8").split("\n") : [];
1860
+ const lines = existsSync4(filePath) ? readFileSync4(filePath, "utf8").split("\n") : [];
1843
1861
  const updated = /* @__PURE__ */ new Set();
1844
1862
  const result = lines.map((line) => {
1845
1863
  const trimmed = line.trim();
@@ -1899,10 +1917,10 @@ Callback URL for your Connected App: ${callbackUrl}`
1899
1917
  writeEnv(envWeb, updates);
1900
1918
  writeEnv(envEngine, updates);
1901
1919
  log14.success("Updated .env.web and .env.engine");
1902
- const s = spinner8();
1920
+ const s = spinner6();
1903
1921
  s.start("Restarting web and engine containers\u2026");
1904
1922
  try {
1905
- await execa7("docker", ["compose", "up", "-d", "--force-recreate", "web", "engine"], {
1923
+ await execa4("docker", ["compose", "up", "-d", "--force-recreate", "web", "engine"], {
1906
1924
  cwd: dir
1907
1925
  });
1908
1926
  s.stop("Containers restarted");
@@ -1936,9 +1954,8 @@ function runConfigShow() {
1936
1954
  }
1937
1955
 
1938
1956
  // src/commands/sfdc.ts
1939
- import { intro as intro5, outro as outro5, text as text4, spinner as spinner9, log as log15 } from "@clack/prompts";
1957
+ import { intro as intro5, outro as outro5, text as text4, log as log15 } from "@clack/prompts";
1940
1958
  import chalk6 from "chalk";
1941
- import { execa as execa8 } from "execa";
1942
1959
  async function runSfdcDeploy() {
1943
1960
  intro5("Lead Routing \u2014 Deploy Salesforce Package");
1944
1961
  let appUrl;
@@ -1964,18 +1981,6 @@ async function runSfdcDeploy() {
1964
1981
  if (typeof rawEngine === "symbol") process.exit(0);
1965
1982
  engineUrl = rawEngine.trim();
1966
1983
  }
1967
- const s = spinner9();
1968
- s.start("Checking Salesforce CLI\u2026");
1969
- try {
1970
- await execa8("sf", ["--version"], { all: true });
1971
- s.stop("Salesforce CLI found");
1972
- } catch {
1973
- s.stop("Salesforce CLI (sf) not found");
1974
- log15.error(
1975
- "Install the Salesforce CLI and re-run this command:\n https://developer.salesforce.com/tools/salesforcecli"
1976
- );
1977
- process.exit(1);
1978
- }
1979
1984
  const alias = await text4({
1980
1985
  message: "Salesforce org alias (used to log in)",
1981
1986
  placeholder: "lead-routing",
@@ -1988,7 +1993,7 @@ async function runSfdcDeploy() {
1988
1993
  appUrl,
1989
1994
  engineUrl,
1990
1995
  orgAlias: alias,
1991
- // Read from config if available; alreadyAuthed check will skip login if already logged in
1996
+ // Read from config if available
1992
1997
  sfdcClientId: config2?.sfdcClientId ?? "",
1993
1998
  sfdcLoginUrl: config2?.sfdcLoginUrl ?? "https://login.salesforce.com",
1994
1999
  installDir: dir ?? void 0
@@ -2006,7 +2011,7 @@ async function runSfdcDeploy() {
2006
2011
  }
2007
2012
 
2008
2013
  // src/commands/uninstall.ts
2009
- import { rmSync as rmSync2, existsSync as existsSync6 } from "fs";
2014
+ import { rmSync as rmSync2, existsSync as existsSync5 } from "fs";
2010
2015
  import { intro as intro6, outro as outro6, log as log16, confirm as confirm3, password as promptPassword3, isCancel as isCancel5 } from "@clack/prompts";
2011
2016
  import chalk7 from "chalk";
2012
2017
  async function runUninstall() {
@@ -2082,7 +2087,7 @@ async function runUninstall() {
2082
2087
  await ssh.disconnect();
2083
2088
  }
2084
2089
  log16.step("Removing local config directory");
2085
- if (existsSync6(dir)) {
2090
+ if (existsSync5(dir)) {
2086
2091
  rmSync2(dir, { recursive: true, force: true });
2087
2092
  log16.success(`Removed ${dir}`);
2088
2093
  }