@simon-says-build/harbormaster 0.14.0 → 0.14.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.
- package/README.md +19 -0
- package/harbormaster +32 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -155,6 +155,25 @@ env, under the caller's explicit `--fn-env`. `exec`: on top of the caller's
|
|
|
155
155
|
ambient env, so a key exported in some shell can't leak into the suite.
|
|
156
156
|
`python3 test/project_env.py`.
|
|
157
157
|
|
|
158
|
+
## A function env value can name a leased port (0.14.2)
|
|
159
|
+
|
|
160
|
+
`--fn-env KEY={port:firestore}`, or the same in `harbormaster.env`, becomes
|
|
161
|
+
this lease's firestore port, inside a value too (`127.0.0.1:{port:auth}`). A
|
|
162
|
+
caller can't know its ports before the lease, and an app whose functions read
|
|
163
|
+
their own variable for the emulator (Tapuat's `EMULATOR_PORT_FIRESTORE ||
|
|
164
|
+
"8080"`) otherwise talks to whatever holds the default. An unknown service is
|
|
165
|
+
left as written. `python3 test/port_env.py`.
|
|
166
|
+
|
|
167
|
+
## A lease never reads the hosting config it doesn't start (0.14.1)
|
|
168
|
+
|
|
169
|
+
A lease whose `--only` leaves hosting out boots from a config with no
|
|
170
|
+
`hosting` block. firebase-tools 15.33 reads `hosting` even when it starts no
|
|
171
|
+
hosting emulator, and a web-framework target (`"source": "."`, Next.js)
|
|
172
|
+
makes it refuse every emulator without the `webframeworks` experiment.
|
|
173
|
+
Tapuat's lease on simon-linux-1 died there on 2026-10-21; 15.28 on the
|
|
174
|
+
studio never checked. An `--only` that asks for hosting, or no `--only`,
|
|
175
|
+
keeps it. `python3 test/injected_config.py`.
|
|
176
|
+
|
|
158
177
|
## An idle simulator nobody leases is shut down (0.13.0)
|
|
159
178
|
|
|
160
179
|
The world sweep (every `FBPORTS_SWEEP_INTERVAL`, 5 min) now shuts down a
|
package/harbormaster
CHANGED
|
@@ -84,7 +84,7 @@ import uuid
|
|
|
84
84
|
from datetime import datetime, timezone
|
|
85
85
|
from pathlib import Path
|
|
86
86
|
|
|
87
|
-
VERSION = "0.14.
|
|
87
|
+
VERSION = "0.14.2"
|
|
88
88
|
|
|
89
89
|
# Overridable for tests / parallel installs; default is the shared user daemon.
|
|
90
90
|
# The dir keeps the old "firebase-ports" name: pre-rename binaries still installed
|
|
@@ -361,11 +361,35 @@ def _service_ports(ports):
|
|
|
361
361
|
return {s: p for s, p in ports.items() if s not in ("ui", "hub", "logging")}
|
|
362
362
|
|
|
363
363
|
|
|
364
|
-
|
|
364
|
+
# A value that names a LEASED port: `{port:firestore}` becomes this lease's
|
|
365
|
+
# firestore port. The caller cannot know its ports before the lease, and an
|
|
366
|
+
# app whose functions read their own variable for the emulator (Tapuat's
|
|
367
|
+
# admin.ts: `127.0.0.1:${EMULATOR_PORT_FIRESTORE || "8080"}`, unconditionally)
|
|
368
|
+
# otherwise talks to whatever holds the default — on simon-linux-1, nothing;
|
|
369
|
+
# every callable 500'd (run 20261021-131438-exploration-everyday-user). An
|
|
370
|
+
# unknown service is left as written, so a typo shows in the app's own error.
|
|
371
|
+
_PORT_REF = re.compile(r"\{port:([a-z_]+)\}")
|
|
372
|
+
|
|
373
|
+
def _with_ports(env, ports):
|
|
374
|
+
return {k: _PORT_REF.sub(lambda m: str(ports[m.group(1)]) if m.group(1) in ports else m.group(0), v)
|
|
375
|
+
for k, v in env.items()}
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def _write_injected_config(cwd, ports, only=None):
|
|
365
379
|
"""Set ports on the emulators the portless firebase.json DECLARES (+ hub/logging).
|
|
366
|
-
Never adds emulators the project doesn't use. Returns the temp config basename.
|
|
380
|
+
Never adds emulators the project doesn't use. Returns the temp config basename.
|
|
381
|
+
|
|
382
|
+
With an --only that leaves hosting out, the `hosting` block goes too: no
|
|
383
|
+
emulator a lease starts serves it, and firebase-tools still READS it. A
|
|
384
|
+
web-framework target (`"source": "."`, a Next.js app) makes 15.33 refuse to
|
|
385
|
+
start ANY emulator without the webframeworks experiment — Tapuat's run
|
|
386
|
+
20261021-130009-exploration-everyday-user died there on simon-linux-1 while
|
|
387
|
+
15.28 on the studio never checked."""
|
|
367
388
|
cfg = json.loads((Path(cwd) / "firebase.json").read_text())
|
|
368
389
|
emu = cfg.setdefault("emulators", {})
|
|
390
|
+
if only and "hosting" not in only:
|
|
391
|
+
cfg.pop("hosting", None)
|
|
392
|
+
emu.pop("hosting", None)
|
|
369
393
|
for svc in [k for k in emu if k != "singleProjectMode"]:
|
|
370
394
|
if svc in ports and isinstance(emu[svc], dict):
|
|
371
395
|
emu[svc]["port"] = ports[svc]
|
|
@@ -775,7 +799,7 @@ class Broker:
|
|
|
775
799
|
|
|
776
800
|
def _boot(self, cwd, block, only, project_id, label, on_spawn=None, extra_env=None):
|
|
777
801
|
all_ports = _block_ports(block)
|
|
778
|
-
tmp = _write_injected_config(cwd, all_ports)
|
|
802
|
+
tmp = _write_injected_config(cwd, all_ports, only)
|
|
779
803
|
# Readiness/liveness track ONLY the data services actually started (respect
|
|
780
804
|
# --only, else the project's declared emulators; skip ui/hub/logging) — else
|
|
781
805
|
# we'd wait forever for ports (functions, pubsub, …) that never open.
|
|
@@ -804,9 +828,9 @@ class Broker:
|
|
|
804
828
|
# daemon's env — the functions emulator runtime inherits it, which is
|
|
805
829
|
# the only way a CALLER can set per-suite env (e.g. LLM_CACHE_DIR)
|
|
806
830
|
# when the daemon, not the caller, spawns the emulator.
|
|
807
|
-
spawn_env = {**os.environ, **project_env(cwd), "TMPDIR": str(suite_tmp)}
|
|
831
|
+
spawn_env = {**os.environ, **_with_ports(project_env(cwd), all_ports), "TMPDIR": str(suite_tmp)}
|
|
808
832
|
if extra_env:
|
|
809
|
-
spawn_env.update({str(k): str(v) for k, v in dict(extra_env).items()})
|
|
833
|
+
spawn_env.update(_with_ports({str(k): str(v) for k, v in dict(extra_env).items()}, all_ports))
|
|
810
834
|
with open(log, "w") as lf:
|
|
811
835
|
proc = subprocess.Popen(cmd, cwd=cwd, stdout=lf, stderr=subprocess.STDOUT, start_new_session=True, env=spawn_env)
|
|
812
836
|
if on_spawn:
|
|
@@ -1841,7 +1865,7 @@ def main():
|
|
|
1841
1865
|
me = os.getpid() # this process owns the reservation for the run
|
|
1842
1866
|
res = ok(_call("POST", "/allocate", {"owner_pid": me, "owner_start": _proc_start(me), "label": "exec"}), "exec")
|
|
1843
1867
|
base, lid = res["base"], res["leaseId"]
|
|
1844
|
-
tmp = _write_injected_config(cwd, _block_ports(base))
|
|
1868
|
+
tmp = _write_injected_config(cwd, _block_ports(base), only)
|
|
1845
1869
|
try:
|
|
1846
1870
|
run = ["firebase", "emulators:exec", "--config", tmp]
|
|
1847
1871
|
if only:
|
|
@@ -1850,7 +1874,7 @@ def main():
|
|
|
1850
1874
|
# The project's env beats the caller's AMBIENT env here: a key
|
|
1851
1875
|
# exported in some shell for a one-off script is exactly what
|
|
1852
1876
|
# harbormaster.env exists to keep out of the suite (project_env).
|
|
1853
|
-
rc = subprocess.call(run, cwd=cwd, env={**os.environ, **project_env(cwd)})
|
|
1877
|
+
rc = subprocess.call(run, cwd=cwd, env={**os.environ, **_with_ports(project_env(cwd), _block_ports(base))})
|
|
1854
1878
|
finally:
|
|
1855
1879
|
try:
|
|
1856
1880
|
_call("POST", "/release", {"leaseId": lid})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simon-says-build/harbormaster",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.2",
|
|
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",
|