diff --git a/.changeset/healthy-monkeys-count.md b/.changeset/healthy-monkeys-count.md new file mode 100644 index 000000000000..52566d44ec8c --- /dev/null +++ b/.changeset/healthy-monkeys-count.md @@ -0,0 +1,7 @@ +--- +"wrangler": patch +--- + +fix: don't crash when browser windows don't open + +We open browser windows for a few things; during `wrangler dev`, and logging in. There are environments where this doesn't work as expected (like codespaces, stackblitz, etc). This fix simply logs an error instead of breaking the flow. This is the same fix as https://github.com/cloudflare/wrangler2/pull/263, now applied to the rest of wrangler. diff --git a/packages/wrangler/src/dev.tsx b/packages/wrangler/src/dev.tsx index 60b5d1e1ba40..69fa2e4f6716 100644 --- a/packages/wrangler/src/dev.tsx +++ b/packages/wrangler/src/dev.tsx @@ -9,7 +9,6 @@ import commandExists from "command-exists"; import * as esbuild from "esbuild"; import { execaCommand } from "execa"; import { Box, Text, useApp, useInput } from "ink"; -import open from "open"; import React, { useState, useEffect, useRef } from "react"; import { withErrorBoundary, useErrorHandler } from "react-error-boundary"; import onExit from "signal-exit"; @@ -19,6 +18,7 @@ import { createWorker } from "./api/worker"; import guessWorkerFormat from "./guess-worker-format"; import useInspector from "./inspect"; import makeModuleCollector from "./module-collection"; +import openInBrowser from "./open-in-brower"; import { usePreviewServer, waitForPortToBeAvailable } from "./proxy"; import { syncAssets } from "./sites"; import { getAPIToken } from "./user"; @@ -804,15 +804,17 @@ function useHotkeys(initial: useHotkeysInitialState, port: number) { ) => { switch (input.toLowerCase()) { // open browser - case "b": - await open(`http://localhost:${port}/`); + case "b": { + await openInBrowser(`http://localhost:${port}`); break; + } // toggle inspector - case "d": - await open( + case "d": { + await openInBrowser( `https://built-devtools.pages.dev/js_app?experiments=true&v8only=true&ws=localhost:9229/ws` ); break; + } // toggle tunnel case "s": setToggles((previousToggles) => ({ diff --git a/packages/wrangler/src/open-in-brower.ts b/packages/wrangler/src/open-in-brower.ts new file mode 100644 index 000000000000..8708c1043b6a --- /dev/null +++ b/packages/wrangler/src/open-in-brower.ts @@ -0,0 +1,13 @@ +import open from "open"; +/** + * An extremely simple wrapper around the open command. + * Specifically, it adds an 'error' event handler so that when this function + * is called in environments where we can't open the browser (e.g. github codespaces, + * stackblitz, remote servers), it doesn't just crash the process. + */ +export default async function openInBrowser(url: string): Promise { + const childProcess = await open(url); + childProcess.on("error", () => { + console.warn(`Failed to open ${url} in a browser`); + }); +} diff --git a/packages/wrangler/src/pages.tsx b/packages/wrangler/src/pages.tsx index 734eec1b9b92..a5e1e9503cad 100644 --- a/packages/wrangler/src/pages.tsx +++ b/packages/wrangler/src/pages.tsx @@ -7,10 +7,10 @@ import { join } from "node:path"; import { URL } from "node:url"; import { watch } from "chokidar"; import { getType } from "mime"; -import open from "open"; import { buildWorker } from "../pages/functions/buildWorker"; import { generateConfigFromFileTree } from "../pages/functions/filepath-routing"; import { writeRoutesModule } from "../pages/functions/routes"; +import openInBrowser from "./open-in-brower"; import { toUrlPath } from "./paths"; import type { Config } from "../pages/functions/routes"; import type { Headers, Request, fetch } from "@miniflare/core"; @@ -945,9 +945,7 @@ export const pages: BuilderCallback = (yargs) => { console.log(`Serving at http://localhost:${port}/`); if (process.env.BROWSER !== "none") { - const childProcess = await open(`http://localhost:${port}/`); - // fail silently if the open command doesn't work (e.g. in GitHub Codespaces) - childProcess.on("error", (_err) => {}); + await openInBrowser(`http://localhost:${port}/`); } if (directory !== undefined && liveReload) { diff --git a/packages/wrangler/src/user.tsx b/packages/wrangler/src/user.tsx index 3dcba1c03f34..ab957682497c 100644 --- a/packages/wrangler/src/user.tsx +++ b/packages/wrangler/src/user.tsx @@ -218,10 +218,10 @@ import TOML from "@iarna/toml"; import { render, Text } from "ink"; import SelectInput from "ink-select-input"; import Table from "ink-table"; -import open from "open"; import React from "react"; import { fetch } from "undici"; import { CF_API_BASE_URL } from "./cfetch"; +import openInBrowser from "./open-in-brower"; import type { ParsedUrlQuery } from "node:querystring"; import type { Response } from "undici"; @@ -804,18 +804,18 @@ export async function loginOrRefreshIfRequired(): Promise { export async function login(props?: LoginProps): Promise { const urlToOpen = await getAuthURL(props?.scopes); - await open(urlToOpen); - // TODO: log url only if on system where it's unreliable/unavailable - // console.log(`💁 Opened ${urlToOpen}`); + await openInBrowser(urlToOpen); let server; let loginTimeoutHandle; const timerPromise = new Promise((resolve) => { loginTimeoutHandle = setTimeout(() => { - console.error("Timed out waiting for authorization code."); + console.error( + "Timed out waiting for authorization code, please try again." + ); server.close(); clearTimeout(loginTimeoutHandle); resolve(false); - }, 60000); // wait for 30 seconds for the user to authorize + }, 60000); // wait for 60 seconds for the user to authorize }); const loginPromise = new Promise((resolve, reject) => {