Skip to content

Commit ce48b77

Browse files
emily-shendevin-ai-integration[bot]NuroDev
authored
Ungate local explorer (#12848)
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Ben <[email protected]>
1 parent 3de3ce5 commit ce48b77

File tree

5 files changed

+113
-113
lines changed

5 files changed

+113
-113
lines changed

.changeset/tender-hoops-strive.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"@cloudflare/vite-plugin": minor
3+
"miniflare": minor
4+
"wrangler": minor
5+
---
6+
7+
Enable local explorer by default
8+
9+
This ungates the local explorer, a UI that lets you inspect the state of D1, DO and KV resources locally by visiting `/cdn-cgi/explorer` during local development.
10+
11+
Note: this feature is still experimental, and can be disabled by setting the env var `X_LOCAL_EXPLORER=false`.

fixtures/interactive-dev-tests/tests/index.test.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -280,19 +280,12 @@ if (process.platform === "win32") {
280280
expect(wrangler.stdout).not.toContain("to exit");
281281
expect(wrangler.stdout).not.toContain("rebuild container");
282282
});
283+
// TODO: update this when we release properly
283284
it("should not show local explorer hotkey by default", async () => {
284285
const wrangler = await startWranglerDev(args);
285286
wrangler.pty.kill();
286287
expect(wrangler.stdout).not.toContain("open local explorer");
287288
});
288-
it("should show local explorer hotkey when X_LOCAL_EXPLORER=true", async () => {
289-
const wrangler = await startWranglerDev(args, false, {
290-
...(process.env as Record<string, string>),
291-
X_LOCAL_EXPLORER: "true",
292-
});
293-
wrangler.pty.kill();
294-
expect(wrangler.stdout).toContain("open local explorer");
295-
});
296289
});
297290
});
298291

fixtures/worker-with-resources/tests/index.test.ts

Lines changed: 98 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -4,121 +4,119 @@ import { afterAll, assert, beforeAll, describe, it } from "vitest";
44
import { runWranglerDev } from "../../shared/src/run-wrangler-long-lived";
55

66
describe("local explorer", () => {
7-
describe("with X_LOCAL_EXPLORER=true", () => {
8-
let ip: string;
9-
let port: number;
10-
let stop: (() => Promise<unknown>) | undefined;
11-
12-
beforeAll(async () => {
13-
({ ip, port, stop } = await runWranglerDev(
14-
resolve(__dirname, ".."),
15-
["--port=0", "--inspector-port=0"],
16-
{ X_LOCAL_EXPLORER: "true" }
17-
));
18-
});
7+
let ip: string;
8+
let port: number;
9+
let stop: (() => Promise<unknown>) | undefined;
10+
11+
beforeAll(async () => {
12+
({ ip, port, stop } = await runWranglerDev(resolve(__dirname, ".."), [
13+
"--port=0",
14+
"--inspector-port=0",
15+
]));
16+
});
1917

20-
afterAll(async () => {
21-
await stop?.();
22-
});
18+
afterAll(async () => {
19+
await stop?.();
20+
});
2321

24-
it(`returns local explorer API response for ${LOCAL_EXPLORER_API_PATH}`, async ({
25-
expect,
26-
}) => {
27-
const response = await fetch(
28-
`http://${ip}:${port}${LOCAL_EXPLORER_API_PATH}/storage/kv/namespaces`
29-
);
30-
expect(response.headers.get("Content-Type")).toBe("application/json");
31-
const json = await response.json();
32-
expect(json).toMatchObject({
33-
errors: [],
34-
messages: [],
35-
result: [
36-
{
37-
id: "KV",
38-
title: "KV",
39-
},
40-
{
41-
id: "some-kv-id",
42-
title: "KV_WITH_ID",
43-
},
44-
],
45-
result_info: {
46-
count: 2,
22+
it(`returns local explorer API response for ${LOCAL_EXPLORER_API_PATH}`, async ({
23+
expect,
24+
}) => {
25+
const response = await fetch(
26+
`http://${ip}:${port}${LOCAL_EXPLORER_API_PATH}/storage/kv/namespaces`
27+
);
28+
expect(response.headers.get("Content-Type")).toBe("application/json");
29+
const json = await response.json();
30+
expect(json).toMatchObject({
31+
errors: [],
32+
messages: [],
33+
result: [
34+
{
35+
id: "KV",
36+
title: "KV",
4737
},
48-
success: true,
49-
});
50-
});
51-
52-
it("returns worker response for normal requests", async ({ expect }) => {
53-
const response = await fetch(`http://${ip}:${port}/`);
54-
const text = await response.text();
55-
expect(text).toBe("Hello World!");
56-
});
57-
58-
it(`serves UI index.html at ${LOCAL_EXPLORER_BASE_PATH}`, async ({
59-
expect,
60-
}) => {
61-
const response = await fetch(
62-
`http://${ip}:${port}${LOCAL_EXPLORER_BASE_PATH}`
63-
);
64-
expect(response.status).toBe(200);
65-
expect(response.headers.get("Content-Type")).toBe(
66-
"text/html; charset=utf-8"
67-
);
68-
const text = await response.text();
69-
expect(text).toContain("<!doctype html>");
70-
expect(text).toContain("Cloudflare Local Explorer");
38+
{
39+
id: "some-kv-id",
40+
title: "KV_WITH_ID",
41+
},
42+
],
43+
result_info: {
44+
count: 2,
45+
},
46+
success: true,
7147
});
48+
});
7249

73-
it(`serves UI assets at ${LOCAL_EXPLORER_BASE_PATH}/assets/*`, async ({
74-
expect,
75-
}) => {
76-
// First get index.html to find the actual asset paths
77-
const indexResponse = await fetch(
78-
`http://${ip}:${port}${LOCAL_EXPLORER_BASE_PATH}`
79-
);
80-
const html = await indexResponse.text();
50+
it("returns worker response for normal requests", async ({ expect }) => {
51+
const response = await fetch(`http://${ip}:${port}/`);
52+
const text = await response.text();
53+
expect(text).toBe("Hello World!");
54+
});
8155

82-
// Extract JS asset path from the HTML
83-
// The HTML looks like: <script type="module" crossorigin src="/cdn-cgi/explorer/assets/index-xxx.js">
84-
const jsMatch = html.match(/assets\/index-[^"]+\.js/);
85-
assert(jsMatch, "Expected JS asset path in HTML");
86-
const jsPath = jsMatch[0];
56+
it(`serves UI index.html at ${LOCAL_EXPLORER_BASE_PATH}`, async ({
57+
expect,
58+
}) => {
59+
const response = await fetch(
60+
`http://${ip}:${port}${LOCAL_EXPLORER_BASE_PATH}`
61+
);
62+
expect(response.status).toBe(200);
63+
expect(response.headers.get("Content-Type")).toBe(
64+
"text/html; charset=utf-8"
65+
);
66+
const text = await response.text();
67+
expect(text).toContain("<!doctype html>");
68+
expect(text).toContain("Cloudflare Local Explorer");
69+
});
8770

88-
// Fetch the JS asset
89-
const jsResponse = await fetch(
90-
`http://${ip}:${port}${LOCAL_EXPLORER_BASE_PATH}/${jsPath}`
91-
);
92-
expect(jsResponse.status).toBe(200);
93-
expect(jsResponse.headers.get("Content-Type")).toMatch(
94-
/^application\/javascript/
95-
);
96-
});
71+
it(`serves UI assets at ${LOCAL_EXPLORER_BASE_PATH}/assets/*`, async ({
72+
expect,
73+
}) => {
74+
// First get index.html to find the actual asset paths
75+
const indexResponse = await fetch(
76+
`http://${ip}:${port}${LOCAL_EXPLORER_BASE_PATH}`
77+
);
78+
const html = await indexResponse.text();
79+
80+
// Extract JS asset path from the HTML
81+
// The HTML looks like: <script type="module" crossorigin src="/cdn-cgi/explorer/assets/index-xxx.js">
82+
const jsMatch = html.match(/assets\/index-[^"]+\.js/);
83+
assert(jsMatch, "Expected JS asset path in HTML");
84+
const jsPath = jsMatch[0];
85+
86+
// Fetch the JS asset
87+
const jsResponse = await fetch(
88+
`http://${ip}:${port}${LOCAL_EXPLORER_BASE_PATH}/${jsPath}`
89+
);
90+
expect(jsResponse.status).toBe(200);
91+
expect(jsResponse.headers.get("Content-Type")).toMatch(
92+
/^application\/javascript/
93+
);
94+
});
9795

98-
it("serves UI with SPA fallback for unknown routes", async ({ expect }) => {
99-
// Request a route that doesn't exist as a file but should be handled by the SPA
100-
const response = await fetch(
101-
`http://${ip}:${port}${LOCAL_EXPLORER_BASE_PATH}/kv/some-namespace`
102-
);
103-
expect(response.status).toBe(200);
104-
expect(response.headers.get("Content-Type")).toBe(
105-
"text/html; charset=utf-8"
106-
);
107-
const text = await response.text();
108-
expect(text).toContain("<!doctype html>");
109-
});
96+
it("serves UI with SPA fallback for unknown routes", async ({ expect }) => {
97+
// Request a route that doesn't exist as a file but should be handled by the SPA
98+
const response = await fetch(
99+
`http://${ip}:${port}${LOCAL_EXPLORER_BASE_PATH}/kv/some-namespace`
100+
);
101+
expect(response.status).toBe(200);
102+
expect(response.headers.get("Content-Type")).toBe(
103+
"text/html; charset=utf-8"
104+
);
105+
const text = await response.text();
106+
expect(text).toContain("<!doctype html>");
110107
});
111108

112-
describe("without X_LOCAL_EXPLORER (default)", () => {
109+
describe("with X_LOCAL_EXPLORER=false", () => {
113110
let ip: string;
114111
let port: number;
115112
let stop: (() => Promise<unknown>) | undefined;
116113

117114
beforeAll(async () => {
118-
({ ip, port, stop } = await runWranglerDev(resolve(__dirname, ".."), [
119-
"--port=0",
120-
"--inspector-port=0",
121-
]));
115+
({ ip, port, stop } = await runWranglerDev(
116+
resolve(__dirname, ".."),
117+
["--port=0", "--inspector-port=0"],
118+
{ X_LOCAL_EXPLORER: "false" }
119+
));
122120
});
123121

124122
afterAll(async () => {

packages/workers-utils/src/environment-variables/misc-variables.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -339,12 +339,11 @@ export const getOpenNextDeployFromEnv = getEnvironmentVariableFactory({
339339

340340
/**
341341
* `X_LOCAL_EXPLORER` enables the local explorer UI at /cdn-cgi/explorer.
342-
* This is an experimental feature flag. Defaults to false when not set.
343342
*/
344343
export const getLocalExplorerEnabledFromEnv =
345344
getBooleanEnvironmentVariableFactory({
346345
variableName: "X_LOCAL_EXPLORER",
347-
defaultValue: false,
346+
defaultValue: true,
348347
});
349348

350349
/**

packages/wrangler/src/dev/hotkeys.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { generateContainerBuildId } from "@cloudflare/containers-shared";
2-
import { getLocalExplorerEnabledFromEnv } from "@cloudflare/workers-utils";
32
import { LOCAL_EXPLORER_BASE_PATH } from "miniflare";
43
import { LocalRuntimeController } from "../api/startDevWorker/LocalRuntimeController";
54
import registerHotKeys from "../cli-hotkeys";
@@ -49,8 +48,8 @@ export default function registerDevHotKeys(
4948
},
5049
{
5150
keys: ["e"],
52-
label: "open local explorer",
53-
disabled: !getLocalExplorerEnabledFromEnv(),
51+
// This makes the label hidden but still enabled
52+
// label: "open local explorer",
5453
handler: async () => {
5554
const { url } = await primaryDevEnv.proxy.ready.promise;
5655
const explorerUrl = new URL(LOCAL_EXPLORER_BASE_PATH, url);

0 commit comments

Comments
 (0)