diff --git a/.changeset/add-explorer-shortcut-vite.md b/.changeset/add-explorer-shortcut-vite.md new file mode 100644 index 0000000000..fb1041ccb5 --- /dev/null +++ b/.changeset/add-explorer-shortcut-vite.md @@ -0,0 +1,8 @@ +--- +"@cloudflare/vite-plugin": minor +--- + +Add `e` hotkey to open local explorer during dev + +Press `e` during `vite dev` to open the local explorer UI at `/cdn-cgi/explorer`, which allows you to inspect the state of your D1, R2, KV, DO and Workflow bindings. + diff --git a/.changeset/silly-pears-kick.md b/.changeset/silly-pears-kick.md new file mode 100644 index 0000000000..157a27e387 --- /dev/null +++ b/.changeset/silly-pears-kick.md @@ -0,0 +1,7 @@ +--- +"wrangler": minor +--- + +explorer: expose the local explorer hotkey + +List the local explorer's hotkey `[e]` in wrangler dev output. \ No newline at end of file diff --git a/fixtures/interactive-dev-tests/tests/index.test.ts b/fixtures/interactive-dev-tests/tests/index.test.ts index 746a296f07..caa3f49e73 100644 --- a/fixtures/interactive-dev-tests/tests/index.test.ts +++ b/fixtures/interactive-dev-tests/tests/index.test.ts @@ -266,6 +266,7 @@ if (process.platform === "win32") { expect(wrangler.stdout).toContain("open devtools"); expect(wrangler.stdout).toContain("clear console"); expect(wrangler.stdout).toContain("to exit"); + expect(wrangler.stdout).toContain("open local explorer"); expect(wrangler.stdout).not.toContain("rebuild container"); }); it("should not show hotkeys with --show-interactive-dev-session=false", async () => { @@ -279,11 +280,6 @@ if (process.platform === "win32") { expect(wrangler.stdout).not.toContain("clear console"); expect(wrangler.stdout).not.toContain("to exit"); expect(wrangler.stdout).not.toContain("rebuild container"); - }); - // TODO: update this when we release properly - it("should not show local explorer hotkey by default", async () => { - const wrangler = await startWranglerDev(args); - wrangler.pty.kill(); expect(wrangler.stdout).not.toContain("open local explorer"); }); }); diff --git a/packages/create-cloudflare/package.json b/packages/create-cloudflare/package.json index 8ddeb1ec1c..a2804e7361 100644 --- a/packages/create-cloudflare/package.json +++ b/packages/create-cloudflare/package.json @@ -73,7 +73,7 @@ "indent-string": "^5.0.0", "jsonc-parser": "catalog:default", "magic-string": "^0.30.5", - "open": "^8.4.0", + "open": "catalog:default", "recast": "^0.23.11", "semver": "^7.7.1", "smol-toml": "catalog:default", diff --git a/packages/vite-plugin-cloudflare/package.json b/packages/vite-plugin-cloudflare/package.json index b45fd69b8c..79fa3b920a 100644 --- a/packages/vite-plugin-cloudflare/package.json +++ b/packages/vite-plugin-cloudflare/package.json @@ -66,6 +66,7 @@ "get-port": "^7.1.0", "magic-string": "^0.30.12", "mlly": "^1.7.4", + "open": "catalog:default", "picocolors": "^1.1.1", "semver": "^7.7.1", "tinyglobby": "^0.2.12", diff --git a/packages/vite-plugin-cloudflare/playground/bindings/__tests__/shortcuts.spec.ts b/packages/vite-plugin-cloudflare/playground/bindings/__tests__/shortcuts.spec.ts index 06e8992e22..437aa0caaf 100644 --- a/packages/vite-plugin-cloudflare/playground/bindings/__tests__/shortcuts.spec.ts +++ b/packages/vite-plugin-cloudflare/playground/bindings/__tests__/shortcuts.spec.ts @@ -3,7 +3,10 @@ import { stripVTControlCharacters } from "node:util"; import { afterAll, beforeAll, describe, test, vi } from "vitest"; import { PluginContext } from "../../../src/context"; import { resolvePluginConfig } from "../../../src/plugin-config"; -import { addBindingsShortcut } from "../../../src/plugins/shortcuts"; +import { + addBindingsShortcut, + addExplorerShortcut, +} from "../../../src/plugins/shortcuts"; import { resetServerLogs, satisfiesViteVersion, @@ -186,4 +189,33 @@ describe.skipIf(!satisfiesViteVersion("7.2.7"))("shortcuts", () => { " `); }); + + test("registers explorer shortcut with correct URL", async ({ expect }) => { + const mockOpen = vi.hoisted(() => vi.fn(() => ({ on: vi.fn() }))); + vi.mock("open", () => ({ default: mockOpen })); + + const mockBindCLIShortcuts = vi.spyOn(viteServer, "bindCLIShortcuts"); + + addExplorerShortcut(viteServer); + + expect(mockBindCLIShortcuts).toHaveBeenCalledWith({ + customShortcuts: [ + { + key: "e", + description: "open local explorer", + action: expect.any(Function), + }, + ], + }); + + const { customShortcuts } = mockBindCLIShortcuts.mock.calls[0]?.[0] ?? {}; + const explorerShortcut = customShortcuts?.find((s) => s.key === "e"); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await explorerShortcut?.action?.(viteServer as any); + + expect(mockOpen).toHaveBeenCalledWith( + expect.stringMatching(/^http:\/\/localhost:\d+\/cdn-cgi\/explorer$/) + ); + }); }); diff --git a/packages/vite-plugin-cloudflare/src/plugins/shortcuts.ts b/packages/vite-plugin-cloudflare/src/plugins/shortcuts.ts index 578edc3e46..af1f17d633 100644 --- a/packages/vite-plugin-cloudflare/src/plugins/shortcuts.ts +++ b/packages/vite-plugin-cloudflare/src/plugins/shortcuts.ts @@ -1,4 +1,9 @@ -import { getDefaultDevRegistryPath, getWorkerRegistry } from "miniflare"; +import { + CorePaths, + getDefaultDevRegistryPath, + getWorkerRegistry, +} from "miniflare"; +import open from "open"; import colors from "picocolors"; import * as wrangler from "wrangler"; import { assertIsNotPreview, assertIsPreview } from "../context"; @@ -19,6 +24,7 @@ export const shortcutsPlugin = createPlugin("shortcuts", (ctx) => { assertIsNotPreview(ctx); addBindingsShortcut(viteDevServer, ctx); + addExplorerShortcut(viteDevServer); }, async configurePreviewServer(vitePreviewServer) { if (!isCustomShortcutsSupported) { @@ -27,6 +33,7 @@ export const shortcutsPlugin = createPlugin("shortcuts", (ctx) => { assertIsPreview(ctx); addBindingsShortcut(vitePreviewServer, ctx); + addExplorerShortcut(vitePreviewServer); }, }; }); @@ -107,3 +114,60 @@ export function addBindingsShortcut( customShortcuts: [printBindingsShortcut], }); } + +export function addExplorerShortcut( + server: vite.ViteDevServer | vite.PreviewServer +) { + if (!process.stdin.isTTY) { + return; + } + + const openExplorerShortcut = { + key: "e", + description: "open local explorer", + action: async (viteServer) => { + const url = viteServer.resolvedUrls?.local[0]; + if (!url) { + viteServer.config.logger.warn("No local URL available"); + return; + } + + const explorerUrl = new URL(CorePaths.EXPLORER, url).href; + const childProcess = await open(explorerUrl); + childProcess.on("error", () => { + viteServer.config.logger.warn( + "Failed to open browser, the local explorer can be accessed at " + + explorerUrl + ); + }); + }, + } satisfies vite.CLIShortcut; + + // Wrap bindCLIShortcuts to print our shortcut hint + const bindCLIShortcuts = server.bindCLIShortcuts.bind(server); + server.bindCLIShortcuts = ( + options?: vite.BindCLIShortcutsOptions< + vite.ViteDevServer | vite.PreviewServer + > + ) => { + if ( + server.httpServer && + process.stdin.isTTY && + !process.env.CI && + options?.print + ) { + server.config.logger.info( + colors.dim(colors.green(" ➜")) + + colors.dim(" press ") + + colors.bold(`${openExplorerShortcut.key} + enter`) + + colors.dim(` to ${openExplorerShortcut.description}`) + ); + } + + bindCLIShortcuts(options); + }; + + server.bindCLIShortcuts({ + customShortcuts: [openExplorerShortcut], + }); +} diff --git a/packages/wrangler/package.json b/packages/wrangler/package.json index 3fba7cbbcf..d23999e001 100644 --- a/packages/wrangler/package.json +++ b/packages/wrangler/package.json @@ -140,7 +140,7 @@ "mock-socket": "^9.3.1", "msw": "catalog:default", "node-forge": "^1.3.2", - "open": "^8.4.0", + "open": "catalog:default", "p-queue": "^9.0.0", "patch-console": "^1.0.0", "pretty-bytes": "^6.0.0", diff --git a/packages/wrangler/src/dev/hotkeys.ts b/packages/wrangler/src/dev/hotkeys.ts index e627acae92..80d34b8b6d 100644 --- a/packages/wrangler/src/dev/hotkeys.ts +++ b/packages/wrangler/src/dev/hotkeys.ts @@ -48,8 +48,7 @@ export default function registerDevHotKeys( }, { keys: ["e"], - // This makes the label hidden but still enabled - // label: "open local explorer", + label: "open local explorer", handler: async () => { const { url } = await primaryDevEnv.proxy.ready.promise; const explorerUrl = new URL(CorePaths.EXPLORER, url); diff --git a/packages/wrangler/src/dev/inspect.ts b/packages/wrangler/src/dev/inspect.ts index 542d54264e..c8efc68274 100644 --- a/packages/wrangler/src/dev/inspect.ts +++ b/packages/wrangler/src/dev/inspect.ts @@ -1,7 +1,7 @@ import { readFileSync } from "node:fs"; import path from "node:path"; import { fileURLToPath, URL } from "node:url"; -import open from "open"; +import open, { apps } from "open"; import { isAllowedSourceMapPath, isAllowedSourcePath, @@ -276,16 +276,16 @@ export const openInspector = async ( const childProcess = await open(url, { app: [ { - name: open.apps.chrome, + name: apps.chrome, }, { name: braveBrowser, }, { - name: open.apps.edge, + name: apps.edge, }, { - name: open.apps.firefox, + name: apps.firefox, }, ], }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9a85eac7c6..2e0cbebd88 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -42,6 +42,9 @@ catalogs: msw: specifier: 2.12.4 version: 2.12.4 + open: + specifier: ^11.0.0 + version: 11.0.0 playwright-chromium: specifier: ^1.56.1 version: 1.56.1 @@ -1759,8 +1762,8 @@ importers: specifier: ^0.30.5 version: 0.30.14 open: - specifier: ^8.4.0 - version: 8.4.0 + specifier: catalog:default + version: 11.0.0 recast: specifier: ^0.23.11 version: 0.23.11 @@ -2437,6 +2440,9 @@ importers: mlly: specifier: ^1.7.4 version: 1.7.4 + open: + specifier: catalog:default + version: 11.0.0 picocolors: specifier: ^1.1.1 version: 1.1.1 @@ -4141,8 +4147,8 @@ importers: specifier: ^1.3.2 version: 1.3.3 open: - specifier: ^8.4.0 - version: 8.4.0 + specifier: catalog:default + version: 11.0.0 p-queue: specifier: ^9.0.0 version: 9.0.0 @@ -10132,10 +10138,6 @@ packages: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} - define-lazy-prop@2.0.0: - resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} - engines: {node: '>=8'} - define-lazy-prop@3.0.0: resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} engines: {node: '>=12'} @@ -11359,11 +11361,6 @@ packages: is-deflate@1.0.0: resolution: {integrity: sha512-YDoFpuZWu1VRXlsnlYMzKyVRITXj7Ej/V9gXQ2/pAe7X1J7M/RNOqaIYi6qUn+B7nGyB9pDXrv02dsB58d2ZAQ==} - is-docker@2.2.1: - resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} - engines: {node: '>=8'} - hasBin: true - is-docker@3.0.0: resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -11519,10 +11516,6 @@ packages: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} - is-wsl@2.2.0: - resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} - engines: {node: '>=8'} - is-wsl@3.1.0: resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} engines: {node: '>=16'} @@ -12488,10 +12481,6 @@ packages: resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} engines: {node: '>=20'} - open@8.4.0: - resolution: {integrity: sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==} - engines: {node: '>=12'} - optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -21202,8 +21191,6 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - define-lazy-prop@2.0.0: {} - define-lazy-prop@3.0.0: {} define-properties@1.2.1: @@ -22610,8 +22597,6 @@ snapshots: is-deflate@1.0.0: {} - is-docker@2.2.1: {} - is-docker@3.0.0: {} is-even@1.0.0: @@ -22738,10 +22723,6 @@ snapshots: is-windows@1.0.2: {} - is-wsl@2.2.0: - dependencies: - is-docker: 2.2.1 - is-wsl@3.1.0: dependencies: is-inside-container: 1.0.0 @@ -23629,12 +23610,6 @@ snapshots: powershell-utils: 0.1.0 wsl-utils: 0.3.1 - open@8.4.0: - dependencies: - define-lazy-prop: 2.0.0 - is-docker: 2.2.1 - is-wsl: 2.2.0 - optionator@0.9.4: dependencies: deep-is: 0.1.4 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 77a6ba5b78..547ce6c3d7 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -107,6 +107,7 @@ catalog: "capnp-es": "^0.0.14" "capnweb": "^0.5.0" "ci-info": "^4.4.0" + "open": "^11.0.0" # CAUTION: Most usage of @cloudflare/vitest-pool-workers in this monorepo should use workspace:* instead of this catalog version # However, some packages (pages-shared, workers-shared, etc...) need to be tested using vitest-pool-workers but are themselves # ultimately included in vitest-pool-workers (through Wrangler), causing a circular dependency.