Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a3ed9a7
Fix bindings with `remote: true` being logged as `remote` when run vi…
dario-piotrowicz Nov 3, 2025
efc3494
remote outdated tsdoc param
dario-piotrowicz Nov 3, 2025
70383ae
Update the description of the `--local` flag for the `wrangler dev` c…
dario-piotrowicz Nov 3, 2025
2230f82
fixup! Update the description of the `--local` flag for the `wrangler…
dario-piotrowicz Nov 3, 2025
4ebead9
add changeset and disable remote bindings when `localBindingsOnly` is…
dario-piotrowicz Nov 3, 2025
443b6cb
make sure startWorker doesn't connect to remote bindings when localBi…
dario-piotrowicz Nov 4, 2025
07efcac
add hack not to break `wrangler pages dev`
dario-piotrowicz Nov 4, 2025
ba50b6a
add `localBindingsOnly` option to `getPlatformProxy` to disable the r…
dario-piotrowicz Nov 4, 2025
0fc182b
use `remoteBindings` instead of `localBindingsOnly`
dario-piotrowicz Nov 4, 2025
ef2101d
Apply suggestions from code review
dario-piotrowicz Nov 4, 2025
1476462
remove unnecessary timeout
dario-piotrowicz Nov 4, 2025
0c14ec8
fix incorrect check in `getPlatformProxy`
dario-piotrowicz Nov 4, 2025
bfed066
remove unnecessary function wrapping
dario-piotrowicz Nov 4, 2025
b9b0e54
remove unnecessary function invocation
dario-piotrowicz Nov 4, 2025
7b9a624
remove `startDev` change
dario-piotrowicz Nov 4, 2025
0777e3b
remove unnecessary new `remoteBindings` option from `startWorker`
dario-piotrowicz Nov 4, 2025
a061a10
add `remoteBindings` option to vite plugin
dario-piotrowicz Nov 4, 2025
fa36841
skip e2e vite test on windows
dario-piotrowicz Nov 5, 2025
cf7cc62
Update packages/wrangler/src/api/integrations/platform/index.ts
dario-piotrowicz Nov 5, 2025
d335b5b
revert unnecessary spreading
dario-piotrowicz Nov 5, 2025
3db45f9
rename `remoteBindingsEnabled` to `remoteBindings`
dario-piotrowicz Nov 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cyan-pets-prove.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cloudflare/vite-plugin": minor
---

add a `remoteBindings` option to allow the disabling of remote bindings
5 changes: 5 additions & 0 deletions .changeset/modern-foxes-dream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": minor
---

add a `remoteBindings` option to `getPlatformProxy` to allow the disabling of remote bindings
5 changes: 5 additions & 0 deletions .changeset/shy-teams-appear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": patch
---

Update the description of the `--local` flag for the `wrangler dev` command to clarify that it disables remote bindings, also un-deprecate and un-hide it
5 changes: 5 additions & 0 deletions .changeset/thirty-lights-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": patch
---

Fix bindings with `remote: true` being logged as `remote` when run via `wrangler dev --local`
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { describe, expect, test } from "vitest";
import { getPlatformProxy } from "wrangler";
import type { Ai } from "@cloudflare/workers-types/experimental";

describe("getPlatformProxy - remote bindings with remoteBindings: false", () => {
test("getPlatformProxy works with remote bindings", async () => {
const { env, dispose } = await getPlatformProxy<{
AI: Ai;
}>({
configPath: "./wrangler.remote-bindings-false.jsonc",
remoteBindings: false,
});

await expect(
env.AI.run("@cf/meta/llama-3.1-8b-instruct-fp8", {
messages: [],
})
).rejects.toThrowErrorMatchingInlineSnapshot(
`[Error: Binding AI needs to be run remotely]`
);

await dispose();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "get-platform-proxy-remote-bindings-with-local-bindings-only-test",
"compatibility_date": "2025-05-07",
"ai": {
"binding": "AI",
"remote": true,
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "@cloudflare/vite-plugin-e2e-remote-bindings-disabled",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"build": "vite build",
"buildAndPreview": "vite build && vite preview",
"dev": "vite",
"lint": "eslint .",
"preview": "vite preview"
},
"devDependencies": {
"@cloudflare/vite-plugin": "*",
"@cloudflare/workers-types": "^4.20250204.0",
"@eslint/js": "^9.19.0",
"eslint": "^9.19.0",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.18",
"globals": "^15.14.0",
"typescript": "~5.7.2",
"typescript-eslint": "^8.22.0",
"vite": "^6.1.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default {
async fetch(_req, env) {
const content = await env.AI.run("@cf/meta/llama-2-7b-chat-fp16", {
messages: [],
});

return Response.json({
response: content.response,
});
},
} satisfies ExportedHandler<{ AI: Ai }>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.node.json" },
{ "path": "./tsconfig.worker.json" }
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,

/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,

/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.node.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.worker.tsbuildinfo",
"types": ["@cloudflare/workers-types/2023-07-01", "vite/client"]
},
"include": ["src"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { cloudflare } from "@cloudflare/vite-plugin";
import { defineConfig } from "vite";

export default defineConfig({
plugins: [
cloudflare({
configPath: "./wrangler.jsonc",
inspectorPort: false,
remoteBindings: false,
persistState: false,
}),
],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "cloudflare-vite-e2e-remote-bindings-disabled",
"main": "./src/index.ts",
"compatibility_date": "2024-12-30",
"compatibility_flags": ["nodejs_compat"],
"ai": {
"binding": "AI",
"remote": true,
},
}
25 changes: 25 additions & 0 deletions packages/vite-plugin-cloudflare/e2e/remote-bindings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,28 @@ if (!process.env.CLOUDFLARE_ACCOUNT_ID || !process.env.CLOUDFLARE_API_TOKEN) {
});
});
}

describe("remote bindings disabled", () => {
const projectPath = seed("remote-bindings-disabled", "pnpm");

describe.each(commands)('with "%s" command', (command) => {
// On Windows the path for the miniflare dependency gets pretty long and this fails in node < 22.7
// (see: https://github.com/shellscape/jsx-email/issues/225#issuecomment-2420567832), so
// we need to skip this on windows since in CI we're using node 20
// we should look into re-enable this once we can move to a node a newer version of node
test.skipIf(process.platform === "win32")(
"cannot connect to remote bindings",
async ({ expect }) => {
const proc = await runLongLived("pnpm", command, projectPath);
const url = await waitForReady(proc);

const response = await fetch(url);

const responseText = await response.text();

expect(responseText).toContain("Error");
expect(responseText).toContain("Binding AI needs to be run remotely");
}
);
});
});
35 changes: 20 additions & 15 deletions packages/vite-plugin-cloudflare/src/miniflare-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,13 +413,16 @@ export async function getDevMiniflareOptions(config: {
: undefined;

const remoteProxySessionData =
await maybeStartOrUpdateRemoteProxySession(
{
name: workerConfig.name,
bindings: bindings ?? {},
},
preExistingRemoteProxySession ?? null
);
!resolvedPluginConfig.remoteBindings
? // if remote bindings are not enabled then the proxy session can simply be null
null
: await maybeStartOrUpdateRemoteProxySession(
{
name: workerConfig.name,
bindings: bindings ?? {},
},
preExistingRemoteProxySession ?? null
);

if (workerConfig.configPath && remoteProxySessionData) {
remoteProxySessionsDataMap.set(
Expand Down Expand Up @@ -738,14 +741,16 @@ export async function getPreviewMiniflareOptions(config: {
? remoteProxySessionsDataMap.get(workerConfig.configPath)
: undefined;

const remoteProxySessionData =
await maybeStartOrUpdateRemoteProxySession(
{
name: workerConfig.name,
bindings: bindings ?? {},
},
preExistingRemoteProxySessionData ?? null
);
const remoteProxySessionData = !resolvedPluginConfig.remoteBindings
? // if remote bindings are not enabled then the proxy session can simply be null
null
: await maybeStartOrUpdateRemoteProxySession(
{
name: workerConfig.name,
bindings: bindings ?? {},
},
preExistingRemoteProxySessionData ?? null
);

if (workerConfig.configPath && remoteProxySessionData) {
remoteProxySessionsDataMap.set(
Expand Down
5 changes: 5 additions & 0 deletions packages/vite-plugin-cloudflare/src/plugin-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export interface PluginConfig extends EntryWorkerConfig {
auxiliaryWorkers?: AuxiliaryWorkerConfig[];
persistState?: PersistState;
inspectorPort?: number | false;
remoteBindings?: boolean;
experimental?: Experimental;
}

Expand All @@ -58,6 +59,7 @@ interface BaseResolvedConfig {
persistState: PersistState;
inspectorPort: number | false | undefined;
experimental: Experimental;
remoteBindings: boolean;
}

export interface AssetsOnlyResolvedConfig extends BaseResolvedConfig {
Expand Down Expand Up @@ -123,6 +125,7 @@ export function resolvePluginConfig(
if (viteEnv.isPreview) {
return {
...shared,
remoteBindings: pluginConfig.remoteBindings ?? true,
type: "preview",
workers: getWorkerConfigs(root),
};
Expand Down Expand Up @@ -151,6 +154,7 @@ export function resolvePluginConfig(
cloudflareEnv,
config: entryWorkerResolvedConfig.config,
configPaths,
remoteBindings: pluginConfig.remoteBindings ?? true,
rawConfigs: {
entryWorker: entryWorkerResolvedConfig,
},
Expand Down Expand Up @@ -231,6 +235,7 @@ export function resolvePluginConfig(
entryWorkerEnvironmentName,
nodeJsCompatMap,
staticRouting,
remoteBindings: pluginConfig.remoteBindings ?? true,
rawConfigs: {
entryWorker: entryWorkerResolvedConfig,
auxiliaryWorkers: auxiliaryWorkersResolvedConfigs,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,30 +174,17 @@ describe.skipIf(!CLOUDFLARE_ACCOUNT_ID)(
);

// This should only include logs from the user Wrangler session (i.e. a single list of attached bindings, and only one ready message)
// Logs order are not deterministic, so we match against to possible outputs.
const output1 = dedent`
Your Worker has access to the following bindings:
Binding Resource Mode
env.AI AI remote
▲ [WARNING] AI bindings always access remote resources, and so may incur usage charges even in local dev. To suppress this warning, set \`remote: true\` for the binding definition in your configuration file.
⎔ Starting local server...
[wrangler:info] Ready on http://<HOST>:<PORT>
[wrangler:info] GET / 200 OK (TIMINGS)`;

const output2 = dedent`
Your Worker has access to the following bindings:
Binding Resource Mode
env.AI AI remote
▲ [WARNING] AI bindings always access remote resources, and so may incur usage charges even in local dev. To suppress this warning, set \`remote: true\` for the binding definition in your configuration file.
⎔ Starting local server...
[wrangler:info] Ready on http://<HOST>:<PORT>
[wrangler:info] GET / 200 OK (TIMINGS)`;

const normalizedOutput = normalizeOutput(worker.currentOutput);

expect(
normalizedOutput === output1 || normalizedOutput === output2
).toEqual(true);
expect(normalizedOutput).toMatchInlineSnapshot(`
"Your Worker has access to the following bindings:
Binding Resource Mode
env.AI AI remote
▲ [WARNING] AI bindings always access remote resources, and so may incur usage charges even in local dev. To suppress this warning, set \`remote: true\` for the binding definition in your configuration file.
⎔ Starting local server...
[wrangler:info] Ready on http://<HOST>:<PORT>
[wrangler:info] GET / 200 OK (TIMINGS)"
`);
});

describe("shows helpful error logs", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,36 @@ describe.skipIf(!CLOUDFLARE_ACCOUNT_ID)("startWorker - remote bindings", () => {
await worker.dispose();
});
});

it("doesn't connect to remote bindings when `remote` is set to `false`", async () => {
const helper = new WranglerE2ETestHelper();
await helper.seed(resolve(__dirname, "./workers"));
await helper.seed({
"wrangler.json": JSON.stringify({
name: "remote-bindings-test",
main: "ai.js",
compatibility_date: "2025-05-07",
ai: {
binding: "AI",
remote: true,
},
}),
});

await expect(async () => {
const worker = await startWorker({
config: `${helper.tmpPath}/wrangler.json`,
dev: {
inspector: false,
server: { port: 0 },
remote: false,
},
});

await worker.ready;

await worker.fetch("http://example.com");
}).rejects.toThrowErrorMatchingInlineSnapshot(
`[Error: Binding AI needs to be run remotely]`
);
});
12 changes: 12 additions & 0 deletions packages/wrangler/src/__tests__/dev/remote-bindings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,18 @@ describe("dev with remote bindings", { sequential: true }, () => {
await vi.waitFor(() => expect(std.out).toMatch(/Ready/), {
timeout: 2_000,
});
const bindingsPrintStart = std.out.indexOf(
"Your Worker has access to the following bindings:"
);
const bindingsPrintEnd = std.out.indexOf("⎔ Starting local server...") - 1;
expect(std.out.slice(bindingsPrintStart, bindingsPrintEnd))
.toMatchInlineSnapshot(`
"Your Worker has access to the following bindings:
Binding Resource Mode
env.KV_LOCAL_BINDING (mock-kv-namespace) KV Namespace local
env.KV_REMOTE_BINDING (mock-kv-namespace) KV Namespace local
"
`);
expect(proxyWorkerBindings).toEqual(undefined);
expect(workerOptions).toEqual([
expect.objectContaining({
Expand Down
6 changes: 5 additions & 1 deletion packages/wrangler/src/api/integrations/platform/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ export type GetPlatformProxyOptions = {
* If `false` is specified no data is persisted on the filesystem.
*/
persist?: boolean | { path: string };
/**
* Whether remote bindings should be enabled or not (defaults to `true`)
*/
remoteBindings?: boolean;
};

/**
Expand Down Expand Up @@ -138,7 +142,7 @@ export async function getPlatformProxy<
});

let remoteProxySession: RemoteProxySession | undefined = undefined;
if (config.configPath) {
if (config.configPath && options.remoteBindings !== false) {
remoteProxySession = (
(await maybeStartOrUpdateRemoteProxySession({
path: config.configPath,
Expand Down
Loading
Loading