@dutodev/cli 0.4.5 → 0.4.6

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.
@@ -373,6 +373,7 @@ async function withFullscreen(stdout, run2) {
373
373
  const onSignal = () => ac.abort();
374
374
  process.on("SIGINT", onSignal);
375
375
  process.on("SIGTERM", onSignal);
376
+ process.on("SIGHUP", onSignal);
376
377
  process.once("exit", restore);
377
378
  stdout.write(ENTER_SCREEN);
378
379
  try {
@@ -380,6 +381,7 @@ async function withFullscreen(stdout, run2) {
380
381
  } finally {
381
382
  process.off("SIGINT", onSignal);
382
383
  process.off("SIGTERM", onSignal);
384
+ process.off("SIGHUP", onSignal);
383
385
  process.off("exit", restore);
384
386
  restore();
385
387
  }
package/dist/index.js CHANGED
@@ -33,7 +33,7 @@ var fail = (msg) => `${paint("err", "\u2716", process.stderr)} ${msg}`;
33
33
 
34
34
  // src/index.ts
35
35
  import { hostname } from "os";
36
- import { relative } from "path";
36
+ import { join as join5, relative } from "path";
37
37
  import cac from "cac";
38
38
 
39
39
  // src/api.ts
@@ -538,8 +538,18 @@ function waitForService(res, port, checkMs, pingMs) {
538
538
  async function startProxy(o) {
539
539
  const max = o.maxBody ?? MAX_BODY;
540
540
  let nextId = 1;
541
+ const routeWaitMs = o.routeWaitMs ?? 3e3;
542
+ async function routeFor(req) {
543
+ const host = hostOf(req);
544
+ for (let waited = 0; ; waited += 50) {
545
+ const route = o.routes.get(host);
546
+ if (route || waited >= routeWaitMs) return route;
547
+ await new Promise((r) => setTimeout(r, 50));
548
+ }
549
+ }
541
550
  const server = createServer(async (req, res) => {
542
- const route = o.routes.get(hostOf(req));
551
+ req.pause();
552
+ const route = await routeFor(req);
543
553
  if (!route) {
544
554
  res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
545
555
  res.end("T\xFAnel n\xE3o encontrado nesta sess\xE3o");
@@ -619,7 +629,7 @@ async function startProxy(o) {
619
629
  });
620
630
  server.on("upgrade", async (req, client, head) => {
621
631
  client.on("error", () => client.destroy());
622
- const route = o.routes.get(hostOf(req));
632
+ const route = await routeFor(req);
623
633
  if (!route) return void client.end("HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n");
624
634
  const id = nextId++;
625
635
  const started = Date.now();
@@ -684,6 +694,74 @@ async function startProxy(o) {
684
694
  };
685
695
  }
686
696
 
697
+ // src/session/orphans.ts
698
+ import { spawnSync } from "child_process";
699
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync3, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
700
+ import { join as join4 } from "path";
701
+ var fileOf = (dir, cfPid) => join4(dir, `cloudflared-${cfPid}.json`);
702
+ function recordCloudflared(dir, cfPid, sessionPid = process.pid) {
703
+ try {
704
+ mkdirSync3(dir, { recursive: true });
705
+ writeFileSync3(fileOf(dir, cfPid), JSON.stringify({ sessionPid, cfPid, at: Date.now() }));
706
+ } catch {
707
+ }
708
+ }
709
+ function forgetCloudflared(dir, cfPid) {
710
+ rmSync2(fileOf(dir, cfPid), { force: true });
711
+ }
712
+ var realProc = {
713
+ alive(pid) {
714
+ try {
715
+ process.kill(pid, 0);
716
+ return true;
717
+ } catch (err) {
718
+ return err.code === "EPERM";
719
+ }
720
+ },
721
+ isCloudflared(pid) {
722
+ try {
723
+ if (process.platform === "linux") return readFileSync3(`/proc/${pid}/cmdline`, "utf8").includes("cloudflared");
724
+ if (process.platform === "darwin") return spawnSync("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf8" }).stdout.includes("cloudflared");
725
+ } catch {
726
+ return false;
727
+ }
728
+ return false;
729
+ },
730
+ kill(pid) {
731
+ try {
732
+ process.kill(pid, "SIGTERM");
733
+ } catch {
734
+ }
735
+ }
736
+ };
737
+ function reapOrphans(dir, proc = realProc) {
738
+ if (!existsSync4(dir)) return 0;
739
+ let killed = 0;
740
+ for (const name of readdirSync(dir)) {
741
+ if (!/^cloudflared-\d+\.json$/.test(name)) continue;
742
+ const path = join4(dir, name);
743
+ let rec;
744
+ try {
745
+ rec = JSON.parse(readFileSync3(path, "utf8"));
746
+ } catch {
747
+ rmSync2(path, { force: true });
748
+ continue;
749
+ }
750
+ const { sessionPid, cfPid } = rec;
751
+ if (typeof sessionPid !== "number" || typeof cfPid !== "number") {
752
+ rmSync2(path, { force: true });
753
+ continue;
754
+ }
755
+ if (proc.alive(sessionPid)) continue;
756
+ if (proc.alive(cfPid) && proc.isCloudflared(cfPid)) {
757
+ proc.kill(cfPid);
758
+ killed++;
759
+ }
760
+ rmSync2(path, { force: true });
761
+ }
762
+ return killed;
763
+ }
764
+
687
765
  // src/session/records.ts
688
766
  var RecordStore = class {
689
767
  constructor(max = 500) {
@@ -765,6 +843,7 @@ var Session = class {
765
843
  }
766
844
  /** Abre os túneis em sequência; falhas ficam no estado e não interrompem os outros. */
767
845
  async start(specs) {
846
+ if (this.o.runDir) reapOrphans(this.o.runDir);
768
847
  this.proxy = await startProxy({ routes: this.routes, store: this.records });
769
848
  this.tunnels = specs.map((s) => this.blank(s, false));
770
849
  this.emit();
@@ -853,6 +932,11 @@ var Session = class {
853
932
  stdio: ["ignore", "ignore", "pipe"]
854
933
  });
855
934
  this.child = c;
935
+ const runDir = this.o.runDir;
936
+ if (runDir && c.pid) {
937
+ recordCloudflared(runDir, c.pid);
938
+ c.once("exit", () => forgetCloudflared(runDir, c.pid));
939
+ }
856
940
  let buf = "";
857
941
  c.stderr?.on("data", (chunk) => {
858
942
  if (c !== this.child) return;
@@ -1010,21 +1094,23 @@ async function runSession(specs, source, flags) {
1010
1094
  } catch (err) {
1011
1095
  fail2(err instanceof Error ? err.message : String(err));
1012
1096
  }
1013
- const session = new Session({ api, deviceId: config.deviceId, deviceName: hostname(), cloudflaredPath, source });
1097
+ const session = new Session({ api, deviceId: config.deviceId, deviceName: hostname(), cloudflaredPath, source, runDir: join5(dutoHome(), "run") });
1014
1098
  if (wantsUi(flags)) {
1015
- const { runFullscreen } = await import("./fullscreen-5437X7MP.js");
1016
- process.exit(await runFullscreen(session, specs, "0.4.5"));
1099
+ const { runFullscreen } = await import("./fullscreen-EPWB53EP.js");
1100
+ process.exit(await runFullscreen(session, specs, "0.4.6"));
1017
1101
  }
1018
- console.log(`${mark()} ${paint("muted", `${"0.4.5"} \xB7 ${source} \xB7 Ctrl+C para encerrar`)}`);
1102
+ console.log(`${mark()} ${paint("muted", `${"0.4.6"} \xB7 ${source} \xB7 Ctrl+C para encerrar`)}`);
1019
1103
  const stopLog = runPlain(session, (l) => console.log(l));
1020
1104
  const stopped = new Promise((resolve) => {
1021
1105
  const onSignal = () => {
1022
1106
  process.off("SIGINT", onSignal);
1023
1107
  process.off("SIGTERM", onSignal);
1108
+ process.off("SIGHUP", onSignal);
1024
1109
  resolve();
1025
1110
  };
1026
1111
  process.on("SIGINT", onSignal);
1027
1112
  process.on("SIGTERM", onSignal);
1113
+ process.on("SIGHUP", onSignal);
1028
1114
  });
1029
1115
  await Promise.race([session.start(specs), stopped]);
1030
1116
  await stopped;
@@ -1102,7 +1188,7 @@ cli.help(
1102
1188
  body: s.body.replace(/^\s*For more info, run any command with the `--help` flag:/m, " Mais detalhes de um comando: duto <comando> --help").replace("Display this message", "Mostra esta ajuda").replace("Display version number", "Mostra a vers\xE3o")
1103
1189
  }))
1104
1190
  );
1105
- cli.version("0.4.5");
1191
+ cli.version("0.4.6");
1106
1192
  try {
1107
1193
  cli.parse(process.argv, { run: false });
1108
1194
  await cli.runMatchedCommand();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dutodev/cli",
3
- "version": "0.4.5",
3
+ "version": "0.4.6",
4
4
  "description": "Exponha uma porta local em https://<nome>-<usuário>.duto.dev com um comando.",
5
5
  "license": "MIT",
6
6
  "type": "module",