bindler 1.6.0 → 1.6.1

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/cli.js CHANGED
@@ -11,10 +11,10 @@ import { Command } from "commander";
11
11
  import chalk31 from "chalk";
12
12
 
13
13
  // src/commands/new.ts
14
- import { existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
14
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
15
15
  import { basename as basename2 } from "path";
16
16
  import inquirer from "inquirer";
17
- import chalk2 from "chalk";
17
+ import chalk3 from "chalk";
18
18
 
19
19
  // src/lib/config.ts
20
20
  import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync } from "fs";
@@ -890,9 +890,296 @@ function runPreflightChecks(config) {
890
890
  return result;
891
891
  }
892
892
 
893
+ // src/commands/apply.ts
894
+ import chalk2 from "chalk";
895
+ import { existsSync as existsSync6 } from "fs";
896
+
897
+ // src/lib/cloudflare.ts
898
+ function isCloudflaredInstalled() {
899
+ const result = execCommandSafe("which cloudflared");
900
+ return result.success;
901
+ }
902
+ function getCloudflaredVersion() {
903
+ const result = execCommandSafe("cloudflared --version");
904
+ if (result.success) {
905
+ const match = result.output.match(/cloudflared version (\S+)/);
906
+ return match ? match[1] : result.output;
907
+ }
908
+ return null;
909
+ }
910
+ function listTunnels() {
911
+ const result = execCommandSafe("cloudflared tunnel list --output json");
912
+ if (!result.success) {
913
+ return [];
914
+ }
915
+ try {
916
+ const tunnels = JSON.parse(result.output);
917
+ return tunnels.map((t) => ({
918
+ id: t.id,
919
+ name: t.name,
920
+ createdAt: t.created_at
921
+ }));
922
+ } catch {
923
+ return [];
924
+ }
925
+ }
926
+ function getTunnelByName(name) {
927
+ const tunnels = listTunnels();
928
+ const tunnel = tunnels.find((t) => t.name === name);
929
+ return tunnel ? { id: tunnel.id, name: tunnel.name } : null;
930
+ }
931
+ function routeDns(tunnelName, hostname) {
932
+ const result = execCommandSafe(`cloudflared tunnel route dns "${tunnelName}" "${hostname}"`);
933
+ if (!result.success) {
934
+ if (result.error?.includes("already exists") || result.output?.includes("already exists")) {
935
+ return { success: true, output: "DNS route already exists" };
936
+ }
937
+ return { success: false, error: result.error };
938
+ }
939
+ return { success: true, output: result.output };
940
+ }
941
+ function routeDnsForAllProjects() {
942
+ const config = readConfig();
943
+ const { tunnelName, applyCloudflareDnsRoutes } = config.defaults;
944
+ if (!applyCloudflareDnsRoutes) {
945
+ return [];
946
+ }
947
+ const results = [];
948
+ for (const project of config.projects) {
949
+ if (project.enabled === false) {
950
+ continue;
951
+ }
952
+ if (project.local) {
953
+ results.push({
954
+ hostname: project.hostname,
955
+ success: true,
956
+ skipped: true,
957
+ output: "Local project - skipped"
958
+ });
959
+ continue;
960
+ }
961
+ const result = routeDns(tunnelName, project.hostname);
962
+ results.push({
963
+ hostname: project.hostname,
964
+ ...result
965
+ });
966
+ }
967
+ return results;
968
+ }
969
+ function isTunnelRunning(tunnelName) {
970
+ const result = execCommandSafe(`pgrep -f "cloudflared.*tunnel.*run.*${tunnelName}"`);
971
+ return result.success;
972
+ }
973
+ function getTunnelInfo(tunnelName) {
974
+ const tunnel = getTunnelByName(tunnelName);
975
+ if (!tunnel) {
976
+ return { exists: false, running: false };
977
+ }
978
+ return {
979
+ exists: true,
980
+ running: isTunnelRunning(tunnelName),
981
+ id: tunnel.id
982
+ };
983
+ }
984
+
985
+ // src/commands/apply.ts
986
+ async function applyCommand(options) {
987
+ let config = readConfig();
988
+ const defaults = getDefaults();
989
+ if (options.sync) {
990
+ console.log(chalk2.dim("Syncing bindler.yaml from project directories...\n"));
991
+ let synced = 0;
992
+ for (const project of config.projects) {
993
+ if (!existsSync6(project.path)) continue;
994
+ const yamlConfig = readBindlerYaml(project.path);
995
+ if (yamlConfig) {
996
+ const merged = mergeYamlWithProject(project, yamlConfig);
997
+ updateProject(project.name, merged);
998
+ console.log(chalk2.green(` \u2713 Synced ${project.name} from bindler.yaml`));
999
+ synced++;
1000
+ }
1001
+ }
1002
+ if (synced === 0) {
1003
+ console.log(chalk2.dim(" No bindler.yaml files found in project directories"));
1004
+ } else {
1005
+ console.log(chalk2.dim(`
1006
+ Synced ${synced} project(s)
1007
+ `));
1008
+ }
1009
+ config = readConfig();
1010
+ }
1011
+ if (options.env) {
1012
+ console.log(chalk2.dim(`Using ${options.env} environment configuration...
1013
+ `));
1014
+ const envProjects = listProjectsForEnv(options.env);
1015
+ config = { ...config, projects: envProjects };
1016
+ }
1017
+ const hasProjects = config.projects.length > 0;
1018
+ console.log(chalk2.blue("Applying configuration...\n"));
1019
+ if (hasProjects && !options.skipChecks) {
1020
+ console.log(chalk2.dim("Running preflight checks..."));
1021
+ const checkResult = runPreflightChecks(config);
1022
+ if (!checkResult.valid) {
1023
+ printValidationResult(checkResult);
1024
+ console.log(chalk2.red("\n\u2717 Preflight checks failed. Fix the errors above before applying."));
1025
+ console.log(chalk2.dim(" Use --skip-checks to bypass (not recommended)"));
1026
+ process.exit(1);
1027
+ }
1028
+ if (checkResult.warnings.length > 0) {
1029
+ printValidationResult(checkResult);
1030
+ console.log("");
1031
+ } else {
1032
+ console.log(chalk2.green(" \u2713 Preflight checks passed"));
1033
+ }
1034
+ }
1035
+ console.log(chalk2.dim("Generating nginx configuration..."));
1036
+ if (options.dryRun) {
1037
+ const nginxConfig = generateNginxConfig(config);
1038
+ console.log(chalk2.cyan("\n--- Generated nginx config (dry-run) ---\n"));
1039
+ console.log(nginxConfig);
1040
+ console.log(chalk2.cyan("--- End of config ---\n"));
1041
+ console.log(chalk2.yellow("Dry run mode - no changes were made."));
1042
+ return;
1043
+ }
1044
+ try {
1045
+ const { path, content } = writeNginxConfig(config);
1046
+ console.log(chalk2.green(` \u2713 Wrote nginx config to ${path}`));
1047
+ } catch (error) {
1048
+ const errMsg = error instanceof Error ? error.message : String(error);
1049
+ console.error(chalk2.red(` \u2717 Failed to write nginx config: ${errMsg}`));
1050
+ if (errMsg.includes("EACCES") || errMsg.includes("permission denied")) {
1051
+ console.log(chalk2.yellow(`
1052
+ Try running with sudo: ${chalk2.cyan("sudo bindler apply")}`));
1053
+ }
1054
+ process.exit(1);
1055
+ }
1056
+ const authProjects = config.projects.filter(
1057
+ (p) => p.security?.basicAuth?.enabled && p.security.basicAuth.users?.length
1058
+ );
1059
+ if (authProjects.length > 0) {
1060
+ console.log(chalk2.dim("Generating htpasswd files..."));
1061
+ try {
1062
+ generateHtpasswdFiles(config.projects);
1063
+ console.log(chalk2.green(` \u2713 Generated htpasswd files for ${authProjects.length} project(s)`));
1064
+ } catch (error) {
1065
+ console.log(chalk2.yellow(` ! Failed to generate htpasswd files: ${error}`));
1066
+ }
1067
+ }
1068
+ console.log(chalk2.dim("Testing nginx configuration..."));
1069
+ const testResult = testNginxConfig();
1070
+ if (!testResult.success) {
1071
+ console.error(chalk2.red(" \u2717 Nginx configuration test failed:"));
1072
+ console.error(chalk2.red(testResult.output));
1073
+ console.log(chalk2.yellow("\nConfiguration was written but nginx was NOT reloaded."));
1074
+ console.log(chalk2.dim("Fix the configuration and run `sudo bindler apply` again."));
1075
+ process.exit(1);
1076
+ }
1077
+ console.log(chalk2.green(" \u2713 Nginx configuration test passed"));
1078
+ if (!options.noReload) {
1079
+ console.log(chalk2.dim("Reloading nginx..."));
1080
+ const reloadResult = reloadNginx();
1081
+ if (!reloadResult.success) {
1082
+ console.error(chalk2.red(` \u2717 Failed to reload nginx: ${reloadResult.error}`));
1083
+ console.log(chalk2.dim("You may need to reload nginx manually: sudo systemctl reload nginx"));
1084
+ process.exit(1);
1085
+ }
1086
+ console.log(chalk2.green(" \u2713 Nginx reloaded successfully"));
1087
+ const listenPort = parseInt(defaults.nginxListen.split(":").pop() || "80", 10);
1088
+ const portCheck = execCommandSafe(`lsof -i :${listenPort} -P -n 2>/dev/null | grep LISTEN | grep nginx`);
1089
+ if (!portCheck.success || !portCheck.output) {
1090
+ const isPrivilegedPort = listenPort < 1024;
1091
+ console.log(chalk2.yellow(`
1092
+ \u26A0 Nginx is not listening on port ${listenPort}`));
1093
+ if (isPrivilegedPort) {
1094
+ console.log(chalk2.dim(` Port ${listenPort} requires root privileges.
1095
+ `));
1096
+ console.log(chalk2.cyan(" Solutions:\n"));
1097
+ console.log(chalk2.white(" Option 1: Restart nginx with sudo (recommended for port 80)"));
1098
+ console.log(chalk2.dim(" sudo pkill nginx && sudo nginx\n"));
1099
+ console.log(chalk2.white(" Option 2: Use a non-privileged port (no sudo needed)"));
1100
+ console.log(chalk2.dim(" bindler config set nginxListen 8080"));
1101
+ console.log(chalk2.dim(" bindler apply"));
1102
+ console.log(chalk2.dim(" # Then access via http://hostname:8080\n"));
1103
+ } else {
1104
+ console.log(chalk2.dim(" Try restarting nginx: brew services restart nginx"));
1105
+ }
1106
+ }
1107
+ } else {
1108
+ console.log(chalk2.yellow(" - Skipped nginx reload (--no-reload)"));
1109
+ }
1110
+ const isDirectMode = defaults.mode === "direct";
1111
+ if (!hasProjects) {
1112
+ } else if (isDirectMode) {
1113
+ console.log(chalk2.dim("\n - Direct mode: skipping Cloudflare DNS routes"));
1114
+ } else if (!options.noCloudflare && defaults.applyCloudflareDnsRoutes) {
1115
+ console.log(chalk2.dim("\nConfiguring Cloudflare DNS routes..."));
1116
+ if (!isCloudflaredInstalled()) {
1117
+ console.log(chalk2.yellow(" - cloudflared not installed, skipping DNS routes"));
1118
+ } else {
1119
+ const dnsResults = routeDnsForAllProjects();
1120
+ if (dnsResults.length === 0) {
1121
+ console.log(chalk2.dim(" No hostnames to route"));
1122
+ } else {
1123
+ for (const result of dnsResults) {
1124
+ if (result.skipped) {
1125
+ console.log(chalk2.dim(` - ${result.hostname} (local - skipped)`));
1126
+ } else if (result.success) {
1127
+ const msg = result.output?.includes("already exists") ? "exists" : "routed";
1128
+ console.log(chalk2.green(` \u2713 ${result.hostname} (${msg})`));
1129
+ } else {
1130
+ console.log(chalk2.red(` \u2717 ${result.hostname}: ${result.error}`));
1131
+ }
1132
+ }
1133
+ }
1134
+ }
1135
+ } else if (options.noCloudflare) {
1136
+ console.log(chalk2.dim("\n - Skipped Cloudflare DNS routes (--no-cloudflare)"));
1137
+ }
1138
+ if (hasProjects && isDirectMode && defaults.sslEnabled && options.ssl !== false) {
1139
+ console.log(chalk2.dim("\nSetting up SSL certificates..."));
1140
+ const hostnames = config.projects.filter((p) => p.enabled !== false && !p.local).map((p) => p.hostname);
1141
+ if (hostnames.length === 0) {
1142
+ console.log(chalk2.dim(" No hostnames to secure"));
1143
+ } else {
1144
+ const certbotResult = execCommandSafe("which certbot");
1145
+ if (!certbotResult.success) {
1146
+ console.log(chalk2.yellow(" - certbot not installed, skipping SSL"));
1147
+ console.log(chalk2.dim(" Run: bindler setup --direct"));
1148
+ } else {
1149
+ for (const hostname of hostnames) {
1150
+ console.log(chalk2.dim(` Requesting certificate for ${hostname}...`));
1151
+ const email = defaults.sslEmail || "admin@" + hostname.split(".").slice(-2).join(".");
1152
+ const result = execCommandSafe(
1153
+ `sudo certbot --nginx -d ${hostname} --non-interactive --agree-tos --email ${email} 2>&1`
1154
+ );
1155
+ if (result.success || result.output?.includes("Certificate not yet due for renewal")) {
1156
+ console.log(chalk2.green(` \u2713 ${hostname} (secured)`));
1157
+ } else if (result.output?.includes("already exists")) {
1158
+ console.log(chalk2.green(` \u2713 ${hostname} (exists)`));
1159
+ } else {
1160
+ console.log(chalk2.yellow(` ! ${hostname}: ${result.error || "failed"}`));
1161
+ console.log(chalk2.dim(" Run manually: sudo certbot --nginx -d " + hostname));
1162
+ }
1163
+ }
1164
+ }
1165
+ }
1166
+ }
1167
+ console.log(chalk2.green("\n\u2713 Configuration applied successfully!"));
1168
+ if (hasProjects) {
1169
+ console.log(chalk2.dim(`
1170
+ ${config.projects.length} project(s) configured:`));
1171
+ for (const project of config.projects) {
1172
+ const status = project.enabled !== false ? chalk2.green("enabled") : chalk2.yellow("disabled");
1173
+ console.log(chalk2.dim(` - ${project.name} \u2192 ${project.hostname} (${status})`));
1174
+ }
1175
+ } else {
1176
+ console.log(chalk2.dim("\nNo projects configured. Nginx config cleared."));
1177
+ }
1178
+ }
1179
+
893
1180
  // src/commands/new.ts
894
1181
  async function newCommand(options) {
895
- console.log(chalk2.dim("Checking prerequisites...\n"));
1182
+ console.log(chalk3.dim("Checking prerequisites...\n"));
896
1183
  const issues = [];
897
1184
  if (!isNginxInstalled()) {
898
1185
  issues.push("nginx is not installed. Install: brew install nginx (macOS) or apt install nginx (Linux)");
@@ -901,11 +1188,11 @@ async function newCommand(options) {
901
1188
  issues.push("PM2 is not installed. Install: npm install -g pm2");
902
1189
  }
903
1190
  if (issues.length > 0) {
904
- console.log(chalk2.red("Missing prerequisites:\n"));
1191
+ console.log(chalk3.red("Missing prerequisites:\n"));
905
1192
  for (const issue of issues) {
906
- console.log(chalk2.red(` \u2717 ${issue}`));
1193
+ console.log(chalk3.red(` \u2717 ${issue}`));
907
1194
  }
908
- console.log(chalk2.dim("\nRun `bindler doctor` for full diagnostics."));
1195
+ console.log(chalk3.dim("\nRun `bindler doctor` for full diagnostics."));
909
1196
  const { proceed } = await inquirer.prompt([
910
1197
  {
911
1198
  type: "confirm",
@@ -919,17 +1206,17 @@ async function newCommand(options) {
919
1206
  }
920
1207
  console.log("");
921
1208
  } else {
922
- console.log(chalk2.green("\u2713 Prerequisites OK\n"));
1209
+ console.log(chalk3.green("\u2713 Prerequisites OK\n"));
923
1210
  }
924
1211
  const defaults = getDefaults();
925
1212
  let project = {};
926
1213
  const cwd = process.cwd();
927
1214
  const cwdName = basename2(cwd);
928
1215
  const initialPath = options.path || cwd;
929
- const yamlConfig = existsSync6(initialPath) ? readBindlerYaml(initialPath) : null;
1216
+ const yamlConfig = existsSync7(initialPath) ? readBindlerYaml(initialPath) : null;
930
1217
  let yamlDefaults = {};
931
1218
  if (yamlConfig) {
932
- console.log(chalk2.cyan("Found bindler.yaml - using as defaults\n"));
1219
+ console.log(chalk3.cyan("Found bindler.yaml - using as defaults\n"));
933
1220
  yamlDefaults = yamlToProject(yamlConfig, initialPath);
934
1221
  }
935
1222
  if (!options.name) {
@@ -963,7 +1250,7 @@ async function newCommand(options) {
963
1250
  name: "type",
964
1251
  message: "Project type:",
965
1252
  choices: (answers2) => {
966
- const detected = existsSync6(answers2.path) ? detectProjectType(answers2.path) : "static";
1253
+ const detected = existsSync7(answers2.path) ? detectProjectType(answers2.path) : "static";
967
1254
  return [
968
1255
  { name: `npm (Node.js app)${detected === "npm" ? " - detected" : ""}`, value: "npm" },
969
1256
  { name: `static (HTML/CSS/JS)${detected === "static" ? " - detected" : ""}`, value: "static" }
@@ -971,7 +1258,7 @@ async function newCommand(options) {
971
1258
  },
972
1259
  default: (answers2) => {
973
1260
  if (yamlDefaults.type) return yamlDefaults.type;
974
- return existsSync6(answers2.path) ? detectProjectType(answers2.path) : "static";
1261
+ return existsSync7(answers2.path) ? detectProjectType(answers2.path) : "static";
975
1262
  }
976
1263
  },
977
1264
  {
@@ -1004,7 +1291,7 @@ async function newCommand(options) {
1004
1291
  if (yamlDefaults.security) project.security = yamlDefaults.security;
1005
1292
  if (yamlDefaults.environments) project.environments = yamlDefaults.environments;
1006
1293
  if (answers.type === "npm") {
1007
- const scripts = existsSync6(answers.path) ? getPackageJsonScripts(answers.path) : [];
1294
+ const scripts = existsSync7(answers.path) ? getPackageJsonScripts(answers.path) : [];
1008
1295
  let suggestedPort = yamlDefaults.port || findAvailablePort();
1009
1296
  const portCheck = checkPortInUse(suggestedPort);
1010
1297
  if (portCheck.inUse) {
@@ -1073,7 +1360,7 @@ async function newCommand(options) {
1073
1360
  }
1074
1361
  } else {
1075
1362
  if (!options.hostname) {
1076
- console.error(chalk2.red("Error: --hostname is required"));
1363
+ console.error(chalk3.red("Error: --hostname is required"));
1077
1364
  process.exit(1);
1078
1365
  }
1079
1366
  project.name = options.name;
@@ -1091,13 +1378,13 @@ async function newCommand(options) {
1091
1378
  const portCheck = checkPortInUse(port);
1092
1379
  if (portCheck.inUse) {
1093
1380
  const processInfo = portCheck.process ? ` by ${portCheck.process}` : "";
1094
- console.log(chalk2.yellow(`Warning: Port ${port} is already in use${processInfo}`));
1381
+ console.log(chalk3.yellow(`Warning: Port ${port} is already in use${processInfo}`));
1095
1382
  if (!options.port) {
1096
1383
  for (let p = port + 1; p <= 9e3; p++) {
1097
1384
  const check = checkPortInUse(p);
1098
1385
  if (!check.inUse) {
1099
1386
  port = p;
1100
- console.log(chalk2.dim(` Using port ${port} instead`));
1387
+ console.log(chalk3.dim(` Using port ${port} instead`));
1101
1388
  break;
1102
1389
  }
1103
1390
  }
@@ -1109,14 +1396,14 @@ async function newCommand(options) {
1109
1396
  }
1110
1397
  }
1111
1398
  if (!validateProjectName(project.name)) {
1112
- console.error(chalk2.red("Error: Invalid project name"));
1399
+ console.error(chalk3.red("Error: Invalid project name"));
1113
1400
  process.exit(1);
1114
1401
  }
1115
1402
  if (!validateHostname(project.hostname)) {
1116
- console.error(chalk2.red("Error: Invalid hostname"));
1403
+ console.error(chalk3.red("Error: Invalid hostname"));
1117
1404
  process.exit(1);
1118
1405
  }
1119
- if (!existsSync6(project.path)) {
1406
+ if (!existsSync7(project.path)) {
1120
1407
  const createDir = options.name ? true : (await inquirer.prompt([
1121
1408
  {
1122
1409
  type: "confirm",
@@ -1127,7 +1414,7 @@ async function newCommand(options) {
1127
1414
  ])).create;
1128
1415
  if (createDir) {
1129
1416
  mkdirSync3(project.path, { recursive: true });
1130
- console.log(chalk2.green(`Created directory: ${project.path}`));
1417
+ console.log(chalk3.green(`Created directory: ${project.path}`));
1131
1418
  }
1132
1419
  }
1133
1420
  const validationResult = validateProject(project);
@@ -1135,7 +1422,7 @@ async function newCommand(options) {
1135
1422
  console.log("");
1136
1423
  printValidationResult(validationResult);
1137
1424
  if (!validationResult.valid) {
1138
- console.log(chalk2.red("\n\u2717 Cannot add project due to validation errors."));
1425
+ console.log(chalk3.red("\n\u2717 Cannot add project due to validation errors."));
1139
1426
  process.exit(1);
1140
1427
  }
1141
1428
  const { proceed } = await inquirer.prompt([
@@ -1147,52 +1434,57 @@ async function newCommand(options) {
1147
1434
  }
1148
1435
  ]);
1149
1436
  if (!proceed) {
1150
- console.log(chalk2.yellow("Aborted."));
1437
+ console.log(chalk3.yellow("Aborted."));
1151
1438
  process.exit(0);
1152
1439
  }
1153
1440
  }
1154
1441
  try {
1155
1442
  addProject(project);
1156
- console.log(chalk2.green(`
1443
+ console.log(chalk3.green(`
1157
1444
  Project "${project.name}" added successfully!`));
1158
- if (project.local) {
1159
- console.log(chalk2.yellow(`
1160
- Local project - add to /etc/hosts:`));
1161
- console.log(chalk2.cyan(` echo "127.0.0.1 ${project.hostname}" | sudo tee -a /etc/hosts`));
1162
- console.log(chalk2.dim(`
1163
- Run ${chalk2.cyan("sudo bindler apply")} to update nginx.`));
1164
- console.log(chalk2.dim(`Then access at: ${chalk2.cyan(`http://${project.hostname}:8080`)}`));
1445
+ if (options.apply) {
1446
+ console.log("");
1447
+ await applyCommand({});
1165
1448
  } else {
1166
- console.log(chalk2.dim(`
1167
- Configuration saved. Run ${chalk2.cyan("sudo bindler apply")} to update nginx and cloudflare.`));
1449
+ if (project.local) {
1450
+ console.log(chalk3.yellow(`
1451
+ Local project - add to /etc/hosts:`));
1452
+ console.log(chalk3.cyan(` echo "127.0.0.1 ${project.hostname}" | sudo tee -a /etc/hosts`));
1453
+ console.log(chalk3.dim(`
1454
+ Run ${chalk3.cyan("sudo bindler apply")} to update nginx.`));
1455
+ console.log(chalk3.dim(`Then access at: ${chalk3.cyan(`http://${project.hostname}:8080`)}`));
1456
+ } else {
1457
+ console.log(chalk3.dim(`
1458
+ Configuration saved. Run ${chalk3.cyan("sudo bindler apply")} to update nginx and cloudflare.`));
1459
+ }
1168
1460
  }
1169
1461
  if (project.type === "npm") {
1170
- console.log(chalk2.dim(`Run ${chalk2.cyan(`bindler start ${project.name}`)} to start the application.`));
1462
+ console.log(chalk3.dim(`Run ${chalk3.cyan(`bindler start ${project.name}`)} to start the application.`));
1171
1463
  }
1172
1464
  } catch (error) {
1173
- console.error(chalk2.red(`Error: ${error instanceof Error ? error.message : error}`));
1465
+ console.error(chalk3.red(`Error: ${error instanceof Error ? error.message : error}`));
1174
1466
  process.exit(1);
1175
1467
  }
1176
1468
  }
1177
1469
 
1178
1470
  // src/commands/list.ts
1179
- import chalk3 from "chalk";
1471
+ import chalk4 from "chalk";
1180
1472
  import Table from "cli-table3";
1181
1473
  async function listCommand() {
1182
1474
  const projects = listProjects();
1183
1475
  if (projects.length === 0) {
1184
- console.log(chalk3.yellow("No projects registered."));
1185
- console.log(chalk3.dim(`Run ${chalk3.cyan("bindler new")} to create one.`));
1476
+ console.log(chalk4.yellow("No projects registered."));
1477
+ console.log(chalk4.dim(`Run ${chalk4.cyan("bindler new")} to create one.`));
1186
1478
  return;
1187
1479
  }
1188
1480
  const table = new Table({
1189
1481
  head: [
1190
- chalk3.cyan("Name"),
1191
- chalk3.cyan("Type"),
1192
- chalk3.cyan("Hostname"),
1193
- chalk3.cyan("Port"),
1194
- chalk3.cyan("Path"),
1195
- chalk3.cyan("Status")
1482
+ chalk4.cyan("Name"),
1483
+ chalk4.cyan("Type"),
1484
+ chalk4.cyan("Hostname"),
1485
+ chalk4.cyan("Port"),
1486
+ chalk4.cyan("Path"),
1487
+ chalk4.cyan("Status")
1196
1488
  ],
1197
1489
  style: {
1198
1490
  head: [],
@@ -1204,15 +1496,15 @@ async function listCommand() {
1204
1496
  if (project.type === "npm") {
1205
1497
  const process2 = getProcessByName(project.name);
1206
1498
  if (process2) {
1207
- status = process2.status === "online" ? chalk3.green("online") : process2.status === "stopped" ? chalk3.yellow("stopped") : chalk3.red(process2.status);
1499
+ status = process2.status === "online" ? chalk4.green("online") : process2.status === "stopped" ? chalk4.yellow("stopped") : chalk4.red(process2.status);
1208
1500
  } else {
1209
- status = chalk3.dim("not started");
1501
+ status = chalk4.dim("not started");
1210
1502
  }
1211
1503
  } else {
1212
- status = project.enabled !== false ? chalk3.green("serving") : chalk3.yellow("disabled");
1504
+ status = project.enabled !== false ? chalk4.green("serving") : chalk4.yellow("disabled");
1213
1505
  }
1214
1506
  if (project.enabled === false) {
1215
- status = chalk3.yellow("disabled");
1507
+ status = chalk4.yellow("disabled");
1216
1508
  }
1217
1509
  table.push([
1218
1510
  project.name,
@@ -1224,17 +1516,17 @@ async function listCommand() {
1224
1516
  ]);
1225
1517
  }
1226
1518
  console.log(table.toString());
1227
- console.log(chalk3.dim(`
1519
+ console.log(chalk4.dim(`
1228
1520
  ${projects.length} project(s) registered`));
1229
1521
  }
1230
1522
 
1231
1523
  // src/commands/status.ts
1232
- import chalk4 from "chalk";
1524
+ import chalk5 from "chalk";
1233
1525
  import Table2 from "cli-table3";
1234
1526
  async function statusCommand() {
1235
1527
  const projects = listProjects();
1236
1528
  if (projects.length === 0) {
1237
- console.log(chalk4.yellow("No projects registered."));
1529
+ console.log(chalk5.yellow("No projects registered."));
1238
1530
  return;
1239
1531
  }
1240
1532
  const statuses = [];
@@ -1258,12 +1550,12 @@ async function statusCommand() {
1258
1550
  }
1259
1551
  const table = new Table2({
1260
1552
  head: [
1261
- chalk4.cyan("Name"),
1262
- chalk4.cyan("Type"),
1263
- chalk4.cyan("Hostname"),
1264
- chalk4.cyan("Port"),
1265
- chalk4.cyan("PM2 Status"),
1266
- chalk4.cyan("Port Check")
1553
+ chalk5.cyan("Name"),
1554
+ chalk5.cyan("Type"),
1555
+ chalk5.cyan("Hostname"),
1556
+ chalk5.cyan("Port"),
1557
+ chalk5.cyan("PM2 Status"),
1558
+ chalk5.cyan("Port Check")
1267
1559
  ],
1268
1560
  style: {
1269
1561
  head: [],
@@ -1276,25 +1568,25 @@ async function statusCommand() {
1276
1568
  if (status.type === "npm") {
1277
1569
  switch (status.pm2Status) {
1278
1570
  case "online":
1279
- pm2StatusStr = chalk4.green("online");
1571
+ pm2StatusStr = chalk5.green("online");
1280
1572
  break;
1281
1573
  case "stopped":
1282
- pm2StatusStr = chalk4.yellow("stopped");
1574
+ pm2StatusStr = chalk5.yellow("stopped");
1283
1575
  break;
1284
1576
  case "errored":
1285
- pm2StatusStr = chalk4.red("errored");
1577
+ pm2StatusStr = chalk5.red("errored");
1286
1578
  break;
1287
1579
  case "not_managed":
1288
- pm2StatusStr = chalk4.dim("not started");
1580
+ pm2StatusStr = chalk5.dim("not started");
1289
1581
  break;
1290
1582
  default:
1291
- pm2StatusStr = chalk4.dim(status.pm2Status || "-");
1583
+ pm2StatusStr = chalk5.dim(status.pm2Status || "-");
1292
1584
  }
1293
- portCheckStr = status.portListening ? chalk4.green("listening") : chalk4.red("not listening");
1585
+ portCheckStr = status.portListening ? chalk5.green("listening") : chalk5.red("not listening");
1294
1586
  }
1295
1587
  if (status.enabled === false) {
1296
- pm2StatusStr = chalk4.yellow("disabled");
1297
- portCheckStr = chalk4.dim("-");
1588
+ pm2StatusStr = chalk5.yellow("disabled");
1589
+ portCheckStr = chalk5.dim("-");
1298
1590
  }
1299
1591
  table.push([
1300
1592
  status.name,
@@ -1308,14 +1600,14 @@ async function statusCommand() {
1308
1600
  console.log(table.toString());
1309
1601
  const runningProcesses = getPm2List().filter((p) => p.name.startsWith("bindler:"));
1310
1602
  if (runningProcesses.length > 0) {
1311
- console.log(chalk4.bold("\nPM2 Process Details:"));
1603
+ console.log(chalk5.bold("\nPM2 Process Details:"));
1312
1604
  const detailTable = new Table2({
1313
1605
  head: [
1314
- chalk4.cyan("Process"),
1315
- chalk4.cyan("CPU"),
1316
- chalk4.cyan("Memory"),
1317
- chalk4.cyan("Uptime"),
1318
- chalk4.cyan("Restarts")
1606
+ chalk5.cyan("Process"),
1607
+ chalk5.cyan("CPU"),
1608
+ chalk5.cyan("Memory"),
1609
+ chalk5.cyan("Uptime"),
1610
+ chalk5.cyan("Restarts")
1319
1611
  ],
1320
1612
  style: {
1321
1613
  head: [],
@@ -1336,198 +1628,198 @@ async function statusCommand() {
1336
1628
  }
1337
1629
 
1338
1630
  // src/commands/start.ts
1339
- import chalk5 from "chalk";
1631
+ import chalk6 from "chalk";
1340
1632
  async function startCommand(name, options) {
1341
1633
  if (options.all) {
1342
1634
  const projects = listProjects();
1343
1635
  const npmProjects = projects.filter((p) => p.type === "npm");
1344
1636
  if (npmProjects.length === 0) {
1345
- console.log(chalk5.yellow("No npm projects to start."));
1637
+ console.log(chalk6.yellow("No npm projects to start."));
1346
1638
  return;
1347
1639
  }
1348
- console.log(chalk5.blue(`Starting ${npmProjects.length} npm project(s)...`));
1640
+ console.log(chalk6.blue(`Starting ${npmProjects.length} npm project(s)...`));
1349
1641
  const results = startAllProjects(npmProjects);
1350
1642
  for (const result2 of results) {
1351
1643
  if (result2.success) {
1352
- console.log(chalk5.green(` \u2713 ${result2.name}`));
1644
+ console.log(chalk6.green(` \u2713 ${result2.name}`));
1353
1645
  } else {
1354
- console.log(chalk5.red(` \u2717 ${result2.name}: ${result2.error}`));
1646
+ console.log(chalk6.red(` \u2717 ${result2.name}: ${result2.error}`));
1355
1647
  }
1356
1648
  }
1357
1649
  const succeeded = results.filter((r) => r.success).length;
1358
- console.log(chalk5.dim(`
1650
+ console.log(chalk6.dim(`
1359
1651
  ${succeeded}/${results.length} started successfully`));
1360
1652
  return;
1361
1653
  }
1362
1654
  if (!name) {
1363
- console.error(chalk5.red("Error: Project name is required. Use --all to start all projects."));
1655
+ console.error(chalk6.red("Error: Project name is required. Use --all to start all projects."));
1364
1656
  process.exit(1);
1365
1657
  }
1366
1658
  const project = getProject(name);
1367
1659
  if (!project) {
1368
- console.error(chalk5.red(`Error: Project "${name}" not found`));
1660
+ console.error(chalk6.red(`Error: Project "${name}" not found`));
1369
1661
  process.exit(1);
1370
1662
  }
1371
1663
  if (project.type !== "npm") {
1372
- console.log(chalk5.blue(`Project "${name}" is a static site - no process to start.`));
1373
- console.log(chalk5.dim("Static sites are served directly by nginx."));
1664
+ console.log(chalk6.blue(`Project "${name}" is a static site - no process to start.`));
1665
+ console.log(chalk6.dim("Static sites are served directly by nginx."));
1374
1666
  return;
1375
1667
  }
1376
- console.log(chalk5.blue(`Starting ${name}...`));
1668
+ console.log(chalk6.blue(`Starting ${name}...`));
1377
1669
  const result = startProject(project);
1378
1670
  if (result.success) {
1379
- console.log(chalk5.green(`\u2713 ${name} started successfully`));
1380
- console.log(chalk5.dim(` Port: ${project.port}`));
1381
- console.log(chalk5.dim(` URL: https://${project.hostname}`));
1671
+ console.log(chalk6.green(`\u2713 ${name} started successfully`));
1672
+ console.log(chalk6.dim(` Port: ${project.port}`));
1673
+ console.log(chalk6.dim(` URL: https://${project.hostname}`));
1382
1674
  } else {
1383
- console.error(chalk5.red(`\u2717 Failed to start ${name}: ${result.error}`));
1675
+ console.error(chalk6.red(`\u2717 Failed to start ${name}: ${result.error}`));
1384
1676
  process.exit(1);
1385
1677
  }
1386
1678
  }
1387
1679
 
1388
1680
  // src/commands/stop.ts
1389
- import chalk6 from "chalk";
1681
+ import chalk7 from "chalk";
1390
1682
  async function stopCommand(name, options) {
1391
1683
  if (options.all) {
1392
1684
  const projects = listProjects();
1393
1685
  const npmProjects = projects.filter((p) => p.type === "npm");
1394
1686
  if (npmProjects.length === 0) {
1395
- console.log(chalk6.yellow("No npm projects to stop."));
1687
+ console.log(chalk7.yellow("No npm projects to stop."));
1396
1688
  return;
1397
1689
  }
1398
- console.log(chalk6.blue(`Stopping ${npmProjects.length} npm project(s)...`));
1690
+ console.log(chalk7.blue(`Stopping ${npmProjects.length} npm project(s)...`));
1399
1691
  const results = stopAllProjects(npmProjects);
1400
1692
  for (const result2 of results) {
1401
1693
  if (result2.success) {
1402
- console.log(chalk6.green(` \u2713 ${result2.name}`));
1694
+ console.log(chalk7.green(` \u2713 ${result2.name}`));
1403
1695
  } else {
1404
- console.log(chalk6.red(` \u2717 ${result2.name}: ${result2.error}`));
1696
+ console.log(chalk7.red(` \u2717 ${result2.name}: ${result2.error}`));
1405
1697
  }
1406
1698
  }
1407
1699
  const succeeded = results.filter((r) => r.success).length;
1408
- console.log(chalk6.dim(`
1700
+ console.log(chalk7.dim(`
1409
1701
  ${succeeded}/${results.length} stopped successfully`));
1410
1702
  return;
1411
1703
  }
1412
1704
  if (!name) {
1413
- console.error(chalk6.red("Error: Project name is required. Use --all to stop all projects."));
1705
+ console.error(chalk7.red("Error: Project name is required. Use --all to stop all projects."));
1414
1706
  process.exit(1);
1415
1707
  }
1416
1708
  const project = getProject(name);
1417
1709
  if (!project) {
1418
- console.error(chalk6.red(`Error: Project "${name}" not found`));
1710
+ console.error(chalk7.red(`Error: Project "${name}" not found`));
1419
1711
  process.exit(1);
1420
1712
  }
1421
1713
  if (project.type !== "npm") {
1422
- console.log(chalk6.yellow(`Project "${name}" is a static site - no process to stop.`));
1714
+ console.log(chalk7.yellow(`Project "${name}" is a static site - no process to stop.`));
1423
1715
  return;
1424
1716
  }
1425
- console.log(chalk6.blue(`Stopping ${name}...`));
1717
+ console.log(chalk7.blue(`Stopping ${name}...`));
1426
1718
  const result = stopProject(name);
1427
1719
  if (result.success) {
1428
- console.log(chalk6.green(`\u2713 ${name} stopped successfully`));
1720
+ console.log(chalk7.green(`\u2713 ${name} stopped successfully`));
1429
1721
  } else {
1430
- console.error(chalk6.red(`\u2717 Failed to stop ${name}: ${result.error}`));
1722
+ console.error(chalk7.red(`\u2717 Failed to stop ${name}: ${result.error}`));
1431
1723
  process.exit(1);
1432
1724
  }
1433
1725
  }
1434
1726
 
1435
1727
  // src/commands/restart.ts
1436
- import chalk7 from "chalk";
1728
+ import chalk8 from "chalk";
1437
1729
  async function restartCommand(name, options) {
1438
1730
  if (options.all) {
1439
1731
  const projects = listProjects();
1440
1732
  const npmProjects = projects.filter((p) => p.type === "npm");
1441
1733
  if (npmProjects.length === 0) {
1442
- console.log(chalk7.yellow("No npm projects to restart."));
1734
+ console.log(chalk8.yellow("No npm projects to restart."));
1443
1735
  return;
1444
1736
  }
1445
- console.log(chalk7.blue(`Restarting ${npmProjects.length} npm project(s)...`));
1737
+ console.log(chalk8.blue(`Restarting ${npmProjects.length} npm project(s)...`));
1446
1738
  const results = restartAllProjects(npmProjects);
1447
1739
  for (const result2 of results) {
1448
1740
  if (result2.success) {
1449
- console.log(chalk7.green(` \u2713 ${result2.name}`));
1741
+ console.log(chalk8.green(` \u2713 ${result2.name}`));
1450
1742
  } else {
1451
- console.log(chalk7.red(` \u2717 ${result2.name}: ${result2.error}`));
1743
+ console.log(chalk8.red(` \u2717 ${result2.name}: ${result2.error}`));
1452
1744
  }
1453
1745
  }
1454
1746
  const succeeded = results.filter((r) => r.success).length;
1455
- console.log(chalk7.dim(`
1747
+ console.log(chalk8.dim(`
1456
1748
  ${succeeded}/${results.length} restarted successfully`));
1457
1749
  return;
1458
1750
  }
1459
1751
  if (!name) {
1460
- console.error(chalk7.red("Error: Project name is required. Use --all to restart all projects."));
1752
+ console.error(chalk8.red("Error: Project name is required. Use --all to restart all projects."));
1461
1753
  process2.exit(1);
1462
1754
  }
1463
1755
  const project = getProject(name);
1464
1756
  if (!project) {
1465
- console.error(chalk7.red(`Error: Project "${name}" not found`));
1757
+ console.error(chalk8.red(`Error: Project "${name}" not found`));
1466
1758
  process2.exit(1);
1467
1759
  }
1468
1760
  if (project.type !== "npm") {
1469
- console.log(chalk7.yellow(`Project "${name}" is a static site - no process to restart.`));
1761
+ console.log(chalk8.yellow(`Project "${name}" is a static site - no process to restart.`));
1470
1762
  return;
1471
1763
  }
1472
- console.log(chalk7.blue(`Restarting ${name}...`));
1764
+ console.log(chalk8.blue(`Restarting ${name}...`));
1473
1765
  const process2 = getProcessByName(name);
1474
1766
  let result;
1475
1767
  if (process2) {
1476
1768
  result = restartProject(name);
1477
1769
  } else {
1478
- console.log(chalk7.dim("Process not running, starting..."));
1770
+ console.log(chalk8.dim("Process not running, starting..."));
1479
1771
  result = startProject(project);
1480
1772
  }
1481
1773
  if (result.success) {
1482
- console.log(chalk7.green(`\u2713 ${name} restarted successfully`));
1774
+ console.log(chalk8.green(`\u2713 ${name} restarted successfully`));
1483
1775
  } else {
1484
- console.error(chalk7.red(`\u2717 Failed to restart ${name}: ${result.error}`));
1776
+ console.error(chalk8.red(`\u2717 Failed to restart ${name}: ${result.error}`));
1485
1777
  process2.exit(1);
1486
1778
  }
1487
1779
  }
1488
1780
 
1489
1781
  // src/commands/logs.ts
1490
- import chalk8 from "chalk";
1782
+ import chalk9 from "chalk";
1491
1783
  async function logsCommand(name, options) {
1492
1784
  const project = getProject(name);
1493
1785
  if (!project) {
1494
- console.error(chalk8.red(`Error: Project "${name}" not found`));
1786
+ console.error(chalk9.red(`Error: Project "${name}" not found`));
1495
1787
  process2.exit(1);
1496
1788
  }
1497
1789
  if (project.type !== "npm") {
1498
- console.log(chalk8.yellow(`Project "${name}" is a static site - no logs available.`));
1499
- console.log(chalk8.dim("Check nginx access/error logs instead:"));
1500
- console.log(chalk8.dim(" /var/log/nginx/access.log"));
1501
- console.log(chalk8.dim(" /var/log/nginx/error.log"));
1790
+ console.log(chalk9.yellow(`Project "${name}" is a static site - no logs available.`));
1791
+ console.log(chalk9.dim("Check nginx access/error logs instead:"));
1792
+ console.log(chalk9.dim(" /var/log/nginx/access.log"));
1793
+ console.log(chalk9.dim(" /var/log/nginx/error.log"));
1502
1794
  return;
1503
1795
  }
1504
1796
  const process2 = getProcessByName(name);
1505
1797
  if (!process2) {
1506
- console.log(chalk8.yellow(`Project "${name}" has not been started yet.`));
1507
- console.log(chalk8.dim(`Run ${chalk8.cyan(`bindler start ${name}`)} first.`));
1798
+ console.log(chalk9.yellow(`Project "${name}" has not been started yet.`));
1799
+ console.log(chalk9.dim(`Run ${chalk9.cyan(`bindler start ${name}`)} first.`));
1508
1800
  return;
1509
1801
  }
1510
1802
  const lines = options.lines || 200;
1511
1803
  const follow = options.follow || false;
1512
1804
  if (follow) {
1513
- console.log(chalk8.dim(`Following logs for ${name}... (Ctrl+C to exit)`));
1805
+ console.log(chalk9.dim(`Following logs for ${name}... (Ctrl+C to exit)`));
1514
1806
  }
1515
1807
  await showLogs(name, follow, lines);
1516
1808
  }
1517
1809
 
1518
1810
  // src/commands/update.ts
1519
- import chalk9 from "chalk";
1520
- import { existsSync as existsSync7 } from "fs";
1811
+ import chalk10 from "chalk";
1812
+ import { existsSync as existsSync8 } from "fs";
1521
1813
  async function updateCommand(name, options) {
1522
1814
  const project = getProject(name);
1523
1815
  if (!project) {
1524
- console.error(chalk9.red(`Error: Project "${name}" not found`));
1816
+ console.error(chalk10.red(`Error: Project "${name}" not found`));
1525
1817
  process.exit(1);
1526
1818
  }
1527
1819
  const updates = {};
1528
1820
  if (options.hostname) {
1529
1821
  if (!validateHostname(options.hostname)) {
1530
- console.error(chalk9.red("Error: Invalid hostname format"));
1822
+ console.error(chalk10.red("Error: Invalid hostname format"));
1531
1823
  process.exit(1);
1532
1824
  }
1533
1825
  updates.hostname = options.hostname;
@@ -1535,11 +1827,11 @@ async function updateCommand(name, options) {
1535
1827
  if (options.port) {
1536
1828
  const port = parseInt(options.port, 10);
1537
1829
  if (!validatePort(port)) {
1538
- console.error(chalk9.red("Error: Invalid port. Use a number between 1024 and 65535."));
1830
+ console.error(chalk10.red("Error: Invalid port. Use a number between 1024 and 65535."));
1539
1831
  process.exit(1);
1540
1832
  }
1541
1833
  if (!isPortAvailable(port) && port !== project.port) {
1542
- console.error(chalk9.red(`Error: Port ${port} is already in use by another project.`));
1834
+ console.error(chalk10.red(`Error: Port ${port} is already in use by another project.`));
1543
1835
  process.exit(1);
1544
1836
  }
1545
1837
  updates.port = port;
@@ -1549,20 +1841,20 @@ async function updateCommand(name, options) {
1549
1841
  }
1550
1842
  if (options.start) {
1551
1843
  if (project.type !== "npm") {
1552
- console.error(chalk9.red("Error: Start command only applies to npm projects"));
1844
+ console.error(chalk10.red("Error: Start command only applies to npm projects"));
1553
1845
  process.exit(1);
1554
1846
  }
1555
1847
  updates.start = options.start;
1556
1848
  }
1557
1849
  if (options.path) {
1558
- if (!existsSync7(options.path)) {
1559
- console.error(chalk9.red(`Error: Path does not exist: ${options.path}`));
1850
+ if (!existsSync8(options.path)) {
1851
+ console.error(chalk10.red(`Error: Path does not exist: ${options.path}`));
1560
1852
  process.exit(1);
1561
1853
  }
1562
1854
  if (isProtectedPath(options.path)) {
1563
- console.error(chalk9.red(`Error: Path is in a macOS protected folder (Desktop/Documents/Downloads).`));
1564
- console.error(chalk9.yellow(`Nginx cannot access these folders without Full Disk Access.`));
1565
- console.error(chalk9.dim(`Move your project to ~/projects or another accessible location.`));
1855
+ console.error(chalk10.red(`Error: Path is in a macOS protected folder (Desktop/Documents/Downloads).`));
1856
+ console.error(chalk10.yellow(`Nginx cannot access these folders without Full Disk Access.`));
1857
+ console.error(chalk10.dim(`Move your project to ~/projects or another accessible location.`));
1566
1858
  process.exit(1);
1567
1859
  }
1568
1860
  updates.path = options.path;
@@ -1572,7 +1864,7 @@ async function updateCommand(name, options) {
1572
1864
  for (const envStr of options.env) {
1573
1865
  const [key, ...valueParts] = envStr.split("=");
1574
1866
  if (!key) {
1575
- console.error(chalk9.red(`Error: Invalid env format: ${envStr}. Use KEY=value`));
1867
+ console.error(chalk10.red(`Error: Invalid env format: ${envStr}. Use KEY=value`));
1576
1868
  process.exit(1);
1577
1869
  }
1578
1870
  env[key] = valueParts.join("=");
@@ -1585,23 +1877,23 @@ async function updateCommand(name, options) {
1585
1877
  updates.enabled = false;
1586
1878
  }
1587
1879
  if (Object.keys(updates).length === 0) {
1588
- console.log(chalk9.yellow("No updates specified."));
1589
- console.log(chalk9.dim("Available options: --hostname, --port, --start, --path, --env, --enable, --disable"));
1880
+ console.log(chalk10.yellow("No updates specified."));
1881
+ console.log(chalk10.dim("Available options: --hostname, --port, --start, --path, --env, --enable, --disable"));
1590
1882
  return;
1591
1883
  }
1592
1884
  try {
1593
1885
  updateProject(name, updates);
1594
- console.log(chalk9.green(`\u2713 Project "${name}" updated successfully`));
1886
+ console.log(chalk10.green(`\u2713 Project "${name}" updated successfully`));
1595
1887
  for (const [key, value] of Object.entries(updates)) {
1596
- console.log(chalk9.dim(` ${key}: ${typeof value === "object" ? JSON.stringify(value) : value}`));
1888
+ console.log(chalk10.dim(` ${key}: ${typeof value === "object" ? JSON.stringify(value) : value}`));
1597
1889
  }
1598
- console.log(chalk9.dim(`
1599
- Run ${chalk9.cyan("sudo bindler apply")} to apply changes to nginx.`));
1890
+ console.log(chalk10.dim(`
1891
+ Run ${chalk10.cyan("sudo bindler apply")} to apply changes to nginx.`));
1600
1892
  if (project.type === "npm" && (updates.port || updates.start || updates.env)) {
1601
- console.log(chalk9.dim(`Run ${chalk9.cyan(`bindler restart ${name}`)} to apply changes to the running process.`));
1893
+ console.log(chalk10.dim(`Run ${chalk10.cyan(`bindler restart ${name}`)} to apply changes to the running process.`));
1602
1894
  }
1603
1895
  } catch (error) {
1604
- console.error(chalk9.red(`Error: ${error instanceof Error ? error.message : error}`));
1896
+ console.error(chalk10.red(`Error: ${error instanceof Error ? error.message : error}`));
1605
1897
  process.exit(1);
1606
1898
  }
1607
1899
  }
@@ -1610,20 +1902,20 @@ Run ${chalk9.cyan("sudo bindler apply")} to apply changes to nginx.`));
1610
1902
  import { writeFileSync as writeFileSync4, readFileSync as readFileSync4, unlinkSync } from "fs";
1611
1903
  import { tmpdir } from "os";
1612
1904
  import { join as join6 } from "path";
1613
- import chalk10 from "chalk";
1905
+ import chalk11 from "chalk";
1614
1906
  async function editCommand(name) {
1615
1907
  const project = getProject(name);
1616
1908
  if (!project) {
1617
- console.error(chalk10.red(`Error: Project "${name}" not found`));
1909
+ console.error(chalk11.red(`Error: Project "${name}" not found`));
1618
1910
  process.exit(1);
1619
1911
  }
1620
1912
  const editor = process.env.EDITOR || process.env.VISUAL || "vi";
1621
1913
  const tmpFile = join6(tmpdir(), `bindler-${name}-${Date.now()}.json`);
1622
1914
  writeFileSync4(tmpFile, JSON.stringify(project, null, 2) + "\n");
1623
- console.log(chalk10.dim(`Opening ${name} config in ${editor}...`));
1915
+ console.log(chalk11.dim(`Opening ${name} config in ${editor}...`));
1624
1916
  const exitCode = await spawnInteractive(editor, [tmpFile]);
1625
1917
  if (exitCode !== 0) {
1626
- console.error(chalk10.red("Editor exited with error"));
1918
+ console.error(chalk11.red("Editor exited with error"));
1627
1919
  unlinkSync(tmpFile);
1628
1920
  process.exit(1);
1629
1921
  }
@@ -1631,7 +1923,7 @@ async function editCommand(name) {
1631
1923
  try {
1632
1924
  editedContent = readFileSync4(tmpFile, "utf-8");
1633
1925
  } catch (error) {
1634
- console.error(chalk10.red("Failed to read edited file"));
1926
+ console.error(chalk11.red("Failed to read edited file"));
1635
1927
  process.exit(1);
1636
1928
  } finally {
1637
1929
  unlinkSync(tmpFile);
@@ -1640,44 +1932,44 @@ async function editCommand(name) {
1640
1932
  try {
1641
1933
  editedProject = JSON.parse(editedContent);
1642
1934
  } catch (error) {
1643
- console.error(chalk10.red("Error: Invalid JSON in edited file"));
1935
+ console.error(chalk11.red("Error: Invalid JSON in edited file"));
1644
1936
  process.exit(1);
1645
1937
  }
1646
1938
  if (editedProject.name !== project.name) {
1647
- console.error(chalk10.red("Error: Cannot change project name via edit. Use a new project instead."));
1939
+ console.error(chalk11.red("Error: Cannot change project name via edit. Use a new project instead."));
1648
1940
  process.exit(1);
1649
1941
  }
1650
1942
  const originalStr = JSON.stringify(project);
1651
1943
  const editedStr = JSON.stringify(editedProject);
1652
1944
  if (originalStr === editedStr) {
1653
- console.log(chalk10.yellow("No changes made."));
1945
+ console.log(chalk11.yellow("No changes made."));
1654
1946
  return;
1655
1947
  }
1656
1948
  try {
1657
1949
  const config = readConfig();
1658
1950
  const index = config.projects.findIndex((p) => p.name === name);
1659
1951
  if (index === -1) {
1660
- console.error(chalk10.red("Error: Project not found"));
1952
+ console.error(chalk11.red("Error: Project not found"));
1661
1953
  process.exit(1);
1662
1954
  }
1663
1955
  config.projects[index] = editedProject;
1664
1956
  writeConfig(config);
1665
- console.log(chalk10.green(`\u2713 Project "${name}" updated successfully`));
1666
- console.log(chalk10.dim(`
1667
- Run ${chalk10.cyan("sudo bindler apply")} to apply changes to nginx.`));
1957
+ console.log(chalk11.green(`\u2713 Project "${name}" updated successfully`));
1958
+ console.log(chalk11.dim(`
1959
+ Run ${chalk11.cyan("sudo bindler apply")} to apply changes to nginx.`));
1668
1960
  } catch (error) {
1669
- console.error(chalk10.red(`Error: ${error instanceof Error ? error.message : error}`));
1961
+ console.error(chalk11.red(`Error: ${error instanceof Error ? error.message : error}`));
1670
1962
  process.exit(1);
1671
1963
  }
1672
1964
  }
1673
1965
 
1674
1966
  // src/commands/remove.ts
1675
1967
  import inquirer2 from "inquirer";
1676
- import chalk11 from "chalk";
1968
+ import chalk12 from "chalk";
1677
1969
  async function removeCommand(name, options) {
1678
1970
  const project = getProject(name);
1679
1971
  if (!project) {
1680
- console.error(chalk11.red(`Error: Project "${name}" not found`));
1972
+ console.error(chalk12.red(`Error: Project "${name}" not found`));
1681
1973
  process.exit(1);
1682
1974
  }
1683
1975
  if (!options.force) {
@@ -1690,338 +1982,51 @@ async function removeCommand(name, options) {
1690
1982
  }
1691
1983
  ]);
1692
1984
  if (!confirm) {
1693
- console.log(chalk11.yellow("Cancelled."));
1985
+ console.log(chalk12.yellow("Cancelled."));
1694
1986
  return;
1695
1987
  }
1696
1988
  }
1697
1989
  if (project.type === "npm") {
1698
1990
  const process2 = getProcessByName(name);
1699
1991
  if (process2) {
1700
- console.log(chalk11.dim("Stopping PM2 process..."));
1992
+ console.log(chalk12.dim("Stopping PM2 process..."));
1701
1993
  deleteProject(name);
1702
1994
  }
1703
1995
  }
1704
1996
  try {
1705
1997
  removeProject(name);
1706
- console.log(chalk11.green(`\u2713 Project "${name}" removed from registry`));
1998
+ console.log(chalk12.green(`\u2713 Project "${name}" removed from registry`));
1707
1999
  if (options.apply) {
1708
- console.log(chalk11.dim("\nApplying nginx configuration..."));
2000
+ console.log(chalk12.dim("\nApplying nginx configuration..."));
1709
2001
  const config = readConfig();
1710
2002
  try {
1711
2003
  writeNginxConfig(config);
1712
2004
  const testResult = testNginxConfig();
1713
2005
  if (testResult.success) {
1714
2006
  reloadNginx();
1715
- console.log(chalk11.green("\u2713 Nginx configuration updated"));
2007
+ console.log(chalk12.green("\u2713 Nginx configuration updated"));
1716
2008
  } else {
1717
- console.log(chalk11.yellow("! Nginx config test failed, reload skipped"));
2009
+ console.log(chalk12.yellow("! Nginx config test failed, reload skipped"));
1718
2010
  }
1719
2011
  } catch (err) {
1720
- console.log(chalk11.yellow(`! Failed to update nginx: ${err}`));
1721
- console.log(chalk11.dim(" Try running: sudo bindler apply"));
2012
+ console.log(chalk12.yellow(`! Failed to update nginx: ${err}`));
2013
+ console.log(chalk12.dim(" Try running: sudo bindler apply"));
1722
2014
  }
1723
- } else {
1724
- console.log(chalk11.dim(`
1725
- Run ${chalk11.cyan("sudo bindler apply")} to update nginx configuration.`));
1726
- }
1727
- console.log(chalk11.yellow("\nNote: Project files were not deleted."));
1728
- console.log(chalk11.dim(` Path: ${project.path}`));
1729
- if (!project.local) {
1730
- console.log(chalk11.yellow("\nCloudflare DNS route was not removed."));
1731
- console.log(chalk11.dim(" Remove it manually from the Cloudflare dashboard:"));
1732
- console.log(chalk11.dim(" https://dash.cloudflare.com \u2192 DNS \u2192 Records \u2192 Delete the CNAME for " + project.hostname));
1733
- }
1734
- } catch (error) {
1735
- console.error(chalk11.red(`Error: ${error instanceof Error ? error.message : error}`));
1736
- process.exit(1);
1737
- }
1738
- }
1739
-
1740
- // src/commands/apply.ts
1741
- import chalk12 from "chalk";
1742
- import { existsSync as existsSync8 } from "fs";
1743
-
1744
- // src/lib/cloudflare.ts
1745
- function isCloudflaredInstalled() {
1746
- const result = execCommandSafe("which cloudflared");
1747
- return result.success;
1748
- }
1749
- function getCloudflaredVersion() {
1750
- const result = execCommandSafe("cloudflared --version");
1751
- if (result.success) {
1752
- const match = result.output.match(/cloudflared version (\S+)/);
1753
- return match ? match[1] : result.output;
1754
- }
1755
- return null;
1756
- }
1757
- function listTunnels() {
1758
- const result = execCommandSafe("cloudflared tunnel list --output json");
1759
- if (!result.success) {
1760
- return [];
1761
- }
1762
- try {
1763
- const tunnels = JSON.parse(result.output);
1764
- return tunnels.map((t) => ({
1765
- id: t.id,
1766
- name: t.name,
1767
- createdAt: t.created_at
1768
- }));
1769
- } catch {
1770
- return [];
1771
- }
1772
- }
1773
- function getTunnelByName(name) {
1774
- const tunnels = listTunnels();
1775
- const tunnel = tunnels.find((t) => t.name === name);
1776
- return tunnel ? { id: tunnel.id, name: tunnel.name } : null;
1777
- }
1778
- function routeDns(tunnelName, hostname) {
1779
- const result = execCommandSafe(`cloudflared tunnel route dns "${tunnelName}" "${hostname}"`);
1780
- if (!result.success) {
1781
- if (result.error?.includes("already exists") || result.output?.includes("already exists")) {
1782
- return { success: true, output: "DNS route already exists" };
1783
- }
1784
- return { success: false, error: result.error };
1785
- }
1786
- return { success: true, output: result.output };
1787
- }
1788
- function routeDnsForAllProjects() {
1789
- const config = readConfig();
1790
- const { tunnelName, applyCloudflareDnsRoutes } = config.defaults;
1791
- if (!applyCloudflareDnsRoutes) {
1792
- return [];
1793
- }
1794
- const results = [];
1795
- for (const project of config.projects) {
1796
- if (project.enabled === false) {
1797
- continue;
1798
- }
1799
- if (project.local) {
1800
- results.push({
1801
- hostname: project.hostname,
1802
- success: true,
1803
- skipped: true,
1804
- output: "Local project - skipped"
1805
- });
1806
- continue;
1807
- }
1808
- const result = routeDns(tunnelName, project.hostname);
1809
- results.push({
1810
- hostname: project.hostname,
1811
- ...result
1812
- });
1813
- }
1814
- return results;
1815
- }
1816
- function isTunnelRunning(tunnelName) {
1817
- const result = execCommandSafe(`pgrep -f "cloudflared.*tunnel.*run.*${tunnelName}"`);
1818
- return result.success;
1819
- }
1820
- function getTunnelInfo(tunnelName) {
1821
- const tunnel = getTunnelByName(tunnelName);
1822
- if (!tunnel) {
1823
- return { exists: false, running: false };
1824
- }
1825
- return {
1826
- exists: true,
1827
- running: isTunnelRunning(tunnelName),
1828
- id: tunnel.id
1829
- };
1830
- }
1831
-
1832
- // src/commands/apply.ts
1833
- async function applyCommand(options) {
1834
- let config = readConfig();
1835
- const defaults = getDefaults();
1836
- if (options.sync) {
1837
- console.log(chalk12.dim("Syncing bindler.yaml from project directories...\n"));
1838
- let synced = 0;
1839
- for (const project of config.projects) {
1840
- if (!existsSync8(project.path)) continue;
1841
- const yamlConfig = readBindlerYaml(project.path);
1842
- if (yamlConfig) {
1843
- const merged = mergeYamlWithProject(project, yamlConfig);
1844
- updateProject(project.name, merged);
1845
- console.log(chalk12.green(` \u2713 Synced ${project.name} from bindler.yaml`));
1846
- synced++;
1847
- }
1848
- }
1849
- if (synced === 0) {
1850
- console.log(chalk12.dim(" No bindler.yaml files found in project directories"));
1851
2015
  } else {
1852
2016
  console.log(chalk12.dim(`
1853
- Synced ${synced} project(s)
1854
- `));
2017
+ Run ${chalk12.cyan("sudo bindler apply")} to update nginx configuration.`));
1855
2018
  }
1856
- config = readConfig();
1857
- }
1858
- if (options.env) {
1859
- console.log(chalk12.dim(`Using ${options.env} environment configuration...
1860
- `));
1861
- const envProjects = listProjectsForEnv(options.env);
1862
- config = { ...config, projects: envProjects };
1863
- }
1864
- const hasProjects = config.projects.length > 0;
1865
- console.log(chalk12.blue("Applying configuration...\n"));
1866
- if (hasProjects && !options.skipChecks) {
1867
- console.log(chalk12.dim("Running preflight checks..."));
1868
- const checkResult = runPreflightChecks(config);
1869
- if (!checkResult.valid) {
1870
- printValidationResult(checkResult);
1871
- console.log(chalk12.red("\n\u2717 Preflight checks failed. Fix the errors above before applying."));
1872
- console.log(chalk12.dim(" Use --skip-checks to bypass (not recommended)"));
1873
- process.exit(1);
1874
- }
1875
- if (checkResult.warnings.length > 0) {
1876
- printValidationResult(checkResult);
1877
- console.log("");
1878
- } else {
1879
- console.log(chalk12.green(" \u2713 Preflight checks passed"));
2019
+ console.log(chalk12.yellow("\nNote: Project files were not deleted."));
2020
+ console.log(chalk12.dim(` Path: ${project.path}`));
2021
+ if (!project.local) {
2022
+ console.log(chalk12.yellow("\nCloudflare DNS route was not removed."));
2023
+ console.log(chalk12.dim(" Remove it manually from the Cloudflare dashboard:"));
2024
+ console.log(chalk12.dim(" https://dash.cloudflare.com \u2192 DNS \u2192 Records \u2192 Delete the CNAME for " + project.hostname));
1880
2025
  }
1881
- }
1882
- console.log(chalk12.dim("Generating nginx configuration..."));
1883
- if (options.dryRun) {
1884
- const nginxConfig = generateNginxConfig(config);
1885
- console.log(chalk12.cyan("\n--- Generated nginx config (dry-run) ---\n"));
1886
- console.log(nginxConfig);
1887
- console.log(chalk12.cyan("--- End of config ---\n"));
1888
- console.log(chalk12.yellow("Dry run mode - no changes were made."));
1889
- return;
1890
- }
1891
- try {
1892
- const { path, content } = writeNginxConfig(config);
1893
- console.log(chalk12.green(` \u2713 Wrote nginx config to ${path}`));
1894
2026
  } catch (error) {
1895
- const errMsg = error instanceof Error ? error.message : String(error);
1896
- console.error(chalk12.red(` \u2717 Failed to write nginx config: ${errMsg}`));
1897
- if (errMsg.includes("EACCES") || errMsg.includes("permission denied")) {
1898
- console.log(chalk12.yellow(`
1899
- Try running with sudo: ${chalk12.cyan("sudo bindler apply")}`));
1900
- }
2027
+ console.error(chalk12.red(`Error: ${error instanceof Error ? error.message : error}`));
1901
2028
  process.exit(1);
1902
2029
  }
1903
- const authProjects = config.projects.filter(
1904
- (p) => p.security?.basicAuth?.enabled && p.security.basicAuth.users?.length
1905
- );
1906
- if (authProjects.length > 0) {
1907
- console.log(chalk12.dim("Generating htpasswd files..."));
1908
- try {
1909
- generateHtpasswdFiles(config.projects);
1910
- console.log(chalk12.green(` \u2713 Generated htpasswd files for ${authProjects.length} project(s)`));
1911
- } catch (error) {
1912
- console.log(chalk12.yellow(` ! Failed to generate htpasswd files: ${error}`));
1913
- }
1914
- }
1915
- console.log(chalk12.dim("Testing nginx configuration..."));
1916
- const testResult = testNginxConfig();
1917
- if (!testResult.success) {
1918
- console.error(chalk12.red(" \u2717 Nginx configuration test failed:"));
1919
- console.error(chalk12.red(testResult.output));
1920
- console.log(chalk12.yellow("\nConfiguration was written but nginx was NOT reloaded."));
1921
- console.log(chalk12.dim("Fix the configuration and run `sudo bindler apply` again."));
1922
- process.exit(1);
1923
- }
1924
- console.log(chalk12.green(" \u2713 Nginx configuration test passed"));
1925
- if (!options.noReload) {
1926
- console.log(chalk12.dim("Reloading nginx..."));
1927
- const reloadResult = reloadNginx();
1928
- if (!reloadResult.success) {
1929
- console.error(chalk12.red(` \u2717 Failed to reload nginx: ${reloadResult.error}`));
1930
- console.log(chalk12.dim("You may need to reload nginx manually: sudo systemctl reload nginx"));
1931
- process.exit(1);
1932
- }
1933
- console.log(chalk12.green(" \u2713 Nginx reloaded successfully"));
1934
- const listenPort = parseInt(defaults.nginxListen.split(":").pop() || "80", 10);
1935
- const portCheck = execCommandSafe(`lsof -i :${listenPort} -P -n 2>/dev/null | grep LISTEN | grep nginx`);
1936
- if (!portCheck.success || !portCheck.output) {
1937
- const isPrivilegedPort = listenPort < 1024;
1938
- console.log(chalk12.yellow(`
1939
- \u26A0 Nginx is not listening on port ${listenPort}`));
1940
- if (isPrivilegedPort) {
1941
- console.log(chalk12.dim(` Port ${listenPort} requires root privileges.
1942
- `));
1943
- console.log(chalk12.cyan(" Solutions:\n"));
1944
- console.log(chalk12.white(" Option 1: Restart nginx with sudo (recommended for port 80)"));
1945
- console.log(chalk12.dim(" sudo pkill nginx && sudo nginx\n"));
1946
- console.log(chalk12.white(" Option 2: Use a non-privileged port (no sudo needed)"));
1947
- console.log(chalk12.dim(" bindler config set nginxListen 8080"));
1948
- console.log(chalk12.dim(" bindler apply"));
1949
- console.log(chalk12.dim(" # Then access via http://hostname:8080\n"));
1950
- } else {
1951
- console.log(chalk12.dim(" Try restarting nginx: brew services restart nginx"));
1952
- }
1953
- }
1954
- } else {
1955
- console.log(chalk12.yellow(" - Skipped nginx reload (--no-reload)"));
1956
- }
1957
- const isDirectMode = defaults.mode === "direct";
1958
- if (!hasProjects) {
1959
- } else if (isDirectMode) {
1960
- console.log(chalk12.dim("\n - Direct mode: skipping Cloudflare DNS routes"));
1961
- } else if (!options.noCloudflare && defaults.applyCloudflareDnsRoutes) {
1962
- console.log(chalk12.dim("\nConfiguring Cloudflare DNS routes..."));
1963
- if (!isCloudflaredInstalled()) {
1964
- console.log(chalk12.yellow(" - cloudflared not installed, skipping DNS routes"));
1965
- } else {
1966
- const dnsResults = routeDnsForAllProjects();
1967
- if (dnsResults.length === 0) {
1968
- console.log(chalk12.dim(" No hostnames to route"));
1969
- } else {
1970
- for (const result of dnsResults) {
1971
- if (result.skipped) {
1972
- console.log(chalk12.dim(` - ${result.hostname} (local - skipped)`));
1973
- } else if (result.success) {
1974
- const msg = result.output?.includes("already exists") ? "exists" : "routed";
1975
- console.log(chalk12.green(` \u2713 ${result.hostname} (${msg})`));
1976
- } else {
1977
- console.log(chalk12.red(` \u2717 ${result.hostname}: ${result.error}`));
1978
- }
1979
- }
1980
- }
1981
- }
1982
- } else if (options.noCloudflare) {
1983
- console.log(chalk12.dim("\n - Skipped Cloudflare DNS routes (--no-cloudflare)"));
1984
- }
1985
- if (hasProjects && isDirectMode && defaults.sslEnabled && options.ssl !== false) {
1986
- console.log(chalk12.dim("\nSetting up SSL certificates..."));
1987
- const hostnames = config.projects.filter((p) => p.enabled !== false && !p.local).map((p) => p.hostname);
1988
- if (hostnames.length === 0) {
1989
- console.log(chalk12.dim(" No hostnames to secure"));
1990
- } else {
1991
- const certbotResult = execCommandSafe("which certbot");
1992
- if (!certbotResult.success) {
1993
- console.log(chalk12.yellow(" - certbot not installed, skipping SSL"));
1994
- console.log(chalk12.dim(" Run: bindler setup --direct"));
1995
- } else {
1996
- for (const hostname of hostnames) {
1997
- console.log(chalk12.dim(` Requesting certificate for ${hostname}...`));
1998
- const email = defaults.sslEmail || "admin@" + hostname.split(".").slice(-2).join(".");
1999
- const result = execCommandSafe(
2000
- `sudo certbot --nginx -d ${hostname} --non-interactive --agree-tos --email ${email} 2>&1`
2001
- );
2002
- if (result.success || result.output?.includes("Certificate not yet due for renewal")) {
2003
- console.log(chalk12.green(` \u2713 ${hostname} (secured)`));
2004
- } else if (result.output?.includes("already exists")) {
2005
- console.log(chalk12.green(` \u2713 ${hostname} (exists)`));
2006
- } else {
2007
- console.log(chalk12.yellow(` ! ${hostname}: ${result.error || "failed"}`));
2008
- console.log(chalk12.dim(" Run manually: sudo certbot --nginx -d " + hostname));
2009
- }
2010
- }
2011
- }
2012
- }
2013
- }
2014
- console.log(chalk12.green("\n\u2713 Configuration applied successfully!"));
2015
- if (hasProjects) {
2016
- console.log(chalk12.dim(`
2017
- ${config.projects.length} project(s) configured:`));
2018
- for (const project of config.projects) {
2019
- const status = project.enabled !== false ? chalk12.green("enabled") : chalk12.yellow("disabled");
2020
- console.log(chalk12.dim(` - ${project.name} \u2192 ${project.hostname} (${status})`));
2021
- }
2022
- } else {
2023
- console.log(chalk12.dim("\nNo projects configured. Nginx config cleared."));
2024
- }
2025
2030
  }
2026
2031
 
2027
2032
  // src/commands/doctor.ts
@@ -2333,7 +2338,7 @@ async function infoCommand() {
2333
2338
  `));
2334
2339
  console.log(chalk15.white(" Manage multiple projects behind Cloudflare Tunnel"));
2335
2340
  console.log(chalk15.white(" with Nginx and PM2\n"));
2336
- console.log(chalk15.dim(" Version: ") + chalk15.white("1.6.0"));
2341
+ console.log(chalk15.dim(" Version: ") + chalk15.white("1.6.1"));
2337
2342
  console.log(chalk15.dim(" Author: ") + chalk15.white("alfaoz"));
2338
2343
  console.log(chalk15.dim(" License: ") + chalk15.white("MIT"));
2339
2344
  console.log(chalk15.dim(" GitHub: ") + chalk15.cyan("https://github.com/alfaoz/bindler"));
@@ -4075,28 +4080,28 @@ async function checkForUpdates() {
4075
4080
  const cache = readCache();
4076
4081
  const now = Date.now();
4077
4082
  if (now - cache.lastCheck < CHECK_INTERVAL) {
4078
- if (cache.latestVersion && compareVersions("1.6.0", cache.latestVersion) < 0) {
4083
+ if (cache.latestVersion && compareVersions("1.6.1", cache.latestVersion) > 0) {
4079
4084
  showUpdateMessage(cache.latestVersion);
4080
4085
  }
4081
4086
  return;
4082
4087
  }
4083
4088
  fetchLatestVersion().then((latestVersion) => {
4084
4089
  writeCache({ lastCheck: now, latestVersion });
4085
- if (latestVersion && compareVersions("1.6.0", latestVersion) < 0) {
4090
+ if (latestVersion && compareVersions("1.6.1", latestVersion) > 0) {
4086
4091
  showUpdateMessage(latestVersion);
4087
4092
  }
4088
4093
  });
4089
4094
  }
4090
4095
  function showUpdateMessage(latestVersion) {
4091
4096
  console.log("");
4092
- console.log(chalk30.yellow(` Update available: ${"1.6.0"} \u2192 ${latestVersion}`));
4097
+ console.log(chalk30.yellow(` Update available: ${"1.6.1"} \u2192 ${latestVersion}`));
4093
4098
  console.log(chalk30.dim(` Run: npm update -g bindler`));
4094
4099
  console.log("");
4095
4100
  }
4096
4101
 
4097
4102
  // src/cli.ts
4098
4103
  var program = new Command();
4099
- program.name("bindler").description("Manage multiple projects behind Cloudflare Tunnel with Nginx and PM2").version("1.6.0");
4104
+ program.name("bindler").description("Manage multiple projects behind Cloudflare Tunnel with Nginx and PM2").version("1.6.1");
4100
4105
  program.hook("preAction", async () => {
4101
4106
  try {
4102
4107
  initConfig();