bindler 1.7.0 → 1.8.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 +75 -17
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -103,14 +103,31 @@ function getProject(name) {
|
|
|
103
103
|
const config = readConfig();
|
|
104
104
|
return config.projects.find((p) => p.name === name);
|
|
105
105
|
}
|
|
106
|
-
function
|
|
106
|
+
function canAddProject(project) {
|
|
107
107
|
const config = readConfig();
|
|
108
108
|
if (config.projects.some((p) => p.name === project.name)) {
|
|
109
|
-
|
|
109
|
+
return { valid: false, error: `Project "${project.name}" already exists` };
|
|
110
110
|
}
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
const conflictingProject = config.projects.find(
|
|
112
|
+
(p) => p.hostname === project.hostname && (p.basePath || "/") === (project.basePath || "/")
|
|
113
|
+
);
|
|
114
|
+
if (conflictingProject) {
|
|
115
|
+
if (project.basePath || conflictingProject.basePath) {
|
|
116
|
+
return {
|
|
117
|
+
valid: false,
|
|
118
|
+
error: `Hostname "${project.hostname}" with base path "${project.basePath || "/"}" conflicts with project "${conflictingProject.name}"`
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return { valid: false, error: `Hostname "${project.hostname}" is already in use by project "${conflictingProject.name}"` };
|
|
122
|
+
}
|
|
123
|
+
return { valid: true };
|
|
124
|
+
}
|
|
125
|
+
function addProject(project) {
|
|
126
|
+
const check = canAddProject(project);
|
|
127
|
+
if (!check.valid) {
|
|
128
|
+
throw new Error(check.error);
|
|
113
129
|
}
|
|
130
|
+
const config = readConfig();
|
|
114
131
|
config.projects.push(project);
|
|
115
132
|
writeConfig(config);
|
|
116
133
|
}
|
|
@@ -1412,11 +1429,16 @@ async function newCommand(options) {
|
|
|
1412
1429
|
}
|
|
1413
1430
|
}
|
|
1414
1431
|
if (!validateProjectName(project.name)) {
|
|
1415
|
-
console.error(chalk3.red("Error: Invalid project name"));
|
|
1432
|
+
console.error(chalk3.red("Error: Invalid project name. Use alphanumeric characters, dashes, and underscores only."));
|
|
1416
1433
|
process.exit(1);
|
|
1417
1434
|
}
|
|
1418
1435
|
if (!validateHostname(project.hostname)) {
|
|
1419
|
-
console.error(chalk3.red("Error: Invalid hostname"));
|
|
1436
|
+
console.error(chalk3.red("Error: Invalid hostname format"));
|
|
1437
|
+
process.exit(1);
|
|
1438
|
+
}
|
|
1439
|
+
const conflictCheck = canAddProject(project);
|
|
1440
|
+
if (!conflictCheck.valid) {
|
|
1441
|
+
console.error(chalk3.red(`Error: ${conflictCheck.error}`));
|
|
1420
1442
|
process.exit(1);
|
|
1421
1443
|
}
|
|
1422
1444
|
if (!existsSync7(project.path)) {
|
|
@@ -1486,13 +1508,42 @@ Configuration saved. Run ${chalk3.cyan("sudo bindler apply")} to update nginx an
|
|
|
1486
1508
|
// src/commands/list.ts
|
|
1487
1509
|
import chalk4 from "chalk";
|
|
1488
1510
|
import Table from "cli-table3";
|
|
1489
|
-
async function listCommand() {
|
|
1511
|
+
async function listCommand(options = {}) {
|
|
1490
1512
|
const projects = listProjects();
|
|
1491
1513
|
if (projects.length === 0) {
|
|
1514
|
+
if (options.json) {
|
|
1515
|
+
console.log("[]");
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1492
1518
|
console.log(chalk4.yellow("No projects registered."));
|
|
1493
1519
|
console.log(chalk4.dim(`Run ${chalk4.cyan("bindler new")} to create one.`));
|
|
1494
1520
|
return;
|
|
1495
1521
|
}
|
|
1522
|
+
if (options.json) {
|
|
1523
|
+
const output = projects.map((project) => {
|
|
1524
|
+
let status = "n/a";
|
|
1525
|
+
if (project.type === "npm") {
|
|
1526
|
+
const process2 = getProcessByName(project.name);
|
|
1527
|
+
status = process2?.status || "not_started";
|
|
1528
|
+
} else {
|
|
1529
|
+
status = project.enabled !== false ? "serving" : "disabled";
|
|
1530
|
+
}
|
|
1531
|
+
if (project.enabled === false) status = "disabled";
|
|
1532
|
+
return {
|
|
1533
|
+
name: project.name,
|
|
1534
|
+
type: project.type,
|
|
1535
|
+
hostname: project.hostname,
|
|
1536
|
+
basePath: project.basePath,
|
|
1537
|
+
port: project.port,
|
|
1538
|
+
path: project.path,
|
|
1539
|
+
status,
|
|
1540
|
+
local: project.local,
|
|
1541
|
+
enabled: project.enabled !== false
|
|
1542
|
+
};
|
|
1543
|
+
});
|
|
1544
|
+
console.log(JSON.stringify(output, null, 2));
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1496
1547
|
const table = new Table({
|
|
1497
1548
|
head: [
|
|
1498
1549
|
chalk4.cyan("Name"),
|
|
@@ -1873,7 +1924,11 @@ async function updateCommand(name, options) {
|
|
|
1873
1924
|
process.exit(1);
|
|
1874
1925
|
}
|
|
1875
1926
|
const updates = {};
|
|
1876
|
-
if (options.hostname) {
|
|
1927
|
+
if (options.hostname !== void 0) {
|
|
1928
|
+
if (!options.hostname || options.hostname.trim() === "") {
|
|
1929
|
+
console.error(chalk10.red("Error: Hostname cannot be empty"));
|
|
1930
|
+
process.exit(1);
|
|
1931
|
+
}
|
|
1877
1932
|
if (!validateHostname(options.hostname)) {
|
|
1878
1933
|
console.error(chalk10.red("Error: Invalid hostname format"));
|
|
1879
1934
|
process.exit(1);
|
|
@@ -2394,7 +2449,7 @@ async function infoCommand() {
|
|
|
2394
2449
|
`));
|
|
2395
2450
|
console.log(chalk15.white(" Manage multiple projects behind Cloudflare Tunnel"));
|
|
2396
2451
|
console.log(chalk15.white(" with Nginx and PM2\n"));
|
|
2397
|
-
console.log(chalk15.dim(" Version: ") + chalk15.white("1.
|
|
2452
|
+
console.log(chalk15.dim(" Version: ") + chalk15.white("1.8.0"));
|
|
2398
2453
|
console.log(chalk15.dim(" Author: ") + chalk15.white("alfaoz"));
|
|
2399
2454
|
console.log(chalk15.dim(" License: ") + chalk15.white("MIT"));
|
|
2400
2455
|
console.log(chalk15.dim(" GitHub: ") + chalk15.cyan("https://github.com/alfaoz/bindler"));
|
|
@@ -4001,9 +4056,12 @@ Note: Add to /etc/hosts if not already:`));
|
|
|
4001
4056
|
console.log(chalk28.cyan(` echo "127.0.0.1 ${project.hostname}" | sudo tee -a /etc/hosts`));
|
|
4002
4057
|
}
|
|
4003
4058
|
const defaults = getDefaults();
|
|
4004
|
-
const
|
|
4059
|
+
const listenParts = defaults.nginxListen.split(":");
|
|
4060
|
+
const listenPort = listenParts.length > 1 ? listenParts[1] : listenParts[0];
|
|
4061
|
+
const isDefaultPort = listenPort === "80" || listenPort === "443";
|
|
4062
|
+
const accessUrl = isDefaultPort ? `http://${project.hostname}` : `http://${project.hostname}:${listenPort}`;
|
|
4005
4063
|
console.log(chalk28.green(`
|
|
4006
|
-
Access at:
|
|
4064
|
+
Access at: ${accessUrl}`));
|
|
4007
4065
|
console.log(chalk28.dim("Press Ctrl+C to stop\n"));
|
|
4008
4066
|
console.log(chalk28.dim("---"));
|
|
4009
4067
|
const env = {
|
|
@@ -4174,28 +4232,28 @@ async function checkForUpdates() {
|
|
|
4174
4232
|
const cache = readCache();
|
|
4175
4233
|
const now = Date.now();
|
|
4176
4234
|
if (now - cache.lastCheck < CHECK_INTERVAL) {
|
|
4177
|
-
if (cache.latestVersion && compareVersions("1.
|
|
4235
|
+
if (cache.latestVersion && compareVersions("1.8.0", cache.latestVersion) > 0) {
|
|
4178
4236
|
showUpdateMessage(cache.latestVersion);
|
|
4179
4237
|
}
|
|
4180
4238
|
return;
|
|
4181
4239
|
}
|
|
4182
4240
|
fetchLatestVersion().then((latestVersion) => {
|
|
4183
4241
|
writeCache({ lastCheck: now, latestVersion });
|
|
4184
|
-
if (latestVersion && compareVersions("1.
|
|
4242
|
+
if (latestVersion && compareVersions("1.8.0", latestVersion) > 0) {
|
|
4185
4243
|
showUpdateMessage(latestVersion);
|
|
4186
4244
|
}
|
|
4187
4245
|
});
|
|
4188
4246
|
}
|
|
4189
4247
|
function showUpdateMessage(latestVersion) {
|
|
4190
4248
|
console.log("");
|
|
4191
|
-
console.log(chalk30.yellow(` Update available: ${"1.
|
|
4249
|
+
console.log(chalk30.yellow(` Update available: ${"1.8.0"} \u2192 ${latestVersion}`));
|
|
4192
4250
|
console.log(chalk30.dim(` Run: npm update -g bindler`));
|
|
4193
4251
|
console.log("");
|
|
4194
4252
|
}
|
|
4195
4253
|
|
|
4196
4254
|
// src/cli.ts
|
|
4197
4255
|
var program = new Command();
|
|
4198
|
-
program.name("bindler").description("Manage multiple projects behind Cloudflare Tunnel with Nginx and PM2").version("1.
|
|
4256
|
+
program.name("bindler").description("Manage multiple projects behind Cloudflare Tunnel with Nginx and PM2").version("1.8.0");
|
|
4199
4257
|
program.hook("preAction", async () => {
|
|
4200
4258
|
try {
|
|
4201
4259
|
initConfig();
|
|
@@ -4206,8 +4264,8 @@ program.hook("preAction", async () => {
|
|
|
4206
4264
|
program.command("new").description("Create and register a new project").option("-n, --name <name>", "Project name").option("-t, --type <type>", "Project type (static or npm)", "static").option("-p, --path <path>", "Project directory path").option("-h, --hostname <hostname>", "Hostname for the project").option("-b, --base-path <path>", "Base path for path-based routing (e.g., /api)").option("--port <port>", "Port number (npm projects only)").option("-s, --start <command>", "Start command (npm projects only)").option("-l, --local", "Local project (skip Cloudflare, use .local hostname)").option("--apply", "Apply nginx config after creating").action(async (options) => {
|
|
4207
4265
|
await newCommand(options);
|
|
4208
4266
|
});
|
|
4209
|
-
program.command("list").alias("ls").description("List all registered projects").action(async () => {
|
|
4210
|
-
await listCommand();
|
|
4267
|
+
program.command("list").alias("ls").description("List all registered projects").option("--json", "Output as JSON (for scripting)").action(async (options) => {
|
|
4268
|
+
await listCommand(options);
|
|
4211
4269
|
});
|
|
4212
4270
|
program.command("status").description("Show detailed status of all projects").action(async () => {
|
|
4213
4271
|
await statusCommand();
|