bindler 1.6.0 → 1.6.2

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,229 @@ async function statusCommand() {
1336
1628
  }
1337
1629
 
1338
1630
  // src/commands/start.ts
1339
- import chalk5 from "chalk";
1631
+ import chalk6 from "chalk";
1632
+ var CRASH_CHECK_DELAY = 2500;
1633
+ function sleep(ms) {
1634
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
1635
+ }
1340
1636
  async function startCommand(name, options) {
1341
1637
  if (options.all) {
1342
1638
  const projects = listProjects();
1343
1639
  const npmProjects = projects.filter((p) => p.type === "npm");
1344
1640
  if (npmProjects.length === 0) {
1345
- console.log(chalk5.yellow("No npm projects to start."));
1641
+ console.log(chalk6.yellow("No npm projects to start."));
1346
1642
  return;
1347
1643
  }
1348
- console.log(chalk5.blue(`Starting ${npmProjects.length} npm project(s)...`));
1644
+ console.log(chalk6.blue(`Starting ${npmProjects.length} npm project(s)...`));
1349
1645
  const results = startAllProjects(npmProjects);
1350
1646
  for (const result2 of results) {
1351
1647
  if (result2.success) {
1352
- console.log(chalk5.green(` \u2713 ${result2.name}`));
1648
+ console.log(chalk6.green(` \u2713 ${result2.name}`));
1353
1649
  } else {
1354
- console.log(chalk5.red(` \u2717 ${result2.name}: ${result2.error}`));
1650
+ console.log(chalk6.red(` \u2717 ${result2.name}: ${result2.error}`));
1355
1651
  }
1356
1652
  }
1357
- const succeeded = results.filter((r) => r.success).length;
1358
- console.log(chalk5.dim(`
1359
- ${succeeded}/${results.length} started successfully`));
1653
+ console.log(chalk6.dim("\nChecking for crashes..."));
1654
+ await sleep(CRASH_CHECK_DELAY);
1655
+ const crashed = [];
1656
+ for (const project2 of npmProjects) {
1657
+ const process2 = getProcessByName(project2.name);
1658
+ if (process2 && process2.status !== "online") {
1659
+ crashed.push(project2.name);
1660
+ }
1661
+ }
1662
+ if (crashed.length > 0) {
1663
+ console.log(chalk6.red(`
1664
+ \u2717 ${crashed.length} project(s) crashed: ${crashed.join(", ")}`));
1665
+ console.log(chalk6.dim(` Run: bindler logs <name> to see errors`));
1666
+ } else {
1667
+ const succeeded = results.filter((r) => r.success).length;
1668
+ console.log(chalk6.green(`\u2713 ${succeeded}/${results.length} running`));
1669
+ }
1360
1670
  return;
1361
1671
  }
1362
1672
  if (!name) {
1363
- console.error(chalk5.red("Error: Project name is required. Use --all to start all projects."));
1673
+ console.error(chalk6.red("Error: Project name is required. Use --all to start all projects."));
1364
1674
  process.exit(1);
1365
1675
  }
1366
1676
  const project = getProject(name);
1367
1677
  if (!project) {
1368
- console.error(chalk5.red(`Error: Project "${name}" not found`));
1678
+ console.error(chalk6.red(`Error: Project "${name}" not found`));
1369
1679
  process.exit(1);
1370
1680
  }
1371
1681
  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."));
1682
+ console.log(chalk6.blue(`Project "${name}" is a static site - no process to start.`));
1683
+ console.log(chalk6.dim("Static sites are served directly by nginx."));
1374
1684
  return;
1375
1685
  }
1376
- console.log(chalk5.blue(`Starting ${name}...`));
1686
+ console.log(chalk6.blue(`Starting ${name}...`));
1377
1687
  const result = startProject(project);
1378
- 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}`));
1382
- } else {
1383
- console.error(chalk5.red(`\u2717 Failed to start ${name}: ${result.error}`));
1688
+ if (!result.success) {
1689
+ console.error(chalk6.red(`\u2717 Failed to start ${name}: ${result.error}`));
1690
+ process.exit(1);
1691
+ }
1692
+ console.log(chalk6.dim("Waiting for process to stabilize..."));
1693
+ await sleep(CRASH_CHECK_DELAY);
1694
+ const processStatus = getProcessByName(name);
1695
+ if (!processStatus || processStatus.status !== "online") {
1696
+ console.error(chalk6.red(`
1697
+ \u2717 ${name} crashed immediately after starting`));
1698
+ console.log(chalk6.dim("\nShowing recent logs:\n"));
1699
+ console.log(chalk6.dim("\u2500".repeat(50)));
1700
+ await showLogs(name, false, 30);
1701
+ console.log(chalk6.dim("\u2500".repeat(50)));
1702
+ console.log(chalk6.yellow(`
1703
+ Fix the error and try again: bindler start ${name}`));
1384
1704
  process.exit(1);
1385
1705
  }
1706
+ console.log(chalk6.green(`\u2713 ${name} started successfully`));
1707
+ console.log(chalk6.dim(` Port: ${project.port}`));
1708
+ console.log(chalk6.dim(` URL: https://${project.hostname}`));
1386
1709
  }
1387
1710
 
1388
1711
  // src/commands/stop.ts
1389
- import chalk6 from "chalk";
1712
+ import chalk7 from "chalk";
1390
1713
  async function stopCommand(name, options) {
1391
1714
  if (options.all) {
1392
1715
  const projects = listProjects();
1393
1716
  const npmProjects = projects.filter((p) => p.type === "npm");
1394
1717
  if (npmProjects.length === 0) {
1395
- console.log(chalk6.yellow("No npm projects to stop."));
1718
+ console.log(chalk7.yellow("No npm projects to stop."));
1396
1719
  return;
1397
1720
  }
1398
- console.log(chalk6.blue(`Stopping ${npmProjects.length} npm project(s)...`));
1721
+ console.log(chalk7.blue(`Stopping ${npmProjects.length} npm project(s)...`));
1399
1722
  const results = stopAllProjects(npmProjects);
1400
1723
  for (const result2 of results) {
1401
1724
  if (result2.success) {
1402
- console.log(chalk6.green(` \u2713 ${result2.name}`));
1725
+ console.log(chalk7.green(` \u2713 ${result2.name}`));
1403
1726
  } else {
1404
- console.log(chalk6.red(` \u2717 ${result2.name}: ${result2.error}`));
1727
+ console.log(chalk7.red(` \u2717 ${result2.name}: ${result2.error}`));
1405
1728
  }
1406
1729
  }
1407
1730
  const succeeded = results.filter((r) => r.success).length;
1408
- console.log(chalk6.dim(`
1731
+ console.log(chalk7.dim(`
1409
1732
  ${succeeded}/${results.length} stopped successfully`));
1410
1733
  return;
1411
1734
  }
1412
1735
  if (!name) {
1413
- console.error(chalk6.red("Error: Project name is required. Use --all to stop all projects."));
1736
+ console.error(chalk7.red("Error: Project name is required. Use --all to stop all projects."));
1414
1737
  process.exit(1);
1415
1738
  }
1416
1739
  const project = getProject(name);
1417
1740
  if (!project) {
1418
- console.error(chalk6.red(`Error: Project "${name}" not found`));
1741
+ console.error(chalk7.red(`Error: Project "${name}" not found`));
1419
1742
  process.exit(1);
1420
1743
  }
1421
1744
  if (project.type !== "npm") {
1422
- console.log(chalk6.yellow(`Project "${name}" is a static site - no process to stop.`));
1745
+ console.log(chalk7.yellow(`Project "${name}" is a static site - no process to stop.`));
1423
1746
  return;
1424
1747
  }
1425
- console.log(chalk6.blue(`Stopping ${name}...`));
1748
+ console.log(chalk7.blue(`Stopping ${name}...`));
1426
1749
  const result = stopProject(name);
1427
1750
  if (result.success) {
1428
- console.log(chalk6.green(`\u2713 ${name} stopped successfully`));
1751
+ console.log(chalk7.green(`\u2713 ${name} stopped successfully`));
1429
1752
  } else {
1430
- console.error(chalk6.red(`\u2717 Failed to stop ${name}: ${result.error}`));
1753
+ console.error(chalk7.red(`\u2717 Failed to stop ${name}: ${result.error}`));
1431
1754
  process.exit(1);
1432
1755
  }
1433
1756
  }
1434
1757
 
1435
1758
  // src/commands/restart.ts
1436
- import chalk7 from "chalk";
1759
+ import chalk8 from "chalk";
1437
1760
  async function restartCommand(name, options) {
1438
1761
  if (options.all) {
1439
1762
  const projects = listProjects();
1440
1763
  const npmProjects = projects.filter((p) => p.type === "npm");
1441
1764
  if (npmProjects.length === 0) {
1442
- console.log(chalk7.yellow("No npm projects to restart."));
1765
+ console.log(chalk8.yellow("No npm projects to restart."));
1443
1766
  return;
1444
1767
  }
1445
- console.log(chalk7.blue(`Restarting ${npmProjects.length} npm project(s)...`));
1768
+ console.log(chalk8.blue(`Restarting ${npmProjects.length} npm project(s)...`));
1446
1769
  const results = restartAllProjects(npmProjects);
1447
1770
  for (const result2 of results) {
1448
1771
  if (result2.success) {
1449
- console.log(chalk7.green(` \u2713 ${result2.name}`));
1772
+ console.log(chalk8.green(` \u2713 ${result2.name}`));
1450
1773
  } else {
1451
- console.log(chalk7.red(` \u2717 ${result2.name}: ${result2.error}`));
1774
+ console.log(chalk8.red(` \u2717 ${result2.name}: ${result2.error}`));
1452
1775
  }
1453
1776
  }
1454
1777
  const succeeded = results.filter((r) => r.success).length;
1455
- console.log(chalk7.dim(`
1778
+ console.log(chalk8.dim(`
1456
1779
  ${succeeded}/${results.length} restarted successfully`));
1457
1780
  return;
1458
1781
  }
1459
1782
  if (!name) {
1460
- console.error(chalk7.red("Error: Project name is required. Use --all to restart all projects."));
1783
+ console.error(chalk8.red("Error: Project name is required. Use --all to restart all projects."));
1461
1784
  process2.exit(1);
1462
1785
  }
1463
1786
  const project = getProject(name);
1464
1787
  if (!project) {
1465
- console.error(chalk7.red(`Error: Project "${name}" not found`));
1788
+ console.error(chalk8.red(`Error: Project "${name}" not found`));
1466
1789
  process2.exit(1);
1467
1790
  }
1468
1791
  if (project.type !== "npm") {
1469
- console.log(chalk7.yellow(`Project "${name}" is a static site - no process to restart.`));
1792
+ console.log(chalk8.yellow(`Project "${name}" is a static site - no process to restart.`));
1470
1793
  return;
1471
1794
  }
1472
- console.log(chalk7.blue(`Restarting ${name}...`));
1795
+ console.log(chalk8.blue(`Restarting ${name}...`));
1473
1796
  const process2 = getProcessByName(name);
1474
1797
  let result;
1475
1798
  if (process2) {
1476
1799
  result = restartProject(name);
1477
1800
  } else {
1478
- console.log(chalk7.dim("Process not running, starting..."));
1801
+ console.log(chalk8.dim("Process not running, starting..."));
1479
1802
  result = startProject(project);
1480
1803
  }
1481
1804
  if (result.success) {
1482
- console.log(chalk7.green(`\u2713 ${name} restarted successfully`));
1805
+ console.log(chalk8.green(`\u2713 ${name} restarted successfully`));
1483
1806
  } else {
1484
- console.error(chalk7.red(`\u2717 Failed to restart ${name}: ${result.error}`));
1807
+ console.error(chalk8.red(`\u2717 Failed to restart ${name}: ${result.error}`));
1485
1808
  process2.exit(1);
1486
1809
  }
1487
1810
  }
1488
1811
 
1489
1812
  // src/commands/logs.ts
1490
- import chalk8 from "chalk";
1813
+ import chalk9 from "chalk";
1491
1814
  async function logsCommand(name, options) {
1492
1815
  const project = getProject(name);
1493
1816
  if (!project) {
1494
- console.error(chalk8.red(`Error: Project "${name}" not found`));
1817
+ console.error(chalk9.red(`Error: Project "${name}" not found`));
1495
1818
  process2.exit(1);
1496
1819
  }
1497
1820
  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"));
1821
+ console.log(chalk9.yellow(`Project "${name}" is a static site - no logs available.`));
1822
+ console.log(chalk9.dim("Check nginx access/error logs instead:"));
1823
+ console.log(chalk9.dim(" /var/log/nginx/access.log"));
1824
+ console.log(chalk9.dim(" /var/log/nginx/error.log"));
1502
1825
  return;
1503
1826
  }
1504
1827
  const process2 = getProcessByName(name);
1505
1828
  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.`));
1829
+ console.log(chalk9.yellow(`Project "${name}" has not been started yet.`));
1830
+ console.log(chalk9.dim(`Run ${chalk9.cyan(`bindler start ${name}`)} first.`));
1508
1831
  return;
1509
1832
  }
1510
1833
  const lines = options.lines || 200;
1511
1834
  const follow = options.follow || false;
1512
1835
  if (follow) {
1513
- console.log(chalk8.dim(`Following logs for ${name}... (Ctrl+C to exit)`));
1836
+ console.log(chalk9.dim(`Following logs for ${name}... (Ctrl+C to exit)`));
1514
1837
  }
1515
1838
  await showLogs(name, follow, lines);
1516
1839
  }
1517
1840
 
1518
1841
  // src/commands/update.ts
1519
- import chalk9 from "chalk";
1520
- import { existsSync as existsSync7 } from "fs";
1842
+ import chalk10 from "chalk";
1843
+ import { existsSync as existsSync8 } from "fs";
1521
1844
  async function updateCommand(name, options) {
1522
1845
  const project = getProject(name);
1523
1846
  if (!project) {
1524
- console.error(chalk9.red(`Error: Project "${name}" not found`));
1847
+ console.error(chalk10.red(`Error: Project "${name}" not found`));
1525
1848
  process.exit(1);
1526
1849
  }
1527
1850
  const updates = {};
1528
1851
  if (options.hostname) {
1529
1852
  if (!validateHostname(options.hostname)) {
1530
- console.error(chalk9.red("Error: Invalid hostname format"));
1853
+ console.error(chalk10.red("Error: Invalid hostname format"));
1531
1854
  process.exit(1);
1532
1855
  }
1533
1856
  updates.hostname = options.hostname;
@@ -1535,11 +1858,11 @@ async function updateCommand(name, options) {
1535
1858
  if (options.port) {
1536
1859
  const port = parseInt(options.port, 10);
1537
1860
  if (!validatePort(port)) {
1538
- console.error(chalk9.red("Error: Invalid port. Use a number between 1024 and 65535."));
1861
+ console.error(chalk10.red("Error: Invalid port. Use a number between 1024 and 65535."));
1539
1862
  process.exit(1);
1540
1863
  }
1541
1864
  if (!isPortAvailable(port) && port !== project.port) {
1542
- console.error(chalk9.red(`Error: Port ${port} is already in use by another project.`));
1865
+ console.error(chalk10.red(`Error: Port ${port} is already in use by another project.`));
1543
1866
  process.exit(1);
1544
1867
  }
1545
1868
  updates.port = port;
@@ -1549,20 +1872,20 @@ async function updateCommand(name, options) {
1549
1872
  }
1550
1873
  if (options.start) {
1551
1874
  if (project.type !== "npm") {
1552
- console.error(chalk9.red("Error: Start command only applies to npm projects"));
1875
+ console.error(chalk10.red("Error: Start command only applies to npm projects"));
1553
1876
  process.exit(1);
1554
1877
  }
1555
1878
  updates.start = options.start;
1556
1879
  }
1557
1880
  if (options.path) {
1558
- if (!existsSync7(options.path)) {
1559
- console.error(chalk9.red(`Error: Path does not exist: ${options.path}`));
1881
+ if (!existsSync8(options.path)) {
1882
+ console.error(chalk10.red(`Error: Path does not exist: ${options.path}`));
1560
1883
  process.exit(1);
1561
1884
  }
1562
1885
  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.`));
1886
+ console.error(chalk10.red(`Error: Path is in a macOS protected folder (Desktop/Documents/Downloads).`));
1887
+ console.error(chalk10.yellow(`Nginx cannot access these folders without Full Disk Access.`));
1888
+ console.error(chalk10.dim(`Move your project to ~/projects or another accessible location.`));
1566
1889
  process.exit(1);
1567
1890
  }
1568
1891
  updates.path = options.path;
@@ -1572,7 +1895,7 @@ async function updateCommand(name, options) {
1572
1895
  for (const envStr of options.env) {
1573
1896
  const [key, ...valueParts] = envStr.split("=");
1574
1897
  if (!key) {
1575
- console.error(chalk9.red(`Error: Invalid env format: ${envStr}. Use KEY=value`));
1898
+ console.error(chalk10.red(`Error: Invalid env format: ${envStr}. Use KEY=value`));
1576
1899
  process.exit(1);
1577
1900
  }
1578
1901
  env[key] = valueParts.join("=");
@@ -1585,23 +1908,23 @@ async function updateCommand(name, options) {
1585
1908
  updates.enabled = false;
1586
1909
  }
1587
1910
  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"));
1911
+ console.log(chalk10.yellow("No updates specified."));
1912
+ console.log(chalk10.dim("Available options: --hostname, --port, --start, --path, --env, --enable, --disable"));
1590
1913
  return;
1591
1914
  }
1592
1915
  try {
1593
1916
  updateProject(name, updates);
1594
- console.log(chalk9.green(`\u2713 Project "${name}" updated successfully`));
1917
+ console.log(chalk10.green(`\u2713 Project "${name}" updated successfully`));
1595
1918
  for (const [key, value] of Object.entries(updates)) {
1596
- console.log(chalk9.dim(` ${key}: ${typeof value === "object" ? JSON.stringify(value) : value}`));
1919
+ console.log(chalk10.dim(` ${key}: ${typeof value === "object" ? JSON.stringify(value) : value}`));
1597
1920
  }
1598
- console.log(chalk9.dim(`
1599
- Run ${chalk9.cyan("sudo bindler apply")} to apply changes to nginx.`));
1921
+ console.log(chalk10.dim(`
1922
+ Run ${chalk10.cyan("sudo bindler apply")} to apply changes to nginx.`));
1600
1923
  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.`));
1924
+ console.log(chalk10.dim(`Run ${chalk10.cyan(`bindler restart ${name}`)} to apply changes to the running process.`));
1602
1925
  }
1603
1926
  } catch (error) {
1604
- console.error(chalk9.red(`Error: ${error instanceof Error ? error.message : error}`));
1927
+ console.error(chalk10.red(`Error: ${error instanceof Error ? error.message : error}`));
1605
1928
  process.exit(1);
1606
1929
  }
1607
1930
  }
@@ -1610,20 +1933,20 @@ Run ${chalk9.cyan("sudo bindler apply")} to apply changes to nginx.`));
1610
1933
  import { writeFileSync as writeFileSync4, readFileSync as readFileSync4, unlinkSync } from "fs";
1611
1934
  import { tmpdir } from "os";
1612
1935
  import { join as join6 } from "path";
1613
- import chalk10 from "chalk";
1936
+ import chalk11 from "chalk";
1614
1937
  async function editCommand(name) {
1615
1938
  const project = getProject(name);
1616
1939
  if (!project) {
1617
- console.error(chalk10.red(`Error: Project "${name}" not found`));
1940
+ console.error(chalk11.red(`Error: Project "${name}" not found`));
1618
1941
  process.exit(1);
1619
1942
  }
1620
1943
  const editor = process.env.EDITOR || process.env.VISUAL || "vi";
1621
1944
  const tmpFile = join6(tmpdir(), `bindler-${name}-${Date.now()}.json`);
1622
1945
  writeFileSync4(tmpFile, JSON.stringify(project, null, 2) + "\n");
1623
- console.log(chalk10.dim(`Opening ${name} config in ${editor}...`));
1946
+ console.log(chalk11.dim(`Opening ${name} config in ${editor}...`));
1624
1947
  const exitCode = await spawnInteractive(editor, [tmpFile]);
1625
1948
  if (exitCode !== 0) {
1626
- console.error(chalk10.red("Editor exited with error"));
1949
+ console.error(chalk11.red("Editor exited with error"));
1627
1950
  unlinkSync(tmpFile);
1628
1951
  process.exit(1);
1629
1952
  }
@@ -1631,7 +1954,7 @@ async function editCommand(name) {
1631
1954
  try {
1632
1955
  editedContent = readFileSync4(tmpFile, "utf-8");
1633
1956
  } catch (error) {
1634
- console.error(chalk10.red("Failed to read edited file"));
1957
+ console.error(chalk11.red("Failed to read edited file"));
1635
1958
  process.exit(1);
1636
1959
  } finally {
1637
1960
  unlinkSync(tmpFile);
@@ -1640,44 +1963,44 @@ async function editCommand(name) {
1640
1963
  try {
1641
1964
  editedProject = JSON.parse(editedContent);
1642
1965
  } catch (error) {
1643
- console.error(chalk10.red("Error: Invalid JSON in edited file"));
1966
+ console.error(chalk11.red("Error: Invalid JSON in edited file"));
1644
1967
  process.exit(1);
1645
1968
  }
1646
1969
  if (editedProject.name !== project.name) {
1647
- console.error(chalk10.red("Error: Cannot change project name via edit. Use a new project instead."));
1970
+ console.error(chalk11.red("Error: Cannot change project name via edit. Use a new project instead."));
1648
1971
  process.exit(1);
1649
1972
  }
1650
1973
  const originalStr = JSON.stringify(project);
1651
1974
  const editedStr = JSON.stringify(editedProject);
1652
1975
  if (originalStr === editedStr) {
1653
- console.log(chalk10.yellow("No changes made."));
1976
+ console.log(chalk11.yellow("No changes made."));
1654
1977
  return;
1655
1978
  }
1656
1979
  try {
1657
1980
  const config = readConfig();
1658
1981
  const index = config.projects.findIndex((p) => p.name === name);
1659
1982
  if (index === -1) {
1660
- console.error(chalk10.red("Error: Project not found"));
1983
+ console.error(chalk11.red("Error: Project not found"));
1661
1984
  process.exit(1);
1662
1985
  }
1663
1986
  config.projects[index] = editedProject;
1664
1987
  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.`));
1988
+ console.log(chalk11.green(`\u2713 Project "${name}" updated successfully`));
1989
+ console.log(chalk11.dim(`
1990
+ Run ${chalk11.cyan("sudo bindler apply")} to apply changes to nginx.`));
1668
1991
  } catch (error) {
1669
- console.error(chalk10.red(`Error: ${error instanceof Error ? error.message : error}`));
1992
+ console.error(chalk11.red(`Error: ${error instanceof Error ? error.message : error}`));
1670
1993
  process.exit(1);
1671
1994
  }
1672
1995
  }
1673
1996
 
1674
1997
  // src/commands/remove.ts
1675
1998
  import inquirer2 from "inquirer";
1676
- import chalk11 from "chalk";
1999
+ import chalk12 from "chalk";
1677
2000
  async function removeCommand(name, options) {
1678
2001
  const project = getProject(name);
1679
2002
  if (!project) {
1680
- console.error(chalk11.red(`Error: Project "${name}" not found`));
2003
+ console.error(chalk12.red(`Error: Project "${name}" not found`));
1681
2004
  process.exit(1);
1682
2005
  }
1683
2006
  if (!options.force) {
@@ -1690,338 +2013,51 @@ async function removeCommand(name, options) {
1690
2013
  }
1691
2014
  ]);
1692
2015
  if (!confirm) {
1693
- console.log(chalk11.yellow("Cancelled."));
2016
+ console.log(chalk12.yellow("Cancelled."));
1694
2017
  return;
1695
2018
  }
1696
2019
  }
1697
2020
  if (project.type === "npm") {
1698
2021
  const process2 = getProcessByName(name);
1699
2022
  if (process2) {
1700
- console.log(chalk11.dim("Stopping PM2 process..."));
2023
+ console.log(chalk12.dim("Stopping PM2 process..."));
1701
2024
  deleteProject(name);
1702
2025
  }
1703
2026
  }
1704
2027
  try {
1705
2028
  removeProject(name);
1706
- console.log(chalk11.green(`\u2713 Project "${name}" removed from registry`));
2029
+ console.log(chalk12.green(`\u2713 Project "${name}" removed from registry`));
1707
2030
  if (options.apply) {
1708
- console.log(chalk11.dim("\nApplying nginx configuration..."));
2031
+ console.log(chalk12.dim("\nApplying nginx configuration..."));
1709
2032
  const config = readConfig();
1710
2033
  try {
1711
2034
  writeNginxConfig(config);
1712
2035
  const testResult = testNginxConfig();
1713
2036
  if (testResult.success) {
1714
2037
  reloadNginx();
1715
- console.log(chalk11.green("\u2713 Nginx configuration updated"));
2038
+ console.log(chalk12.green("\u2713 Nginx configuration updated"));
1716
2039
  } else {
1717
- console.log(chalk11.yellow("! Nginx config test failed, reload skipped"));
2040
+ console.log(chalk12.yellow("! Nginx config test failed, reload skipped"));
1718
2041
  }
1719
2042
  } catch (err) {
1720
- console.log(chalk11.yellow(`! Failed to update nginx: ${err}`));
1721
- console.log(chalk11.dim(" Try running: sudo bindler apply"));
1722
- }
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++;
2043
+ console.log(chalk12.yellow(`! Failed to update nginx: ${err}`));
2044
+ console.log(chalk12.dim(" Try running: sudo bindler apply"));
1847
2045
  }
1848
- }
1849
- if (synced === 0) {
1850
- console.log(chalk12.dim(" No bindler.yaml files found in project directories"));
1851
2046
  } else {
1852
2047
  console.log(chalk12.dim(`
1853
- Synced ${synced} project(s)
1854
- `));
2048
+ Run ${chalk12.cyan("sudo bindler apply")} to update nginx configuration.`));
1855
2049
  }
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"));
2050
+ console.log(chalk12.yellow("\nNote: Project files were not deleted."));
2051
+ console.log(chalk12.dim(` Path: ${project.path}`));
2052
+ if (!project.local) {
2053
+ console.log(chalk12.yellow("\nCloudflare DNS route was not removed."));
2054
+ console.log(chalk12.dim(" Remove it manually from the Cloudflare dashboard:"));
2055
+ console.log(chalk12.dim(" https://dash.cloudflare.com \u2192 DNS \u2192 Records \u2192 Delete the CNAME for " + project.hostname));
1880
2056
  }
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
2057
  } 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
- }
1901
- process.exit(1);
1902
- }
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."));
2058
+ console.error(chalk12.red(`Error: ${error instanceof Error ? error.message : error}`));
1922
2059
  process.exit(1);
1923
2060
  }
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
2061
  }
2026
2062
 
2027
2063
  // src/commands/doctor.ts
@@ -2333,7 +2369,7 @@ async function infoCommand() {
2333
2369
  `));
2334
2370
  console.log(chalk15.white(" Manage multiple projects behind Cloudflare Tunnel"));
2335
2371
  console.log(chalk15.white(" with Nginx and PM2\n"));
2336
- console.log(chalk15.dim(" Version: ") + chalk15.white("1.6.0"));
2372
+ console.log(chalk15.dim(" Version: ") + chalk15.white("1.6.2"));
2337
2373
  console.log(chalk15.dim(" Author: ") + chalk15.white("alfaoz"));
2338
2374
  console.log(chalk15.dim(" License: ") + chalk15.white("MIT"));
2339
2375
  console.log(chalk15.dim(" GitHub: ") + chalk15.cyan("https://github.com/alfaoz/bindler"));
@@ -4075,28 +4111,28 @@ async function checkForUpdates() {
4075
4111
  const cache = readCache();
4076
4112
  const now = Date.now();
4077
4113
  if (now - cache.lastCheck < CHECK_INTERVAL) {
4078
- if (cache.latestVersion && compareVersions("1.6.0", cache.latestVersion) < 0) {
4114
+ if (cache.latestVersion && compareVersions("1.6.2", cache.latestVersion) > 0) {
4079
4115
  showUpdateMessage(cache.latestVersion);
4080
4116
  }
4081
4117
  return;
4082
4118
  }
4083
4119
  fetchLatestVersion().then((latestVersion) => {
4084
4120
  writeCache({ lastCheck: now, latestVersion });
4085
- if (latestVersion && compareVersions("1.6.0", latestVersion) < 0) {
4121
+ if (latestVersion && compareVersions("1.6.2", latestVersion) > 0) {
4086
4122
  showUpdateMessage(latestVersion);
4087
4123
  }
4088
4124
  });
4089
4125
  }
4090
4126
  function showUpdateMessage(latestVersion) {
4091
4127
  console.log("");
4092
- console.log(chalk30.yellow(` Update available: ${"1.6.0"} \u2192 ${latestVersion}`));
4128
+ console.log(chalk30.yellow(` Update available: ${"1.6.2"} \u2192 ${latestVersion}`));
4093
4129
  console.log(chalk30.dim(` Run: npm update -g bindler`));
4094
4130
  console.log("");
4095
4131
  }
4096
4132
 
4097
4133
  // src/cli.ts
4098
4134
  var program = new Command();
4099
- program.name("bindler").description("Manage multiple projects behind Cloudflare Tunnel with Nginx and PM2").version("1.6.0");
4135
+ program.name("bindler").description("Manage multiple projects behind Cloudflare Tunnel with Nginx and PM2").version("1.6.2");
4100
4136
  program.hook("preAction", async () => {
4101
4137
  try {
4102
4138
  initConfig();