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/publish.ts
CHANGED
|
@@ -6,16 +6,23 @@ import tmp from "tmp-promise";
|
|
|
6
6
|
import { bundleWorker } from "./bundle";
|
|
7
7
|
import { fetchResult } from "./cfetch";
|
|
8
8
|
import { createWorkerUploadForm } from "./create-worker-upload-form";
|
|
9
|
+
import { confirm } from "./dialogs";
|
|
9
10
|
import { logger } from "./logger";
|
|
10
11
|
import { syncAssets } from "./sites";
|
|
11
12
|
import type { Config } from "./config";
|
|
13
|
+
import type {
|
|
14
|
+
Route,
|
|
15
|
+
ZoneIdRoute,
|
|
16
|
+
ZoneNameRoute,
|
|
17
|
+
CustomDomainRoute,
|
|
18
|
+
} from "./config/environment";
|
|
12
19
|
import type { Entry } from "./entry";
|
|
13
20
|
import type { AssetPaths } from "./sites";
|
|
14
21
|
import type { CfWorkerInit } from "./worker";
|
|
15
22
|
|
|
16
23
|
type Props = {
|
|
17
24
|
config: Config;
|
|
18
|
-
accountId: string;
|
|
25
|
+
accountId: string | undefined;
|
|
19
26
|
entry: Entry;
|
|
20
27
|
rules: Config["rules"];
|
|
21
28
|
name: string | undefined;
|
|
@@ -36,10 +43,178 @@ type Props = {
|
|
|
36
43
|
dryRun: boolean | undefined;
|
|
37
44
|
};
|
|
38
45
|
|
|
46
|
+
type RouteObject = ZoneIdRoute | ZoneNameRoute | CustomDomainRoute;
|
|
47
|
+
|
|
39
48
|
function sleep(ms: number) {
|
|
40
49
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
41
50
|
}
|
|
42
51
|
|
|
52
|
+
function renderRoute(route: Route): string {
|
|
53
|
+
let result = "";
|
|
54
|
+
if (typeof route === "string") {
|
|
55
|
+
result = route;
|
|
56
|
+
} else {
|
|
57
|
+
result = route.pattern;
|
|
58
|
+
const isCustomDomain = Boolean(
|
|
59
|
+
"custom_domain" in route && route.custom_domain
|
|
60
|
+
);
|
|
61
|
+
if (isCustomDomain && "zone_id" in route) {
|
|
62
|
+
result += ` (custom domain - zone id: ${route.zone_id})`;
|
|
63
|
+
} else if (isCustomDomain && "zone_name" in route) {
|
|
64
|
+
result += ` (custom domain - zone name: ${route.zone_name})`;
|
|
65
|
+
} else if (isCustomDomain) {
|
|
66
|
+
result += ` (custom domain)`;
|
|
67
|
+
} else if ("zone_id" in route) {
|
|
68
|
+
result += ` (zone id: ${route.zone_id})`;
|
|
69
|
+
} else if ("zone_name" in route) {
|
|
70
|
+
result += ` (zone name: ${route.zone_name})`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// this function takes a string with quotes in it
|
|
77
|
+
// (i.e. `hello "world", if that really is your name`)
|
|
78
|
+
// and peels out the first instance of a substring
|
|
79
|
+
// bounded by quotes (so, in the example above, `world`)
|
|
80
|
+
//
|
|
81
|
+
// this is useful because the /domains api will return
|
|
82
|
+
// which domains conflicted in an error message, bounded
|
|
83
|
+
// by a string, which we can use to provide helpful
|
|
84
|
+
// messages to a user
|
|
85
|
+
function getQuoteBoundedSubstring(content: string) {
|
|
86
|
+
const matches = content.split('"');
|
|
87
|
+
return matches[1] ?? "";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isOriginConflictError(
|
|
91
|
+
e: unknown
|
|
92
|
+
): e is { code: 100116; message: string; notes: Array<{ text: string }> } {
|
|
93
|
+
return (
|
|
94
|
+
typeof e === "object" &&
|
|
95
|
+
e !== null &&
|
|
96
|
+
(e as { code: number }).code === 100116
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function isDNSConflictError(
|
|
101
|
+
e: unknown
|
|
102
|
+
): e is { code: 100117; message: string; notes: Array<{ text: string }> } {
|
|
103
|
+
return (
|
|
104
|
+
typeof e === "object" &&
|
|
105
|
+
e !== null &&
|
|
106
|
+
(e as { code: number }).code === 100117
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// empty error class to throw and then explicitly catch via `instanceof`
|
|
111
|
+
class CustomDomainOverrideRejected extends Error {}
|
|
112
|
+
|
|
113
|
+
// publishing to custom domains involves a few more steps than just updating
|
|
114
|
+
// the routing table, and thus the api implementing it is fairly defensive -
|
|
115
|
+
// it will error eagerly on conflicts against existing domains or existing
|
|
116
|
+
// managed DNS records
|
|
117
|
+
//
|
|
118
|
+
// however, you can pass params to override the errors. we start on the
|
|
119
|
+
// defensive path, and if one of these errors occur, we prompt the user
|
|
120
|
+
// for confirmation that they do indeed want to override the conflicts, and
|
|
121
|
+
// then retry the request with the right override added
|
|
122
|
+
//
|
|
123
|
+
// if a user does not confirm that they want to override, we skip publishing
|
|
124
|
+
// to these custom domains, but continue on through the rest of the
|
|
125
|
+
// publish stage
|
|
126
|
+
function publishCustomDomains(
|
|
127
|
+
workerUrl: string,
|
|
128
|
+
domains: Array<RouteObject>
|
|
129
|
+
): Promise<string[]> {
|
|
130
|
+
const config = {
|
|
131
|
+
override_scope: true,
|
|
132
|
+
override_existing_origin: false,
|
|
133
|
+
override_existing_dns_record: false,
|
|
134
|
+
};
|
|
135
|
+
const origins = domains.map((domainRoute) => {
|
|
136
|
+
return {
|
|
137
|
+
hostname: domainRoute.pattern,
|
|
138
|
+
zone_id: "zone_id" in domainRoute ? domainRoute.zone_id : undefined,
|
|
139
|
+
zone_name: "zone_name" in domainRoute ? domainRoute.zone_name : undefined,
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
if (!process.stdout.isTTY) {
|
|
144
|
+
// running in non-interactive mode.
|
|
145
|
+
// existing origins / dns records are not indicative of errors,
|
|
146
|
+
// so we aggressively update rather than aggressively fail
|
|
147
|
+
config.override_existing_origin = true;
|
|
148
|
+
config.override_existing_dns_record = true;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Mixing promise chains with async/await is funky, but it allows us to keep related
|
|
152
|
+
// logic synchronous-looking (i.e. prompting for confirmation and then re-requesting)
|
|
153
|
+
// while retaining the flexibility of promise chain fall-throughs. We can group error
|
|
154
|
+
// handling logic in dedicated catch calls, and all we have to do is re-throw an
|
|
155
|
+
// error and it will pass down to the next catch call
|
|
156
|
+
return fetchResult(`${workerUrl}/domains`, {
|
|
157
|
+
method: "PUT",
|
|
158
|
+
body: JSON.stringify({ ...config, origins }),
|
|
159
|
+
headers: {
|
|
160
|
+
"Content-Type": "application/json",
|
|
161
|
+
},
|
|
162
|
+
})
|
|
163
|
+
.catch(async (err) => {
|
|
164
|
+
if (isOriginConflictError(err)) {
|
|
165
|
+
const conflictingOrigins = getQuoteBoundedSubstring(err.notes[0].text);
|
|
166
|
+
const shouldContinue = await confirm(
|
|
167
|
+
`Custom Domains already exist for these domains: "${conflictingOrigins}"\nUpdate them to point to this script instead?`
|
|
168
|
+
);
|
|
169
|
+
if (!shouldContinue) {
|
|
170
|
+
throw new CustomDomainOverrideRejected();
|
|
171
|
+
}
|
|
172
|
+
config.override_existing_origin = true;
|
|
173
|
+
await fetchResult(`${workerUrl}/domains`, {
|
|
174
|
+
method: "PUT",
|
|
175
|
+
body: JSON.stringify({ ...config, origins }),
|
|
176
|
+
headers: {
|
|
177
|
+
"Content-Type": "application/json",
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
} else {
|
|
181
|
+
throw err;
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
.catch(async (err) => {
|
|
185
|
+
if (isDNSConflictError(err)) {
|
|
186
|
+
const conflictingOrigins = getQuoteBoundedSubstring(err.notes[0].text);
|
|
187
|
+
const shouldContinue = await confirm(
|
|
188
|
+
`You already have conflicting DNS records for these domains: "${conflictingOrigins}"\nUpdate them to point to this script instead?`
|
|
189
|
+
);
|
|
190
|
+
if (!shouldContinue) {
|
|
191
|
+
throw new CustomDomainOverrideRejected();
|
|
192
|
+
}
|
|
193
|
+
config.override_existing_dns_record = true;
|
|
194
|
+
await fetchResult(`${workerUrl}/domains`, {
|
|
195
|
+
method: "PUT",
|
|
196
|
+
body: JSON.stringify({ ...config, origins }),
|
|
197
|
+
headers: {
|
|
198
|
+
"Content-Type": "application/json",
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
} else {
|
|
202
|
+
throw err;
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
.then(() => domains.map((domain) => renderRoute(domain)))
|
|
206
|
+
.catch((err) => {
|
|
207
|
+
if (err instanceof CustomDomainOverrideRejected) {
|
|
208
|
+
return [
|
|
209
|
+
domains.length > 1
|
|
210
|
+
? `Publishing to ${domains.length} Custom Domains was skipped, fix conflicts and try again`
|
|
211
|
+
: `Publishing to Custom Domain "${domains[0].pattern}" was skipped, fix conflict and try again`,
|
|
212
|
+
];
|
|
213
|
+
}
|
|
214
|
+
throw err;
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
43
218
|
export default async function publish(props: Props): Promise<void> {
|
|
44
219
|
// TODO: warn if git/hg has uncommitted changes
|
|
45
220
|
const { config, accountId } = props;
|
|
@@ -52,6 +227,25 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
52
227
|
const triggers = props.triggers || config.triggers?.crons;
|
|
53
228
|
const routes =
|
|
54
229
|
props.routes ?? config.routes ?? (config.route ? [config.route] : []) ?? [];
|
|
230
|
+
const routesOnly: Array<Route> = [];
|
|
231
|
+
const customDomainsOnly: Array<RouteObject> = [];
|
|
232
|
+
for (const route of routes) {
|
|
233
|
+
if (typeof route !== "string" && route.custom_domain) {
|
|
234
|
+
if (route.pattern.includes("*")) {
|
|
235
|
+
throw new Error(
|
|
236
|
+
`Cannot use "${route.pattern}" as a Custom Domain; wildcard operators (*) are not allowed`
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
if (route.pattern.includes("/")) {
|
|
240
|
+
throw new Error(
|
|
241
|
+
`Cannot use "${route.pattern}" as a Custom Domain; paths are not allowed`
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
customDomainsOnly.push(route);
|
|
245
|
+
} else {
|
|
246
|
+
routesOnly.push(route);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
55
249
|
|
|
56
250
|
// deployToWorkersDev defaults to true only if there aren't any routes defined
|
|
57
251
|
const deployToWorkersDev = config.workers_dev ?? routes.length === 0;
|
|
@@ -164,7 +358,7 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
164
358
|
|
|
165
359
|
// if config.migrations
|
|
166
360
|
let migrations;
|
|
167
|
-
if (config.migrations.length > 0) {
|
|
361
|
+
if (!props.dryRun && config.migrations.length > 0) {
|
|
168
362
|
// get current migration tag
|
|
169
363
|
type ScriptData = { id: string; migration_tag?: string };
|
|
170
364
|
let script: ScriptData | undefined;
|
|
@@ -271,6 +465,7 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
271
465
|
data_blobs: config.data_blobs,
|
|
272
466
|
durable_objects: config.durable_objects,
|
|
273
467
|
r2_buckets: config.r2_buckets,
|
|
468
|
+
services: config.services,
|
|
274
469
|
unsafe: config.unsafe?.bindings,
|
|
275
470
|
};
|
|
276
471
|
|
|
@@ -324,6 +519,7 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
324
519
|
logger.log(`--dry-run: exiting now.`);
|
|
325
520
|
return;
|
|
326
521
|
}
|
|
522
|
+
assert(accountId, "Missing accountId");
|
|
327
523
|
|
|
328
524
|
const uploadMs = Date.now() - start;
|
|
329
525
|
const deployments: Promise<string[]>[] = [];
|
|
@@ -374,13 +570,13 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
374
570
|
logger.log("Uploaded", workerName, formatTime(uploadMs));
|
|
375
571
|
|
|
376
572
|
// Update routing table for the script.
|
|
377
|
-
if (
|
|
573
|
+
if (routesOnly.length > 0) {
|
|
378
574
|
deployments.push(
|
|
379
575
|
fetchResult(`${workerUrl}/routes`, {
|
|
380
576
|
// Note: PUT will delete previous routes on this script.
|
|
381
577
|
method: "PUT",
|
|
382
578
|
body: JSON.stringify(
|
|
383
|
-
|
|
579
|
+
routesOnly.map((route) =>
|
|
384
580
|
typeof route !== "object" ? { pattern: route } : route
|
|
385
581
|
)
|
|
386
582
|
),
|
|
@@ -388,29 +584,22 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
388
584
|
"Content-Type": "application/json",
|
|
389
585
|
},
|
|
390
586
|
}).then(() => {
|
|
391
|
-
if (
|
|
392
|
-
return
|
|
587
|
+
if (routesOnly.length > 10) {
|
|
588
|
+
return routesOnly
|
|
393
589
|
.slice(0, 9)
|
|
394
|
-
.map((route) =>
|
|
395
|
-
|
|
396
|
-
? route
|
|
397
|
-
: "zone_id" in route
|
|
398
|
-
? `${route.pattern} (zone id: ${route.zone_id})`
|
|
399
|
-
: `${route.pattern} (zone name: ${route.zone_name})`
|
|
400
|
-
)
|
|
401
|
-
.concat([`...and ${routes.length - 10} more routes`]);
|
|
590
|
+
.map((route) => renderRoute(route))
|
|
591
|
+
.concat([`...and ${routesOnly.length - 10} more routes`]);
|
|
402
592
|
}
|
|
403
|
-
return
|
|
404
|
-
typeof route === "string"
|
|
405
|
-
? route
|
|
406
|
-
: "zone_id" in route
|
|
407
|
-
? `${route.pattern} (zone id: ${route.zone_id})`
|
|
408
|
-
: `${route.pattern} (zone name: ${route.zone_name})`
|
|
409
|
-
);
|
|
593
|
+
return routesOnly.map((route) => renderRoute(route));
|
|
410
594
|
})
|
|
411
595
|
);
|
|
412
596
|
}
|
|
413
597
|
|
|
598
|
+
// Update custom domains for the script
|
|
599
|
+
if (customDomainsOnly.length > 0) {
|
|
600
|
+
deployments.push(publishCustomDomains(workerUrl, customDomainsOnly));
|
|
601
|
+
}
|
|
602
|
+
|
|
414
603
|
// Configure any schedules for the script.
|
|
415
604
|
// TODO: rename this to `schedules`?
|
|
416
605
|
if (triggers && triggers.length) {
|
package/src/sites.tsx
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
1
2
|
import { readdir, readFile, stat } from "node:fs/promises";
|
|
2
3
|
import * as path from "node:path";
|
|
3
4
|
import ignore from "ignore";
|
|
4
5
|
import xxhash from "xxhash-wasm";
|
|
5
6
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
createKVNamespace,
|
|
8
|
+
listKVNamespaceKeys,
|
|
9
|
+
listKVNamespaces,
|
|
10
|
+
putKVBulkKeyValue,
|
|
11
|
+
deleteKVBulkKeyValue,
|
|
11
12
|
} from "./kv";
|
|
12
13
|
import { logger } from "./logger";
|
|
13
14
|
import type { Config } from "./config";
|
|
@@ -75,14 +76,14 @@ async function createKVNamespaceIfNotAlreadyExisting(
|
|
|
75
76
|
) {
|
|
76
77
|
// check if it already exists
|
|
77
78
|
// TODO: this is super inefficient, should be made better
|
|
78
|
-
const namespaces = await
|
|
79
|
+
const namespaces = await listKVNamespaces(accountId);
|
|
79
80
|
const found = namespaces.find((x) => x.title === title);
|
|
80
81
|
if (found) {
|
|
81
82
|
return { created: false, id: found.id };
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
// else we make the namespace
|
|
85
|
-
const id = await
|
|
86
|
+
const id = await createKVNamespace(accountId, title);
|
|
86
87
|
logger.log(`🌀 Created namespace for Workers Site "${title}"`);
|
|
87
88
|
|
|
88
89
|
return {
|
|
@@ -103,7 +104,7 @@ async function createKVNamespaceIfNotAlreadyExisting(
|
|
|
103
104
|
* asset in the KV namespace.
|
|
104
105
|
*/
|
|
105
106
|
export async function syncAssets(
|
|
106
|
-
accountId: string,
|
|
107
|
+
accountId: string | undefined,
|
|
107
108
|
scriptName: string,
|
|
108
109
|
siteAssets: AssetPaths | undefined,
|
|
109
110
|
preview: boolean,
|
|
@@ -120,6 +121,7 @@ export async function syncAssets(
|
|
|
120
121
|
logger.log("(Note: doing a dry run, not uploading or deleting anything.)");
|
|
121
122
|
return { manifest: undefined, namespace: undefined };
|
|
122
123
|
}
|
|
124
|
+
assert(accountId, "Missing accountId");
|
|
123
125
|
|
|
124
126
|
const title = `__${scriptName}-workers_sites_assets${
|
|
125
127
|
preview ? "_preview" : ""
|
|
@@ -131,7 +133,7 @@ export async function syncAssets(
|
|
|
131
133
|
);
|
|
132
134
|
|
|
133
135
|
// let's get all the keys in this namespace
|
|
134
|
-
const namespaceKeysResponse = await
|
|
136
|
+
const namespaceKeysResponse = await listKVNamespaceKeys(accountId, namespace);
|
|
135
137
|
const namespaceKeys = new Set(namespaceKeysResponse.map((x) => x.name));
|
|
136
138
|
|
|
137
139
|
const manifest: Record<string, string> = {};
|
|
@@ -185,9 +187,9 @@ export async function syncAssets(
|
|
|
185
187
|
|
|
186
188
|
await Promise.all([
|
|
187
189
|
// upload all the new assets
|
|
188
|
-
|
|
190
|
+
putKVBulkKeyValue(accountId, namespace, toUpload, () => {}),
|
|
189
191
|
// delete all the unused assets
|
|
190
|
-
|
|
192
|
+
deleteKVBulkKeyValue(
|
|
191
193
|
accountId,
|
|
192
194
|
namespace,
|
|
193
195
|
Array.from(namespaceKeys),
|
package/src/user.tsx
CHANGED
|
@@ -214,6 +214,7 @@ import path from "node:path";
|
|
|
214
214
|
import url from "node:url";
|
|
215
215
|
import { TextEncoder } from "node:util";
|
|
216
216
|
import TOML from "@iarna/toml";
|
|
217
|
+
import { HostURL } from "@webcontainer/env";
|
|
217
218
|
import { render, Text } from "ink";
|
|
218
219
|
import SelectInput from "ink-select-input";
|
|
219
220
|
import Table from "ink-table";
|
|
@@ -339,9 +340,18 @@ export function validateScopeKeys(
|
|
|
339
340
|
const CLIENT_ID = "54d11594-84e4-41aa-b438-e81b8fa78ee7";
|
|
340
341
|
const AUTH_URL = "https://dash.cloudflare.com/oauth2/auth";
|
|
341
342
|
const TOKEN_URL = "https://dash.cloudflare.com/oauth2/token";
|
|
342
|
-
const CALLBACK_URL = "http://localhost:8976/oauth/callback";
|
|
343
343
|
const REVOKE_URL = "https://dash.cloudflare.com/oauth2/revoke";
|
|
344
344
|
|
|
345
|
+
/**
|
|
346
|
+
* To allow OAuth callbacks in environments such as WebContainer we need to
|
|
347
|
+
* create a host URL which only resolves `localhost` to a WebContainer
|
|
348
|
+
* hostname if the process is running in a WebContainer. On local this will
|
|
349
|
+
* be a no-op and it leaves the URL unmodified.
|
|
350
|
+
*
|
|
351
|
+
* @see https://www.npmjs.com/package/@webcontainer/env
|
|
352
|
+
*/
|
|
353
|
+
const CALLBACK_URL = HostURL.parse("http://localhost:8976/oauth/callback").href;
|
|
354
|
+
|
|
345
355
|
let LocalState: State = getAuthTokens();
|
|
346
356
|
|
|
347
357
|
/**
|
package/src/worker.ts
CHANGED
|
@@ -105,6 +105,7 @@ interface CfDurableObject {
|
|
|
105
105
|
name: string;
|
|
106
106
|
class_name: string;
|
|
107
107
|
script_name?: string;
|
|
108
|
+
environment?: string;
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
interface CfR2Bucket {
|
|
@@ -112,6 +113,12 @@ interface CfR2Bucket {
|
|
|
112
113
|
bucket_name: string;
|
|
113
114
|
}
|
|
114
115
|
|
|
116
|
+
interface CfService {
|
|
117
|
+
binding: string;
|
|
118
|
+
service: string;
|
|
119
|
+
environment?: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
115
122
|
interface CfUnsafeBinding {
|
|
116
123
|
name: string;
|
|
117
124
|
type: string;
|
|
@@ -157,6 +164,7 @@ export interface CfWorkerInit {
|
|
|
157
164
|
data_blobs: CfDataBlobBindings | undefined;
|
|
158
165
|
durable_objects: { bindings: CfDurableObject[] } | undefined;
|
|
159
166
|
r2_buckets: CfR2Bucket[] | undefined;
|
|
167
|
+
services: CfService[] | undefined;
|
|
160
168
|
unsafe: CfUnsafeBinding[] | undefined;
|
|
161
169
|
};
|
|
162
170
|
migrations: CfDurableObjectMigrations | undefined;
|