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 +120 -26
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
2461
|
-
|
|
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:
|
|
3854
|
+
path: targetPath
|
|
3777
3855
|
};
|
|
3778
3856
|
if (newProject.type === "npm") {
|
|
3779
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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:"));
|