bindler 1.6.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -741,14 +741,30 @@ function isProtectedPath(path) {
741
741
  (protected_) => normalizedPath === protected_ || normalizedPath.startsWith(protected_ + "/")
742
742
  );
743
743
  }
744
- function checkPortAvailable(port) {
744
+ function checkPortAvailable(port, projectName) {
745
745
  const result = execCommandSafe(`lsof -i :${port} -P -n 2>/dev/null | grep LISTEN | head -1`);
746
746
  if (!result.success || !result.output) {
747
747
  return { available: true };
748
748
  }
749
749
  const parts = result.output.trim().split(/\s+/);
750
750
  const processName = parts[0] || "unknown";
751
- return { available: false, usedBy: processName };
751
+ const pid = parts[1] || "";
752
+ if (projectName && pid) {
753
+ const pm2Check = execCommandSafe(`pm2 jlist 2>/dev/null`);
754
+ if (pm2Check.success && pm2Check.output) {
755
+ try {
756
+ const processes = JSON.parse(pm2Check.output);
757
+ const bindlerProcess = processes.find(
758
+ (p) => p.name === `bindler:${projectName}` && String(p.pid) === pid
759
+ );
760
+ if (bindlerProcess) {
761
+ return { available: false, usedBy: processName, isOwnProcess: true };
762
+ }
763
+ } catch {
764
+ }
765
+ }
766
+ }
767
+ return { available: false, usedBy: processName, isOwnProcess: false };
752
768
  }
753
769
  function validateProject(project) {
754
770
  const errors = [];
@@ -762,8 +778,8 @@ function validateProject(project) {
762
778
  );
763
779
  }
764
780
  if (project.type === "npm" && project.port) {
765
- const portCheck = checkPortAvailable(project.port);
766
- if (!portCheck.available) {
781
+ const portCheck = checkPortAvailable(project.port, project.name);
782
+ if (!portCheck.available && !portCheck.isOwnProcess) {
767
783
  warnings.push(`Port ${project.port} is in use by ${portCheck.usedBy}`);
768
784
  }
769
785
  }
@@ -1629,6 +1645,10 @@ async function statusCommand() {
1629
1645
 
1630
1646
  // src/commands/start.ts
1631
1647
  import chalk6 from "chalk";
1648
+ var CRASH_CHECK_DELAY = 2500;
1649
+ function sleep(ms) {
1650
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
1651
+ }
1632
1652
  async function startCommand(name, options) {
1633
1653
  if (options.all) {
1634
1654
  const projects = listProjects();
@@ -1646,9 +1666,23 @@ async function startCommand(name, options) {
1646
1666
  console.log(chalk6.red(` \u2717 ${result2.name}: ${result2.error}`));
1647
1667
  }
1648
1668
  }
1649
- const succeeded = results.filter((r) => r.success).length;
1650
- console.log(chalk6.dim(`
1651
- ${succeeded}/${results.length} started successfully`));
1669
+ console.log(chalk6.dim("\nChecking for crashes..."));
1670
+ await sleep(CRASH_CHECK_DELAY);
1671
+ const crashed = [];
1672
+ for (const project2 of npmProjects) {
1673
+ const process2 = getProcessByName(project2.name);
1674
+ if (process2 && process2.status !== "online") {
1675
+ crashed.push(project2.name);
1676
+ }
1677
+ }
1678
+ if (crashed.length > 0) {
1679
+ console.log(chalk6.red(`
1680
+ \u2717 ${crashed.length} project(s) crashed: ${crashed.join(", ")}`));
1681
+ console.log(chalk6.dim(` Run: bindler logs <name> to see errors`));
1682
+ } else {
1683
+ const succeeded = results.filter((r) => r.success).length;
1684
+ console.log(chalk6.green(`\u2713 ${succeeded}/${results.length} running`));
1685
+ }
1652
1686
  return;
1653
1687
  }
1654
1688
  if (!name) {
@@ -1667,14 +1701,27 @@ ${succeeded}/${results.length} started successfully`));
1667
1701
  }
1668
1702
  console.log(chalk6.blue(`Starting ${name}...`));
1669
1703
  const result = startProject(project);
1670
- if (result.success) {
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}`));
1674
- } else {
1704
+ if (!result.success) {
1675
1705
  console.error(chalk6.red(`\u2717 Failed to start ${name}: ${result.error}`));
1676
1706
  process.exit(1);
1677
1707
  }
1708
+ console.log(chalk6.dim("Waiting for process to stabilize..."));
1709
+ await sleep(CRASH_CHECK_DELAY);
1710
+ const processStatus = getProcessByName(name);
1711
+ if (!processStatus || processStatus.status !== "online") {
1712
+ console.error(chalk6.red(`
1713
+ \u2717 ${name} crashed immediately after starting`));
1714
+ console.log(chalk6.dim("\nShowing recent logs:\n"));
1715
+ console.log(chalk6.dim("\u2500".repeat(50)));
1716
+ await showLogs(name, false, 30);
1717
+ console.log(chalk6.dim("\u2500".repeat(50)));
1718
+ console.log(chalk6.yellow(`
1719
+ Fix the error and try again: bindler start ${name}`));
1720
+ process.exit(1);
1721
+ }
1722
+ console.log(chalk6.green(`\u2713 ${name} started successfully`));
1723
+ console.log(chalk6.dim(` Port: ${project.port}`));
1724
+ console.log(chalk6.dim(` URL: https://${project.hostname}`));
1678
1725
  }
1679
1726
 
1680
1727
  // src/commands/stop.ts
@@ -1714,6 +1761,15 @@ ${succeeded}/${results.length} stopped successfully`));
1714
1761
  console.log(chalk7.yellow(`Project "${name}" is a static site - no process to stop.`));
1715
1762
  return;
1716
1763
  }
1764
+ const processStatus = getProcessByName(name);
1765
+ if (!processStatus) {
1766
+ console.log(chalk7.yellow(`${name} is not managed by PM2 (never started)`));
1767
+ return;
1768
+ }
1769
+ if (processStatus.status === "stopped") {
1770
+ console.log(chalk7.yellow(`${name} is already stopped`));
1771
+ return;
1772
+ }
1717
1773
  console.log(chalk7.blue(`Stopping ${name}...`));
1718
1774
  const result = stopProject(name);
1719
1775
  if (result.success) {
@@ -2338,7 +2394,7 @@ async function infoCommand() {
2338
2394
  `));
2339
2395
  console.log(chalk15.white(" Manage multiple projects behind Cloudflare Tunnel"));
2340
2396
  console.log(chalk15.white(" with Nginx and PM2\n"));
2341
- console.log(chalk15.dim(" Version: ") + chalk15.white("1.6.1"));
2397
+ console.log(chalk15.dim(" Version: ") + chalk15.white("1.7.0"));
2342
2398
  console.log(chalk15.dim(" Author: ") + chalk15.white("alfaoz"));
2343
2399
  console.log(chalk15.dim(" License: ") + chalk15.white("MIT"));
2344
2400
  console.log(chalk15.dim(" GitHub: ") + chalk15.cyan("https://github.com/alfaoz/bindler"));
@@ -2395,8 +2451,9 @@ async function checkDns(hostname) {
2395
2451
  }
2396
2452
  return result;
2397
2453
  }
2398
- async function checkHttp(hostname, path = "/") {
2399
- const url = `https://${hostname}${path}`;
2454
+ async function checkHttp(hostname, path = "/", useHttp = false) {
2455
+ const protocol = useHttp ? "http" : "https";
2456
+ const url = `${protocol}://${hostname}${path}`;
2400
2457
  const startTime = Date.now();
2401
2458
  try {
2402
2459
  const controller = new AbortController();
@@ -2412,12 +2469,14 @@ async function checkHttp(hostname, path = "/") {
2412
2469
  reachable: true,
2413
2470
  statusCode: response.status,
2414
2471
  redirectUrl: response.headers.get("location") || void 0,
2415
- responseTime
2472
+ responseTime,
2473
+ protocol
2416
2474
  };
2417
2475
  } catch (error) {
2418
2476
  return {
2419
2477
  reachable: false,
2420
- error: error instanceof Error ? error.message : String(error)
2478
+ error: error instanceof Error ? error.message : String(error),
2479
+ protocol
2421
2480
  };
2422
2481
  }
2423
2482
  }
@@ -2457,8 +2516,9 @@ Checking ${hostname}...
2457
2516
  console.log(chalk16.green(" \u2713 AAAA (IPv6): ") + dns.ipv6.join(", "));
2458
2517
  }
2459
2518
  console.log("");
2460
- console.log(chalk16.bold("HTTP Check:"));
2461
- const http = await checkHttp(hostname, basePath);
2519
+ const useHttp = options.http ?? isLocal;
2520
+ console.log(chalk16.bold(`HTTP Check (${useHttp ? "HTTP" : "HTTPS"}):`));
2521
+ const http = await checkHttp(hostname, basePath, useHttp);
2462
2522
  if (!http.reachable) {
2463
2523
  console.log(chalk16.red(" \u2717 Not reachable"));
2464
2524
  const err = http.error || "";
@@ -3769,14 +3829,48 @@ async function cloneCommand(source, newName, options) {
3769
3829
  process.exit(1);
3770
3830
  }
3771
3831
  }
3832
+ let targetPath = options.path;
3833
+ if (!targetPath) {
3834
+ const answer = await inquirer5.prompt([
3835
+ {
3836
+ type: "input",
3837
+ name: "path",
3838
+ message: "Path for new project:",
3839
+ default: sourceProject.path.replace(source, targetName),
3840
+ validate: (input) => {
3841
+ if (!input || input.trim() === "") {
3842
+ return "Path is required";
3843
+ }
3844
+ return true;
3845
+ }
3846
+ }
3847
+ ]);
3848
+ targetPath = answer.path;
3849
+ }
3772
3850
  const newProject = {
3773
3851
  ...sourceProject,
3774
3852
  name: targetName,
3775
3853
  hostname: targetHostname,
3776
- path: options.path || sourceProject.path
3854
+ path: targetPath
3777
3855
  };
3778
3856
  if (newProject.type === "npm") {
3779
- newProject.port = options.port || findAvailablePort();
3857
+ let port = options.port || findAvailablePort();
3858
+ const portCheck = checkPortInUse(port);
3859
+ if (portCheck.inUse) {
3860
+ const processInfo = portCheck.process ? ` by ${portCheck.process}` : "";
3861
+ console.log(chalk27.yellow(`Warning: Port ${port} is already in use${processInfo}`));
3862
+ if (!options.port) {
3863
+ for (let p = port + 1; p <= 9e3; p++) {
3864
+ const check = checkPortInUse(p);
3865
+ if (!check.inUse) {
3866
+ port = p;
3867
+ console.log(chalk27.dim(` Using port ${port} instead`));
3868
+ break;
3869
+ }
3870
+ }
3871
+ }
3872
+ }
3873
+ newProject.port = port;
3780
3874
  if (newProject.env?.PORT) {
3781
3875
  newProject.env = { ...newProject.env, PORT: String(newProject.port) };
3782
3876
  }
@@ -4080,28 +4174,28 @@ async function checkForUpdates() {
4080
4174
  const cache = readCache();
4081
4175
  const now = Date.now();
4082
4176
  if (now - cache.lastCheck < CHECK_INTERVAL) {
4083
- if (cache.latestVersion && compareVersions("1.6.1", cache.latestVersion) > 0) {
4177
+ if (cache.latestVersion && compareVersions("1.7.0", cache.latestVersion) > 0) {
4084
4178
  showUpdateMessage(cache.latestVersion);
4085
4179
  }
4086
4180
  return;
4087
4181
  }
4088
4182
  fetchLatestVersion().then((latestVersion) => {
4089
4183
  writeCache({ lastCheck: now, latestVersion });
4090
- if (latestVersion && compareVersions("1.6.1", latestVersion) > 0) {
4184
+ if (latestVersion && compareVersions("1.7.0", latestVersion) > 0) {
4091
4185
  showUpdateMessage(latestVersion);
4092
4186
  }
4093
4187
  });
4094
4188
  }
4095
4189
  function showUpdateMessage(latestVersion) {
4096
4190
  console.log("");
4097
- console.log(chalk30.yellow(` Update available: ${"1.6.1"} \u2192 ${latestVersion}`));
4191
+ console.log(chalk30.yellow(` Update available: ${"1.7.0"} \u2192 ${latestVersion}`));
4098
4192
  console.log(chalk30.dim(` Run: npm update -g bindler`));
4099
4193
  console.log("");
4100
4194
  }
4101
4195
 
4102
4196
  // src/cli.ts
4103
4197
  var program = new Command();
4104
- program.name("bindler").description("Manage multiple projects behind Cloudflare Tunnel with Nginx and PM2").version("1.6.1");
4198
+ program.name("bindler").description("Manage multiple projects behind Cloudflare Tunnel with Nginx and PM2").version("1.7.0");
4105
4199
  program.hook("preAction", async () => {
4106
4200
  try {
4107
4201
  initConfig();
@@ -4203,7 +4297,7 @@ program.command("ports").description("Show allocated ports").action(async () =>
4203
4297
  program.command("info").description("Show bindler information and stats").action(async () => {
4204
4298
  await infoCommand();
4205
4299
  });
4206
- program.command("check [hostname]").description("Check DNS propagation and HTTP accessibility for a hostname").option("-v, --verbose", "Show verbose output").action(async (hostname, options) => {
4300
+ program.command("check [hostname]").description("Check DNS propagation and HTTP accessibility for a hostname").option("-v, --verbose", "Show verbose output").option("--http", "Use HTTP instead of HTTPS (auto-enabled for .local hostnames)").action(async (hostname, options) => {
4207
4301
  if (!hostname) {
4208
4302
  console.log(chalk31.red("Usage: bindler check <hostname>"));
4209
4303
  console.log(chalk31.dim("\nExamples:"));