Skip to content

Commit 12a63ef

Browse files
authored
feat(vite-plugin-cloudflare): surface binding info (#11045)
1 parent 06f48c0 commit 12a63ef

File tree

15 files changed

+372
-23
lines changed

15 files changed

+372
-23
lines changed

.changeset/chubby-comics-brush.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@cloudflare/vite-plugin": minor
3+
---
4+
5+
Add keyboard shortcut to display Worker bindings during development
6+
7+
When running `vite dev` or `vite preview`, you can now press `b + Enter` to display a list of all bindings configured for your Worker(s). This makes it easier to discover and verify which resources (e.g. KV namespaces, Durable Objects, environment variables, etc.) are available to your Worker during development.
8+
9+
This feature requires `vite` version `7.2.7` or later.

.changeset/ninety-suns-read.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wrangler": minor
3+
---
4+
5+
Add an internal `unstable_printBindings` API for vite plugin integration
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import path from "node:path";
2+
import stripAnsi from "strip-ansi";
3+
import { afterAll, beforeAll, describe, expect, test, vi } from "vitest";
4+
import { resetServerLogs, serverLogs, viteServer } from "../../__test-utils__";
5+
import { PluginContext } from "../../../src/context";
6+
import { resolvePluginConfig } from "../../../src/plugin-config";
7+
import { addBindingsShortcut } from "../../../src/plugins/shortcuts";
8+
import { satisfiesViteVersion } from "../../../src/utils";
9+
10+
const normalize = (logs: string[]) =>
11+
stripAnsi(logs.join("\n"))
12+
.split("\n")
13+
.map((line) => line.trim())
14+
.join("\n");
15+
16+
describe.skipIf(!satisfiesViteVersion("7.2.7"))("shortcuts", () => {
17+
beforeAll(() => {
18+
vi.stubEnv("CI", undefined);
19+
process.stdin.isTTY = true;
20+
});
21+
22+
afterAll(() => {
23+
vi.unstubAllEnvs();
24+
process.stdin.isTTY = false;
25+
});
26+
27+
test("display binding shortcut hint", () => {
28+
viteServer.bindCLIShortcuts();
29+
30+
expect(normalize(serverLogs.info)).not.toMatch(
31+
"press b + enter to list configured Cloudflare bindings"
32+
);
33+
34+
viteServer.bindCLIShortcuts({
35+
print: true,
36+
});
37+
38+
expect(normalize(serverLogs.info)).toMatch(
39+
"press b + enter to list configured Cloudflare bindings"
40+
);
41+
});
42+
});
43+
44+
test("prints bindings with a single Worker", () => {
45+
// Create a test server with a spy on bindCLIShortcuts
46+
const mockBindCLIShortcuts = vi.spyOn(viteServer, "bindCLIShortcuts");
47+
// Create mock plugin context
48+
const mockContext = new PluginContext({
49+
hasShownWorkerConfigWarnings: false,
50+
isRestartingDevServer: false,
51+
});
52+
53+
mockContext.setResolvedPluginConfig(
54+
resolvePluginConfig(
55+
{
56+
configPath: path.resolve(__dirname, "../wrangler.jsonc"),
57+
},
58+
{},
59+
{
60+
command: "serve",
61+
mode: "development",
62+
}
63+
)
64+
);
65+
66+
addBindingsShortcut(viteServer, mockContext);
67+
// Confirm that addBindingsShortcut wrapped the original method
68+
expect(viteServer.bindCLIShortcuts).not.toBe(mockBindCLIShortcuts);
69+
expect(mockBindCLIShortcuts).toHaveBeenCalledExactlyOnceWith({
70+
customShortcuts: [
71+
{
72+
key: "b",
73+
description: "list configured Cloudflare bindings",
74+
action: expect.any(Function),
75+
},
76+
],
77+
});
78+
79+
const { customShortcuts } = mockBindCLIShortcuts.mock.calls[0]?.[0] ?? {};
80+
const printBindingShortcut = customShortcuts?.find((s) => s.key === "b");
81+
82+
resetServerLogs();
83+
printBindingShortcut?.action?.(viteServer as any);
84+
85+
expect(normalize(serverLogs.info)).toMatchInlineSnapshot(`
86+
"
87+
Your Worker has access to the following bindings:
88+
Binding Resource
89+
env.KV (test-kv-id) KV Namespace
90+
env.HYPERDRIVE (test-hyperdrive-id) Hyperdrive Config
91+
env.HELLO_WORLD (Timer disabled) Hello World
92+
env.WAE (test) Analytics Engine Dataset
93+
env.IMAGES Images
94+
env.RATE_LIMITER (ratelimit) Unsafe Metadata
95+
"
96+
`);
97+
});
98+
99+
test("prints bindings with multi Workers", () => {
100+
// Create a test server with a spy on bindCLIShortcuts
101+
const mockBindCLIShortcuts = vi.spyOn(viteServer, "bindCLIShortcuts");
102+
// Create mock plugin context
103+
const mockContext = new PluginContext({
104+
hasShownWorkerConfigWarnings: false,
105+
isRestartingDevServer: false,
106+
});
107+
108+
mockContext.setResolvedPluginConfig(
109+
resolvePluginConfig(
110+
{
111+
configPath: path.resolve(__dirname, "../wrangler.jsonc"),
112+
auxiliaryWorkers: [
113+
{
114+
configPath: path.resolve(__dirname, "../wrangler.auxiliary.jsonc"),
115+
},
116+
],
117+
},
118+
{},
119+
{
120+
command: "serve",
121+
mode: "development",
122+
}
123+
)
124+
);
125+
126+
addBindingsShortcut(viteServer, mockContext);
127+
// Confirm that addBindingsShortcut wrapped the original method
128+
expect(viteServer.bindCLIShortcuts).not.toBe(mockBindCLIShortcuts);
129+
expect(mockBindCLIShortcuts).toHaveBeenCalledExactlyOnceWith({
130+
customShortcuts: [
131+
{
132+
key: "b",
133+
description: "list configured Cloudflare bindings",
134+
action: expect.any(Function),
135+
},
136+
],
137+
});
138+
139+
const { customShortcuts } = mockBindCLIShortcuts.mock.calls[0]?.[0] ?? {};
140+
const printBindingShortcut = customShortcuts?.find((s) => s.key === "b");
141+
142+
resetServerLogs();
143+
printBindingShortcut?.action?.(viteServer as any);
144+
145+
expect(normalize(serverLogs.info)).toMatchInlineSnapshot(`
146+
"
147+
primary-worker has access to the following bindings:
148+
Binding Resource
149+
env.KV (test-kv-id) KV Namespace
150+
env.HYPERDRIVE (test-hyperdrive-id) Hyperdrive Config
151+
env.HELLO_WORLD (Timer disabled) Hello World
152+
env.WAE (test) Analytics Engine Dataset
153+
env.IMAGES Images
154+
env.RATE_LIMITER (ratelimit) Unsafe Metadata
155+
156+
auxiliary-worker has access to the following bindings:
157+
Binding Resource
158+
env.SERVICE (primary-worker) Worker
159+
"
160+
`);
161+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "auxiliary-worker",
3+
"main": "./src/index.ts",
4+
"compatibility_date": "2024-12-30",
5+
"services": [
6+
{
7+
"binding": "SERVICE",
8+
"service": "primary-worker",
9+
},
10+
],
11+
}

packages/vite-plugin-cloudflare/playground/bindings/wrangler.jsonc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "./node_modules/wrangler/config-schema.json",
3-
"name": "worker",
3+
"name": "primary-worker",
44
"main": "./src/index.ts",
55
"compatibility_date": "2024-12-30",
66
"kv_namespaces": [

packages/vite-plugin-cloudflare/playground/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"@cloudflare/vite-plugin": "workspace:*",
1818
"@cloudflare/workers-tsconfig": "workspace:*",
1919
"playwright-chromium": "catalog:default",
20+
"strip-ansi": "^7.1.0",
2021
"ts-dedent": "^2.2.0",
2122
"typescript": "catalog:default"
2223
}

packages/vite-plugin-cloudflare/playground/vitest-setup.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99
preview,
1010
} from "vite";
1111
import { beforeAll, inject } from "vitest";
12-
import type * as http from "node:http";
1312
import type { Browser, Page } from "playwright-chromium";
1413
import type {
1514
ConfigEnv,
@@ -39,7 +38,7 @@ export const isLocalWithoutDockerRunning =
3938
/**
4039
* Vite Dev Server when testing serve
4140
*/
42-
export let viteServer: ViteDevServer;
41+
export let viteServer: ViteDevServer | PreviewServer;
4342
/**
4443
* Root of the Vite fixture
4544
*/
@@ -81,13 +80,13 @@ export function setViteUrl(url: string): void {
8180
}
8281

8382
export function resetServerLogs() {
84-
serverLogs.info = [];
85-
serverLogs.warns = [];
86-
serverLogs.errors = [];
83+
serverLogs.info.splice(0, serverLogs.info.length);
84+
serverLogs.warns.splice(0, serverLogs.warns.length);
85+
serverLogs.errors.splice(0, serverLogs.errors.length);
8786
}
8887

8988
beforeAll(async (s) => {
90-
let server: ViteDevServer | http.Server | PreviewServer | undefined;
89+
let server: ViteDevServer | PreviewServer | undefined;
9190

9291
const suite = s as RunnerTestFile;
9392

@@ -262,7 +261,7 @@ export async function loadConfig(configEnv: ConfigEnv) {
262261
}
263262

264263
export async function startDefaultServe(): Promise<
265-
ViteDevServer | http.Server | PreviewServer
264+
ViteDevServer | PreviewServer
266265
> {
267266
setupConsoleWarnCollector(serverLogs.warns);
268267

@@ -309,6 +308,7 @@ export async function startDefaultServe(): Promise<
309308
const previewServer = await preview(previewConfig);
310309
// prevent preview change NODE_ENV
311310
process.env.NODE_ENV = _nodeEnv;
311+
viteServer = previewServer;
312312
viteTestUrl = previewServer!.resolvedUrls!.local[0]!;
313313
if (previewServer.config.base === "/") {
314314
viteTestUrl = viteTestUrl.replace(/\/$/, "");

packages/vite-plugin-cloudflare/src/context.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {
1313
} from "./plugin-config";
1414
import type { MiniflareOptions } from "miniflare";
1515
import type * as vite from "vite";
16+
import type { Unstable_Config } from "wrangler";
1617

1718
/**
1819
* Used to store state that should persist across server restarts.
@@ -148,6 +149,19 @@ export class PluginContext {
148149
: undefined;
149150
}
150151

152+
get allWorkerConfigs(): Unstable_Config[] {
153+
switch (this.resolvedPluginConfig.type) {
154+
case "workers":
155+
return Array.from(
156+
this.resolvedPluginConfig.environmentNameToWorkerMap.values()
157+
).map((worker) => worker.config);
158+
case "preview":
159+
return this.resolvedPluginConfig.workers;
160+
default:
161+
return [];
162+
}
163+
}
164+
151165
get entryWorkerConfig(): ResolvedWorkerConfig | undefined {
152166
if (this.resolvedPluginConfig.type !== "workers") {
153167
return;

packages/vite-plugin-cloudflare/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
} from "./plugins/nodejs-compat";
1414
import { outputConfigPlugin } from "./plugins/output-config";
1515
import { previewPlugin } from "./plugins/preview";
16+
import { shortcutsPlugin } from "./plugins/shortcuts";
1617
import { triggerHandlersPlugin } from "./plugins/trigger-handlers";
1718
import {
1819
virtualClientFallbackPlugin,
@@ -70,6 +71,7 @@ export function cloudflare(pluginConfig: PluginConfig = {}): vite.Plugin[] {
7071
configPlugin(ctx),
7172
devPlugin(ctx),
7273
previewPlugin(ctx),
74+
shortcutsPlugin(ctx),
7375
debugPlugin(ctx),
7476
triggerHandlersPlugin(ctx),
7577
virtualModulesPlugin(ctx),

0 commit comments

Comments
 (0)