Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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/modern-foxes-dream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": minor
---

add a `localBindingsOnly` option to `getPlatformProxy` to disable the remote aspect 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/tangy-steaks-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": minor
---

Add `localBindingsOnly` option to `startWorker` to disable remote bindings
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,30 @@
import { describe, expect, test } from "vitest";
import { getPlatformProxy } from "wrangler";
import type { Ai } from "@cloudflare/workers-types/experimental";

describe(
"getPlatformProxy - remote bindings with localBindingsOnly",
{ timeout: 50_000 },
() => {
test("getPlatformProxy works with remote bindings", async () => {
const { env, dispose } = await getPlatformProxy<{
AI: Ai;
}>({
configPath: "./wrangler.local-bindings-only.jsonc",
localBindingsOnly: true,
});

await expect(
(async () => {
await 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
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,38 @@ describe.skipIf(!CLOUDFLARE_ACCOUNT_ID)("startWorker - remote bindings", () => {
await worker.dispose();
});
});

it("doesn't connect to remote bindings when `localBindingsOnly` is set", 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 },
localBindingsOnly: true,
},
});

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
7 changes: 6 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,11 @@ export type GetPlatformProxyOptions = {
* If `false` is specified no data is persisted on the filesystem.
*/
persist?: boolean | { path: string };
/**
* Wether only local bindings should be used. This effectively disables the remote aspect of remote bindings
* (exactly as if their `remote` configuration was changed from `true` to `false`)
*/
localBindingsOnly?: boolean;
};

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

let remoteProxySession: RemoteProxySession | undefined = undefined;
if (config.configPath) {
if (config.configPath && !options.localBindingsOnly) {
remoteProxySession = (
(await maybeStartOrUpdateRemoteProxySession({
path: config.configPath,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ async function resolveDevConfig(
return {
auth,
remote: input.dev?.remote,
localBindingsOnly: input.dev?.localBindingsOnly,
server: {
hostname: input.dev?.server?.hostname || config.dev.ip,
port:
Expand Down Expand Up @@ -218,6 +219,7 @@ async function resolveBindings(
{
registry,
local: !input.dev?.remote,
localBindingsOnly: !!input.dev?.localBindingsOnly,
name: config.name,
}
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,10 @@ export class LocalRuntimeController extends RuntimeController {
try {
const configBundle = await convertToConfigBundle(data);

if (data.config.dev?.remote !== false) {
if (
!data.config.dev.localBindingsOnly &&
data.config.dev?.remote !== false
) {
// note: remote bindings use (transitively) LocalRuntimeController, so we need to import
// from the module lazily in order to avoid circular dependency issues
const { maybeStartOrUpdateRemoteProxySession, pickRemoteBindings } =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,10 @@ export class MultiworkerRuntimeController extends LocalRuntimeController {
try {
const configBundle = await convertToConfigBundle(data);

if (data.config.dev?.remote !== false) {
if (
!data.config.dev.localBindingsOnly &&
data.config.dev?.remote !== false
) {
// note: remote bindings use (transitively) LocalRuntimeController, so we need to import
// from the module lazily in order to avoid circular dependency issues
const { maybeStartOrUpdateRemoteProxySession } = await import(
Expand Down
2 changes: 2 additions & 0 deletions packages/wrangler/src/api/startDevWorker/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ export interface StartDevWorkerInput {
* - undefined (default): Run your Worker's code locally, and any configured remote bindings remotely
*/
remote?: boolean | "minimal";
/** Whether only local bindings (no remote ones) should be used during local development */
localBindingsOnly?: boolean;
/** Cloudflare Account credentials. Can be provided upfront or as a function which will be called only when required. */
auth?: AsyncHook<CfAccount, [Pick<Config, "account_id">]>; // provide config.account_id as a hook param
/** Whether local storage (KV, Durable Objects, R2, D1, etc) is persisted. You can also specify the directory to persist data to. */
Expand Down
7 changes: 2 additions & 5 deletions packages/wrangler/src/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,10 +214,8 @@ export const dev = createCommand({
},
local: {
alias: "l",
describe: "Run on my machine",
describe: "Run fully locally (this also disables all remote bindings)",
type: "boolean",
deprecated: true,
hidden: true,
},
minify: {
describe: "Minify the script",
Expand Down Expand Up @@ -288,7 +286,7 @@ export const dev = createCommand({
}
},
async handler(args) {
const devInstance = await startDev(args);
const devInstance = await startDev({ ...args, forceLocal: args.local });
assert(devInstance.devEnv !== undefined);
await events.once(devInstance.devEnv, "teardown");
await Promise.all(devInstance.secondary.map((d) => d.teardown()));
Expand Down Expand Up @@ -454,7 +452,6 @@ export function getInferredHost(
* If `undefined` it defaults to the standard .env files from `getDefaultEnvFiles()`.
* @param local Whether the dev server should run locally.
* @param args Additional arguments for the dev server.
* @param remoteBindingsEnabled Whether remote bindings are enabled, defaults to the value of the `REMOTE_BINDINGS` flag.
* @returns The bindings for the Cloudflare Worker.
*/
export function getBindings(
Expand Down
8 changes: 8 additions & 0 deletions packages/wrangler/src/dev/start-dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,14 @@ async function setupDevEnv(
auth,
remote:
args.remote || (args.forceLocal || args.local ? false : undefined),
localBindingsOnly:
// Note: this ternary is a hack, just to make sure that `wrangler pages dev`
// supports the AI binding (the issue is that `wrangler pages dev` sets
// `forceLocal` to true, so it should disable all remote bindings, but it
// still needs to support the AI bindings regardless because removing such
// support now would be a breaking change)
// TODO: remove this hack in wrangler v5 (the following line should just be `!!args.forceLocal,`)
args.enablePagesAssetsServiceBinding ? false : !!args.forceLocal,
server: {
hostname: args.ip,
port: args.port,
Expand Down
Loading
Loading