@simon-says-build/harbormaster 0.9.0 → 0.9.1
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/harbormaster +24 -3
- package/package.json +1 -1
package/harbormaster
CHANGED
|
@@ -72,6 +72,7 @@ import json
|
|
|
72
72
|
import os
|
|
73
73
|
import signal
|
|
74
74
|
import socket
|
|
75
|
+
import shutil
|
|
75
76
|
import subprocess
|
|
76
77
|
import sys
|
|
77
78
|
import threading
|
|
@@ -80,7 +81,7 @@ import uuid
|
|
|
80
81
|
from datetime import datetime, timezone
|
|
81
82
|
from pathlib import Path
|
|
82
83
|
|
|
83
|
-
VERSION = "0.9.
|
|
84
|
+
VERSION = "0.9.1"
|
|
84
85
|
|
|
85
86
|
# Overridable for tests / parallel installs; default is the shared user daemon.
|
|
86
87
|
# The dir keeps the old "firebase-ports" name: pre-rename binaries still installed
|
|
@@ -88,6 +89,9 @@ VERSION = "0.9.0"
|
|
|
88
89
|
STATE_DIR = Path(os.environ.get("FBPORTS_STATE_DIR",
|
|
89
90
|
str(Path.home() / ".local" / "state" / "firebase-ports")))
|
|
90
91
|
LEDGER = STATE_DIR / "ledger.json"
|
|
92
|
+
# Per-suite TMPDIR for the emulator process (see EmulatorPool._boot).
|
|
93
|
+
def _suite_tmpdir(label):
|
|
94
|
+
return STATE_DIR / "tmp" / str(label)
|
|
91
95
|
BROKER_HOST, BROKER_PORT = "127.0.0.1", int(os.environ.get("FBPORTS_PORT", "4999"))
|
|
92
96
|
# Blocks of 20 ports, 11000-14999 by default. Overridable so an isolated test
|
|
93
97
|
# daemon (FBPORTS_PORT + FBPORTS_STATE_DIR) can also use a DISJOINT pool — its
|
|
@@ -639,6 +643,18 @@ class Broker:
|
|
|
639
643
|
run_ports = {s: all_ports[s] for s in (only or declared) if s in all_ports}
|
|
640
644
|
check = {s: p for s, p in run_ports.items() if s not in ("ui", "hub", "logging")}
|
|
641
645
|
log = STATE_DIR / f"{label}.log"
|
|
646
|
+
# Each suite gets its OWN temp dir. firebase-tools keeps the Storage
|
|
647
|
+
# emulator's blobs at `${os.tmpdir()}/firebase/storage/blobs` — one
|
|
648
|
+
# fixed path for every instance on the box — and `stop()` rm -rf's it.
|
|
649
|
+
# So any suite shutting down wiped the blobs of every other suite still
|
|
650
|
+
# running, and their next upload died with ENOENT inside firebase-tools
|
|
651
|
+
# ("An unexpected error has occurred.", exit 2, all emulators gone).
|
|
652
|
+
# On a box that runs several suites at once that is not a corner case:
|
|
653
|
+
# 2026-08-28, three walk2gether e2e runs in a row on the studio, each
|
|
654
|
+
# killed by a daemon verify drive finishing. os.tmpdir() honours
|
|
655
|
+
# TMPDIR, so a per-suite one makes the path private.
|
|
656
|
+
suite_tmp = _suite_tmpdir(label)
|
|
657
|
+
suite_tmp.mkdir(parents=True, exist_ok=True)
|
|
642
658
|
cmd = ["firebase", "emulators:start", "--config", tmp]
|
|
643
659
|
if only:
|
|
644
660
|
cmd += ["--only", ",".join(only)]
|
|
@@ -648,9 +664,9 @@ class Broker:
|
|
|
648
664
|
# daemon's env — the functions emulator runtime inherits it, which is
|
|
649
665
|
# the only way a CALLER can set per-suite env (e.g. LLM_CACHE_DIR)
|
|
650
666
|
# when the daemon, not the caller, spawns the emulator.
|
|
651
|
-
spawn_env =
|
|
667
|
+
spawn_env = {**os.environ, "TMPDIR": str(suite_tmp)}
|
|
652
668
|
if extra_env:
|
|
653
|
-
spawn_env
|
|
669
|
+
spawn_env.update({str(k): str(v) for k, v in dict(extra_env).items()})
|
|
654
670
|
with open(log, "w") as lf:
|
|
655
671
|
proc = subprocess.Popen(cmd, cwd=cwd, stdout=lf, stderr=subprocess.STDOUT, start_new_session=True, env=spawn_env)
|
|
656
672
|
if on_spawn:
|
|
@@ -770,6 +786,11 @@ class Broker:
|
|
|
770
786
|
tmp = rec.get("tmp_config")
|
|
771
787
|
if tmp and rec.get("cwd") and (Path(rec["cwd"]) / tmp).exists():
|
|
772
788
|
(Path(rec["cwd"]) / tmp).unlink()
|
|
789
|
+
# The suite's private TMPDIR (see _boot). Derived from the label the
|
|
790
|
+
# same way _boot derived it, so old ledger records need no new field.
|
|
791
|
+
label = rec.get("label") or (Path(rec["log"]).stem if rec.get("log") else None)
|
|
792
|
+
if label:
|
|
793
|
+
shutil.rmtree(_suite_tmpdir(label), ignore_errors=True)
|
|
773
794
|
if rec.get("sim_udid"):
|
|
774
795
|
gone = _sim_teardown(rec["sim_udid"])
|
|
775
796
|
actions.append(f"sim {rec['sim_udid'][:8]} " +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simon-says-build/harbormaster",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "A localhost daemon that leases each caller its own port block — isolated Firebase emulator sandboxes, Metro/Expo ports, private iOS simulators — reaped when the caller exits. No port collisions, no shared instances, no pkill.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"harbormaster": "harbormaster",
|