@cliftonc/cfld 0.2.1 → 0.2.2

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 (3) hide show
  1. package/README.md +2 -0
  2. package/dist/cli.js +43 -36
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # cfld
2
2
 
3
+ **`cfld` = cloudflare local dev.**
4
+
3
5
  **One command to give your localhost a permanent public HTTPS URL — no API tokens, no teardown.**
4
6
 
5
7
  `cfld` wraps [`cloudflared`](https://github.com/cloudflare/cloudflared) into a frictionless dev experience: the **same** custom URL every run (not a random `trycloudflare.com` one), authorized with a **browser login** (zero API tokens), reusing the same named tunnel and DNS record across restarts instead of churning them. It runs a **live dashboard** (URL, connection count, request/latency counters, tailing logs), **auto-reconnects** on edge blips, and **guards** against stealing a DNS record that belongs to another tunnel.
package/dist/cli.js CHANGED
@@ -700,9 +700,20 @@ function startChild(options) {
700
700
  const exited = new Promise((resolve) => {
701
701
  markExited = resolve;
702
702
  });
703
+ const trackedPgids = /* @__PURE__ */ new Set();
704
+ const trackTree = () => {
705
+ const pid = child.pid;
706
+ if (pid === void 0) return;
707
+ for (const g of descendantPgids(pid)) {
708
+ if (g > 1 && g !== process.pid) trackedPgids.add(g);
709
+ }
710
+ };
711
+ const poll = setInterval(trackTree, options.pollTreeMs ?? 1500);
712
+ poll.unref();
703
713
  wireLines2(child, "stdout", options.onLine);
704
714
  wireLines2(child, "stderr", options.onLine);
705
715
  child.on("error", (err) => {
716
+ clearInterval(poll);
706
717
  markExited();
707
718
  if (stopped) return;
708
719
  stopped = true;
@@ -710,11 +721,18 @@ function startChild(options) {
710
721
  options.onLine?.(String(err.message ?? err), "stderr");
711
722
  });
712
723
  child.on("exit", (code, signal) => {
724
+ clearInterval(poll);
713
725
  markExited();
714
726
  if (stopped) return;
715
727
  stopped = true;
716
728
  options.onExit?.(code, signal);
717
729
  });
730
+ const signalTree = (signal) => {
731
+ trackTree();
732
+ const pid = child.pid;
733
+ if (pid !== void 0) trySignalGroup(pid, signal);
734
+ for (const g of trackedPgids) trySignalGroup(g, signal);
735
+ };
718
736
  return {
719
737
  exited,
720
738
  get pid() {
@@ -723,63 +741,64 @@ function startChild(options) {
723
741
  stop() {
724
742
  if (stopped) return;
725
743
  stopped = true;
726
- killGroup(child, "SIGINT");
744
+ clearInterval(poll);
745
+ signalTree("SIGINT");
727
746
  const t = setTimeout(() => {
728
- if (child.exitCode === null && !child.signalCode) killGroup(child, "SIGKILL");
747
+ if (child.exitCode === null && !child.signalCode) signalTree("SIGKILL");
729
748
  }, 4e3);
730
749
  t.unref();
731
750
  },
732
751
  kill() {
733
- killGroup(child, "SIGKILL");
752
+ clearInterval(poll);
753
+ signalTree("SIGKILL");
734
754
  }
735
755
  };
736
756
  }
737
- function descendantPids(pid) {
757
+ function descendantPgids(pid) {
738
758
  let out = "";
739
759
  try {
740
- out = execSync("ps -A -o pid=,ppid=", { encoding: "utf8" });
760
+ out = execSync("ps -A -o pid=,ppid=,pgid=", { encoding: "utf8" });
741
761
  } catch {
742
762
  return [];
743
763
  }
744
764
  const childrenOf = /* @__PURE__ */ new Map();
765
+ const pgidOf = /* @__PURE__ */ new Map();
745
766
  for (const line of out.split("\n")) {
746
- const m = line.trim().match(/^(\d+)\s+(\d+)$/);
767
+ const m = line.trim().match(/^(\d+)\s+(\d+)\s+(\d+)$/);
747
768
  if (!m) continue;
748
- const child = Number(m[1]);
769
+ const proc = Number(m[1]);
749
770
  const parent = Number(m[2]);
771
+ pgidOf.set(proc, Number(m[3]));
750
772
  const list = childrenOf.get(parent);
751
- if (list) list.push(child);
752
- else childrenOf.set(parent, [child]);
773
+ if (list) list.push(proc);
774
+ else childrenOf.set(parent, [proc]);
753
775
  }
754
- const found = [];
776
+ const pgids = /* @__PURE__ */ new Set();
777
+ const seen = /* @__PURE__ */ new Set();
755
778
  const stack = [pid];
756
779
  while (stack.length) {
757
780
  for (const child of childrenOf.get(stack.pop()) ?? []) {
758
- if (!found.includes(child)) {
759
- found.push(child);
760
- stack.push(child);
761
- }
781
+ if (seen.has(child)) continue;
782
+ seen.add(child);
783
+ stack.push(child);
784
+ const g = pgidOf.get(child);
785
+ if (g !== void 0) pgids.add(g);
762
786
  }
763
787
  }
764
- return found;
788
+ return [...pgids];
765
789
  }
766
- function signalPids(pids, signal) {
767
- for (const pid of pids) {
768
- try {
769
- process.kill(pid, signal);
770
- } catch {
771
- }
790
+ function trySignalGroup(pgid, signal) {
791
+ try {
792
+ process.kill(-pgid, signal);
793
+ } catch {
772
794
  }
773
795
  }
774
796
  function reapChild(child, graceMs = 3e3) {
775
- const tree = child.pid ? descendantPids(child.pid) : [];
776
797
  child.stop();
777
- signalPids(tree, "SIGINT");
778
798
  return new Promise((resolve) => {
779
799
  const done2 = () => {
780
800
  clearTimeout(grace);
781
801
  child.kill();
782
- signalPids(tree, "SIGKILL");
783
802
  resolve();
784
803
  };
785
804
  const grace = setTimeout(done2, graceMs);
@@ -787,18 +806,6 @@ function reapChild(child, graceMs = 3e3) {
787
806
  child.exited.then(done2);
788
807
  });
789
808
  }
790
- function killGroup(child, signal) {
791
- const pid = child.pid;
792
- if (pid === void 0) return;
793
- try {
794
- process.kill(-pid, signal);
795
- } catch {
796
- try {
797
- child.kill(signal);
798
- } catch {
799
- }
800
- }
801
- }
802
809
  function wireLines2(child, stream, onLine) {
803
810
  const source = stream === "stdout" ? child.stdout : child.stderr;
804
811
  if (!source || !onLine) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cliftonc/cfld",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Zero-token, persistent Cloudflare tunnel CLI for local dev — same public HTTPS URL every run.",
5
5
  "type": "module",
6
6
  "bin": {