wrangler 2.0.2 → 2.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cloudflare_CA.pem +18 -0
- package/bin/wrangler.js +23 -3
- package/package.json +4 -2
- package/pages/functions/buildWorker.ts +1 -1
- package/pages/functions/template-plugin.ts +3 -2
- package/src/__tests__/configuration.test.ts +205 -28
- package/src/__tests__/dev.test.tsx +137 -17
- package/src/__tests__/index.test.ts +55 -63
- package/src/__tests__/jest.setup.ts +8 -0
- package/src/__tests__/kv.test.ts +157 -130
- package/src/__tests__/pages.test.ts +34 -13
- package/src/__tests__/publish.test.ts +362 -14
- package/src/__tests__/r2.test.ts +55 -36
- package/src/__tests__/secret.test.ts +35 -0
- package/src/bundle.ts +32 -1
- package/src/cfetch/internal.ts +3 -0
- package/src/config/config.ts +1 -1
- package/src/config/environment.ts +40 -14
- package/src/config/validation.ts +103 -36
- package/src/create-worker-upload-form.ts +22 -8
- package/src/dev/dev.tsx +5 -2
- package/src/dev/local.tsx +6 -0
- package/src/entry.ts +64 -9
- package/src/index.tsx +119 -68
- package/src/kv.ts +55 -15
- package/src/pages.tsx +11 -5
- package/src/publish.ts +210 -21
- package/src/sites.tsx +13 -11
- package/src/user.tsx +11 -1
- package/src/worker.ts +8 -0
- package/wrangler-dist/cli.js +1430 -1004
package/src/bundle.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
|
+
import { builtinModules } from "node:module";
|
|
3
4
|
import * as path from "node:path";
|
|
4
5
|
import NodeGlobalsPolyfills from "@esbuild-plugins/node-globals-polyfill";
|
|
5
6
|
import NodeModulesPolyfills from "@esbuild-plugins/node-modules-polyfill";
|
|
@@ -16,6 +17,33 @@ type BundleResult = {
|
|
|
16
17
|
stop: (() => void) | undefined;
|
|
17
18
|
};
|
|
18
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Searches for any uses of node's builtin modules, and throws an error if it
|
|
22
|
+
* finds anything. This plugin is only used when nodeCompat is not enabled.
|
|
23
|
+
* Supports both regular node builtins, and the new "node:<MODULE>" format.
|
|
24
|
+
*/
|
|
25
|
+
const checkForNodeBuiltinsPlugin = {
|
|
26
|
+
name: "checkForNodeBuiltins",
|
|
27
|
+
setup(build: esbuild.PluginBuild) {
|
|
28
|
+
build.onResolve(
|
|
29
|
+
{
|
|
30
|
+
filter: new RegExp(
|
|
31
|
+
"^(" +
|
|
32
|
+
builtinModules.join("|") +
|
|
33
|
+
"|" +
|
|
34
|
+
builtinModules.map((module) => "node:" + module).join("|") +
|
|
35
|
+
")$"
|
|
36
|
+
),
|
|
37
|
+
},
|
|
38
|
+
() => {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Detected a Node builtin module import while Node compatibility is disabled.\nAdd node_compat = true to your wrangler.toml file to enable Node compatibility.`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
19
47
|
/**
|
|
20
48
|
* Generate a bundle for the worker identified by the arguments passed in.
|
|
21
49
|
*/
|
|
@@ -60,6 +88,7 @@ export async function bundleWorker(
|
|
|
60
88
|
format: entry.format,
|
|
61
89
|
rules,
|
|
62
90
|
});
|
|
91
|
+
|
|
63
92
|
const result = await esbuild.build({
|
|
64
93
|
...getEntryPoint(entry.file, serveAssetsFromWorker),
|
|
65
94
|
bundle: true,
|
|
@@ -87,7 +116,9 @@ export async function bundleWorker(
|
|
|
87
116
|
moduleCollector.plugin,
|
|
88
117
|
...(nodeCompat
|
|
89
118
|
? [NodeGlobalsPolyfills({ buffer: true }), NodeModulesPolyfills()]
|
|
90
|
-
:
|
|
119
|
+
: // we use checkForNodeBuiltinsPlugin to throw a nicer error
|
|
120
|
+
// if we find node builtins when nodeCompat isn't turned on
|
|
121
|
+
[checkForNodeBuiltinsPlugin]),
|
|
91
122
|
],
|
|
92
123
|
...(jsxFactory && { jsxFactory }),
|
|
93
124
|
...(jsxFragment && { jsxFragment }),
|
package/src/cfetch/internal.ts
CHANGED
|
@@ -112,6 +112,9 @@ function addAuthorizationHeader(
|
|
|
112
112
|
* doesn't return json. We inline the implementation and try not to share
|
|
113
113
|
* any code with the other calls. We should push back on any new APIs that
|
|
114
114
|
* try to introduce non-"standard" response structures.
|
|
115
|
+
*
|
|
116
|
+
* Note: any calls to fetchKVGetValue must call encodeURIComponent on key
|
|
117
|
+
* before passing it
|
|
115
118
|
*/
|
|
116
119
|
|
|
117
120
|
export async function fetchKVGetValue(
|
package/src/config/config.ts
CHANGED
|
@@ -8,6 +8,24 @@ export interface Environment
|
|
|
8
8
|
extends EnvironmentInheritable,
|
|
9
9
|
EnvironmentNonInheritable {}
|
|
10
10
|
|
|
11
|
+
export type SimpleRoute = string;
|
|
12
|
+
export type ZoneIdRoute = {
|
|
13
|
+
pattern: string;
|
|
14
|
+
zone_id: string;
|
|
15
|
+
custom_domain?: boolean;
|
|
16
|
+
};
|
|
17
|
+
export type ZoneNameRoute = {
|
|
18
|
+
pattern: string;
|
|
19
|
+
zone_name: string;
|
|
20
|
+
custom_domain?: boolean;
|
|
21
|
+
};
|
|
22
|
+
export type CustomDomainRoute = { pattern: string; custom_domain: boolean };
|
|
23
|
+
export type Route =
|
|
24
|
+
| SimpleRoute
|
|
25
|
+
| ZoneIdRoute
|
|
26
|
+
| ZoneNameRoute
|
|
27
|
+
| CustomDomainRoute;
|
|
28
|
+
|
|
11
29
|
/**
|
|
12
30
|
* The `EnvironmentInheritable` interface declares all the configuration fields for an environment
|
|
13
31
|
* that can be inherited (and overridden) from the top-level environment.
|
|
@@ -74,13 +92,7 @@ interface EnvironmentInheritable {
|
|
|
74
92
|
*
|
|
75
93
|
* @inheritable
|
|
76
94
|
*/
|
|
77
|
-
routes:
|
|
78
|
-
| (
|
|
79
|
-
| string
|
|
80
|
-
| { pattern: string; zone_id: string }
|
|
81
|
-
| { pattern: string; zone_name: string }
|
|
82
|
-
)[]
|
|
83
|
-
| undefined;
|
|
95
|
+
routes: Route[] | undefined;
|
|
84
96
|
|
|
85
97
|
/**
|
|
86
98
|
* A route that your worker should be published to. Literally
|
|
@@ -91,13 +103,7 @@ interface EnvironmentInheritable {
|
|
|
91
103
|
*
|
|
92
104
|
* @inheritable
|
|
93
105
|
*/
|
|
94
|
-
route:
|
|
95
|
-
| (
|
|
96
|
-
| string
|
|
97
|
-
| { pattern: string; zone_id: string }
|
|
98
|
-
| { pattern: string; zone_name: string }
|
|
99
|
-
)
|
|
100
|
-
| undefined;
|
|
106
|
+
route: Route | undefined;
|
|
101
107
|
|
|
102
108
|
/**
|
|
103
109
|
* Path to a custom tsconfig
|
|
@@ -236,6 +242,8 @@ interface EnvironmentNonInheritable {
|
|
|
236
242
|
class_name: string;
|
|
237
243
|
/** The script where the Durable Object is defined (if it's external to this worker) */
|
|
238
244
|
script_name?: string;
|
|
245
|
+
/** The service environment of the script_name to bind to */
|
|
246
|
+
environment?: string;
|
|
239
247
|
}[];
|
|
240
248
|
};
|
|
241
249
|
|
|
@@ -279,6 +287,24 @@ interface EnvironmentNonInheritable {
|
|
|
279
287
|
preview_bucket_name?: string;
|
|
280
288
|
}[];
|
|
281
289
|
|
|
290
|
+
/**
|
|
291
|
+
* Specifies service bindings (worker-to-worker) that are bound to this Worker environment.
|
|
292
|
+
*
|
|
293
|
+
* NOTE: This field is not automatically inherited from the top level environment,
|
|
294
|
+
* and so must be specified in every named environment.
|
|
295
|
+
*
|
|
296
|
+
* @default `[]`
|
|
297
|
+
* @nonInheritable
|
|
298
|
+
*/
|
|
299
|
+
services: {
|
|
300
|
+
/** The binding name used to refer to the bound service. */
|
|
301
|
+
binding: string;
|
|
302
|
+
/** The name of the service. */
|
|
303
|
+
service: string;
|
|
304
|
+
/** The environment of the service (e.g. production, staging, etc). */
|
|
305
|
+
environment?: string;
|
|
306
|
+
}[];
|
|
307
|
+
|
|
282
308
|
/**
|
|
283
309
|
* "Unsafe" tables for features that aren't directly supported by wrangler.
|
|
284
310
|
*
|
package/src/config/validation.ts
CHANGED
|
@@ -352,7 +352,7 @@ function normalizeAndValidateDev(
|
|
|
352
352
|
): DevConfig {
|
|
353
353
|
const {
|
|
354
354
|
ip = "localhost",
|
|
355
|
-
port
|
|
355
|
+
port,
|
|
356
356
|
local_protocol = "http",
|
|
357
357
|
upstream_protocol = "https",
|
|
358
358
|
host,
|
|
@@ -562,19 +562,37 @@ function normalizeAndValidateModulePaths(
|
|
|
562
562
|
* or an object that looks like {pattern: string, zone_id: string }
|
|
563
563
|
*/
|
|
564
564
|
function isValidRouteValue(item: unknown): boolean {
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
565
|
+
if (!item) {
|
|
566
|
+
return false;
|
|
567
|
+
}
|
|
568
|
+
if (typeof item === "string") {
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
if (typeof item === "object") {
|
|
572
|
+
if (!hasProperty(item, "pattern") || typeof item.pattern !== "string") {
|
|
573
|
+
return false;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const otherKeys = Object.keys(item).length - 1; // minus one to subtract "pattern"
|
|
577
|
+
|
|
578
|
+
const hasZoneId =
|
|
579
|
+
hasProperty(item, "zone_id") && typeof item.zone_id === "string";
|
|
580
|
+
const hasZoneName =
|
|
581
|
+
hasProperty(item, "zone_name") && typeof item.zone_name === "string";
|
|
582
|
+
const hasCustomDomainFlag =
|
|
583
|
+
hasProperty(item, "custom_domain") &&
|
|
584
|
+
typeof item.custom_domain === "boolean";
|
|
585
|
+
|
|
586
|
+
if (otherKeys === 2 && hasCustomDomainFlag && (hasZoneId || hasZoneName)) {
|
|
587
|
+
return true;
|
|
588
|
+
} else if (
|
|
589
|
+
otherKeys === 1 &&
|
|
590
|
+
(hasZoneId || hasZoneName || hasCustomDomainFlag)
|
|
591
|
+
) {
|
|
592
|
+
return true;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
return false;
|
|
578
596
|
}
|
|
579
597
|
|
|
580
598
|
/**
|
|
@@ -583,7 +601,7 @@ function isValidRouteValue(item: unknown): boolean {
|
|
|
583
601
|
const isRoute: ValidatorFn = (diagnostics, field, value) => {
|
|
584
602
|
if (value !== undefined && !isValidRouteValue(value)) {
|
|
585
603
|
diagnostics.errors.push(
|
|
586
|
-
`Expected "${field}" to be either a string, or an object with shape { pattern, zone_id | zone_name }, but got ${JSON.stringify(
|
|
604
|
+
`Expected "${field}" to be either a string, or an object with shape { pattern, custom_domain, zone_id | zone_name }, but got ${JSON.stringify(
|
|
587
605
|
value
|
|
588
606
|
)}.`
|
|
589
607
|
);
|
|
@@ -613,7 +631,7 @@ const isRouteArray: ValidatorFn = (diagnostics, field, value) => {
|
|
|
613
631
|
}
|
|
614
632
|
if (invalidRoutes.length > 0) {
|
|
615
633
|
diagnostics.errors.push(
|
|
616
|
-
`Expected "${field}" to be an array of either strings or objects with the shape { pattern, zone_id | zone_name }, but these weren't valid: ${JSON.stringify(
|
|
634
|
+
`Expected "${field}" to be an array of either strings or objects with the shape { pattern, custom_domain, zone_id | zone_name }, but these weren't valid: ${JSON.stringify(
|
|
617
635
|
invalidRoutes,
|
|
618
636
|
null,
|
|
619
637
|
2
|
|
@@ -696,27 +714,12 @@ function normalizeAndValidateEnvironment(
|
|
|
696
714
|
diagnostics,
|
|
697
715
|
rawEnv,
|
|
698
716
|
"experimental_services",
|
|
699
|
-
`The "experimental_services" field is no longer supported.
|
|
700
|
-
"```\n" +
|
|
701
|
-
TOML.stringify({
|
|
702
|
-
unsafe: {
|
|
703
|
-
bindings: (rawEnv?.experimental_services || []).map(
|
|
704
|
-
(serviceDefinition) => {
|
|
705
|
-
return {
|
|
706
|
-
name: serviceDefinition.name,
|
|
707
|
-
type: "service",
|
|
708
|
-
service: serviceDefinition.service,
|
|
709
|
-
environment: serviceDefinition.environment,
|
|
710
|
-
};
|
|
711
|
-
}
|
|
712
|
-
),
|
|
713
|
-
},
|
|
714
|
-
}) +
|
|
715
|
-
"```",
|
|
717
|
+
`The "experimental_services" field is no longer supported. Simply rename the [experimental_services] field to [services].`,
|
|
716
718
|
true
|
|
717
719
|
);
|
|
718
720
|
|
|
719
721
|
experimental(diagnostics, rawEnv, "unsafe");
|
|
722
|
+
experimental(diagnostics, rawEnv, "services");
|
|
720
723
|
|
|
721
724
|
const route = validateRoute(diagnostics, topLevelEnv, rawEnv);
|
|
722
725
|
|
|
@@ -880,6 +883,16 @@ function normalizeAndValidateEnvironment(
|
|
|
880
883
|
validateBindingArray(envName, validateR2Binding),
|
|
881
884
|
[]
|
|
882
885
|
),
|
|
886
|
+
services: notInheritable(
|
|
887
|
+
diagnostics,
|
|
888
|
+
topLevelEnv,
|
|
889
|
+
rawConfig,
|
|
890
|
+
rawEnv,
|
|
891
|
+
envName,
|
|
892
|
+
"services",
|
|
893
|
+
validateBindingArray(envName, validateServiceBinding),
|
|
894
|
+
[]
|
|
895
|
+
),
|
|
883
896
|
unsafe: notInheritable(
|
|
884
897
|
diagnostics,
|
|
885
898
|
topLevelEnv,
|
|
@@ -1014,7 +1027,7 @@ const validateRule: ValidatorFn = (diagnostics, field, value) => {
|
|
|
1014
1027
|
|
|
1015
1028
|
if (!isOptionalProperty(rule, "fallthrough", "boolean")) {
|
|
1016
1029
|
diagnostics.errors.push(
|
|
1017
|
-
`
|
|
1030
|
+
`the field "fallthrough", when present, should be a boolean.`
|
|
1018
1031
|
);
|
|
1019
1032
|
isValid = false;
|
|
1020
1033
|
}
|
|
@@ -1135,7 +1148,7 @@ const validateDurableObjectBinding: ValidatorFn = (
|
|
|
1135
1148
|
return false;
|
|
1136
1149
|
}
|
|
1137
1150
|
|
|
1138
|
-
// Durable Object bindings must have a name and class_name, and optionally a script_name.
|
|
1151
|
+
// Durable Object bindings must have a name and class_name, and optionally a script_name and an environment.
|
|
1139
1152
|
let isValid = true;
|
|
1140
1153
|
if (!isRequiredProperty(value, "name", "string")) {
|
|
1141
1154
|
diagnostics.errors.push(`binding should have a string "name" field.`);
|
|
@@ -1147,7 +1160,21 @@ const validateDurableObjectBinding: ValidatorFn = (
|
|
|
1147
1160
|
}
|
|
1148
1161
|
if (!isOptionalProperty(value, "script_name", "string")) {
|
|
1149
1162
|
diagnostics.errors.push(
|
|
1150
|
-
`
|
|
1163
|
+
`the field "script_name", when present, should be a string.`
|
|
1164
|
+
);
|
|
1165
|
+
isValid = false;
|
|
1166
|
+
}
|
|
1167
|
+
// environment requires a script_name
|
|
1168
|
+
if (!isOptionalProperty(value, "environment", "string")) {
|
|
1169
|
+
diagnostics.errors.push(
|
|
1170
|
+
`the field "environment", when present, should be a string.`
|
|
1171
|
+
);
|
|
1172
|
+
isValid = false;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
if ("environment" in value && !("script_name" in value)) {
|
|
1176
|
+
diagnostics.errors.push(
|
|
1177
|
+
`binding should have a "script_name" field if "environment" is present.`
|
|
1151
1178
|
);
|
|
1152
1179
|
isValid = false;
|
|
1153
1180
|
}
|
|
@@ -1178,8 +1205,13 @@ const validateUnsafeBinding: ValidatorFn = (diagnostics, field, value) => {
|
|
|
1178
1205
|
const safeBindings = [
|
|
1179
1206
|
"plain_text",
|
|
1180
1207
|
"json",
|
|
1208
|
+
"wasm_module",
|
|
1209
|
+
"data_blob",
|
|
1210
|
+
"text_blob",
|
|
1181
1211
|
"kv_namespace",
|
|
1182
1212
|
"durable_object_namespace",
|
|
1213
|
+
"r2_bucket",
|
|
1214
|
+
"service",
|
|
1183
1215
|
];
|
|
1184
1216
|
|
|
1185
1217
|
if (safeBindings.includes(value.type)) {
|
|
@@ -1422,3 +1454,38 @@ const validateBindingsHaveUniqueNames = (
|
|
|
1422
1454
|
|
|
1423
1455
|
return !hasDuplicates;
|
|
1424
1456
|
};
|
|
1457
|
+
const validateServiceBinding: ValidatorFn = (diagnostics, field, value) => {
|
|
1458
|
+
if (typeof value !== "object" || value === null) {
|
|
1459
|
+
diagnostics.errors.push(
|
|
1460
|
+
`"services" bindings should be objects, but got ${JSON.stringify(value)}`
|
|
1461
|
+
);
|
|
1462
|
+
return false;
|
|
1463
|
+
}
|
|
1464
|
+
let isValid = true;
|
|
1465
|
+
// Service bindings must have a binding, service, and environment.
|
|
1466
|
+
if (!isRequiredProperty(value, "binding", "string")) {
|
|
1467
|
+
diagnostics.errors.push(
|
|
1468
|
+
`"${field}" bindings should have a string "binding" field but got ${JSON.stringify(
|
|
1469
|
+
value
|
|
1470
|
+
)}.`
|
|
1471
|
+
);
|
|
1472
|
+
isValid = false;
|
|
1473
|
+
}
|
|
1474
|
+
if (!isRequiredProperty(value, "service", "string")) {
|
|
1475
|
+
diagnostics.errors.push(
|
|
1476
|
+
`"${field}" bindings should have a string "service" field but got ${JSON.stringify(
|
|
1477
|
+
value
|
|
1478
|
+
)}.`
|
|
1479
|
+
);
|
|
1480
|
+
isValid = false;
|
|
1481
|
+
}
|
|
1482
|
+
if (!isOptionalProperty(value, "environment", "string")) {
|
|
1483
|
+
diagnostics.errors.push(
|
|
1484
|
+
`"${field}" bindings should have a string "environment" field but got ${JSON.stringify(
|
|
1485
|
+
value
|
|
1486
|
+
)}.`
|
|
1487
|
+
);
|
|
1488
|
+
isValid = false;
|
|
1489
|
+
}
|
|
1490
|
+
return isValid;
|
|
1491
|
+
};
|
|
@@ -32,20 +32,24 @@ export interface WorkerMetadata {
|
|
|
32
32
|
compatibility_flags?: string[];
|
|
33
33
|
usage_model?: "bundled" | "unbound";
|
|
34
34
|
migrations?: CfDurableObjectMigrations;
|
|
35
|
+
// If you add any new binding types here, also add it to safeBindings
|
|
36
|
+
// under validateUnsafeBinding in config/validation.ts
|
|
35
37
|
bindings: (
|
|
36
|
-
| { type: "kv_namespace"; name: string; namespace_id: string }
|
|
37
38
|
| { type: "plain_text"; name: string; text: string }
|
|
38
39
|
| { type: "json"; name: string; json: unknown }
|
|
39
40
|
| { type: "wasm_module"; name: string; part: string }
|
|
40
41
|
| { type: "text_blob"; name: string; part: string }
|
|
41
42
|
| { type: "data_blob"; name: string; part: string }
|
|
43
|
+
| { type: "kv_namespace"; name: string; namespace_id: string }
|
|
42
44
|
| {
|
|
43
45
|
type: "durable_object_namespace";
|
|
44
46
|
name: string;
|
|
45
47
|
class_name: string;
|
|
46
48
|
script_name?: string;
|
|
49
|
+
environment?: string;
|
|
47
50
|
}
|
|
48
51
|
| { type: "r2_bucket"; name: string; bucket_name: string }
|
|
52
|
+
| { type: "service"; name: string; service: string; environment?: string }
|
|
49
53
|
)[];
|
|
50
54
|
}
|
|
51
55
|
|
|
@@ -67,6 +71,14 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
|
|
|
67
71
|
|
|
68
72
|
const metadataBindings: WorkerMetadata["bindings"] = [];
|
|
69
73
|
|
|
74
|
+
Object.entries(bindings.vars || {})?.forEach(([key, value]) => {
|
|
75
|
+
if (typeof value === "string") {
|
|
76
|
+
metadataBindings.push({ name: key, type: "plain_text", text: value });
|
|
77
|
+
} else {
|
|
78
|
+
metadataBindings.push({ name: key, type: "json", json: value });
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
70
82
|
bindings.kv_namespaces?.forEach(({ id, binding }) => {
|
|
71
83
|
metadataBindings.push({
|
|
72
84
|
name: binding,
|
|
@@ -76,12 +88,13 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
|
|
|
76
88
|
});
|
|
77
89
|
|
|
78
90
|
bindings.durable_objects?.bindings.forEach(
|
|
79
|
-
({ name, class_name, script_name }) => {
|
|
91
|
+
({ name, class_name, script_name, environment }) => {
|
|
80
92
|
metadataBindings.push({
|
|
81
93
|
name,
|
|
82
94
|
type: "durable_object_namespace",
|
|
83
95
|
class_name: class_name,
|
|
84
96
|
...(script_name && { script_name }),
|
|
97
|
+
...(environment && { environment }),
|
|
85
98
|
});
|
|
86
99
|
}
|
|
87
100
|
);
|
|
@@ -94,12 +107,13 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
|
|
|
94
107
|
});
|
|
95
108
|
});
|
|
96
109
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
110
|
+
bindings.services?.forEach(({ binding, service, environment }) => {
|
|
111
|
+
metadataBindings.push({
|
|
112
|
+
name: binding,
|
|
113
|
+
type: "service",
|
|
114
|
+
service,
|
|
115
|
+
...(environment && { environment }),
|
|
116
|
+
});
|
|
103
117
|
});
|
|
104
118
|
|
|
105
119
|
for (const [name, filePath] of Object.entries(bindings.wasm_modules || {})) {
|
package/src/dev/dev.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import * as path from "node:path";
|
|
2
3
|
import { watch } from "chokidar";
|
|
3
4
|
import clipboardy from "clipboardy";
|
|
4
5
|
import commandExists from "command-exists";
|
|
@@ -242,9 +243,11 @@ function useCustomBuild(
|
|
|
242
243
|
persistent: true,
|
|
243
244
|
ignoreInitial: true,
|
|
244
245
|
}).on("all", (_event, filePath) => {
|
|
246
|
+
const relativeFile =
|
|
247
|
+
path.relative(expectedEntry.directory, expectedEntry.file) || ".";
|
|
245
248
|
//TODO: we should buffer requests to the proxy until this completes
|
|
246
249
|
logger.log(`The file ${filePath} changed, restarting build...`);
|
|
247
|
-
runCustomBuild(expectedEntry.file, build).catch((err) => {
|
|
250
|
+
runCustomBuild(expectedEntry.file, relativeFile, build).catch((err) => {
|
|
248
251
|
logger.error("Custom build failed:", err);
|
|
249
252
|
});
|
|
250
253
|
});
|
|
@@ -253,7 +256,7 @@ function useCustomBuild(
|
|
|
253
256
|
return () => {
|
|
254
257
|
watcher?.close();
|
|
255
258
|
};
|
|
256
|
-
}, [build, expectedEntry
|
|
259
|
+
}, [build, expectedEntry]);
|
|
257
260
|
}
|
|
258
261
|
|
|
259
262
|
function sleep(period: number) {
|
package/src/dev/local.tsx
CHANGED
|
@@ -87,6 +87,11 @@ function useLocalWorker({
|
|
|
87
87
|
'⎔ A "public" folder is not yet supported in local mode.'
|
|
88
88
|
);
|
|
89
89
|
}
|
|
90
|
+
if (bindings.services && bindings.services.length > 0) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
"⎔ Service bindings are not yet supported in local mode."
|
|
93
|
+
);
|
|
94
|
+
}
|
|
90
95
|
|
|
91
96
|
// In local mode, we want to copy all referenced modules into
|
|
92
97
|
// the output bundle directory before starting up
|
|
@@ -297,6 +302,7 @@ function useLocalWorker({
|
|
|
297
302
|
bindings.durable_objects?.bindings,
|
|
298
303
|
bindings.kv_namespaces,
|
|
299
304
|
bindings.vars,
|
|
305
|
+
bindings.services,
|
|
300
306
|
compatibilityDate,
|
|
301
307
|
compatibilityFlags,
|
|
302
308
|
localPersistencePath,
|
package/src/entry.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
2
|
+
import { existsSync, statSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import * as esbuild from "esbuild";
|
|
5
5
|
import { execaCommand } from "execa";
|
|
@@ -45,11 +45,16 @@ export async function getEntry(
|
|
|
45
45
|
file = path.resolve(directory, config.main);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
const relativeFile = path.relative(directory, file) || ".";
|
|
49
|
+
await runCustomBuild(file, relativeFile, config.build);
|
|
49
50
|
|
|
50
51
|
if (fileExists(file) === false) {
|
|
51
52
|
throw new Error(
|
|
52
|
-
|
|
53
|
+
getMissingEntryPointMessage(
|
|
54
|
+
`The entry-point file at "${relativeFile}" was not found.`,
|
|
55
|
+
file,
|
|
56
|
+
relativeFile
|
|
57
|
+
)
|
|
53
58
|
);
|
|
54
59
|
}
|
|
55
60
|
const format = await guessWorkerFormat(
|
|
@@ -90,7 +95,8 @@ export async function getEntry(
|
|
|
90
95
|
}
|
|
91
96
|
|
|
92
97
|
export async function runCustomBuild(
|
|
93
|
-
|
|
98
|
+
expectedEntryAbsolute: string,
|
|
99
|
+
expectedEntryRelative: string,
|
|
94
100
|
build: Config["build"]
|
|
95
101
|
) {
|
|
96
102
|
if (build?.command) {
|
|
@@ -105,12 +111,14 @@ export async function runCustomBuild(
|
|
|
105
111
|
...(build.cwd && { cwd: build.cwd }),
|
|
106
112
|
});
|
|
107
113
|
|
|
108
|
-
if (fileExists(
|
|
114
|
+
if (fileExists(expectedEntryAbsolute) === false) {
|
|
109
115
|
throw new Error(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
116
|
+
getMissingEntryPointMessage(
|
|
117
|
+
`The expected output file at "${expectedEntryRelative}" was not found after running custom build: ${build.command}.\n` +
|
|
118
|
+
"The `main` property in wrangler.toml should point to the file generated by the custom build.",
|
|
119
|
+
expectedEntryAbsolute,
|
|
120
|
+
expectedEntryRelative
|
|
121
|
+
)
|
|
114
122
|
);
|
|
115
123
|
}
|
|
116
124
|
}
|
|
@@ -265,3 +273,50 @@ function generateAddScriptNameExamples(
|
|
|
265
273
|
})
|
|
266
274
|
.join("\n");
|
|
267
275
|
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Generate an appropriate message for when the entry-point is missing.
|
|
279
|
+
*
|
|
280
|
+
* To be more helpful to developers, we check whether there is a suitable file
|
|
281
|
+
* nearby to the expected file path.
|
|
282
|
+
*/
|
|
283
|
+
function getMissingEntryPointMessage(
|
|
284
|
+
message: string,
|
|
285
|
+
absoluteEntryPointPath: string,
|
|
286
|
+
relativeEntryPointPath: string
|
|
287
|
+
): string {
|
|
288
|
+
if (
|
|
289
|
+
existsSync(absoluteEntryPointPath) &&
|
|
290
|
+
statSync(absoluteEntryPointPath).isDirectory()
|
|
291
|
+
) {
|
|
292
|
+
// The expected entry-point is a directory, so offer further guidance.
|
|
293
|
+
message += `\nThe provided entry-point path, "${relativeEntryPointPath}", points to a directory, rather than a file.\n`;
|
|
294
|
+
|
|
295
|
+
// Perhaps we can even guess what the correct path should be...
|
|
296
|
+
const possiblePaths: string[] = [];
|
|
297
|
+
for (const basenamePath of [
|
|
298
|
+
"worker",
|
|
299
|
+
"dist/worker",
|
|
300
|
+
"index",
|
|
301
|
+
"dist/index",
|
|
302
|
+
]) {
|
|
303
|
+
for (const extension of [".ts", ".tsx", ".js", ".jsx"]) {
|
|
304
|
+
const filePath = basenamePath + extension;
|
|
305
|
+
if (fileExists(path.resolve(absoluteEntryPointPath, filePath))) {
|
|
306
|
+
possiblePaths.push(path.join(relativeEntryPointPath, filePath));
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (possiblePaths.length > 0) {
|
|
312
|
+
message +=
|
|
313
|
+
`\nDid you mean to set the main field to${
|
|
314
|
+
possiblePaths.length > 1 ? " one of" : ""
|
|
315
|
+
}:\n` +
|
|
316
|
+
"```\n" +
|
|
317
|
+
possiblePaths.map((filePath) => `main = "./${filePath}"\n`).join("") +
|
|
318
|
+
"```";
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return message;
|
|
322
|
+
}
|