Merge pull request #1982 from debpalash/fix/1974-dev-port-ownership
fix(dev): reclaim the port from a backend the app itself left running
This commit is contained in:
@@ -9,6 +9,7 @@ the frozen-backend fallback mirror it for their toolchains.
|
||||
## [Unreleased]
|
||||
|
||||
**Highlights**
|
||||
- `bun run desktop` reclaims port 3900 from a backend the app itself left running, instead of refusing to start (#1974)
|
||||
- A dictation shortcut another app already owns now says so, instead of silently doing nothing (#1858)
|
||||
- Quitting on Windows is no longer reported as a crash on the next launch (#1898)
|
||||
- A Reduce motion switch in Settings, for calm without changing your whole system (#1857)
|
||||
|
||||
@@ -76,6 +76,22 @@ function normalized(value, windows = process.platform === "win32") {
|
||||
return path.posix.resolve(String(value || ""));
|
||||
}
|
||||
|
||||
// The app's own reverse-DNS identity (tauri.conf.json `identifier`). A backend
|
||||
// the Tauri shell spawned lives under a per-app directory named after this —
|
||||
// `…/com.debpalash.omnivoice-studio/project/.venv/…` — so its path names
|
||||
// VoiceStudio as unambiguously as the checkout path does, just from the other
|
||||
// direction.
|
||||
//
|
||||
// Without this the ownership test only recognised a listener running out of
|
||||
// the git checkout, so an app-managed backend left holding the port was
|
||||
// treated as a stranger and the launcher refused to reclaim it — the run
|
||||
// aborted with "Refusing to stop unrelated process" and no way forward
|
||||
// except Task Manager (#1974).
|
||||
//
|
||||
// A reverse-DNS bundle id is specific enough to be safe here: nothing else
|
||||
// on the machine carries it, which is the whole point of the namespace.
|
||||
export const APP_BUNDLE_ID = "com.debpalash.omnivoice-studio";
|
||||
|
||||
export function belongsToCheckout(
|
||||
cwd,
|
||||
command,
|
||||
@@ -91,6 +107,11 @@ export function belongsToCheckout(
|
||||
return path === root || path.startsWith(prefix);
|
||||
};
|
||||
if (ownedPath(cwd) || ownedPath(executable)) return true;
|
||||
// App-managed backend: the bundle id appears in the executable path or in
|
||||
// the command line, whichever the platform gave us.
|
||||
for (const value of [cwd, executable, command]) {
|
||||
if (String(value || "").toLowerCase().includes(APP_BUNDLE_ID)) return true;
|
||||
}
|
||||
const haystack = windows
|
||||
? String(command || "")
|
||||
.replaceAll("/", "\\")
|
||||
|
||||
@@ -2,6 +2,7 @@ import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import {
|
||||
APP_BUNDLE_ID,
|
||||
belongsToCheckout,
|
||||
clearDevPortsWith,
|
||||
isUninspectableProcessError,
|
||||
@@ -199,3 +200,39 @@ test("windows stop fails when Terminate reports a non-zero ReturnValue", () => {
|
||||
/Could not stop process 4242: Terminate returned 2/,
|
||||
);
|
||||
});
|
||||
|
||||
// #1974: a backend the Tauri shell spawned lives under a per-app directory
|
||||
// named after the bundle id, not under the git checkout. The ownership test
|
||||
// only knew about the checkout, so the launcher treated its own orphaned
|
||||
// backend as a stranger, refused to reclaim port 3900, and aborted the run
|
||||
// with no way forward but Task Manager.
|
||||
test("an app-managed backend is recognised as ours", () => {
|
||||
const root = "/work/VoiceStudio";
|
||||
const macApp = `/Users/x/Library/Application Support/${APP_BUNDLE_ID}/project/.venv/bin/python`;
|
||||
assert.equal(belongsToCheckout("", "", macApp, false, root), true);
|
||||
assert.equal(belongsToCheckout("", `${macApp} -m uvicorn main:app`, "", false, root), true);
|
||||
|
||||
// Built by join so the Windows separators need no escaping in source.
|
||||
const sep = String.fromCharCode(92);
|
||||
const winApp = ["C:", "Users", "x", "AppData", "Roaming", APP_BUNDLE_ID, "project", ".venv", "Scripts", "python.exe"].join(sep);
|
||||
assert.equal(belongsToCheckout("", "", winApp, true, ["C:", "repo"].join(sep)), true);
|
||||
});
|
||||
|
||||
test("the bundle id match is case-insensitive", () => {
|
||||
// Windows paths come back with inconsistent casing depending on the API.
|
||||
const root = "/work/VoiceStudio";
|
||||
const shouty = `/Users/x/Library/Application Support/${APP_BUNDLE_ID.toUpperCase()}/project/.venv/bin/python`;
|
||||
assert.equal(belongsToCheckout("", "", shouty, false, root), true);
|
||||
});
|
||||
|
||||
test("a foreign process is still refused", () => {
|
||||
// The guard exists to avoid killing someone else's service on the port;
|
||||
// widening ownership must not widen it to everything.
|
||||
const root = "/work/VoiceStudio";
|
||||
assert.equal(belongsToCheckout("", "python -m http.server 3900", "", false, root), false);
|
||||
assert.equal(belongsToCheckout("", "", "/usr/bin/python3", false, root), false);
|
||||
assert.equal(
|
||||
belongsToCheckout("", "node /opt/com.someoneelse.app/server.js", "", false, root),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user