From ec489ec4b286a3bbbc25bde90020de2fd48b2e1c Mon Sep 17 00:00:00 2001 From: Jan Karres Date: Tue, 18 Aug 2026 23:44:42 +0200 Subject: [PATCH 1/3] fix(dev): make the browser dev server reachable from a remote workstation --- dev/browser/start.js | 11 ++++++++++- dev/start.js | 32 ++++++++++++++++++++++++++++---- webpack.config.js | 26 +++++++++++++++++++------- 3 files changed, 57 insertions(+), 12 deletions(-) diff --git a/dev/browser/start.js b/dev/browser/start.js index f2794ac..5c54335 100644 --- a/dev/browser/start.js +++ b/dev/browser/start.js @@ -2,7 +2,16 @@ import { exec } from 'node:child_process'; import os from 'node:os'; export default async function start(server) { - const url = server ? `https://${server.host}:${server.port}` : 'https://localhost:3000'; + const protocol = server?.protocol || 'https'; + const url = server ? `${protocol}://${server.host}:${server.port}` : 'https://localhost:3000'; + + console.log(`-> Dev server: ${url}`); + + // A remote or headless workstation has no browser to open; printing the URL + // is all that is useful there. + if (server && server.open === false) { + return; + } if (os.platform() === 'darwin') { exec(`open ${url}`); diff --git a/dev/start.js b/dev/start.js index 368beb6..adf1a38 100644 --- a/dev/start.js +++ b/dev/start.js @@ -7,6 +7,14 @@ const args = process.argv.slice(2); const platform = args.find((arg) => /(android|ios|browser)/i.test(arg)) || "android"; const isRelease = args.includes("--release") || args.includes("-r") || false; const noServer = args.includes("--no-server"); +// Bind and serve overrides, needed whenever the dev server is not reached over +// the LAN: a remote workstation (VS Code Remote port forwarding) wants +// `--host localhost --http`, and `--no-open` keeps a headless box from trying +// to launch a browser. +const hostOverride = getArgValue("--host") || process.env.SHELLULAR_DEV_HOST; +const portOverride = getArgValue("--port") || process.env.SHELLULAR_DEV_PORT; +const noHttps = args.includes("--http"); +const noOpen = args.includes("--no-open"); const { default: start } = await import(`./${platform}/start.js`); @@ -37,10 +45,11 @@ async function main() { } else if (noServer) { command = `webpack --mode development --env platform=${platform}`; } else { - const host = getIp(); - const port = getPort(); - devServer = { host, port }; - command = `webpack serve --mode development --env platform=${platform} host=${host} port=${port}`; + const host = hostOverride || getIp(); + const port = portOverride || getPort(); + const protocol = platform === "browser" && !noHttps ? "https" : "http"; + devServer = { host, port, protocol, open: !noOpen }; + command = `webpack serve --mode development --env platform=${platform} host=${host} port=${port}${noHttps ? " https=false" : ""}`; } console.log(command); @@ -131,6 +140,21 @@ function printToStdOut(error, stdout, stderr) { } } +/** + * Reads the value of a `--flag value` or `--flag=value` argument. + * @param {string} flag - The flag to look for. + * @returns {string|undefined} The value, or undefined when the flag is absent. + */ +function getArgValue(flag) { + const inline = args.find((arg) => arg.startsWith(`${flag}=`)); + if (inline) { + return inline.slice(flag.length + 1); + } + + const index = args.indexOf(flag); + return index !== -1 ? args[index + 1] : undefined; +} + function getPort() { if (platform === "browser") { return 7977; diff --git a/webpack.config.js b/webpack.config.js index f47ef42..60252a7 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -34,8 +34,14 @@ export default (_, { env = {}, mode = "development" }) => { platform = "android", host, port, + https = "true", console: compileConsole = false, } = env; + // The browser target is served over https so the phone gets a secure + // context on the LAN. Behind a localhost tunnel (VS Code Remote port + // forwarding) http is both a secure context and one less certificate + // warning, so `https=false` turns TLS off. + const useHttps = platform === "browser" && https !== "false"; const outputPath = platform === "browser" ? BROWSER_BUNDLE @@ -46,10 +52,13 @@ export default (_, { env = {}, mode = "development" }) => { let alias; if (isDev) { console.log(`dev server: ${host}:${port}`); - alias = { - // assuming app & packages are in the same monorepo, adjust as needed - "@shellular/protocol": resolve("../packages/protocol/dist/index.js"), - }; + // Only alias the protocol package when this checkout really sits next to + // it in the monorepo. A standalone checkout has no ../packages, and an + // alias pointing at a missing file breaks every dev build. + const localProtocol = resolve("../packages/protocol/dist/index.js"); + if (existsSync(localProtocol)) { + alias = { "@shellular/protocol": localProtocol }; + } } else if (!compileConsole) { clearOutputDir(outputPath); } @@ -125,7 +134,7 @@ export default (_, { env = {}, mode = "development" }) => { webSocketURL: { hostname: host, port: Number(port), - protocol: platform === "browser" ? "wss" : "ws", + protocol: useHttps ? "wss" : "ws", pathname: "/ws", }, }, @@ -138,7 +147,7 @@ export default (_, { env = {}, mode = "development" }) => { : {}), ...(platform === "browser" ? { - server: "https", + server: useHttps ? "https" : "http", headers: (request) => { const isAuthCallback = new URL( @@ -255,7 +264,10 @@ export default (_, { env = {}, mode = "development" }) => { DISPLAY_NAME: packageJson.displayName, HOST: isDev && host ? host : null, PORT: isDev && port ? port : null, - ORIGIN: isDev && host && port ? `http://${host}:${port}` : null, + ORIGIN: + isDev && host && port + ? `${useHttps ? "https" : "http"}://${host}:${port}` + : null, }), ], optimization: { From b6b8ee9e407377fe18d11d4cc3378bc18a202950 Mon Sep 17 00:00:00 2001 From: Biraj Date: Thu, 20 Aug 2026 13:19:33 -0700 Subject: [PATCH 2/3] chore: change --no-open to --headless --- dev/browser/start.js | 2 +- dev/start.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dev/browser/start.js b/dev/browser/start.js index 5c54335..f378c10 100644 --- a/dev/browser/start.js +++ b/dev/browser/start.js @@ -9,7 +9,7 @@ export default async function start(server) { // A remote or headless workstation has no browser to open; printing the URL // is all that is useful there. - if (server && server.open === false) { + if (server && server.headless) { return; } diff --git a/dev/start.js b/dev/start.js index adf1a38..a0bfd15 100644 --- a/dev/start.js +++ b/dev/start.js @@ -9,12 +9,12 @@ const isRelease = args.includes("--release") || args.includes("-r") || false; const noServer = args.includes("--no-server"); // Bind and serve overrides, needed whenever the dev server is not reached over // the LAN: a remote workstation (VS Code Remote port forwarding) wants -// `--host localhost --http`, and `--no-open` keeps a headless box from trying +// `--host localhost --http`, and `--headless` keeps a headless box from trying // to launch a browser. const hostOverride = getArgValue("--host") || process.env.SHELLULAR_DEV_HOST; const portOverride = getArgValue("--port") || process.env.SHELLULAR_DEV_PORT; const noHttps = args.includes("--http"); -const noOpen = args.includes("--no-open"); +const headless = args.includes("--headless"); const { default: start } = await import(`./${platform}/start.js`); @@ -48,7 +48,7 @@ async function main() { const host = hostOverride || getIp(); const port = portOverride || getPort(); const protocol = platform === "browser" && !noHttps ? "https" : "http"; - devServer = { host, port, protocol, open: !noOpen }; + devServer = { host, port, protocol, headless }; command = `webpack serve --mode development --env platform=${platform} host=${host} port=${port}${noHttps ? " https=false" : ""}`; } From c358a837d3d220669e90bb1f688d73645ff08738 Mon Sep 17 00:00:00 2001 From: Biraj Date: Thu, 20 Aug 2026 13:19:51 -0700 Subject: [PATCH 3/3] chore: log when using local protocol --- webpack.config.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/webpack.config.js b/webpack.config.js index 60252a7..3b8b9de 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -51,13 +51,15 @@ export default (_, { env = {}, mode = "development" }) => { let alias; if (isDev) { - console.log(`dev server: ${host}:${port}`); // Only alias the protocol package when this checkout really sits next to // it in the monorepo. A standalone checkout has no ../packages, and an // alias pointing at a missing file breaks every dev build. const localProtocol = resolve("../packages/protocol/dist/index.js"); if (existsSync(localProtocol)) { + console.log(`using local protocol: ${localProtocol}`); alias = { "@shellular/protocol": localProtocol }; + } else { + console.log(`no local protocol found. using the one from node_modules`); } } else if (!compileConsole) { clearOutputDir(outputPath);