From a70d7d1842d2f418c8a9cf7010829715b4a236d4 Mon Sep 17 00:00:00 2001 From: kiyo-e Date: Thu, 6 Aug 2026 14:06:24 +0900 Subject: [PATCH 1/2] fix: honor run-local port option --- packages/workshop-frontend/src/main.tsx | 6 ++++-- run-dev-server.js | 8 ++++++++ scripts/run-local.mjs | 17 +++++++++++++---- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/packages/workshop-frontend/src/main.tsx b/packages/workshop-frontend/src/main.tsx index 9854d9d0..58ac93e7 100644 --- a/packages/workshop-frontend/src/main.tsx +++ b/packages/workshop-frontend/src/main.tsx @@ -63,8 +63,10 @@ function getBackendHost(): string { if (backendHost) return backendHost; // When opening the Vite dev server directly (localhost:3000), the backend is at localhost:8787. - // Otherwise, the API is on the same host as the frontend. - return window.location.hostname === 'localhost' ? 'localhost:8787' : window.location.host; + // Otherwise, including run-local, the API is on the same host as the frontend. + return window.location.hostname === 'localhost' && window.location.port === '3000' + ? 'localhost:8787' + : window.location.host; } function startConnection(): RpcStub { diff --git a/run-dev-server.js b/run-dev-server.js index a5aba890..7a60cfa9 100644 --- a/run-dev-server.js +++ b/run-dev-server.js @@ -199,6 +199,14 @@ for (const gk of gatekeepers) { const config = parse(readFileSync(srcPath, "utf8")); config.build = { ...config.build, cwd: gk.dir }; + // Gatekeepers build callback and resource URLs from BASE_URL. Keep those URLs on the selected + // local server when VITE_BACKEND_HOST overrides Wrangler's default port. + if (process.env.VITE_BACKEND_HOST) { + config.vars = config.vars || {}; + config.vars.BASE_URL = `http://${process.env.VITE_BACKEND_HOST}/gatekeeper/${ + gk.name.slice("gatekeeper-".length)}`; + } + const shared = SHARED_GATEKEEPER_CREDS[gk.name]; if (shared && process.env[shared.id] && process.env[shared.secret]) { config.vars = config.vars || {}; diff --git a/scripts/run-local.mjs b/scripts/run-local.mjs index ac0d5898..cd2c24ae 100644 --- a/scripts/run-local.mjs +++ b/scripts/run-local.mjs @@ -29,8 +29,14 @@ const FRONTEND_DIST = join(ROOT, "packages", "workshop-frontend", "dist"); const TYPED_STORAGE_DIST = join(ROOT, "packages", "typed-storage", "dist"); const NODE_MODULES = join(ROOT, "node_modules"); -// Forward any extra flags (e.g. --use-workers-ai-binding) on to run-dev-server.js. -const passthroughArgs = process.argv.slice(2); +// Forward extra flags (e.g. --use-workers-ai-binding) on to run-dev-server.js. `--port` is +// handled here because run-dev-server.js derives Wrangler's port from VITE_BACKEND_HOST. +const cliArgs = process.argv.slice(2); +const portIndex = cliArgs.indexOf("--port"); +const port = portIndex === -1 ? null : cliArgs[portIndex + 1]; +const passthroughArgs = portIndex === -1 + ? cliArgs + : cliArgs.filter((_, index) => index !== portIndex && index !== portIndex + 1); // --------------------------------------------------------------------------- // Enumerate source files and compute a content hash. @@ -139,11 +145,14 @@ if (needsBuild) { // Launch the local server (serves the built frontend as static assets). // --------------------------------------------------------------------------- -console.log("\nStarting local server at http://localhost:8787 ..."); +const backendHost = port ? `localhost:${port}` : process.env.VITE_BACKEND_HOST; +const localUrl = backendHost ? `http://${backendHost}` : "http://localhost:8787"; +console.log(`\nStarting local server at ${localUrl} ...`); const server = spawn( process.execPath, [join(ROOT, "run-dev-server.js"), "--serve-frontend-assets", ...passthroughArgs], - { stdio: "inherit", cwd: ROOT }); + { stdio: "inherit", cwd: ROOT, + env: port ? { ...process.env, VITE_BACKEND_HOST: backendHost } : process.env }); server.on("exit", (code, signal) => { if (signal) process.kill(process.pid, signal); From e82062b9c5a01217215a952162fcfd5c8885c92b Mon Sep 17 00:00:00 2001 From: kiyo-e Date: Fri, 7 Aug 2026 11:02:05 +0900 Subject: [PATCH 2/2] fix: address run-local port feedback --- packages/workshop-frontend/src/main.tsx | 8 +++--- scripts/dev-server-config.js | 33 ++++++++++++++++++++++--- scripts/dev-server-config.test.js | 31 ++++++++++++++++++++++- scripts/run-local.mjs | 15 ++++++----- 4 files changed, 72 insertions(+), 15 deletions(-) diff --git a/packages/workshop-frontend/src/main.tsx b/packages/workshop-frontend/src/main.tsx index 58ac93e7..24056c0c 100644 --- a/packages/workshop-frontend/src/main.tsx +++ b/packages/workshop-frontend/src/main.tsx @@ -62,11 +62,9 @@ function getBackendHost(): string { const backendHost = import.meta.env.VITE_BACKEND_HOST?.trim(); if (backendHost) return backendHost; - // When opening the Vite dev server directly (localhost:3000), the backend is at localhost:8787. - // Otherwise, including run-local, the API is on the same host as the frontend. - return window.location.hostname === 'localhost' && window.location.port === '3000' - ? 'localhost:8787' - : window.location.host; + // When opening the Vite dev server directly, the backend is at localhost:8787. Otherwise, + // including run-local, the API is on the same host as the frontend. + return import.meta.env.DEV ? 'localhost:8787' : window.location.host; } function startConnection(): RpcStub { diff --git a/scripts/dev-server-config.js b/scripts/dev-server-config.js index 401096ca..d4721743 100644 --- a/scripts/dev-server-config.js +++ b/scripts/dev-server-config.js @@ -15,12 +15,39 @@ export function getWranglerPortFromBackendHost(backendHost) { throw new Error("VITE_BACKEND_HOST must include a valid host with an optional port."); } - if (!url.port) return null; + const explicitPort = url.port || trimmed.match(/:(\d+)$/)?.[1]; + if (!explicitPort) return null; - const port = Number(url.port); + const port = Number(explicitPort); if (port < 1) { throw new Error("VITE_BACKEND_HOST must include a valid port between 1 and 65535."); } - return url.port; + return String(port); +} + +export function parseRunLocalArgs(args) { + let port = null; + const passthroughArgs = []; + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + if (arg !== "--port" && !arg.startsWith("--port=")) { + passthroughArgs.push(arg); + continue; + } + + const value = arg === "--port" ? args[++i] : arg.slice("--port=".length); + if (value === undefined || value === "") { + throw new Error("--port requires a value."); + } + + const numericPort = Number(value); + if (!/^\d+$/.test(value) || numericPort < 1 || numericPort > 65535) { + throw new Error("--port must be a valid port between 1 and 65535."); + } + port = String(numericPort); + } + + return { port, passthroughArgs }; } diff --git a/scripts/dev-server-config.test.js b/scripts/dev-server-config.test.js index f38d863a..5c5952ab 100644 --- a/scripts/dev-server-config.test.js +++ b/scripts/dev-server-config.test.js @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import { describe, it } from "node:test"; -import { getWranglerPortFromBackendHost } from "./dev-server-config.js"; +import { getWranglerPortFromBackendHost, parseRunLocalArgs } from "./dev-server-config.js"; describe("getWranglerPortFromBackendHost", () => { it("extracts a port from a localhost backend host", () => { @@ -12,6 +12,10 @@ describe("getWranglerPortFromBackendHost", () => { assert.equal(getWranglerPortFromBackendHost("[::1]:9001"), "9001"); }); + it("extracts an explicit HTTP default port", () => { + assert.equal(getWranglerPortFromBackendHost("localhost:80"), "80"); + }); + it("returns null when the backend host has no port", () => { assert.equal(getWranglerPortFromBackendHost("localhost"), null); }); @@ -40,3 +44,28 @@ describe("getWranglerPortFromBackendHost", () => { /VITE_BACKEND_HOST must include a valid host/); }); }); + +describe("parseRunLocalArgs", () => { + it("extracts a separated port value and preserves other arguments", () => { + assert.deepEqual( + parseRunLocalArgs(["--use-workers-ai-binding", "--port", "8999"]), + { port: "8999", passthroughArgs: ["--use-workers-ai-binding"] }); + }); + + it("extracts an equals-separated port value", () => { + assert.deepEqual( + parseRunLocalArgs(["--port=8999"]), + { port: "8999", passthroughArgs: [] }); + }); + + it("rejects a missing port value", () => { + assert.throws(() => parseRunLocalArgs(["--port"]), /--port requires a value/); + assert.throws(() => parseRunLocalArgs(["--port="]), /--port requires a value/); + }); + + it("rejects an invalid port value", () => { + assert.throws(() => parseRunLocalArgs(["--port", "0"]), /valid port/); + assert.throws(() => parseRunLocalArgs(["--port=65536"]), /valid port/); + assert.throws(() => parseRunLocalArgs(["--port", "invalid"]), /valid port/); + }); +}); diff --git a/scripts/run-local.mjs b/scripts/run-local.mjs index cd2c24ae..2e797318 100644 --- a/scripts/run-local.mjs +++ b/scripts/run-local.mjs @@ -21,6 +21,7 @@ import { execFileSync, spawn } from "node:child_process"; import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs"; import { dirname, join, relative, sep } from "node:path"; import { fileURLToPath } from "node:url"; +import { parseRunLocalArgs } from "./dev-server-config.js"; const ROOT = dirname(dirname(fileURLToPath(import.meta.url))); const STAMP_PATH = join(ROOT, ".run-local-stamp"); @@ -31,12 +32,14 @@ const NODE_MODULES = join(ROOT, "node_modules"); // Forward extra flags (e.g. --use-workers-ai-binding) on to run-dev-server.js. `--port` is // handled here because run-dev-server.js derives Wrangler's port from VITE_BACKEND_HOST. -const cliArgs = process.argv.slice(2); -const portIndex = cliArgs.indexOf("--port"); -const port = portIndex === -1 ? null : cliArgs[portIndex + 1]; -const passthroughArgs = portIndex === -1 - ? cliArgs - : cliArgs.filter((_, index) => index !== portIndex && index !== portIndex + 1); +let port; +let passthroughArgs; +try { + ({ port, passthroughArgs } = parseRunLocalArgs(process.argv.slice(2))); +} catch (err) { + console.error(err.message); + process.exit(1); +} // --------------------------------------------------------------------------- // Enumerate source files and compute a content hash.