wrangler 2.0.3 → 2.0.7
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/bin/wrangler.js +2 -2
- package/package.json +5 -3
- package/pages/functions/buildPlugin.ts +13 -0
- package/pages/functions/buildWorker.ts +13 -0
- package/src/__tests__/configuration.test.ts +217 -29
- package/src/__tests__/dev.test.tsx +71 -9
- package/src/__tests__/index.test.ts +30 -16
- package/src/__tests__/init.test.ts +61 -20
- package/src/__tests__/kv.test.ts +109 -103
- package/src/__tests__/pages.test.ts +363 -33
- package/src/__tests__/parse.test.ts +5 -1
- package/src/__tests__/publish.test.ts +486 -72
- package/src/__tests__/r2.test.ts +47 -24
- package/src/__tests__/secret.test.ts +35 -0
- package/src/abort.d.ts +3 -0
- package/src/bundle.ts +32 -1
- package/src/cfetch/index.ts +4 -2
- package/src/cfetch/internal.ts +11 -9
- package/src/config/environment.ts +40 -14
- package/src/config/index.ts +162 -0
- package/src/config/validation.ts +126 -37
- package/src/create-worker-preview.ts +17 -7
- package/src/create-worker-upload-form.ts +22 -8
- package/src/dev/dev.tsx +5 -4
- package/src/dev/local.tsx +6 -0
- package/src/dev/remote.tsx +15 -1
- package/src/durable.ts +102 -0
- package/src/index.tsx +185 -98
- package/src/inspect.ts +39 -0
- package/src/kv.ts +111 -24
- package/src/open-in-browser.ts +5 -12
- package/src/pages.tsx +206 -65
- package/src/parse.ts +21 -4
- package/src/proxy.ts +38 -22
- package/src/publish.ts +227 -113
- package/src/sites.tsx +13 -16
- package/src/worker.ts +8 -0
- package/templates/new-worker.ts +16 -1
- package/wrangler-dist/cli.js +32273 -19295
package/src/config/validation.ts
CHANGED
|
@@ -193,7 +193,8 @@ export function normalizeAndValidateConfig(
|
|
|
193
193
|
dev: normalizeAndValidateDev(diagnostics, rawConfig.dev ?? {}),
|
|
194
194
|
migrations: normalizeAndValidateMigrations(
|
|
195
195
|
diagnostics,
|
|
196
|
-
rawConfig.migrations ?? []
|
|
196
|
+
rawConfig.migrations ?? [],
|
|
197
|
+
activeEnv.durable_objects
|
|
197
198
|
),
|
|
198
199
|
site: normalizeAndValidateSite(
|
|
199
200
|
diagnostics,
|
|
@@ -387,7 +388,8 @@ function normalizeAndValidateDev(
|
|
|
387
388
|
*/
|
|
388
389
|
function normalizeAndValidateMigrations(
|
|
389
390
|
diagnostics: Diagnostics,
|
|
390
|
-
rawMigrations: Config["migrations"]
|
|
391
|
+
rawMigrations: Config["migrations"],
|
|
392
|
+
durableObjects: Config["durable_objects"]
|
|
391
393
|
): Config["migrations"] {
|
|
392
394
|
if (!Array.isArray(rawMigrations)) {
|
|
393
395
|
diagnostics.errors.push(
|
|
@@ -441,6 +443,26 @@ function normalizeAndValidateMigrations(
|
|
|
441
443
|
"string"
|
|
442
444
|
);
|
|
443
445
|
}
|
|
446
|
+
|
|
447
|
+
if (
|
|
448
|
+
Array.isArray(durableObjects?.bindings) &&
|
|
449
|
+
durableObjects.bindings.length > 0
|
|
450
|
+
) {
|
|
451
|
+
// intrinsic [durable_objects] implies [migrations]
|
|
452
|
+
const exportedDurableObjects = (durableObjects.bindings || []).filter(
|
|
453
|
+
(binding) => !binding.script_name
|
|
454
|
+
);
|
|
455
|
+
if (exportedDurableObjects.length > 0 && rawMigrations.length === 0) {
|
|
456
|
+
diagnostics.warnings.push(
|
|
457
|
+
`In wrangler.toml, you have configured [durable_objects] exported by this Worker (${exportedDurableObjects
|
|
458
|
+
.map((durable) => durable.class_name || "(unnamed)")
|
|
459
|
+
.join(
|
|
460
|
+
", "
|
|
461
|
+
)}), but no [migrations] for them. This may not work as expected until you add a [migrations] section to your wrangler.toml. Refer to https://developers.cloudflare.com/workers/learning/using-durable-objects/#durable-object-migrations-in-wranglertoml for more details.`
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
444
466
|
return rawMigrations;
|
|
445
467
|
}
|
|
446
468
|
}
|
|
@@ -562,19 +584,37 @@ function normalizeAndValidateModulePaths(
|
|
|
562
584
|
* or an object that looks like {pattern: string, zone_id: string }
|
|
563
585
|
*/
|
|
564
586
|
function isValidRouteValue(item: unknown): boolean {
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
587
|
+
if (!item) {
|
|
588
|
+
return false;
|
|
589
|
+
}
|
|
590
|
+
if (typeof item === "string") {
|
|
591
|
+
return true;
|
|
592
|
+
}
|
|
593
|
+
if (typeof item === "object") {
|
|
594
|
+
if (!hasProperty(item, "pattern") || typeof item.pattern !== "string") {
|
|
595
|
+
return false;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const otherKeys = Object.keys(item).length - 1; // minus one to subtract "pattern"
|
|
599
|
+
|
|
600
|
+
const hasZoneId =
|
|
601
|
+
hasProperty(item, "zone_id") && typeof item.zone_id === "string";
|
|
602
|
+
const hasZoneName =
|
|
603
|
+
hasProperty(item, "zone_name") && typeof item.zone_name === "string";
|
|
604
|
+
const hasCustomDomainFlag =
|
|
605
|
+
hasProperty(item, "custom_domain") &&
|
|
606
|
+
typeof item.custom_domain === "boolean";
|
|
607
|
+
|
|
608
|
+
if (otherKeys === 2 && hasCustomDomainFlag && (hasZoneId || hasZoneName)) {
|
|
609
|
+
return true;
|
|
610
|
+
} else if (
|
|
611
|
+
otherKeys === 1 &&
|
|
612
|
+
(hasZoneId || hasZoneName || hasCustomDomainFlag)
|
|
613
|
+
) {
|
|
614
|
+
return true;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
return false;
|
|
578
618
|
}
|
|
579
619
|
|
|
580
620
|
/**
|
|
@@ -583,7 +623,7 @@ function isValidRouteValue(item: unknown): boolean {
|
|
|
583
623
|
const isRoute: ValidatorFn = (diagnostics, field, value) => {
|
|
584
624
|
if (value !== undefined && !isValidRouteValue(value)) {
|
|
585
625
|
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(
|
|
626
|
+
`Expected "${field}" to be either a string, or an object with shape { pattern, custom_domain, zone_id | zone_name }, but got ${JSON.stringify(
|
|
587
627
|
value
|
|
588
628
|
)}.`
|
|
589
629
|
);
|
|
@@ -613,7 +653,7 @@ const isRouteArray: ValidatorFn = (diagnostics, field, value) => {
|
|
|
613
653
|
}
|
|
614
654
|
if (invalidRoutes.length > 0) {
|
|
615
655
|
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(
|
|
656
|
+
`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
657
|
invalidRoutes,
|
|
618
658
|
null,
|
|
619
659
|
2
|
|
@@ -696,27 +736,12 @@ function normalizeAndValidateEnvironment(
|
|
|
696
736
|
diagnostics,
|
|
697
737
|
rawEnv,
|
|
698
738
|
"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
|
-
"```",
|
|
739
|
+
`The "experimental_services" field is no longer supported. Simply rename the [experimental_services] field to [services].`,
|
|
716
740
|
true
|
|
717
741
|
);
|
|
718
742
|
|
|
719
743
|
experimental(diagnostics, rawEnv, "unsafe");
|
|
744
|
+
experimental(diagnostics, rawEnv, "services");
|
|
720
745
|
|
|
721
746
|
const route = validateRoute(diagnostics, topLevelEnv, rawEnv);
|
|
722
747
|
|
|
@@ -880,6 +905,16 @@ function normalizeAndValidateEnvironment(
|
|
|
880
905
|
validateBindingArray(envName, validateR2Binding),
|
|
881
906
|
[]
|
|
882
907
|
),
|
|
908
|
+
services: notInheritable(
|
|
909
|
+
diagnostics,
|
|
910
|
+
topLevelEnv,
|
|
911
|
+
rawConfig,
|
|
912
|
+
rawEnv,
|
|
913
|
+
envName,
|
|
914
|
+
"services",
|
|
915
|
+
validateBindingArray(envName, validateServiceBinding),
|
|
916
|
+
[]
|
|
917
|
+
),
|
|
883
918
|
unsafe: notInheritable(
|
|
884
919
|
diagnostics,
|
|
885
920
|
topLevelEnv,
|
|
@@ -1014,7 +1049,7 @@ const validateRule: ValidatorFn = (diagnostics, field, value) => {
|
|
|
1014
1049
|
|
|
1015
1050
|
if (!isOptionalProperty(rule, "fallthrough", "boolean")) {
|
|
1016
1051
|
diagnostics.errors.push(
|
|
1017
|
-
`
|
|
1052
|
+
`the field "fallthrough", when present, should be a boolean.`
|
|
1018
1053
|
);
|
|
1019
1054
|
isValid = false;
|
|
1020
1055
|
}
|
|
@@ -1135,7 +1170,7 @@ const validateDurableObjectBinding: ValidatorFn = (
|
|
|
1135
1170
|
return false;
|
|
1136
1171
|
}
|
|
1137
1172
|
|
|
1138
|
-
// Durable Object bindings must have a name and class_name, and optionally a script_name.
|
|
1173
|
+
// Durable Object bindings must have a name and class_name, and optionally a script_name and an environment.
|
|
1139
1174
|
let isValid = true;
|
|
1140
1175
|
if (!isRequiredProperty(value, "name", "string")) {
|
|
1141
1176
|
diagnostics.errors.push(`binding should have a string "name" field.`);
|
|
@@ -1147,7 +1182,21 @@ const validateDurableObjectBinding: ValidatorFn = (
|
|
|
1147
1182
|
}
|
|
1148
1183
|
if (!isOptionalProperty(value, "script_name", "string")) {
|
|
1149
1184
|
diagnostics.errors.push(
|
|
1150
|
-
`
|
|
1185
|
+
`the field "script_name", when present, should be a string.`
|
|
1186
|
+
);
|
|
1187
|
+
isValid = false;
|
|
1188
|
+
}
|
|
1189
|
+
// environment requires a script_name
|
|
1190
|
+
if (!isOptionalProperty(value, "environment", "string")) {
|
|
1191
|
+
diagnostics.errors.push(
|
|
1192
|
+
`the field "environment", when present, should be a string.`
|
|
1193
|
+
);
|
|
1194
|
+
isValid = false;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
if ("environment" in value && !("script_name" in value)) {
|
|
1198
|
+
diagnostics.errors.push(
|
|
1199
|
+
`binding should have a "script_name" field if "environment" is present.`
|
|
1151
1200
|
);
|
|
1152
1201
|
isValid = false;
|
|
1153
1202
|
}
|
|
@@ -1178,8 +1227,13 @@ const validateUnsafeBinding: ValidatorFn = (diagnostics, field, value) => {
|
|
|
1178
1227
|
const safeBindings = [
|
|
1179
1228
|
"plain_text",
|
|
1180
1229
|
"json",
|
|
1230
|
+
"wasm_module",
|
|
1231
|
+
"data_blob",
|
|
1232
|
+
"text_blob",
|
|
1181
1233
|
"kv_namespace",
|
|
1182
1234
|
"durable_object_namespace",
|
|
1235
|
+
"r2_bucket",
|
|
1236
|
+
"service",
|
|
1183
1237
|
];
|
|
1184
1238
|
|
|
1185
1239
|
if (safeBindings.includes(value.type)) {
|
|
@@ -1422,3 +1476,38 @@ const validateBindingsHaveUniqueNames = (
|
|
|
1422
1476
|
|
|
1423
1477
|
return !hasDuplicates;
|
|
1424
1478
|
};
|
|
1479
|
+
const validateServiceBinding: ValidatorFn = (diagnostics, field, value) => {
|
|
1480
|
+
if (typeof value !== "object" || value === null) {
|
|
1481
|
+
diagnostics.errors.push(
|
|
1482
|
+
`"services" bindings should be objects, but got ${JSON.stringify(value)}`
|
|
1483
|
+
);
|
|
1484
|
+
return false;
|
|
1485
|
+
}
|
|
1486
|
+
let isValid = true;
|
|
1487
|
+
// Service bindings must have a binding, service, and environment.
|
|
1488
|
+
if (!isRequiredProperty(value, "binding", "string")) {
|
|
1489
|
+
diagnostics.errors.push(
|
|
1490
|
+
`"${field}" bindings should have a string "binding" field but got ${JSON.stringify(
|
|
1491
|
+
value
|
|
1492
|
+
)}.`
|
|
1493
|
+
);
|
|
1494
|
+
isValid = false;
|
|
1495
|
+
}
|
|
1496
|
+
if (!isRequiredProperty(value, "service", "string")) {
|
|
1497
|
+
diagnostics.errors.push(
|
|
1498
|
+
`"${field}" bindings should have a string "service" field but got ${JSON.stringify(
|
|
1499
|
+
value
|
|
1500
|
+
)}.`
|
|
1501
|
+
);
|
|
1502
|
+
isValid = false;
|
|
1503
|
+
}
|
|
1504
|
+
if (!isOptionalProperty(value, "environment", "string")) {
|
|
1505
|
+
diagnostics.errors.push(
|
|
1506
|
+
`"${field}" bindings should have a string "environment" field but got ${JSON.stringify(
|
|
1507
|
+
value
|
|
1508
|
+
)}.`
|
|
1509
|
+
);
|
|
1510
|
+
isValid = false;
|
|
1511
|
+
}
|
|
1512
|
+
return isValid;
|
|
1513
|
+
};
|
|
@@ -66,7 +66,12 @@ async function sessionToken(
|
|
|
66
66
|
? `/zones/${ctx.zone.id}/workers/edge-preview`
|
|
67
67
|
: `/accounts/${accountId}/workers/subdomain/edge-preview`;
|
|
68
68
|
|
|
69
|
-
const { exchange_url } = await fetchResult<{ exchange_url: string }>(
|
|
69
|
+
const { exchange_url } = await fetchResult<{ exchange_url: string }>(
|
|
70
|
+
initUrl,
|
|
71
|
+
undefined,
|
|
72
|
+
undefined,
|
|
73
|
+
abortSignal
|
|
74
|
+
);
|
|
70
75
|
const { inspector_websocket, prewarm, token } = (await (
|
|
71
76
|
await fetch(exchange_url, { signal: abortSignal })
|
|
72
77
|
).json()) as { inspector_websocket: string; token: string; prewarm: string };
|
|
@@ -119,13 +124,18 @@ async function createPreviewToken(
|
|
|
119
124
|
const formData = createWorkerUploadForm(worker);
|
|
120
125
|
formData.set("wrangler-session-config", JSON.stringify(mode));
|
|
121
126
|
|
|
122
|
-
const { preview_token } = await fetchResult<{ preview_token: string }>(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
+
const { preview_token } = await fetchResult<{ preview_token: string }>(
|
|
128
|
+
url,
|
|
129
|
+
{
|
|
130
|
+
method: "POST",
|
|
131
|
+
body: formData,
|
|
132
|
+
headers: {
|
|
133
|
+
"cf-preview-upload-config-token": value,
|
|
134
|
+
},
|
|
127
135
|
},
|
|
128
|
-
|
|
136
|
+
undefined,
|
|
137
|
+
abortSignal
|
|
138
|
+
);
|
|
129
139
|
|
|
130
140
|
return {
|
|
131
141
|
value: preview_token,
|
|
@@ -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
|
@@ -9,7 +9,9 @@ import { withErrorBoundary, useErrorHandler } from "react-error-boundary";
|
|
|
9
9
|
import onExit from "signal-exit";
|
|
10
10
|
import tmp from "tmp-promise";
|
|
11
11
|
import { fetch } from "undici";
|
|
12
|
+
import { printBindings } from "../config";
|
|
12
13
|
import { runCustomBuild } from "../entry";
|
|
14
|
+
import { openInspector } from "../inspect";
|
|
13
15
|
import { logger } from "../logger";
|
|
14
16
|
import openInBrowser from "../open-in-browser";
|
|
15
17
|
import { getAPIToken } from "../user";
|
|
@@ -104,6 +106,8 @@ export function DevImplementation(props: DevProps): JSX.Element {
|
|
|
104
106
|
nodeCompat: props.nodeCompat,
|
|
105
107
|
});
|
|
106
108
|
|
|
109
|
+
printBindings(props.bindings);
|
|
110
|
+
|
|
107
111
|
// only load the UI if we're running in a supported environment
|
|
108
112
|
const { isRawModeSupported } = useStdin();
|
|
109
113
|
return isRawModeSupported ? (
|
|
@@ -378,10 +382,7 @@ function useHotkeys(
|
|
|
378
382
|
}
|
|
379
383
|
// toggle inspector
|
|
380
384
|
case "d": {
|
|
381
|
-
await
|
|
382
|
-
`https://built-devtools.pages.dev/js_app?experiments=true&v8only=true&ws=localhost:${inspectorPort}/ws`,
|
|
383
|
-
{ forceChromium: true }
|
|
384
|
-
);
|
|
385
|
+
await openInspector(inspectorPort);
|
|
385
386
|
break;
|
|
386
387
|
}
|
|
387
388
|
// toggle local
|
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/dev/remote.tsx
CHANGED
|
@@ -185,7 +185,21 @@ export function useWorker(props: {
|
|
|
185
185
|
// we want to log the error, but not end the process
|
|
186
186
|
// since it could recover after the developer fixes whatever's wrong
|
|
187
187
|
if ((err as { code: string }).code !== "ABORT_ERR") {
|
|
188
|
-
|
|
188
|
+
// instead of logging the raw API error to the user,
|
|
189
|
+
// give them friendly instructions
|
|
190
|
+
// for error 10063 (workers.dev subdomain required)
|
|
191
|
+
if (err.code === 10063) {
|
|
192
|
+
const errorMessage =
|
|
193
|
+
"Error: You need to register a workers.dev subdomain before running the dev command in remote mode";
|
|
194
|
+
const solutionMessage =
|
|
195
|
+
"You can either enable local mode by pressing l, or register a workers.dev subdomain here:";
|
|
196
|
+
const onboardingLink = `https://dash.cloudflare.com/${accountId}/workers/onboarding`;
|
|
197
|
+
logger.error(
|
|
198
|
+
`${errorMessage}\n${solutionMessage}\n${onboardingLink}`
|
|
199
|
+
);
|
|
200
|
+
} else {
|
|
201
|
+
logger.error("Error on remote worker:", err);
|
|
202
|
+
}
|
|
189
203
|
}
|
|
190
204
|
});
|
|
191
205
|
|
package/src/durable.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { fetchResult } from "./cfetch";
|
|
3
|
+
import { logger } from "./logger";
|
|
4
|
+
import type { Config } from "./config";
|
|
5
|
+
import type { CfWorkerInit } from "./worker";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* For a given Worker + migrations config, figure out which migrations
|
|
9
|
+
* to upload based on the current migration tag of the deployed Worker.
|
|
10
|
+
*/
|
|
11
|
+
export async function getMigrationsToUpload(
|
|
12
|
+
scriptName: string,
|
|
13
|
+
props: {
|
|
14
|
+
accountId: string | undefined;
|
|
15
|
+
config: Config;
|
|
16
|
+
legacyEnv: boolean | undefined;
|
|
17
|
+
env: string | undefined;
|
|
18
|
+
}
|
|
19
|
+
): Promise<CfWorkerInit["migrations"]> {
|
|
20
|
+
const { config, accountId } = props;
|
|
21
|
+
|
|
22
|
+
assert(accountId, "Missing accountId");
|
|
23
|
+
// if config.migrations
|
|
24
|
+
let migrations;
|
|
25
|
+
if (config.migrations.length > 0) {
|
|
26
|
+
// get current migration tag
|
|
27
|
+
type ScriptData = { id: string; migration_tag?: string };
|
|
28
|
+
let script: ScriptData | undefined;
|
|
29
|
+
if (!props.legacyEnv) {
|
|
30
|
+
try {
|
|
31
|
+
if (props.env) {
|
|
32
|
+
const scriptData = await fetchResult<{
|
|
33
|
+
script: ScriptData;
|
|
34
|
+
}>(
|
|
35
|
+
`/accounts/${accountId}/workers/services/${scriptName}/environments/${props.env}`
|
|
36
|
+
);
|
|
37
|
+
script = scriptData.script;
|
|
38
|
+
} else {
|
|
39
|
+
const scriptData = await fetchResult<{
|
|
40
|
+
default_environment: {
|
|
41
|
+
script: ScriptData;
|
|
42
|
+
};
|
|
43
|
+
}>(`/accounts/${accountId}/workers/services/${scriptName}`);
|
|
44
|
+
script = scriptData.default_environment.script;
|
|
45
|
+
}
|
|
46
|
+
} catch (err) {
|
|
47
|
+
if (
|
|
48
|
+
![
|
|
49
|
+
10090, // corresponds to workers.api.error.service_not_found, so the script wasn't previously published at all
|
|
50
|
+
10092, // workers.api.error.environment_not_found, so the script wasn't published to this environment yet
|
|
51
|
+
].includes((err as { code: number }).code)
|
|
52
|
+
) {
|
|
53
|
+
throw err;
|
|
54
|
+
}
|
|
55
|
+
// else it's a 404, no script found, and we can proceed
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
const scripts = await fetchResult<ScriptData[]>(
|
|
59
|
+
`/accounts/${accountId}/workers/scripts`
|
|
60
|
+
);
|
|
61
|
+
script = scripts.find(({ id }) => id === scriptName);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (script?.migration_tag) {
|
|
65
|
+
// was already published once
|
|
66
|
+
const scriptMigrationTag = script.migration_tag;
|
|
67
|
+
const foundIndex = config.migrations.findIndex(
|
|
68
|
+
(migration) => migration.tag === scriptMigrationTag
|
|
69
|
+
);
|
|
70
|
+
if (foundIndex === -1) {
|
|
71
|
+
logger.warn(
|
|
72
|
+
`The published script ${scriptName} has a migration tag "${script.migration_tag}, which was not found in wrangler.toml. You may have already deleted it. Applying all available migrations to the script...`
|
|
73
|
+
);
|
|
74
|
+
migrations = {
|
|
75
|
+
old_tag: script.migration_tag,
|
|
76
|
+
new_tag: config.migrations[config.migrations.length - 1].tag,
|
|
77
|
+
steps: config.migrations.map(({ tag: _tag, ...rest }) => rest),
|
|
78
|
+
};
|
|
79
|
+
} else {
|
|
80
|
+
if (foundIndex !== config.migrations.length - 1) {
|
|
81
|
+
// there are new migrations to send up
|
|
82
|
+
migrations = {
|
|
83
|
+
old_tag: script.migration_tag,
|
|
84
|
+
new_tag: config.migrations[config.migrations.length - 1].tag,
|
|
85
|
+
steps: config.migrations
|
|
86
|
+
.slice(foundIndex + 1)
|
|
87
|
+
.map(({ tag: _tag, ...rest }) => rest),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// else, we're up to date, no migrations to send
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
// first time publishing durable objects to this script,
|
|
94
|
+
// so we send all the migrations
|
|
95
|
+
migrations = {
|
|
96
|
+
new_tag: config.migrations[config.migrations.length - 1].tag,
|
|
97
|
+
steps: config.migrations.map(({ tag: _tag, ...rest }) => rest),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return migrations;
|
|
102
|
+
}
|