Skip to content

Commit 478eb47

Browse files
add remote bindings support to getPlatformProxy
1 parent 17d23d8 commit 478eb47

File tree

9 files changed

+174
-29
lines changed

9 files changed

+174
-29
lines changed

.changeset/better-sheep-reply.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
add remote bindings support to `getPlatformProxy`
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { execSync } from "child_process";
2+
import { randomUUID } from "crypto";
3+
import assert from "node:assert";
4+
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
5+
import test, { after, before, describe } from "node:test";
6+
import { getPlatformProxy } from "wrangler";
7+
8+
if (
9+
!process.env.TEST_CLOUDFLARE_API_TOKEN ||
10+
!process.env.TEST_CLOUDFLARE_ACCOUNT_ID
11+
) {
12+
console.warn("No credentials provided, skipping test...");
13+
process.exit(0);
14+
}
15+
16+
const env = {
17+
...process.env,
18+
CLOUDFLARE_API_TOKEN: process.env.TEST_CLOUDFLARE_API_TOKEN,
19+
CLOUDFLARE_ACCOUNT_ID: process.env.TEST_CLOUDFLARE_ACCOUNT_ID,
20+
};
21+
22+
describe("getPlatformProxy remote-bindings", () => {
23+
const remoteWorkerName = `get-platform-proxy-remote-worker-test-${randomUUID().split("-")[0]}`;
24+
25+
before(async () => {
26+
const deployOut = execSync(
27+
`pnpm dlx wrangler deploy remote-worker.js --name ${remoteWorkerName} --compatibility-date 2025-06-19`,
28+
{
29+
stdio: "pipe",
30+
env,
31+
}
32+
);
33+
34+
if (
35+
!new RegExp(`Deployed\\s+${remoteWorkerName}\\b`).test(`${deployOut}`)
36+
) {
37+
throw new Error(`Failed to deploy ${remoteWorkerName}`);
38+
}
39+
40+
rmSync("./.tmp", { recursive: true, force: true });
41+
42+
mkdirSync("./.tmp");
43+
44+
writeFileSync(
45+
"./.tmp/wrangler.json",
46+
JSON.stringify(
47+
{
48+
name: "get-platform-proxy-fixture-test",
49+
compatibility_date: "2025-06-01",
50+
services: [
51+
{
52+
binding: "MY_WORKER",
53+
service: remoteWorkerName,
54+
experimental_remote: true,
55+
},
56+
],
57+
},
58+
undefined,
59+
2
60+
),
61+
"utf8"
62+
);
63+
});
64+
65+
test("getPlatformProxy works with remote bindings", async () => {
66+
const { env, dispose } = await getPlatformProxy({
67+
configPath: "./.tmp/wrangler.json",
68+
experimental: { remoteBindings: true },
69+
});
70+
71+
try {
72+
assert.strictEqual(
73+
await (await env.MY_WORKER.fetch("http://example.com")).text(),
74+
"Hello from a remote Worker part of the getPlatformProxy remote bindings fixture!"
75+
);
76+
} finally {
77+
await dispose();
78+
}
79+
});
80+
81+
after(async () => {
82+
execSync(`pnpm dlx wrangler delete --name ${remoteWorkerName}`, { env });
83+
rmSync("./.tmp", { recursive: true, force: true });
84+
});
85+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "@fixture/vitest-pool-workers-remote-bindings",
3+
"private": true,
4+
"description": "",
5+
"license": "ISC",
6+
"author": "",
7+
"type": "module",
8+
"scripts": {
9+
"test:ci": "node --test"
10+
},
11+
"devDependencies": {
12+
"@cloudflare/workers-tsconfig": "workspace:*",
13+
"@cloudflare/workers-types": "^4.20250617.0",
14+
"wrangler": "workspace:*"
15+
},
16+
"volta": {
17+
"extends": "../../package.json"
18+
}
19+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default {
2+
fetch() {
3+
return new Response(
4+
"Hello from a remote Worker part of the getPlatformProxy remote bindings fixture!"
5+
);
6+
},
7+
};

packages/wrangler/src/api/integrations/platform/index.ts

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,16 @@ import {
1111
buildMiniflareBindingOptions,
1212
buildSitesOptions,
1313
} from "../../../dev/miniflare";
14-
import { run } from "../../../experimental-flags";
1514
import { logger } from "../../../logger";
1615
import { getSiteAssetPaths } from "../../../sites";
1716
import { dedent } from "../../../utils/dedent";
17+
import { maybeStartOrUpdateRemoteProxySession } from "../../remoteBindings";
1818
import { CacheStorage } from "./caches";
1919
import { ExecutionContext } from "./executionContext";
2020
import { getServiceBindings } from "./services";
2121
import type { AssetsOptions } from "../../../assets";
2222
import type { Config, RawConfig, RawEnvironment } from "../../../config";
23+
import type { RemoteProxySession } from "../../remoteBindings";
2324
import type { IncomingRequestCfProperties } from "@cloudflare/workers-types/experimental";
2425
import type {
2526
MiniflareOptions,
@@ -58,6 +59,13 @@ export type GetPlatformProxyOptions = {
5859
* If `false` is specified no data is persisted on the filesystem.
5960
*/
6061
persist?: boolean | { path: string };
62+
/**
63+
* Experimental flags (note: these can change at any time and are not version-controlled use at your own risk)
64+
*/
65+
experimental?: {
66+
/** whether access to remove bindings should be enabled */
67+
remoteBindings?: boolean;
68+
};
6169
};
6270

6371
/**
@@ -103,27 +111,37 @@ export async function getPlatformProxy<
103111
>(
104112
options: GetPlatformProxyOptions = {}
105113
): Promise<PlatformProxy<Env, CfProperties>> {
114+
const experimentalRemoteBindings = !!options.experimental?.remoteBindings;
115+
106116
const env = options.environment;
107117

108118
const rawConfig = readConfig({
109119
config: options.configPath,
110120
env,
111121
});
112122

113-
const miniflareOptions = await run(
114-
{
115-
MULTIWORKER: false,
116-
RESOURCES_PROVISION: false,
117-
// TODO: when possible remote bindings should be made available for getPlatformProxy
118-
REMOTE_BINDINGS: false,
119-
},
120-
() => getMiniflareOptionsFromConfig(rawConfig, env, options)
123+
let remoteProxySession: RemoteProxySession | undefined = undefined;
124+
if (experimentalRemoteBindings && rawConfig.configPath) {
125+
const a = await maybeStartOrUpdateRemoteProxySession(
126+
rawConfig.configPath,
127+
null
128+
);
129+
remoteProxySession = a?.session;
130+
}
131+
132+
const miniflareOptions = await getMiniflareOptionsFromConfig(
133+
rawConfig,
134+
env,
135+
options,
136+
remoteProxySession?.remoteProxyConnectionString
121137
);
122138

123139
const mf = new Miniflare({
124140
script: "",
141+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
142+
// @ts-ignore
125143
modules: true,
126-
...(miniflareOptions as Record<string, unknown>),
144+
...miniflareOptions,
127145
});
128146

129147
const bindings: Env = await mf.getBindings();
@@ -136,15 +154,19 @@ export async function getPlatformProxy<
136154
cf: cf as CfProperties,
137155
ctx: new ExecutionContext(),
138156
caches: new CacheStorage(),
139-
dispose: () => mf.dispose(),
157+
dispose: async () => {
158+
await remoteProxySession?.dispose();
159+
await mf.dispose();
160+
},
140161
};
141162
}
142163

143164
// this is only used by getPlatformProxy
144165
async function getMiniflareOptionsFromConfig(
145166
rawConfig: Config,
146167
env: string | undefined,
147-
options: GetPlatformProxyOptions
168+
options: GetPlatformProxyOptions,
169+
remoteProxyConnectionString?: RemoteProxyConnectionString
148170
): Promise<Partial<MiniflareOptions>> {
149171
const bindings = getBindings(rawConfig, env, true, {});
150172

@@ -183,8 +205,7 @@ async function getMiniflareOptionsFromConfig(
183205
containers: undefined,
184206
containerBuildId: undefined,
185207
},
186-
undefined,
187-
false
208+
remoteProxyConnectionString
188209
);
189210

190211
const defaultPersistRoot = getMiniflarePersistRoot(options.persist);
@@ -320,8 +341,9 @@ export function unstable_getMiniflareWorkerOptions(
320341
containers: undefined,
321342
containerBuildId: undefined,
322343
},
323-
options?.remoteProxyConnectionString,
324-
options?.remoteBindingsEnabled ?? false
344+
options?.remoteBindingsEnabled
345+
? options?.remoteProxyConnectionString
346+
: undefined
325347
);
326348

327349
// This function is currently only exported for the Workers Vitest pool.

packages/wrangler/src/api/startDevWorker/LocalRuntimeController.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,7 @@ export class LocalRuntimeController extends RuntimeController {
234234
this.#log,
235235
configBundle,
236236
this.#proxyToUserWorkerAuthenticationSecret,
237-
this.#remoteProxySessionData?.session?.remoteProxyConnectionString,
238-
!!experimentalRemoteBindings
237+
this.#remoteProxySessionData?.session?.remoteProxyConnectionString
239238
);
240239
options.liveReload = false; // TODO: set in buildMiniflareOptions once old code path is removed
241240
if (this.#mf === undefined) {

packages/wrangler/src/api/startDevWorker/MultiworkerRuntimeController.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,7 @@ export class MultiworkerRuntimeController extends LocalRuntimeController {
134134
await convertToConfigBundle(data),
135135
this.#proxyToUserWorkerAuthenticationSecret,
136136
this.#remoteProxySessionsData.get(data.config.name)?.session
137-
?.remoteProxyConnectionString,
138-
!!experimentalRemoteBindings
137+
?.remoteProxyConnectionString
139138
);
140139

141140
this.#options.set(data.config.name, {

packages/wrangler/src/dev/miniflare.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -535,13 +535,14 @@ type MiniflareBindingsConfig = Pick<
535535
// each plugin options schema and use those
536536
export function buildMiniflareBindingOptions(
537537
config: MiniflareBindingsConfig,
538-
remoteProxyConnectionString: RemoteProxyConnectionString | undefined,
539-
remoteBindingsEnabled: boolean
538+
remoteProxyConnectionString: RemoteProxyConnectionString | undefined
540539
): {
541540
bindingOptions: WorkerOptionsBindings;
542541
internalObjects: CfDurableObject[];
543542
externalWorkers: WorkerOptions[];
544543
} {
544+
const remoteBindingsEnabled = !!remoteProxyConnectionString;
545+
545546
const bindings = config.bindings;
546547

547548
// Setup blob and module bindings
@@ -1289,8 +1290,7 @@ export async function buildMiniflareOptions(
12891290
log: Log,
12901291
config: Omit<ConfigBundle, "rules">,
12911292
proxyToUserWorkerAuthenticationSecret: UUID,
1292-
remoteProxyConnectionString: RemoteProxyConnectionString | undefined,
1293-
remoteBindingsEnabled: boolean
1293+
remoteProxyConnectionString: RemoteProxyConnectionString | undefined
12941294
): Promise<{
12951295
options: Options;
12961296
internalObjects: CfDurableObject[];
@@ -1305,6 +1305,7 @@ export async function buildMiniflareOptions(
13051305
}
13061306
}
13071307

1308+
const remoteBindingsEnabled = !!remoteProxyConnectionString;
13081309
if (!remoteBindingsEnabled) {
13091310
if (config.bindings.ai) {
13101311
if (!didWarnAiAccountUsage) {
@@ -1339,11 +1340,7 @@ export async function buildMiniflareOptions(
13391340

13401341
const { sourceOptions, entrypointNames } = await buildSourceOptions(config);
13411342
const { bindingOptions, internalObjects, externalWorkers } =
1342-
buildMiniflareBindingOptions(
1343-
config,
1344-
remoteProxyConnectionString,
1345-
remoteBindingsEnabled
1346-
);
1343+
buildMiniflareBindingOptions(config, remoteProxyConnectionString);
13471344
const sitesOptions = buildSitesOptions(config);
13481345
const defaultPersistRoot = getDefaultPersistRoot(config.localPersistencePath);
13491346
const assetOptions = buildAssetOptions(config);

pnpm-lock.yaml

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)