fix(portless): fall forward when branch route is occupied

This commit is contained in:
Tommaso Casaburi
2026-03-31 17:42:37 +07:00
parent 315505fb8d
commit 6a3040dc55
2 changed files with 31 additions and 6 deletions
+1 -1
View File
@@ -75,5 +75,5 @@ If uncertain, ask the developer before adding an entry.
- **Context:** Starting `yarn start` in one 5chan worktree while another 5chan worktree was already serving through Portless
- **What was surprising:** Using the literal Portless app name `5chan` in every worktree makes the route itself collide, even when the backing ports are different, so the second process fails with `"5chan.localhost" is already registered`.
- **Impact:** Parallel 5chan branches can block each other even though Portless is meant to let them coexist safely.
- **Mitigation:** Keep Portless startup behind `scripts/start-dev.js`, which now uses a branch-scoped `*.5chan.localhost:1355` route outside the canonical case and falls back to a branch-scoped route when the bare `5chan.localhost` name is already occupied.
- **Mitigation:** Keep Portless startup behind `scripts/start-dev.js`, which now uses a branch-scoped `*.5chan.localhost:1355` route outside the canonical case and automatically increments a `-2`, `-3`, ... suffix when that branch-scoped route is already occupied.
- **Status:** confirmed
+30 -5
View File
@@ -36,7 +36,7 @@ function getCurrentBranch() {
return branch || null;
}
function isCanonicalRouteBusy() {
function getActivePortlessRoutes() {
const result = spawnSync(portlessBin, ['list'], {
cwd: process.cwd(),
encoding: 'utf8',
@@ -44,13 +44,19 @@ function isCanonicalRouteBusy() {
});
if (result.status !== 0) {
return false;
return new Set();
}
return result.stdout.includes('http://5chan.localhost:1355');
const matches = result.stdout.match(/http:\/\/[a-z0-9.-]+\.localhost:1355/g) || [];
return new Set(matches);
}
function getPortlessAppName() {
function isRouteBusy(activeRoutes, appName) {
return activeRoutes.has(`http://${appName}.localhost:1355`);
}
function getPreferredPortlessAppName(activeRoutes) {
const branch = getCurrentBranch();
const branchLabel = sanitizeLabel(branch || 'current');
@@ -58,13 +64,32 @@ function getPortlessAppName() {
return `${branchLabel}.5chan`;
}
if (isCanonicalRouteBusy()) {
if (isRouteBusy(activeRoutes, '5chan')) {
return `${branchLabel}.5chan`;
}
return '5chan';
}
function getPortlessAppName() {
const activeRoutes = getActivePortlessRoutes();
const preferredAppName = getPreferredPortlessAppName(activeRoutes);
if (!isRouteBusy(activeRoutes, preferredAppName)) {
return preferredAppName;
}
for (let suffix = 2; suffix < 1000; suffix += 1) {
const candidate = `${preferredAppName}-${suffix}`;
if (!isRouteBusy(activeRoutes, candidate)) {
return candidate;
}
}
return `${preferredAppName}-${Date.now()}`;
}
const command = usePortless && existsSync(portlessBin) ? portlessBin : viteBin;
let args;
let publicUrl = null;