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 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 addProject(project) {
106
+ function canAddProject(project) {
107
107
  const config = readConfig();
108
108
  if (config.projects.some((p) => p.name === project.name)) {
109
- throw new Error(`Project "${project.name}" already exists`);
109
+ return { valid: false, error: `Project "${project.name}" already exists` };
110
110
  }
111
- if (config.projects.some((p) => p.hostname === project.hostname)) {
112
- throw new Error(`Hostname "${project.hostname}" is already in use`);
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.7.0"));
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 listenPort = defaults.nginxListen.split(":")[1] || "8080";
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: http://${project.hostname}:${listenPort}`));
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.7.0", cache.latestVersion) > 0) {
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.7.0", latestVersion) > 0) {
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.7.0"} \u2192 ${latestVersion}`));
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.7.0");
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();