Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 0c53ad2

Browse files
authored
Fix inconsistency: services have binding not name in wrangler.toml (#302)
* Allow helper `parsePluginWranglerConfig` to be given `Log` instances * Fix inconsistency: services have `binding` not `name` in `wrangler.toml` I noticed that in Workers docs and Miniflare there's inconsistency in how services are declared: - Workers expect `binding` key https://github.com/cloudflare/cloudflare-docs/blob/a65a35843ffb7b69caf2758cc5f2ad805251d166/content/workers/wrangler/configuration.md?plain=1#L212-L220 - Miniflare expects `name` I added 2 graceful and 2 error scenarios: - (graceful) accept `name` with warning if `binding` is missing - (graceful) accept `name` and `binding` both given and the same - (error) throw if both `binding` and `name` are given but they don't match - (error) throw if neither `binding` not `name` is given I decided to handle it with errors, not with mutually exclusive presence of either `name` or `binding` in interface `WranglerServiceConfig` because `name` should go away - it's just a behavior mismatch. Related: #280 Syntax in wrangler2: cloudflare/workers-sdk#906
1 parent 7f35bdd commit 0c53ad2

File tree

5 files changed

+137
-11
lines changed

5 files changed

+137
-11
lines changed

packages/core/src/error.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ export type MiniflareCoreErrorCode =
77
| "ERR_MOUNT" // Error whilst mounting worker
88
| "ERR_MOUNT_NAME_MISMATCH" // Mounted name must match service name if defined
99
| "ERR_SERVICE_NOT_MOUNTED" // Mount for service binding not found
10+
| "ERR_SERVICE_NO_NAME" // In service definition in wranger.toml three's no `binding` (and no deprecated `name` either)
11+
| "ERR_SERVICE_NAME_MISMATCH" // In service definition in wranger.toml three's both `name` and `binding` and they don't match
1012
| "ERR_INVALID_UPSTREAM"; // Invalid upstream URL
1113

1214
export class MiniflareCoreError extends MiniflareError<MiniflareCoreErrorCode> {}

packages/core/src/plugins/bindings.ts

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -243,10 +243,42 @@ export class BindingsPlugin
243243
const allServices: WranglerServiceConfig[] = [];
244244
if (services) allServices.push(...services);
245245
if (experimental_services) allServices.push(...experimental_services);
246-
return allServices?.reduce((services, { name, service, environment }) => {
247-
services[name] = { service, environment };
248-
return services;
249-
}, {} as ServiceBindingsOptions);
246+
const getBindingName = (
247+
service: string,
248+
{ name, binding }: Partial<WranglerServiceConfig>
249+
): string => {
250+
if (name && binding && name !== binding) {
251+
throw new MiniflareCoreError(
252+
"ERR_SERVICE_NAME_MISMATCH",
253+
`Service "${service}" declared with name=${name} and binding=${binding}.
254+
\`binding\` key should be used to define binding names.`
255+
);
256+
} else if (binding === undefined) {
257+
if (name) {
258+
log.warn(
259+
`Service "${service}" is declared using deprecated syntax. Key \`name\` should be renamed to \`binding\`.`
260+
);
261+
return name;
262+
}
263+
throw new MiniflareCoreError(
264+
"ERR_SERVICE_NO_NAME",
265+
`Service "${service}" declared with neither \`bindding\` nor \`name\`.
266+
\`binding\` key should be used to define binding names.`
267+
);
268+
}
269+
270+
return binding;
271+
};
272+
return allServices?.reduce(
273+
(services, { name, binding, service, environment }) => {
274+
services[getBindingName(service, { name, binding })] = {
275+
service,
276+
environment,
277+
};
278+
return services;
279+
},
280+
{} as ServiceBindingsOptions
281+
);
250282
},
251283
})
252284
serviceBindings?: ServiceBindingsOptions;

packages/core/test/plugins/bindings.spec.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@ import {
1313
} from "@miniflare/core";
1414
import {
1515
Compatibility,
16+
LogLevel,
1617
NoOpLog,
1718
PluginContext,
1819
getRequestContext,
1920
viewToBuffer,
2021
} from "@miniflare/shared";
22+
import { TestLog } from "@miniflare/shared-test";
2123
import {
2224
getObjectProperties,
2325
logPluginOptions,
@@ -124,9 +126,12 @@ test("BindingsPlugin: parses options from wrangler config", async (t) => {
124126
services: [
125127
{ name: "SERVICE1", service: "service1", environment: "development" },
126128
{ name: "SERVICE2", service: "service2", environment: "production" },
129+
{ binding: "SERVICE_A", service: "service1", environment: "development" },
130+
{ binding: "SERVICE_B", service: "service2", environment: "production" },
127131
],
128132
experimental_services: [
129133
{ name: "SERVICE3", service: "service3", environment: "staging" },
134+
{ binding: "SERVICE_C", service: "service3", environment: "staging" },
130135
],
131136
miniflare: {
132137
globals: { KEY5: "value5", KEY6: false, KEY7: 10 },
@@ -143,6 +148,9 @@ test("BindingsPlugin: parses options from wrangler config", async (t) => {
143148
SERVICE1: { service: "service1", environment: "development" },
144149
SERVICE2: { service: "service2", environment: "production" },
145150
SERVICE3: { service: "service3", environment: "staging" },
151+
SERVICE_A: { service: "service1", environment: "development" },
152+
SERVICE_B: { service: "service2", environment: "production" },
153+
SERVICE_C: { service: "service3", environment: "staging" },
146154
},
147155
});
148156

@@ -161,6 +169,90 @@ test("BindingsPlugin: parses options from wrangler config", async (t) => {
161169
KEY4: "42",
162170
});
163171
});
172+
173+
test("BindingsPlugin: logs warning if `name` is used instead of `binding`", async (t) => {
174+
const log = new TestLog();
175+
const service = "service123";
176+
177+
parsePluginWranglerConfig(
178+
BindingsPlugin,
179+
{
180+
services: [{ name: "SERVICE123", service, environment: "development" }],
181+
},
182+
"",
183+
log
184+
);
185+
186+
// Check warning logged
187+
const warnings = log.logsAtLevel(LogLevel.WARN);
188+
t.is(warnings.length, 1);
189+
const [warning] = warnings;
190+
191+
t.true(warning.includes(service));
192+
t.regex(
193+
warning,
194+
/Service "\w+" is declared using deprecated syntax. Key `name` should be renamed to `binding`./
195+
);
196+
});
197+
198+
test("BindingsPlugin: logs no warning if `name` and `binding` are both used but they are the same", async (t) => {
199+
const log = new TestLog();
200+
const service = "service123";
201+
const name = "SERVICE123";
202+
const binding = name;
203+
204+
parsePluginWranglerConfig(
205+
BindingsPlugin,
206+
{
207+
services: [{ name, binding, service, environment: "development" }],
208+
},
209+
"",
210+
log
211+
);
212+
213+
// Check no warnings logged
214+
const warnings = log.logsAtLevel(LogLevel.WARN);
215+
t.is(warnings.length, 0);
216+
});
217+
218+
test("BindingsPlugin: throws if `name` and `binding` are both present and don't match", (t) => {
219+
t.throws(
220+
() =>
221+
parsePluginWranglerConfig(BindingsPlugin, {
222+
services: [
223+
{
224+
name: "SERVICE1",
225+
binding: "SERVICE_A",
226+
service: "service1",
227+
environment: "development",
228+
},
229+
],
230+
}),
231+
{
232+
instanceOf: MiniflareCoreError,
233+
code: "ERR_SERVICE_NAME_MISMATCH",
234+
}
235+
);
236+
});
237+
238+
test("BindingsPlugin: throws if `name` and `binding` are both absent", (t) => {
239+
t.throws(
240+
() =>
241+
parsePluginWranglerConfig(BindingsPlugin, {
242+
services: [
243+
{
244+
service: "service1",
245+
environment: "development",
246+
},
247+
],
248+
}),
249+
{
250+
instanceOf: MiniflareCoreError,
251+
code: "ERR_SERVICE_NO_NAME",
252+
}
253+
);
254+
});
255+
164256
test("BindingsPlugin: logs options", (t) => {
165257
// wranglerOptions should contain [kWranglerBindings]
166258
const wranglerOptions = parsePluginWranglerConfig(BindingsPlugin, {

packages/shared-test/src/plugin.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
BeforeSetupResult,
44
Context,
55
ExtractOptions,
6+
Log,
67
Mount,
78
NoOpLog,
89
Option,
@@ -27,15 +28,12 @@ export function parsePluginArgv<Plugin extends PluginSignature>(
2728
export function parsePluginWranglerConfig<Plugin extends PluginSignature>(
2829
plugin: Plugin,
2930
config: WranglerConfig,
30-
configDir = ""
31+
configDir = "",
32+
log: Log = new NoOpLog()
3133
): ExtractOptions<InstanceType<Plugin>> {
3234
const result = {} as ExtractOptions<InstanceType<Plugin>>;
3335
for (const [key, meta] of plugin.prototype.opts?.entries() ?? []) {
34-
(result as any)[key] = meta.fromWrangler?.(
35-
config,
36-
configDir,
37-
new NoOpLog()
38-
);
36+
(result as any)[key] = meta.fromWrangler?.(config, configDir, log);
3937
}
4038
return result;
4139
}

packages/shared/src/wrangler.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import { ModuleRuleType } from "./runner";
66
export type UsageModel = "bundled" | "unbound";
77

88
export interface WranglerServiceConfig {
9-
name: string;
9+
/** @deprecated Use `binding` instead */
10+
name?: string;
11+
binding?: string;
1012
service: string;
1113
environment: string;
1214
}

0 commit comments

Comments
 (0)