apiblaze 0.1.9 → 0.1.12

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.
Files changed (2) hide show
  1. package/dist/index.js +232 -137
  2. package/package.json +2 -4
package/dist/index.js CHANGED
@@ -134,8 +134,9 @@ async function getLocalhostTargets(teamId) {
134
134
  async function getProjects(teamId) {
135
135
  return apiFetch(`/api/cli/projects?team_id=${encodeURIComponent(teamId)}`);
136
136
  }
137
- async function checkProxyName(name) {
138
- return apiFetch(`/api/cli/check-name?name=${encodeURIComponent(name)}`);
137
+ async function checkProxyName(name, teamId) {
138
+ const q = teamId ? `&team_id=${encodeURIComponent(teamId)}` : "";
139
+ return apiFetch(`/api/cli/check-name?name=${encodeURIComponent(name)}${q}`);
139
140
  }
140
141
  async function createProxy(payload) {
141
142
  return apiFetch("/api/cli/create-proxy", {
@@ -149,8 +150,11 @@ async function putDevTunnel(payload) {
149
150
  body: JSON.stringify(payload)
150
151
  });
151
152
  }
152
- async function deleteDevTunnel() {
153
- return apiFetch("/api/cli/dev-tunnel", { method: "DELETE" });
153
+ async function deleteDevTunnel(restore) {
154
+ return apiFetch("/api/cli/dev-tunnel", {
155
+ method: "DELETE",
156
+ body: JSON.stringify({ restore })
157
+ });
154
158
  }
155
159
  var DASHBOARD_BASE;
156
160
  var init_api = __esm({
@@ -404,9 +408,7 @@ function killCloudflared(proc) {
404
408
  }
405
409
 
406
410
  // src/lib/traffic.ts
407
- var import_ws = __toESM(require("ws"));
408
411
  var import_chalk2 = __toESM(require("chalk"));
409
- init_auth();
410
412
  var METHOD_COLORS = {
411
413
  GET: import_chalk2.default.cyan,
412
414
  POST: import_chalk2.default.green,
@@ -438,35 +440,51 @@ function formatLogLine(entry) {
438
440
  const ts = now.toTimeString().slice(0, 8);
439
441
  return `${import_chalk2.default.gray(`[${ts}]`)} ${colorMethod(entry.method)} ${import_chalk2.default.white(entry.path)} ${import_chalk2.default.gray("\u2192")} ${colorStatus(entry.status)} ${import_chalk2.default.gray(`(${colorLatency(entry.latency)})`)}`;
440
442
  }
441
- function connectTrafficStream(wsUrl, onEntry) {
442
- const token = getAccessToken();
443
- let ws;
444
- let reconnectCount = 0;
445
- const MAX_RECONNECTS = 3;
446
- function connect() {
447
- ws = new import_ws.default(wsUrl, { headers: { Authorization: `Bearer ${token}` } });
448
- ws.on("message", (data) => {
449
- try {
450
- const entry = JSON.parse(data.toString());
451
- onEntry(entry);
452
- } catch {
453
- }
443
+
444
+ // src/lib/proxy-logger.ts
445
+ var import_http = __toESM(require("http"));
446
+ var import_net = __toESM(require("net"));
447
+ function startPassthroughProxy(targetPort, onEntry) {
448
+ return new Promise((resolve, reject) => {
449
+ const server = import_http.default.createServer((req, res) => {
450
+ const start = Date.now();
451
+ const method = req.method ?? "GET";
452
+ const path3 = req.url ?? "/";
453
+ const proxyReq = import_http.default.request(
454
+ { host: "127.0.0.1", port: targetPort, method, path: path3, headers: req.headers },
455
+ (proxyRes) => {
456
+ onEntry({ method, path: path3, status: proxyRes.statusCode ?? 0, latency: Date.now() - start });
457
+ res.writeHead(proxyRes.statusCode ?? 502, proxyRes.headers);
458
+ proxyRes.pipe(res);
459
+ }
460
+ );
461
+ proxyReq.on("error", () => {
462
+ onEntry({ method, path: path3, status: 502, latency: Date.now() - start });
463
+ if (!res.headersSent) res.writeHead(502);
464
+ res.end();
465
+ });
466
+ req.pipe(proxyReq);
454
467
  });
455
- ws.on("close", () => {
456
- if (reconnectCount < MAX_RECONNECTS) {
457
- reconnectCount++;
458
- setTimeout(() => connect(), 2e3);
459
- } else {
460
- console.warn(import_chalk2.default.yellow("\nTraffic stream disconnected. Stop and restart `apiblaze dev` to reconnect."));
461
- }
468
+ server.on("upgrade", (req, clientSocket, head) => {
469
+ const upstream = import_net.default.connect(targetPort, "127.0.0.1", () => {
470
+ const headerLines = Object.entries(req.headers).map(([k, v]) => `${k}: ${Array.isArray(v) ? v.join(", ") : v}`).join("\r\n");
471
+ upstream.write(`${req.method} ${req.url} HTTP/1.1\r
472
+ ${headerLines}\r
473
+ \r
474
+ `);
475
+ if (head && head.length) upstream.write(head);
476
+ upstream.pipe(clientSocket);
477
+ clientSocket.pipe(upstream);
478
+ });
479
+ upstream.on("error", () => clientSocket.destroy());
480
+ clientSocket.on("error", () => upstream.destroy());
462
481
  });
463
- ws.on("error", (err) => {
464
- console.warn(import_chalk2.default.yellow(`
465
- Traffic stream error: ${err.message}`));
482
+ server.on("error", reject);
483
+ server.listen(0, "127.0.0.1", () => {
484
+ const { port } = server.address();
485
+ resolve({ port, close: () => server.close() });
466
486
  });
467
- return ws;
468
- }
469
- return connect();
487
+ });
470
488
  }
471
489
 
472
490
  // src/commands/dev.ts
@@ -551,60 +569,58 @@ Tunneling ${selectedTargets.length} project(s) to localhost:${options.port}
551
569
  `)
552
570
  );
553
571
  await ensureCloudflared();
572
+ const logger = await startPassthroughProxy(options.port, (entry) => {
573
+ console.log(formatLogLine(entry));
574
+ });
554
575
  let cfProcess;
555
576
  let tunnelUrl;
556
577
  {
557
578
  const spinner = (0, import_ora3.default)("Starting Cloudflare tunnel...").start();
558
579
  try {
559
- ({ process: cfProcess, tunnelUrl } = await spawnCloudflared(options.port));
580
+ ({ process: cfProcess, tunnelUrl } = await spawnCloudflared(logger.port));
560
581
  spinner.succeed(`Tunnel active: ${import_chalk3.default.bold.cyan(tunnelUrl)}`);
561
582
  } catch (err) {
562
583
  spinner.fail("Failed to start cloudflared tunnel.");
584
+ logger.close();
563
585
  throw err;
564
586
  }
565
587
  }
588
+ let restore = [];
566
589
  {
567
590
  const spinner = (0, import_ora3.default)("Registering tunnel with APIblaze...").start();
568
- let wsUrl;
569
591
  try {
570
592
  const result = await putDevTunnel({
571
593
  tunnelUrl,
572
594
  targets: selectedTargets.map((t) => ({ projectId: t.projectId, tenantId: t.tenantId }))
573
595
  });
574
- wsUrl = result.wsUrl;
596
+ restore = result.restore ?? [];
575
597
  spinner.succeed("Tunnel registered. Proxying traffic.");
576
598
  } catch (err) {
577
599
  spinner.fail("Failed to register tunnel.");
600
+ logger.close();
578
601
  killCloudflared(cfProcess);
579
602
  throw err;
580
603
  }
581
- let ws;
582
- console.log("\n" + import_chalk3.default.gray("\u2500".repeat(60)));
583
- console.log(import_chalk3.default.bold("Live traffic") + import_chalk3.default.gray(" (Ctrl+C to stop)"));
584
- console.log(import_chalk3.default.gray("\u2500".repeat(60)) + "\n");
585
- ws = connectTrafficStream(wsUrl, (entry) => {
586
- console.log(formatLogLine(entry));
587
- });
588
- let isCleaningUp = false;
589
- async function cleanup() {
590
- if (isCleaningUp) return;
591
- isCleaningUp = true;
592
- console.log(import_chalk3.default.gray("\n\nShutting down..."));
593
- try {
594
- ws.close();
595
- } catch {
596
- }
597
- await deleteDevTunnel().catch(() => {
598
- });
599
- killCloudflared(cfProcess);
600
- console.log(import_chalk3.default.green("Tunnel stopped."));
601
- process.exit(0);
602
- }
603
- process.on("SIGINT", () => void cleanup());
604
- process.on("SIGTERM", () => void cleanup());
605
- await new Promise(() => {
604
+ }
605
+ console.log("\n" + import_chalk3.default.gray("\u2500".repeat(60)));
606
+ console.log(import_chalk3.default.bold("Live traffic") + import_chalk3.default.gray(" (Ctrl+C to stop)"));
607
+ console.log(import_chalk3.default.gray("\u2500".repeat(60)) + "\n");
608
+ let isCleaningUp = false;
609
+ async function cleanup() {
610
+ if (isCleaningUp) return;
611
+ isCleaningUp = true;
612
+ console.log(import_chalk3.default.gray("\n\nShutting down..."));
613
+ await deleteDevTunnel(restore).catch(() => {
606
614
  });
615
+ logger.close();
616
+ killCloudflared(cfProcess);
617
+ console.log(import_chalk3.default.green("Tunnel stopped."));
618
+ process.exit(0);
607
619
  }
620
+ process.on("SIGINT", () => void cleanup());
621
+ process.on("SIGTERM", () => void cleanup());
622
+ await new Promise(() => {
623
+ });
608
624
  }
609
625
 
610
626
  // src/commands/projects.ts
@@ -677,7 +693,7 @@ function normalizeName(raw) {
677
693
  }
678
694
  function isHttpUrl(s) {
679
695
  try {
680
- const u = new URL(s.trim());
696
+ const u = new URL((s || "").trim());
681
697
  return u.protocol === "http:" || u.protocol === "https:";
682
698
  } catch {
683
699
  return false;
@@ -695,78 +711,144 @@ function stripTenantFromPortal(devPortal) {
695
711
  return devPortal;
696
712
  }
697
713
  }
698
- async function runCreate() {
714
+ function fail(message) {
715
+ console.error(import_chalk5.default.red(`Error: ${message}`));
716
+ process.exit(1);
717
+ }
718
+ var VALID_AUTH = ["api_key", "none", "oauth"];
719
+ async function runCreate(opts = {}) {
699
720
  const creds = loadCredentials();
700
721
  if (!creds) {
701
- console.error(import_chalk5.default.red("Not logged in. Run `apiblaze login` first."));
702
- process.exit(1);
722
+ fail("Not logged in. Run `apiblaze login` first.");
703
723
  }
704
- console.log(import_chalk5.default.bold("\nCreate an API proxy\n"));
705
- const { default: inquirer2 } = await import("inquirer");
724
+ const interactive = !!process.stdin.isTTY && !opts.json;
725
+ const auth = (opts.auth ?? "api_key").toLowerCase();
726
+ if (!VALID_AUTH.includes(auth)) {
727
+ fail(`Invalid --auth "${auth}". Use one of: ${VALID_AUTH.join(", ")}.`);
728
+ }
729
+ let teamId = creds.teamId;
730
+ if (opts.team) {
731
+ if (opts.team.startsWith("team_")) {
732
+ teamId = opts.team;
733
+ } else {
734
+ const teams = await getTeams().catch(() => []);
735
+ const match = teams.find(
736
+ (t) => t.teamId === opts.team || t.name.toLowerCase() === opts.team.toLowerCase()
737
+ );
738
+ if (!match) {
739
+ fail(`Team "${opts.team}" not found. Run \`apiblaze team\` to see your teams.`);
740
+ }
741
+ teamId = match.teamId;
742
+ }
743
+ }
744
+ if (!opts.json) console.log(import_chalk5.default.bold("\nCreate an API proxy\n"));
706
745
  let name = "";
707
- for (; ; ) {
708
- const { rawName } = await inquirer2.prompt([{
709
- type: "input",
710
- name: "rawName",
711
- message: "Proxy name (your API will live at <name>.apiblaze.com):",
712
- transformer: (v) => normalizeName(v)
713
- }]);
714
- name = normalizeName(rawName);
715
- if (name.length < 3) {
716
- console.log(import_chalk5.default.yellow(" Name must be at least 3 characters (letters and digits only).\n"));
717
- continue;
746
+ if (opts.name !== void 0) {
747
+ name = normalizeName(opts.name);
748
+ if (name.length < 3) fail("Proxy name must be at least 3 characters (letters and digits only).");
749
+ const check = await checkProxyName(name, teamId).catch(() => null);
750
+ if (check && (!check.canUseProjectName || !check.canUseApiVersion)) {
751
+ fail(`Proxy name "${name}" is not available${check.message ? ` \u2014 ${check.message}` : ""}.`);
718
752
  }
719
- const spinner2 = (0, import_ora5.default)("Checking availability...").start();
720
- try {
721
- const check = await checkProxyName(name);
722
- spinner2.stop();
723
- if (!check.canUseProjectName || !check.canUseApiVersion) {
724
- const why = check.message ? ` \u2014 ${check.message}` : "";
725
- console.log(import_chalk5.default.yellow(` "${name}" is not available${why}. Try another.
726
- `));
753
+ } else if (interactive) {
754
+ const { default: inquirer2 } = await import("inquirer");
755
+ for (; ; ) {
756
+ const { rawName } = await inquirer2.prompt([{
757
+ type: "input",
758
+ name: "rawName",
759
+ message: "Proxy name (your API will live at <name>.apiblaze.com):",
760
+ transformer: (v) => normalizeName(v)
761
+ }]);
762
+ name = normalizeName(rawName);
763
+ if (name.length < 3) {
764
+ console.log(import_chalk5.default.yellow(" Name must be at least 3 characters (letters and digits only).\n"));
727
765
  continue;
728
766
  }
729
- } catch {
730
- spinner2.stop();
731
- console.log(import_chalk5.default.dim(" (could not verify availability; continuing)"));
732
- }
733
- console.log(`${import_chalk5.default.cyan("\u2192")} Your API will live at ${import_chalk5.default.bold(`https://${name}.apiblaze.com`)}
767
+ const spinner2 = (0, import_ora5.default)("Checking availability...").start();
768
+ try {
769
+ const check = await checkProxyName(name, teamId);
770
+ spinner2.stop();
771
+ if (!check.canUseProjectName || !check.canUseApiVersion) {
772
+ console.log(import_chalk5.default.yellow(` "${name}" is not available${check.message ? ` \u2014 ${check.message}` : ""}. Try another.
773
+ `));
774
+ continue;
775
+ }
776
+ } catch {
777
+ spinner2.stop();
778
+ console.log(import_chalk5.default.dim(" (could not verify availability; continuing)"));
779
+ }
780
+ console.log(`${import_chalk5.default.cyan("\u2192")} Your API will live at ${import_chalk5.default.bold(`https://${name}.apiblaze.com`)}
734
781
  `);
735
- break;
782
+ break;
783
+ }
784
+ } else {
785
+ fail("--name is required in non-interactive mode.");
736
786
  }
737
787
  let targetUrl = "";
738
- for (; ; ) {
739
- const { url } = await inquirer2.prompt([{
740
- type: "input",
741
- name: "url",
742
- message: "Target URL to forward requests to (e.g. https://httpbin.org):"
788
+ if (opts.target !== void 0) {
789
+ if (!isHttpUrl(opts.target)) fail("--target must be a valid http(s) URL.");
790
+ targetUrl = opts.target.trim();
791
+ } else if (interactive) {
792
+ const { default: inquirer2 } = await import("inquirer");
793
+ for (; ; ) {
794
+ const { url } = await inquirer2.prompt([{
795
+ type: "input",
796
+ name: "url",
797
+ message: "Target URL to forward requests to (e.g. https://httpbin.org):"
798
+ }]);
799
+ if (!isHttpUrl(url)) {
800
+ console.log(import_chalk5.default.yellow(" Enter a valid http(s) URL.\n"));
801
+ continue;
802
+ }
803
+ targetUrl = url.trim();
804
+ break;
805
+ }
806
+ } else {
807
+ fail("--target is required in non-interactive mode.");
808
+ }
809
+ if (interactive && !opts.yes) {
810
+ const { default: inquirer2 } = await import("inquirer");
811
+ console.log(`${import_chalk5.default.cyan("\u2192")} Auth: ${import_chalk5.default.bold(auth)}${auth === "api_key" ? " \u2014 consumers send an X-API-Key header" : ""}`);
812
+ const { ok } = await inquirer2.prompt([{
813
+ type: "confirm",
814
+ name: "ok",
815
+ message: `Create proxy "${name}" \u2192 ${targetUrl}?`,
816
+ default: true
743
817
  }]);
744
- if (!isHttpUrl(url)) {
745
- console.log(import_chalk5.default.yellow(" Enter a valid http(s) URL.\n"));
746
- continue;
818
+ if (!ok) {
819
+ console.log(import_chalk5.default.yellow("Cancelled."));
820
+ return;
747
821
  }
748
- targetUrl = url.trim();
749
- break;
750
822
  }
751
- console.log(`${import_chalk5.default.cyan("\u2192")} Auth: ${import_chalk5.default.bold("API key")} \u2014 consumers send an X-API-Key header
752
- `);
753
- const spinner = (0, import_ora5.default)("Creating proxy (tenant, keys, dev portal)...").start();
823
+ const spinner = !opts.json ? (0, import_ora5.default)("Creating proxy (tenant, keys, dev portal)...").start() : null;
754
824
  let result;
755
825
  try {
756
- result = await createProxy({ name, target_url: targetUrl, auth_type: "api_key" });
757
- spinner.succeed(import_chalk5.default.green("Proxy created!"));
826
+ result = await createProxy({ name, target_url: targetUrl, auth_type: auth, team_id: teamId });
827
+ spinner?.succeed(import_chalk5.default.green("Proxy created!"));
758
828
  } catch (err) {
759
- spinner.fail("Failed to create proxy.");
829
+ spinner?.fail("Failed to create proxy.");
760
830
  throw err;
761
831
  }
762
832
  const version = result.api_version || "1.0.0";
763
833
  const keys = result.api_keys ?? {};
764
834
  const adminKey = keys.dev ?? Object.values(keys)[0];
765
- console.log();
766
- console.log(` ${import_chalk5.default.dim("Proxy URL: ")} ${import_chalk5.default.bold(`https://${name}.apiblaze.com/${version}/dev`)}`);
767
- if (result.devPortal) {
768
- console.log(` ${import_chalk5.default.dim("Dev portal:")} ${import_chalk5.default.bold(stripTenantFromPortal(result.devPortal))}`);
835
+ const proxyUrl = `https://${name}.apiblaze.com/${version}/dev`;
836
+ const devPortal = result.devPortal ? stripTenantFromPortal(result.devPortal) : void 0;
837
+ if (opts.json) {
838
+ process.stdout.write(JSON.stringify({
839
+ project_id: result.project_id,
840
+ api_version: version,
841
+ proxy_url: proxyUrl,
842
+ dev_portal: devPortal,
843
+ api_key: adminKey,
844
+ api_keys: keys,
845
+ team_id: teamId
846
+ }) + "\n");
847
+ return;
769
848
  }
849
+ console.log();
850
+ console.log(` ${import_chalk5.default.dim("Proxy URL: ")} ${import_chalk5.default.bold(proxyUrl)}`);
851
+ if (devPortal) console.log(` ${import_chalk5.default.dim("Dev portal:")} ${import_chalk5.default.bold(devPortal)}`);
770
852
  if (adminKey) {
771
853
  console.log();
772
854
  console.log(` ${import_chalk5.default.dim("Consumer admin API key (dev):")}`);
@@ -784,43 +866,51 @@ async function runCreate() {
784
866
  var import_chalk6 = __toESM(require("chalk"));
785
867
  init_auth();
786
868
  init_api();
787
- async function runTeam() {
869
+ function fail2(message) {
870
+ console.error(import_chalk6.default.red(`Error: ${message}`));
871
+ process.exit(1);
872
+ }
873
+ async function runTeam(arg) {
788
874
  const creds = loadCredentials();
789
875
  if (!creds) {
790
- console.error(import_chalk6.default.red("Not logged in. Run `apiblaze login` first."));
791
- process.exit(1);
876
+ fail2("Not logged in. Run `apiblaze login` first.");
792
877
  }
793
878
  const teams = await getTeams().catch(() => []);
794
879
  if (teams.length === 0) {
795
- console.log(import_chalk6.default.yellow("No teams found for your account."));
796
- return;
797
- }
798
- let teamId;
799
- let teamName;
800
- if (teams.length === 1) {
801
- teamId = teams[0].teamId;
802
- teamName = teams[0].name;
803
- console.log(`${import_chalk6.default.cyan("\u2192")} You only have one team: ${import_chalk6.default.bold(teamName)}`);
804
- } else {
880
+ fail2("No teams found for your account.");
881
+ }
882
+ let chosen;
883
+ if (arg) {
884
+ chosen = teams.find(
885
+ (t) => t.teamId === arg || t.name.toLowerCase() === arg.toLowerCase()
886
+ );
887
+ if (!chosen) {
888
+ fail2(`Team "${arg}" not found. Available: ${teams.map((t) => t.name).join(", ")}.`);
889
+ }
890
+ } else if (teams.length === 1) {
891
+ chosen = teams[0];
892
+ console.log(`${import_chalk6.default.cyan("\u2192")} You only have one team: ${import_chalk6.default.bold(chosen.name)}`);
893
+ } else if (process.stdin.isTTY) {
805
894
  const { default: inquirer2 } = await import("inquirer");
806
- const { chosen } = await inquirer2.prompt([{
895
+ const { picked } = await inquirer2.prompt([{
807
896
  type: "list",
808
- name: "chosen",
897
+ name: "picked",
809
898
  message: "Switch to which team?",
810
899
  default: creds.teamId,
811
900
  choices: teams.map((t) => ({ name: t.name, value: t.teamId }))
812
901
  }]);
813
- teamId = chosen;
814
- teamName = teams.find((t) => t.teamId === chosen)?.name;
902
+ chosen = teams.find((t) => t.teamId === picked);
903
+ } else {
904
+ fail2(`Specify a team: \`apiblaze team <name|id>\`. Available: ${teams.map((t) => t.name).join(", ")}.`);
815
905
  }
816
- saveCredentials({ ...creds, teamId, teamName });
906
+ saveCredentials({ ...creds, teamId: chosen.teamId, teamName: chosen.name });
817
907
  console.log(import_chalk6.default.green(`
818
- \u2714 Active team: ${import_chalk6.default.bold(teamName ?? teamId)}`));
908
+ \u2714 Active team: ${import_chalk6.default.bold(chosen.name)}`));
819
909
  }
820
910
 
821
911
  // src/index.ts
822
912
  var program = new import_commander.Command();
823
- program.name("apiblaze").description("APIblaze dev tunnel CLI").version("0.1.0");
913
+ program.name("apiblaze").description("APIblaze dev tunnel CLI").version("0.1.11");
824
914
  program.command("login").description("Authenticate with APIblaze").action(async () => {
825
915
  try {
826
916
  await runLogin();
@@ -829,17 +919,17 @@ program.command("login").description("Authenticate with APIblaze").action(async
829
919
  process.exit(1);
830
920
  }
831
921
  });
832
- program.command("create").description("Create a new API proxy").action(async () => {
922
+ program.command("create").description("Create a new API proxy").option("--name <name>", "Proxy name (becomes <name>.apiblaze.com)").option("--target <url>", "Target URL to forward requests to").option("--team <id|name>", "Team to create under (defaults to your active team)").option("--auth <type>", "Auth type: api_key | none | oauth", "api_key").option("-y, --yes", "Skip the confirmation prompt").option("--json", "Output machine-readable JSON (non-interactive)").action(async (opts) => {
833
923
  try {
834
- await runCreate();
924
+ await runCreate(opts);
835
925
  } catch (err) {
836
926
  printError(err);
837
927
  process.exit(1);
838
928
  }
839
929
  });
840
- program.command("team").description("Switch the active team").action(async () => {
930
+ program.command("team").description("Switch the active team").argument("[team]", "Team name or id to switch to (omit to choose interactively)").action(async (team) => {
841
931
  try {
842
- await runTeam();
932
+ await runTeam(team);
843
933
  } catch (err) {
844
934
  printError(err);
845
935
  process.exit(1);
@@ -853,9 +943,14 @@ program.command("projects").description("List the projects in your team").action
853
943
  process.exit(1);
854
944
  }
855
945
  });
856
- program.command("dev").description("Start a dev tunnel for your localhost projects").option("-p, --port <number>", "Local port to tunnel", "3000").action(async (opts) => {
946
+ program.command("dev").description("Start a dev tunnel for your localhost projects").argument("[port]", "Local port to tunnel (positional; overrides --port)").option("-p, --port <number>", "Local port to tunnel", "3000").action(async (port, opts) => {
857
947
  try {
858
- await runDev({ port: parseInt(opts.port, 10) });
948
+ const resolved = parseInt(port ?? opts.port, 10);
949
+ if (Number.isNaN(resolved)) {
950
+ console.error(import_chalk7.default.red(`Invalid port: ${port ?? opts.port}`));
951
+ process.exit(1);
952
+ }
953
+ await runDev({ port: resolved });
859
954
  } catch (err) {
860
955
  printError(err);
861
956
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apiblaze",
3
- "version": "0.1.9",
3
+ "version": "0.1.12",
4
4
  "description": "Dev tunnel CLI for APIblaze — route localhost projects through Cloudflare tunnels",
5
5
  "keywords": [
6
6
  "apiblaze",
@@ -32,13 +32,11 @@
32
32
  "chalk": "^4.1.2",
33
33
  "commander": "^11.1.0",
34
34
  "inquirer": "^8.2.6",
35
- "ora": "^5.4.1",
36
- "ws": "^8.17.0"
35
+ "ora": "^5.4.1"
37
36
  },
38
37
  "devDependencies": {
39
38
  "@types/inquirer": "^8.2.10",
40
39
  "@types/node": "^20.0.0",
41
- "@types/ws": "^8.5.10",
42
40
  "tsup": "^8.0.0",
43
41
  "typescript": "^5.4.0"
44
42
  }